Global Acceleratorで作成されるセキュリティグループをALBのセキュリティグループで許可するカスタムリソース作ってみた

2022.08.31

こんにちは!AWS事業本部コンサルティング部のたかくに(@takakuni_)です。

今回は、Global Acceleratorエンドポイント作成時にAWS側で作成されるGlobal Accelerator用のセキュリティグループをALBのセキュリティグループに紐づけるカスタムリソースを作ってみました。

なぜそんなことをしているのか

Global Acceleratorエンドポイントグループ作成時、Global Acceleratorはエンドポイントが存在するVPCごとに1つずつセキュリティグループを作成します。

つまり、セキュリティグループを作成するのはCloudFormationではなく、Global Accelerator側となります。

そのため、Global AcceleratorエンドポイントとALBを同時に作成するテンプレートの場合、CloudFormation側でGlobal Acceleratorのセキュリティグループを定義していないので、ALBのセキュリティグループでGlobal Acceleratorのセキュリティグループを許可するルールが作成できない事象が発生します。

もちろんですが、Global Acceleratorエンドポイントグループを見ても、返り値としてセキュリティグループを取得することができませんでした。

AWS::GlobalAccelerator::EndpointGroup

今回は、CloudFormationテンプレートを利用して1発で設定する方法が2つあったのでご紹介します。

方法1(非推奨)

非推奨ですが、可能ではあったため載せています。方法2をご検討ください。

方法1は、Global Acceleratorエンドポイントグループ作成前に、セキュリティグループを作ってしまう方法です。

Global Acceleratorで作成されるセキュリティグループの名前は、私が検証した限り「GlobalAccelerator」と名前がついたセキュリティグループが対象VPCに作成されていました。

そのため、次のようにGlobal Acceleratorエンドポイントグループ作成前にセキュリティグループを作成します。

template.yaml

Resources:
  AlbSg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VpcId
      GroupName: !Sub "${PrjPrefix}-alb-sg"
      GroupDescription: !Sub "${PrjPrefix}-alb-sg"
      Tags:
        - Key: Name
          Value: !Sub "${PrjPrefix}-alb-sg"
  Alb:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties: 
      Name: !Sub "${PrjPrefix}-alb"
      Type: application
      IpAddressType: ipv4
      Scheme: internet-facing
      SecurityGroups:
        - !GetAtt AlbSg.GroupId
      Subnets: !Ref SubnetIds
      Tags:
        - Key: Name
          Value: !Sub "${PrjPrefix}-alb"

  # Global Accelerator用のSGを先に作る
  GlobalAcceleratorSg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VpcId
      GroupName: "GlobalAccelerator" # 名前は固定
      GroupDescription: !Sub "${PrjPrefix}-ga-sg"
      Tags:
        - Key: Name
          Value: !Sub "${PrjPrefix}-ga-sg"
  
  AlbFromGaIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties: 
      GroupId: !GetAtt AlbSg.GroupId
      Description: GlobalAccelerator Communication
      FromPort: 80
      ToPort: 80
      IpProtocol: tcp
      SourceSecurityGroupId: !GetAtt GlobalAcceleratorSg.GroupId

  AcceleratorListeneralb:
    Type: AWS::GlobalAccelerator::EndpointGroup
    Properties:
      EndpointGroupRegion:
        Ref: AWS::Region
      ListenerArn:
        Fn::GetAtt:
          - AcceleratorListener
          - ListenerArn
      EndpointConfigurations:
        - EndpointId:
            Ref: Alb
    DependsOn:
      - GlobalAcceleratorSg # 明示的に依存関係を持たせる

ただし、以下のドキュメントにある通り、Global Acceleratorが作成するセキュリティグループを操作するのは非推奨とされています。

グローバルアクセラレータによって作成されたセキュリティグループ

