AWS Systems Manager Session Managerのセッションロギング設定をAWS CDKで実装してみた。

# はじめに
`AWS Systems Manager Session Manager`（以降、`Session Manager`）とは、`AWS Systems Manager`の機能の一つで、`Amazon EC2 for Linux`（以降、`EC2`）に対してSSHを利用せずに接続できる機能となります。
`Session Manager`には、セッションロギング機能も提供されており、`Session Manager`経由で接続したセッションの操作ログを残すことが可能です。
`Amazon CloudTrail`（以降、`CloudTrail`）の操作ログはあくまでも`Session Manager`から`EC2`へのセッションの開始/終了などのログしか記録されないので、`EC2`内での操作も証跡として残したい場合は、`Session Manager`のセッションロギング機能を有効化するのがよさそうです。

ロギングの有効化設定はマネージメントコンソールや`AWS CDK`（以降、`CDK`）などで可能なのですが、`CDK`の実装方法があまり出回っていないため、本記事でご紹介したいと思います。

# 前提条件
`Session Manager`を利用するためには、`EC2`が以下の条件を満たしている必要があります。

* `SSM Agent`のインストール＋起動
    * AWSが提供する`Amazon Linux`はデフォルトで導入済み
* IAMのAWSマネージドポリシー`AmazonSSMManagedInstanceCore`のアタッチ
* HTTPS (ポート 443) アウトバウンド・トラフィックの通信許可
        * `EC2`をプライベートサブネットに配置する場合は、`NAT Gateway`もしくは以下の`VPC Endpoint`を構築する必要あり
            * com.amazonaws.`region`.ssm
            * com.amazonaws.`region`.ssmmessages
            * com.amazonaws.`region`.ec2messages ※`SSM Agent`のバージョンが`3.3.40.0`以降の場合は不要

# 実装
## `CDK`ソースコードの構成図
今回は動作確認用に以下の構成図のAWSリソースを作成します。
![session-manager-logging.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601200/72f79fb6-78bb-3b2a-a0ec-120992bc596d.png)

