Добавить список групп безопасности неизвестного размера в экземпляр EC2

У нас есть шаблон CloudFormation, который создает экземпляр EC2 и группу безопасности (среди многих других ресурсов), но мы должны иметь возможность добавлять некоторые дополнительные ранее существовавшие группы безопасности в тот же экземпляр EC2.

Проблема заключается в том, что количество ранее существовавших групп безопасности не всегда будет одинаковым, и мы хотим иметь один шаблон, который будет обрабатывать все случаи.

В настоящее время у нас есть входной параметр, который выглядит так:

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

Мы передаем этому параметру строку с разделителями-запятыми ранее существовавших групп безопасности, например 'sg-abc123,sg-abc456'.

Тег SecurityGroup экземпляра EC2 выглядит следующим образом:

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

С помощью этого кода, когда экземпляр создается, мы получаем эту ошибку в консоли AWS:

Необходимо использовать либо идентификатор группы, либо имя группы для всех групп безопасности, а не одновременно в обоих случаях

Параметр "WebSg" выше - это одна из групп безопасности, созданных в другом месте шаблона. Эта же ошибка появляется, если мы передаем список имен групп, а не список идентификаторов группы через входной параметр.

Когда мы меняем поле "Тип" входного параметра как "CommaDelimitedList", мы получаем сообщение об ошибке:

Значение свойства SecurityGroups должно быть типа List of String

Очевидно, он не может присоединиться к списку со строкой, чтобы сделать его новым.

Когда параметр содержит только один идентификатор sg, все создается успешно, однако нам нужно иметь возможность добавлять не более одного идентификатора sg.

Мы использовали множество различных комбинаций использования Fn::Join в теге SecurityGroups, но ничего не работает. Нам действительно нужна какая-то функция "Explode", чтобы извлечь отдельный идентификатор из строки параметров.

Кто-нибудь знает хороший способ заставить это работать?

Ответ 1

Как вы выяснили, проблема в том, что вам необходимо отправить список строк в качестве групп безопасности, и хотя CloudFormation предоставляет вам метод объединения списка строк в одну строку с разделителями, она не предоставляет простой способ разделения строки с разделителями на список строк.

К сожалению, единственный способ, которым я знаю, как это сделать, - это вложенный стек. Вы можете использовать тип параметра "CommaDelimitedList" для разделения строки с разделителями-запятыми на список строк.

Основной метод таков:

  • Создайте свою группу безопасности в своем шаблоне cloudformation.
  • Объединить этот идентификатор группы безопасности с вашим списком групп безопасности, используя Fn:: Join.
  • Передайте этот список во вложенный стек (ресурс типа AWS:: CloudFormation:: Stack).
  • Возьмите этот параметр как тип "CommaDelimitedList" в отдельном шаблоне.
  • Передайте параметр ref в ваше объявление экземпляра EC2.

Я тестировал это со следующими двумя шаблонами и работал:

Основной шаблон:

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

Хотелось бы узнать, есть ли лучший способ сделать это. Как вы говорите, ему действительно нужна функция взрыва.

Ответ 2

AWS представила Fn :: Split в январе 2017 года, и теперь это возможно. Это не красиво, но вы по сути конвертируете два списка в строки с помощью 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 возьмет строку и разделит элементы на список через запятую.

Ответ 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"
    }
],

Надеюсь, это поможет кому угодно.

Ответ 4

Фактически вы можете написать необходимую функцию преобразования в лямбда без создания подстановки, чтобы получить необходимую строку List.

Например: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/walkthrough-custom-resources-lambda-lookup-amiids.html

Фактически, этой функции действительно не нужно делать ничего, кроме списка получателей, и возвращать его обратно. У меня был аналогичный сценарий, когда у меня был List-AWS:: EC2:: Subnet:: Id - и он должен был передать это ресурсу, который требовал List-String.

процесс:

Определите LambdaExecutionRole.

Определить ListToStringListFunction типа AWS:: Lambda:: Function, добавить код, который возвращается в данных cfnresponse {'Result': event ['ResourceProperties'] ['List']}.

Определите пользовательский ресурс MyList type Custom:: MyList, дайте ServiceToken GetAtt ListToStringListFunction Arn. Также передайте List как свойство, которое возвращает исходный список.

Ссылка MyList с Fn:: GetAtt (MyList, результат)

Ответ 5

Более простое 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.

Я тестировал и использовал его для подобной проблемы, работал очень хорошо.

Ответ 6

Следуя подходу @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"]
                  }
                }
              ]
            }
          ]
        }
      }