AWS

CFNカスタムリソースを使用してNLBのプライベートアドレスを取得し、セキュリティグループの送信元に指定する

スポンサーリンク
CFNカスタムリソースを使用して、NLBのプライベートアドレスを取得して、セキュリティグループの送信元に指定する AWS
スポンサーリンク
スポンサーリンク

CloudFormationカスタムリソースを使用してNLBのプライベートアドレスを取得し、セキュリティグループの送信元に指定する

ALBとは異なり、NLBにはセキュリティグループをアタッチすることはできません。

Network Load Balancer には関連付けられたセキュリティグループがありません。

ターゲットグループへのターゲットの登録

この仕様から、NLBに関連づけるEC2インスタンス用のセキュリティグループは、IPアドレスベースのルールを定義する必要があります。

今回はCloudFormationカスタムリソースを使用して、NLBのプライベートアドレスを取得し、セキュリティグループの送信元に指定することを目標とします。

構築する環境

Diagram of using CFN Custom Resource to obtain NLB private address and set as source of security group.

NLBを作成します。
ターゲットグループにプライベートサブネット内のAuto Scalingグループを指定します。

ターゲットグループは2つのAZにまたがるように配置します。
グループにAuto Scalingグループを関連付け、内部に最新のAmazon Linux 2ベースのEC2インスタンスを作成します。
EC2インスタンスはApacheをインストールし、Webサーバとして動作させます。

Apacheをインストールするために、S3バケット上に構築されたAmazon Linux 2用のyumリポジトリにアクセスします。
このリポジトリにアクセスするために、S3用VPCエンドポイントを作成します。

Lambda関数を定義し、この関数をCloudFormationカスタムリソースとして設定します。
この関数の働きは、NLBに関連づけられたプライベートアドレスを取得することです。
関数のランタイムはPython3.8とします。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

awstut-fa/093 at main · awstut-an-r/awstut-fa
Contribute to awstut-an-r/awstut-fa development by creating an account on GitHub.

テンプレートファイルのポイント解説

本ページはCloudFormationカスタムリソースを使用して、NLBのプライベートアドレスを取得し、セキュリティグループの送信元に指定する方法を中心に取り上げます。

CloudFormationカスタムリソースの基本的な事項については、以下のページをご確認ください。

プライベートサブネット内のリソースをELB(ALB)にアタッチする方法については、以下のページをご確認ください。

プライベートサブネット内のAmazon Linuxインスタンスでyumを実行する方法については、以下のページをご確認ください。

スケーリングポリシーなしのAuto Scalingについては、以下のページをご確認ください。

NLBのクライアントIPの保存を無効化する

Resources: NLBTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: !Sub "${Prefix}-NLBTargetGroup" Protocol: TCP Port: !Ref HTTPPort TargetGroupAttributes: - Key: preserve_client_ip.enabled Value: false VpcId: !Ref VPC
Code language: YAML (yaml)

ポイントはNLBターゲットグループの属性です。
今回はクライアントIPアドレスの保持を無効化します。

Network Load Balancer は、リクエストをバックエンドターゲットにルーティングするときに、クライアントのソース IP アドレスを保持できます。クライアント IP の保存を無効にした場合、Network Load Balancer のプライベート IP アドレスは、すべての受信トラフィックのクライアント IP になります。

クライアント IP の保存

本設定が有効化された状態ですと、クライアントが使用しているパブリックアドレスが送信元になるため、セキュリティグループの送信元として指定することができません。
これはクライアントの送信元アドレスが不特定多数であるためです。
ですから本設定を無効化し、NLBのプライベートアドレスを送信元とします。

CloudFormationカスタムリソースでNLBのプライベートアドレスを取得する

カスタムリソース

Resources: CustomResource: Type: Custom::CustomResource Properties: ServiceToken: !GetAtt Function.Arn
Code language: YAML (yaml)

ServiceTokenプロパティにバックエンドで動作するリソースのARNを設定します。
今回は後述のLambda関数が動作しますので、この関数のARNを設定します。

Lambda関数

Resources: Function: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import boto3 import cfnresponse import os nlb_loadbalancer_name = os.environ['NLB_LOADBALANCER_NAME'] filter_value = '*{nlb}*'.format(nlb=nlb_loadbalancer_name) client = boto3.client('ec2') CREATE = 'Create' UPDATE = 'Update' response_data = {} def lambda_handler(event, context): try: if event['RequestType'] == CREATE or event['RequestType'] == UPDATE: response = client.describe_network_interfaces( Filters=[ { 'Name':'description', 'Values':[ filter_value ] } ] ) private_addresses = [interface['PrivateIpAddress'] for interface in response['NetworkInterfaces']] response_data['PrivateAddresses'] = private_addresses cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data) except Exception as e: print(e) cfnresponse.send(event, context, cfnresponse.FAILED, response_data) Environment: Variables: NLB_LOADBALANCER_NAME: !Ref NLBLoadBalancerName FunctionName: !Sub "${Prefix}-function" Handler: !Ref Handler Runtime: !Ref Runtime Role: !GetAtt FunctionRole.Arn
Code language: YAML (yaml)

