AWS Systems Manager Session Managerでの監査ログ取得 – セッションログ設定編

こんにちは、SCSKでAWSの内製化支援『テクニカルエスコートサービス』を担当している貝塚です。

先日、テクニカルエスコートの顧客から、Windows EC2インスタンスへのRDP接続を画面録画したいというご要望をいただきました。監査要件対応などから、委託先ベンダーが実施するすべての操作を証跡として残す必要があるとのことでした。

AWS Systems Manager Session ManagerはRDP接続に対応しており、録画機能も持っているのでこの機能の導入をベースに検討を進めることになるわけですが、録画機能の設定・検証だけではなくその周辺にも考慮することがありました。それらをまとめたものが本記事となります。

課題と顧客要望

今回の顧客要件は、Windows EC2インスタンスへのすべてのアクセスを記録したいというものでした。監査要件対応のため、誰が、いつ、どのような操作を行ったかを証跡として残す必要があります。

Session Managerは、EC2インスタンスへのアクセス方法として以下の2つをサポートしています。

1. 対話型シェルセッション(CLI): コマンドラインでの操作
2. Fleet Manager Remote Desktop(RDP): GUIでの画面操作

委託先ベンダーが使用するのはRDP接続だけとのお話でしたが、Session ManagerのCLI経由でもWindows PowerShellやコマンドプロンプトを使ってOS操作が可能です。つまり、RDP録画だけでは不十分で、CLIセッションのログ記録も必要になります。

さらに重要なのは、Session Manager以外の手段でログインできてしまうと、すべての操作記録を取るという目的が達成できないという点です。ネットワーク経由で直接RDP(ポート3389)接続できてしまうと記録漏れが発生する可能性があります。

そのため、今回の実装では以下の2つのアプローチを採用しました。

1. Session Managerのセッションログ記録とRDP録画を設定
2. ネットワークレベルでSession Manager以外の接続経路をブロック

本記事では、1つ目のアプローチであるSession Managerの設定のうち、セッションログ記録について説明します。RDP録画の設定、ネットワークまわりの対応については次回以降の記事で説明します。

Session Managerの基本概念

Session Managerのセッションログ記録とRDP録画の設定方法を解説する前に、Session Managerの基本的な仕組みを理解しておきましょう。

Session Managerとは

AWS Systems Manager Session Managerは、EC2インスタンスへの安全なアクセスを提供するマネージドサービスです。従来のSSH(Linuxの場合)やRDP接続と異なり、以下の特徴があります。

  • インバウンドポート(SSH 22番、RDP 3389番)の開放が不要
  • SSHキーの管理が不要(Linuxの場合)
  • IAMベースのアクセス制御
  • すべての接続がCloudTrailに記録される

プライベートVPC環境での動作

今回の顧客はインターネットゲートウェイを持たないプライベートなVPC環境を構築しているため、AWSサービスにはVPCエンドポイント経由でアクセスします。
Session Managerが動作するために必要なVPCエンドポイントは以下の通りです。

  • ssm.region.amazonaws.com: Systems Manager APIエンドポイント
  • ssmmessages.region.amazonaws.com: Session Managerメッセージングエンドポイント
  • ec2messages.region.amazonaws.com: EC2メッセージングエンドポイント ※SSM Agent バージョン 3.3.40.0以降では不要になっています

セッションログ記録とRDP録画を有効化する場合は、さらに以下のエンドポイントが必要です。

  • logs.region.amazonaws.com: CloudWatch Logsエンドポイント(セッションログをCloudWatchに記録するとき用)
  • kms.region.amazonaws.com: KMSエンドポイント(暗号化用)
  • s3.region.amazonaws.com: S3エンドポイント(RDP録画ファイル保存用)

これらのVPCエンドポイントにより、EC2インスタンスはインターネットに接続することなく、AWSサービスと通信できます。

セッションログ記録の設定

セッションログ記録の概要

Session Managerは、セッション中に実行されたコマンドや操作の詳細をログとして記録できます。ログの保存先として、以下の2つのオプションがあります。

  • CloudWatch Logs: リアルタイムでログを確認でき、検索やフィルタリングが容易
  • Amazon S3: 長期保存に適しており、コスト効率が高い

ログ記録を有効化することで、以下のような監査要件に対応できます。

  • 誰が、いつ、どのインスタンスに接続したか
  • セッション中にどのようなコマンドを実行したか
  • 操作の証跡を長期保存し、監査時に提示できる

本実装での設計方針

今回の実装では、監査要件対応のため、すべてのSession Manager接続でログを記録する設計を採用しました。具体的には、以下の方針で実装します。

1. SSM-SessionManagerRunShellドキュメント(後述)でログ記録を有効化: マネジメントコンソールおよびAWS CLIからの接続で自動的にログが記録される
2. CloudWatch Logsに記録: リアルタイムでログを確認でき、検索やフィルタリングが容易
3. KMS暗号化を有効化: ログデータを暗号化して保護
4. IAMポリシーでドキュメントを制限: ユーザーが別のドキュメントを指定してログ記録を回避できないようにする

この設計により、記録漏れを防ぎ、すべての操作を証跡として残すことができます。詳細な設定方法については、後続のセクションで説明します。

AWS CloudFormationでの設定

検証用のCloudFormationテンプレート(cfn-session-manager-rdp-logging.yaml)を用意しました。※本記事末尾に掲載

事前準備: EC2キーペアの作成

CloudFormationテンプレートをデプロイする前に、EC2キーペアを作成しておいてください。

