亚马逊AWS官方博客

亚马逊云科技资源标签控制策略实践

背景

亚马逊云科技标签 是以键-值对的形式附加到某一个资源中,成为资源的一个属性,因此通过一致性资源标记(标签策略)来为资源和服务添加一致性的属性字段,可实现成本分析、自动化运维、安全运维、(业务)资产管理等使用场景。

成本分析:分析不同部门的成本,以及在不同资源及服务的成本分布,是非常有效使得成本可见的方案

自动化运维:实现自动化运维,典型场景如服务器自动启停,设置启停时间和执行周期,可以有效降低费用成本

安全运维:实现高效的安全运维管理,典型场景如:

  • 安全更新:使用 Amazon Systems Manager Patch Manager Amazon Lambda 自动执行针对可变计算环境的安全更新策略,通过将实例分配给安全更新组和维护窗口来管理这些环境中可变实例的标记策略,并使可变实例与该应用程序环境定义的安全更新基线保持一致。
  • 事件响应:标签可以在事件响应的事件日志、优先级、调查、沟通、解决、关闭等所有阶段发挥重要作用。标签可以详细说明应该记录事件的位置、应该通知事件的一个或多个团队以及定义的升级优先级。
  • 访问控制:实现基于属性的访问控制策略(ABAC),它允许您基于标签(属性)定义对操作和资源的访问,您可以为 Amazon IAM 实体创建单个策略或者小组策略。这些策略可设计为在IAM实体的标签与资源标签匹配时允许/拒绝操作。帮助您实现基于员工属性的访问控制。

业务视角资产管理:实现资源与服务的业务化描述,帮助您统计和分析不同业务所使用的服务和资源。

通常在资源标签使用场景中,大多依托人工去控制标签的合规性;这样对于未添加标签或者标签不正确的资源很难以监测和管控。基于本文所描述的方案,可以从资源标签的创建、检测、自动修复等多个阶段实现标签的合规;在资源创建方几乎无感的情况下即可完成目标标签的添加。

本文主要介绍了一种实践方法:利用设计的标签内容,实现对资源的访问控制

标签控制概述

对于标签的控制策略来说,整体的思路包含以下几个步骤:

以上持续合规思路应用到标签控制中,可以大幅提高资源标签的准确性和易用性。

指导性控制

在本文示例的场景中,指导性控制策略如下:

  • 将企业组织下的全部账号内的 EC2 和 EBS 卷都附加上 {“aws-project”:”migration”} 的标签。
  • 保证此标签在任何情况下,都可以自动附加和修复。

预防性控制

预防性控制目的是限制对附加了指定标签的资源的操作权限;在亚马逊云科技平台上,可以使用Amazon Organizations服务的 服务控制策略(SCP) 来实现对附加标签资源的预防性控制,为您组织中的所有账户提供对最大可用权限的集中控制。SCP 可帮助确保您的账户符合组织的访问控制准则。

服务控制策略是一种强制控制策略;除此之外,还可以使用标签策略来规范标签的使用。

本文中设计的预防性控制策略包含:

  • 服务控制策略:拒绝未使用一致性标签的 EC2实例的创建。
  • 标签策略:规范目标标签的键值 { “aws-project”: “migration”}

控制台配置

