😎

Traffic Mirroring のセッション作成を cfn 化

2024/03/11に公開

やりたいこと

Traffic Mirroring を展開して、ミラーリングセッションを張るまでを自動化する

いきなり結論

以下の CloudFormation テンプレートでスタックを作成する

ミラーリングソース

EC2インスタンスのタグに "Mirroring" キーが設定されていて、値が"true"となっているインスタンスへのすべてのトラフィック

ミラーリングターゲット

ターゲットにする NLB の ARN を展開時に指定

CloudFormation テンプレート

AWSTemplateFormatVersion: 2010-09-09
Description: Create Traffic Mirror

Parameters:
  NLBArn:
    Description: Enter the NLB ARN
    Type: String

Resources:
  TrafficMirrorFilter:
    Type: AWS::EC2::TrafficMirrorFilter
    Properties:
      Description: traffic mirror filter
      NetworkServices:
        - amazon-dns
      Tags:
        - Key: Name
          Value: TrafficMirroringFilter

  InboundTrafficMirrorFilterRule:
    Type: AWS::EC2::TrafficMirrorFilterRule
    Properties:
      Description: mirror all inbound traffic
      TrafficMirrorFilterId: !Ref TrafficMirrorFilter
      TrafficDirection: ingress
      RuleNumber: 10
      DestinationCidrBlock: 0.0.0.0/0
      SourceCidrBlock: 0.0.0.0/0
      RuleAction: accept

  OutboundTrafficMirrorFilterRule:
    Type: AWS::EC2::TrafficMirrorFilterRule
    Properties:
      Description: mirror all outbound traffic
      TrafficMirrorFilterId: !Ref TrafficMirrorFilter
      TrafficDirection: egress
      RuleNumber: 10
      DestinationCidrBlock: 0.0.0.0/0
      SourceCidrBlock: 0.0.0.0/0
      RuleAction: accept

  NLBTrafficMirrorTarget:
    Type: AWS::EC2::TrafficMirrorTarget
    Properties:
      Description: traffic mirror target associated with a network load balancer
      NetworkLoadBalancerArn: !Ref NLBArn
      Tags:
        - Key: Name
          Value: NLBTarget

# Creating Mirroring Sessions
  CustomLambdaCreateMirroringSession:
    Type: Custom::CreateMirroringSessionLambda
    Properties:
      ServiceToken: !GetAtt LambdaFunctionCreateMirroringSession.Arn

  LambdaFunctionCreateMirroringSession:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.lambda_handler
      Environment:
        Variables:
          TARGET_ID: !Ref NLBTrafficMirrorTarget
          FILTER_ID: !Ref TrafficMirrorFilter
      Role: !GetAtt LambdaFunctionExecutionCreateMirroringSessionRole.Arn
      Runtime: python3.9
      Timeout: 10
      Code:
        ZipFile: !Sub |
          import boto3
          import base64
          import os
          import cfnresponse
          client = boto3.client('ec2')
          target_id = os.getenv('TARGET_ID', '')
          filter_id = os.getenv('FILTER_ID', '')
          def lambda_handler(event, context):
            tag_key = "Mirroring"
            tag_value = "true"
            try:
              if event['RequestType'] == 'Create':
                response = client.describe_instances(
                  Filters=[
                    {
                      'Name': 'tag:' + tag_key,
                      'Values': [
                        tag_value,
                      ]
                    }
                  ]
                )
                eni_ids = []
                for resp in response['Reservations']:
                  for instance in resp['Instances']:
                    for eni in instance['NetworkInterfaces']:
                      eni_ids.append(eni['NetworkInterfaceId'])
                print(eni_ids)
                session_num = 1
                for eni_id in eni_ids:
                  response = client.create_traffic_mirror_session(
                    NetworkInterfaceId=eni_id,
                    TrafficMirrorTargetId=target_id,
                    TrafficMirrorFilterId=filter_id,
                    SessionNumber=session_num
                  )
                  session_num += 1
              cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
            except Exception as e:
              cfnresponse.send(event, context, cfnresponse.FAILED, {})
              print(e)
            try:
              if event['RequestType'] == 'Delete':
                response = client.describe_traffic_mirror_sessions(
                  Filters=[
                    {
                      'Name': 'traffic-mirror-target-id',
                      'Values': [
                        target_id,
                      ]
                    },
                  ],
                )
                mirroring_session_ids = [ mirroring_session['TrafficMirrorSessionId'] for mirroring_session in response['TrafficMirrorSessions']]
                for session_id in mirroring_session_ids:
                  response = client.delete_traffic_mirror_session(
                    TrafficMirrorSessionId=session_id
                  )
              cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
            except Exception as e:
              cfnresponse.send(event, context, cfnresponse.FAILED, {})
              print(e)
  LambdaFunctionExecutionCreateMirroringSessionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: "mirroring-lambda-policy"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - ec2:CreateTags
                  - ec2:CreateTrafficMirrorSession
                  - ec2:DeleteTrafficMirrorSession
                  - ec2:DescribeTrafficMirrorSessions
                  - ec2:DescribeInstances
                Resource:
                  - "*"

