すべてのプロダクト
Search
ドキュメントセンター

Elastic Compute Service:非準拠のセキュリティグループルールの自動監査と修復

最終更新日:May 16, 2026

Cloud Config を使用してセキュリティグループを監視し、SSH ポート 22 や RDP ポート 3389 などの脆弱なポートを 0.0.0.0/0 に公開するルールを自動的に修復します。

背景情報

セキュリティグループはネットワークトラフィックを制御し、インスタンスのアクセスルールを定義します。複雑なマルチインスタンス環境では、運用保守の見落としや不適切なポリシー設計により、セキュリティグループルールに危険な設定が含まれる可能性があります。

  • すべての IP 範囲に対する脆弱なポートの公開:SSH ポート 22、RDP ポート 3389、または 3306 や 6379 などのデータベースポートを 0.0.0.0/0 に開放すると、インスタンスがブルートフォース攻撃やデータ漏洩のリスクにさらされます。

  • 内部サービスと公開サービスの混在:公開 Web サービスと内部データベースなど、インスタンスの役割を分離しないと、非公開インスタンスに対してすべての IP アクセスが許可され、内部ネットワーク内でのラテラルムーブメントが可能になる恐れがあります。

ソリューション

Cloud Config ルールを作成して、22、3389、3306 などの脆弱なポートが公開されているかどうかを監視します。セキュリティグループルールがこれらのポートをインターネットに公開している場合、Cloud Config はコンプライアンス監査をトリガーし、Function Compute を呼び出して修復ロジックを実行します。修復ロジックは Alibaba Cloud SDK を使用して危険なルールを削除します。修復後、システムはルールを再評価して修正を確認します。Cloud Config コンソールで修復の詳細を確認できます。このプロセスには透明性とトレーサビリティがあり、手動介入を削減し、リソース構成がセキュリティおよびコンプライアンス要件を満たすことを保証します。

image

Cloud Config ルールと Function Compute 修復関数の作成

このソリューションでは、Terraform を使用して Cloud Config ルールと、非準拠リソースを自動的に修復する Function Compute 関数を作成します。

説明

RAM ユーザーの場合は、以下の権限を RAM ユーザーに付与してください。「RAM ユーザーへの権限付与」をご参照ください。

RAM アクセスポリシー

このポリシーは、ECS セキュリティグループルールと Function Compute のサービスおよび関数を管理する権限を付与します。

{
  "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": "*"
    }
  ]
}

説明

サンプルコードはワンクリックで実行できます。 今すぐ実行

重要

このソリューションは、非準拠ルールを直接削除することで修復を行うため、事業継続性に影響を与える可能性があります。必要に応じて Function Compute の修復コードを変更してください。

Terraform コード

variable "region_id" {
  type    = string
  default = "cn-shenzhen"
}
# main.tf


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  # jmespath を jsonpath の代わりに使用します。
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_tea_openapi.client import Client as OpenApiClient
from alibabacloud_openapi_util.client import Client as OpenApiUtilClient
from alibabacloud_tea_util import models as util_models

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):
    # 非準拠リソースに関する情報を取得します。
    resources = parse_json(event)
    # 非準拠リソースを走査して修復を実行します。
    for resource in resources:
        remediation(resource, context)