创建服务控制策略

  1. 登录到 Amazon Organizations 控制台
  2. Service control policies (服务控制策略)页面上,选择 Create policy (创建策略)
  3. Create new service control policy (创建新的服务控制策略)页面上,输入策略的 Policy name (策略名称) 和可选 Policy description (策略说明)
  4. 构建策略,按需添加拒绝允许访问的语句。如果您使用 Deny 语句,请执行以下操作:
    1. 在右侧 Edit statement (编辑语句) 窗格的 Add actions (添加操作) 下方,选择服务(Amazon EC2)。当您选择右侧的选项时,JSON 编辑器会更新,以在左侧显示相应的 JSON 策略。
    2. 选择服务后,将打开一个列表,其中包含该服务的可用操作。选择要拒绝的操作ec2:CreateVolume、ec2:RunInstances,左侧的 JSON 将更新,以包含您选择的操作。
    3. 指定要包含在语句中的资源,arn:aws:ec2:*:*:instance/*", "arn:aws:ec2:*:*:volume/*",即所有EC2实例及储存卷
  5. 添加语句判断条件,以标签判断为例,更多语法参考官方文档
    1. Condition Key(判断条件字段名),选择aws:RequestTag
    2. Tag key(标签名称),输入aws-project
    3. Qualifier(匹配方式),选择默认
    4. Operater(操作符),即判断方式,选择Null,即判断指定的标签的健值不为空
    5. 在Value(值)中输入要指定的标签值
  6. 添加完语句后,选择 Create policy (创建策略) 以保存已完成的 SCP。

服务控制策略示例代码如下:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "EC2Tags",
      "Effect": "Deny",
      "Action": [
        "ec2:CreateVolume",
        "ec2:RunInstances"
      ],
      "Resource": [
        "arn:aws:ec2:*:*:instance/*",
        "arn:aws:ec2:*:*:volume/*"
      ],
      "Condition": {
        "Null": {
          "aws:RequestTag/aws-project": "migration"
        }
      }
    }
  ]
}

创建标签策略

  1. 登录到 Amazon Organizations 控制台
  2. 选择策略 -> 标签策略,将以下 JSON 文本复制到对应的策略中
{
    "tags": {
        "aws-project": {
            "tag_key": {
                "@@assign": "aws-project"
            },
            "tag_value": {
                "@@assign": [
                    "migration"
                ]
            },
            "enforced_for": {
                "@@assign": [
                    "ec2:instance",
                    "ec2:volume"
                ]
            }
        }
    }
}

以上策略的含义是如果在请求创建EC2或者EBS的过程中添加了标签键为 ”aws-project”,则其值必须为 “migration”;否则标签就会被拒绝创建。

注意标签策略目前仅支持亚马逊云科技国际区,中国区需要使用替代方案:亚马逊云科技标签策略中国区替代方案 。

检测性控制

检测性控制目的是自动发现非预期的资源标签;在亚马逊云科技的平台上,可以使用 Amazon Config 这个服务来实现对于资源标签的检测。

控制台配置

(1)进入Amazon Config 控制台,确认 Amazon Config 已启用(启用方法参见: https://docs.thinkwithwp.com/zh_cn/config/latest/developerguide/gs-console.html)。

(2)在Rules 页面,选择 Add rule进入 Add rule 页面,默认选择 AWS managed rule。搜索选择 required-tags ;完成以下主要配置:

  • Name (名称):输入自定义规则名称。
  • Scope of changes (更改范围):选择 Resources。本文这里选择 EC2:Instance 和 EC2:Volume。
  • Rule parameters (规则参数):
    • 在本条规则下,最多可以一次性检测 6 个资源标签是否符合预期。
    • 在本文的案例中,配置 tag1Key:aws-project ; tag1Value:migration。
  • Remediation action (修正操作) 我们在后续响应式控制中配置。
  • 完成上述配置后点击 Save。Amazon Config 会自动监听资源的标签配置,告警不符合预期的 EC2 实例和 EBS 卷的标签。

CloudFormation 代码示例

通过 CloudFormation 可以快速创建以上Amazon Config 规则,并通过 CloudFormation 堆栈集的特性,可以将CloudFormation 堆栈一键部署到企业组织多账号体系下。

本文的示例代码如下:

AWSTemplateFormatVersion: '2010-09-09'
Description: 'AWS Config Rules'
Resources:
  ConfigRuleForTags:
    Type: AWS::Config::ConfigRule
    Properties: 
      ConfigRuleName: aws-required-tags-01
      Description: 'Checks whether your resources have the tags that you specify.'
      InputParameters: |
        {"tag1Key": "aws-project",
        "tag1Value": "migration"}
      Scope: 
        ComplianceResourceTypes:
          - "AWS::EC2::Volume"
          - "AWS::EC2::Instance"
      Source: 
        Owner: AWS
        SourceIdentifier: REQUIRED_TAGS

CloudFormation 的部署可以参考亚马逊云科技的官方文档

响应式控制

响应式控制目的是在检测出非预期的资源标签后,自动修复打上预期的标签。本文的响应式控制策略结合 Amazon Config 修正操作特性和自定义 Amazon Systems Manager 自动化文档,实现预期资源标签的自动添加。

控制台配置

配置 System Manager 自动化文档

  1. 进入AWS Systems Manager控制台,点击左侧导航栏Documents,在Documents页面点击 Create Automation。
  2. 在 Create Automation页面,主要配置如下:
    1. Name:输入名称 (config-remediation-for-ec2-tags)
    2. 以Editor方式进行配置,在Editor处输入以下代码后点击创建。
schemaVersion: '0.3'
assumeRole: '{{ AutomationAssumeRole }}'
outputs:
  - SetRequiredTags.SuccessfulResources
  - SetRequiredTags.FailedResources
parameters:
  RequiredTags:
    type: StringMap
    description: (Required) The tags to add to the resources.
    displayType: textarea
  ResourceIDs:
    type: StringList
    description: (Required) The IDs of the resources to add the tags to.
    minItems: 1
    displayType: textarea
  AutomationAssumeRole:
    type: String
    description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf.
    default: ''
    allowedPattern: '^arn:aws(-cn|-us-gov)?:iam::\d{12}:role\/[\w+=,.@_\/-]+|^$'
mainSteps:
  - name: SetRequiredTags
    action: 'aws:executeScript'
    onFailure: Abort
    isCritical: true
    timeoutSeconds: 600
    isEnd: true
    inputs:
      Runtime: python3.7
      Handler: set_required_tags_handler
      InputPayload:
        RequiredTags: '{{RequiredTags}}'
        ResourceIDs: '{{ResourceIDs}}'
      Script: |
        import json
        import boto3
        import os
        def get_account_id():
          client = boto3.client("sts")
          return client.get_caller_identity()["Account"]

        def set_required_tags_handler(event, context):
          client = boto3.client('resourcegroupstaggingapi')
          successesResources = []
          FailedResources = []
          ResourceIDs = event["ResourceIDs"]
          tags = event["RequiredTags"]
          current_account_id = get_account_id()
          runtime_region = os.environ['AWS_REGION']
          for arn in ResourceIDs:
            try:
              if(arn.split("-")[0] == "i"):
                arn = "arn:aws:ec2:{0}:{1}:instance/{2}".format(runtime_region,current_account_id,arn)
                response = client.tag_resources(ResourceARNList = [arn], Tags = tags)
              if(arn.split("-")[0] == "vol"):
                arn = "arn:aws:ec2:{0}:{1}:volume/{2}".format(runtime_region,current_account_id,arn)
                response = client.tag_resources(ResourceARNList = [arn], Tags = tags)              
              successesResources.append(arn)

            except Exception as e:
              errorMsg = str(e)
              FailedResources.append({'ResourceIDs': arn , "error": errorMsg})

          out = {
              "SuccessesResources": successesResources,
              "FailedResources": FailedResources
          }
          return out
    outputs:
      - Name: SuccessfulResources
        Selector: $.Payload.SuccessesResources
        Type: StringList
      - Name: FailedResources
        Selector: $.Payload.FailedResources
        Type: MapList

配置 System Manager 服务角色

  1. 进入 IAM 控制台,创建 IAM 角色,本文配置角色名为:AmazonSSMDocumentAutomationRole
  2. 添加亚马逊云科技托管权限策略:AmazonSSMAutomationRole 和ResourceGroupsandTagEditorFullAccess

配置 Amazon Config

  1. 进入 Amazon Config 控制台,点击左侧导航栏的Rules进入Rule页面,选择编辑在检测性控制创建的 Config 规则 (默认是 aws-required-tags-01)。
  2. 在 Choose remediation action部分,完成以下主要配置:
    1. Remediation action:选择在前一步骤中建立的Amazon Systems Manager 自动化文档名称;本文默认是config-remediation-for-ec2-tags
    2. Auto remediation:若您希望自动修正,选择Yes;若希望以点击修复按钮的形式进行修正,则选择No(建议在启用自动修正前先进行测试)
    3. Resource ID parameter:选择 ResourceIDs
    4. Parameters:
      • AutomationAssumeRole:输入在前一步中为Amazon Systems Manager 自动化文档所创建的角色对应的ARN值。
      • RequiredTags: 输入自动修正后的 Tags。 本文默认为 {“aws-project”:”migration”}

CloudFormation 代码示例

响应式控制 CloudFormation 代码示例如下,可以配置 CloudFormation 堆栈集将此一键部署到整个企业组织下。

AWSTemplateFormatVersion: '2010-09-09'
Description: 'AWS Config Rules'
Resources:
  SSMDocumentForTags:
    Type: AWS::SSM::Document
    Properties: 
      Content: 
        schemaVersion: '0.3'
        assumeRole: '{{ AutomationAssumeRole }}'
        outputs:
          - SetRequiredTags.SuccessfulResources
          - SetRequiredTags.FailedResources
        parameters:
          RequiredTags:
            type: StringMap
            description: (Required) The tags to add to the resources.
            displayType: textarea
          ResourceIDs:
            type: StringList
            description: (Required) The IDs of the resources to add the tags to.
            minItems: 1
            displayType: textarea
          AutomationAssumeRole:
            type: String
            description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf.
            default: ''
            allowedPattern: '^arn:aws(-cn|-us-gov)?:iam::\d{12}:role\/[\w+=,.@_\/-]+|^$'
        mainSteps:
          - name: SetRequiredTags
            action: 'aws:executeScript'
            onFailure: Abort
            isCritical: true
            timeoutSeconds: 600
            isEnd: true
            inputs:
              Runtime: python3.7
              Handler: set_required_tags_handler
              InputPayload:
                RequiredTags: '{{RequiredTags}}'
                ResourceIDs: '{{ResourceIDs}}'
              Script: |
                import json
                import boto3
                import os
                def get_account_id():
                  client = boto3.client("sts")
                  return client.get_caller_identity()["Account"]

                def get_partition(runtime_region):
                  client = boto3.Session()
                  partition = client.get_partition_for_region(runtime_region)
                  return partition

                def set_required_tags_handler(event, context):
                  client = boto3.client('resourcegroupstaggingapi')

                  successesResources = []
                  FailedResources = []
                  ResourceIDs = event["ResourceIDs"]
                  tags = event["RequiredTags"]
                  current_account_id = get_account_id()
                  runtime_region = os.environ['AWS_REGION']
                  partition = get_partition(runtime_region)

                  for arn in ResourceIDs:
                    try:
                      if(arn.split("-")[0] == "i"):
                        arn = "arn:{0}:ec2:{1}:{2}:instance/{3}".format(partition,runtime_region,current_account_id,arn)
                        response = client.tag_resources(ResourceARNList = [arn], Tags = tags)
                      if(arn.split("-")[0] == "vol"):
                        arn = "arn:{0}:ec2:{1}:{2}:volume/{3}".format(partition,runtime_region,current_account_id,arn)
                        response = client.tag_resources(ResourceARNList = [arn], Tags = tags)              
                      successesResources.append(arn)

                    except Exception as e:
                      errorMsg = str(e)
                      FailedResources.append({'ResourceIDs': arn , "error": errorMsg})

                  out = {
                      "SuccessesResources": successesResources,
                      "FailedResources": FailedResources
                  }
                  return out
            outputs:
              - Name: SuccessfulResources
                Selector: $.Payload.SuccessesResources
                Type: StringList
              - Name: FailedResources
                Selector: $.Payload.FailedResources
                Type: MapList
      DocumentFormat: YAML
      DocumentType: Automation
      Name: config-remediation-for-ec2-tags
      Tags: 
        - Key: aws-project
          Value: landing-zone
          
  SSMAutomationAssumeRole:
    Type: AWS::IAM::Role
    Properties: 
      Description: "Role for SSM Automation Actions"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ssm.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      ManagedPolicyArns: 
        - !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonSSMAutomationRole"
        - !Sub "arn:${AWS::Partition}:iam::aws:policy/ResourceGroupsandTagEditorFullAccess"
      RoleName: AmazonSSMDocumentAutomationRole

  EC2TagsRemediationConfiguration:
    Type: "AWS::Config::RemediationConfiguration"
    Properties:
      Automatic: TRUE
      MaximumAutomaticAttempts: 3
      RetryAttemptSeconds: 120
      ConfigRuleName: !Ref ConfigRuleForTags
      Parameters:
        AutomationAssumeRole:
          StaticValue:
            Values: 
              - !GetAtt SSMAutomationAssumeRole.Arn
        ResourceIDs:
          ResourceValue:
            Value: RESOURCE_ID
        RequiredTags:
          StaticValue:
            Values:
              - '{"aws-project":"migration"}'
      TargetId: !Ref SSMDocumentForTags
      TargetType: "SSM_DOCUMENT"

结合检测性控制的 CloudFormation 模板,可以实现一键部署实现对于资源标签的监测和自动修复。

总结

本文以亚马逊云科技持续合规的思想为基石,将资源标签的配置转换为安全策略配置、将标签检测和修正响应转换为相应自动化检测和自动修正响应;实现了合规即代码(Compliance as Code)的合规控制,一定程度上满足终端用户无感知的资源标签控制。

对于非 EC2 的资源,大部分也可以使用 Tagging API 的TagResources 方法来进行标签附加操作;读者可以参考上述响应式控制代码进行二次开发。但请注意,不是所有的资源都支持标签,相关说明参考亚马逊云科技官方文档

本篇作者

王嘉伟

亚马逊云科技专业服务团队云基础架构顾问。有多年的公有云服务运维经验,专注于企业上云基础架构设计、迁移方案设计以及落地实施。

何久

亚马逊云科技专业服务团队安全顾问,专注于企业云安全架构、云安全解决方案等的咨询设计及落地实施。