そもそもトラフィックミラーリング #とは

https://aws.amazon.com/jp/blogs/news/new-vpc-traffic-mirroring/

今日、AWS では VPC トラフィックミラーリングの利用を開始します。これは既存の Virtual Private Clouds (VPC) を使用する新機能で、ネットワークトラフィックを捕捉し、検査します。また、この機能はスケール可能です。次のことを実行できます。
◆ネットワークおよびセキュリティ上の異常を検出 – VPC 上の任意のワークロードから関心のあるトラフィックを抽出し、指定した検出ツールにルーティングできます。従来のログベースのツールと比較して、より迅速に攻撃を検出し、対応できます。
◆運用上のインサイトを取得 – VPC トラフィックミラーリングを使用することで、ネットワークを可視化し、コントロールを得ることができます。それは後に、より詳細な情報を得たうえでセキュリティの意思決定を下すのに役立ちます。
◆コンプライアンスとセキュリティコントロールを実装 – モニタリング、ログ作成、その他を必要とする法令およびコンプライアンスの要件に準拠できるようになります。
◆問題のトラブルシューティング – テストやトラブルシューティングの目的で、アプリケーションのトラフィックを社内的にミラーリングできます。トラフィックパターンを分析し、事前にアプリケーションのパフォーマンスを損なう「渋滞」ポイントを見つけることができます。

シンプルに書くと ENI を指定してその ENI で行われる通信をフィルタリングして宛先の ENI/NLB/GWLB に送信するサービス
※2022/05/12 から 送信先の GWLB が対応
https://aws.amazon.com/jp/about-aws/whats-new/2022/05/amazon-vps-traffic-mirroring-supports-sending-mirrored-traffic-gateway-load-balancer-backed-monitoring-appliances/

利用用途としては IDS に対してミラーリングトラフィックを送信することでトラフィックの検査をしたり、本番環境の通信のチェック、テストを行うことが考えられます。

参考ブログ

以下のんピさんのブログは検証まで丁寧に記載していただけてるのでとても参考になります~(素敵)
https://dev.classmethod.jp/articles/amazon-vpc-traffic-mirroring-supports-sending-mirrored-traffic-gateway-load-balancer/

CloudFormation で作成されるものの詳細

作成されるものリスト

以下5つのコンポーネントが作成されます。

  • トラフィックミラーフィルタ
  • インバウンドトラフィックミラーフィルタールール
  • アウトバウンドトラフィックミラーフィルタールール
  • トラフィックミラーターゲット
  • トラフィックミラーセッション(カスタムリソース)

トラフィックミラーフィルタ + In/Outルール

トラフィックミラーフィルタを作成します。
→これはミラーリングセッションを張った時にどのトラフィックをミラーリングするかの指定を行うものです。
この時すべてのインバウンド/アウトバウンドですべてのトラフィックをミラーリング対象とするように設定します。
※プロトコルを明示的に指定しない場合、CloudFormation では"すべてのプロトコル"が対象になります。(Security Groupだと-1指定でこれとは違うのがまた、、←)

  TrafficMirrorFilter:
    Type: AWS::EC2::TrafficMirrorFilter
    Properties:
      Description: traffic mirror filter
      NetworkServices:
        - amazon-dns
      Tags:
        - Key: Name
          Value: TrafficMirroringFilter

  InboundTrafficMirrorFilterRule:
    Type: AWS::EC2::TrafficMirrorFilterRule
    Properties:
      Description: mirror all inbound traffic
      TrafficMirrorFilterId: !Ref TrafficMirrorFilter
      TrafficDirection: ingress
      RuleNumber: 10
      DestinationCidrBlock: 0.0.0.0/0
      SourceCidrBlock: 0.0.0.0/0
      RuleAction: accept

  OutboundTrafficMirrorFilterRule:
    Type: AWS::EC2::TrafficMirrorFilterRule
    Properties:
      Description: mirror all outbound traffic
      TrafficMirrorFilterId: !Ref TrafficMirrorFilter
      TrafficDirection: egress
      RuleNumber: 10
      DestinationCidrBlock: 0.0.0.0/0
      SourceCidrBlock: 0.0.0.0/0
      RuleAction: accept

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-trafficmirrorfilter.html
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-trafficmirrorfilterrule.html

