2024/10/8に ElastiCache for Valkeyが利用可能になりました。
特にElastiCache Severless for Valkeyだと月額約6ドル程度から使用でき、なかなか魅力的です。
ServerlessだとRBACによる権限制御が必要であり、User
を作成してキャッシュに紐づける必要があります。
RBAC is also the only way to control access to serverless caches.
このUser
の仕様がなかなかにクセがあるため、ハマったところをまとめておきます。
なおCDK L1 Constructをベースに記載しますが、CloudFormationでも同様です。
class CfnUser (construct) · AWS CDK
目次
Userの作成
RBACのためにキャッシュを使用するユーザーを作成する必要があります。
例えばパスワード認証を行うユーザーの実装例としては以下になります。
const passwordUser1 = new CfnUser(this, 'PasswordUser1', { engine: 'redis', // エンジン(redis固定) userId: 'password-user-id-1', userName: 'password-user-1', accessString: 'on ~app:*', // 権限を指定 passwords: [cdk.SecretValue.unsafePlainText('UserPassword1234').unsafeUnwrap()], // パスワードを指定 })
以降ハマった点を記載していきます。
engine は redis 固定
まず前提としてRBACはValkeyとRedis OSSでのみ使えます。
で、Valkeyを使うときはengine
にvalkey
を設定したくなりますが、ユーザーとユーザーグループはValkeyを使う場合でもredis
の設定が必要です(つまり実質redis
の固定値)
When using RBAC with Valkey clusters, you will still need to assign users and user groups the engine “redis”.
accessString はoptionalだが設定しないとdeployできない
accessString
に該当ユーザーの権限を指定します。
これはSyntaxが定められているのでドキュメントを参照してください。on ~* +@all
だと該当ユーザーをアクティブにし、すべてのキーに対してすべてのコマンドが使用できる、というような意味です。
このaccessString
ですが、ドキュメント上では Optional なものの、未設定だとdeploy時にInternal Errorになります。
上記のため実質必須項目です。
なおマネジメントコンソールでaccessString
に該当する項目を未設定のまま作成すると、off -@all
が設定されます(該当ユーザーはインアクティブ、かつすべての操作ができない)。
認証方式が複数、かつ設定方法も複数ある
ユーザーの認証方式としては以下の3つがあります。
- パスワードなし
- パスワード認証
- IAM
このうち1と2は設定方法が2通りあります。
「1. パスワードなし」のユーザー
以下のように noPasswordRequired
による設定と authenticationMode
による設定の2通りがあります。
なぜ2通りあるかは推測ですが、最初は前者だけだったものの、「3. IAM」の認証方式が後から追加になり後者が増えたのかと思います。
authenticationMode は any
なので以下のパターン2のように設定してやる必要があります。若干ややこしいですね。
基本的にはパターン1で設定するのが良いのではないかと思います。
// ------- パスワード不要のユーザー ------- const noPasswordUser1 = new CfnUser(this, 'NoPasswordUser1', { engine: 'redis', userId: 'no-password-user-id-1', userName: 'no-password-user-1', accessString: 'on ~* +@all', // パスワード不要を設定(パターン1) noPasswordRequired: true, }) const noPasswordUser2 = new CfnUser(this, 'NoPasswordUser2', { engine: 'redis', userId: 'no-password-user-id-2', userName: 'no-password-user-2', accessString: 'on ~* +@all', // パスワード不要を設定(パターン2) authenticationMode: { Type: 'no-password-required' }, })
「2. パスワード認証」のユーザー
続いてパスワード認証ありのユーザーです。パスワードは最大2コマで設定できます。
これも2通りあり、 passwords
による設定と authenticationMode
による設定の2通りがあります。
1と同様に前者のほうが扱いやすいと思います。
// ------- パスワード認証のユーザー ------- const passwordUser1 = new CfnUser(this, 'PasswordUser1', { engine: 'redis', userId: 'password-user-id-1', userName: 'password-user-1', accessString: 'on ~* +@all', // パスワード認証を設定(パターン1) passwords: [cdk.SecretValue.unsafePlainText('UserPassword1234').unsafeUnwrap()], }) const passwordUser2 = new CfnUser(this, 'PasswordUser2', { engine: 'redis', userId: 'password-user-id-2', userName: 'password-user-2', accessString: 'on ~* +@all', // パスワード認証を設定(パターン2) authenticationMode: { Type: 'password', Passwords: [cdk.SecretValue.unsafePlainText('UserPassword1234').unsafeUnwrap()], }, })
「3. IAM」のユーザー
最後にIAM認証のパターンです。これはauthenticationMode
による設定のみです。
// ------- IAM認証のユーザー ------- const iamUser = new CfnUser(this, 'IamUser', { engine: 'redis', // 認証方式がIAMの時はuserIdとuserNameが一致する必要あり userId: 'iam-user', userName: 'iam-user', accessString: 'on ~* +@all', // IAM認証を設定 authenticationMode: { Type: 'iam', }, })
なおIAMの時はuserId
とuserName
は同じ値の必要があります。こちら含め制約は以下のドキュメントにまとまっています。
デフォルトユーザー作成とUserGroup作成
作成したUser
はUserGroupに追加して、UserGroup
をキャッシュに関連づけることで、ユーザーとキャッシュの紐付けが可能です。
このUserGroup
ですが一点注意が必要で、userName
がdefault
のユーザー(デフォルトユーザー)を含める必要があります(userIdではないので注意)
以下のドキュメントに詳細の記載があります。
各UserGroup
ではデフォルトのユーザー(userName
がdefault
)が必須になります。
そのため以下のどちらかのユーザーを必ずUserGroup
に含める必要があります。
- ElastiCacheが自動作成しているデフォルトユーザー(
userId
:default
,userName
:default
) ※編集不可 - 新たに作成したデフォルトユーザー(
userId
:default
以外,userName
:default
)
1は自動作成されていますが、編集が不可です。またaccessString
がon ~* +@all
と強い権限になっています。そのためデフォルトの権限を弱くしたい場合は、別のuserId
でデフォルトユーザーを作成する形になります。
以下はデフォルトのユーザーとUserGroup
の実装例です。
// ------- デフォルトユーザー ------- const newDefaultUser = new CfnUser(this, 'NewDefaultUser', { engine: 'redis', // IDはdefault"以外”にする userId: 'new-default-user', // Nameは default にする userName: 'default', // デフォルトユーザーの権限や認証方式を設定 accessString: 'off -@all', authenticationMode: { Type: 'no-password-required', }, }) // ------- ユーザー作成後 -------Ï // ユーザーグループを作成 const userGroup = new CfnUserGroup(this, "RedisRbacUserGroup", { userGroupId: "user-group-id", engine: "redis", userIds: [ noPasswordUser1.ref, noPasswordUser2.ref, passwordUser1.ref, passwordUser2.ref, iamUser.ref, newDefaultUser.ref, // userName が default のユーザーを含める必要あり ], });
UserGroupをキャッシュに関連付け
最後にUserGroup
をキャッシュに関連付けて終わりです。以下はServerlessの例です
const cluster = new CfnServerlessCache(this, 'ServerlessCache', { engine: 'valkey', // valkeyを指定 serverlessCacheName: 'my-serverless-cache', majorEngineVersion: '7', userGroupId: userGroup.ref, // UserGroupを関連付け });
終わりに
そんなややこしい User
と UserGroup
ですが、Serverless Cache
と合わせて、open-constructsでL2 Constructを作成しています(執筆時点では未レビューのためマージ前)
マージされたらまた改めて記事書きます。