自定义函数规则定义和运行原理

当您基于函数计算新建自定义规则时,如果规则被触发,配置审计会运行规则对应的函数对资源进行检测,并提供资源合规评估结果。本文通过Python示例为您介绍函数规则的函数代码和函数入参。

什么是自定义函数规则

自定义函数规则是配置审计通过函数计算服务的函数来承载规则代码的自定义规则。

应用场景

当配置审计预置的规则模板和条件规则均不能满足检测资源合规性的需求时,您可以通过编写函数代码,完成复杂场景的合规检测。更多场景和代码示例,请参见自定义函数规则示例库

运行原理

基于函数计算创建自定义规则的运行原理如下图所示。

image.png

基于函数计算创建自定义规则运行原理的说明如下:

  1. 在函数计算中创建函数。

  2. 在配置审计中基于函数计算创建自定义规则,并自动触发评估。

    说明

    当您创建规则后,配置审计会自动触发一次评估。

  3. 配置审计通过配置审计服务关联角色(AliyunServiceRoleForConfig)获取GetFunctionInvokeFunction接口的权限。

  4. 配置审计调用InvokeFunction接口执行函数,获取资源配置和规则信息。

  5. 函数执行对资源的评估。

  6. 函数计算通过PutEvaluations接口将资源评估结果返回给配置审计。

    配置审计对资源评估结果进行保存并在控制台展示,以便查看。您还可以对不合规资源进行修正,或设置将资源数据投递到对应云服务。

函数代码

规则的本质是一段逻辑判断代码,这段代码存放在新建的函数中。在配置审计对资源的持续审计中,通过触发该函数的执行来实现对资源的评估。本函数代码主要有两个函数,具体如下:

函数代码的Python示例如下:

# # !/usr/bin/env python
# # -*- encoding: utf-8 -*-
import json
import logging

from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.request import CommonRequest


logger = logging.getLogger()
# 资源的合规类型。
COMPLIANCE_TYPE_COMPLIANT = 'COMPLIANT'
COMPLIANCE_TYPE_NON_COMPLIANT = 'NON_COMPLIANT'
COMPLIANCE_TYPE_NOT_APPLICABLE = 'NOT_APPLICABLE'
# 资源配置的推送类型。
CONFIGURATION_TYPE_COMMON = 'COMMON'
CONFIGURATION_TYPE_OVERSIZE = 'OVERSIZE'
CONFIGURATION_TYPE_NONE = 'NONE'


# 入口函数,完成业务逻辑编排和处理。
def handler(event, context):
    """
    处理函数
    :param event:事件
    :param context:上下文
    :return:评估结果
    """
    # 校验Event,代码可直接复制。
    evt = validate_event(event)
    if not evt:
        return None
    creds = context.credentials
    rule_parameters = evt.get('ruleParameters')
    result_token = evt.get('resultToken')
    invoking_event = evt.get('invokingEvent')
    ordering_timestamp = evt.get('orderingTimestamp')

    # 资源的配置信息。规则触发机制需要设置为配置变更。当您新建规则或手动执行规则时,配置审计会逐个调用函数触发对所有资源的评估。如果资源配置发生变更,配置审计根据变更的资源信息自动调用函数触发一次资源评估。
    configuration_item = invoking_event.get('configurationItem')
    account_id = configuration_item.get('accountId')
    resource_id = configuration_item.get('resourceId')
    resource_type = configuration_item.get('resourceType')
    region_id = configuration_item.get('regionId')

    # 判断当前推送的资源配置信息是否大于等于配置(100 KB),如果是,则需要调用GetDiscoveredResource查询资源详情。
    configuration_type = invoking_event.get('configurationType')
    if configuration_type and configuration_type == CONFIGURATION_TYPE_OVERSIZE:
        resource_result = get_discovered_resource(creds, resource_id, resource_type, region_id)
        resource_json = json.loads(resource_result)
        configuration_item["configuration"] = resource_json["DiscoveredResourceDetail"]["Configuration"]

    # 对资源进行评估,需要根据实际业务自行实现评估逻辑,以下代码仅供参考。
    compliance_type, annotation = evaluate_configuration_item(
        rule_parameters, configuration_item)

    # 设置评估结果,格式需符合以下示例要求。
    evaluations = [
        {
            'accountId': account_id,
            'complianceResourceId': resource_id,
            'complianceResourceType': resource_type,
            'complianceRegionId': region_id,
            'orderingTimestamp': ordering_timestamp,
            'complianceType': compliance_type,
            'annotation': annotation
        }
    ]

    # 返回评估结果并写入配置审计,代码可直接复制。
    put_evaluations(creds, result_token, evaluations)
    return evaluations