パラメータファイルの作成

CloudFormationテンプレートのパラメータを指定するため、以下の内容でパラメータファイルを作成します。

[
  {
    "ParameterKey": "KeyPairName",
    "ParameterValue": "my-keypair"
  },
  {
    "ParameterKey": "ProjectName",
    "ParameterValue": "session-manager-logging"
  }
]

パラメータの説明:
KeyPairName: 既存のEC2キーペア名(必須)
ProjectName: プロジェクト名(リソース名のプレフィックスに使用)

AWS CloudFormationスタックのデプロイ

以下のコマンドでCloudFormationスタックをデプロイします。

aws cloudformation create-stack \
  --stack-name my-session-manager-stack \
  --template-body file://cfn-session-manager-rdp-logging.yaml \
  --parameters file://parameters-my-stack.json \
  --capabilities CAPABILITY_NAMED_IAM \
  --region ap-northeast-1

このテンプレートで以下のリソースが作成されます。

作成されるリソース概要

検証用ネットワーク: VPC 1つ、プライベートサブネット 1つ、ルートテーブル、セキュリティグループ 2つ
VPCエンドポイント: SSM、SSM Messages、EC2 Messages、CloudWatch Logs、KMS(各Interface型)、S3(Gateway型)
EC2インスタンス: Windows Server 2022インスタンス 1台
IAMロールとポリシー: EC2インスタンスプロファイル、Session Manager/CloudWatch Logs/S3/RDP録画用の権限ポリシー
S3バケット: RDP録画ファイル保存用バケット(バケットポリシー含む)
CloudWatch Logsロググループ: セッションログ記録用ロググループ
KMSキー: RDP録画とセッションログ暗号化用のカスタマーマネージドキー(エイリアス含む)
Session Managerドキュメント: オプトインSession Manager設定ドキュメント(テスト用)

セッションログ用だけではなく、RDP録画用のリソースも作成しています。
以下では、セッションログ取得にあたって重要なリソースについて詳細を説明します。

1. CloudWatch Logsロググループ

セッションログを記録するCloudWatch Logsロググループを作成します。保持期間は本記事用に30日に設定していますが、実際の要件に合わせて変更してください。

SessionLogsLogGroup:
  Type: AWS::Logs::LogGroup
  Properties:
    LogGroupName: !Sub '/aws/ssm/${ProjectName}-session-logs'
    RetentionInDays: 30
    KmsKeyId: !GetAtt RDPRecordingKMSKey.Arn

2. IAMロールへの権限追加

EC2インスタンスロールに、CloudWatch Logsへの書き込み権限を追加します。

EC2RoleCloudWatchLogsPolicy:
  Type: AWS::IAM::Policy
  Properties:
    PolicyName: !Sub '${ProjectName}-cloudwatch-logs-policy'
    PolicyDocument:
      Version: '2012-10-17'
      Statement:
        - Effect: Allow
          Action:
            - 'logs:CreateLogStream'
            - 'logs:PutLogEvents'
            - 'logs:DescribeLogStreams'
          Resource: !GetAtt SessionLogsLogGroup.Arn
        - Effect: Allow
          Action:
            - 'logs:DescribeLogGroups'
          Resource: '*'
    Roles:
      - !Ref EC2Role

3. KMSキーの作成
CloudWatch Logsに記録されるセッションログは、KMSカスタマーマネージドキーで暗号化されます。
CloudFormationテンプレートで、以下のようにKMSキーを作成します。

  RDPRecordingKMSKey:
    Type: AWS::KMS::Key
    Properties:
      Description: 'KMS key for RDP recording and session logs encryption'
      KeyPolicy:
        Version: '2012-10-17'
        Statement:
(中略)
          - Sid: 'Allow EC2 role to use the key'
            Effect: Allow
            Principal:
              AWS: !GetAtt EC2Role.Arn
            Action:
              - 'kms:CreateGrant'
              - 'kms:Decrypt'
              - 'kms:DescribeKey'
            Resource: '*'
          - Sid: 'Allow CloudWatch Logs to use the key'
            Effect: Allow
            Principal:
              Service: !Sub 'logs.${AWS::Region}.amazonaws.com'
            Action:
              - 'kms:Encrypt'
              - 'kms:Decrypt'
              - 'kms:ReEncrypt*'
              - 'kms:GenerateDataKey*'
              - 'kms:CreateGrant'
              - 'kms:DescribeKey'
            Resource: '*'
            Condition:
              ArnLike:
                'kms:EncryptionContext:aws:logs:arn': !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/ssm/${ProjectName}-session-logs'
      Tags:
        - Key: SystemsManagerJustInTimeNodeAccessManaged
          Value: 'true'

設計上の留意点:

  • KMSキーには必須タグ SystemsManagerJustInTimeNodeAccessManaged: true を設定
  • CloudWatch Logsサービスプリンシパルに暗号化・復号化権限を付与
  • kms:EncryptionContext:aws:logs:arn 条件でアクセスを制限

SSM-SessionManagerRunShellドキュメントとは

Session Managerの設定を行うと、AWSは自動的に SSM-SessionManagerRunShell という名前のセッションドキュメントを作成します。このドキュメントには、以下のようなセッション設定が保存されます。

  • ログ保存先(CloudWatch LogsまたはS3バケット)
  • KMS暗号化の設定
  • Run As設定(Linuxノードでの実行ユーザー指定)