グローバルアクセラレータとセキュリティグループを使用する場合は、次の情報とベストプラクティスを確認してください。

  • Global Accelerator は、Elastic Network インターフェイスに関連付けられているセキュリティグループを作成します。システムによって禁止されるわけではありませんが、これらのグループのセキュリティグループ設定を編集しないでください。
  • グローバルアクセラレータは、作成したセキュリティグループを削除しません。ただし、Global Accelerator では、アカウントのアクセラレータのエンドポイントでelastic network interface が使用されていない場合、エラスティックネットワークインターフェイスは削除されます。
  • Global Accelerator によって作成されたセキュリティグループは、保守する他のセキュリティグループのソースグループとして使用できますが、Global Accelerator は VPC で指定したターゲットにのみトラフィックを転送します。
  • Global Accelerator で作成されたセキュリティグループのルールを変更すると、エンドポイントが異常になることがあります。その場合は、AWS サポート詳細については、
  • グローバルアクセラレータは、VPC ごとに特定のセキュリティグループを作成します。特定の VPC 内のエンドポイント用に作成された Elastic ネットワークインターフェイスは、elastic network interface が関連付けられているサブネットに関係なく、すべて同じセキュリティグループを使用します。
  • クライアント IP アドレスの保存に関するベストプラクティス

    方法2(おすすめ)

    というわけで、Global Acceleratorが作成するセキュリティグループを操作せずに設定する方法が方法2になります。

    具体的には、Global Acceleratorが作成したセキュリティグループIDを取得してALBに許可するカスタムリソースになります。

    以下のようにカスタムリソースとして設定することで、Global Acceleratorの非推奨項目を回避しつつALBに設定ができました。

    template.yaml

    AWSTemplateFormatVersion: "2010-09-09"
    Description: Terraform pipeline template.
    Metadata:
      AWS::CloudFormation::Interface:
        ParameterGroups:
          - Label:
              default: "Project Name Prefix"
            Parameters:
            - PrjPrefix
          - Label:
              default: "Network Configration"
            Parameters:
            - VpcId
            - SubnetIds
    Parameters:
      PrjPrefix:
        Type: String
      VpcId:
        Type: AWS::EC2::VPC::Id
      SubnetIds:
        Type: List<AWS::EC2::Subnet::Id>
    Resources:
    # ALB
      AlbSg:
        Type: AWS::EC2::SecurityGroup
        Properties:
          VpcId: !Ref VpcId
          GroupName: !Sub "${PrjPrefix}-alb-sg"
          GroupDescription: !Sub "${PrjPrefix}-alb-sg"
          Tags:
            - Key: Name
              Value: !Sub "${PrjPrefix}-alb-sg"
      Alb:
        Type: AWS::ElasticLoadBalancingV2::LoadBalancer
        Properties: 
          Name: !Sub "${PrjPrefix}-alb"
          Type: application
          IpAddressType: ipv4
          Scheme: internet-facing
          SecurityGroups:
            - !GetAtt AlbSg.GroupId
          Subnets: !Ref SubnetIds
          Tags:
            - Key: Name
              Value: !Sub "${PrjPrefix}-alb"
      AlbTargetGroup:
        Type: AWS::ElasticLoadBalancingV2::TargetGroup
        Properties:
          HealthCheckIntervalSeconds: 30
          HealthCheckPath: /healthcheck
          HealthCheckPort: 80
          HealthCheckProtocol: HTTP
          HealthCheckTimeoutSeconds: 6
          HealthyThresholdCount: 3
          Name: !Sub "${PrjPrefix}-alb"
          Port: 80
          Protocol: HTTP
          UnhealthyThresholdCount: 3
          TargetType: ip
          VpcId: !Ref VpcId
      AlbListerner:
        Type: AWS::ElasticLoadBalancingV2::Listener
        Properties:
          DefaultActions:
            - Type: forward
              TargetGroupArn: !Ref AlbTargetGroup
          LoadBalancerArn: !Ref Alb
          Port: 80
          Protocol: HTTP
    
    # GlobalAccelerator
      Accelerator:
        Type: AWS::GlobalAccelerator::Accelerator
        Properties:
          Name: !Sub "${PrjPrefix}-ga"
          Enabled: true
      AcceleratorListener:
        Type: AWS::GlobalAccelerator::Listener
        Properties:
          AcceleratorArn:
            Fn::GetAtt:
              - Accelerator
              - AcceleratorArn
          PortRanges:
            - FromPort: 80
              ToPort: 80
          Protocol: TCP
          ClientAffinity: NONE
      AcceleratorListenerAlb:
        Type: AWS::GlobalAccelerator::EndpointGroup
        Properties:
          EndpointGroupRegion:
            Ref: AWS::Region
          ListenerArn:
            Fn::GetAtt:
              - AcceleratorListener
              - ListenerArn
          EndpointConfigurations:
            - EndpointId:
                Ref: Alb
    
    # Security Group
      ModifySecrityGroupRole:
        Type: AWS::IAM::Role
        Properties:
          AssumeRolePolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Principal:
                  Service: lambda.amazonaws.com
                Action: sts:AssumeRole
          Path: /
          ManagedPolicyArns:
            - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
            - "arn:aws:iam::aws:policy/job-function/NetworkAdministrator"
          RoleName: !Sub "${PrjPrefix}-modify-security-group"
    
      ModifySecrityGroupLog:
        Type: AWS::Logs::LogGroup
        Properties:
          LogGroupName: !Sub "/aws/lambda/${PrjPrefix}-modify-security-group"
    
      ModifySecrityGroupFunction:
        Type: AWS::Lambda::Function
        Properties:
          FunctionName: !Sub "${PrjPrefix}-modify-security-group"
          Code:
            ZipFile: |
              import json
              import boto3
              import cfnresponse
              def handler(event, context):
                  vpc_id = event['ResourceProperties']['VpcId']
                  alb_sg_id = event['ResourceProperties']['AlbSgId']
                  alb_lintener_port = int(event['ResourceProperties']['AlbListernerPort'])
    
                  try:
                      if event['RequestType'] == 'Create':
                          ec2 = boto3.client('ec2')
                          globalaccelerator_sg_id = ec2.describe_security_groups(
                              Filters=[
                                  {
                                      'Name': 'vpc-id',
                                      'Values': [ vpc_id ]
                                  },
                                  {
                                      'Name': 'group-name',
                                      'Values': [ 'GlobalAccelerator' ]
                                  }
                              ]
                          )['SecurityGroups'][0]['GroupId']
                          response = ec2.authorize_security_group_ingress(
                              GroupId = alb_sg_id,
                              IpPermissions = [
                                  {
                                      'FromPort': alb_lintener_port,
                                      'ToPort': alb_lintener_port,
                                      'IpProtocol': 'tcp',
                                      'UserIdGroupPairs': [
                                          {
                                              'GroupId': globalaccelerator_sg_id,
                                              'Description': 'GlobalAccelerator Communication'
                                          }
                                      ]
                                  }
                              ]
                          )
                          cfnresponse.send(event, context, cfnresponse.SUCCESS, response)
                      if event['RequestType'] == 'Delete':
                          ec2 = boto3.client('ec2')
                          globalaccelerator_sg_id = ec2.describe_security_groups(
                              Filters=[
                                  {
                                      'Name': 'vpc-id',
                                      'Values': [ vpc_id ]
                                  },
                                  {
                                      'Name': 'group-name',
                                      'Values': [ 'GlobalAccelerator' ]
                                  }
                              ]
                          )['SecurityGroups'][0]['GroupId']
                          response = ec2.revoke_security_group_ingress(
                              GroupId = alb_sg_id,
                              IpPermissions  = [
                                  {
                                      'FromPort': alb_lintener_port,
                                      'ToPort': alb_lintener_port,
                                      'IpProtocol': 'tcp',
                                      'UserIdGroupPairs': [
                                          {
                                              'GroupId': globalaccelerator_sg_id,
                                              'Description': 'GlobalAccelerator Communication'
                                          }
                                      ]
                                  }
                              ]
                          )
                          cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Success'})
                      if event['RequestType'] == 'Update':
                          cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Success'})
                  except Exception as e:
                      print(e)
                      cfnresponse.send(event, context, cfnresponse.FAILED, {})
          Handler: index.handler
          MemorySize: 128
          Role: !GetAtt ModifySecrityGroupRole.Arn
          Runtime: "python3.9"
          Timeout: 60
          Tags:
            - Key: "Name"
              Value: !Sub "${PrjPrefix}-modify-security-group"
        DependsOn: ModifySecrityGroupLog
    
      ModifySecrityGroup:
        Type: Custom::ModifySecrityGroup
        Properties:
          ServiceToken: !GetAtt ModifySecrityGroupFunction.Arn
          VpcId: !Ref VpcId
          AlbSgId: !GetAtt AlbSg.GroupId
          AlbListernerPort: 80
        DependsOn:
          - AcceleratorListenerAlb

    まとめ

    以上、「Global AcceleratorのセキュリティグループをALBのセキュリティグループで許可するカスタムリソース作ってみた」でした。

    Global Accelerator利用時に参考になるととても嬉しいです。

    以上、AWS事業本部コンサルティング部のたかくに(@takakuni_)でした!