Zenn
🐈

IAM認証とAWS SSM を使ってパスワードレスなEC2/RDSアクセスをCDKで実現する

2025/03/19に公開

ローカルから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の追加プラグインが必要です。以下のガイドに従ってください:

これで準備完了です!サーバーにアクセスするには、EC2コンソールページでインスタンスIDを探し、以下のコマンドを使用します。

aws ssm start-session --target {instanceId} --profile={your profile} --region=us-west-2 

SSMのログ

補足:

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://github.com/knot-inc/ssm-access-cdk

(こちらにも同じ記事があります: https://tomoima525.hatenablog.com/entry/2025/03/19/093416)

Discussion

ログインするとコメントできます