ただし、デフォルトではログ記録は無効になっています。ログ記録を有効化するには、SSM-SessionManagerRunShell ドキュメントを更新する必要があります。その前に、このドキュメントがSession Managerでどのような役割を果たしているのか説明します。

マネジメントコンソールからの接続の場合

AWS Management Console(Systems ManagerのFleet ManagerまたはSession Manager、EC2 ConnectのSession Manager)からSession Manager接続を開始すると、必ずSSM-SessionManagerRunShellドキュメントが使用されます。

したがって、マネジメントコンソールから開始したセッションのログを記録したい場合は、SSM-SessionManagerRunShellドキュメントでログ記録を有効化しておく必要があります。

AWS CLIからの接続の場合

AWS CLIの aws ssm start-session コマンドでSession Manager接続を開始する場合、以下の動作になります。

ドキュメントを指定しない場合: SSM-SessionManagerRunShellドキュメントが使用される
–document-nameパラメータでドキュメントを指定した場合: 指定したドキュメントの設定が使用される

例えば、以下のコマンドではポートフォワーディング用のドキュメントを指定しています。

aws ssm start-session \
    --target i-1234567890abcdef0 \
    --document-name AWS-StartPortForwardingSession \
    --parameters '{"portNumber":["80"],"localPortNumber":["8080"]}'

この場合、AWS-StartPortForwardingSessionのログ記録設定が使用され、SSM-SessionManagerRunShellのログ記録設定は適用されません。

ログ記録を回避できる可能性と対策

上記の通り、AWS CLIで –document-name パラメータを使用すると、ログ記録を回避できてしまう可能性があります。すべてのセッションのログを記録するには、以下のいずれかの対策が必要です。

対策1: ログ記録しないドキュメントを作成させない

この対策を有効にするには、以下の2つの条件を満たす必要があります。

1. ログ記録設定のないカスタムドキュメントをアカウント内に置かない
2. ユーザーに ssm:CreateDocument および ssm:UpdateDocument 権限を付与しない(ドキュメントの新規作成・変更を禁止)

対策2: IAMポリシーでSSM-SessionManagerRunShellのみを許可する

IAMポリシーでSSM-SessionManagerRunShell以外のドキュメントを明示的に拒否(Deny)することで、ユーザーがログ記録を回避できないようにします。本案件ではこちらを採用しました。

以下のポリシー例では、NotResourceを使ったDenyステートメントで、SSM-SessionManagerRunShell以外のドキュメントを使用した接続(ssm:StartSession)を禁止しています。(SSM-SessionManagerRunShellはポリシー内の別の箇所で許可してください)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenySessionsWithoutLoggingDocument",
      "Effect": "Deny",
      "Action": "ssm:StartSession",
      "NotResource": "arn:aws:ssm:*:*:document/SSM-SessionManagerRunShell"
    }
  ]
}

EC2インスタンスプロファイルの権限要件

SSM-SessionManagerRunShellドキュメントでログ記録を有効化した場合、EC2インスタンスプロファイルにログ記録先への書き込み権限が必要です。

権限が不足している場合、ログが記録されないだけでなく、セッション自体がエラーになります。したがって、マネジメントコンソールからSession Manager接続するすべてのEC2インスタンスには、以下の権限が必要です。

  • CloudWatch Logsへの書き込み権限(logs:CreateLogStream、logs:PutLogEvents)
  • KMS暗号化を使用する場合は、KMSキーへのアクセス権限(kms:Decrypt、kms:GenerateDataKey)

デフォルトログ記録の有効化手順

CloudFormationスタックのデプロイ後、enable-default-session-logging.shスクリプトを実行してSSM-SessionManagerRunShellドキュメントのログ記録を有効化します。※本記事末尾に掲載

./enable-default-session-logging.sh ap-northeast-1 default session-manager-stack

defaultのところはAWS CLIで使用するプロファイル名、session-manager-stackのところは、デプロイ済みのCloudFormationスタック名を指定してください。

このスクリプトは、CloudFormationスタックからCloudWatch LogsグループとKMSキーARNを取得し、SSM-SessionManagerRunShellドキュメントを更新します。スクリプト実行後、マネジメントコンソールおよびAWS CLI(ドキュメント指定なし)からのすべてのSession Manager接続で、自動的にCloudWatch Logsにログが記録されるようになります。

動作確認手順

ここまでで、設定は完了です。

マネージドコンソールから、作成されたWindowsインスタンスにSSM Session Managerで接続してみてください。

CLIが表示され、ログ保存先S3バケットにログファイルが保存されていれば正常に動作しています。

また、インスタンスへのSession Manager接続を許可するときに使われるAmazonSSMManagedInstanceCoreのみをポリシーとして持つインスタンスプロファイルのEC2に接続すると図のようなエラーになることも確認してみてください。(下図)

デフォルトログ記録の無効化手順

この設定では本検証外でSession Managerを使用するEC2インスタンスへの接続がエラーになってしまうので、検証が終わったら設定を元に戻す必要があります。disable-default-session-logging.shスクリプトを実行してSSM-SessionManagerRunShellドキュメントのログ記録を無効化することができます。※本記事末尾に掲載

./disable-default-session-logging.sh ap-northeast-1 default

defaultのところはAWS CLIで使用するプロファイル名を指定してください。

 

まとめ

監査用ログの取得という観点から、確実にSession Managerでセッションログを取得する方法について検討してきました。CLIのセッションログで、Linuxインスタンスのログも取得できるためこれはこれで有用な設定なのですが、まだRDP録画にすらたどり着いていません。次の記事ではRDP録画について検討します。

