特定のセキュリティグループが変更されたことをカスタマイズした日本語メールで通知してみた

EventBridge+Step Functions+SNSを使って、通知内容をいい感じの日本語メールにして通知してみました。
2023.01.25

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

日本語でメール通知してほしい

こんにちは!AWS事業本部のおつまみです。

みなさん、特定のセキュリティグループが変更されたことを検知して、メール通知してほしいなぁと思ったことはありますか?私はあります。

例えば以下のようなメール通知です。

いい感じの方法がないかなぁと探していると、AWSナレッジセンターと弊社ブログにどんぴしゃな記事を見つけました。

そこで今回はこれらを組み合わせて、特定のセキュリティグループが変更されたことをいい感じの日本語メールで通知させたいと思います!

構成図

今回構築する構成です。

セキュリティグループを変更してから、ユーザーにメール通知するまでの流れ

①Amazon EventBridge:セキュリティグループの変更イベントを検知。EventBridgeの入力トランスフォーマーで、メールの件名と本文を設定。
②AWS StepFunctions:Amazon SNS publish API を呼び出し、件名と本文を指定して通知。※SNSをEventBridgeのターゲットに直接指定するとメール件名の指定ができません。
③Amazon SNS:指定したアドレス宛にメール通知。

前提として、CloudTrailが既に有効化されている環境で構築します。

CloudFormationテンプレートを準備

まずは通知元となるセキュリティグループやその他リソースののテンプレートを用意します。

sg-and-other-resources.yml

sg-and-other-resources.yml

AWSTemplateFormatVersion: '2010-09-09'
Description: "create Security groups and other resources"

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------# 
Parameters:
  PJPrefix:
    Type: String

  VPCCIDR:
    Type: String
    Default: "10.1.0.0/16"

  PublicSubnetACIDR:
    Type: String
    Default: "10.1.10.0/24"

  KeyName:
    Type: AWS::EC2::KeyPair::KeyName

Resources:
# ------------------------------------------------------------#
#  VPC
# ------------------------------------------------------------#
# VPC Create
  VPC: 
    Type: "AWS::EC2::VPC"
    Properties: 
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: "true"
      EnableDnsHostnames: "true"
      InstanceTenancy: default
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-vpc"

# InternetGateway Create
  InternetGateway: 
    Type: "AWS::EC2::InternetGateway"
    Properties: 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-igw"

# IGW Attach
  InternetGatewayAttachment: 
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties: 
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC 
   
# ------------------------------------------------------------#
#  PublicSubnet
# ------------------------------------------------------------#
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: 'ap-northeast-1a'
      VpcId: !Ref VPC
      CidrBlock: !Ref PublicSubnetACIDR
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-public-subnet-a"

# ------------------------------------------------------------#
#  PublicRouteTable
# ------------------------------------------------------------#
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-public-route-a"

# ------------------------------------------------------------#
#  SubnetとRoutetableの関連付け
# ------------------------------------------------------------#
  PublicRouteTableAssociation1a:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet

# ------------------------------------------------------------#
#  Routeの指定
# ------------------------------------------------------------#
  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId: !Ref PublicRouteTable
      GatewayId: !Ref InternetGateway

# ------------------------------------------------------------#
#  SecurityGroup 
# ------------------------------------------------------------#
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: SecurityGroup
      GroupDescription: SecurityGroup
      VpcId: !Ref VPC
  SGIngress01:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref SecurityGroup
      IpProtocol: tcp
      FromPort: 22
      ToPort: 22
      CidrIp: 104.28.211.105/32
      Description: MyIP

# ------------------------------------------------------------#
#  EC2
# ------------------------------------------------------------#
  EC2: 
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: ami-00d101850e971728d
      KeyName: !Ref KeyName
      InstanceType: t2.micro
      NetworkInterfaces: 
        - AssociatePublicIpAddress: "true"
          DeviceIndex: "0"
          SubnetId: !Ref PublicSubnet
          GroupSet:
            - !Ref SecurityGroup
      Tags:
          - Key: Name
            Value: "${PJPrefix}-ec2"

#------------------------------------------------------------------------------#
# Outputs
#------------------------------------------------------------------------------#
Outputs:
  SecurityGroup:
    Value: !Ref SecurityGroup
    Export:
      Name: !Sub SecurityGroup

テンプレート作成時のポイント

  • 通知用のテンプレートでセキュリティグループを指定できるようにOutputセクションで値を出力しています。

続いて、今回のメインとなる通知用のテンプレートです。

sg-change-alarm.yml

sg-change-alarm.yml

AWSTemplateFormatVersion: '2010-09-09'
Description: "create Security group change notification"

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------# 
Parameters:
  PJPrefix:
    Type: String

  SNSTopicName:
    Type: String
    Default: SecurityGroup_Change_Alarm

  Email:
    Type: String
    Default: email_address

Resources:
# ------------------------------------------------------------#
# SNS Topic
# ------------------------------------------------------------# 
  SNSTopic:
    Type: "AWS::SNS::Topic"
    Properties:
      TopicName: !Ref SNSTopicName
      Tags: 
      - 
        Key: "Name"
        Value: !Sub ${PJPrefix}-${SNSTopicName}

