以前に以下の記事でCloudTrailの取得をCloudFormationで有効化しました。 S3にCloudTrailログがどんどん溜まってきているところまで作成しています。
AWSのCloudFormationでIaCを実践(6) – CloudTrail 
https://syachiku.net/awscloudformationiac6-trail/
今回はCloudTrailログを溜めるだけではなく、せっかくなのでグラフィカルに可視化をしていきたいと思います。
可視化する方法としてはEC2インスタンスに可視化ツールをインストールすることもできますが、今回はAWSのマネジメントサービスである「Amazon Elasticsearch Service」を使います。
さらに、できる限り(全部ではないです)CloudFormationによるコード化をするようにしたいと思います。
1. 前提条件
- AWSアカウントが取得できていること
- 適切な権限(=Admin相当)をもったIAMユーザーを作成していること
- ルーティングなどの最低限のネットワークの知識があること
- YAMLの書きかたを知っていること(CloudFormationはjsonもしくはyamlで書けますが、今回はyamlで書いていきます。)
- kibanaレポート(ElasticSeachのクラスタ)がVPCのプライベートサブネットに配置されるので、ブラウザでアクセスできる環境があること(WindowsのEC2などでもOK)- 今回は自宅からVPNで接続してProxyを経由してアクセスします。
 
※今回実施するElasticseachについては無料枠の範囲ではないため、少なからず利用料金が発生します、あくまで自己責任でお願いします。
2. 構成図
構成図としては以下の様な感じになるかと思います。CloudWatchLogsのリソースはCroudTrailと同じスタックにしました。

3. 今回作成するAWS環境と作業手順
大阪リージョンの場合にはElasticSeachの最小インスタンスが結構大きいものしか選択できなかったことと、CWLogSubscriptionでElasticseachの選択肢がなかったので、オレゴンリージョンで構築しています。
それでは、以下の手順で構築を行っていきます。
- ElasticSearchのドメイン作成時に必要となる「EsServiceLinkedRole」を作成
- CloudTrailのログをS3バケットへ保管するのに追加し、Cloudwach Loggroupにも転送するように設定(以下は前回との変更点です)- CloudTrailログ保管するCWLogGroupを新規作成
- LogGroupのSubscription Filterで利用するロールを作成
 
- ElasticSeachドメインを作成- 2台のクラスタ構成(PrivateSubnet)でドメイン構成
- Elasticsearch用のSecurityGroupを作成
 
ここまではCloufFormationで構築しますが、以下の作業は手動でマネジメントコンソールで構築しました。 マネジメントコンソール経由だと自動でLambdaなどを作成してくれるのでこっちにしてます。
- LogGroupへサブスクリプションフィルター作成(+Lambda関数も自動で作成)
- kibanaのレポートを手動で作成
4. CloudFormation Template
最初にEsServiceLinkedRoleの構築です。 ES以外も想定してsystem-roleとしてスタックを分離して構築してます。
AWSTemplateFormatVersion: "2010-09-09"
Description:
  system role
Resources:
  EsServiceLinkedRole:
    Type: "AWS::IAM::ServiceLinkedRole"
    Properties:
      AWSServiceName: es.amazonaws.com
      Description: "Service Linked Role for Amazon Elasticsearch Service"
Outputs:
  EsServiceLinkedRole:
    Value: !Ref EsServiceLinkedRole
    Export:
      Name: es-service-linked-role
次にCloudTrailのテンプレートとなります。
AWSTemplateFormatVersion: 2010-09-09
Description: CloudTrail
Parameters:
  LogGroupName:
    Type: String
    Description: Enter LogGroupName
    Default: "aws-infra-croudtrail-loggroup"
  LogStreamName:
    Type: String
    Description: Enter LogStreamName
    Default: "aws-infra-croudtrail-logstream"