ソースコード

本記事で使用したソースコードはこちらです。

cfn-session-manager-rdp-logging.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Session Managerセッションログ取得、RDP録画の検証用環境を構築する'

Parameters:
  KeyPairName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: 'EC2インスタンスに関連付けるキーペア名 (既存のキーペアを指定)'
  
  ProjectName:
    Type: String
    Default: 'session-manager-recording'
    Description: 'プロジェクト名 (リソース名とタグに使用)'
    AllowedPattern: '^[a-z0-9-]+$'
    ConstraintDescription: '小文字英数字とハイフンのみ使用可能'

Resources:
  # VPC
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-vpc'
        - Key: Project
          Value: !Ref ProjectName
        - Key: Environment
          Value: Development
        - Key: ManagedBy
          Value: CloudFormation

  # プライベートサブネット
  PrivateSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.1.0/24
      AvailabilityZone: ap-northeast-1a
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-private-subnet-1a'
        - Key: Project
          Value: !Ref ProjectName
        - Key: Environment
          Value: Development
        - Key: ManagedBy
          Value: CloudFormation

  # プライベートサブネット用ルートテーブル
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-private-rt'
        - Key: Project
          Value: !Ref ProjectName
        - Key: Environment
          Value: Development
        - Key: ManagedBy
          Value: CloudFormation

  # プライベートサブネットとルートテーブルの関連付け
  PrivateSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet
      RouteTableId: !Ref PrivateRouteTable

  # VPCエンドポイント用セキュリティグループ
  VPCEndpointSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub '${ProjectName}-vpce-sg'
      GroupDescription: 'Security group for VPC Endpoints'
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 10.0.0.0/16
          Description: 'Allow HTTPS from VPC CIDR'
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-vpce-sg'
        - Key: Project
          Value: !Ref ProjectName
        - Key: Environment
          Value: Development
        - Key: ManagedBy
          Value: CloudFormation

  # EC2インスタンス用セキュリティグループ
  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub '${ProjectName}-ec2-sg'
      GroupDescription: 'Security group for EC2 instance'
      VpcId: !Ref VPC
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          DestinationSecurityGroupId: !Ref VPCEndpointSecurityGroup
          Description: 'Allow HTTPS to VPC Endpoints'
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-ec2-sg'
        - Key: Project
          Value: !Ref ProjectName
        - Key: Environment
          Value: Development
        - Key: ManagedBy
          Value: CloudFormation

  # VPCエンドポイント - SSM
  SSMVPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssm'
      VpcId: !Ref VPC
      SubnetIds:
        - !Ref PrivateSubnet
      SecurityGroupIds:
        - !Ref VPCEndpointSecurityGroup
      PrivateDnsEnabled: true

  # VPCエンドポイント - SSM Messages
  SSMMessagesVPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssmmessages'
      VpcId: !Ref VPC
      SubnetIds:
        - !Ref PrivateSubnet
      SecurityGroupIds:
        - !Ref VPCEndpointSecurityGroup
      PrivateDnsEnabled: true

  # VPCエンドポイント - EC2 Messages
  EC2MessagesVPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ec2messages'
      VpcId: !Ref VPC
      SubnetIds:
        - !Ref PrivateSubnet
      SecurityGroupIds:
        - !Ref VPCEndpointSecurityGroup
      PrivateDnsEnabled: true

  # VPCエンドポイント - CloudWatch Logs
  LogsVPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.logs'
      VpcId: !Ref VPC
      SubnetIds:
        - !Ref PrivateSubnet
      SecurityGroupIds:
        - !Ref VPCEndpointSecurityGroup
      PrivateDnsEnabled: true

  # VPCエンドポイント - KMS
  KMSVPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.kms'
      VpcId: !Ref VPC
      SubnetIds:
        - !Ref PrivateSubnet
      SecurityGroupIds:
        - !Ref VPCEndpointSecurityGroup
      PrivateDnsEnabled: true

  # VPCエンドポイント - S3 (Gateway型)
  S3VPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Gateway
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3'
      VpcId: !Ref VPC
      RouteTableIds:
        - !Ref PrivateRouteTable

  # EC2用IAMロール
  EC2Role:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${ProjectName}-ec2-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore'
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-ec2-role'
        - Key: Project
          Value: !Ref ProjectName
        - Key: Environment
          Value: Development
        - Key: ManagedBy
          Value: CloudFormation

  # EC2インスタンスプロファイル
  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: !Sub '${ProjectName}-ec2-instance-profile'
      Roles:
        - !Ref EC2Role

  # RDP記録用S3バケット
  SessionLogsBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub '${ProjectName}-session-logs-${AWS::AccountId}'
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      VersioningConfiguration:
        Status: Enabled
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      LifecycleConfiguration:
        Rules:
          - Id: MoveToGlacierAfter90Days
            Status: Enabled
            Transitions:
              - TransitionInDays: 90
                StorageClass: GLACIER
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-session-logs'
        - Key: Project
          Value: !Ref ProjectName
        - Key: Environment
          Value: Development
        - Key: ManagedBy
          Value: CloudFormation

  # IAMロールにS3書き込み権限を追加
  EC2RoleS3Policy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub '${ProjectName}-s3-write-policy'
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - 's3:PutObject'
              - 's3:PutObjectAcl'
            Resource: !Sub '${SessionLogsBucket.Arn}/*'
          - Effect: Allow
            Action:
              - 's3:GetEncryptionConfiguration'
            Resource: !GetAtt SessionLogsBucket.Arn
      Roles:
        - !Ref EC2Role

  # Session Logs用CloudWatch Logsロググループ
  SessionLogsLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/ssm/${ProjectName}-session-logs'
      KmsKeyId: !GetAtt RDPRecordingKMSKey.Arn
      RetentionInDays: 30
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-session-logs'
        - Key: Project
          Value: !Ref ProjectName
        - Key: Environment
          Value: Development
        - Key: ManagedBy
          Value: CloudFormation

  # IAMロールにCloudWatch Logs書き込み権限を追加
  EC2RoleCloudWatchLogsPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub '${ProjectName}-cloudwatch-logs-policy'
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'
              - 'logs:DescribeLogStreams'
            Resource: !GetAtt SessionLogsLogGroup.Arn
          - Effect: Allow
            Action:
              - 'logs:DescribeLogGroups'
            Resource: '*'
      Roles:
        - !Ref EC2Role

  # オプトインSession Manager設定ドキュメント(テスト用)
  SessionManagerOptInDocument:
    Type: AWS::SSM::Document
    Properties:
      DocumentType: Session
      Content:
        schemaVersion: '1.0'
        description: 'Opt-in Session Manager preferences for logging to CloudWatch'
        sessionType: Standard_Stream
        inputs:
          s3BucketName: ''
          s3KeyPrefix: ''
          s3EncryptionEnabled: false
          cloudWatchLogGroupName: !Ref SessionLogsLogGroup
          cloudWatchEncryptionEnabled: false
          cloudWatchStreamingEnabled: true
          runAsEnabled: false
          runAsDefaultUser: ''
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-session-manager-opt-in'
        - Key: Project
          Value: !Ref ProjectName
        - Key: Environment
          Value: Development
        - Key: ManagedBy
          Value: CloudFormation

  # Windows EC2インスタンス
  WindowsEC2Instance:
    Type: AWS::EC2::Instance
    DependsOn:
      - SSMVPCEndpoint
      - SSMMessagesVPCEndpoint
      - EC2MessagesVPCEndpoint
      - S3VPCEndpoint
      - LogsVPCEndpoint
      - KMSVPCEndpoint
    Properties:
      ImageId: !Sub '{{resolve:ssm:/aws/service/ami-windows-latest/Windows_Server-2022-Japanese-Full-Base}}'
      InstanceType: t3.medium
      KeyName: !Ref KeyPairName
      IamInstanceProfile: !Ref EC2InstanceProfile
      SubnetId: !Ref PrivateSubnet
      SecurityGroupIds:
        - !Ref EC2SecurityGroup
      PropagateTagsToVolumeOnCreation: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-windows-ec2'
        - Key: Project
          Value: !Ref ProjectName
        - Key: Environment
          Value: Development
        - Key: ManagedBy
          Value: CloudFormation
        - Key: Cost
          Value: h.kaizuka

  # RDP接続とセッションログ記録用KMSカスタマーマネージドキー
  RDPRecordingKMSKey:
    Type: AWS::KMS::Key
    Properties:
      Description: 'KMS key for RDP recording and session logs encryption'
      KeyPolicy:
        Version: '2012-10-17'
        Statement:
          - Sid: 'Enable IAM User Permissions'
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
            Action: 'kms:*'
            Resource: '*'
          - Sid: 'Allow GUI Connect service to use the key'
            Effect: Allow
            Principal:
              Service: 'ssm-guiconnect.amazonaws.com'
            Action:
              - 'kms:GenerateDataKey'
              - 'kms:Decrypt'
            Resource: '*'
            Condition:
              StringEquals:
                'aws:SourceAccount': !Ref AWS::AccountId
          - Sid: 'Allow EC2 role to use the key'
            Effect: Allow
            Principal:
              AWS: !GetAtt EC2Role.Arn
            Action:
              - 'kms:CreateGrant'
              - 'kms:Decrypt'
              - 'kms:DescribeKey'
            Resource: '*'
          - Sid: 'Allow CloudWatch Logs to use the key'
            Effect: Allow
            Principal:
              Service: !Sub 'logs.${AWS::Region}.amazonaws.com'
            Action:
              - 'kms:Encrypt'
              - 'kms:Decrypt'
              - 'kms:ReEncrypt*'
              - 'kms:GenerateDataKey*'
              - 'kms:CreateGrant'
              - 'kms:DescribeKey'
            Resource: '*'
            Condition:
              ArnLike:
                'kms:EncryptionContext:aws:logs:arn': !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/ssm/${ProjectName}-session-logs'
      Tags:
        - Key: SystemsManagerJustInTimeNodeAccessManaged
          Value: 'true'
        - Key: Name
          Value: !Sub '${ProjectName}-rdp-recording-key'
        - Key: Project
          Value: !Ref ProjectName
        - Key: Environment
          Value: Development
        - Key: ManagedBy
          Value: CloudFormation

  # RDP接続記録用KMSキーエイリアス
  RDPRecordingKMSKeyAlias:
    Type: AWS::KMS::Alias
    Properties:
      AliasName: !Sub 'alias/${ProjectName}-rdp-recording-key'
      TargetKeyId: !Ref RDPRecordingKMSKey

  # S3バケットポリシー (RDP接続記録用)
  SessionLogsBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref SessionLogsBucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: 'ConnectionRecording'
            Effect: Allow
            Principal:
              Service: 'ssm-guiconnect.amazonaws.com'
            Action: 's3:PutObject'
            Resource:
              - !GetAtt SessionLogsBucket.Arn
              - !Sub '${SessionLogsBucket.Arn}/*'
            Condition:
              StringEquals:
                'aws:SourceAccount': !Ref AWS::AccountId

  # IAMロールにRDP接続記録の権限を追加
  EC2RoleRDPRecordingPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub '${ProjectName}-rdp-recording-policy'
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - 'ssm-guiconnect:UpdateConnectionRecordingPreferences'
              - 'ssm-guiconnect:GetConnectionRecordingPreferences'
              - 'ssm-guiconnect:DeleteConnectionRecordingPreferences'
              - 'ssm-guiconnect:CancelConnection'
              - 'ssm-guiconnect:GetConnection'
              - 'ssm-guiconnect:StartConnection'
            Resource: '*'
          - Effect: Allow
            Action:
              - 'kms:CreateGrant'
              - 'kms:Decrypt'
              - 'kms:DescribeKey'
            Resource: !GetAtt RDPRecordingKMSKey.Arn
      Roles:
        - !Ref EC2Role