def parse_json(content):
    """
    文字列を JSON オブジェクトに解析します。
    :param content: JSON 文字列コンテンツ
    :return: JSON オブジェクト
    """
    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"修復対象のリソース情報: {resource}")
    region_id = resource['regionId']
    account_id = resource['accountId']
    resource_id = resource['resourceId']
    resource_type = resource['resourceType']
    if resource_type == 'ACS::ECS::SecurityGroup':
        # 非準拠セキュリティグループの構成を取得し、再検証して評価の正確性を確保します。
        resource_result = get_discovered_resource(context, resource_id, resource_type, region_id)
        configuration = json.loads(resource_result["body"]["DiscoveredResourceDetail"]["Configuration"])
        # セキュリティグループがマネージドセキュリティグループかどうかを確認します。
        is_managed_security_group = configuration.get('ServiceManaged')
        # jmespath を使用して、インバウンド方向で 0.0.0.0/0 へのアクセスを許可するセキュリティグループルールの ID を取得します。
        delete_security_group_rule_ids = jmespath.search(
            "Permissions.Permission[?SourceCidrIp=='0.0.0.0/0'].SecurityGroupRuleId",
            configuration
        )
        # セキュリティグループがマネージドセキュリティグループではなく、0.0.0.0/0 へのアクセスを許可するインバウンドルールがある場合、そのルールを削除します。
        if is_managed_security_group is False and delete_security_group_rule_ids:
            logger.info(f"注意: セキュリティグループルール {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
    config = open_api_models.Config(
        access_key_id=creds.access_key_id,
        access_key_secret=creds.access_key_secret,
        security_token=creds.security_token,
        endpoint=f'ecs.{region_id}.aliyuncs.com'
    )
    client = OpenApiClient(config)
    params = open_api_models.Params(
        style='RPC',  # API スタイル
        version='2014-05-26',  # API バージョン番号
        action='RevokeSecurityGroup',  # API 名
        method='POST',  # リクエストメソッド
        pathname='/',  # API パス。RPC API のデフォルトパスは "/" です。
        protocol='HTTPS',  # API プロトコル
        auth_type='AK',
        req_body_type='json',  # リクエストボディの形式
        body_type='json'  # レスポンスボディの形式
    )
    query = {'RegionId': region_id, 'SecurityGroupId': resource_id, 'SecurityGroupRuleId': security_group_rule_ids}
    # API リクエストオブジェクトを作成します。
    request = open_api_models.OpenApiRequest(
        query=OpenApiUtilClient.query(query),
    )
    runtime = util_models.RuntimeOptions()
    response = client.call_api(params, request, runtime)
    logger.info(f"削除結果: {response}")


# リソースの詳細を取得します。
def get_discovered_resource(context, resource_id, resource_type, region_id):
    """
    API を呼び出してリソースの構成詳細を取得します。
    :param context: Function Compute コンテキスト
    :param resource_id: リソース ID
    :param resource_type: リソースタイプ
    :param region_id: リソースが存在するリージョンの ID
    :return: リソースの詳細
    """
    # Function Compute (FC) のサービスロールには AliyunConfigFullAccess 権限が必要です。
    creds = context.credentials
    config = open_api_models.Config(
        access_key_id=creds.access_key_id,
        access_key_secret=creds.access_key_secret,
        security_token=creds.security_token,
        endpoint='config.cn-shanghai.aliyuncs.com'
    )
    client = OpenApiClient(config)
    params = open_api_models.Params(
        style='RPC',  # API スタイル
        version='2020-09-07',  # API バージョン番号
        action='GetDiscoveredResource',  # API 名
        method='POST',  # リクエストメソッド
        pathname='/',  # API パス。RPC API のデフォルトパスは "/" です。
        protocol='HTTPS',  # API プロトコル
        auth_type='AK',
        req_body_type='json',  # リクエストボディの形式
        body_type='json'  # レスポンスボディの形式
    )
    query = {'ResourceId': resource_id, 'ResourceType': resource_type, 'Region': region_id}
    # API リクエストオブジェクトを作成します。
    request = open_api_models.OpenApiRequest(
        query=OpenApiUtilClient.query(query),
    )
    runtime = util_models.RuntimeOptions()
    try:
        response = client.call_api(params, request, runtime)
        return response
    except Exception as e:
        logger.error('GetDiscoveredResource error: %s' % e)

EOF
  filename = "${path.module}/python/index.py"
}

resource "local_file" "requirements_txt" {
  content  = <<EOF
  alibabacloud-tea-openapi
  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
    ]
  }

  # log_config を明示的に空に設定します。
  log_config {}

  depends_on = [data.local_file.base64_encoded_code]
}

resource "alicloud_config_rule" "default" {
  rule_name    = "SPM0014-sg-disallow-risky-ports-for-all-ips"
  description  = "セキュリティグループがすべての IP 範囲に対して脆弱なポート 22 および 3389 を開放することを禁止します。"
  source_owner = "ALIYUN"
  # (必須、ForceNew) ルールを所有および管理するのがユーザーか Alibaba Cloud かを指定します。有効な値: CUSTOM_FC: ユーザーが所有するカスタムルール。ALIYUN: Alibaba Cloud が所有するマネージドルール。
  source_identifier = "sg-risky-ports-check"
  # ルールの識別子。マネージドルールの場合、値はマネージドルールの名前です。カスタムルールの場合、値はカスタムルールの Alibaba Cloud Resource Name (ARN) です。(必須、ForceNew)
  resource_types_scope = ["ACS::ECS::SecurityGroup"]
  # 監視範囲から除外されるリソースの ID。複数の ID はカンマ (,) で区切ります。このパラメータは、マネージドルールに基づいて作成されたルールにのみ適用されます。カスタムルールの場合、このパラメータは空です。
  config_rule_trigger_types = "ConfigurationItemChangeNotification" # 構成が変更されたときにルールがトリガーされます。
  risk_level = 1 # 1: 重大、2: 警告、3: 情報

  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
}

結果の表示

  1. Cloud Config コンソールにログインしてルールを表示します。

    image

  1. Function Compute コンソールにログインして関数を表示します。

image

View the remediation results

Before remediation

  1. Non-compliant resources are displayed in Cloud Config.

    image

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

    image

After remediation

  1. The automatic remediation details are displayed in Cloud Config.

    image

  1. View the security group rules in the ECS console after remediation.

    image

References