requested changes

This commit is contained in:
Daniel Hougaard
2025-09-24 02:00:27 +04:00
parent 869d8ca47f
commit b1d73013b7
8 changed files with 54 additions and 36 deletions

View File

@@ -15,6 +15,21 @@ import {
TRedisCredentialsRotationGeneratedCredentials,
TRedisCredentialsRotationWithConnection
} from "./redis-credentials-rotation-types";
import { verifyHostInputValidity } from "../../dynamic-secret/dynamic-secret-fns";
const redactPasswords = (e: unknown, credentials: TRedisCredentialsRotationGeneratedCredentials) => {
const error = e as Error;
if (!error?.message) return "Unknown error";
let redactedMessage = error.message;
credentials.forEach(({ password }) => {
redactedMessage = redactedMessage.replaceAll(password, "*******************");
});
return redactedMessage;
};
export const redisCredentialsRotationFactory: TRotationFactory<
TRedisCredentialsRotationWithConnection,
@@ -23,11 +38,13 @@ export const redisCredentialsRotationFactory: TRotationFactory<
const { connection, secretsMapping, parameters } = secretRotation;
const $getClient = async () => {
const [hostIp] = await verifyHostInputValidity(connection.credentials.host);
let conn: Redis | null = null;
try {
conn = new Redis({
username: connection.credentials.username,
host: connection.credentials.host,
host: hostIp,
port: connection.credentials.port,
password: connection.credentials.password,
...(connection.credentials.sslEnabled && {
@@ -63,23 +80,23 @@ export const redisCredentialsRotationFactory: TRotationFactory<
const $rotateAclUser = async () => {
let client: Redis | null = null;
const username = generatePassword({
length: 32,
required: {
symbols: 0,
digits: 5,
uppercase: 5,
lowercase: 5
}
});
const password = generatePassword(parameters.passwordRequirements || DEFAULT_PASSWORD_REQUIREMENTS);
try {
client = await $getClient();
const username = generatePassword({
length: 32,
required: {
symbols: 0,
digits: 5,
uppercase: 5,
lowercase: 5
}
});
const password = generatePassword(parameters.passwordRequirements || DEFAULT_PASSWORD_REQUIREMENTS);
// important: permissionScope is user input so we need to sanitize it, which we do by splitting the permission scope into parts and then passing them to the ACL command as separate arguments
const permissionParts = (parameters.permissionScope || "~* +@all").split(" ");
const permissionParts = parameters.permissionScope.split(" ");
await client.call("ACL", "SETUSER", username, `>${password}`, "on", ...permissionParts);
return {
@@ -88,7 +105,7 @@ export const redisCredentialsRotationFactory: TRotationFactory<
};
} catch (error: unknown) {
throw new BadRequestError({
message: "Unable to validate connection: verify credentials"
message: `Unable to rotate credentials: ${redactPasswords(error, [{ username, password }])}`
});
} finally {
if (client) await client.quit();
@@ -106,7 +123,7 @@ export const redisCredentialsRotationFactory: TRotationFactory<
await client.call("ACL", "DELUSER", username);
} catch (error: unknown) {
throw new BadRequestError({
message: "Unable to revoke credential: verify credentials"
message: `Unable to revoke credential: ${redactPasswords(error, [{ username, password: username }])}`
});
} finally {
if (client) await client.quit();

View File

@@ -28,7 +28,11 @@ const RedisCredentialsRotationSecretsMappingSchema = z.object({
export const RedisCredentialsRotationParametersSchema = z.object({
passwordRequirements: PasswordRequirementsSchema.optional(),
permissionScope: z.string().optional().describe(SecretRotations.PARAMETERS.REDIS_CREDENTIALS.permissionScope)
permissionScope: z
.string()
.trim()
.min(1, "Permission scope is required")
.describe(SecretRotations.PARAMETERS.REDIS_CREDENTIALS.permissionScope)
});
export const RedisCredentialsRotationTemplateSchema = z.object({
@@ -40,30 +44,21 @@ export const RedisCredentialsRotationTemplateSchema = z.object({
export const RedisCredentialsRotationSchema = BaseSecretRotationSchema(SecretRotation.RedisCredentials).extend({
type: z.literal(SecretRotation.RedisCredentials),
parameters: z.object({
passwordRequirements: PasswordRequirementsSchema.optional(),
permissionScope: z.string().optional()
}),
parameters: RedisCredentialsRotationParametersSchema,
secretsMapping: RedisCredentialsRotationSecretsMappingSchema
});
export const CreateRedisCredentialsRotationSchema = BaseCreateSecretRotationSchema(
SecretRotation.RedisCredentials
).extend({
parameters: z.object({
passwordRequirements: PasswordRequirementsSchema.optional(),
permissionScope: z.string().optional()
}),
parameters: RedisCredentialsRotationParametersSchema,
secretsMapping: RedisCredentialsRotationSecretsMappingSchema
});
export const UpdateRedisCredentialsRotationSchema = BaseUpdateSecretRotationSchema(
SecretRotation.RedisCredentials
).extend({
parameters: z.object({
passwordRequirements: PasswordRequirementsSchema.optional(),
permissionScope: z.string().optional()
}),
parameters: RedisCredentialsRotationParametersSchema.optional(),
secretsMapping: RedisCredentialsRotationSecretsMappingSchema.optional()
});

View File

@@ -101,10 +101,6 @@ Create a Redis Credentials Rotation in Infisical
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "my-redis-rotation",
"description": "my database credentials rotation",
"secretsMapping": {
"username": "REDIS_USERNAME",
"password": "REDIS_PASSWORD"
},
"isAutoRotationEnabled": true,
"activeIndex": 0,
"folderId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",

View File

@@ -111,6 +111,14 @@ Infisical supports the use of Username & Password authentication to connect with
"credentialsHash": "7c2d371dec195f82a6a0d5b41c970a229cfcaf88e894a5b6395e2dbd0280661f",
"app": "redis",
"method": "username-and-password",
credentials: {
"host": "<redis-host>",
"port": 6379,
"username": "<redis-username>",
"sslEnabled": true,
"sslRejectUnauthorized": false,
"sslCertificate": "<redis-ssl-certificate>"
}
}
}
```

View File

@@ -53,7 +53,7 @@ export const RedisCredentialsRotationParametersFields = () => {
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="~* +@all" />
<Input {...field} placeholder="~* +@read @write" />
</FormControl>
)}
/>

View File

@@ -10,7 +10,7 @@ export const RedisCredentialsRotationSchema = z
type: z.literal(SecretRotation.RedisCredentials),
parameters: z.object({
passwordRequirements: PasswordRequirementsSchema.optional(),
permissionScope: z.string().optional()
permissionScope: z.string().trim().min(1, "Permission scope is required")
}),
secretsMapping: z.object({
username: z.string().trim().min(1, "Username required"),

View File

@@ -10,7 +10,7 @@ export type TRedisCredentialsRotation = TSecretRotationV2Base & {
type: SecretRotation.RedisCredentials;
parameters: {
passwordRequirements?: TPasswordRequirements;
permissionScope?: string;
permissionScope: string;
};
secretsMapping: {
username: string;

View File

@@ -323,6 +323,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
return <DigitalOceanConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.Okta:
return <OktaConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.Redis:
return <RedisConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
default:
throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`);
}