Outputs:
  VPCId:
    Description: 'VPC ID'
    Value: !Ref VPC
    Export:
      Name: !Sub '${AWS::StackName}-VPCId'

  EC2InstanceId:
    Description: 'EC2 Instance ID'
    Value: !Ref WindowsEC2Instance
    Export:
      Name: !Sub '${AWS::StackName}-EC2InstanceId'

  SessionLogsBucketName:
    Description: 'Session Logs S3 Bucket Name'
    Value: !Ref SessionLogsBucket
    Export:
      Name: !Sub '${AWS::StackName}-SessionLogsBucketName'

  SessionLogsLogGroupName:
    Description: 'Session Logs CloudWatch Log Group Name'
    Value: !Ref SessionLogsLogGroup
    Export:
      Name: !Sub '${AWS::StackName}-SessionLogsLogGroupName'

  SessionManagerOptInDocumentName:
    Description: 'Opt-in Session Manager Document Name'
    Value: !Ref SessionManagerOptInDocument
    Export:
      Name: !Sub '${AWS::StackName}-SessionManagerOptInDocumentName'

  EC2PrivateIP:
    Description: 'EC2 Instance Private IP Address'
    Value: !GetAtt WindowsEC2Instance.PrivateIp
    Export:
      Name: !Sub '${AWS::StackName}-EC2PrivateIP'

  RDPRecordingKMSKeyId:
    Description: 'RDP Recording KMS Key ID'
    Value: !Ref RDPRecordingKMSKey
    Export:
      Name: !Sub '${AWS::StackName}-RDPRecordingKMSKeyId'

  RDPRecordingKMSKeyArn:
    Description: 'RDP Recording KMS Key ARN'
    Value: !GetAtt RDPRecordingKMSKey.Arn
    Export:
      Name: !Sub '${AWS::StackName}-RDPRecordingKMSKeyArn'