# 根据规则信息和资源配置进行评估。需要根据实际业务自行实现评估逻辑,以下代码仅供参考。
def evaluate_configuration_item(rule_parameters, configuration_item):
    """
    评估逻辑
    :param rule_parameters:规则信息
    :param configuration_item:资源配置
    :return:评估类型
    """
    # 初始化返回值
    compliance_type = COMPLIANCE_TYPE_NON_COMPLIANT
    annotation = None

    # 获取资源配置完整信息
    full_configuration = configuration_item['configuration']
    if not full_configuration:
        annotation = 'Configuration is empty.'
        return compliance_type, annotation

    # 转换为JSON
    configuration = parse_json(full_configuration)
    if not configuration:
        annotation = 'Configuration:{} is invalid.'.format(full_configuration)
        return compliance_type, annotation
    return compliance_type, annotation


def validate_event(event):
    """
    校验Event
    :param event:Event
    :return:JSON对象
    """
    if not event:
        logger.error('Event is empty.')
    evt = parse_json(event)
    logger.info('Loading event: %s .' % evt)

    if 'resultToken' not in evt:
        logger.error('ResultToken is empty.')
        return None
    if 'ruleParameters' not in evt:
        logger.error('RuleParameters is empty.')
        return None
    if 'invokingEvent' not in evt:
        logger.error('InvokingEvent is empty.')
        return None
    return evt


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 put_evaluations(creds, result_token, evaluations):
    """
    调用API返回并写入评估结果。
    :param context:函数计算上下文
    :param result_token:回调令牌
    :param evaluations:评估结果
    :return: None
    """
    # 需具备权限AliyunConfigFullAccess的函数计算FC的服务角色。
    client = AcsClient(creds.access_key_id, creds.access_key_secret, region_id='cn-shanghai')

    # 新建Request,并设置参数,Domain为config.cn-shanghai.aliyuncs.com。
    request = CommonRequest()
    request.set_domain('config.cn-shanghai.aliyuncs.com')
    request.set_version('2019-01-08')
    request.set_action_name('PutEvaluations')
    request.add_body_params('ResultToken', result_token)
    request.add_body_params('Evaluations', evaluations)
    request.add_body_params('SecurityToken', creds.security_token)
    request.set_method('POST')

    try:
        response = client.do_action_with_exception(request)
        logger.info('PutEvaluations with request: {}, response: {}.'.format(request, response))
    except Exception as e:
        logger.error('PutEvaluations error: %s' % e)


# 获取资源详情,代码可直接复制。
def get_discovered_resource(creds, resource_id, resource_type, region_id):
    """
    调用API获取资源配置详情
    :param context:函数计算上下文
    :param resource_id:资源ID
    :param resource_type:资源类型
    :param region_id:资源所属地域ID
    :return: 资源详情
    """
    # 需具备权限AliyunConfigFullAccess的函数计算FC的服务角色。
    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)
        
重要

推荐您通过配置审计控制台查看代码中资源配置(configuration)的核心参数,获取待设置规则的参数名称。具体操作,请参见查看资源信息中的步骤 6