トラフィックミラーターゲット

トラフィックミラーターゲットを作成します。
→ミラーリングしたトラフィックを送るあて先を指定して作成します。
今回は NLB を指定しています

  NLBTrafficMirrorTarget:
    Type: AWS::EC2::TrafficMirrorTarget
    Properties:
      Description: traffic mirror target associated with a network load balancer
      NetworkLoadBalancerArn: !Ref NLBArn
      Tags:
        - Key: Name
          Value: NLBTarget

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-trafficmirrortarget.html

トラフィックミラーセッション(カスタムリソース)

実際にミラーリングを行うセッションを作成します。
この時通常であれば CloudFormation のタイプで "AWS::EC2::TrafficMirrorSession" を指定して作成しますが今回はカスタムリソースで作成しています。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-trafficmirrorsession.html

今回は手動で一つ一つ送信元 ENI を指定するのが手間だったため、タグベースでミラーリング対象の ENI を特定しすべてに一括でミラーリングセッションを作成したかったためカスタムリソースでの作成にしています。

注意点としてはミラーリングセッションはセッションIDを一意で持たなければいけない点、削除時に既存のミラーリングセッションを削除しないとほかのミラーフィルタ系が削除できない点です。
そのため、カスタムリソースではリクエストタイプにて上記を解決しています。

  LambdaFunctionCreateMirroringSession:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.lambda_handler
      Environment:
        Variables:
          TARGET_ID: !Ref NLBTrafficMirrorTarget
          FILTER_ID: !Ref TrafficMirrorFilter
      Role: !GetAtt LambdaFunctionExecutionCreateMirroringSessionRole.Arn
      Runtime: python3.9
      Timeout: 10
      Code:
        ZipFile: !Sub |
          import boto3
          import base64
          import os
          import cfnresponse
          client = boto3.client('ec2')
          target_id = os.getenv('TARGET_ID', '')
          filter_id = os.getenv('FILTER_ID', '')
          def lambda_handler(event, context):
            tag_key = "Mirroring"
            tag_value = "true"
            try:
              if event['RequestType'] == 'Create':
                response = client.describe_instances(
                  Filters=[
                    {
                      'Name': 'tag:' + tag_key,
                      'Values': [
                        tag_value,
                      ]
                    }
                  ]
                )
                eni_ids = []
                for resp in response['Reservations']:
                  for instance in resp['Instances']:
                    for eni in instance['NetworkInterfaces']:
                      eni_ids.append(eni['NetworkInterfaceId'])
                print(eni_ids)
                session_num = 1
                for eni_id in eni_ids:
                  response = client.create_traffic_mirror_session(
                    NetworkInterfaceId=eni_id,
                    TrafficMirrorTargetId=target_id,
                    TrafficMirrorFilterId=filter_id,
                    SessionNumber=session_num
                  )
                  session_num += 1
              cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
            except Exception as e:
              cfnresponse.send(event, context, cfnresponse.FAILED, {})
              print(e)
            try:
              if event['RequestType'] == 'Delete':
                response = client.describe_traffic_mirror_sessions(
                  Filters=[
                    {
                      'Name': 'traffic-mirror-target-id',
                      'Values': [
                        target_id,
                      ]
                    },
                  ],
                )
                mirroring_session_ids = [ mirroring_session['TrafficMirrorSessionId'] for mirroring_session in response['TrafficMirrorSessions']]
                for session_id in mirroring_session_ids:
                  response = client.delete_traffic_mirror_session(
                    TrafficMirrorSessionId=session_id
                  )
              cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
            except Exception as e:
              cfnresponse.send(event, context, cfnresponse.FAILED, {})
              print(e)
  LambdaFunctionExecutionCreateMirroringSessionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: "mirroring-lambda-policy"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - ec2:CreateTags
                  - ec2:CreateTrafficMirrorSession
                  - ec2:DeleteTrafficMirrorSession
                  - ec2:DescribeTrafficMirrorSessions
                  - ec2:DescribeInstances
                Resource:
                  - "*"

最後に

Traffic Mirroring を展開して、EC2インスタンスのタグに "Mirroring" キーが設定されていて、値が"true"となっているインスタンスへのすべてのトラフィックのミラーリングセッションを張るまでを自動化するテンプレートを作成してみました。

どなたかの参考になれば幸いです~

Discussion