From 8b50bbd05d9e6567ca5881336d1c8b4c7ab2f8e3 Mon Sep 17 00:00:00 2001 From: lorenzo merici Date: Wed, 9 Apr 2025 11:07:42 +0200 Subject: [PATCH 1/2] cross account and region support --- modules/integrations/cloud-logs/README.md | 14 ++++- modules/integrations/cloud-logs/main.tf | 54 +++++++++++++++---- modules/integrations/cloud-logs/outputs.tf | 2 +- .../templates/stackset_template_body.tpl | 35 ++++++++++++ 4 files changed, 92 insertions(+), 13 deletions(-) diff --git a/modules/integrations/cloud-logs/README.md b/modules/integrations/cloud-logs/README.md index 33182ba..7442387 100644 --- a/modules/integrations/cloud-logs/README.md +++ b/modules/integrations/cloud-logs/README.md @@ -14,13 +14,16 @@ The following resources will be created based on the deployment scenario: - SNS Topic and Subscription for CloudTrail notifications 3. For organizational cross-account deployments: - - A CloudFormation StackSet that deploys an IAM role directly in the bucket account + - A CloudFormation StackSet that deploys: + - An IAM role in the bucket account (in the current region) + - An SNS subscription in the topic account (in the topic's region) - The role in the bucket account allows Sysdig to access S3 data directly - - SNS Topic and Subscription for CloudTrail notifications + - The SNS subscription forwards CloudTrail notifications to Sysdig Additional features include: - Support for KMS-encrypted S3 buckets by granting the necessary KMS decryption permissions - Support for AWS GovCloud deployments +- Support for cross-region deployments where the S3 bucket and SNS topic are in different regions ## Important Notes for Cross-Account Access @@ -34,6 +37,13 @@ For KMS-encrypted S3 buckets, this module configures the necessary decrypt permi 2. For cross-account scenarios, specify the bucket account ID using the `bucket_account_id` variable 3. Ensure the KMS key policy allows the created role to use the decrypt operation +### Cross-Region Deployments + +This module supports deployments where the S3 bucket and SNS topic are in different regions: +- The IAM role will be created in the current region but can access the S3 bucket regardless of its region +- The SNS subscription will be created in the same region as the SNS topic +- The StackSet will deploy to both regions as needed + ## Requirements diff --git a/modules/integrations/cloud-logs/main.tf b/modules/integrations/cloud-logs/main.tf index 2b01d2b..d2760ca 100644 --- a/modules/integrations/cloud-logs/main.tf +++ b/modules/integrations/cloud-logs/main.tf @@ -46,17 +46,24 @@ data "sysdig_secure_cloud_ingestion_assets" "assets" { locals { trusted_identity = var.is_gov_cloud_onboarding ? data.sysdig_secure_trusted_cloud_identity.trusted_identity.gov_identity : data.sysdig_secure_trusted_cloud_identity.trusted_identity.identity - topic_name = split(":", var.topic_arn)[5] - topic_region = split(":", var.topic_arn)[3] + routing_key = data.sysdig_secure_cloud_ingestion_assets.assets.aws.sns_routing_key ingestion_url = data.sysdig_secure_cloud_ingestion_assets.assets.aws.sns_routing_url - - # Determine bucket owner account ID - use provided value or default to current account + + # Topic variables + topic_name = split(":", var.topic_arn)[5] + topic_region = split(":", var.topic_arn)[3] + topic_account_id = split(":", var.topic_arn)[4] + is_cross_account_topic = local.topic_account_id != data.aws_caller_identity.current.account_id + + # Bucket variables bucket_account_id = var.bucket_account_id != null ? var.bucket_account_id : data.aws_caller_identity.current.account_id - - # Flag for cross-account bucket access is_cross_account = var.bucket_account_id != null && var.bucket_account_id != data.aws_caller_identity.current.account_id + # KMS variables + kms_account_id = split(":", var.kms_key_arn)[3] + need_kms_policy = var.bucket_account_id != null && var.bucket_account_id != local.kms_account_id + account_id_hash = substr(md5(local.bucket_account_id), 0, 4) role_name = "${var.name}-${random_id.suffix.hex}-${local.account_id_hash}" @@ -156,14 +163,14 @@ provider aws { } resource "aws_sns_topic" "cloudtrail_notifications" { - count = var.create_topic ? 1 : 0 + count = var.create_topic && !local.is_cross_account_topic ? 1 : 0 provider = aws.sns name = local.topic_name tags = var.tags } resource "aws_sns_topic_policy" "cloudtrail_notifications" { - count = var.create_topic ? 1 : 0 + count = var.create_topic && !local.is_cross_account_topic ? 1 : 0 provider = aws.sns arn = aws_sns_topic.cloudtrail_notifications[0].arn policy = jsonencode({ @@ -183,6 +190,7 @@ resource "aws_sns_topic_policy" "cloudtrail_notifications" { } resource "aws_sns_topic_subscription" "cloudtrail_notifications" { + count = !local.is_cross_account_topic ? 1 : 0 topic_arn = var.topic_arn provider = aws.sns protocol = "https" @@ -207,9 +215,12 @@ resource "aws_cloudformation_stack_set" "cloudlogs_s3_access" { parameters = { RoleName = local.role_name BucketAccountId = local.bucket_account_id + TopicAccountId = local.topic_account_id SysdigTrustedIdentity = local.trusted_identity SysdigExternalId = data.sysdig_secure_tenant_external_id.external_id.external_id KmsKeyArn = var.kms_key_arn + TopicArn = var.topic_arn + IngestionUrl = local.ingestion_url } permission_model = "SERVICE_MANAGED" @@ -229,7 +240,8 @@ resource "aws_cloudformation_stack_set" "cloudlogs_s3_access" { tags = var.tags } -resource "aws_cloudformation_stack_set_instance" "cloudlogs_s3_access" { +# StackSet instance for the bucket account +resource "aws_cloudformation_stack_set_instance" "cloudlogs_s3_access_bucket" { count = local.is_cross_account ? 1 : 0 stack_set_name = aws_cloudformation_stack_set.cloudlogs_s3_access[0].name @@ -249,6 +261,27 @@ resource "aws_cloudformation_stack_set_instance" "cloudlogs_s3_access" { } } +# StackSet instance for the topic account +resource "aws_cloudformation_stack_set_instance" "cloudlogs_s3_access_topic" { + count = local.is_cross_account ? 1 : 0 + + stack_set_name = aws_cloudformation_stack_set.cloudlogs_s3_access[0].name + + deployment_targets { + organizational_unit_ids = var.org_units + account_filter_type = "INTERSECTION" + accounts = [local.topic_account_id] + } + + region = local.topic_region + + timeouts { + create = var.timeout + update = var.timeout + delete = var.timeout + } +} + #----------------------------------------------------------------------------------------------------------------------------------------- # Call Sysdig Backend to add the cloud logs integration #----------------------------------------------------------------------------------------------------------------------------------------- @@ -272,6 +305,7 @@ resource "sysdig_secure_cloud_auth_account_component" "aws_cloud_logs" { depends_on = [ aws_iam_role.cloudlogs_s3_access, - aws_cloudformation_stack_set_instance.cloudlogs_s3_access + aws_cloudformation_stack_set_instance.cloudlogs_s3_access_bucket, + aws_cloudformation_stack_set_instance.cloudlogs_s3_access_topic ] } diff --git a/modules/integrations/cloud-logs/outputs.tf b/modules/integrations/cloud-logs/outputs.tf index 93405fa..d6251c1 100644 --- a/modules/integrations/cloud-logs/outputs.tf +++ b/modules/integrations/cloud-logs/outputs.tf @@ -6,7 +6,7 @@ output "cloud_logs_component_id" { output "kms_policy_instructions" { description = "Instructions for updating KMS key policy when KMS encryption is enabled" - value = (var.kms_key_arn != null) ? templatefile( + value = (local.need_kms_policy) ? templatefile( "${path.module}/templates/kms_policy_instructions.tpl", { role_arn = "arn:${data.aws_partition.current.partition}:iam::${local.bucket_account_id}:role/${local.role_name}" diff --git a/modules/integrations/cloud-logs/templates/stackset_template_body.tpl b/modules/integrations/cloud-logs/templates/stackset_template_body.tpl index 2737787..117d37b 100644 --- a/modules/integrations/cloud-logs/templates/stackset_template_body.tpl +++ b/modules/integrations/cloud-logs/templates/stackset_template_body.tpl @@ -10,6 +10,10 @@ "Type": "String", "Description": "The account id that the bucket resides in" }, + "TopicAccountId": { + "Type": "String", + "Description": "The account id that the topic resides in" + }, "SysdigTrustedIdentity": { "Type": "String", "Description": "ARN of the Sysdig service that needs to assume the role" @@ -21,6 +25,14 @@ "KmsKeyArn": { "Type": "String", "Description": "ARN of the KMS key used for encryption" + }, + "TopicArn": { + "Type": "String", + "Description": "ARN of the SNS topic for CloudTrail notifications" + }, + "IngestionUrl": { + "Type": "String", + "Description": "URL for Sysdig's ingestion endpoint" } }, "Conditions": { @@ -34,6 +46,16 @@ } ] }, + "IsTopicAccount": { + "Fn::Equals": [ + { + "Ref": "AWS::AccountId" + }, + { + "Ref": "TopicAccountId" + } + ] + }, "HasKMSKey": { "Fn::Not": [ { @@ -127,6 +149,19 @@ } ] } + }, + "CloudTrailSNSSubscription": { + "Type": "AWS::SNS::Subscription", + "Condition": "IsTopicAccount", + "Properties": { + "TopicArn": { + "Ref": "TopicArn" + }, + "Protocol": "https", + "Endpoint": { + "Ref": "IngestionUrl" + } + } } }, "Outputs": { From e052a06d5bbb4b505592b68477249f82a53113f4 Mon Sep 17 00:00:00 2001 From: lorenzo merici Date: Wed, 9 Apr 2025 11:14:44 +0200 Subject: [PATCH 2/2] simplify conditions --- modules/integrations/cloud-logs/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/integrations/cloud-logs/main.tf b/modules/integrations/cloud-logs/main.tf index d2760ca..902af42 100644 --- a/modules/integrations/cloud-logs/main.tf +++ b/modules/integrations/cloud-logs/main.tf @@ -163,14 +163,14 @@ provider aws { } resource "aws_sns_topic" "cloudtrail_notifications" { - count = var.create_topic && !local.is_cross_account_topic ? 1 : 0 + count = var.create_topic ? 1 : 0 provider = aws.sns name = local.topic_name tags = var.tags } resource "aws_sns_topic_policy" "cloudtrail_notifications" { - count = var.create_topic && !local.is_cross_account_topic ? 1 : 0 + count = var.create_topic ? 1 : 0 provider = aws.sns arn = aws_sns_topic.cloudtrail_notifications[0].arn policy = jsonencode({