関数そのものの設定に特別な項目はありません。
1つ挙げるとすると、環境変数がポイントです。
環境変数としてNLB名を関数に渡します。

event[‘RequestType’]の値を参照して、スタック操作に応じた処理を実装します。
今回はこの値が「Create」または「Update」の時、つまりスタック作成時・更新時に処理を実行します。

describe_network_interfacesメソッドを実行し、AWSアカウント上に存在するネットワークインターフェース情報を取得します。
Filtersオプションを使用して、全インターフェースの中から、NLBにアタッチしているものを抽出します。
フィルタする条件は、説明文に含まれるNLB名です。
前後にワイルドカードである「*」を配置して、あいまい検索します。

リスト内包表記を使って、抽出したネットワークインターフェースの情報の中から、NLBに割り当てられたプライベートアドレスを取得します。
プライベートアドレスはNLBが関連づけられているサブネットの分だけあります。
今回の2つのパブリックアドレスがNLBに関連づけられていますので、2アドレスが取得できます。

cfnresponse.sendを使って、関数の実行完了メッセージをCloudFormationスタックに返す際に、この2アドレスもリスト型で渡します。
これでCloudFormationテンプレートから、この2アドレスにアクセスすることができるようになります。

IAMロール

Resources: FunctionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: sts:AssumeRole Principal: Service: - lambda.amazonaws.com ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: !Sub "${Prefix}-DescribeNetworkInterfaces" PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - ec2:DescribeNetworkInterfaces Resource: "*"
Code language: YAML (yaml)

先述の通り、Lambda関数でNLBの情報にアクセスしますので、「ec2:DescribeNetworkInterfaces」アクションを許可します。

Outputsセクション

Outputs: NLBPrivateAddress1: Value: !Select [0, !GetAtt CustomResource.PrivateAddresses] NLBPrivateAddress2: Value: !Select [1, !GetAtt CustomResource.PrivateAddresses]
Code language: YAML (yaml)

NLBのプライベートアドレスを受け取る方法についてです。
これらのアドレスは、外部のテンプレートから参照する必要があるため、Outputsセクションに記載しますが、この記法に注意してください。
それは今回のCloudFormationカスタムリソースから返される値が配列型であるということに起因します。
Outputsセクションに定義できる値は数値か文字列のみです。
ですから組み込み関数Fn::Selectを使用し、配列から値を1つずつ取り出して出力を定義します。

セキュリティグループ

Resources: InstanceSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Sub "${Prefix}-InstanceSecurityGroup" GroupDescription: Allow HTTP from NLB. VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: !Ref HTTPPort ToPort: !Ref HTTPPort CidrIp: !Sub "${NLBPrivateAddress1}/32" - IpProtocol: tcp FromPort: !Ref HTTPPort ToPort: !Ref HTTPPort CidrIp: !Sub "${NLBPrivateAddress2}/32"
Code language: YAML (yaml)

セキュリティグループのルールを定義します。
NLBのプライベートアドレスを使用して、2つのルールに対して、ホストレベルでNLBを送信元として指定します。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • NLB:fa-093-NLB
  • NLBのDNS名:fa-093-NLB-e247ddf9edcb5d7a.elb.ap-northeast-1.amazonaws.com
  • NLBのターゲットグループ:fa-093-NLBTargetGroup
  • Lambda関数:fa-093-function
  • EC2インスタンスのセキュリティグループ:sg-04b7daf27ae507cc1

作成されたリソースをAWS Management Consoleから確認します。
NLBを確認します。

Detail of NLB 1.

NLBのDNS名等が確認できます。

ターゲットグループを確認します。

Detail of NLB 2.

ターゲットタブを見ると、2つのEC2インスタンスが登録されていることがわかります。

Detail of NLB 3.

属性タブを見ると、クライアントIPアドレスの保存が無効になっていることがわかります。

NLBに関連づいているENIを確認すると、NLBが使用しているプライベートアドレスがわかります。

Detail of NLB 4.
Detail of NLB 5.

上記の通り、今回使用しているプライベートアドレスは「10.0.1.166」「10.0.2.152」でした。

CloudFormationカスタムリソースであるLambda関数の実行ログを確認します。

Detail of CloudFormation Custom Resource 1.

正常にNLBのプライベートアドレスが取得できていることがわかります。

Detail of CloudFormation Custom Resource 2.

これらの値がLambda用スタックのOutputsにも設定されていることがわかります。

EC2インスタンスのセキュリティグループを確認します。

Detail of EC2 Security Group 1.

送信元の列を見ると、NLBのプライベートアドレスが指定されていることがわかります。
つまりCloudFormationカスタムリソースで取得した値を用いて、セキュリティグループのルールが作成されたということです。

動作確認

準備が整いましたので、NLBにアクセスします。

Detail of NLB 6.
Detail of NLB 7.

NLBターゲットグループ内のEC2インスタンスにアクセスすることができました。
以上のことから、セキュリティグループのルールが、正常に動作しているということがわかります。

まとめ

CloudFormationカスタムリソースを使用して、NLBのプライベートアドレスを取得し、セキュリティグループの送信元に指定する方法を確認しました。

タイトルとURLをコピーしました