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

Elastic Compute Service:非準拠のセキュリティグループルールを自動的に監査および修復する

最終更新日:Nov 25, 2025

セキュリティグループルールが、Secure Shell Protocol (SSH) のポート 22 や Remote Desktop Protocol (RDP) のポート 3389 などの脆弱なポートをすべての IP アドレス (0.0.0.0/0) に開放している場合、システムは重大なセキュリティ脅威にさらされます。Cloud Config を使用して、セキュリティグループの構成を継続的にモニターし、非準拠の設定項目を自動的に修復して、システムのセキュリティを確保できます。

背景

エンタープライズクラウド環境では、セキュリティグループはネットワークトラフィックを制御し、サーバーインスタンスのアクセスルールを定義するための主要な方法です。複雑なマルチインスタンスシナリオでは、運用保守 (O&M) の見落としや不備のあるポリシー設計により、セキュリティグループルールに次のようなリスクのある構成が含まれる場合があります。

  • すべての 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 コンソールで非準拠リソースの修復の詳細を表示することもできます。プロセス全体が透明で追跡可能であるため、不正なパブリックアクセスを効果的に防止できます。このソリューションは、O&M 効率を向上させ、手動介入を削減し、リソース構成が常にセキュリティとコンプライアンスの要件を満たすようにします。これにより、環境のセキュリティと安定性が向上します。

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

このソリューションでは、Terraform を使用して Cloud Config ルールと Function Compute を作成し、非準拠リソースを自動的に修復します。これにより、クラウドリソースのコンプライアンスの検出と管理が自動化されます。

説明

Resource Access Management (RAM) ユーザーの場合は、RAM ユーザーに次の権限を付与してください。詳細については、「RAM ユーザーに権限を付与する」をご参照ください。

RAM アクセスポリシー

このカスタムポリシーにより、ユーザーは Elastic Compute Service (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  # jsonpath の代わりに jmespath を使用します。
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"Information about the resource to be remediated: {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"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
    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"Deletion result: {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  = "セキュリティグループが脆弱なポート 22 と 3389 をすべての IP 範囲に開放することを禁止します。"
  source_owner = "ALIYUN"
  # (必須, ForceNew) ルールをユーザーと Alibaba Cloud のどちらが所有および管理するかを指定します。有効な値: CUSTOM_FC: ルールはユーザーが所有するカスタムルールです。● ALIYUN: ルールは Alibaba Cloud が所有するマネージドルールです。
  source_identifier = "sg-risky-ports-check"
  # ルールの識別子。マネージドルールの場合、値はマネージドルールの名前です。カスタムルールの場合、値はカスタムルールの Alibaba Cloud リソースネーム (ARN) です。(必須, ForceNew)
  resource_types_scope = ["ACS::ECS::SecurityGroup"]
  # モニタリング範囲から除外されるリソースの ID。複数の ID はコンマ (,) で区切ります。このパラメーターは、マネージドルールに基づいて作成されたルールにのみ適用されます。カスタムルールの場合、このパラメーターは空です。
  config_rule_trigger_types = "ConfigurationItemChangeNotification" # 構成が変更されるとルールがトリガーされます。
  # 有効な値: One_Hour、Three_Hours、Six_Hours、Twelve_Hours、および TwentyFour_Hours。
  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

修復結果の表示

修復前

  1. 非準拠のリソースが Cloud Config に表示されます。

    image

  2. ECS コンソールにログオンして、セキュリティグループを表示します。

    image

修復後

  1. 自動修復の詳細が Cloud Config に表示されます。

    image

  1. 修復後に ECS コンソールでセキュリティグループを表示します。

    image

関連情報