## `CDK`ソースコード（スタックファイルのみ）
```ts:lib/session-manager-logging-stack.ts
import { Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import {
  Vpc,
  IpAddresses,
  SubnetType,
  SecurityGroup,
  Peer,
  Port,
  Instance,
  InstanceType,
  InstanceClass,
  InstanceSize,
  AmazonLinuxImage,
  AmazonLinuxGeneration,
} from "aws-cdk-lib/aws-ec2";
import { Bucket } from "aws-cdk-lib/aws-s3";
import { PolicyStatement } from "aws-cdk-lib/aws-iam";
import { CfnDocument } from "aws-cdk-lib/aws-ssm";

export class SessionManagerLoggingStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // Session Managerログ保管用S3バケット
    const bucket = new Bucket(this, "MyBucket");

    // EC2向けVPC（パブリックサブネット1つのみ）
    const vpc = new Vpc(this, "MyVpc", {
      ipAddresses: IpAddresses.cidr("10.0.0.0/16"),
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: "public-subnet",
          subnetType: SubnetType.PUBLIC,
        },
      ],
      maxAzs: 1,
    });

    // EC2（アウトバウンド通信443のみ許可されたインスタンス）
    const securityGroup = new SecurityGroup(this, "MySecurityGroup", {
      vpc,
      allowAllOutbound: false,
    });
    securityGroup.addEgressRule(Peer.anyIpv4(), Port.tcp(443));
    const instance = new Instance(this, "MyInstance", {
      vpcSubnets: {
        subnetType: SubnetType.PUBLIC,
      },
      instanceType: InstanceType.of(InstanceClass.T3, InstanceSize.NANO),
      machineImage: new AmazonLinuxImage({
        generation: AmazonLinuxGeneration.AMAZON_LINUX_2023,
      }),
      vpc: vpc,
      associatePublicIpAddress: true,
      securityGroup: securityGroup,
      ssmSessionPermissions: true, // EC2のRoleにAmazonSSMManagedInstanceCoreのPolicyがアタッチされる
    });
    // セッションログをS3に出力するための権限追加
    instance.addToRolePolicy(
      new PolicyStatement({
        actions: ["s3:GetEncryptionConfiguration", "s3:PutObject"],
        resources: [bucket.bucketArn, `${bucket.bucketArn}/*`],
      })
    );

    // Session Managerログ設定（SSMドキュメント）
    new CfnDocument(this, "MySessionManagerPreferences", {
      name: "SSM-SessionManagerRunShell",
      documentType: "Session",
      content: {
        schemaVersion: "1.0",
        description: "Document to hold regional settings for Session Manager",
        sessionType: "Standard_Stream",
        inputs: {
          s3BucketName: bucket.bucketName,
          s3EncryptionEnabled: true,
        },
      },
    });
  }
}

```

上記ソースコードを利用して環境をデプロイし、以降の動作確認を実施していきます。

# 動作確認

1. マネージメントコンソールから`EC2`に`Session Manager`で接続
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601200/23715ac8-35d8-d9fe-dc3e-018896cafffd.png)
接続後、ログ確認向けにいくつかコマンドを実行して、右上の「終了」ボタンを押下しセッションを終了します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601200/f145242a-05a5-4ea4-55db-4bbd166e5dd3.png)
2. アップロードされた`S3`ログファイルを確認
セッションを終了すると`EC2`から`S3`に対してセッションログが以下のスクショのとおりに出力されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601200/90c2abc7-ca4b-ffb1-da61-6c902a016d5b.png)
3. セッションログファイル中身を確認
セッションログをローカルにダウンロードして中身を見てみると以下のようにセッション内で実行したコマンドやその結果が記録されていました。
※ただしなぜか文字化けや不要な改行が含まれています。要因がわかっていません…。
    ```txt:MatsuMikan-iskvzqvatbpxd3eynv64u9eqqa.log
    Script started on 2024-12-15 03:38:33+00:00 [<not executed on terminal>]
    [?2004hsh-5.2$ 
    [K
    sh-5.2$ s[Kpwd
    
    [?2004l
    /usr/bin
    
    [?2004hsh-5.2$ ua[Kname 0a[K[K-a
    
    [?2004l
    Linux ip-10-0-0-147.ap-northeast-1.compute.internal 6.1.115-126.197.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Nov  5 17:36:57 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
    
    [?2004hsh-5.2$ [7myum info amazon-ssm-agent[27m
    [C[C[C[C[C[C[C[Cyum info amazon-ssm-agent
    
    [?2004l
    Amazon Linux 2023 repository                           [===                                              ] ---  B/s |   0  B     --:-- ETA
    Amazon Linux 2023 repository                       56% [===========================-                     ]  55 MB/s |  17 MB     00:00 ETA
    Amazon Linux 2023 repository                       91% [============================================-    ]  54 MB/s |  27 MB     00:00 ETA
    Amazon Linux 2023 repository                                                                                35 MB/s |  29 MB     00:00    
    
    Amazon Linux 2023 Kernel Livepatch repository          [===                                              ] ---  B/s |   0  B     --:-- ETA
    Amazon Linux 2023 Kernel Livepatch repository                                                               54 kB/s |  11 kB     00:00    
    
    Installed Packages
    
    History database cannot be created, using in-memory database instead: SQLite error on "/var/lib/dnf/history.sqlite": Open failed: unable to open database file
    
    Name         : [36mamazon-ssm-agent(B[m
    
    Version      : 3.3.987.0
    
    Release      : 1.amzn2023
    
    Architecture : x86_64
    
    Size         : 125 M
    
    Source       : amazon-ssm-agent-3.3.987.0-1.amzn2023.src.rpm
    
    Repository   : @System
    
    Summary      : Manage EC2 Instances using SSM APIs
    
    URL          : http://docs.aws.amazon.com/ssm/latest/APIReference/Welcome.html
    
    License      : ASL 2.0
    
    Description  : This package provides Amazon SSM Agent for managing EC2 Instances using SSM APIs
    
    
    
    [?2004hsh-5.2$ 
    [K
    sh-5.2$ 
    [K
    sh-5.2$ 
    Script done on 2024-12-15 03:38:33+00:00 [COMMAND_EXIT_CODE="0"]
    ```

一部の文字化けは残念な感じではありますが、一応`EC2`内でどのようなコマンドが実行されているかの履歴を取得することができました。

:::note alert
上記ログ内容のとおり、セッションログはコマンド結果がもろにログに記録されます。
特にDBのコマンド（psqlなど）を実行するとそのコマンド結果もログに記録されますので、DBのSELECT結果がログに残ってしまう可能性があります。そういった仕様もご理解の上、セッションログの有効化有無をご検討いただくのがよいでしょう。
※公式のドキュメントには、コマンド実行時に秘匿情報を出力しないようなコマンド例はありますが、`Session Manager`の機能で特定のコマンド結果を出力しないように制御することはできない（2024/12時点）ので、ヒューマンエラーによりうっかりログ出力される危険性はあります。
:::

# トラブルシューティング

## CDKのデプロイ時に`'SSM-SessionManagerRunShell' already exists.`のデプロイエラーが発生する

一度でもマネージメントコンソールからセッションロギング設定を有効化していると、
`SSM-SessionManagerRunShell`のドキュメントが既に存在しているエラーが発生してしまいます。

```bash:エラーメッセージ
❌  SessionManagerLoggingStack failed: Error: The stack named SessionManagerLoggingStack failed creation, it may need to be manually deleted from the AWS console: ROLLBACK_COMPLETE: Resource handler returned message: "Resource of type 'AWS::SSM::Document' with identifier 'SSM-SessionManagerRunShell' already exists." (RequestToken: 6ab45d62-c824-bf44-1d0b-dd991baadd2b, HandlerErrorCode: AlreadyExists)
```

本設定を`CDK`で管理したい場合は、作成済みの`SSM-SessionManagerRunShell`ドキュメントを削除すればよいのですが、マネージメントコンソール上では削除できないため、`AWS CLI`での削除が必要になります。

```bash:削除コマンド
aws ssm delete-document --name SSM-SessionManagerRunShell
```

## `S3-KMS`を利用している`S3`に送付されない

`S3`のサーバーサイド暗号化に`KMS`を指定している場合には、`EC2`のIAMロールに以下二つのアクションを追加してください。

* `kms:Decrypt`
* `kms:GenerateDataKey`

# 補足

## `CloudTrail`で取得できる`Session Manager`のログ

`Session Manager`では主に以下のAPIを用いて`EC2`にセッションの操作を行いますので、`CloudTrail`でいつセッションを開始したか、または終了したかのログを追跡することは可能です。

* `StartSession`: セッションを開始する
* `TerminateSession`: セッションを終了する
* `ResumeSession`: 接続されたセッションに再接続する

▼以下は`StartSession`で絞り込んだスクリーンショット
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601200/fb057f9f-9180-8771-962d-bf23873f5d59.png)


## マネージメントコンソールでの設定方法（2024/12時点、スクショのみ）

`CDK`のソースコードでは`SSM Document`を新規作成したのですが、マネージメントコンソール上では以下のようにセッションログ設定用の画面が用意されているので、こちらから設定することも可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601200/f30dcb63-a87c-ba9f-36a9-3f19ed56a13c.png)

# おまけ：`CDK`実装で困ったら`Amazon Q`に頼るもよし

実は、本設定の`CDK`実装はかなり苦戦しました。
マネージメントコンソール上での設定方法は把握していたものの、本設定が`SSM Document`に関連していることに気づくことができず、`CDK`のリファレンスを探しても本設定方法になかなかたどり着きませんでした。
そんなときにおすすめなのが、`Amazon Q`で、今回はそちらを頼っていました（笑）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601200/ebac3bbb-7186-7f1d-0803-f89c33767830.png)

本設定に限らず、`CDK`の実装で困ったことがあれば、`Amazon Q`に頼ってみるのも手ではないでしょうか。

# おわりに

ニッチな記事ではございますが、`CDK`でセッションログの有効化を行いたい方や、セッションログの出力内容について、少しでも知見を共有できれば幸いです。

`AWS re:Invent 2024`の情報で盛り上がっている中、お付き合いくださりありがとうございました！

こちらからは以上です。

# 参考URL

* https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager-logging.html

* https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/getting-started-create-iam-instance-profile.html