# ------------------------------------------------------------#
# SNS Subscription
# ------------------------------------------------------------# 
  Subscription:
    Type: "AWS::SNS::Subscription"
    Properties:
      TopicArn: !Ref SNSTopic
      Endpoint: !Ref Email
      Protocol: "email"

# ------------------------------------------------------------#
# SNS TopicPolicy
# ------------------------------------------------------------# 
  EventTopicPolicy:
    Type: 'AWS::SNS::TopicPolicy'
    Properties:
      PolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Action: 'sns:Publish'
            Resource: '*'
      Topics:
        - !Ref SNSTopic

# ------------------------------------------------------------#
# IAMロール (StepFunction用)
# ------------------------------------------------------------# 
  PublishRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - states.amazonaws.com
            Action: "sts:AssumeRole"
      Path: "/"
      Policies:
        - PolicyName: SNSPublish
          PolicyDocument:
            Statement:
              - Effect: Allow
                Resource:
                  - !Ref SNSTopic
                Action:
                  - sns:Publish

  StateMachineExecuteRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - events.amazonaws.com
            Action: "sts:AssumeRole"
      Path: "/"
      Policies:
        - PolicyName: StateMachineExecute
          PolicyDocument:
            Statement:
              - Effect: Allow
                Resource:
                  - !Ref SNSStateMachine
                Action:
                  - states:StartExecution
 
# ------------------------------------------------------------#
# EventBridge Rule
# ------------------------------------------------------------# 
  EventBridgeRule:
    Type: AWS::Events::Rule
    DependsOn: SNSTopic
    Properties: 
      Description: !Ref SNSTopicName
      EventBusName: default
      EventPattern: 
        !Sub
        - |-
          {
            "source": ["aws.ec2"],
            "detail-type": ["AWS API Call via CloudTrail"],
            "detail": {
              "eventSource": ["ec2.amazonaws.com"],
              "eventName": ["AuthorizeSecurityGroupIngress", "AuthorizeSecurityGroupEgress", "RevokeSecurityGroupIngress", "RevokeSecurityGroupEgress"],
              "requestParameters": {
                "groupId": ["${SecurityGroup}"]
              }
            }
          }
        - SecurityGroup:  {'Fn::ImportValue': !Sub 'SecurityGroup'}  
      Name: !Ref SNSTopicName
      Targets: 
        - Arn: !Ref SNSStateMachine
          Id: step-function
          RoleArn: !GetAtt StateMachineExecuteRole.Arn
          InputTransformer:
            InputPathsMap:
              "Account" : "$.account"
              "SecuritygroupId" : "$.detail.requestParameters.groupId"
              "eventId" : "$.detail.eventID"
              "time" : "$.time"
              "userName" : "$.detail.userIdentity.sessionContext.sessionIssuer.userName"
              "value" : "$.detail"
            InputTemplate: |
              {
                "subject": "セキュリティグループ変更通知",
                "message": "セキュリティグループ \"<SecuritygroupId>\" が変更されたことを検知しました。 \n詳細は次のとおりです。 \nAccount ID : \"<Account>\" \n発生時間 : \"<time> \"(UTC) \n変更ユーザー名 : \"<userName>\" \nセキュリティグループID : \"<SecuritygroupId>\" \nCloudTrailイベントID : \"<eventId>\" \n\nセキュリティグループの確認方法 \nEC2コンソール画面の左メニュータグより「セキュリティグループ」を選択します。\n検索ウィンドウで「セキュリティグループID」を入力し、セキュリティグループが正しいことを確認してください。 \n\nCloudTrailイベント履歴の確認方法 \nCloudTrailコンソール画面の左メニュータグより「イベント履歴」を選択します。 \n検索ウィンドウで[イベントID]を選択し、「CloudTrailイベントID」入力します。イベント詳細が確認できます。"
              }
                               
# ------------------------------------------------------------#
# StepFunction メール本文カスタマイズ用
# ------------------------------------------------------------# 
  SNSStateMachine:
    Type: "AWS::StepFunctions::StateMachine"
    Properties:
      DefinitionString: !Sub |-
            {
              "StartAt": "PublishSns",
              "States": {
                "PublishSns": {
                  "Type": "Task",
                  "Resource": "arn:aws:states:::sns:publish",
                  "Parameters": {
                    "TopicArn": "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:${SNSTopicName}",
                    "Message.$": "$.message",
                    "Subject.$": "$.subject"
                  },
                  "End": true
                }
              }
            }
      RoleArn: !GetAtt PublishRole.Arn

テンプレート作成時のポイント

  • EventBridgeのルールのEventPatternで監視対象のリソースとイベント内容を指定しています。今回はセキュリティグループの変更通知であるAuthorizeSecurityGroupIngress, AuthorizeSecurityGroupEgress, RevokeSecurityGroupIngress, RevokeSecurityGroupEgressを指定しています。(114〜128行目)

  • EventBridgeのルールのInputTransformerでメールに通知する値の取得、およびメール件名・メッセージを設定しています。InputPathsMapではメールに通知する値を取得するために、EventBridgeに送られてきたJSONログイベントから特定の値を変数として抽出します。セキュリティグループの変更では以下のようなJSONログイベントが送られてきます。

