From b1d73013b75f7b48373939586c29b7b608bcde3e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 24 Sep 2025 02:00:27 +0400 Subject: [PATCH] requested changes --- .../redis-credentials-rotation-fns.ts | 49 +++++++++++++------ .../redis-credentials-rotation-schemas.ts | 21 +++----- .../secret-rotation/redis-credentials.mdx | 4 -- docs/integrations/app-connections/redis.mdx | 8 +++ ...disCredentialsRotationParametersFields.tsx | 2 +- .../redis-credentials-rotation-schema.ts | 2 +- .../types/redis-credentials-rotation.ts | 2 +- .../AppConnectionForm/AppConnectionForm.tsx | 2 + 8 files changed, 54 insertions(+), 36 deletions(-) diff --git a/backend/src/ee/services/secret-rotation-v2/redis-credentials/redis-credentials-rotation-fns.ts b/backend/src/ee/services/secret-rotation-v2/redis-credentials/redis-credentials-rotation-fns.ts index 5dec96d2d..154b4359f 100644 --- a/backend/src/ee/services/secret-rotation-v2/redis-credentials/redis-credentials-rotation-fns.ts +++ b/backend/src/ee/services/secret-rotation-v2/redis-credentials/redis-credentials-rotation-fns.ts @@ -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(); diff --git a/backend/src/ee/services/secret-rotation-v2/redis-credentials/redis-credentials-rotation-schemas.ts b/backend/src/ee/services/secret-rotation-v2/redis-credentials/redis-credentials-rotation-schemas.ts index 4df00f336..c4948a26d 100644 --- a/backend/src/ee/services/secret-rotation-v2/redis-credentials/redis-credentials-rotation-schemas.ts +++ b/backend/src/ee/services/secret-rotation-v2/redis-credentials/redis-credentials-rotation-schemas.ts @@ -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() }); diff --git a/docs/documentation/platform/secret-rotation/redis-credentials.mdx b/docs/documentation/platform/secret-rotation/redis-credentials.mdx index 558980fa6..577d0e956 100644 --- a/docs/documentation/platform/secret-rotation/redis-credentials.mdx +++ b/docs/documentation/platform/secret-rotation/redis-credentials.mdx @@ -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", diff --git a/docs/integrations/app-connections/redis.mdx b/docs/integrations/app-connections/redis.mdx index 03da3041e..224abbea8 100644 --- a/docs/integrations/app-connections/redis.mdx +++ b/docs/integrations/app-connections/redis.mdx @@ -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": "", + "port": 6379, + "username": "", + "sslEnabled": true, + "sslRejectUnauthorized": false, + "sslCertificate": "" + } } } ``` diff --git a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ParametersFields/RedisCredentialsRotationParametersFields.tsx b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ParametersFields/RedisCredentialsRotationParametersFields.tsx index 2c15b3ca8..0aeffef21 100644 --- a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ParametersFields/RedisCredentialsRotationParametersFields.tsx +++ b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ParametersFields/RedisCredentialsRotationParametersFields.tsx @@ -53,7 +53,7 @@ export const RedisCredentialsRotationParametersFields = () => { isError={Boolean(error)} errorText={error?.message} > - + )} /> diff --git a/frontend/src/components/secret-rotations-v2/forms/schemas/redis-credentials-rotation-schema.ts b/frontend/src/components/secret-rotations-v2/forms/schemas/redis-credentials-rotation-schema.ts index 8e4dad117..26fccf963 100644 --- a/frontend/src/components/secret-rotations-v2/forms/schemas/redis-credentials-rotation-schema.ts +++ b/frontend/src/components/secret-rotations-v2/forms/schemas/redis-credentials-rotation-schema.ts @@ -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"), diff --git a/frontend/src/hooks/api/secretRotationsV2/types/redis-credentials-rotation.ts b/frontend/src/hooks/api/secretRotationsV2/types/redis-credentials-rotation.ts index bcbc7a3f4..58ef1e01a 100644 --- a/frontend/src/hooks/api/secretRotationsV2/types/redis-credentials-rotation.ts +++ b/frontend/src/hooks/api/secretRotationsV2/types/redis-credentials-rotation.ts @@ -10,7 +10,7 @@ export type TRedisCredentialsRotation = TSecretRotationV2Base & { type: SecretRotation.RedisCredentials; parameters: { passwordRequirements?: TPasswordRequirements; - permissionScope?: string; + permissionScope: string; }; secretsMapping: { username: string; diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx index 232087377..bb331bf3d 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx @@ -323,6 +323,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { return ; case AppConnection.Okta: return ; + case AppConnection.Redis: + return ; default: throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`); }