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
}