Create Cloud Config rules with Terraform to automate audit and remediation

更新时间:
复制 MD 格式

Exposing high-risk ports such as 22 (SSH) and 3389 (RDP) to all IP addresses (0.0.0.0/0) in a security group poses a major security risk. You can use Terraform to create Cloud Config rules to continuously monitor your security group configurations and automatically remediate non-compliant settings, ensuring your system remains secure.

Background

In an enterprise cloud environment, security groups are a core component for controlling network traffic and access to instances. However, in complex, multi-instance scenarios, operational oversights or flawed policy designs can lead to high-risk configurations. Common issues include:

  • Exposing high-risk ports to the internet: For example, opening SSH (port 22), RDP (port 3389), or database service ports (such as 3306 or 6379) to the public internet (0.0.0.0/0). This makes your instances prime targets for attacks like brute-force attempts and data breaches.

  • Mixing internal and public services: Failing to distinguish between instance roles, such as public-facing web servers and internal databases. This can lead to mistakenly granting broad network access to internal services, creating a risk of lateral movement within your network.

Solution

This solution uses Terraform to create Cloud Config rules for continuous monitoring of security group configurations. These rules detect when high-risk ports like 22, 3389, or 3306 are open to the public. When a security group rule is added or modified to allow public access to these ports, Cloud Config automatically triggers a compliance audit. Cloud Config then invokes a Function Compute function to perform the custom remediation. This function uses the Alibaba Cloud SDK to adjust the security group settings, for example, by deleting the non-compliant rule. After remediation, the system re-evaluates the rules to confirm the issue is resolved. You can view the remediation details for any non-compliant resource in the Cloud Config console. This entire process is transparent and traceable, effectively preventing unauthorized public access. This automated approach improves operational efficiency, reduces manual intervention, and ensures your resource configurations meet security standards, thereby strengthening your environment's security and stability.

image

Create Cloud Config rules with Function Compute remediation

This solution uses Terraform to create Cloud Config rules and integrates with Function Compute for automatic remediation. This enables automated detection and management of cloud resource compliance.

Note

If you are a RAM user, grant the required permissions to the RAM user. For more information, see Grant permissions to RAM users.

RAM policy

This custom RAM policy allows users to manage ECS security group rules and Function Compute services and functions.

{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iacservice:CreateExplorerModuleVersion",
        "iacservice:GetExplorerModule",
        "iacservice:CreateExplorerModule",
        "iacservice:ListExplorerModules",
        "iacservice:UpdateExplorerModuleAttribute",
        "iacservice:DeleteExplorerModule"
      ],
      "Resource": "acs:iacservice:*:*:explorermodule/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "iacservice:CreateExplorerTask",
        "iacservice:UpdateExplorerTaskAttribute",
        "iacservice:GetExplorerTask",
        "iacservice:DeleteExplorerTask"
      ],
      "Resource": "acs:iacservice:*:*:explorertask/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "iacservice:CreateJob",
        "iacservice:GetJob",
        "iacservice:listJobs",
        "iacservice:OperateJob"
      ],
      "Resource": "acs:iacservice:*:*:explorertask/*/job/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "iacservice:ListResources",
        "iacservice:ListExplorerHistories",
        "iacservice:CreateExplorerHistory",
        "iacservice:ExportTerraformCode"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ecs:RevokeSecurityGroup",
        "ecs:DescribeSecurityGroups",
        "ecs:DescribeSecurityGroupAttributes"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "fc:CreateService",
        "fc:DeleteService",
        "fc:UpdateService",
        "fc:CreateFunction",
        "fc:DeleteFunction",
        "fc:UpdateFunction",
        "fc:InvokeFunction",
        "fc:ListServices",
        "fc:ListFunctions",
        "fc:GetService",
        "fc:GetFunction"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "config:*",
      "Resource": "*"
    }
  ]
}
Note

You can run the sample code in this topic with one click. Run

Important

This solution directly deletes non-compliant security group rules for automatic remediation. This action may impact business continuity. You must modify the Function Compute remediation code to meet your specific business requirements.

Terraform code

