亚马逊云科技 - 将未知大小的安全组列表添加到 EC2 实例



我们有一个 CloudFormation 模板,用于创建 EC2 实例和安全组(以及许多其他资源(,但我们需要能够将一些额外的预先存在的安全组添加到同一个 EC2 实例。

我们遇到的问题是,预先存在的安全组的数量并不总是相同的,我们希望有一个模板来处理所有情况。

目前,我们有一个如下所示的输入参数:

"WebTierSgAdditional": {
  "Type": "String",
  "Default": "",
  "Description": ""
}

我们将一个以逗号分隔的预先存在的安全组字符串传递给此参数,例如 'sg-abc123,sg-abc456' .

EC2 实例的安全组标签如下所示:

"SecurityGroups": [
  {
    "Ref": "WebSg"
  },
  {
    "Ref": "WebTierSgAdditional"
  }
]

使用此代码,创建实例时,我们会在 AWS 控制台中收到此错误:

必须对所有安全组使用组 ID 或组名,而不是同时使用两者

上面的"WebSg"引用是在模板中其他位置创建的安全组之一。如果我们通过输入参数传入组名称列表而不是组 ID 列表,则会出现相同的错误。

当我们将输入参数的"类型"字段更改为"逗号分隔列表"时,我们得到一个错误,指出:

属性的值 安全组 必须为字符串列表类型

它显然不能用字符串连接列表以使其成为新列表。

当参数只包含一个 sg id 时,所有内容都会成功创建,但是,我们需要能够添加多个 sg id。

我们已经尝试了在安全组标签中使用Fn::Join的许多不同的组合,但似乎没有任何效果。 我们真正需要的是某种"爆炸"函数,从参数字符串中提取单个 id。

有谁知道让它工作的好方法?

AWS

在2017年1月推出了Fn::Split,现在可以这样做。它并不漂亮,但您实际上是使用 Fn::Join 将两个列表转换为字符串,然后将字符串转换回带有 Fn::Split 的列表。

Parameters:
    WebTierSgAdditional:
        Type: CommaDelimitedList
        Default: ''
Conditions:
    HasWebTierSgAdditional: !Not [ !Equals [ '', !Select [ 0, !Ref WebTierSgAdditional ] ] ]
Resources:
    WebSg:
        Type: AWS::EC2::SecurityGroup
        Properties:
            # ...
    Ec2Instance:
        Type: AWS::EC2::Instance
        Properties:
            # ...
            SecurityGroupIds: !Split
                  - ','
                  - !Join
                      - ','
                      -   - !Ref WebSg
                          - !If [ HasWebTierSgAdditional, !Join [ ',', !Ref WebTierSgAdditional ], !Ref 'AWS::NoValue' ]

WebTierSgAdditional 以列表开始,但会转换为字符串,每个项目通过 !Join [ ',', !Ref WebTierSgAdditional ] 用逗号分隔。它包含在另一个列表中,该列表包括!Ref WebSg,该列表也会转换为字符串,每个项目用逗号分隔。 !Split将采用一个字符串并将项目拆分为一个列表,用逗号分隔。

正如您所发现的,问题是您需要发送字符串列表作为安全组,尽管 CloudFormation 为您提供了一种将字符串列表连接到单个分隔字符串的方法,但它并没有提供将分隔字符串拆分为字符串列表的简单方法。

不幸的是,我知道如何做到这一点的唯一方法是使用嵌套堆栈。您可以使用参数类型"逗号分隔列表"将逗号分隔的字符串拆分为字符串列表。

基本方法是这样的:

  1. 在云形成模板中创建安全组。
  2. 使用 Fn::Join 将该安全组 ID 与您的安全组列表合并。
  3. 将该列表传递给嵌套堆栈(类型为 AWS::CloudFormation::Stack 的资源(。
  4. 将该参数作为单独模板中的类型"逗号分隔列表"。
  5. 将参数 ref 传递到 EC2 实例声明中。

我已经使用以下 2 个模板对此进行了测试,它正在工作:

主模板:

{
    "AWSTemplateFormatVersion": "2010-09-09",                                                                                                  
    "Parameters" : {
        "SecurityGroups" : {                                                                        
            "Description" : "A comma separated list of security groups to merge with the web security group",
            "Type" : "String"
        }
    },
    "Resources" : {
        "WebSg" : ... your web security group here,
        "Ec2Instance" : {
            "Type" : "AWS::CloudFormation::Stack",
            "Properties" : {
                "TemplateURL" : "s3 link to the other stack template",
                "Parameters" : {
                    "SecurityGroups" : { "Fn::Join" : [ ",", [ { "Ref" : "WebSg" }, { "Ref" : "SecurityGroups" } ] ] },
                }
            }
        }
    }
}

嵌套模板(通过上面的"模板URL"链接到(:

{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Parameters" : {
        "SecurityGroups" : {
            "Description" : "The Security Groups to launch the instance with",
            "Type" : "CommaDelimitedList"
        },
    }
    "Resources" : {
        "Ec2Instance" : {
            "Type" : "AWS::EC2::Instance",
            "Properties" : {                                     
                ... etc           
                "SecurityGroupIds" : { "Ref" : "SecurityGroups" }
            }
        }
    }
}

我很想知道是否有更好的方法来做到这一点。正如你提到的,它确实需要一个爆炸功能。

我从支持中得到了另一种解决方案,我发现更好。

基本上,它只是从列表中获取每个安全组的索引,然后在末尾添加内部安全组。

"SecurityGroupList": {
    "Description": "List of existing security groups",
    "Type": "CommaDelimitedList"
},
...
"InternalSecurityGroup": {
    "Type": "AWS::EC2::SecurityGroup"
},
...
"SecurityGroupIds": [
    {
        "Fn::Select": [
            "0",
            {
                "Ref": "SecurityGroupList"
            }
        ]
    },
    {
        "Fn::Select": [
            "1",
            {
                "Ref": "SecurityGroupList"
            }
        ]
    },
    {
        "Fn::Select": [
            "2",
            {
                "Ref": "SecurityGroupList"
            }
        ]
    },
    {
        "Ref": "InternalSecurityGroup"
    }
],

希望这对其他人有所帮助。

您实际上可以在 lambda 中编写必要的转换函数,而无需创建子堆栈,以获取必要的列表字符串。

例如:http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/walkthrough-custom-resources-lambda-lookup-amiids.html

事实上,这个函数除了接收列表并将其返回之外,实际上不需要做任何事情。我有一个类似的场景,我有一个List-AWS::EC2::Subnet::Id-,需要将其传递给需要List-String的资源。

过程:

定义 LambdaExecutionRole。

定义 AWS::Lambda::Function 类型的 ListToStringList函数,添加在 cfnresponse 数据 {'Result':event['ResourceProperties']['List']} 中返回的代码。

定义自定义资源 MyList 类型 Custom::MyList,为 GetAtt 的 ServiceToken 提供 ListToStringListFunction Arn。同时将 List 作为引用原始列表的属性传递。

引用 MyList with Fn::GetAtt(MyList,Result(

按照@alanthing的方法,我想出了这个 JSON 格式的解决方案。只需确保参数不为空,因为在我的情况下我没有检查它。另请注意,模板不完整,仅显示相关部分。

将其发布在这里,以防对任何人都有用。

  "Parameters": {
    "SecurityGroups": {
      "Type": "List<AWS::EC2::SecurityGroup::Id>",
      "Description": "List of security groups"
    }
  },
  "Resources": {
    "EC2Instance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "SecurityGroupIds": {
          "Fn::Split": [
            ",",
            {
              "Fn::Sub": [
                "${SGIdsByParam},${SGByLogicalId}",
                {
                  "SGIdsByParam": {
                    "Fn::Join": [",", {
                      "Ref": "SecurityGroups"
                    }]
                  },
                  "SGByLogicalId": {
                    "Fn::GetAtt": ["InstanceSecurityGroup", "GroupId"]
                  }
                }
              ]
            }
          ]
        }
      }

一个更简单的 2 行解决方案如下:

前提是您在映射中具有如下通用AppSecurityGroups,或者可以作为参数:

"AppSecurityGroups" : "sg-xxx,sg-yyyy,sg-zzz"

第二个安全组(特定于服务(ServiceSecurityGroupsg-aaa值作为 CFT 中的参数。

在服务安全组上编写条件,以检查它是否为"无"。

"Conditions": {
  "IsEmptySSG": {
     "Fn::Equals": [
       {"Ref": "ServiceSecurityGroup"}, 
       "None"
     ]
  }
}

然后根据ServiceSecurityGroup不为空的条件合并安全组。

"SecurityGroups" : {
  "Fn::If" : [
    "IsEmptySSG",
    {"Fn::Split" : [ "," , {"Ref" : "AppSecurityGroups"} ]},
    {"Fn::Split" : [ "," , 
        { "Fn::Join" : [ ",", [{"Ref" : "AppSecurityGroups"}, { "Ref" : "ServiceSecurityGroup" }]]}
      ]
    }
  ]
}

这会将安全组的 2 个 CSV 列表动态追加到一个列表中,以供您的 AWS 资源使用。

我已经测试并使用它来解决类似的问题,效果非常好。

最新更新