diff --git a/API.md b/API.md
index 8735b32..61f55d7 100644
--- a/API.md
+++ b/API.md
@@ -2833,7 +2833,7 @@ The principal to grant access to.
| isConstruct
| Checks if `x` is a construct. |
| isOwnedResource
| Returns true if the construct was created by CDK, and false otherwise. |
| isResource
| Check whether the given construct is a Resource. |
-| fromUserId
| Imports an existing User from attributes. |
+| fromUserAttributes
| Imports an existing User from attributes. |
---
@@ -2901,31 +2901,31 @@ Check whether the given construct is a Resource.
---
-##### `fromUserId`
+##### `fromUserAttributes`
```typescript
import { aws_elasticache } from '@open-constructs/aws-cdk'
-aws_elasticache.User.fromUserId(scope: Construct, id: string, userId: string)
+aws_elasticache.User.fromUserAttributes(scope: Construct, id: string, attrs: UserAttributes)
```
Imports an existing User from attributes.
-###### `scope`Required
+###### `scope`Required
- *Type:* constructs.Construct
---
-###### `id`Required
+###### `id`Required
- *Type:* string
---
-###### `userId`Required
+###### `attrs`Required
-- *Type:* string
+- *Type:* @open-constructs/aws-cdk.aws_elasticache.UserAttributes
---
@@ -2938,6 +2938,7 @@ Imports an existing User from attributes.
| stack
| aws-cdk-lib.Stack
| The stack in which this resource is defined. |
| userArn
| string
| The ARN of the user. |
| userId
| string
| The ID of the user. |
+| userName
| string
| The name of the user. |
---
@@ -3008,6 +3009,18 @@ The ID of the user.
---
+##### `userName`Required
+
+```typescript
+public readonly userName: string;
+```
+
+- *Type:* string
+
+The name of the user.
+
+---
+
### UserBase
@@ -3143,7 +3156,7 @@ The principal to grant access to.
| isConstruct
| Checks if `x` is a construct. |
| isOwnedResource
| Returns true if the construct was created by CDK, and false otherwise. |
| isResource
| Check whether the given construct is a Resource. |
-| fromUserId
| Imports an existing User from attributes. |
+| fromUserAttributes
| Imports an existing User from attributes. |
---
@@ -3211,31 +3224,31 @@ Check whether the given construct is a Resource.
---
-##### `fromUserId`
+##### `fromUserAttributes`
```typescript
import { aws_elasticache } from '@open-constructs/aws-cdk'
-aws_elasticache.UserBase.fromUserId(scope: Construct, id: string, userId: string)
+aws_elasticache.UserBase.fromUserAttributes(scope: Construct, id: string, attrs: UserAttributes)
```
Imports an existing User from attributes.
-###### `scope`Required
+###### `scope`Required
- *Type:* constructs.Construct
---
-###### `id`Required
+###### `id`Required
- *Type:* string
---
-###### `userId`Required
+###### `attrs`Required
-- *Type:* string
+- *Type:* @open-constructs/aws-cdk.aws_elasticache.UserAttributes
---
@@ -3248,6 +3261,7 @@ Imports an existing User from attributes.
| stack
| aws-cdk-lib.Stack
| The stack in which this resource is defined. |
| userArn
| string
| The ARN of the user. |
| userId
| string
| The ID of the user. |
+| userName
| string
| The name of the user. |
---
@@ -3318,6 +3332,18 @@ The ID of the user.
---
+##### `userName`Required
+
+```typescript
+public readonly userName: string;
+```
+
+- *Type:* string
+
+The name of the user.
+
+---
+
### UserGroup
@@ -5447,6 +5473,7 @@ const userAttributes: aws_elasticache.UserAttributes = { ... }
| **Name** | **Type** | **Description** |
| --- | --- | --- |
| userId
| string
| The ID of the user. |
+| userName
| string
| The name of the user. |
---
@@ -5462,6 +5489,18 @@ The ID of the user.
---
+##### `userName`Required
+
+```typescript
+public readonly userName: string;
+```
+
+- *Type:* string
+
+The name of the user.
+
+---
+
### UserGroupAttributes
Attributes for importing an User Group.
@@ -5510,41 +5549,57 @@ const userGroupProps: aws_elasticache.UserGroupProps = { ... }
| **Name** | **Type** | **Description** |
| --- | --- | --- |
-| userGroupId
| string
| The ID of the user group. |
+| defaultUser
| @open-constructs/aws-cdk.aws_elasticache.IUser
| The default user of the user group. The `userName` of the default user must be `default`. |
| users
| @open-constructs/aws-cdk.aws_elasticache.IUser[]
| The list of User that belong to the user group. |
+| userGroupId
| string
| The ID of the user group. |
---
-##### `userGroupId`Optional
+##### `defaultUser`Required
```typescript
-public readonly userGroupId: string;
+public readonly defaultUser: IUser;
```
-- *Type:* string
-- *Default:* auto generate
+- *Type:* @open-constructs/aws-cdk.aws_elasticache.IUser
-The ID of the user group.
+The default user of the user group. The `userName` of the default user must be `default`.
-\`userGroupId\` can have up to 40 characters.
+`defaultUser` must be included in `users`.
-\`userGroupId\` must consist only of alphanumeric characters or hyphens,
-with the first character as a letter, and it can't end with a hyphen or contain two consecutive hyphens.
+> [https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/Clusters.RBAC.html#Users-management](https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/Clusters.RBAC.html#Users-management)
---
-##### `users`Optional
+##### `users`Required
```typescript
public readonly users: IUser[];
```
- *Type:* @open-constructs/aws-cdk.aws_elasticache.IUser[]
-- *Default:* only `default` user added
The list of User that belong to the user group.
-`default` user is automatically added to the user group.
+`defaultUser` must be included in `users`.
+
+---
+
+##### `userGroupId`Optional
+
+```typescript
+public readonly userGroupId: string;
+```
+
+- *Type:* string
+- *Default:* auto generate
+
+The ID of the user group.
+
+\`userGroupId\` can have up to 40 characters.
+
+\`userGroupId\` must consist only of alphanumeric characters or hyphens,
+with the first character as a letter, and it can't end with a hyphen or contain two consecutive hyphens.
---
@@ -7683,6 +7738,7 @@ Grant the given identity connection access to the cache.
| stack
| aws-cdk-lib.Stack
| The stack in which this resource is defined. |
| userArn
| string
| The ARN of the user. |
| userId
| string
| The ID of the user. |
+| userName
| string
| The name of the user. |
---
@@ -7753,6 +7809,18 @@ The ID of the user.
---
+##### `userName`Required
+
+```typescript
+public readonly userName: string;
+```
+
+- *Type:* string
+
+The name of the user.
+
+---
+
### IUserGroup
- *Extends:* aws-cdk-lib.IResource
diff --git a/src/aws-elasticache/README.md b/src/aws-elasticache/README.md
index 3babdd5..d3cf42c 100644
--- a/src/aws-elasticache/README.md
+++ b/src/aws-elasticache/README.md
@@ -12,12 +12,14 @@ This module has constructs for [Amazon ElastiCache](https://docs.aws.amazon.com/
Setup required properties and create:
```ts
-const user = User(this, 'User', {
- authenticationType: AuthenticationType.IAM,
+const newDefaultUser = User(this, 'DefaultUser', {
+ authenticationType: AuthenticationType.NO_PASSWORD,
+ userName: 'default',
});
const userGroup = new UserGroup(this, 'UserGroup', {
- users: [user],
+ defaultUser: newDefaultUser,
+ users: [defaultUser, user],
});
```
@@ -41,7 +43,7 @@ First, you need to create users by using `User` construct.
With RBAC, you create users and assign them specific permissions by using `accessString` property.
-For more information, see [**Specifying Permissions Using an Access String](https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/Clusters.RBAC.html#Access-string).
+For more information, see [Specifying Permissions Using an Access String](https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/Clusters.RBAC.html#Access-string).
Also you can choose authentication type by setting `authenticationType` property:
@@ -71,33 +73,48 @@ const user = User(this, 'User', {
});
```
+ElastiCache automatically configures a default user with user ID and user name `default` and adds it to all user groups.
+You can't modify or delete this user.
+
+This user is intended for compatibility with the default behavior of previous Redis OSS versions and has an access string that permits it to call all commands and access all keys.
+
+To add proper access control to a cache, replace this default user with a new one that isn't enabled or uses a strong password.
+To change the default user, create a new user with the user name set to `default`. You can then swap it with the original default user.
+
+For more information, see [Applying RBAC to a Cache for ElastiCache with Valkey or Redis OSS](https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/Clusters.RBAC.html#rbac-using).
+
+If you want to create new default user, `userName` must be `default` and `userId` must not be `default`:
+
+```ts
+const newDefaultUser = User(this, 'NewDefaultUser', {
+ authenticationType: AuthenticationType.NO_PASSWORD,
+ // default user name must be 'default'
+ userName: 'default',
+ // new default user id must not be 'default'
+ userId: 'new-default'
+});
+```
+
### Add users to the user group
Next, create a user group by using `UserGroup` Construct and add users to the group:
```ts
+declare const newDefaultUser: User;
declare const user: User;
declare const anotherUser: User;
const userGroup = new UserGroup(this, 'UserGroup', {
- // add a user
- users: [user],
+ // assign a default user
+ defaultUser: newDefaultUser,
+ // add users including default user
+ users: [defaultUser, user],
});
// you can also add a user by using addUser method
userGroup.addUser(anotherUser);
```
-ElastiCache automatically configures a default user with user ID and user name `default` and adds it to all user groups.
-You can't modify or delete this user.
-
-This user is intended for compatibility with the default behavior of previous Redis OSS versions and has an access string that permits it to call all commands and access all keys.
-
-To add proper access control to a cache, replace this default user with a new one that isn't enabled or uses a strong password.
-To change the default user, create a new user with the user name set to `default`. You can then swap it with the original default user.
-
-For more information, see [**Creating Users and User Groups with the Console and CLI**](https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/Clusters.RBAC.html#Users-management).
-
### Assign user group
Finally, assign a user group to cache:
@@ -135,10 +152,10 @@ serverlessCache.grantConncet(role);
### Import an existing user and user group
-To import an existing user and user group, use the `User.fromUserId` and `UserGroup.fromUserGroupId` method:
+To import an existing user and user group, use the `User.fromUserAttributes` and `UserGroup.fromUserGroupId` method:
```ts
-const importedUser = User.fromUserId(this, 'ImportedUser', 'my-user-id');
+const importedUser = User.fromUserAttributes(this, 'ImportedUser', { userId: 'my-user-id', userName: 'my-user-name' });
const importedUserGroup = UserGroup.fromUserGroupId(this, 'ImportedUser', 'my-user-group-id');
```
diff --git a/src/aws-elasticache/user-group.ts b/src/aws-elasticache/user-group.ts
index 3e7f000..14e3dd8 100644
--- a/src/aws-elasticache/user-group.ts
+++ b/src/aws-elasticache/user-group.ts
@@ -38,13 +38,21 @@ export interface UserGroupProps {
readonly userGroupId?: string;
/**
- * The list of User that belong to the user group.
+ * The default user of the user group.
+ * The `userName` of the default user must be `default`.
*
- * `default` user is automatically added to the user group.
+ * `defaultUser` must be included in `users`.
*
- * @default - only `default` user added
+ * @see https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/Clusters.RBAC.html#Users-management
*/
- readonly users?: IUser[];
+ readonly defaultUser: IUser;
+
+ /**
+ * The list of User that belong to the user group.
+ *
+ * `defaultUser` must be included in `users`.
+ */
+ readonly users: IUser[];
}
/**
@@ -110,7 +118,6 @@ export class UserGroup extends Resource implements IUserGroup {
Names.uniqueResourceName(this, {
maxLength: 40,
separator: '-',
- allowedSpecialCharacters: '-',
}).toLowerCase(),
}),
});
@@ -118,11 +125,12 @@ export class UserGroup extends Resource implements IUserGroup {
this.users = this.props.users ?? [];
this.validateUserGroupId();
+ this.validateDefaultUser();
const userGroup = this.createResource(this, 'Resource', {
engine: Engine.REDIS,
userGroupId: this.physicalName,
- userIds: Lazy.list({ produce: () => ['default', ...this.users.map(user => user.userId)] }),
+ userIds: Lazy.list({ produce: () => this.users.map(user => user.userId) }),
});
this.userGroupArn = userGroup.attrArn;
@@ -157,6 +165,25 @@ export class UserGroup extends Resource implements IUserGroup {
}
}
+ /**
+ * Validates default user.
+ */
+ private validateDefaultUser(): void {
+ const defaultUserName = this.props.defaultUser.userName;
+
+ if (Token.isUnresolved(defaultUserName)) {
+ return;
+ }
+
+ if (defaultUserName !== 'default') {
+ throw new Error(`\`defaultUser\` must have \`userName\` as \`default\`, got: ${defaultUserName}.`);
+ }
+
+ if (!this.props.users.includes(this.props.defaultUser)) {
+ throw new Error('`defaultUser` must be included in `users`.');
+ }
+ }
+
/**
* Adds an user to the user group
*
diff --git a/src/aws-elasticache/user.ts b/src/aws-elasticache/user.ts
index e9068d6..f0dba80 100644
--- a/src/aws-elasticache/user.ts
+++ b/src/aws-elasticache/user.ts
@@ -29,6 +29,12 @@ export interface IUser extends IResource {
* @attribute
*/
readonly userId: string;
+ /**
+ * The name of the user.
+ *
+ * @attribute
+ */
+ readonly userName: string;
/**
* Grant the given identity the specified actions
@@ -123,6 +129,10 @@ export interface UserAttributes {
* The ID of the user.
*/
readonly userId: string;
+ /**
+ * The name of the user.
+ */
+ readonly userName: string;
}
/**
@@ -132,13 +142,14 @@ export abstract class UserBase extends Resource implements IUser {
/**
* Imports an existing User from attributes
*/
- public static fromUserId(scope: Construct, id: string, userId: string): IUser {
+ public static fromUserAttributes(scope: Construct, id: string, attrs: UserAttributes): IUser {
class Import extends UserBase implements IUser {
- public readonly userId = userId;
+ public readonly userId = attrs.userId;
+ public readonly userName = attrs.userName;
public readonly userArn = Stack.of(this).formatArn({
service: 'elasticache',
resource: 'user',
- resourceName: userId,
+ resourceName: attrs.userId,
});
}
return new Import(scope, id);
@@ -154,6 +165,11 @@ export abstract class UserBase extends Resource implements IUser {
*/
public abstract readonly userId: string;
+ /**
+ * The name of the user.
+ */
+ public abstract readonly userName: string;
+
/**
* Grant the given identity the specified actions
* @param grantee the identity to be granted the actions
@@ -213,6 +229,11 @@ export class User extends UserBase implements IUser {
*/
readonly userId: string;
+ /**
+ * The name of the user.
+ */
+ readonly userName: string;
+
private readonly props: UserProps;
constructor(scope: Construct, id: string, props: UserProps) {
@@ -239,6 +260,7 @@ export class User extends UserBase implements IUser {
this.userArn = user.attrArn;
this.userId = user.ref;
+ this.userName = user.userName;
}
protected createResource(scope: Construct, id: string, props: aws_elasticache.CfnUserProps): aws_elasticache.CfnUser {
@@ -275,6 +297,12 @@ export class User extends UserBase implements IUser {
throw new Error(`\`userId\` must be between 1 and 40 characters, got ${userId.length} characters.`);
}
+ if (userId === 'default') {
+ throw new Error(
+ '`userId` cannot be `default` because ElastiCache automatically configures a default user with user ID `default`.',
+ );
+ }
+
if (!/^[A-Za-z][A-Za-z0-9]*(-[A-Za-z0-9]+)*$/.test(userId)) {
throw new Error(
`\`userId\` must consist only of alphanumeric characters or hyphens, with the first character as a letter, and it can't end with a hyphen or contain two consecutive hyphens, got: ${userId}.`,
diff --git a/test/aws-elasticache/integ.elasticache-serverless-cache.ts b/test/aws-elasticache/integ.elasticache-serverless-cache.ts
index e10f99b..47cf03a 100644
--- a/test/aws-elasticache/integ.elasticache-serverless-cache.ts
+++ b/test/aws-elasticache/integ.elasticache-serverless-cache.ts
@@ -8,6 +8,12 @@ class ElastiCacheStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
+ const defaultUser = new ocf.aws_elasticache.User(this, 'DefaultUser', {
+ authenticationType: AuthenticationType.NO_PASSWORD_REQUIRED,
+ userId: 'new-default',
+ userName: 'default',
+ });
+
const iamUser = new ocf.aws_elasticache.User(this, 'IamUser', {
accessString: 'on ~* +@all',
authenticationType: AuthenticationType.IAM,
@@ -31,8 +37,9 @@ class ElastiCacheStack extends cdk.Stack {
});
const userGroup = new ocf.aws_elasticache.UserGroup(this, 'UserGroup', {
+ defaultUser,
userGroupId: 'my-user-group',
- users: [iamUser, noPasswordRequiredUser],
+ users: [defaultUser, iamUser, noPasswordRequiredUser],
});
userGroup.addUser(passwordUser);
diff --git a/test/aws-elasticache/integ.elasticache-serverless-cache.ts.snapshot/ElastiCacheServerlessCacheStack.assets.json b/test/aws-elasticache/integ.elasticache-serverless-cache.ts.snapshot/ElastiCacheServerlessCacheStack.assets.json
index 017a0af..df87117 100644
--- a/test/aws-elasticache/integ.elasticache-serverless-cache.ts.snapshot/ElastiCacheServerlessCacheStack.assets.json
+++ b/test/aws-elasticache/integ.elasticache-serverless-cache.ts.snapshot/ElastiCacheServerlessCacheStack.assets.json
@@ -14,7 +14,7 @@
}
}
},
- "53813f4340cd59f342603e3ac21f9efbb735e36be76f8e0a9c8e40b11c287abe": {
+ "091b314c74e210e41032d9dfdb223773f882c9286ae0c7dd2e445c5de8983786": {
"source": {
"path": "ElastiCacheServerlessCacheStack.template.json",
"packaging": "file"
@@ -22,7 +22,7 @@
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
- "objectKey": "53813f4340cd59f342603e3ac21f9efbb735e36be76f8e0a9c8e40b11c287abe.json",
+ "objectKey": "091b314c74e210e41032d9dfdb223773f882c9286ae0c7dd2e445c5de8983786.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
diff --git a/test/aws-elasticache/integ.elasticache-serverless-cache.ts.snapshot/ElastiCacheServerlessCacheStack.template.json b/test/aws-elasticache/integ.elasticache-serverless-cache.ts.snapshot/ElastiCacheServerlessCacheStack.template.json
index c0809c8..36ff89b 100644
--- a/test/aws-elasticache/integ.elasticache-serverless-cache.ts.snapshot/ElastiCacheServerlessCacheStack.template.json
+++ b/test/aws-elasticache/integ.elasticache-serverless-cache.ts.snapshot/ElastiCacheServerlessCacheStack.template.json
@@ -1,5 +1,17 @@
{
"Resources": {
+ "DefaultUser59E167ED": {
+ "Type": "AWS::ElastiCache::User",
+ "Properties": {
+ "AccessString": "off -@all",
+ "AuthenticationMode": {
+ "Type": "no-password-required"
+ },
+ "Engine": "redis",
+ "UserId": "new-default",
+ "UserName": "default"
+ }
+ },
"IamUser83DA77ED": {
"Type": "AWS::ElastiCache::User",
"Properties": {
@@ -8,8 +20,8 @@
"Type": "iam"
},
"Engine": "redis",
- "UserId": "elasticacheserveachestackiamuser1980b570",
- "UserName": "elasticacheserveachestackiamuser1980b570"
+ "UserId": "elasticacheservehestack-iamuser-1980b570",
+ "UserName": "elasticacheservehestack-iamuser-1980b570"
}
},
"NoPasswordRequiredUser09EED42D": {
@@ -46,7 +58,9 @@
"Engine": "redis",
"UserGroupId": "my-user-group",
"UserIds": [
- "default",
+ {
+ "Ref": "DefaultUser59E167ED"
+ },
{
"Ref": "IamUser83DA77ED"
},
diff --git a/test/aws-elasticache/integ.elasticache-serverless-cache.ts.snapshot/manifest.json b/test/aws-elasticache/integ.elasticache-serverless-cache.ts.snapshot/manifest.json
index 6613f8e..ef65339 100644
--- a/test/aws-elasticache/integ.elasticache-serverless-cache.ts.snapshot/manifest.json
+++ b/test/aws-elasticache/integ.elasticache-serverless-cache.ts.snapshot/manifest.json
@@ -18,7 +18,7 @@
"validateOnSynth": false,
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
- "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/53813f4340cd59f342603e3ac21f9efbb735e36be76f8e0a9c8e40b11c287abe.json",
+ "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/091b314c74e210e41032d9dfdb223773f882c9286ae0c7dd2e445c5de8983786.json",
"requiresBootstrapStackVersion": 6,
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
"additionalDependencies": [
@@ -34,6 +34,12 @@
"ElastiCacheServerlessCacheStack.assets"
],
"metadata": {
+ "/ElastiCacheServerlessCacheStack/DefaultUser/Resource": [
+ {
+ "type": "aws:cdk:logicalId",
+ "data": "DefaultUser59E167ED"
+ }
+ ],
"/ElastiCacheServerlessCacheStack/IamUser/Resource": [
{
"type": "aws:cdk:logicalId",
diff --git a/test/aws-elasticache/integ.elasticache-serverless-cache.ts.snapshot/tree.json b/test/aws-elasticache/integ.elasticache-serverless-cache.ts.snapshot/tree.json
index 821ce6f..a1cf588 100644
--- a/test/aws-elasticache/integ.elasticache-serverless-cache.ts.snapshot/tree.json
+++ b/test/aws-elasticache/integ.elasticache-serverless-cache.ts.snapshot/tree.json
@@ -8,6 +8,36 @@
"id": "ElastiCacheServerlessCacheStack",
"path": "ElastiCacheServerlessCacheStack",
"children": {
+ "DefaultUser": {
+ "id": "DefaultUser",
+ "path": "ElastiCacheServerlessCacheStack/DefaultUser",
+ "children": {
+ "Resource": {
+ "id": "Resource",
+ "path": "ElastiCacheServerlessCacheStack/DefaultUser/Resource",
+ "attributes": {
+ "aws:cdk:cloudformation:type": "AWS::ElastiCache::User",
+ "aws:cdk:cloudformation:props": {
+ "accessString": "off -@all",
+ "authenticationMode": {
+ "Type": "no-password-required"
+ },
+ "engine": "redis",
+ "userId": "new-default",
+ "userName": "default"
+ }
+ },
+ "constructInfo": {
+ "fqn": "aws-cdk-lib.aws_elasticache.CfnUser",
+ "version": "2.120.0"
+ }
+ }
+ },
+ "constructInfo": {
+ "fqn": "aws-cdk-lib.Resource",
+ "version": "2.120.0"
+ }
+ },
"IamUser": {
"id": "IamUser",
"path": "ElastiCacheServerlessCacheStack/IamUser",
@@ -23,8 +53,8 @@
"Type": "iam"
},
"engine": "redis",
- "userId": "elasticacheserveachestackiamuser1980b570",
- "userName": "elasticacheserveachestackiamuser1980b570"
+ "userId": "elasticacheservehestack-iamuser-1980b570",
+ "userName": "elasticacheservehestack-iamuser-1980b570"
}
},
"constructInfo": {
@@ -115,7 +145,9 @@
"engine": "redis",
"userGroupId": "my-user-group",
"userIds": [
- "default",
+ {
+ "Ref": "DefaultUser59E167ED"
+ },
{
"Ref": "IamUser83DA77ED"
},
diff --git a/test/aws-elasticache/serverless-cache.test.ts b/test/aws-elasticache/serverless-cache.test.ts
index d62f6ea..48326f5 100644
--- a/test/aws-elasticache/serverless-cache.test.ts
+++ b/test/aws-elasticache/serverless-cache.test.ts
@@ -43,8 +43,15 @@ describe('ElastiCache Serverless Cache', () => {
authenticationType: AuthenticationType.NO_PASSWORD_REQUIRED,
});
+ const defaultUser = new User(stack, 'DefaultUser', {
+ authenticationType: AuthenticationType.NO_PASSWORD_REQUIRED,
+ userId: 'new-default',
+ userName: 'default',
+ });
+
const userGroup = new UserGroup(stack, 'UserGroup', {
- users: [user],
+ defaultUser,
+ users: [defaultUser, user],
userGroupId: 'my-user-group',
});
@@ -163,7 +170,7 @@ describe('ElastiCache Serverless Cache', () => {
{ Ref: 'AWS::Region' },
':',
{ Ref: 'AWS::AccountId' },
- ':serverlesscache:teststackserverlesscache2c8c8b86',
+ ':serverlesscache:teststack-serverlesscache-2c8c8b86',
],
],
},
@@ -276,7 +283,15 @@ describe('ElastiCache Serverless Cache', () => {
describe('validateUserGroup test', () => {
test('throws when userGroup is set with not Valkey or Redis engine', () => {
expect(() => {
- const userGroup = new UserGroup(stack, 'UserGroup', {});
+ const defaultUser = new User(stack, 'DefaultUser', {
+ authenticationType: AuthenticationType.NO_PASSWORD_REQUIRED,
+ userId: 'new-default',
+ userName: 'default',
+ });
+ const userGroup = new UserGroup(stack, 'UserGroup', {
+ defaultUser,
+ users: [defaultUser],
+ });
new ServerlessCache(stack, 'ServerlessCache', {
engine: Engine.MEMCACHED,
diff --git a/test/aws-elasticache/user-group.test.ts b/test/aws-elasticache/user-group.test.ts
index 6c04f70..b1f8f5c 100644
--- a/test/aws-elasticache/user-group.test.ts
+++ b/test/aws-elasticache/user-group.test.ts
@@ -5,18 +5,27 @@ import { AuthenticationType, IUserGroup, User, UserGroup } from '../../src/aws-e
describe('ElastiCache User Group', () => {
let app: App;
let stack: Stack;
+ let defaultUser: User;
beforeEach(() => {
app = new App();
stack = new Stack(app, 'TestStack', {});
+ defaultUser = new User(stack, 'DefaultUser', {
+ authenticationType: AuthenticationType.NO_PASSWORD_REQUIRED,
+ userId: 'new-default',
+ userName: 'default',
+ });
});
test('Create an user group with minimal properties', () => {
- new UserGroup(stack, 'UserGroup', {});
+ new UserGroup(stack, 'UserGroup', {
+ defaultUser,
+ users: [defaultUser],
+ });
Template.fromStack(stack).hasResourceProperties('AWS::ElastiCache::UserGroup', {
Engine: 'redis',
- UserIds: ['default'],
+ UserIds: [stack.resolve(defaultUser.userId)],
});
});
@@ -26,14 +35,15 @@ describe('ElastiCache User Group', () => {
});
new UserGroup(stack, 'UserGroup', {
- users: [user],
+ defaultUser,
+ users: [defaultUser, user],
userGroupId: 'my-user-group',
});
Template.fromStack(stack).hasResourceProperties('AWS::ElastiCache::UserGroup', {
Engine: 'redis',
UserGroupId: 'my-user-group',
- UserIds: ['default', stack.resolve(user.userId)],
+ UserIds: [stack.resolve(defaultUser.userId), stack.resolve(user.userId)],
});
});
@@ -65,7 +75,9 @@ describe('ElastiCache User Group', () => {
test.each(['', 'a'.repeat(41)])('throws when userGroupId length is invalid, got %s', userGroupId => {
expect(() => {
new UserGroup(stack, 'UserGroup', {
+ defaultUser,
userGroupId,
+ users: [defaultUser],
});
}).toThrow(`\`userGroupId\` must be between 1 and 40 characters, got ${userGroupId.length} characters.`);
});
@@ -75,7 +87,9 @@ describe('ElastiCache User Group', () => {
userGroupId => {
expect(() => {
new UserGroup(stack, 'UserGroup', {
+ defaultUser,
userGroupId,
+ users: [defaultUser],
});
}).toThrow(
`\`userGroupId\` must consist only of alphanumeric characters or hyphens, with the first character as a letter, and it can't end with a hyphen or contain two consecutive hyphens, got: ${userGroupId}.`,
@@ -84,6 +98,35 @@ describe('ElastiCache User Group', () => {
);
});
+ describe('validateDefaultUser test', () => {
+ test('throws an error if default user name is not `default`', () => {
+ const invalidDefaultUser = new User(stack, 'InvalidDefaultUser', {
+ userName: 'not-default',
+ authenticationType: AuthenticationType.NO_PASSWORD_REQUIRED,
+ });
+
+ expect(() => {
+ new UserGroup(stack, 'UserGroup', {
+ defaultUser: invalidDefaultUser,
+ users: [invalidDefaultUser],
+ });
+ }).toThrow(`\`defaultUser\` must have \`userName\` as \`default\`, got: ${invalidDefaultUser.userName}.`);
+ });
+
+ test("throws an error if `users` don't include `defaultUser`", () => {
+ const user = new User(stack, 'User', {
+ authenticationType: AuthenticationType.NO_PASSWORD_REQUIRED,
+ });
+
+ expect(() => {
+ new UserGroup(stack, 'UserGroup', {
+ defaultUser,
+ users: [user],
+ });
+ }).toThrow('`defaultUser` must be included in `users`.');
+ });
+ });
+
describe('test addUser method', () => {
test('add user after creation', () => {
const user = new User(stack, 'User', {
@@ -91,7 +134,9 @@ describe('ElastiCache User Group', () => {
});
const userGroup = new UserGroup(stack, 'UserGroup', {
+ defaultUser,
userGroupId: 'my-user-group',
+ users: [defaultUser],
});
userGroup.addUser(user);
@@ -99,7 +144,7 @@ describe('ElastiCache User Group', () => {
Template.fromStack(stack).hasResourceProperties('AWS::ElastiCache::UserGroup', {
Engine: 'redis',
UserGroupId: 'my-user-group',
- UserIds: ['default', stack.resolve(user.userId)],
+ UserIds: [stack.resolve(defaultUser.userId), stack.resolve(user.userId)],
});
});
@@ -109,7 +154,9 @@ describe('ElastiCache User Group', () => {
});
const userGroup = new UserGroup(stack, 'UserGroup', {
+ defaultUser,
userGroupId: 'my-user-group',
+ users: [defaultUser],
});
userGroup.addUser(user);
diff --git a/test/aws-elasticache/user.test.ts b/test/aws-elasticache/user.test.ts
index 3e54755..f89c560 100644
--- a/test/aws-elasticache/user.test.ts
+++ b/test/aws-elasticache/user.test.ts
@@ -66,13 +66,17 @@ describe('ElastiCache User', () => {
beforeEach(() => {
app = new App();
stack = new Stack(app, 'TestStack');
- importedUser = User.fromUserId(stack, 'ImportedUser', 'my-user-id');
+ importedUser = User.fromUserAttributes(stack, 'ImportedUser', { userId: 'my-user-id', userName: 'my-user-name' });
});
test('should correctly set userId', () => {
expect(importedUser.userId).toEqual('my-user-id');
});
+ test('should correctly set userName', () => {
+ expect(importedUser.userName).toEqual('my-user-name');
+ });
+
test('should correctly format userArn', () => {
expect(importedUser.userArn).toEqual(
Stack.of(stack).formatArn({
@@ -150,6 +154,17 @@ describe('ElastiCache User', () => {
);
},
);
+
+ test('throws if userId is default', () => {
+ expect(() => {
+ new User(stack, 'User', {
+ userId: 'default',
+ authenticationType: AuthenticationType.NO_PASSWORD_REQUIRED,
+ });
+ }).toThrow(
+ '`userId` cannot be `default` because ElastiCache automatically configures a default user with user ID `default`.',
+ );
+ });
});
describe('validateUserName test', () => {