enable-default-session-logging.sh
#!/bin/bash
# アカウントレベルのSession Manager設定のログ記録を有効化
# 使用方法: ./enable-default-session-logging-updated.sh [region] [profile] [stack-name]
# 例: ./enable-default-session-logging-updated.sh ap-northeast-1 mng session-manager-stack

set -e

REGION=${1:-ap-northeast-1}
PROFILE=${2:-}
STACK_NAME=${3:-}
TEMP_DIR="/tmp/session-manager-config-$$"

# プロファイルオプションの設定
PROFILE_OPT=""
if [ -n "${PROFILE}" ]; then
    PROFILE_OPT="--profile ${PROFILE}"
fi

echo "Session Manager設定のログ記録を有効化します..."
echo "リージョン: $REGION"

# スタック名が指定されていない場合はエラー
if [ -z "${STACK_NAME}" ]; then
    echo "エラー: スタック名が指定されていません"
    echo "使用方法: $0 [region] [profile] [stack-name]"
    exit 1
fi

# 一時ディレクトリを作成
mkdir -p "$TEMP_DIR"

# クリーンアップ関数
cleanup() {
    rm -rf "$TEMP_DIR"
}
trap cleanup EXIT

# CloudFormationスタックからCloudWatch Logsグループ名を取得
echo "CloudFormationスタックからCloudWatch Logsグループ名を取得中..."
LOG_GROUP=$(aws cloudformation describe-stacks \
    ${PROFILE_OPT} \
    --stack-name "${STACK_NAME}" \
    --region "$REGION" \
    --query 'Stacks[0].Outputs[?OutputKey==`SessionLogsLogGroupName`].OutputValue' \
    --output text \
    --no-cli-pager)

if [ -z "${LOG_GROUP}" ]; then
    echo "エラー: CloudFormationスタックからCloudWatch Logsグループ名を取得できませんでした"
    echo "スタック名が正しいか確認してください: ${STACK_NAME}"
    exit 1
fi

echo "CloudWatch Logsグループ名: $LOG_GROUP"

# CloudFormationスタックからKMSキーARNを取得
echo "CloudFormationスタックからKMSキーARNを取得中..."
KMS_KEY_ARN=$(aws cloudformation describe-stacks \
    ${PROFILE_OPT} \
    --stack-name "${STACK_NAME}" \
    --region "$REGION" \
    --query 'Stacks[0].Outputs[?OutputKey==`RDPRecordingKMSKeyArn`].OutputValue' \
    --output text \
    --no-cli-pager)

echo "KMSキーARN: $KMS_KEY_ARN"