Resources:
  CloudTrailLogsBucket:
    Type: AWS::S3::Bucket
    #    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      BucketName: !Sub ${AWS::Region}-cloudtrail-logs
      AccessControl: LogDeliveryWrite
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      LifecycleConfiguration:
        Rules:
          - Id: AutoDelete
            Status: Enabled
            ExpirationInDays: 14
          - Id: NoncurrentVersionExpiration
            Status: Enabled
            NoncurrentVersionExpirationInDays: 7
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled
  # CloudTrailログ格納用バケットポリシー
  CloudTrailLogsBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref CloudTrailLogsBucket
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: CloudTrailAclCheck
            Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: s3:GetBucketAcl
            Resource: !Sub arn:aws:s3:::${CloudTrailLogsBucket}
          - Sid: CloudTrailWrite
            Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: s3:PutObject
            Resource: !Sub arn:aws:s3:::${CloudTrailLogsBucket}/AWSLogs/${AWS::AccountId}/*
            Condition:
              StringEquals:
                s3:x-amz-acl: bucket-owner-full-control
  # Create CWLog group
  CloudTrailLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Ref LogGroupName
      RetentionInDays: 3
  # Create CloudWTrailRole for CWLogs and ElasticSearch
  CloudTrailLogRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - !Sub "cloudtrail.${AWS::URLSuffix}"
                - !Sub "lambda.${AWS::URLSuffix}"
            Action:
              - "sts:AssumeRole"
      Path: "/"
      Policies:
        - PolicyName: aws-infra-policy-cloudtrail-es
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'es:ESHttpPost'
                Resource: "arn:aws:es:*:*:*"
              - Effect: Allow
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:DescribeLogStreams"
                  - "logs:PutLogEvents"
                  - "ec2:CreateNetworkInterface"
                  - "ec2:DescribeNetworkInterfaces"
                  - "ec2:DeleteNetworkInterface"
                Resource: "*"
              - Effect: Allow
                Action:
                  - 'lambda:InvokeFunction'
                Resource: "arn:aws:logs:*:*:*"
  # CloudTrailから配信されるログの暗号化キー
  # https://docs.aws.amazon.com/ja_jp/awscloudtrail/latest/userguide/create-kms-key-policy-for-cloudtrail.html
  KmsKey:
    Type: AWS::KMS::Key
    Properties:
      KeyPolicy:
        Version: 2012-10-17
        Id: DefaultKeyPolicy
        Statement:
          - Sid: EnableIAMUserPermissions
            Effect: Allow
            Principal:
              AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
            Action: kms:*
            Resource: '*'
          - Sid: AllowCloudTrailToEncryptLogs
            Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: kms:GenerateDataKey*
            Resource: '*'
            Condition:
              StringLike:
                kms:EncryptionContext:aws:cloudtrail:arn:
                  - !Sub arn:aws:cloudtrail:*:${AWS::AccountId}:trail/*
          - Sid: AllowCloudTrailToDescribeKey
            Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: kms:DescribeKey
            Resource: '*'
      KeyUsage: ENCRYPT_DECRYPT
  CloudTrail:
    Type: AWS::CloudTrail::Trail
    Properties:
      CloudWatchLogsLogGroupArn: !GetAtt CloudTrailLogGroup.Arn
      CloudWatchLogsRoleArn: !GetAtt CloudTrailLogRole.Arn
      S3BucketName: !Ref CloudTrailLogsBucket
      KMSKeyId: !Ref KmsKey
      TrailName: CloudTrailLog
      IncludeGlobalServiceEvents: true
      IsLogging: true
      IsMultiRegionTrail: true
      EnableLogFileValidation: true
      EventSelectors:
        - DataResources:
            - Type: AWS::S3::Object
              Values:
                - arn:aws:s3
ElasticSearchを構築するテンプレートです。
AWSTemplateFormatVersion: "2010-09-09"
Description:
  elastic search
Parameters:
  ESDomainName:
    Description: "Your Elasticsearch Domain Name"
    Type: String
    MinLength: 3
    MaxLength: 28
    AllowedPattern: "^[a-z0-9+-]*$"
    Default: "aws-infra-es-domain"
  MyVpcId:
    Type: String
    Default: "vpc-id"
  MyVpcCidrBlock:
    Type: String
    Default: "10.1.0.0/16"
  MyEsSubnetId01:
    Type: String
    Default: "subnet-private01"
  MyEsSubnetId02:
    Type: String
    Default: "subnet-private02"
  InstanceType:
    Type: String
    Default: "t3.small.elasticsearch"
    AllowedValues:
      - t3.small.elasticsearch
      - t3.medium.elasticsearch
      - r5.large.elasticsearch
Resources:
  # Security Group
  # For Amazon Elasticsearch Service
  ElasticsearchSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: elasticsearch-sg
      VpcId: !Ref MyVpcId
      Tags:
        - Key: Name
          Value: aws-infra-elasticsearch-sg
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '443'
          ToPort: '443'
          CidrIp: !Ref MyVpcCidrBlock
  # Elasticsearch on VPC
  MyElasticsearch:
    Type: AWS::Elasticsearch::Domain
    Properties:
      AccessPolicies:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS: '*'
            Action: 'es:*'
            Resource: !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${ESDomainName}/*
      DomainName: !Ref ESDomainName
      EBSOptions:
        EBSEnabled: true
        VolumeSize: 10
        VolumeType: gp2
      ElasticsearchClusterConfig:
        InstanceCount: 2
        InstanceType: !Ref InstanceType
        ZoneAwarenessEnabled: true
      ElasticsearchVersion: 7.4
      SnapshotOptions:
        AutomatedSnapshotStartHour: 17
      VPCOptions:
        SubnetIds:
          - !Ref MyEsSubnetId01
          - !Ref MyEsSubnetId02
        SecurityGroupIds:
          - !Ref ElasticsearchSecurityGroup
    UpdatePolicy:
      EnableVersionUpgrade: true5. スタックの作成
それでは先ほど作成したコードを使って、リソース(CloudFormationで作成できる部分)を作成していきます。
マネージメントコンソールにログオンして、リージョンをオレゴンに切り替えたのち、CloudFormationを開きます。
「スタックの作成」→「新しいリソースを使用」を選択します。
「テンプレートファイルをアップロード」からコードのファイルを選択してアップします。
スタックの名称やネットワーク設定値を入力(デフォルトから変更したい場合には変更してください)してスタック作成をしてください。
※「今回作成するAWS環境と作業手順」に記載している順番通りにスタックを作成してください。
5.1. IAM Role


5.2. セキュリティグループ

5.3. CloudWatchロググループ

5.4. Elasticsearchドメイン
Elasticsearchドメイン構築はかなり時間がかかります。20分くらいかかりました。

6. ロググループのサブスクリプションフィルター作成
次に手動操作になります。マネコン経由でロググループのサブスクリプションフィルター作成をします。
Elasticsearch 用のsubscription Filterのメニューが存在します(大阪リージョンでは存在しませんでした2021年8月時点)

送信先に先ほど作成したElasticsearchを選択します。
ログ形式としてCloudTrailの項目が用意されています。これを選択するだけでLambdaも自動的に作成してくれるようです。なんて便利なんだ・・・

7. kibanaレポートの作成
最後にkibanaのレポート作成です。
プライベートサブネットにアクセスできるマシンのブラウザ経由で実施してください。
まずはkibanaのURLをElasticsearchのドメインの概要から確認します。

kibanaのURLへアクセスします。
インデックスを作成します。cloudtrailのログインデックスがcwl-xxxxという日付単位の形式なので、cws-20*とかで作成します。

timestampを指定するだけですね。

これだけです。簡単に可視化レポートができました。
あとは左側の項目でフィルタかければ項目単位で検索することができますね。

8. まとめ
CloudTrailのログをCWLogsのサブスクリプションフィルター経由でElasticsearchに流してkibanaでのレポート表示までをおこないました。
ログを可視化する流れとしては「CWLogsのサブスクリプションフィルター→Elasticsearch」というのは一番使われている気がします。
他のログなども応用できるかと思います。
できれば全てCloudFormationで行いたかったのですが、思ったよりもマネコン経由での手間がなかったので一部手動での作業手順となりました。
時間がある時(もしくは機会があれば)全てのリソースのCloudFormation化を行いたいと考えています。
AWSを効率的に学習する方法
私が効率的にAWSを学習するために実施した方法は以下の通りです。
 ①最初に書籍(ハンズオンができる)を購入、座学でAWSの基礎を学習
 ②AWS資格試験を取得ための学習
※私の場合は①と②を合わせて2か月でソリューションアーキテクトを取得できました。
①AWS基礎学習
最初に購入した書籍は「Amazon Web Services 基礎からのネットワーク&サーバー構築」です。
Amazon Web Services 基礎からのネットワーク&サーバー構築

この本では、AWSの基本サービスを利用したハンズオンを通じて、AWSの基礎を学習することができます。
また、タイトル通りAWSのネットワークやインフラに関しても網羅しているため、もともとインフラ系の技術者ではない人たちにとっても分かりやすい内容だと思います。
とりあえずAWS上にサーバーを設定して開発を行うための準備までするには最良の一冊です。
Amazon Web Services 基礎からのネットワーク&サーバー構築
②AWS資格取得
AWSの基礎をある程度学習することができたら、次はAWS資格を取得しましょう。まずはAWSソリューションアーキテクトを目指しましょう。

資格勉強のための問題集をひたすら解きながら、AWSの知識を積み重ねて習得していきましょう!
苦行ではありますが、この問題集を3周ほどすればAWS用語や構成に関しても習得できているはずです。
今回は以上となります。
 
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
         
        

コメント