函数入参

函数计算的函数入参包括资源配置和规则信息两部分。资源配置信息保存在configurationItem中,规则的入参信息保存在ruleParameters中。配置审计向函数计算推送的内容因规则的触发机制而不同,具体如下:

说明

关于如何从函数计算中获取函数入参,请参见查看调用日志

  • 当规则的触发机制仅设置为周期执行时,配置审计不会推送资源配置信息至函数计算。

    当新创建规则初次执行、规则周期自动执行和规则手动触发执行时,配置审计只会推送一条不包含资源配置信息的记录,JSON示例如下:

    {
        "orderingTimestamp": 1716365226714,
        "invokingEvent": {
            "accountId": 120886317861****,
            "messageType": "ScheduledNotification",
            "notificationCreationTimestamp": 1716365226714,
            "configurationType": "NONE"
        },
        "ruleParameters": {
            "CpuCount": "2"
        },
        "resultToken": "HLQr3BZx/C+DLjwudFcYdXxZFPF2HnGqlg1uHceZ5kDEFeQF2K5LZGofyhn+GE4NP5VgkwANUH3qcdeSjWwODk1ymtmLWLzFV4JForVWYIKdbwwhbDBOgVwF7Ov9c3uVCNz/KpxNElwhTzMkZB95U1vmLs4vUYXuB/Txw4jiCYBYZZnVumhwXWswTLvAhIe5Y451FckObyM3I47AaB+4KtDW3I5q8O+Kx7eSYkqqGTawmJEYjvWXz9CHHMLFtNYyJX54a35mpVdxFSvgeXYDJTStxqb+d9UH/162fZh7T78OHxpQZgl8bcXzZhml****"
    }
  • 当规则的触发机制包括配置变更时,配置审计会推送资源配置信息至函数计算。

    当新创建规则初次执行、规则周期自动执行和规则手动触发执行时,配置审计会将资源配置信息逐条推送至函数计算。当新增资源或已有资源发生变化时,配置审计仅将变更的单个资源配置信息推送至函数计算,JSON示例如下:

    {
        "orderingTimestamp":1695786337959,
        "invokingEvent":{
            "accountId":120886317861****,
            "messageType":"Manual",
            "notificationCreationTimestamp":1695786337959,
            "configurationType":"COMMON",
            "configurationItem":{
                "accountId":120886317861****,
                "arn":"acs:ecs:ap-southeast-1:120886317861****:instance/i-t4n0vr6x7v54jdbu****",
                "availabilityZone":"ap-southeast-1a",
                "regionId":"ap-southeast-1",
                "configuration":"{\\"ResourceGroupId\\":\\"\\",\\"Memory\\":4096,\\"InstanceChargeType\\":\\"PostPaid\\",\\"Cpu\\":2,\\"OSName\\":\\"Alibaba Cloud Linux  3.2104 LTS 64\xe4\xbd\x8d\\",\\"InstanceNetworkType\\":\\"vpc\\",\\"InnerIpAddress\\":{\\"IpAddress\\":[]},\\"ExpiredTime\\":\\"2099-12-31T15:59Z\\",\\"ImageId\\":\\"aliyun_3_x64_20G_alibase_20230727.vhd\\",\\"EipAddress\\":{\\"AllocationId\\":\\"\\",\\"IpAddress\\":\\"\\",\\"InternetChargeType\\":\\"\\"},\\"ImageOptions\\":{},\\"VlanId\\":\\"\\",\\"HostName\\":\\"iZt4n0vr6x7v54jdbuk****\\",\\"Status\\":\\"Running\\",\\"HibernationOptions\\":{\\"Configured\\":false},\\"MetadataOptions\\":{\\"HttpTokens\\":\\"\\",\\"HttpEndpoint\\":\\"\\"},\\"InstanceId\\":\\"i-t4n0vr6x7v54jdbu****\\",\\"StoppedMode\\":\\"Not-applicable\\",\\"CpuOptions\\":{\\"ThreadsPerCore\\":2,\\"Numa\\":\\"ON\\",\\"CoreCount\\":1},\\"StartTime\\":\\"2023-08-18T09:02Z\\",\\"DeletionProtection\\":false,\\"VpcAttributes\\":{\\"PrivateIpAddress\\":{\\"IpAddress\\":[\\"192.168.XX.XX\\"]},\\"VpcId\\":\\"vpc-t4nmwd0l9a7aj09yr****\\",\\"VSwitchId\\":\\"vsw-t4njclm0dlz2szayi****\\",\\"NatIpAddress\\":\\"\\"},\\"SecurityGroupIds\\":{\\"SecurityGroupId\\":[\\"sg-t4n5pulxj2lvechw****\\"]},\\"InternetChargeType\\":\\"PayByTraffic\\",\\"InstanceName\\":\\"zs-test-peer****\\",\\"DeploymentSetId\\":\\"\\",\\"InternetMaxBandwidthOut\\":0,\\"SerialNumber\\":\\"8c3fadf7-2ea1-4486-84ce-7784aeb7****\\",\\"OSType\\":\\"linux\\",\\"CreationTime\\":\\"2023-08-18T09:02Z\\",\\"AutoReleaseTime\\":\\"\\",\\"Description\\":\\"\\",\\"InstanceTypeFamily\\":\\"ecs.c7\\",\\"DedicatedInstanceAttribute\\":{\\"Tenancy\\":\\"\\",\\"Affinity\\":\\"\\"},\\"PublicIpAddress\\":{\\"IpAddress\\":[]},\\"GPUSpec\\":\\"\\",\\"NetworkInterfaces\\":{\\"NetworkInterface\\":[{\\"Type\\":\\"Primary\\",\\"PrimaryIpAddress\\":\\"192.168.XX.XX\\",\\"MacAddress\\":\\"00:16:3e:04:XX:XX\\",\\"NetworkInterfaceId\\":\\"eni-t4n16tmnpp794y1o****\\",\\"PrivateIpSets\\":{\\"PrivateIpSet\\":[{\\"PrivateIpAddress\\":\\"192.168.XX.XX\\",\\"Primary\\":true}]}}]},\\"SpotPriceLimit\\":0.0,\\"SaleCycle\\":\\"\\",\\"DeviceAvailable\\":true,\\"InstanceType\\":\\"ecs.c7.large\\",\\"OSNameEn\\":\\"Alibaba Cloud Linux  3.2104 LTS 64 bit\\",\\"SpotStrategy\\":\\"NoSpot\\",\\"IoOptimized\\":true,\\"ZoneId\\":\\"ap-southeast-1a\\",\\"ClusterId\\":\\"\\",\\"EcsCapacityReservationAttr\\":{\\"CapacityReservationPreference\\":\\"\\",\\"CapacityReservationId\\":\\"\\"},\\"DedicatedHostAttribute\\":{\\"DedicatedHostId\\":\\"\\",\\"DedicatedHostName\\":\\"\\",\\"DedicatedHostClusterId\\":\\"\\"},\\"GPUAmount\\":0,\\"OperationLocks\\":{\\"LockReason\\":[]},\\"InternetMaxBandwidthIn\\":-1,\\"Recyclable\\":false,\\"RegionId\\":\\"ap-southeast-1\\",\\"CreditSpecification\\":\\"\\"}",
                "captureTime":1695786337959,
                "resourceCreateTime":1692349320000,
                "resourceId":"i-t4n0vr6x7v54jdbu****",
                "resourceName":"zs-test-peer****",
                "resourceGroupId":"rg-acfmw3ty5y7****",
                "resourceType":"ACS::ECS::Instance",
                "tags":"{}"
            }
        },
        "ruleParameters":{
            "CpuCount":"2"
        },
        "resultToken":"HLQr3BZx/C+DLjwudFcYdXxZFPF2HnGqlg1uHceZ5kDEFeQF2K5LZGofyhn+GE4NP5VgkwANUH3qcdeSjWwODk1ymtmLWLzFV4JForVWYIKdbwwhbDBOgVwF7Ov9c3uVCNz/KpxNElwhTzMkZB95U1vmLs4vUYXuB/Txw4jiCYBYZZnVumhwXWswTLvAhIe5Y451FckObyM3I47AaB+4KtDW3I5q8O+Kx7eSYkqqGTawmJEYjvWXz9CHHMLFtNYyJX54a35mpVdxFSvgeXYDJTStxqb+d9UH/162fZh7T78OHxpQZgl8bcXzZhml****"
    }

    当推送的资源配置信息大于等于100 KB时,配置审计只推送资源的摘要信息,不包含资源配置的configuration字段,JSON示例如下:

    说明

    当您需要获取完整的资源配置信息时,可调用接口GetDiscoveredResource

    {
        "orderingTimestamp":1695786337959,
        "invokingEvent":{
            "accountId":120886317861****,
            "messageType":"Manual",
            "notificationCreationTimestamp":1695786337959,
            "configurationType":"OVERSIZE",
            "configurationItem":{
                "accountId":120886317861****,
                "arn":"acs:ecs:ap-southeast-1:120886317861****:instance/i-t4n0vr6x7v54jdbu****",
                "availabilityZone":"ap-southeast-1a",
                "regionId":"ap-southeast-1",
                "captureTime":1695786337959,
                "resourceCreateTime":1692349320000,
                "resourceId":"i-t4n0vr6x7v54jdbu****",
                "resourceName":"zs-test-peer****",
                "resourceGroupId":"rg-acfmw3ty5y7****",
                "resourceType":"ACS::ECS::Instance",
                "tags":"{}"
            }
        },
        "ruleParameters":{
            "CpuCount":"2"
        },
        "resultToken":"HLQr3BZx/C+DLjwudFcYdXxZFPF2HnGqlg1uHceZ5kDEFeQF2K5LZGofyhn+GE4NP5VgkwANUH3qcdeSjWwODk1ymtmLWLzFV4JForVWYIKdbwwhbDBOgVwF7Ov9c3uVCNz/KpxNElwhTzMkZB95U1vmLs4vUYXuB/Txw4jiCYBYZZnVumhwXWswTLvAhIe5Y451FckObyM3I47AaB+4KtDW3I5q8O+Kx7eSYkqqGTawmJEYjvWXz9CHHMLFtNYyJX54a35mpVdxFSvgeXYDJTStxqb+d9UH/162fZh7T78OHxpQZgl8bcXzZhml****"
    }

函数入参的分类及其主要参数说明如下表所示。

分类

参数

描述

资源配置

configurationItem

资源的配置信息。包括:资源所属的阿里云账号ID、资源的ARN、资源所属的可用区、资源所属的地域、资源的详细配置、配置审计发现资源变更事件并生成日志的时间戳、新建资源的时间戳、资源状态、资源ID、资源名称、资源类型和标签。

configurationType

资源配置的推送类型。取值:

  • COMMON:当configurationItem小于100 KB时,资源的配置信息正常推送。

  • OVERSIZE:当configurationItem大于等于100 KB时,调用GetDiscoveredResource接口查询资源详情。

规则信息

orderingTimestamp

评估执行的开始时间戳。

invokingEvent

调用事件。

accountId

调用事件的账号ID。

messageType

消息类型。取值:

  • Manual:手动执行。

  • ConfigurationItemChangeNotification:配置变更。

  • ScheduledNotification:周期执行。

notificationCreationTimestamp

规则触发时的时间戳。

ruleParameters

自定义规则的入参。包括:规则入参名称和期望值。

resultToken

函数计算中的回调令牌。