将未知大小的安全组列表添加到EC2实例

时间:2014-04-28 08:11:24

标签: amazon-web-services amazon-ec2 amazon-cloudformation

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

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

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

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

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

EC2实例的SecurityGroup标记如下所示:

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

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

  

必须对所有安全组使用group-id或group-name,而不是同时使用两者

上面的'WebSg'参考是在模板中其他地方创建的安全组之一。如果我们通过输入参数传入组名列表而不是组ID列表,则会出现同样的错误。

当我们将输入参数的“类型”字段更改为“CommaDelimitedList”时,我们收到错误消息:

  

属性值SecurityGroups的类型为List of String

显然无法使用字符串连接列表以使其成为新列表。

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

我们尝试过在SecurityGroups标记中使用Fn::Join的许多不同组合,但似乎没有任何效果。我们真正需要的是某种“爆炸”功能,用于从参数字符串中提取单个id。

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

6 个答案:

答案 0 :(得分:10)

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

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

基本方法是:

  1. 在您的云计算模板中创建您的安全组。
  2. 使用Fn :: Join。将此安全组ID与您的安全组列表合并。
  3. 将该列表传递给嵌套堆栈(AWS :: CloudFormation :: Stack类型的资源)。
  4. 将该参数作为单独模板中的“CommaDelimitedList”类型。
  5. 将参数ref传递给您的EC2实例声明。
  6. 我已经使用以下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" } ] ] },
                    }
                }
            }
        }
    }
    

    嵌套模板(由上面的“TemplateURL”链接):

    {
        "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" }
                }
            }
        }
    }
    

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

答案 1 :(得分:7)

AWS在Fn::Split中引入了January 2017,现在可以实现。它并不漂亮,但您实际上是将两个列表转换为Fn::Join的字符串,然后将字符串转换回Fn::Split的列表。

Parameters:

    WebTierSgAdditional:
        Type: CommaDelimitedList
        Default: ''
        Description: ''

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将采用字符串并将项目拆分为以逗号分隔的列表。

答案 2 :(得分:3)

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

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

"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"
    }
],

希望这有助于其他任何人。

答案 3 :(得分:1)

你实际上可以在lambda中编写必要的转换函数而不创建子包,以获得必要的List-String。

例如: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的ListToStringListFunction,添加在cfnresponse数据中返回的代码{'结果':event [' ResourceProperties'] ['列表'。]}

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

使用Fn :: GetAtt(MyList,Result)引用MyList

答案 4 :(得分:1)

更简单的2线解决方案如下:

如果你在映射中有一个如下所示的公共AppSecurityGroups,或者可以作为参数:

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

和第二个安全组(特定于服务)ServiceSecurityGroup,其值为sg-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资源使用的安全组列表中。

我已经测试并将其用于类似的问题,效果非常好。

答案 5 :(得分:0)

按照@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"]
                  }
                }
              ]
            }
          ]
        }
      }