JSONログイベント
{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "AROAVIM5J54ZWHCLMVYDN:iam-user-name",
        "arn": "arn:aws:sts::123456789012:assumed-role/iam-user-name/iam-user-name",
        "accountId": "123456789012",
        "accessKeyId": "ASIAVIM5J54ZQ2I24HGX",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "AROAVIM5J54ZWHCLMVYDN",
                "arn": "arn:aws:iam::123456789012:role/iam-user-name",
                "accountId": "123456789012",
                "userName": "iam-user-name"
            },
            "webIdFederationData": {},
            "attributes": {
                "creationDate": "2023-01-25T05:33:28Z",
                "mfaAuthenticated": "true"
            }
        }
    },
    "eventTime": "2023-01-25T06:21:53Z",
    "eventSource": "ec2.amazonaws.com",
    "eventName": "RevokeSecurityGroupIngress",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "104.28.211.105",
    "userAgent": "AWS Internal",
    "requestParameters": {
        "groupId": "sg-0bcd6fbcb3109febe",
        "ipPermissions": {},
        "securityGroupRuleIds": {
            "items": [
                {
                    "securityGroupRuleId": "sgr-04d6f81151aa5f291"
                }
            ]
        }
    },
    "responseElements": {
        "requestId": "c5cbaee6-2c04-49a3-90d4-fc49269da4d0",
        "_return": true
    },
    "requestID": "c5cbaee6-2c04-49a3-90d4-fc49269da4d0",
    "eventID": "e714461b-4431-420b-a0f0-b1cb8c068298",
    "readOnly": false,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "123456789012",
    "eventCategory": "Management",
    "sessionCredentialFromConsole": "true"
}

今回はメール通知に含めたい項目として以下を入力パスに指定しました。(135〜141行目)

"Account" : "$.account"
"SecuritygroupId" : "$.detail.requestParameters.groupId"
"eventId" : "$.detail.eventID"
"time" : "$.time"
"userName" : "$.detail.userIdentity.sessionContext.sessionIssuer.userName"
"value" : "$.detail"
  • InputTemplateではメール件名およびメッセージを設定しています。Step Functionsを挟むと、テンプレートの文字列に\nを入れることで改行ができます。※EventBridgeのターゲットにSNSを直接指定した場合は改行されません。(142〜146行目)
InputTemplate: |
{
  "subject": "セキュリティグループ変更通知",
  "message": "セキュリティグループ \"<SecuritygroupId>\" が変更されたことを検知しました。 \n詳細は次のとおりです。 \nAccount ID : \"<Account>\" \n発生時間 : \"<time> \"(UTC) \n変更ユーザー名 : \"<userName>\" \nセキュリティグループID : \"<SecuritygroupId>\" \nCloudTrailイベントID : \"<eventId>\" \n\nセキュリティグループの確認方法 \nEC2コンソール画面の左メニュータグより「セキュリティグループ」を選択します。\n検索ウィンドウで「セキュリティグループID」を入力し、セキュリティグループが正しいことを確認してください。 \n\nCloudTrailイベント履歴の確認方法 \nCloudTrailコンソール画面の左メニュータグより「イベント履歴」を選択します。 \n検索ウィンドウで[イベントID]を選択し、「CloudTrailイベントID」入力します。イベント詳細が確認できます。"
}
  • StepFunctionsでAmazon SNS publish API を呼び出すステートマシンを設定します。ParametersでSNSトピック、メール件名、本文を指定します。(159~162行目)

いざ検証

1. CloudFromationでデプロイ

CloudFormationで用意したテンプレートをデプロイします。
SNSで設定したメールアドレス宛にサブスクリプションの承認依頼メールが届くため、Confirm subscriptionで承認します。

2. セキュリティグループを変更

設定したセキュリティグループのインバウンドルールを編集してみます。
見事メール通知が届きました!

おまけとして、メールに記載されているCloudTrailイベントIDをコンソールで検索してみます。

ここからより詳細な情報が確認できますね。

最後に

今回は特定のセキュリティグループが変更されたことを検知して、メール通知させる方法をご紹介しました。

EventBridgeに連携できるAPIであれば、色々なサービスと連携して通知が出来そうですね!
夢が広がります。

最後までお読みいただきありがとうございました! どなたかのお役に立てれば幸いです。

以上、おつまみ(@AWS11077)でした!

参考

EC2 Linux セキュリティグループに対する変更をモニタリングする

CloudWatchアラームとSNSで日本語の件名・本文のメールを送るためのCloudFormationテンプレートを作ってみた | DevelopersIO

Amazon EventBridge event patterns - Amazon EventBridge

EventBridge ルール用のカスタムイベントパターンを作成する

Amazon SNSのEメール通知のデフォルトの件名をAWS Step FunctionsとAmazon EventBridge Input Transformerで変更してみた | DevelopersIO