IAM認証とAWS SSM を使ってパスワードレスなEC2/RDSアクセスをCDKで実現する
ローカルからEC2の踏み台サーバーを経由してRDSデータベースにアクセスするために、IAMベースの認証とAWS Systems Manager(SSM)を使ってパスワードレスにする仕組みをCDKで作ったので、やり方を残しておきます。
まず、従来のアプローチ(SSHとパスワード)の問題点について。通常、ユーザーはSSH経由でEC2インスタンスにアクセスします。これにはキーペアの管理が必要です。これには2つのデメリットがあります。
- 運用コスト:EC2管理者がユーザーの公開キーをインスタンスに追加する必要がある
- セキュリティ:秘密キーを安全に保管する必要がある
また、データベースアクセスに関しては、パスワード認証の場合チーム間で共有する際にセキュリティリスクとなります。
SSMとIAM認証
仕組み
- EC2アクセス:AWS Systems Manager (SSM) により、SSHやキーペアなしでEC2インスタンスに接続できます。ユーザーはIAMロールを通じて認証を行います。
- RDSアクセス:IAMデータベース認証により、パスワードが不要になります。ユーザーは認証情報を保存する代わりにAWS IAMロールを通じて認証を行います。
以下の図は全体のアーキテクチャを示しています。
-
ローカルPCからのユーザーアクセス
- ユーザーはAWS Systems Manager (SSM) を通じて認証し、安全なシェルアクセスを取得します。
-
EC2(バスティオン)インスタンス
- RDSデータベースにアクセスするための中間サーバーとして機能します。
-
RDSのIAM認証
- EC2インスタンスは一時的なIAMアクセストークンを取得して、RDSに安全に接続します。
-
CloudWatchによるログとモニタリング
- すべてのアクセスログとSSMセッションアクティビティはAWS CloudWatchに送信され、監査とモニタリングに使用されます。
実装
システムの大部分はCDKを使用して実装し、一部はAWSコンソールとデータベースで個別に設定します。
SSMを使用したEC2のセットアップ
Step1. EC2インスタンス用の新しいインスタンスプロファイルIAMロールを作成し、SSMとCloudWatchへのアクセスを設定
インスタンスプロファイルはEC2インスタンスがSSMとCloudWatchサービスにアクセスするために使用されます
// ... スタック内
const ec2BastionRole = new iam.Role(this, "EC2BastionRole", {
assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"), // EC2をプリンシパルとして設定
description: "Role for EC2 bastion instances",
});
// ロールにポリシーをアタッチ(必要な場合)
ec2BastionRole.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName(
"AmazonSSMManagedInstanceCore",
),
);
// アクセスログの保存用にCloudWatchLogsFullAccessをアタッチ
ec2BastionRole.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName("CloudWatchLogsFullAccess"),
);
// インスタンスプロファイルを作成し、ロールを関連付け
const instanceProfile = new iam.CfnInstanceProfile(
this,
"InstanceProfile",
{
roles: [ec2BastionRole.roleName],
instanceProfileName: "EC2BastionInstanceProfile",
},
);
Step2: EC2インスタンスにインスタンスプロファイルを設定
Step1で作ったEC2インスタンスにインスタンスプロファイルを紐つけます
...
ec2Instance.instance.iamInstanceProfile = instanceProfile.ref;
Step3: SSMセッション用のロググループを作成
監査目的でEC2サーバーへのアクセスを記録するために、CloudWatchでロググループを定義する必要があります。
// CloudWatchロググループを作成
const logGroup = new logs.LogGroup(this, "SSMSessionLogGroup", {
logGroupName: "/ssm/ec2/session",
retention: logs.RetentionDays.ONE_WEEK, // 7日間の保持期間
});
このスタックをデプロイします:cdk deploy
Step4: SSMログの設定
ロググループが作成できたので、SSMでログを有効にします。AWSコンソールから手動で設定する必要があります。
AWS Systems Manager
を開き、左側のパネルからSession Manager
を選択します。次にCloudWatch logging
にチェックを入れ、作成したロググループ(/ssm/ec2/session
)を入力します。
Step5. ローカルPCにSSMプラグインをセットアップ
SSMを使用するには、AWS Cliの追加プラグインが必要です。以下のガイドに従ってください:
- Windows https://docs.aws.amazon.com/systems-manager/latest/userguide/install-plugin-windows.html
- Mac OS https://docs.aws.amazon.com/systems-manager/latest/userguide/install-plugin-macos-overview.html
これで準備完了です!サーバーにアクセスするには、EC2コンソールページでインスタンスIDを探し、以下のコマンドを使用します。
aws ssm start-session --target {instanceId} --profile={your profile} --region=us-west-2
補足:
EC2インスタンスをプライベートサブネット内に作成することで、EC2サーバーへのアクセスをセキュアにできます
const ec2Instance = new ec2.Instance(this, "BastionInstance", {
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.MICRO,
), // インスタンスタイプ
machineImage: ec2.MachineImage.latestAmazonLinux2023(), // 最新のAmazon Linux AMI
vpc, // インスタンスをVPCにアタッチ
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, // プライベートサブネットを使用
role: ec2BastionRole, // インスタンスにロールを割り当て
});
IAM認証を使用したRDSの設定
次に、RDSのIAM認証を設定します。
Step1. EC2からPostgresにアクセスするためのネットワークポートを開放
データベースのセキュリティグループにインバウンドルールを追加します。Postgresの標準ポートである5432を開放します:
// 接続用セキュリティグループ
const dbsg = new ec2.SecurityGroup(this, "DatabaseSecurityGroup", {
vpc: vpc,
description: id + "Database",
securityGroupName: id + "Database",
});
dbsg.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(5432),
"allow Postgres access from Bastion Server",
);
Step2. IAM認証を有効化
iamAuthentication
フラグでIAM認証を有効化します:
const rdsInstance = new rds.DatabaseInstance(this, "PgDatabase", {
// その他の設定
...
iamAuthentication: true, // IAM認証を有効化
});
Step3. インスタンスプロファイルIAMロールにDBアクセスポリシーを追加
IAMを使用してDBに接続するためのポリシーを設定します:
ec2BastionRole.addToPolicy(
new iam.PolicyStatement({
actions: ["rds-db:connect"],
resources: [
`arn:aws:rds-db:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:dbuser:${rdsInstance.instanceResourceId}/db_user`,
],
}),
);
rdsInstance.grantConnect(ec2BastionRole);
ACCOUNT_ID
の後のdbuser
は固定値です。instanceResourceId
の後のdb_user
はDB内のユーザー名で、aws_user
など任意の値に設定できます。
リソースの詳細な説明については、https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.IAMPolicy.html を参照してください。
Step4. デプロイ
cdk deploy
を実行して更新を適用します
Step5. DBとテーブルアクセスの設定
マスター認証情報(管理者ロール)を使用してデータベースに接続します。次に、IAMロールに対応するデータベースユーザーを作成します。作成したものと完全に一致する必要があります。PostgreSQLの場合:
CREATE USER db_user;
GRANT rds_iam TO db_user;
次に、既存のテーブルがある場合、それらへのアクセス権を付与する必要があります。例えば、public
スキーマにテーブルがある場合:
GRANT USAGE ON SCHEMA public TO db_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO db_user;
将来作成されるテーブルにも権限を適用する場合は、デフォルトの権限も変更します:
ALTER DEFAULT PRIVILEGES
IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO db_user;
Step5. db_user
としてDBにアクセス
SSMを使用して踏み台サーバーにssm-user
としてアクセスした後、以下のコマンドを実行します:
sh-5.2$ export RDSHOST="{your db instance name}.rds.amazonaws.com"
sh-5.2$ export PGPASSWORD="$(aws rds generate-db-auth-token --hostname $RDSHOST --port 5432 --region us-west-2 --username db_user)"
sh-5.2$ psql "host=$RDSHOST port=5432 dbname=postgres user=db_user password=$PGPASSWORD"
こちらが最終的なアウトプットです。
(こちらにも同じ記事があります: https://tomoima525.hatenablog.com/entry/2025/03/19/093416)
Discussion