variable "region_id" {
  type    = string
  default = "cn-shenzhen"
}
provider "alicloud" {
  region = var.region_id
}
resource "local_file" "python_script" {
  content  = <<EOF
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
import sys
sys.path.append('/opt/python')
import json
import logging
import jmespath  # Use jmespath instead of jsonpath
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.auth.credentials import AccessKeyCredential
from aliyunsdkcore.auth.credentials import StsTokenCredential
from aliyunsdkcore.request import CommonRequest
logger = logging.getLogger()
def handler(event, context):
    logger.info(f"This is event: {str(event, encoding='utf-8')}")
    get_resources_non_compliant(event, context)
def get_resources_non_compliant(event, context):
    # Get information about non-compliant resources.
    resources = parse_json(event)
    # Iterate through the non-compliant resources and perform remediation.
    for resource in resources:
        remediation(resource, context)
def parse_json(content):
    """
    Parse string to json object
    :param content: json string content
    :return: Json object
    """
    try:
        return json.loads(content)
    except Exception as e:
        logger.error('Parse content:{} to json error:{}.'.format(content, e))
        return None
def remediation(resource, context):
    logger.info(f"Remediation target resource information: {resource}")
    region_id = resource['regionId']
    account_id = resource['accountId']
    resource_id = resource['resourceId']
    resource_type = resource['resourceType']
    if resource_type == 'ACS::ECS::SecurityGroup' :
        # Get the configuration of the non-compliant security group and re-verify to ensure an accurate assessment.
        resource_result = get_discovered_resource(context, resource_id, resource_type, region_id)
        resource_json = json.loads(resource_result)
        configuration = json.loads(resource_json["DiscoveredResourceDetail"]["Configuration"])
        # Check if it is a service-managed security group.
        is_managed_security_group = configuration.get('ServiceManaged')
        # Use jmespath to find the IDs of inbound rules that allow traffic from 0.0.0.0/0.
        delete_security_group_rule_ids = jmespath.search(
            "Permissions.Permission[?SourceCidrIp=='0.0.0.0/0'].SecurityGroupRuleId",
            configuration
        )
        # If it is not a service-managed security group and has inbound rules from 0.0.0.0/0, delete them.
        if is_managed_security_group is False and delete_security_group_rule_ids:
            logger.info(f"Note: Deleting security group rule {region_id}:{resource_id}:{delete_security_group_rule_ids}")
            revoke_security_group(context, region_id, resource_id, delete_security_group_rule_ids)
def revoke_security_group(context, region_id, resource_id, security_group_rule_ids):
    creds = context.credentials
    client = AcsClient(creds.access_key_id, creds.access_key_secret, region_id=region_id)
    request = CommonRequest()
    request.set_accept_format('json')
    request.set_domain(f'ecs.{region_id}.aliyuncs.com')
    request.set_method('POST')
    request.set_protocol_type('https') # https | http
    request.set_version('2014-05-26')
    request.set_action_name('RevokeSecurityGroup')
    request.add_query_param('RegionId', region_id)
    for index, value in enumerate(security_group_rule_ids):
        request.add_query_param(f'SecurityGroupRuleId.{index + 1}', value)
    request.add_query_param('SecurityGroupId', resource_id)
    request.add_query_param('SecurityToken', creds.security_token)
    response = client.do_action_with_exception(request)
    logger.info(f"Deletion result: {str(response, encoding='utf-8')}")
# Get resource details.
def get_discovered_resource(context, resource_id, resource_type, region_id):
    """
    Call an API operation to obtain the details of a resource.
    :param context: The Function Compute context.
    :param resource_id: The ID of the resource.
    :param resource_type: The type of the resource.
    :param region_id: The ID of the region where the resource is located.
    :return: The resource details.
    """
    # The Function Compute service role requires the AliyunConfigFullAccess permission.
    creds = context.credentials
    client = AcsClient(creds.access_key_id, creds.access_key_secret, region_id='cn-shanghai')
    request = CommonRequest()
    request.set_domain('config.cn-shanghai.aliyuncs.com')
    request.set_version('2020-09-07')
    request.set_action_name('GetDiscoveredResource')
    request.add_query_param('ResourceId', resource_id)
    request.add_query_param('ResourceType', resource_type)
    request.add_query_param('Region', region_id)
    request.add_query_param('SecurityToken', creds.security_token)
    request.set_method('GET')
    try:
        response = client.do_action_with_exception(request)
        resource_result = str(response, encoding='utf-8')
        return resource_result
    except Exception as e:
        logger.error('GetDiscoveredResource error: %s' % e)
  EOF
  filename = "${path.module}/python/index.py"
}
resource "local_file" "requirements_txt" {
  content  = <<EOF
  aliyun-python-sdk-core==2.15.2
  jmespath>=0.10.0
  EOF
  filename = "${path.module}/python/requests/requirements.txt"
}
locals {
  code_dir          = "${path.module}/python/"
  archive_output    = "${path.module}/code.zip"
  base64_output     = "${path.module}/code_base64.txt"
}
data "archive_file" "code_package" {
  type        = "zip"
  source_dir  = local.code_dir
  output_path = local.archive_output
  depends_on = [
    local_file.python_script,
    local_file.requirements_txt,
  ]
}
resource "null_resource" "upload_code" {
  provisioner "local-exec" {
    command = <<EOT
      base64 -w 0 ${local.archive_output} > ${local.base64_output}
    EOT
    interpreter = ["sh", "-c"]
  }
  depends_on = [data.archive_file.code_package]
}
data "local_file" "base64_encoded_code" {
  filename = local.base64_output
  depends_on = [null_resource.upload_code]
}
resource "alicloud_fcv3_function" "fc_function" {
  runtime       = "python3.10"
  handler       = "index.handler"
  function_name = "HHM-FC-TEST"
  role          = alicloud_ram_role.role.arn
  code {
    zip_file = data.local_file.base64_encoded_code.content
  }
  lifecycle {
    ignore_changes = [
      code
    ]
  }
  # Explicitly set log_config to empty.
  log_config {}
  depends_on = [data.local_file.base64_encoded_code]
}
resource "alicloud_config_rule" "default" {
  rule_name   = "SPM0014 security groups must not allow high-risk ports to be open to all IP addresses"
  description = "Prohibits security groups from opening high-risk ports 22 and 3389 to all IP address ranges."
  source_owner = "ALIYUN"
  source_identifier = "sg-risky-ports-check"
  resource_types_scope = ["ACS::ECS::SecurityGroup"]
  config_rule_trigger_types = "ConfigurationItemChangeNotification" # The rule is triggered by configuration changes.
  risk_level = 1                                                           # 1: Critical, 2: Warning, 3: Info
  input_parameters = {
    "ports" : "22,3389"
  }
}
resource "alicloud_config_remediation" "default" {
  config_rule_id          = alicloud_config_rule.default.id
  remediation_template_id = alicloud_fcv3_function.fc_function.function_arn
  remediation_source_type = "CUSTOM"
  invoke_type             = "AUTO_EXECUTION"
  params                  = "{}"
  remediation_type        = "FC"
}
resource "random_integer" "default" {
  min = 10000
  max = 99999
}
resource "alicloud_ram_role" "role" {
  name        = "tf-example-role-${random_integer.default.result}"
  document    = <<EOF
{
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "fc.aliyuncs.com"
        ]
      }
    }
  ],
  "Version": "1"
}
EOF
  description = "Ecs ram role."
  force       = true
}
resource "alicloud_ram_policy" "policy" {
  policy_name     = "tf-example-ram-policy-${random_integer.default.result}"
  policy_document = <<EOF
  {
    "Statement": [
      {
        "Action":  [
          "config:GetDiscoveredResource",
          "ecs:RevokeSecurityGroup"
        ],
        "Effect":  "Allow",
        "Resource": ["*"]
      }
    ],
      "Version": "1"
  }
  EOF
  description     = "this is a policy test"
  force           = true
}
resource "alicloud_ram_role_policy_attachment" "attach" {
  policy_name = alicloud_ram_policy.policy.policy_name
  policy_type = "Custom"
  role_name   = alicloud_ram_role.role.name
}

