こんにちは、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録画について検討します。
ソースコード
本記事で使用したソースコードはこちらです。
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'
#!/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"
#!/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ロググループ名: (空)"