# 現在の設定を取得
echo "現在の設定を取得中..."
if ! aws ssm get-document \
    ${PROFILE_OPT} \
    --name "SSM-SessionManagerRunShell" \
    --region "$REGION" \
    --query 'Content' \
    --output text > "$TEMP_DIR/current-session-config.json" 2>/dev/null; then
    echo "SSM-SessionManagerRunShellドキュメントが存在しません。新規作成します..."
    
    # デフォルト設定(ログ記録有効)を作成
    cat > "$TEMP_DIR/new-session-config.json" < /dev/null
    
    echo "[+] SSM-SessionManagerRunShellドキュメントを作成しました"
    echo "[+] Session Manager logging enabled successfully"
    echo "[+] CloudWatchロググループ名: $LOG_GROUP"
    echo "[+] KMSキーARN: $KMS_KEY_ARN"
    exit 0
fi

# 現在の設定を表示
echo "現在の設定:"
jq '.inputs | {s3BucketName, cloudWatchLogGroupName, kmsKeyId}' "$TEMP_DIR/current-session-config.json"

# ログ記録を有効化した設定を作成
echo "ログ記録を有効化した設定を作成中..."
jq ".inputs.cloudWatchLogGroupName = \"${LOG_GROUP}\" | .inputs.kmsKeyId = \"${KMS_KEY_ARN}\"" \
    "$TEMP_DIR/current-session-config.json" > "$TEMP_DIR/updated-session-config.json"

# 更新後の設定を表示
echo "更新後の設定:"
jq '.inputs | {s3BucketName, cloudWatchLogGroupName, kmsKeyId}' "$TEMP_DIR/updated-session-config.json"

# 設定を更新
echo "設定を更新中..."
UPDATE_RESULT=$(aws ssm update-document \
    ${PROFILE_OPT} \
    --name "SSM-SessionManagerRunShell" \
    --content "file://$TEMP_DIR/updated-session-config.json" \
    --document-version "\$LATEST" \
    --region "$REGION" \
    --no-cli-pager 2>&1) || true

# DuplicateDocumentContentエラーの場合は、既に同じ内容のバージョンが存在する
if echo "$UPDATE_RESULT" | grep -q "DuplicateDocumentContent"; then
    echo "[i] 同じ内容のドキュメントバージョンが既に存在します。"
    echo "最新バージョンをデフォルトバージョンに設定します..."
    
    # 最新バージョン番号を取得
    LATEST_VERSION=$(aws ssm describe-document \
        ${PROFILE_OPT} \
        --name "SSM-SessionManagerRunShell" \
        --region "$REGION" \
        --query 'Document.LatestVersion' \
        --output text)
    
    # デフォルトバージョンを最新バージョンに更新
    if aws ssm update-document-default-version \
        ${PROFILE_OPT} \
        --name "SSM-SessionManagerRunShell" \
        --document-version "$LATEST_VERSION" \
        --region "$REGION" \
        --no-cli-pager > /dev/null; then
        echo "[+] デフォルトバージョンをバージョン$LATEST_VERSIONに更新しました"
    else
        echo "エラー: デフォルトバージョンの更新に失敗しました。"
        exit 1
    fi
elif echo "$UPDATE_RESULT" | grep -q "error"; then
    echo "エラー: 設定の更新に失敗しました。"
    echo "$UPDATE_RESULT"
    exit 1
else
    echo "[+] 新しいドキュメントバージョンを作成しました"
    
    # 新しいバージョンをデフォルトに設定
    NEW_VERSION=$(echo "$UPDATE_RESULT" | jq -r '.DocumentDescription.LatestVersion')
    if aws ssm update-document-default-version \
        ${PROFILE_OPT} \
        --name "SSM-SessionManagerRunShell" \
        --document-version "$NEW_VERSION" \
        --region "$REGION" \
        --no-cli-pager > /dev/null; then
        echo "[+] デフォルトバージョンをバージョン$NEW_VERSIONに更新しました"
    else
        echo "エラー: デフォルトバージョンの更新に失敗しました。"
        exit 1
    fi
fi

echo "[+] Session Manager logging enabled successfully"
echo "[+] CloudWatchロググループ名: $LOG_GROUP"
echo "[+] KMSキーARN: $KMS_KEY_ARN"
disable-default-session-logging.sh
#!/bin/bash
# アカウントレベルのSession Manager設定のログ記録を無効化
# 使用方法: ./disable-default-session-logging.sh [region] [profile]
# 例: ./disable-default-session-logging.sh ap-northeast-1 default

set -e

REGION=${1:-ap-northeast-1}
PROFILE=${2:-}
TEMP_DIR="/tmp/session-manager-config-$$"

# プロファイルオプションの設定
PROFILE_OPT=""
if [ -n "${PROFILE}" ]; then
    PROFILE_OPT="--profile ${PROFILE}"
fi

echo "Session Manager設定のログ記録を無効化します..."
echo "リージョン: $REGION"

# 一時ディレクトリを作成
mkdir -p "$TEMP_DIR"

# クリーンアップ関数
cleanup() {
    rm -rf "$TEMP_DIR"
}
trap cleanup EXIT

