diff --git a/modules/config-posture/versions.tf b/modules/config-posture/versions.tf index 48ddb05..16d9ab5 100644 --- a/modules/config-posture/versions.tf +++ b/modules/config-posture/versions.tf @@ -7,7 +7,7 @@ terraform { } sysdig = { source = "sysdiglabs/sysdig" - version = "~> 1.48" + version = "~> 3.3" } } } diff --git a/modules/integrations/cloud-logs/versions.tf b/modules/integrations/cloud-logs/versions.tf index 7bb98df..51e686b 100644 --- a/modules/integrations/cloud-logs/versions.tf +++ b/modules/integrations/cloud-logs/versions.tf @@ -9,7 +9,7 @@ terraform { } sysdig = { source = "sysdiglabs/sysdig" - version = "~> 1.56" + version = "~> 3.3" } random = { source = "hashicorp/random" diff --git a/modules/integrations/cross-account-event-bridge/versions.tf b/modules/integrations/cross-account-event-bridge/versions.tf index 09c0911..6456bf0 100644 --- a/modules/integrations/cross-account-event-bridge/versions.tf +++ b/modules/integrations/cross-account-event-bridge/versions.tf @@ -7,7 +7,7 @@ terraform { } sysdig = { source = "sysdiglabs/sysdig" - version = "~> 1.48" + version = "~> 3.3" } random = { source = "hashicorp/random" diff --git a/modules/integrations/event-bridge/versions.tf b/modules/integrations/event-bridge/versions.tf index b543f52..6456bf0 100644 --- a/modules/integrations/event-bridge/versions.tf +++ b/modules/integrations/event-bridge/versions.tf @@ -7,7 +7,7 @@ terraform { } sysdig = { source = "sysdiglabs/sysdig" - version = "~> 1.51" + version = "~> 3.3" } random = { source = "hashicorp/random" diff --git a/modules/onboarding/versions.tf b/modules/onboarding/versions.tf index db226f1..4764e74 100644 --- a/modules/onboarding/versions.tf +++ b/modules/onboarding/versions.tf @@ -11,7 +11,7 @@ terraform { } sysdig = { source = "sysdiglabs/sysdig" - version = "~> 1.48" + version = "~> 3.3" } } } diff --git a/modules/response-actions/README.md b/modules/response-actions/README.md new file mode 100644 index 0000000..e374330 --- /dev/null +++ b/modules/response-actions/README.md @@ -0,0 +1,12 @@ +For testing, create `provisioning.tf`, with: + +``` +provider "sysdig" { + sysdig_secure_url = "" + sysdig_secure_api_token = "" +} + +provider "aws" { + region = "us-east-1" +} +``` diff --git a/modules/response-actions/locals.tf b/modules/response-actions/locals.tf new file mode 100644 index 0000000..e81b12e --- /dev/null +++ b/modules/response-actions/locals.tf @@ -0,0 +1,182 @@ +#----------------------------------------------------------------------------------------------------------------------------------------- +# Organizational Deployment Configuration +# +# This file contains the logic for determining which organizational units (OUs) and accounts to deploy Response Actions to +# in an AWS Organization deployment. +# +# The module supports flexible inclusion/exclusion patterns: +# - include_ouids/exclude_ouids: Include or exclude entire organizational units +# - include_accounts/exclude_accounts: Include or exclude specific accounts +# +# Key behavior: +# 1. Inclusions are always prioritized over exclusions +# 2. The management account is automatically excluded from delegate role deployment (roles exist there already) +# 3. Due to AWS CloudFormation limitations with UNION filters, when including specific accounts, the stackset +# deploys to the entire org (with account filtering handled by Sysdig backend) +# +#----------------------------------------------------------------------------------------------------------------------------------------- + +#---------------------------------------------------------- +# Fetch & compute required data for organizational install +#---------------------------------------------------------- + +data "aws_organizations_organization" "org" { + count = var.is_organizational ? 1 : 0 +} + +locals { + # fetch the AWS Root OU under org + # As per https://docs.aws.amazon.com/organizations/latest/userguide/orgs_getting-started_concepts.html#organization-structure, there can be only one root + root_org_unit = var.is_organizational ? [for root in data.aws_organizations_organization.org[0].roots : root.id] : [] +} + +# ***************************************************************************************************************************************************** +# INCLUDE/EXCLUDE CONFIGURATION SUPPORT +# +# 1. Inclusions will always be handled for TF cloud provisioning. +# NOTE: +# Till AWS issue with UNION filter (https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-cloudformation/issues/100) +# is fixed, we can't deploy using UNION filters for inclusions. As a workaround to ensure we don't skip any accounts, we deploy it to entire org. +# +# 2. We handle exclusions when only exclusion parameters are provided i.e out of all 4 configuration inputs, +# a. only exclude_ouids are provided, OR +# b. only exclude_accounts are provided, OR +# c. only exclude_ouids AND exclude_accounts are provided +# Else we ignore exclusions during cloud resource provisioning through TF. This is because AWS does not allow both operations - to include some +# accounts and to exclude some. Hence, we will always prioritize include over exclude. +# +# 3. Sysdig however will honor all combinations of configuration inputs exactly as desired. +# ***************************************************************************************************************************************************** + +#------------------------------------------------------------ +# Manage configurations to determine OU targets to deploy in +#------------------------------------------------------------ + +locals { + # OU CONFIGURATION (determine user provided org configuration) + org_configuration = ( + # case1 - if no include/exclude ous provided, include entire org + var.is_organizational && length(var.include_ouids) == 0 && length(var.exclude_ouids) == 0 ? ( + "entire_org" + ) : ( + # case2 - if only included ouids provided, include those ous only + var.is_organizational && length(var.include_ouids) > 0 && length(var.exclude_ouids) == 0 ? ( + "included_ous_only" + ) : ( + # case3 - if only excluded ouids provided, exclude their accounts from rest of org + var.is_organizational && length(var.include_ouids) == 0 && length(var.exclude_ouids) > 0 ? ( + "excluded_ous_only" + ) : ( + # case4 - if both include and exclude ouids are provided, includes override excludes + var.is_organizational && length(var.include_ouids) > 0 && length(var.exclude_ouids) > 0 ? ( + "mixed_ous" + ) : "" + ) + ) + ) + ) + + # switch cases for various user provided org configuration to be onboarded + deployment_options = { + entire_org = { + org_units_to_deploy = local.root_org_unit + } + included_ous_only = { + org_units_to_deploy = var.include_ouids + } + excluded_ous_only = { + # onboard entire org and filter out all accounts in excluded OUs using account filter + org_units_to_deploy = local.root_org_unit + } + mixed_ous = { + # if both include and exclude ouids are provided, includes override excludes + org_units_to_deploy = var.include_ouids + } + default = { + org_units_to_deploy = local.root_org_unit + } + } + + # final targets to deploy organizational resources in + deployment_targets_ous = lookup(local.deployment_options, local.org_configuration, local.deployment_options.default) + + exclude_root_ou = length(local.root_org_unit) > 0 ? contains(var.exclude_ouids, local.root_org_unit[0]) : false +} + +#----------------------------------------------------------------- +# Manage configurations to determine account targets to deploy in +#----------------------------------------------------------------- + +# if only exclude_ouids are provided and as long as it isn't Root OU, fetch all their child accounts to filter exclusions +data "aws_organizations_organizational_unit_descendant_accounts" "ou_accounts_to_exclude" { + for_each = local.org_configuration == "excluded_ous_only" && !local.exclude_root_ou ? var.exclude_ouids : [] + parent_id = each.key +} + +locals { + # ACCOUNTS CONFIGURATION (determine user provided accounts configuration) + accounts_configuration = ( + # case1 - if only included accounts provided, include those accts as well + var.is_organizational && length(var.include_accounts) > 0 && length(var.exclude_accounts) == 0 ? ( + "UNION" + ) : ( + # case2 - if only excluded accounts or only excluded ouids provided, exclude those accounts + var.is_organizational && length(var.include_accounts) == 0 && (length(var.exclude_accounts) > 0 || local.org_configuration == "excluded_ous_only") ? ( + "DIFFERENCE" + ) : ( + # case3 - if both include and exclude accounts are provided, includes override excludes + var.is_organizational && length(var.include_accounts) > 0 && length(var.exclude_accounts) > 0 ? ( + "MIXED" + # case4 - if no explicit include/exclude, we still need to exclude current account + ) : var.is_organizational ? ( + "DIFFERENCE" + ) : "" + ) + ) + ) + + ou_accounts_to_exclude = flatten([for ou_accounts in data.aws_organizations_organizational_unit_descendant_accounts.ou_accounts_to_exclude : [ou_accounts.accounts[*].id]]) + # Always exclude the current account (management account) from StackSet deployment since the Lambda execution roles + # already exist in the management account. Delegate roles are only needed in member accounts. + accounts_to_exclude = setunion(local.ou_accounts_to_exclude, var.exclude_accounts, [data.aws_caller_identity.current.account_id]) + + # switch cases for various user provided accounts configuration to be onboarded + deployment_account_options = { + NONE = { + accounts_to_deploy = [] + account_filter_type = "NONE" + } + UNION = { + accounts_to_deploy = var.include_accounts + account_filter_type = "UNION" + } + DIFFERENCE = { + accounts_to_deploy = local.accounts_to_exclude + account_filter_type = "DIFFERENCE" + } + MIXED = { + accounts_to_deploy = var.include_accounts + account_filter_type = "UNION" + } + default = { + # default when neither of include/exclude accounts are provided + accounts_to_deploy = [] + account_filter_type = "NONE" + } + } + + # list of accounts to deploy organizational resources in + deployment_targets_accounts = lookup(local.deployment_account_options, local.accounts_configuration, local.deployment_account_options.default) +} + +# ----------------------------------------------------------------------------------------------------- +# Remove below conditional once AWS issue is fixed - +# https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-cloudformation/issues/100 +# ----------------------------------------------------------------------------------------------------- +locals { + # XXX: due to AWS bug of not having UNION filter fully working, there is no way to add those extra accounts requested. + # to not miss out on those extra accounts, deploy the cloud resources across entire org and noop the UNION filter. + # i.e till we can't deploy UNION, we deploy it all + deployment_targets_org_units = local.deployment_targets_accounts.account_filter_type == "UNION" ? local.root_org_unit : local.deployment_targets_ous.org_units_to_deploy + deployment_targets_accounts_filter = local.deployment_targets_accounts.account_filter_type == "UNION" ? "NONE" : local.deployment_targets_accounts.account_filter_type +} diff --git a/modules/response-actions/main.tf b/modules/response-actions/main.tf new file mode 100644 index 0000000..d114db9 --- /dev/null +++ b/modules/response-actions/main.tf @@ -0,0 +1,749 @@ +#----------------------------------------------------------------------------------------------------------------------------------------- +# This module deploys Sysdig Secure Response Actions for AWS, enabling automated security responses to detected threats. +# +# For both Single Account and Organizational installs, Lambda functions are deployed using CloudFormation StackSets. +# For Organizational installs, see organizational.tf. +# +# Response Actions include: +# - Quarantine User: Attaches a deny-all policy to IAM users to prevent further actions +# - Fetch Cloud Logs: Retrieves CloudTrail and CloudWatch logs +# - Make Private: Removes public access from S3 buckets and RDS instances +# - Create Volume Snapshot: Creates EBS volume snapshots for forensic investigation +# +# For single installs, the resources in this file instrument the singleton account (management or member account). +# For organizational installs, resources in this file are created in the management account, with delegate roles +# deployed to member accounts via service-managed stacksets. +#----------------------------------------------------------------------------------------------------------------------------------------- + +#----------------------------------------------------------------------------------------- +# Fetch the data sources +#----------------------------------------------------------------------------------------- +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} + +locals { + # Use provided regions or default to current region + region_set = length(var.regions) > 0 ? toset(var.regions) : toset([data.aws_region.current.id]) + 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 + arn_prefix = var.is_gov_cloud_onboarding ? "arn:aws-us-gov" : "arn:aws" + responder_component_type = "COMPONENT_CLOUD_RESPONDER" + roles_component_type = "COMPONENT_CLOUD_RESPONDER_ROLES" + account_id_hash = substr(md5(data.aws_caller_identity.current.account_id), 0, 4) + ra_resource_name = "${var.name}-${random_id.suffix.hex}-${local.account_id_hash}" + + # Centralized role names + quarantine_user_role_name = "${local.ra_resource_name}-quarantine-user-role" + fetch_cloud_logs_role_name = "${local.ra_resource_name}-fetch-cloud-logs-role" + remove_policy_role_name = "${local.ra_resource_name}-remove-policy-role" + configure_resource_access_role_name = "${local.ra_resource_name}-confi-res-access-role" + create_volume_snapshots_role_name = "${local.ra_resource_name}-create-vol-snap-role" + delete_volume_snapshots_role_name = "${local.ra_resource_name}-delete-vol-snap-role" + + # Centralized Lambda function names + quarantine_user_lambda_name = "${local.ra_resource_name}-quarantine-user" + fetch_cloud_logs_lambda_name = "${local.ra_resource_name}-fetch-cloud-logs" + remove_policy_lambda_name = "${local.ra_resource_name}-remove-policy" + configure_resource_access_lambda_name = "${local.ra_resource_name}-configure-resource-access" + create_volume_snapshots_lambda_name = "${local.ra_resource_name}-create-volume-snapshots" + delete_volume_snapshots_lambda_name = "${local.ra_resource_name}-delete-volume-snapshots" + + # Policy templates with role names + quarantine_user_policy = templatefile("${path.module}/policies/quarantine-user-policy.json", { role_name = local.quarantine_user_role_name }) + fetch_cloud_logs_policy = templatefile("${path.module}/policies/fetch-cloud-logs-policy.json", { role_name = local.fetch_cloud_logs_role_name }) + remove_policy_policy = templatefile("${path.module}/policies/remove-policy-policy.json", { role_name = local.remove_policy_role_name }) + configure_resource_access_policy = templatefile("${path.module}/policies/configure-resource-access-policy.json", { role_name = local.configure_resource_access_role_name }) + create_volume_snapshots_policy = templatefile("${path.module}/policies/create-volume-snapshots-policy.json", { role_name = local.create_volume_snapshots_role_name }) + delete_volume_snapshots_policy = templatefile("${path.module}/policies/delete-volume-snapshots-policy.json", { role_name = local.delete_volume_snapshots_role_name }) + + # StackSet role configuration + administration_role_arn = var.auto_create_stackset_roles ? aws_iam_role.lambda_stackset_admin_role[0].arn : var.stackset_admin_role_arn + execution_role_name = var.auto_create_stackset_roles ? aws_iam_role.lambda_stackset_execution_role[0].name : var.stackset_execution_role_name + + # S3 bucket configuration for Lambda packages + s3_bucket_name = "${local.ra_resource_name}-packages" + + # Response action enablement flags + enable_make_private = contains(var.enabled_response_actions, "make_private") + enable_fetch_cloud_logs = contains(var.enabled_response_actions, "fetch_cloud_logs") + enable_create_volume_snapshot = contains(var.enabled_response_actions, "create_volume_snapshot") + enable_quarantine_user = contains(var.enabled_response_actions, "quarantine_user") + + # Build list of Lambda ARNs for invoke policy based on enabled actions + enabled_lambda_arns = concat( + local.enable_quarantine_user ? [ + "${local.arn_prefix}:lambda:*:${data.aws_caller_identity.current.account_id}:function:${local.quarantine_user_lambda_name}", + "${local.arn_prefix}:lambda:*:${data.aws_caller_identity.current.account_id}:function:${local.remove_policy_lambda_name}" + ] : [], + local.enable_fetch_cloud_logs ? [ + "${local.arn_prefix}:lambda:*:${data.aws_caller_identity.current.account_id}:function:${local.fetch_cloud_logs_lambda_name}" + ] : [], + local.enable_make_private ? [ + "${local.arn_prefix}:lambda:*:${data.aws_caller_identity.current.account_id}:function:${local.configure_resource_access_lambda_name}" + ] : [], + local.enable_create_volume_snapshot ? [ + "${local.arn_prefix}:lambda:*:${data.aws_caller_identity.current.account_id}:function:${local.create_volume_snapshots_lambda_name}", + "${local.arn_prefix}:lambda:*:${data.aws_caller_identity.current.account_id}:function:${local.delete_volume_snapshots_lambda_name}" + ] : [] + ) + + # Build list of Lambda function names based on enabled actions + enabled_lambda_names = concat( + local.enable_quarantine_user ? [ + local.quarantine_user_lambda_name, + local.remove_policy_lambda_name + ] : [], + local.enable_fetch_cloud_logs ? [ + local.fetch_cloud_logs_lambda_name + ] : [], + local.enable_make_private ? [ + local.configure_resource_access_lambda_name + ] : [], + local.enable_create_volume_snapshot ? [ + local.create_volume_snapshots_lambda_name, + local.delete_volume_snapshots_lambda_name + ] : [] + ) +} + +#----------------------------------------------------------------------------------------------------------------------- +# A random resource is used to generate unique resource name suffix for Response Actions. +# This prevents conflicts when recreating Response Actions resources with the same name. +#----------------------------------------------------------------------------------------------------------------------- +resource "random_id" "suffix" { + byte_length = 3 +} + +#----------------------------------------------------------------------------------------------------------------------------------------- +# Self-managed stacksets require a pair of StackSetAdministrationRole & StackSetExecutionRole IAM roles with self-managed permissions. +# +# If auto_create_stackset_roles is true, terraform will create this IAM Admin role in the source account with permissions to create +# stacksets. If false, and values for stackset Admin role ARN is provided, stackset will use it, else AWS will look for +# predefined/default role. +#----------------------------------------------------------------------------------------------------------------------------------------- +# StackSet Administration Role +resource "aws_iam_role" "lambda_stackset_admin_role" { + count = var.auto_create_stackset_roles ? 1 : 0 + name = "${local.ra_resource_name}-stackset-admin" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = "cloudformation.amazonaws.com" + } + Action = "sts:AssumeRole" + } + ] + }) + + tags = { + Name = "${local.ra_resource_name}-stackset-admin" + "sysdig.com/response-actions/resource-name" = "stackset-admin" + } +} + +resource "aws_iam_role_policy" "lambda_stackset_admin_policy" { + count = var.auto_create_stackset_roles ? 1 : 0 + name = "${local.ra_resource_name}-stackset-admin-policy" + role = aws_iam_role.lambda_stackset_admin_role[0].id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = "sts:AssumeRole" + Resource = "${local.arn_prefix}:iam::*:role/${local.ra_resource_name}-stackset-execution" + } + ] + }) +} + +#----------------------------------------------------------------------------------------------------------------------------------------- +# Self-managed stacksets require a pair of StackSetAdministrationRole & StackSetExecutionRole IAM roles with self-managed permissions. +# +# If auto_create_stackset_roles is true, terraform will create this IAM Execution role in the source account with permissions to +# deploy Lambda functions, create IAM roles, and manage logs. This role is assumed by the StackSet Administration role. +# If false, and values for stackset Execution role name is provided, stackset will use it. +#----------------------------------------------------------------------------------------------------------------------------------------- +# StackSet Execution Role +resource "aws_iam_role" "lambda_stackset_execution_role" { + count = var.auto_create_stackset_roles ? 1 : 0 + name = "${local.ra_resource_name}-stackset-execution" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + AWS = var.auto_create_stackset_roles ? aws_iam_role.lambda_stackset_admin_role[0].arn : var.stackset_admin_role_arn + } + Action = "sts:AssumeRole" + } + ] + }) + + tags = { + Name = "${local.ra_resource_name}-stackset-execution" + "sysdig.com/response-actions/resource-name" = "stackset-execution" + } +} + +resource "aws_iam_role_policy" "lambda_stackset_execution_policy" { + count = var.auto_create_stackset_roles ? 1 : 0 + name = "${local.ra_resource_name}-stackset-execution-policy" + role = aws_iam_role.lambda_stackset_execution_role[0].id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "lambda:*", + "iam:CreateRole", + "iam:DeleteRole", + "iam:GetRole", + "iam:PassRole", + "iam:PutRolePolicy", + "iam:DeleteRolePolicy", + "iam:AttachRolePolicy", + "iam:DetachRolePolicy", + "iam:GetRolePolicy", + "iam:TagRole", + "iam:UntagRole", + "iam:ListRoleTags", + "logs:CreateLogGroup", + "logs:DeleteLogGroup", + "logs:PutRetentionPolicy", + "logs:TagResource", + "logs:UntagResource", + "logs:TagLogGroup", + "logs:ListTagsForResource", + "s3:CreateBucket", + "s3:DeleteBucket", + "s3:GetBucketLocation", + "s3:PutBucketPublicAccessBlock", + "s3:GetBucketPublicAccessBlock", + "s3:PutBucketTagging", + "s3:GetBucketTagging", + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject", + "s3:ListBucket" + ] + Resource = "*" + } + ] + }) +} + +resource "aws_iam_role_policy_attachment" "lambda_stackset_execution_cloudformation" { + count = var.auto_create_stackset_roles ? 1 : 0 + role = aws_iam_role.lambda_stackset_execution_role[0].name + policy_arn = "${local.arn_prefix}:iam::aws:policy/AWSCloudFormationFullAccess" +} + +data "sysdig_secure_trusted_cloud_identity" "trusted_identity" { + cloud_provider = "aws" +} + +#----------------------------------------------------------------------------------------------------------------------------------------- +# This resource creates an IAM role in the source account with permissions to invoke Response Action Lambda functions. +# This role is assumed by Sysdig's cloud identity to trigger automated response actions. +# +# The role allows: +# 1. Sysdig's trusted identity to assume the role using an external ID for security +# 2. Invoking Lambda functions across all deployed regions based on enabled response actions +# 3. Using AWS Resource Groups Tagging API to discover resources +#----------------------------------------------------------------------------------------------------------------------------------------- +resource "aws_iam_role" "shared_cross_account_lambda_invoker" { + name = "${local.ra_resource_name}-cross-account-invoker" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + AWS = local.trusted_identity + } + Condition = { + StringEquals = { + "sts:ExternalId" = "cloud-actions-lambda-invoke-access" + } + } + } + ] + }) + + tags = { + Name = "${local.ra_resource_name}-cross-account-invoker" + "sysdig.com/response-actions/resource-name" = "cross-account-invoker" + } +} + +#----------------------------------------------------------------------------------------------------------------------------------------- +# This policy grants the necessary permissions for the cross-account Lambda invoker role: +# 1. tag:GetResources - Allows discovering AWS resources by tags for response actions +# 2. lambda:InvokeFunction - Allows invoking enabled Response Action Lambda functions +# 3. lambda:GetFunction - Allows retrieving Lambda function details for validation +# +# The policy dynamically includes only the Lambda ARNs for enabled response actions, based on the +# enabled_response_actions variable configuration. +#----------------------------------------------------------------------------------------------------------------------------------------- +resource "aws_iam_role_policy" "shared_lambda_invoke_policy" { + name = "${local.ra_resource_name}-invoke-policy" + role = aws_iam_role.shared_cross_account_lambda_invoker.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = concat([ + { + Effect = "Allow" + Action = [ + "tag:GetResources" + ] + Resource = "*" + } + ], + length(local.enabled_lambda_arns) > 0 ? [{ + Effect = "Allow" + Action = [ + "lambda:InvokeFunction", + "lambda:GetFunction" + ] + Resource = local.enabled_lambda_arns + }] : [] + ) + }) +} + +#----------------------------------------------------------------------------------------------------------------------------------------- +# IAM Roles for Lambda Functions (Created Once in Management Account) +# +# These roles are created globally in the management account and are used by Lambda functions +# across all regions. Each role is tagged with 'sysdig.com/response-actions/cloud-actions = true' for identification. +# +# The roles grant specific permissions needed for each response action type: +# - Quarantine User: IAM policy and user management permissions +# - Fetch Cloud Logs: CloudTrail and CloudWatch Logs read access +# - Remove Policy: IAM policy detachment permissions +# - Configure Resource Access: S3 and EC2 security group modification permissions +# - Create/Delete Volume Snapshots: EBS snapshot management permissions +#----------------------------------------------------------------------------------------------------------------------------------------- + +# Lambda Execution Role: Quarantine User +resource "aws_iam_role" "quarantine_user_role" { + count = local.enable_quarantine_user ? 1 : 0 + name = local.quarantine_user_role_name + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + Action = "sts:AssumeRole" + } + ] + }) + + tags = { + Name = local.quarantine_user_role_name + "sysdig.com/response-actions/cloud-actions" = "true" + "sysdig.com/response-actions/resource-name" = "quarantine-user-role" + } +} + +resource "aws_iam_role_policy_attachment" "quarantine_user_basic" { + count = local.enable_quarantine_user ? 1 : 0 + policy_arn = "${local.arn_prefix}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + role = aws_iam_role.quarantine_user_role[0].name +} + +resource "aws_iam_role_policy" "quarantine_user_policy" { + count = local.enable_quarantine_user ? 1 : 0 + name = "${local.ra_resource_name}-quarantine-user-policy" + role = aws_iam_role.quarantine_user_role[0].id + policy = local.quarantine_user_policy +} + +# Lambda Execution Role: Fetch Cloud Logs +resource "aws_iam_role" "fetch_cloud_logs_role" { + count = local.enable_fetch_cloud_logs ? 1 : 0 + name = local.fetch_cloud_logs_role_name + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + Action = "sts:AssumeRole" + } + ] + }) + + tags = { + Name = local.fetch_cloud_logs_role_name + "sysdig.com/response-actions/cloud-actions" = "true" + "sysdig.com/response-actions/resource-name" = "fetch-cloud-logs-role" + } +} + +resource "aws_iam_role_policy_attachment" "fetch_cloud_logs_basic" { + count = local.enable_fetch_cloud_logs ? 1 : 0 + policy_arn = "${local.arn_prefix}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + role = aws_iam_role.fetch_cloud_logs_role[0].name +} + +resource "aws_iam_role_policy" "fetch_cloud_logs_policy" { + count = local.enable_fetch_cloud_logs ? 1 : 0 + name = "${local.ra_resource_name}-fetch-cloud-logs-policy" + role = aws_iam_role.fetch_cloud_logs_role[0].id + policy = local.fetch_cloud_logs_policy +} + +# Lambda Execution Role: Remove Policy +resource "aws_iam_role" "remove_policy_role" { + count = local.enable_quarantine_user ? 1 : 0 + name = local.remove_policy_role_name + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + Action = "sts:AssumeRole" + } + ] + }) + + tags = { + Name = local.remove_policy_role_name + "sysdig.com/response-actions/cloud-actions" = "true" + "sysdig.com/response-actions/resource-name" = "remove-policy-role" + } +} + +resource "aws_iam_role_policy_attachment" "remove_policy_basic" { + count = local.enable_quarantine_user ? 1 : 0 + policy_arn = "${local.arn_prefix}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + role = aws_iam_role.remove_policy_role[0].name +} + +resource "aws_iam_role_policy" "remove_policy_policy" { + count = local.enable_quarantine_user ? 1 : 0 + name = "${local.ra_resource_name}-remove-policy-policy" + role = aws_iam_role.remove_policy_role[0].id + policy = local.remove_policy_policy +} + +# Lambda Execution Role: Configure Resource Access +resource "aws_iam_role" "configure_resource_access_role" { + count = local.enable_make_private ? 1 : 0 + name = local.configure_resource_access_role_name + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + Action = "sts:AssumeRole" + } + ] + }) + + tags = { + Name = local.configure_resource_access_role_name + "sysdig.com/response-actions/cloud-actions" = "true" + "sysdig.com/response-actions/resource-name" = "configure-resource-access-role" + } +} + +resource "aws_iam_role_policy_attachment" "configure_resource_access_basic" { + count = local.enable_make_private ? 1 : 0 + policy_arn = "${local.arn_prefix}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + role = aws_iam_role.configure_resource_access_role[0].name +} + +resource "aws_iam_role_policy" "configure_resource_access_policy" { + count = local.enable_make_private ? 1 : 0 + name = "${local.ra_resource_name}-configure-resource-access-policy" + role = aws_iam_role.configure_resource_access_role[0].id + policy = local.configure_resource_access_policy +} + +# Lambda Execution Role: Create Volume Snapshots +resource "aws_iam_role" "create_volume_snapshots_role" { + count = local.enable_create_volume_snapshot ? 1 : 0 + name = local.create_volume_snapshots_role_name + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + Action = "sts:AssumeRole" + } + ] + }) + + tags = { + Name = local.create_volume_snapshots_role_name + "sysdig.com/response-actions/cloud-actions" = "true" + "sysdig.com/response-actions/resource-name" = "create-volume-snapshots-role" + } +} + +resource "aws_iam_role_policy_attachment" "create_volume_snapshots_basic" { + count = local.enable_create_volume_snapshot ? 1 : 0 + policy_arn = "${local.arn_prefix}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + role = aws_iam_role.create_volume_snapshots_role[0].name +} + +resource "aws_iam_role_policy" "create_volume_snapshots_policy" { + count = local.enable_create_volume_snapshot ? 1 : 0 + name = "${local.ra_resource_name}-create-volume-snapshots-policy" + role = aws_iam_role.create_volume_snapshots_role[0].id + policy = local.create_volume_snapshots_policy +} + +# Lambda Execution Role: Delete Volume Snapshots +resource "aws_iam_role" "delete_volume_snapshots_role" { + count = local.enable_create_volume_snapshot ? 1 : 0 + name = local.delete_volume_snapshots_role_name + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + Action = "sts:AssumeRole" + } + ] + }) + + tags = { + Name = local.delete_volume_snapshots_role_name + "sysdig.com/response-actions/cloud-actions" = "true" + "sysdig.com/response-actions/resource-name" = "delete-volume-snapshots-role" + } +} + +resource "aws_iam_role_policy_attachment" "delete_volume_snapshots_basic" { + count = local.enable_create_volume_snapshot ? 1 : 0 + policy_arn = "${local.arn_prefix}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + role = aws_iam_role.delete_volume_snapshots_role[0].name +} + +resource "aws_iam_role_policy" "delete_volume_snapshots_policy" { + count = local.enable_create_volume_snapshot ? 1 : 0 + name = "${local.ra_resource_name}-delete-volume-snapshots-policy" + role = aws_iam_role.delete_volume_snapshots_role[0].id + policy = local.delete_volume_snapshots_policy +} + +# Lambda Execution Role: Package Downloader (Global, used by all regions) +resource "aws_iam_role" "package_downloader_role" { + name = "${local.ra_resource_name}-package-downloader-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + Action = "sts:AssumeRole" + } + ] + }) + + tags = { + Name = "${local.ra_resource_name}-package-downloader-role" + "sysdig.com/response-actions/resource-name" = "package-downloader-role" + } +} + +resource "aws_iam_role_policy_attachment" "package_downloader_basic" { + policy_arn = "${local.arn_prefix}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + role = aws_iam_role.package_downloader_role.name +} + +resource "aws_iam_role_policy" "package_downloader_policy" { + name = "${local.ra_resource_name}-package-downloader-policy" + role = aws_iam_role.package_downloader_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "s3:PutObject", + "s3:DeleteObject", + "s3:GetObject" + ] + Resource = "${local.arn_prefix}:s3:::${local.s3_bucket_name}-*/*" + }, + { + Effect = "Allow" + Action = [ + "s3:ListBucket" + ] + Resource = "${local.arn_prefix}:s3:::${local.s3_bucket_name}-*" + } + ] + }) +} + +# Wait for IAM role to propagate globally before using it +resource "time_sleep" "wait_for_iam_role_propagation" { + depends_on = [ + aws_iam_role.package_downloader_role, + aws_iam_role_policy.package_downloader_policy, + aws_iam_role_policy_attachment.package_downloader_basic + ] + + create_duration = "10s" +} + +#----------------------------------------------------------------------------------------------------------------------------------------- +# S3 Bucket for Lambda deployment packages +# +# The CloudFormation StackSet will create regional S3 buckets automatically in each deployed region. +# The bucket naming follows the pattern: {s3_bucket_name}-{region} +# +# A custom Lambda function (PackageDownloaderFunction) will download the Lambda zip files from the provided +# lambda_packages_base_url and upload them to the regional S3 buckets. The packages are downloaded from: +# {lambda_packages_base_url}/v{version}/{lambda_name}.zip +# +# Lambda packages downloaded: +# - quarantine_user.zip +# - fetch_cloud_logs.zip +# - remove_policy.zip +# - configure_resource_access.zip +# - create_volume_snapshots.zip +# - delete_volume_snapshots.zip +#----------------------------------------------------------------------------------------------------------------------------------------- + +#----------------------------------------------------------------------------------------------------------------------------------------- +# This resource creates a stackset to deploy Response Action Lambda functions across multiple regions. +# +# The stackset creates Lambda functions in each specified region with the following configuration: +# 1. Lambda Functions - One per enabled response action, deployed from regional S3 buckets +# 2. CloudWatch Log Groups - For Lambda execution logs with retention policies +# 3. Function Configuration - Environment variables including API base URL and resource names +# +# The Lambda functions are deployed using deployment packages stored in regional S3 buckets. Each function +# assumes the corresponding IAM execution role created in the management account. +# +# Note: Self-managed stacksets require a pair of StackSetAdministrationRole & StackSetExecutionRole IAM roles +# with self-managed permissions. +#----------------------------------------------------------------------------------------------------------------------------------------- +resource "aws_cloudformation_stack_set" "lambda_functions" { + name = "${local.ra_resource_name}-lambda" + tags = merge(var.tags, { + "sysdig.com/response-actions/resource-name" = "lambda-stackset" + }) + permission_model = "SELF_MANAGED" + capabilities = ["CAPABILITY_NAMED_IAM"] + administration_role_arn = local.administration_role_arn + execution_role_name = local.execution_role_name + + managed_execution { + active = true + } + + lifecycle { + ignore_changes = [administration_role_arn] + } + + parameters = { + ResourceName = local.ra_resource_name + TemplateVersion = md5(file("${path.module}/templates/lambda-stackset.tpl")) + S3BucketName = local.s3_bucket_name + ApiBaseUrl = var.api_base_url + PackageDownloaderRoleArn = aws_iam_role.package_downloader_role.arn + QuarantineUserRoleArn = local.enable_quarantine_user ? aws_iam_role.quarantine_user_role[0].arn : "" + FetchCloudLogsRoleArn = local.enable_fetch_cloud_logs ? aws_iam_role.fetch_cloud_logs_role[0].arn : "" + RemovePolicyRoleArn = local.enable_quarantine_user ? aws_iam_role.remove_policy_role[0].arn : "" + ConfigureResourceAccessRoleArn = local.enable_make_private ? aws_iam_role.configure_resource_access_role[0].arn : "" + CreateVolumeSnapshotsRoleArn = local.enable_create_volume_snapshot ? aws_iam_role.create_volume_snapshots_role[0].arn : "" + DeleteVolumeSnapshotsRoleArn = local.enable_create_volume_snapshot ? aws_iam_role.delete_volume_snapshots_role[0].arn : "" + QuarantineUserRoleName = local.enable_quarantine_user ? aws_iam_role.quarantine_user_role[0].name : "" + FetchCloudLogsRoleName = local.enable_fetch_cloud_logs ? aws_iam_role.fetch_cloud_logs_role[0].name : "" + RemovePolicyRoleName = local.enable_quarantine_user ? aws_iam_role.remove_policy_role[0].name : "" + ConfigureResourceAccessRoleName = local.enable_make_private ? aws_iam_role.configure_resource_access_role[0].name : "" + CreateVolumeSnapshotsRoleName = local.enable_create_volume_snapshot ? aws_iam_role.create_volume_snapshots_role[0].name : "" + DeleteVolumeSnapshotsRoleName = local.enable_create_volume_snapshot ? aws_iam_role.delete_volume_snapshots_role[0].name : "" + ResponseActionsVersion = var.response_actions_version + LambdaPackagesBaseUrl = var.lambda_packages_base_url + EnableQuarantineUser = local.enable_quarantine_user ? "true" : "false" + EnableFetchCloudLogs = local.enable_fetch_cloud_logs ? "true" : "false" + EnableMakePrivate = local.enable_make_private ? "true" : "false" + EnableCreateVolumeSnapshot = local.enable_create_volume_snapshot ? "true" : "false" + } + + template_body = file("${path.module}/templates/lambda-stackset.tpl") + + depends_on = [ + aws_iam_role.lambda_stackset_admin_role, + aws_iam_role.lambda_stackset_execution_role, + time_sleep.wait_for_iam_role_propagation + ] +} + +#----------------------------------------------------------------------------------------------------------------------------------------- +# StackSet instances to deploy Lambda functions in all specified regions. +# +# For each region in the region_set, this creates a stack instance that deploys the Lambda functions and their +# supporting resources. The deployment uses parallel execution across regions for faster rollout. +#----------------------------------------------------------------------------------------------------------------------------------------- +resource "aws_cloudformation_stack_set_instance" "lambda_functions" { + for_each = local.region_set + stack_set_instance_region = each.key + + stack_set_name = aws_cloudformation_stack_set.lambda_functions.name + + operation_preferences { + max_concurrent_percentage = 100 + failure_tolerance_percentage = var.failure_tolerance_percentage + concurrency_mode = "SOFT_FAILURE_TOLERANCE" + region_concurrency_type = "PARALLEL" + } + + timeouts { + create = var.timeout + update = var.timeout + delete = var.timeout + } + + depends_on = [ + aws_iam_role_policy.lambda_stackset_execution_policy, + aws_iam_role_policy_attachment.lambda_stackset_execution_cloudformation + ] +} diff --git a/modules/response-actions/organizational.tf b/modules/response-actions/organizational.tf new file mode 100644 index 0000000..f38a89f --- /dev/null +++ b/modules/response-actions/organizational.tf @@ -0,0 +1,106 @@ +#----------------------------------------------------------------------------------------------------------------------- +# These resources set up delegate IAM roles in member accounts of an AWS Organization for Response Actions via +# service-managed CloudFormation StackSets. For a single account installation, see main.tf. +# +# In an organizational deployment: +# 1. Lambda functions are created in the management account (main.tf) across all specified regions +# 2. Delegate roles are created in member accounts (this file) that allow Lambda functions to assume cross-account +# access to perform response actions in those accounts +# 3. The delegate roles grant the Lambda execution roles permission to perform actions like quarantine users, +# fetch logs, modify S3 buckets, and create snapshots in member accounts +# +# The delegate roles are deployed to all member accounts within the specified OUs, excluding the management account +# itself (since the Lambda execution roles already exist there). +#----------------------------------------------------------------------------------------------------------------------- + +resource "aws_cloudformation_stack_set" "ra_delegate_roles" { + count = var.is_organizational ? 1 : 0 + + name = join("-", [local.ra_resource_name, "delegate-roles"]) + tags = merge(var.tags, { + "sysdig.com/response-actions/resource-name" = "delegate-roles-stackset" + }) + permission_model = "SERVICE_MANAGED" + capabilities = ["CAPABILITY_NAMED_IAM"] + + managed_execution { + active = true + } + + auto_deployment { + enabled = true + retain_stacks_on_account_removal = false + } + + lifecycle { + ignore_changes = [administration_role_arn] + } + + parameters = { + TemplateVersion = md5(file("${path.module}/templates/delegate_roles_stackset.tpl")) + QuarantineUserLambdaRoleArn = local.enable_quarantine_user ? aws_iam_role.quarantine_user_role[0].arn : "" + QuarantineUserRoleName = local.enable_quarantine_user ? aws_iam_role.quarantine_user_role[0].name : "" + FetchCloudLogsLambdaRoleArn = local.enable_fetch_cloud_logs ? aws_iam_role.fetch_cloud_logs_role[0].arn : "" + FetchCloudLogsRoleName = local.enable_fetch_cloud_logs ? aws_iam_role.fetch_cloud_logs_role[0].name : "" + RemovePolicyLambdaRoleArn = local.enable_quarantine_user ? aws_iam_role.remove_policy_role[0].arn : "" + RemovePolicyRoleName = local.enable_quarantine_user ? aws_iam_role.remove_policy_role[0].name : "" + ConfigureResourceAccessLambdaRoleArn = local.enable_make_private ? aws_iam_role.configure_resource_access_role[0].arn : "" + ConfigureResourceAccessRoleName = local.enable_make_private ? aws_iam_role.configure_resource_access_role[0].name : "" + CreateVolumeSnapshotsLambdaRoleArn = local.enable_create_volume_snapshot ? aws_iam_role.create_volume_snapshots_role[0].arn : "" + CreateVolumeSnapshotsRoleName = local.enable_create_volume_snapshot ? aws_iam_role.create_volume_snapshots_role[0].name : "" + DeleteVolumeSnapshotsLambdaRoleArn = local.enable_create_volume_snapshot ? aws_iam_role.delete_volume_snapshots_role[0].arn : "" + DeleteVolumeSnapshotsRoleName = local.enable_create_volume_snapshot ? aws_iam_role.delete_volume_snapshots_role[0].name : "" + EnableQuarantineUser = local.enable_quarantine_user ? "true" : "false" + EnableFetchCloudLogs = local.enable_fetch_cloud_logs ? "true" : "false" + EnableMakePrivate = local.enable_make_private ? "true" : "false" + EnableCreateVolumeSnapshot = local.enable_create_volume_snapshot ? "true" : "false" + } + + template_body = file("${path.module}/templates/delegate_roles_stackset.tpl") + + depends_on = [ + aws_iam_role.quarantine_user_role, + aws_iam_role.fetch_cloud_logs_role, + aws_iam_role.remove_policy_role, + aws_iam_role.configure_resource_access_role, + aws_iam_role.create_volume_snapshots_role, + aws_iam_role.delete_volume_snapshots_role + ] +} + +#----------------------------------------------------------------------------------------------------------------------- +# This resource deploys the delegate roles stackset to member accounts in the organization. +# +# Key deployment characteristics: +# - Deployed to organizational units specified in deployment_targets_org_units (from locals.tf) +# - Uses account_filter_type to exclude the management account (always excluded automatically in locals.tf) +# - Deployed to a single region per OU (delegate roles are global IAM resources) +# - Can optionally include/exclude specific accounts based on include_accounts/exclude_accounts variables +#----------------------------------------------------------------------------------------------------------------------- +resource "aws_cloudformation_stack_set_instance" "ra_delegate_roles" { + for_each = var.is_organizational ? { + for ou in local.deployment_targets_org_units : + ou => ou + } : {} + + stack_set_instance_region = tolist(local.region_set)[0] + stack_set_name = aws_cloudformation_stack_set.ra_delegate_roles[0].name + + deployment_targets { + organizational_unit_ids = [each.value] + accounts = local.deployment_targets_accounts_filter == "NONE" ? null : local.deployment_targets_accounts.accounts_to_deploy + account_filter_type = local.deployment_targets_accounts_filter + } + operation_preferences { + max_concurrent_percentage = 100 + failure_tolerance_percentage = var.failure_tolerance_percentage + concurrency_mode = "SOFT_FAILURE_TOLERANCE" + region_concurrency_type = "PARALLEL" + } + + timeouts { + create = var.timeout + update = var.timeout + delete = var.timeout + } +} diff --git a/modules/response-actions/outputs.tf b/modules/response-actions/outputs.tf new file mode 100644 index 0000000..a369cd8 --- /dev/null +++ b/modules/response-actions/outputs.tf @@ -0,0 +1,69 @@ +output "cross_account_role_arn" { + description = "ARN of the cross-account role for Lambda invocation" + value = aws_iam_role.shared_cross_account_lambda_invoker.arn +} + +output "stackset_name" { + description = "Name of the CloudFormation StackSet deploying Lambda functions" + value = aws_cloudformation_stack_set.lambda_functions.name +} + +output "stackset_id" { + description = "ID of the CloudFormation StackSet" + value = aws_cloudformation_stack_set.lambda_functions.id +} + +output "deployment_regions" { + description = "List of regions where Lambda functions are deployed" + value = local.region_set +} + +output "lambda_functions" { + description = "Information about deployed Lambda functions across all regions" + value = merge( + local.enable_quarantine_user ? { + quarantine_user = { + name = "${local.ra_resource_name}-quarantine-user" + arns = [for region in local.region_set : "${local.arn_prefix}:lambda:${region}:${data.aws_caller_identity.current.account_id}:function:${local.ra_resource_name}-quarantine-user"] + } + remove_policy = { + name = "${local.ra_resource_name}-remove-policy" + arns = [for region in local.region_set : "${local.arn_prefix}:lambda:${region}:${data.aws_caller_identity.current.account_id}:function:${local.ra_resource_name}-remove-policy"] + } + } : {}, + local.enable_fetch_cloud_logs ? { + fetch_cloud_logs = { + name = "${local.ra_resource_name}-fetch-cloud-logs" + arns = [for region in local.region_set : "${local.arn_prefix}:lambda:${region}:${data.aws_caller_identity.current.account_id}:function:${local.ra_resource_name}-fetch-cloud-logs"] + } + } : {}, + local.enable_make_private ? { + configure_resource_access = { + name = "${local.ra_resource_name}-configure-resource-access" + arns = [for region in local.region_set : "${local.arn_prefix}:lambda:${region}:${data.aws_caller_identity.current.account_id}:function:${local.ra_resource_name}-configure-resource-access"] + } + } : {}, + local.enable_create_volume_snapshot ? { + create_volume_snapshots = { + name = "${local.ra_resource_name}-create-volume-snapshots" + arns = [for region in local.region_set : "${local.arn_prefix}:lambda:${region}:${data.aws_caller_identity.current.account_id}:function:${local.ra_resource_name}-create-volume-snapshots"] + } + delete_volume_snapshots = { + name = "${local.ra_resource_name}-delete-volume-snapshots" + arns = [for region in local.region_set : "${local.arn_prefix}:lambda:${region}:${data.aws_caller_identity.current.account_id}:function:${local.ra_resource_name}-delete-volume-snapshots"] + } + } : {} + ) +} + +output "responder_component_id" { + value = "${sysdig_secure_cloud_auth_account_component.aws_responder.type}/${sysdig_secure_cloud_auth_account_component.aws_responder.instance}" + description = "Component identifier of Response Actions responder integration created in Sysdig Backend" + depends_on = [sysdig_secure_cloud_auth_account_component.aws_responder] +} + +output "responder_roles_component_id" { + value = "${sysdig_secure_cloud_auth_account_component.aws_responder_roles.type}/${sysdig_secure_cloud_auth_account_component.aws_responder_roles.instance}" + description = "Component identifier of Response Actions roles integration created in Sysdig Backend" + depends_on = [sysdig_secure_cloud_auth_account_component.aws_responder_roles] +} diff --git a/modules/response-actions/policies/configure-resource-access-policy.json b/modules/response-actions/policies/configure-resource-access-policy.json new file mode 100644 index 0000000..cddd88f --- /dev/null +++ b/modules/response-actions/policies/configure-resource-access-policy.json @@ -0,0 +1,29 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetBucketLocation", + "s3:GetBucketPublicAccessBlock", + "s3:PutBucketPublicAccessBlock", + "s3:ListAllMyBuckets" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "rds:DescribeDBInstances", + "rds:ModifyDBInstance", + "sts:GetCallerIdentity" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": "arn:aws:iam::*:role/${role_name}" + } + ] +} diff --git a/modules/response-actions/policies/create-volume-snapshots-policy.json b/modules/response-actions/policies/create-volume-snapshots-policy.json new file mode 100644 index 0000000..6176846 --- /dev/null +++ b/modules/response-actions/policies/create-volume-snapshots-policy.json @@ -0,0 +1,41 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Resource": "arn:aws:logs:*:*:*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:DescribeInstances", + "ec2:DescribeVolumes", + "ec2:DescribeSnapshots", + "ec2:CreateSnapshot", + "ec2:CreateTags", + "sts:GetCallerIdentity" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateTags" + ], + "Resource": [ + "arn:aws:ec2:*:*:snapshot/*" + ] + }, + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": "arn:aws:iam::*:role/${role_name}" + } + + ] +} diff --git a/modules/response-actions/policies/delete-volume-snapshots-policy.json b/modules/response-actions/policies/delete-volume-snapshots-policy.json new file mode 100644 index 0000000..fd25ba6 --- /dev/null +++ b/modules/response-actions/policies/delete-volume-snapshots-policy.json @@ -0,0 +1,29 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Resource": "arn:aws:logs:*:*:*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:DescribeSnapshots", + "ec2:DeleteSnapshot", + "sts:GetCallerIdentity" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": "arn:aws:iam::*:role/${role_name}" + } + + ] +} diff --git a/modules/response-actions/policies/fetch-cloud-logs-policy.json b/modules/response-actions/policies/fetch-cloud-logs-policy.json new file mode 100644 index 0000000..2c91de3 --- /dev/null +++ b/modules/response-actions/policies/fetch-cloud-logs-policy.json @@ -0,0 +1,19 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "cloudtrail:LookupEvents", + "sts:GetCallerIdentity" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": "arn:aws:iam::*:role/${role_name}" + } + + ] +} diff --git a/modules/response-actions/policies/quarantine-user-policy.json b/modules/response-actions/policies/quarantine-user-policy.json new file mode 100644 index 0000000..73dee4e --- /dev/null +++ b/modules/response-actions/policies/quarantine-user-policy.json @@ -0,0 +1,36 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "iam:AttachUserPolicy", + "iam:DetachUserPolicy", + "iam:PutUserPolicy", + "iam:DeleteUserPolicy", + "iam:ListUserPolicies", + "iam:ListAttachedUserPolicies", + "iam:GetUser", + "iam:GetUserPolicy", + "iam:TagUser", + "iam:UntagUser", + "iam:ListUserTags", + "iam:AttachRolePolicy", + "iam:DetachRolePolicy", + "iam:GetRole", + "iam:ListRolePolicies", + "iam:ListAttachedRolePolicies", + "iam:GetPolicy", + "iam:CreatePolicy", + "sts:GetCallerIdentity" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": "arn:aws:iam::*:role/${role_name}" + } + + ] +} diff --git a/modules/response-actions/policies/remove-policy-policy.json b/modules/response-actions/policies/remove-policy-policy.json new file mode 100644 index 0000000..f435911 --- /dev/null +++ b/modules/response-actions/policies/remove-policy-policy.json @@ -0,0 +1,24 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "iam:DetachUserPolicy", + "iam:DetachRolePolicy", + "iam:ListAttachedUserPolicies", + "iam:ListAttachedRolePolicies", + "iam:GetUser", + "iam:GetRole", + "sts:GetCallerIdentity" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": "arn:aws:iam::*:role/${role_name}" + } + + ] +} diff --git a/modules/response-actions/sysdig_components.tf b/modules/response-actions/sysdig_components.tf new file mode 100644 index 0000000..7eb396f --- /dev/null +++ b/modules/response-actions/sysdig_components.tf @@ -0,0 +1,85 @@ +#----------------------------------------------------------------------------------------------------------------------------------------- +# Call Sysdig Backend to add the response actions responder integration to the Sysdig Cloud Account +# +# Note (optional): To ensure this gets called after all cloud resources are created, add +# explicit dependency using depends_on +#----------------------------------------------------------------------------------------------------------------------------------------- +resource "sysdig_secure_cloud_auth_account_component" "aws_responder" { + account_id = var.sysdig_secure_account_id + type = local.responder_component_type + instance = "cloud-responder" + version = "v${var.response_actions_version}" + cloud_responder_metadata = jsonencode({ + aws = { + responder_lambdas = { + lambda_names = local.enabled_lambda_names + regions = local.region_set + delegate_role_name = aws_iam_role.shared_cross_account_lambda_invoker.name + } + } + }) + + depends_on = [ + aws_cloudformation_stack_set_instance.lambda_functions, + aws_iam_role.shared_cross_account_lambda_invoker + ] +} + +resource "sysdig_secure_cloud_auth_account_component" "aws_responder_roles" { + account_id = var.sysdig_secure_account_id + type = local.roles_component_type + instance = "cloud-responder" + version = "v${var.response_actions_version}" + cloud_responder_roles_metadata = jsonencode({ + roles = concat( + local.enable_quarantine_user ? [ + { + aws = { + role_name = aws_iam_role.quarantine_user_role[0].arn + } + }, + { + aws = { + role_name = aws_iam_role.remove_policy_role[0].arn + } + } + ] : [], + local.enable_fetch_cloud_logs ? [ + { + aws = { + role_name = aws_iam_role.fetch_cloud_logs_role[0].arn + } + } + ] : [], + local.enable_make_private ? [ + { + aws = { + role_name = aws_iam_role.configure_resource_access_role[0].arn + } + } + ] : [], + local.enable_create_volume_snapshot ? [ + { + aws = { + role_name = aws_iam_role.create_volume_snapshots_role[0].arn + } + }, + { + aws = { + role_name = aws_iam_role.delete_volume_snapshots_role[0].arn + } + } + ] : [] + ) + }) + + depends_on = [ + aws_cloudformation_stack_set_instance.lambda_functions, + aws_iam_role.quarantine_user_role, + aws_iam_role.remove_policy_role, + aws_iam_role.fetch_cloud_logs_role, + aws_iam_role.configure_resource_access_role, + aws_iam_role.create_volume_snapshots_role, + aws_iam_role.delete_volume_snapshots_role + ] +} diff --git a/modules/response-actions/templates/delegate_roles_stackset.tpl b/modules/response-actions/templates/delegate_roles_stackset.tpl new file mode 100644 index 0000000..4f8fb6e --- /dev/null +++ b/modules/response-actions/templates/delegate_roles_stackset.tpl @@ -0,0 +1,347 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Sysdig Response Actions Delegate Roles - Multi-Account Deployment' + +Parameters: + TemplateVersion: + Type: String + Description: Template version hash to force updates + Default: "1" + QuarantineUserLambdaRoleArn: + Type: String + Description: ARN of the Lambda execution role for quarantine user function + QuarantineUserRoleName: + Type: String + Description: Name for the quarantine user delegate role + FetchCloudLogsLambdaRoleArn: + Type: String + Description: ARN of the Lambda execution role for fetch cloud logs function + FetchCloudLogsRoleName: + Type: String + Description: Name for the fetch cloud logs delegate role + RemovePolicyLambdaRoleArn: + Type: String + Description: ARN of the Lambda execution role for remove policy function + RemovePolicyRoleName: + Type: String + Description: Name for the remove policy delegate role + ConfigureResourceAccessLambdaRoleArn: + Type: String + Description: ARN of the Lambda execution role for configure resource access function + ConfigureResourceAccessRoleName: + Type: String + Description: Name for the configure resource access delegate role + CreateVolumeSnapshotsLambdaRoleArn: + Type: String + Description: ARN of the Lambda execution role for create volume snapshots function + CreateVolumeSnapshotsRoleName: + Type: String + Description: Name for the create volume snapshots delegate role + DeleteVolumeSnapshotsLambdaRoleArn: + Type: String + Description: ARN of the Lambda execution role for delete volume snapshots function + DeleteVolumeSnapshotsRoleName: + Type: String + Description: Name for the delete volume snapshots delegate role + EnableQuarantineUser: + Type: String + Default: "true" + AllowedValues: ["true", "false"] + Description: Enable quarantine user and remove policy delegate roles + EnableFetchCloudLogs: + Type: String + Default: "true" + AllowedValues: ["true", "false"] + Description: Enable fetch cloud logs delegate role + EnableMakePrivate: + Type: String + Default: "true" + AllowedValues: ["true", "false"] + Description: Enable configure resource access delegate role + EnableCreateVolumeSnapshot: + Type: String + Default: "true" + AllowedValues: ["true", "false"] + Description: Enable create and delete volume snapshot delegate roles + +Conditions: + CreateQuarantineUserResources: !Equals [!Ref EnableQuarantineUser, "true"] + CreateFetchCloudLogsResources: !Equals [!Ref EnableFetchCloudLogs, "true"] + CreateMakePrivateResources: !Equals [!Ref EnableMakePrivate, "true"] + CreateVolumeSnapshotResources: !Equals [!Ref EnableCreateVolumeSnapshot, "true"] + +Resources: + # Delegate Role: Configure Resource Access + ConfigureAccessDelegateRole: + Type: AWS::IAM::Role + Condition: CreateMakePrivateResources + Properties: + RoleName: !Ref ConfigureResourceAccessRoleName + Description: Delegate role for configuring resource access + MaxSessionDuration: 3600 + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + AWS: !Ref ConfigureResourceAccessLambdaRoleArn + Action: 'sts:AssumeRole' + Policies: + - PolicyName: response-actions-configure-resource-access + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - 's3:GetBucketLocation' + - 's3:GetBucketPublicAccessBlock' + - 's3:PutBucketPublicAccessBlock' + - 's3:ListAllMyBuckets' + Resource: '*' + - Effect: Allow + Action: + - 'rds:DescribeDBInstances' + - 'rds:ModifyDBInstance' + - 'sts:GetCallerIdentity' + Resource: '*' + Tags: + - Key: ManagedBy + Value: Terraform + - Key: Purpose + Value: ResponseActions + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'configure-resource-access-delegate-role' + + # Delegate Role: Create Volume Snapshot + CreateVolumeSnapshotDelegateRole: + Type: AWS::IAM::Role + Condition: CreateVolumeSnapshotResources + Properties: + RoleName: !Ref CreateVolumeSnapshotsRoleName + Description: Delegate role for creating volume snapshots + MaxSessionDuration: 3600 + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + AWS: !Ref CreateVolumeSnapshotsLambdaRoleArn + Action: 'sts:AssumeRole' + Policies: + - PolicyName: response-actions-create-volume-snapshot + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - 'logs:CreateLogGroup' + - 'logs:CreateLogStream' + - 'logs:PutLogEvents' + Resource: 'arn:aws:logs:*:*:*' + - Effect: Allow + Action: + - 'ec2:DescribeInstances' + - 'ec2:DescribeVolumes' + - 'ec2:DescribeSnapshots' + - 'ec2:CreateSnapshot' + - 'ec2:CreateTags' + - 'sts:GetCallerIdentity' + Resource: '*' + - Effect: Allow + Action: + - 'ec2:CreateTags' + Resource: + - 'arn:aws:ec2:*:*:snapshot/*' + Tags: + - Key: ManagedBy + Value: Terraform + - Key: Purpose + Value: ResponseActions + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'create-volume-snapshot-delegate-role' + + # Delegate Role: Delete Volume Snapshot + DeleteVolumeSnapshotDelegateRole: + Type: AWS::IAM::Role + Condition: CreateVolumeSnapshotResources + Properties: + RoleName: !Ref DeleteVolumeSnapshotsRoleName + Description: Delegate role for deleting volume snapshots + MaxSessionDuration: 3600 + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + AWS: !Ref DeleteVolumeSnapshotsLambdaRoleArn + Action: 'sts:AssumeRole' + Policies: + - PolicyName: response-actions-delete-volume-snapshots + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - 'logs:CreateLogGroup' + - 'logs:CreateLogStream' + - 'logs:PutLogEvents' + Resource: 'arn:aws:logs:*:*:*' + - Effect: Allow + Action: + - 'ec2:DescribeSnapshots' + - 'ec2:DeleteSnapshot' + - 'sts:GetCallerIdentity' + Resource: '*' + Tags: + - Key: ManagedBy + Value: Terraform + - Key: Purpose + Value: ResponseActions + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'delete-volume-snapshot-delegate-role' + + # Delegate Role: Fetch Cloud Logs + FetchCloudLogsDelegateRole: + Type: AWS::IAM::Role + Condition: CreateFetchCloudLogsResources + Properties: + RoleName: !Ref FetchCloudLogsRoleName + Description: Delegate role for fetching cloud logs + MaxSessionDuration: 3600 + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + AWS: !Ref FetchCloudLogsLambdaRoleArn + Action: 'sts:AssumeRole' + Policies: + - PolicyName: response-actions-fetch-cloud-logs + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - 'cloudtrail:LookupEvents' + - 'sts:GetCallerIdentity' + Resource: '*' + Tags: + - Key: ManagedBy + Value: Terraform + - Key: Purpose + Value: ResponseActions + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'fetch-cloud-logs-delegate-role' + + # Delegate Role: Quarantine User + QuarantineUserRoleDelegateRole: + Type: AWS::IAM::Role + Condition: CreateQuarantineUserResources + Properties: + RoleName: !Ref QuarantineUserRoleName + Description: Delegate role for quarantining users + MaxSessionDuration: 3600 + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + AWS: !Ref QuarantineUserLambdaRoleArn + Action: 'sts:AssumeRole' + Policies: + - PolicyName: response-actions-quarantine-user + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - 'iam:AttachUserPolicy' + - 'iam:DetachUserPolicy' + - 'iam:PutUserPolicy' + - 'iam:DeleteUserPolicy' + - 'iam:ListUserPolicies' + - 'iam:ListAttachedUserPolicies' + - 'iam:GetUser' + - 'iam:GetUserPolicy' + - 'iam:TagUser' + - 'iam:UntagUser' + - 'iam:ListUserTags' + - 'iam:AttachRolePolicy' + - 'iam:DetachRolePolicy' + - 'iam:GetRole' + - 'iam:ListRolePolicies' + - 'iam:ListAttachedRolePolicies' + - 'iam:GetPolicy' + - 'iam:CreatePolicy' + - 'sts:GetCallerIdentity' + Resource: '*' + Tags: + - Key: ManagedBy + Value: Terraform + - Key: Purpose + Value: ResponseActions + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'quarantine-user-delegate-role' + + # Delegate Role: Remove Policy + RemovePolicyDelegateRole: + Type: AWS::IAM::Role + Condition: CreateQuarantineUserResources + Properties: + RoleName: !Ref RemovePolicyRoleName + Description: Delegate role for removing policies + MaxSessionDuration: 3600 + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + AWS: !Ref RemovePolicyLambdaRoleArn + Action: 'sts:AssumeRole' + Policies: + - PolicyName: response-actions-remove-policy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - 'iam:DetachUserPolicy' + - 'iam:DetachRolePolicy' + - 'iam:ListAttachedUserPolicies' + - 'iam:ListAttachedRolePolicies' + - 'iam:GetUser' + - 'iam:GetRole' + - 'sts:GetCallerIdentity' + Resource: '*' + Tags: + - Key: ManagedBy + Value: Terraform + - Key: Purpose + Value: ResponseActions + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'remove-policy-delegate-role' + +Outputs: + ConfigureAccessDelegateRoleName: + Condition: CreateMakePrivateResources + Value: !Ref ConfigureAccessDelegateRole + Description: Name of the configure resource access delegate role + CreateVolumeSnapshotDelegateRoleName: + Condition: CreateVolumeSnapshotResources + Value: !Ref CreateVolumeSnapshotDelegateRole + Description: Name of the create volume snapshot delegate role + DeleteVolumeSnapshotDelegateRoleName: + Condition: CreateVolumeSnapshotResources + Value: !Ref DeleteVolumeSnapshotDelegateRole + Description: Name of the delete volume snapshot delegate role + FetchCloudLogsDelegateRoleName: + Condition: CreateFetchCloudLogsResources + Value: !Ref FetchCloudLogsDelegateRole + Description: Name of the fetch cloud logs delegate role + QuarantineUserRoleDelegateRoleName: + Condition: CreateQuarantineUserResources + Value: !Ref QuarantineUserRoleDelegateRole + Description: Name of the quarantine user delegate role + RemovePolicyDelegateRoleName: + Condition: CreateQuarantineUserResources + Value: !Ref RemovePolicyDelegateRole + Description: Name of the remove policy delegate role diff --git a/modules/response-actions/templates/lambda-stackset.tpl b/modules/response-actions/templates/lambda-stackset.tpl new file mode 100644 index 0000000..7df7a42 --- /dev/null +++ b/modules/response-actions/templates/lambda-stackset.tpl @@ -0,0 +1,508 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Sysdig Response Actions Lambda Functions - Multi-Region Deployment' + +Parameters: + ResourceName: + Type: String + Description: Base name for all resources + TemplateVersion: + Type: String + Description: Template version hash to force updates + Default: "1" + S3BucketName: + Type: String + Description: Name for the regional S3 bucket containing Lambda deployment packages + ApiBaseUrl: + Type: String + Description: API base URL for Lambda functions + PackageDownloaderRoleArn: + Type: String + Description: ARN of the IAM role for package downloader function + QuarantineUserRoleArn: + Type: String + Description: ARN of the IAM role for quarantine user function + FetchCloudLogsRoleArn: + Type: String + Description: ARN of the IAM role for fetch cloud logs function + RemovePolicyRoleArn: + Type: String + Description: ARN of the IAM role for remove policy function + ConfigureResourceAccessRoleArn: + Type: String + Description: ARN of the IAM role for configure resource access function + CreateVolumeSnapshotsRoleArn: + Type: String + Description: ARN of the IAM role for create volume snapshots function + DeleteVolumeSnapshotsRoleArn: + Type: String + Description: ARN of the IAM role for delete volume snapshots function + QuarantineUserRoleName: + Type: String + Description: Name of the IAM role for quarantine user function + FetchCloudLogsRoleName: + Type: String + Description: Name of the IAM role for fetch cloud logs function + RemovePolicyRoleName: + Type: String + Description: Name of the IAM role for remove policy function + ConfigureResourceAccessRoleName: + Type: String + Description: Name of the IAM role for configure resource access function + CreateVolumeSnapshotsRoleName: + Type: String + Description: Name of the IAM role for create volume snapshots function + DeleteVolumeSnapshotsRoleName: + Type: String + Description: Name of the IAM role for delete volume snapshots function + ResponseActionsVersion: + Type: String + Description: Version of response actions packages to download + LambdaPackagesBaseUrl: + Type: String + Description: Base URL for downloading Lambda deployment packages + EnableQuarantineUser: + Type: String + Default: "true" + AllowedValues: ["true", "false"] + Description: Enable quarantine user and remove policy lambdas + EnableFetchCloudLogs: + Type: String + Default: "true" + AllowedValues: ["true", "false"] + Description: Enable fetch cloud logs lambda + EnableMakePrivate: + Type: String + Default: "true" + AllowedValues: ["true", "false"] + Description: Enable configure resource access lambda + EnableCreateVolumeSnapshot: + Type: String + Default: "true" + AllowedValues: ["true", "false"] + Description: Enable create and delete volume snapshot lambdas + +Conditions: + CreateQuarantineUserResources: !Equals [!Ref EnableQuarantineUser, "true"] + CreateFetchCloudLogsResources: !Equals [!Ref EnableFetchCloudLogs, "true"] + CreateMakePrivateResources: !Equals [!Ref EnableMakePrivate, "true"] + CreateVolumeSnapshotResources: !Equals [!Ref EnableCreateVolumeSnapshot, "true"] + +Resources: + # S3 Bucket for Lambda packages (Regional) + LambdaPackagesBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: !Sub '${S3BucketName}-${AWS::Region}' + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + Tags: + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'lambda-packages-bucket' + + # Package Downloader Lambda (per region) + PackageDownloaderLogGroup: + Type: AWS::Logs::LogGroup + DeletionPolicy: Retain + Properties: + LogGroupName: !Sub '/aws/lambda/${ResourceName}-package-downloader' + RetentionInDays: 7 + Tags: + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'package-downloader-log-group' + + PackageDownloaderFunction: + Type: AWS::Lambda::Function + DependsOn: PackageDownloaderLogGroup + Properties: + FunctionName: !Sub '${ResourceName}-package-downloader' + Runtime: python3.12 + Handler: index.handler + Role: !Ref PackageDownloaderRoleArn + Timeout: 300 + MemorySize: 256 + Code: + ZipFile: | + import json + import urllib.request + import boto3 + import cfnresponse + + def handler(event, context): + try: + print(f"Event: {json.dumps(event)}") + + if event['RequestType'] == 'Delete': + # Clean up S3 object on delete + bucket = event['ResourceProperties']['Bucket'] + key = event['ResourceProperties']['Key'] + s3 = boto3.client('s3') + try: + s3.delete_object(Bucket=bucket, Key=key) + print(f"Deleted s3://{bucket}/{key}") + except Exception as e: + print(f"Error deleting object (may not exist): {e}") + cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) + return + + if event['RequestType'] in ['Create', 'Update']: + url = event['ResourceProperties']['Url'] + bucket = event['ResourceProperties']['Bucket'] + key = event['ResourceProperties']['Key'] + + print(f"Downloading from {url}") + print(f"Target: s3://{bucket}/{key}") + + # Download from URL + try: + req = urllib.request.Request(url) + with urllib.request.urlopen(req, timeout=60) as response: + content = response.read() + print(f"Downloaded {len(content)} bytes, status: {response.status}") + except urllib.error.HTTPError as e: + error_msg = f"HTTP {e.code} downloading {url}: {e.reason}" + print(error_msg) + cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': error_msg}) + return + except urllib.error.URLError as e: + error_msg = f"URL error downloading {url}: {str(e.reason)}" + print(error_msg) + cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': error_msg}) + return + + # Upload to S3 + try: + s3 = boto3.client('s3') + s3.put_object(Bucket=bucket, Key=key, Body=content) + print(f"Uploaded to s3://{bucket}/{key}") + except Exception as e: + error_msg = f"S3 upload error: {str(e)}" + print(error_msg) + cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': error_msg}) + return + + cfnresponse.send(event, context, cfnresponse.SUCCESS, { + 'Bucket': bucket, + 'Key': key, + 'Size': len(content) + }) + else: + cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) + + except Exception as e: + import traceback + error_msg = f"Unexpected error: {str(e)}\n{traceback.format_exc()}" + print(error_msg) + cfnresponse.send(event, context, cfnresponse.FAILED, { + 'Error': str(e) + }) + Tags: + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'package-downloader-lambda' + + # Custom Resources to download Lambda packages + QuarantineUserPackage: + Type: Custom::LambdaPackage + Condition: CreateQuarantineUserResources + Properties: + ServiceToken: !GetAtt PackageDownloaderFunction.Arn + Url: !Sub '${LambdaPackagesBaseUrl}/v${ResponseActionsVersion}/quarantine_user.zip' + Bucket: !Ref LambdaPackagesBucket + Key: !Sub '${ResponseActionsVersion}/quarantine_user.zip' + + FetchCloudLogsPackage: + Type: Custom::LambdaPackage + Condition: CreateFetchCloudLogsResources + Properties: + ServiceToken: !GetAtt PackageDownloaderFunction.Arn + Url: !Sub '${LambdaPackagesBaseUrl}/v${ResponseActionsVersion}/fetch_cloud_logs.zip' + Bucket: !Ref LambdaPackagesBucket + Key: !Sub '${ResponseActionsVersion}/fetch_cloud_logs.zip' + + RemovePolicyPackage: + Type: Custom::LambdaPackage + Condition: CreateQuarantineUserResources + Properties: + ServiceToken: !GetAtt PackageDownloaderFunction.Arn + Url: !Sub '${LambdaPackagesBaseUrl}/v${ResponseActionsVersion}/remove_policy.zip' + Bucket: !Ref LambdaPackagesBucket + Key: !Sub '${ResponseActionsVersion}/remove_policy.zip' + + ConfigureResourceAccessPackage: + Type: Custom::LambdaPackage + Condition: CreateMakePrivateResources + Properties: + ServiceToken: !GetAtt PackageDownloaderFunction.Arn + Url: !Sub '${LambdaPackagesBaseUrl}/v${ResponseActionsVersion}/configure_resource_access.zip' + Bucket: !Ref LambdaPackagesBucket + Key: !Sub '${ResponseActionsVersion}/configure_resource_access.zip' + + CreateVolumeSnapshotsPackage: + Type: Custom::LambdaPackage + Condition: CreateVolumeSnapshotResources + Properties: + ServiceToken: !GetAtt PackageDownloaderFunction.Arn + Url: !Sub '${LambdaPackagesBaseUrl}/v${ResponseActionsVersion}/create_volume_snapshots.zip' + Bucket: !Ref LambdaPackagesBucket + Key: !Sub '${ResponseActionsVersion}/create_volume_snapshots.zip' + + DeleteVolumeSnapshotsPackage: + Type: Custom::LambdaPackage + Condition: CreateVolumeSnapshotResources + Properties: + ServiceToken: !GetAtt PackageDownloaderFunction.Arn + Url: !Sub '${LambdaPackagesBaseUrl}/v${ResponseActionsVersion}/delete_volume_snapshots.zip' + Bucket: !Ref LambdaPackagesBucket + Key: !Sub '${ResponseActionsVersion}/delete_volume_snapshots.zip' + + # CloudWatch Log Groups (Regional Resources) + QuarantineUserLogGroup: + Type: AWS::Logs::LogGroup + Condition: CreateQuarantineUserResources + Properties: + LogGroupName: !Sub '/aws/lambda/${ResourceName}-quarantine-user' + RetentionInDays: 7 + Tags: + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'quarantine-user-log-group' + + FetchCloudLogsLogGroup: + Type: AWS::Logs::LogGroup + Condition: CreateFetchCloudLogsResources + Properties: + LogGroupName: !Sub '/aws/lambda/${ResourceName}-fetch-cloud-logs' + RetentionInDays: 7 + Tags: + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'fetch-cloud-logs-log-group' + + RemovePolicyLogGroup: + Type: AWS::Logs::LogGroup + Condition: CreateQuarantineUserResources + Properties: + LogGroupName: !Sub '/aws/lambda/${ResourceName}-remove-policy' + RetentionInDays: 7 + Tags: + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'remove-policy-log-group' + + ConfigureResourceAccessLogGroup: + Type: AWS::Logs::LogGroup + Condition: CreateMakePrivateResources + Properties: + LogGroupName: !Sub '/aws/lambda/${ResourceName}-configure-resource-access' + RetentionInDays: 7 + Tags: + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'configure-resource-access-log-group' + + CreateVolumeSnapshotsLogGroup: + Type: AWS::Logs::LogGroup + Condition: CreateVolumeSnapshotResources + Properties: + LogGroupName: !Sub '/aws/lambda/${ResourceName}-create-volume-snapshots' + RetentionInDays: 7 + Tags: + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'create-volume-snapshots-log-group' + + DeleteVolumeSnapshotsLogGroup: + Type: AWS::Logs::LogGroup + Condition: CreateVolumeSnapshotResources + Properties: + LogGroupName: !Sub '/aws/lambda/${ResourceName}-delete-volume-snapshots' + RetentionInDays: 7 + Tags: + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'delete-volume-snapshots-log-group' + + # Lambda Functions (Regional Resources) + QuarantineUserFunction: + Type: AWS::Lambda::Function + Condition: CreateQuarantineUserResources + DependsOn: + - QuarantineUserLogGroup + - QuarantineUserPackage + Properties: + FunctionName: !Sub '${ResourceName}-quarantine-user' + Runtime: python3.12 + Handler: app.index.handler + Role: !Ref QuarantineUserRoleArn + Code: + S3Bucket: !Ref LambdaPackagesBucket + S3Key: !Sub '${ResponseActionsVersion}/quarantine_user.zip' + Timeout: 300 + MemorySize: 128 + Environment: + Variables: + API_BASE_URL: !Ref ApiBaseUrl + DELEGATE_ROLE_NAME: !Ref QuarantineUserRoleName + Tags: + - Key: Name + Value: !Sub '${ResourceName}-quarantine-user' + - Key: 'sysdig.com/response-actions/cloud-actions' + Value: 'true' + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'quarantine-user-lambda' + + FetchCloudLogsFunction: + Type: AWS::Lambda::Function + Condition: CreateFetchCloudLogsResources + DependsOn: + - FetchCloudLogsLogGroup + - FetchCloudLogsPackage + Properties: + FunctionName: !Sub '${ResourceName}-fetch-cloud-logs' + Runtime: python3.12 + Handler: app.index.handler + Role: !Ref FetchCloudLogsRoleArn + Code: + S3Bucket: !Ref LambdaPackagesBucket + S3Key: !Sub '${ResponseActionsVersion}/fetch_cloud_logs.zip' + Timeout: 300 + MemorySize: 128 + Environment: + Variables: + API_BASE_URL: !Ref ApiBaseUrl + DELEGATE_ROLE_NAME: !Ref FetchCloudLogsRoleName + Tags: + - Key: Name + Value: !Sub '${ResourceName}-fetch-cloud-logs' + - Key: 'sysdig.com/response-actions/cloud-actions' + Value: 'true' + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'fetch-cloud-logs-lambda' + + RemovePolicyFunction: + Type: AWS::Lambda::Function + Condition: CreateQuarantineUserResources + DependsOn: + - RemovePolicyLogGroup + - RemovePolicyPackage + Properties: + FunctionName: !Sub '${ResourceName}-remove-policy' + Runtime: python3.12 + Handler: app.index.handler + Role: !Ref RemovePolicyRoleArn + Code: + S3Bucket: !Ref LambdaPackagesBucket + S3Key: !Sub '${ResponseActionsVersion}/remove_policy.zip' + Timeout: 300 + MemorySize: 128 + Environment: + Variables: + API_BASE_URL: !Ref ApiBaseUrl + DELEGATE_ROLE_NAME: !Ref RemovePolicyRoleName + Tags: + - Key: Name + Value: !Sub '${ResourceName}-remove-policy' + - Key: 'sysdig.com/response-actions/cloud-actions' + Value: 'true' + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'remove-policy-lambda' + + ConfigureResourceAccessFunction: + Type: AWS::Lambda::Function + Condition: CreateMakePrivateResources + DependsOn: + - ConfigureResourceAccessLogGroup + - ConfigureResourceAccessPackage + Properties: + FunctionName: !Sub '${ResourceName}-configure-resource-access' + Runtime: python3.12 + Handler: app.index.handler + Role: !Ref ConfigureResourceAccessRoleArn + Code: + S3Bucket: !Ref LambdaPackagesBucket + S3Key: !Sub '${ResponseActionsVersion}/configure_resource_access.zip' + Timeout: 300 + MemorySize: 128 + Environment: + Variables: + API_BASE_URL: !Ref ApiBaseUrl + DELEGATE_ROLE_NAME: !Ref ConfigureResourceAccessRoleName + Tags: + - Key: Name + Value: !Sub '${ResourceName}-configure-resource-access' + - Key: 'sysdig.com/response-actions/cloud-actions' + Value: 'true' + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'configure-resource-access-lambda' + + CreateVolumeSnapshotsFunction: + Type: AWS::Lambda::Function + Condition: CreateVolumeSnapshotResources + DependsOn: + - CreateVolumeSnapshotsLogGroup + - CreateVolumeSnapshotsPackage + Properties: + FunctionName: !Sub '${ResourceName}-create-volume-snapshots' + Runtime: python3.12 + Handler: app.index.handler + Role: !Ref CreateVolumeSnapshotsRoleArn + Code: + S3Bucket: !Ref LambdaPackagesBucket + S3Key: !Sub '${ResponseActionsVersion}/create_volume_snapshots.zip' + Timeout: 300 + MemorySize: 128 + Environment: + Variables: + API_BASE_URL: !Ref ApiBaseUrl + DELEGATE_ROLE_NAME: !Ref CreateVolumeSnapshotsRoleName + Tags: + - Key: Name + Value: !Sub '${ResourceName}-create-volume-snapshots' + - Key: 'sysdig.com/response-actions/cloud-actions' + Value: 'true' + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'create-volume-snapshots-lambda' + + DeleteVolumeSnapshotsFunction: + Type: AWS::Lambda::Function + Condition: CreateVolumeSnapshotResources + DependsOn: + - DeleteVolumeSnapshotsLogGroup + - DeleteVolumeSnapshotsPackage + Properties: + FunctionName: !Sub '${ResourceName}-delete-volume-snapshots' + Runtime: python3.12 + Handler: app.index.handler + Role: !Ref DeleteVolumeSnapshotsRoleArn + Code: + S3Bucket: !Ref LambdaPackagesBucket + S3Key: !Sub '${ResponseActionsVersion}/delete_volume_snapshots.zip' + Timeout: 300 + MemorySize: 128 + Environment: + Variables: + API_BASE_URL: !Ref ApiBaseUrl + DELEGATE_ROLE_NAME: !Ref DeleteVolumeSnapshotsRoleName + Tags: + - Key: Name + Value: !Sub '${ResourceName}-delete-volume-snapshots' + - Key: 'sysdig.com/response-actions/cloud-actions' + Value: 'true' + - Key: 'sysdig.com/response-actions/resource-name' + Value: 'delete-volume-snapshots-lambda' + +Outputs: + QuarantineUserFunctionArn: + Condition: CreateQuarantineUserResources + Value: !GetAtt QuarantineUserFunction.Arn + FetchCloudLogsFunctionArn: + Condition: CreateFetchCloudLogsResources + Value: !GetAtt FetchCloudLogsFunction.Arn + RemovePolicyFunctionArn: + Condition: CreateQuarantineUserResources + Value: !GetAtt RemovePolicyFunction.Arn + ConfigureResourceAccessFunctionArn: + Condition: CreateMakePrivateResources + Value: !GetAtt ConfigureResourceAccessFunction.Arn + CreateVolumeSnapshotsFunctionArn: + Condition: CreateVolumeSnapshotResources + Value: !GetAtt CreateVolumeSnapshotsFunction.Arn + DeleteVolumeSnapshotsFunctionArn: + Condition: CreateVolumeSnapshotResources + Value: !GetAtt DeleteVolumeSnapshotsFunction.Arn diff --git a/modules/response-actions/variables.tf b/modules/response-actions/variables.tf new file mode 100644 index 0000000..9484db5 --- /dev/null +++ b/modules/response-actions/variables.tf @@ -0,0 +1,127 @@ +variable "is_organizational" { + description = "(Optional) Set this field to 'true' to deploy Response Actions to an AWS Organization (Or specific OUs)" + type = bool + default = false +} + +variable "regions" { + description = "(Optional) List of regions in which to setup Response Actions lambdas. By default, current region is selected" + type = set(string) + default = [] +} + +variable "name" { + description = "(Optional) Name to be assigned to all child resources. A suffix may be added internally when required. Use default value unless you need to install multiple instances" + type = string + default = "sysdig-secure-ra" +} + +variable "tags" { + description = "(Optional) Tags to be attached to all Sysdig resources." + type = map(string) + default = { + "product" = "sysdig-secure-for-cloud" + } +} + +variable "timeout" { + type = string + description = "Default timeout values for create, update, and delete operations" + default = "30m" +} + +variable "failure_tolerance_percentage" { + type = number + description = "The percentage of accounts, per Region, for which stack operations can fail before AWS CloudFormation stops the operation in that Region" + default = 90 +} + +variable "auto_create_stackset_roles" { + description = "Whether to auto create the custom stackset roles to run SELF_MANAGED stackset. Default is true" + type = bool + default = true +} + +variable "stackset_admin_role_arn" { + description = "(Optional) stackset admin role arn to run SELF_MANAGED stackset" + type = string + default = "" +} + +variable "stackset_execution_role_name" { + description = "(Optional) stackset execution role name to run SELF_MANAGED stackset" + type = string + default = "" +} + +variable "sysdig_secure_account_id" { + type = string + description = "ID of the Sysdig Cloud Account to enable Response Actions integration for (incase of organization, ID of the Sysdig management account)" +} + +variable "is_gov_cloud_onboarding" { + type = bool + default = false + description = "true/false whether Response Actions should be deployed in a govcloud account/org or not" +} + +variable "include_ouids" { + description = "(Optional) ouids to include for organization" + type = set(string) + default = [] +} + +variable "exclude_ouids" { + description = "(Optional) ouids to exclude for organization" + type = set(string) + default = [] +} + +variable "include_accounts" { + description = "(Optional) accounts to include for organization" + type = set(string) + default = [] +} + +variable "exclude_accounts" { + description = "(Optional) accounts to exclude for organization" + type = set(string) + default = [] +} + +variable "api_base_url" { + description = "Base URL for the API service" + type = string +} + +variable "response_actions_version" { + description = "Response Actions version" + type = string + default = "0.0.16" +} + +variable "lambda_packages_base_url" { + description = "Base URL for downloading Lambda deployment packages (e.g., https://example.com/packages). The module will automatically create regional S3 buckets and download the packages." + type = string + default = "https://download.sysdig.com/cloud-response-actions" +} + +variable "enabled_response_actions" { + description = <<-EOF + (Optional) List of response actions to enable. Valid values are: + - "make_private": Creates the configure resource access lambda + - "fetch_cloud_logs": Creates the fetch cloud logs lambda + - "create_volume_snapshot": Creates the create and delete volume snapshot lambdas + - "quarantine_user": Creates the quarantine user and remove policy lambdas + By default, all response actions are enabled. + EOF + type = list(string) + default = ["make_private", "fetch_cloud_logs", "create_volume_snapshot", "quarantine_user"] + validation { + condition = alltrue([ + for action in var.enabled_response_actions : + contains(["make_private", "fetch_cloud_logs", "create_volume_snapshot", "quarantine_user"], action) + ]) + error_message = "Valid values for enabled_response_actions are: make_private, fetch_cloud_logs, create_volume_snapshot, quarantine_user" + } +} diff --git a/modules/response-actions/versions.tf b/modules/response-actions/versions.tf new file mode 100644 index 0000000..4764e74 --- /dev/null +++ b/modules/response-actions/versions.tf @@ -0,0 +1,17 @@ +terraform { + required_version = ">= 1.0.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + random = { + source = "hashicorp/random" + version = ">= 3.1" + } + sysdig = { + source = "sysdiglabs/sysdig" + version = "~> 3.3" + } + } +} diff --git a/modules/vm-workload-scanning/versions.tf b/modules/vm-workload-scanning/versions.tf index b9584db..046f6e4 100644 --- a/modules/vm-workload-scanning/versions.tf +++ b/modules/vm-workload-scanning/versions.tf @@ -8,7 +8,7 @@ terraform { } sysdig = { source = "sysdiglabs/sysdig" - version = "~> 1.48" + version = "~> 3.3" } } }