View creation results

  1. Log on to the Cloud Config console to view the rule.

    The rules list shows the created rule SPM0014 security groups must not allow high-risk ports to be open to all IP addresses. The rule status is In effect, the number of non-compliant resources is 2, the risk level is Critical, and the creation channel is Cloud Config.

  1. Log on to the Function Compute console to view the function.

In the Functions list, you can see the successfully created function HHM-FC-TEST, which uses the Python 3.10 runtime.

View remediation results

Before remediation

  1. View non-compliant resources in Cloud Config.

    The evaluation results for the rule SPM0014 security groups must not allow high-risk ports to be open to all IP addresses show that two of the three evaluated security groups are non-compliant. The non-compliant resources are sg-bp1lilp19h7x4gz4dfm9 (sg-20250411) and sg-bp1hcnsurijy302b8bpi (sg-20230520), indicating that these two ECS security groups expose port 22 or 3389 to all IP addresses.

  2. Log on to the ECS console to view the security group.

    In the left-side navigation pane, choose Network & Security > Security Groups. Go to the security group details page to view the inbound rules. The security group has six inbound rules, all using the Custom TCP protocol. The port ranges are 23/23, 1433/1433, 3389/3389, 1/65535, 6379/6379, and 3306/3306. The authorization object for all rules is 0.0.0.0/0 (all IPv4 addresses), and the authorization policy is Allow.

After remediation

  1. View automatic remediation details in Cloud Config.

    On the rule details page, click the Remediation Details tab to view the remediation settings and execution results. The remediation type is custom remediation, the execution method is Automatic, and the remediation template is acs:fc:cn-shenzhen:1595149171858855:functions/HHM-FC-TEST. The execution results list shows a Success status for the remediation of all three ECS security groups.

  1. View the ECS security group after remediation.

    In the left-side navigation pane, click Security Groups, select the China (Hangzhou) region, and go to the details page for security group sg-bp1hcnsurijy302b8bpi. The Inbound Rules list is empty, which means no inbound rules are configured.

Related documents