# 現在の設定を取得
echo "現在の設定を取得中..."
if ! aws ssm get-document \
    ${PROFILE_OPT} \
    --name "SSM-SessionManagerRunShell" \
    --region "$REGION" \
    --query 'Content' \
    --output text > "$TEMP_DIR/current-session-config.json" 2>/dev/null; then
    echo "SSM-SessionManagerRunShellドキュメントが存在しません。新規作成します..."
    
    # デフォルト設定(ログ記録無効)を作成
    cat > "$TEMP_DIR/new-session-config.json" <<'EOF' { "schemaVersion": "1.0", "description": "Document to hold regional settings for Session Manager", "sessionType": "Standard_Stream", "inputs": { "s3BucketName": "", "s3KeyPrefix": "", "s3EncryptionEnabled": true, "cloudWatchLogGroupName": "", "cloudWatchEncryptionEnabled": true, "cloudWatchStreamingEnabled": true, "kmsKeyId": "", "runAsEnabled": false, "runAsDefaultUser": "" } } EOF # ドキュメントを作成 echo "ドキュメントを作成中..." aws ssm create-document \ ${PROFILE_OPT} \ --name "SSM-SessionManagerRunShell" \ --document-type "Session" \ --content "file://$TEMP_DIR/new-session-config.json" \ --region "$REGION" \ --no-cli-pager > /dev/null
    
    echo "[+] SSM-SessionManagerRunShellドキュメントを作成しました"
    echo "[+] Session Manager default logging disabled successfully"
    echo "[+] S3バケット名: (空)"
    echo "[+] CloudWatchロググループ名: (空)"
    exit 0
fi

# 現在の設定を表示
echo "現在の設定:"
jq '.inputs | {s3BucketName, cloudWatchLogGroupName}' "$TEMP_DIR/current-session-config.json"

# ログ記録を無効化した設定を作成
echo "ログ記録を無効化した設定を作成中..."
jq '.inputs.s3BucketName = "" | .inputs.cloudWatchLogGroupName = ""' \
    "$TEMP_DIR/current-session-config.json" > "$TEMP_DIR/updated-session-config.json"

# 更新後の設定を表示
echo "更新後の設定:"
jq '.inputs | {s3BucketName, cloudWatchLogGroupName}' "$TEMP_DIR/updated-session-config.json"

# 設定を更新
echo "設定を更新中..."
UPDATE_RESULT=$(aws ssm update-document \
    ${PROFILE_OPT} \
    --name "SSM-SessionManagerRunShell" \
    --content "file://$TEMP_DIR/updated-session-config.json" \
    --document-version "\$LATEST" \
    --region "$REGION" \
    --no-cli-pager 2>&1) || true

# DuplicateDocumentContentエラーの場合は、既に同じ内容のバージョンが存在する
if echo "$UPDATE_RESULT" | grep -q "DuplicateDocumentContent"; then
    echo "[i] 同じ内容のドキュメントバージョンが既に存在します。"
    echo "最新バージョンをデフォルトバージョンに設定します..."
    
    # 最新バージョン番号を取得
    LATEST_VERSION=$(aws ssm describe-document \
        ${PROFILE_OPT} \
        --name "SSM-SessionManagerRunShell" \
        --region "$REGION" \
        --query 'Document.LatestVersion' \
        --output text)
    
    # デフォルトバージョンを最新バージョンに更新
    if aws ssm update-document-default-version \
        ${PROFILE_OPT} \
        --name "SSM-SessionManagerRunShell" \
        --document-version "$LATEST_VERSION" \
        --region "$REGION" \
        --no-cli-pager > /dev/null; then
        echo "[+] デフォルトバージョンをバージョン$LATEST_VERSIONに更新しました"
    else
        echo "エラー: デフォルトバージョンの更新に失敗しました。"
        exit 1
    fi
elif echo "$UPDATE_RESULT" | grep -q "error"; then
    echo "エラー: 設定の更新に失敗しました。"
    echo "$UPDATE_RESULT"
    exit 1
else
    echo "[+] 新しいドキュメントバージョンを作成しました"
    
    # 新しいバージョンをデフォルトに設定
    NEW_VERSION=$(echo "$UPDATE_RESULT" | jq -r '.DocumentDescription.LatestVersion')
    if aws ssm update-document-default-version \
        ${PROFILE_OPT} \
        --name "SSM-SessionManagerRunShell" \
        --document-version "$NEW_VERSION" \
        --region "$REGION" \
        --no-cli-pager > /dev/null; then
        echo "[+] デフォルトバージョンをバージョン$NEW_VERSIONに更新しました"
    else
        echo "エラー: デフォルトバージョンの更新に失敗しました。"
        exit 1
    fi
fi

echo "[+] Session Manager default logging disabled successfully"
echo "[+] S3バケット名: (空)"
echo "[+] CloudWatchロググループ名: (空)"

 

著者について

SCSKにて、AWSの技術支援を提供するAWSテクニカルエスコートサービスの業務に従事しています。
自称ネットワークエンジニア。
CloudFormationは触っていても面白みが感じられないけれどCDKは好き
…だったのですが、最近、生成AIで作りやすいのでCloudFormationをよく使うようになりました。生成AI任せなのでスキルは向上していません。

2024-2025 Japan AWS Top Engineer (Networking)
2024-2025 Japan All AWS Certifications Engineer

その他所持資格:
IPA 情報処理安全確保支援士

好きなすみっコはとかげ。

貝塚広行をフォローする

クラウドに強いによるエンジニアブログです。

SCSKクラウドサービス(AWS)は、企業価値の向上につながるAWS 導入を全面支援するオールインワンサービスです。AWS最上位パートナーとして、多種多様な業界のシステム構築実績を持つSCSKが、お客様のDX推進を強力にサポートします。

AWSクラウドソリューション運用・監視
シェアする
タイトルとURLをコピーしました