こんにちは。X(クロス)イノベーション本部 ソフトウェアデザインセンター セキュリティグループの耿です。
ElastiCache for Redis クラスターの認証・認可方式のうち、ロールベースのアクセスコントロール(RBAC)を AWS CDK で実装する方法に関する日本語の記事が見当たらなかったため、書き残しておきます。
ElastiCache for Redis の認証・認可方式
ElastiCache for Redis クラスターに対する認証・認可には以下の方法があります。この記事では RBAC を対象とします。
- 認証なし
- Redis クラスターと通信さえできれば全てのコマンドを実行可能
- Redis AUTH
- RBAC(ロールベースのアクセスコントロール)
- Redis v6.0から導入された ACL を利用した認証・認可方式
- ユーザー名とパスワードによる認証
- 複数のユーザーを作成可能
- それぞれのユーザーが実施可能なコマンドとアクセス可能なキャッシュキーを制限可能
- IAM
- Redis v7.0 から利用可能な認証・認可方式
- 有効期間が短い IAM 認証トークンを利用
- 複数のユーザーを作成可能
- それぞれのユーザーが実施可能なコマンドとアクセス可能なキャッシュキーを制限可能
- IAM ポリシーにより、どの Redis ユーザーを利用できるか制御可能
リソース構成
CDK による実装を見る前に、リソース構成の概観を整理しておきます。
- 1つの Redis クラスターには複数の Redis ユーザーグループを追加可能
- 1つの Redis ユーザーグループには複数の Redis ユーザーを所属可能
- ユーザーはユーザー名とユーザーIDを持ち、複数のパスワードを設定可能
- ユーザーに設定するアクセス文字列で、Redis クラスターにおける実施可能なコマンドとアクセス可能なキャッシュキーが制限される
- Redis ユーザーのパスワードを IaC へ記載するのを回避するため、Secrets Manager を利用
- アプリケーションの権限に応じて Secrets Manager シークレットへのアクセスを許可し、アプリケーション側の Redis クライアントで Redis ユーザー名とパスワードを利用する
CDK による実装のサンプル
こちら を参考にしました。
以下の例はシンプルに、1ユーザーグループ/1ユーザーの構成にしています。
ユーザーパスワード
Secrets Managerでユーザーパスワードを生成します。default
という名前のユーザーは必須で作成する必要があり、今回はこの default
ユーザーをそのまま利用することにします。以下はデフォルトの暗号鍵を利用しています。鍵の管理が必要な場合は別途 KMS キーを作成してください。
const userName = "default"; const rbacUserSecret = new secretsmanager.Secret(this, "RedisRbacUserSecret", { secretName: "RedisRbacUserSecret", generateSecretString: { secretStringTemplate: JSON.stringify({ username: userName }), generateStringKey: "password", excludeCharacters: "@%*()_+=`~{}|[]\\:\";'?,./", }, });
ユーザーとユーザーグループ
執筆時点では CDK には ElastiCache の L2 コンストラクトが用意されていないため、L1 コンストラクトで作成します。
アクセス文字列 (accessString
) でユーザーの権限を制限できますが、今回は全コマンドと全キャッシュキーへのアクセスを許可しています。
const userId = "user-1"; const rbacUser = new elasticache.CfnUser(this, "RedisRbacUser", { userId: userId, userName: userName, engine: "redis", accessString: "on ~* +@all", passwords: [rbacUserSecret.secretValueFromJson("password").unsafeUnwrap()], }); const rbacUserGroup = new elasticache.CfnUserGroup(this, "RedisRbacUserGroup", { userGroupId: "user-group", engine: "redis", userIds: [rbacUser.userId], }); rbacUserGroup.node.addDependency(rbacUser);
CfnUser に渡すために、Secrets Manager シークレットで生成したパスワードに対して unsafeUnwrap() を呼んでいます。平文のパスワードは Redis ユーザーの作成に使われ、閲覧可能な場所には出現しないため安全な使い方になります。
VPC
Redis クラスターを配置する VPC とセキュリティグループを作成します。
const subnetName = "Isolated"; const vpc = new ec2.Vpc(this, "Vpc", { subnetConfiguration: [ { cidrMask: 24, name: subnetName, subnetType: ec2.SubnetType.PRIVATE_ISOLATED, } ] }); const redisSG = new ec2.SecurityGroup(this, 'RedisSG', { vpc, allowAllOutbound: false, });
Redis クラスター
Redis クラスターを作成し、先に作成したユーザーグループを関連付けます。
const cluster = new elasticache.CfnReplicationGroup(this, "RedisReplicationGroup", { replicationGroupDescription: "my-replication-group", engine: "redis", engineVersion: "7.0", cacheNodeType: "cache.t3.micro", cacheSubnetGroupName: new elasticache.CfnSubnetGroup(this, "SubnetGroup", { description: "my-subnet-group", subnetIds: vpc.selectSubnets({ subnetGroupName: subnetName }).subnetIds, }).ref, cacheParameterGroupName: new elasticache.CfnParameterGroup(this, "ParameterGroup", { description: "my-parameter-group", cacheParameterGroupFamily: "redis7", }).ref, numNodeGroups: 1, replicasPerNodeGroup: 1, securityGroupIds: [redisSG.securityGroupId], atRestEncryptionEnabled: true, transitEncryptionEnabled: true, userGroupIds: [rbacUserGroup.userGroupId], }); cluster.node.addDependency(rbacUserGroup);
Secrets Manager シークレットへのアクセス許可
あとは Redis クラスターへアクセスするアプリケーションが Secrets Manager シークレットにアクセスできるように、読み取り権限を付与すれば良いです。(CDKコードは省略します)
CDK による Redis の RBAC の実装例はあまり見かけませんが、簡単に実装できました。
お読みいただいてありがとうございました。
私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。
セキュリティエンジニア(セキュリティ設計)執筆:@kou.kinyo、レビュー:@nakamura.toshihiro
(Shodoで執筆されました)