diff --git a/backend/src/ee/routes/v2/secret-rotation-v2-routers/index.ts b/backend/src/ee/routes/v2/secret-rotation-v2-routers/index.ts index 8d1702850..d58f3c2f7 100644 --- a/backend/src/ee/routes/v2/secret-rotation-v2-routers/index.ts +++ b/backend/src/ee/routes/v2/secret-rotation-v2-routers/index.ts @@ -4,6 +4,7 @@ import { registerAuth0ClientSecretRotationRouter } from "./auth0-client-secret-r import { registerAwsIamUserSecretRotationRouter } from "./aws-iam-user-secret-rotation-router"; import { registerAzureClientSecretRotationRouter } from "./azure-client-secret-rotation-router"; import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router"; +import { registerMongoDBCredentialsRotationRouter } from "./mongodb-credentials-rotation-router"; import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router"; import { registerMySqlCredentialsRotationRouter } from "./mysql-credentials-rotation-router"; import { registerOktaClientSecretRotationRouter } from "./okta-client-secret-rotation-router"; @@ -26,5 +27,6 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record< [SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter, [SecretRotation.LdapPassword]: registerLdapPasswordRotationRouter, [SecretRotation.OktaClientSecret]: registerOktaClientSecretRotationRouter, - [SecretRotation.RedisCredentials]: registerRedisCredentialsRotationRouter + [SecretRotation.RedisCredentials]: registerRedisCredentialsRotationRouter, + [SecretRotation.MongoDBCredentials]: registerMongoDBCredentialsRotationRouter }; diff --git a/backend/src/ee/routes/v2/secret-rotation-v2-routers/mongodb-credentials-rotation-router.ts b/backend/src/ee/routes/v2/secret-rotation-v2-routers/mongodb-credentials-rotation-router.ts new file mode 100644 index 000000000..0b41f24f9 --- /dev/null +++ b/backend/src/ee/routes/v2/secret-rotation-v2-routers/mongodb-credentials-rotation-router.ts @@ -0,0 +1,19 @@ +import { + CreateMongoDBCredentialsRotationSchema, + MongoDBCredentialsRotationGeneratedCredentialsSchema, + MongoDBCredentialsRotationSchema, + UpdateMongoDBCredentialsRotationSchema +} from "@app/ee/services/secret-rotation-v2/mongodb-credentials"; +import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums"; + +import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints"; + +export const registerMongoDBCredentialsRotationRouter = async (server: FastifyZodProvider) => + registerSecretRotationEndpoints({ + type: SecretRotation.MongoDBCredentials, + server, + responseSchema: MongoDBCredentialsRotationSchema, + createSchema: CreateMongoDBCredentialsRotationSchema, + updateSchema: UpdateMongoDBCredentialsRotationSchema, + generatedCredentialsSchema: MongoDBCredentialsRotationGeneratedCredentialsSchema + }); diff --git a/backend/src/ee/routes/v2/secret-rotation-v2-routers/secret-rotation-v2-router.ts b/backend/src/ee/routes/v2/secret-rotation-v2-routers/secret-rotation-v2-router.ts index 6ea6497e4..3dc0db292 100644 --- a/backend/src/ee/routes/v2/secret-rotation-v2-routers/secret-rotation-v2-router.ts +++ b/backend/src/ee/routes/v2/secret-rotation-v2-routers/secret-rotation-v2-router.ts @@ -5,6 +5,7 @@ import { Auth0ClientSecretRotationListItemSchema } from "@app/ee/services/secret import { AwsIamUserSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/aws-iam-user-secret"; import { AzureClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret"; import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password"; +import { MongoDBCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mongodb-credentials"; import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials"; import { MySqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials"; import { OktaClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/okta-client-secret"; @@ -27,7 +28,8 @@ const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [ AwsIamUserSecretRotationListItemSchema, LdapPasswordRotationListItemSchema, OktaClientSecretRotationListItemSchema, - RedisCredentialsRotationListItemSchema + RedisCredentialsRotationListItemSchema, + MongoDBCredentialsRotationListItemSchema ]); export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => { @@ -98,4 +100,4 @@ export const registerSecretRotationV2Router = async (server: FastifyZodProvider) return { secretRotations }; } }); -}; +}; \ No newline at end of file diff --git a/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/index.ts b/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/index.ts new file mode 100644 index 000000000..876ab836d --- /dev/null +++ b/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/index.ts @@ -0,0 +1,4 @@ +export * from "./mongodb-credentials-rotation-constants"; +export * from "./mongodb-credentials-rotation-fns"; +export * from "./mongodb-credentials-rotation-schemas"; +export * from "./mongodb-credentials-rotation-types"; diff --git a/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-constants.ts b/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-constants.ts new file mode 100644 index 000000000..f831563da --- /dev/null +++ b/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-constants.ts @@ -0,0 +1,28 @@ +import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums"; +import { TSecretRotationV2ListItem } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; + +export const MONGODB_CREDENTIALS_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = { + name: "MongoDB Credentials", + type: SecretRotation.MongoDBCredentials, + connection: AppConnection.MongoDB, + template: { + createUserStatement: `use [DATABASE_NAME] +db.createUser({ + user: "infisical_user_1", + pwd: "temporary_password", + roles: [{ role: "readWrite", db: "[DATABASE_NAME]" }] +}) + +db.createUser({ + user: "infisical_user_2", + pwd: "temporary_password", + roles: [{ role: "readWrite", db: "[DATABASE_NAME]" }] +})`, + secretsMapping: { + username: "MONGODB_DB_USERNAME", + password: "MONGODB_DB_PASSWORD" + } + } +}; + diff --git a/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-fns.ts b/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-fns.ts new file mode 100644 index 000000000..5b66f13bc --- /dev/null +++ b/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-fns.ts @@ -0,0 +1,248 @@ +/* eslint-disable no-await-in-loop */ +import { MongoClient } from "mongodb"; + +import { verifyHostInputValidity } from "@app/ee/services/dynamic-secret/dynamic-secret-fns"; +import { + TRotationFactory, + TRotationFactoryGetSecretsPayload, + TRotationFactoryIssueCredentials, + TRotationFactoryRevokeCredentials, + TRotationFactoryRotateCredentials +} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types"; + +import { DEFAULT_PASSWORD_REQUIREMENTS, generatePassword } from "../shared/utils"; +import { + TMongoDBCredentialsRotationGeneratedCredentials, + TMongoDBCredentialsRotationWithConnection +} from "./mongodb-credentials-rotation-types"; + +const redactPasswords = (e: unknown, credentials: TMongoDBCredentialsRotationGeneratedCredentials) => { + 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 mongodbCredentialsRotationFactory: TRotationFactory< + TMongoDBCredentialsRotationWithConnection, + TMongoDBCredentialsRotationGeneratedCredentials +> = (secretRotation) => { + const { + connection, + parameters: { username1, username2 }, + activeIndex, + secretsMapping + } = secretRotation; + + const passwordRequirement = DEFAULT_PASSWORD_REQUIREMENTS; + + // Helper function to create MongoDB client with given credentials + const $createMongoClient = async ( + authCredentials: { username: string; password: string }, + options?: { validateConnection?: boolean; requireTlsForSrv?: boolean } + ): Promise => { + let normalizedHost = connection.credentials.host.trim(); + const isSrvFromHost = normalizedHost.startsWith("mongodb+srv://"); + if (isSrvFromHost) { + normalizedHost = normalizedHost.replace(/^mongodb\+srv:\/\//, ""); + } else if (normalizedHost.startsWith("mongodb://")) { + normalizedHost = normalizedHost.replace(/^mongodb:\/\//, ""); + } + + const [hostIp] = await verifyHostInputValidity(normalizedHost); + + const isSrv = !connection.credentials.port || isSrvFromHost; + const uri = isSrv ? `mongodb+srv://${hostIp}` : `mongodb://${hostIp}:${connection.credentials.port}`; + + const clientOptions: { + auth?: { username: string; password?: string }; + authSource?: string; + tls?: boolean; + tlsInsecure?: boolean; + ca?: string; + directConnection?: boolean; + } = { + auth: { + username: authCredentials.username, + password: authCredentials.password + }, + directConnection: !isSrv + }; + + // SSL is enabled if explicitly enabled OR if using SRV (which requires TLS) and requireTlsForSrv is true + if (connection.credentials.sslEnabled || (isSrv && options?.requireTlsForSrv)) { + clientOptions.tls = true; + clientOptions.tlsInsecure = !connection.credentials.sslRejectUnauthorized; + if (connection.credentials.sslCertificate) { + clientOptions.ca = connection.credentials.sslCertificate; + } + } + + const client = new MongoClient(uri, clientOptions); + + if (options?.validateConnection) { + await client.db(connection.credentials.database).command({ ping: 1 }); + } + + return client; + }; + + const $getClient = async () => { + let client: MongoClient | null = null; + try { + client = await $createMongoClient( + { + username: connection.credentials.username, + password: connection.credentials.password + }, + { validateConnection: true } + ); + return client; + } catch (err) { + if (client) await client.close(); + throw err; + } + }; + + const $validateCredentials = async (credentials: TMongoDBCredentialsRotationGeneratedCredentials[number]) => { + let client: MongoClient | null = null; + try { + client = await $createMongoClient( + { + username: credentials.username, + password: credentials.password + }, + { validateConnection: true, requireTlsForSrv: true } + ); + } catch (error) { + throw new Error(redactPasswords(error, [credentials])); + } finally { + if (client) await client.close(); + } + }; + + const issueCredentials: TRotationFactoryIssueCredentials = async ( + callback + ) => { + // For MongoDB, since we get existing users, we change both their passwords + // on issue to invalidate their existing passwords + const credentialsSet = [ + { username: username1, password: generatePassword(passwordRequirement) }, + { username: username2, password: generatePassword(passwordRequirement) } + ]; + + let client: MongoClient | null = null; + try { + client = await $getClient(); + const db = client.db(connection.credentials.database); + + for (const credentials of credentialsSet) { + await db.command({ + updateUser: credentials.username, + pwd: credentials.password + }); + } + } catch (error) { + throw new Error(redactPasswords(error, credentialsSet)); + } finally { + if (client) await client.close(); + } + + for (const credentials of credentialsSet) { + await $validateCredentials(credentials); + } + + return callback(credentialsSet[0]); + }; + + const revokeCredentials: TRotationFactoryRevokeCredentials = async ( + credentialsToRevoke, + callback + ) => { + const revokedCredentials = credentialsToRevoke.map(({ username }) => ({ + username, + password: generatePassword(passwordRequirement) + })); + + let client: MongoClient | null = null; + try { + client = await $getClient(); + const db = client.db(connection.credentials.database); + + for (const credentials of revokedCredentials) { + await db.command({ + updateUser: credentials.username, + pwd: credentials.password + }); + } + } catch (error) { + throw new Error(redactPasswords(error, revokedCredentials)); + } finally { + if (client) await client.close(); + } + + return callback(); + }; + + const rotateCredentials: TRotationFactoryRotateCredentials = async ( + _, + callback + ) => { + const credentials = { + username: activeIndex === 0 ? username2 : username1, + password: generatePassword(passwordRequirement) + }; + + let client: MongoClient | null = null; + try { + client = await $getClient(); + const db = client.db(connection.credentials.database); + + await db.command({ + updateUser: credentials.username, + pwd: credentials.password + }); + } catch (error) { + throw new Error(redactPasswords(error, [credentials])); + } finally { + if (client) await client.close(); + } + + await $validateCredentials(credentials); + + return callback(credentials); + }; + + const getSecretsPayload: TRotationFactoryGetSecretsPayload = ( + generatedCredentials + ) => { + const { username, password } = secretsMapping; + + const secrets = [ + { + key: username, + value: generatedCredentials.username + }, + { + key: password, + value: generatedCredentials.password + } + ]; + + return secrets; + }; + + return { + issueCredentials, + revokeCredentials, + rotateCredentials, + getSecretsPayload + }; +}; diff --git a/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-schemas.ts b/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-schemas.ts new file mode 100644 index 000000000..9a5335f5f --- /dev/null +++ b/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-schemas.ts @@ -0,0 +1,52 @@ +import { z } from "zod"; + +import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums"; +import { + BaseCreateSecretRotationSchema, + BaseSecretRotationSchema, + BaseUpdateSecretRotationSchema +} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-schemas"; +import { + SqlCredentialsRotationGeneratedCredentialsSchema, + SqlCredentialsRotationParametersSchema, + SqlCredentialsRotationTemplateSchema +} from "@app/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-schemas"; +import { SecretRotations } from "@app/lib/api-docs"; +import { SecretNameSchema } from "@app/server/lib/schemas"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; + +export const MongoDBCredentialsRotationGeneratedCredentialsSchema = SqlCredentialsRotationGeneratedCredentialsSchema; +export const MongoDBCredentialsRotationParametersSchema = SqlCredentialsRotationParametersSchema; +export const MongoDBCredentialsRotationTemplateSchema = SqlCredentialsRotationTemplateSchema; + +const MongoDBCredentialsRotationSecretsMappingSchema = z.object({ + username: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.MONGODB_CREDENTIALS.username), + password: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.MONGODB_CREDENTIALS.password) +}); + +export const MongoDBCredentialsRotationSchema = BaseSecretRotationSchema(SecretRotation.MongoDBCredentials).extend({ + type: z.literal(SecretRotation.MongoDBCredentials), + parameters: MongoDBCredentialsRotationParametersSchema, + secretsMapping: MongoDBCredentialsRotationSecretsMappingSchema +}); + +export const CreateMongoDBCredentialsRotationSchema = BaseCreateSecretRotationSchema( + SecretRotation.MongoDBCredentials +).extend({ + parameters: MongoDBCredentialsRotationParametersSchema, + secretsMapping: MongoDBCredentialsRotationSecretsMappingSchema +}); + +export const UpdateMongoDBCredentialsRotationSchema = BaseUpdateSecretRotationSchema( + SecretRotation.MongoDBCredentials +).extend({ + parameters: MongoDBCredentialsRotationParametersSchema.optional(), + secretsMapping: MongoDBCredentialsRotationSecretsMappingSchema.optional() +}); + +export const MongoDBCredentialsRotationListItemSchema = z.object({ + name: z.literal("MongoDB Credentials"), + connection: z.literal(AppConnection.MongoDB), + type: z.literal(SecretRotation.MongoDBCredentials), + template: MongoDBCredentialsRotationTemplateSchema +}); diff --git a/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-types.ts b/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-types.ts new file mode 100644 index 000000000..3a53a8cc5 --- /dev/null +++ b/backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-types.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; + +import { TMongoDBConnection } from "@app/services/app-connection/mongodb"; + +import { + CreateMongoDBCredentialsRotationSchema, + MongoDBCredentialsRotationGeneratedCredentialsSchema, + MongoDBCredentialsRotationListItemSchema, + MongoDBCredentialsRotationSchema +} from "./mongodb-credentials-rotation-schemas"; + +export type TMongoDBCredentialsRotation = z.infer; + +export type TMongoDBCredentialsRotationInput = z.infer; + +export type TMongoDBCredentialsRotationListItem = z.infer; + +export type TMongoDBCredentialsRotationWithConnection = TMongoDBCredentialsRotation & { + connection: TMongoDBConnection; +}; + +export type TMongoDBCredentialsRotationGeneratedCredentials = z.infer< + typeof MongoDBCredentialsRotationGeneratedCredentialsSchema +>; diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-fns.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-fns.ts index e4e6a8531..bb774c4be 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-fns.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-fns.ts @@ -9,6 +9,7 @@ import { AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./auth0-client-secret" import { AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION } from "./aws-iam-user-secret"; import { AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./azure-client-secret"; import { LDAP_PASSWORD_ROTATION_LIST_OPTION, TLdapPasswordRotation } from "./ldap-password"; +import { MONGODB_CREDENTIALS_ROTATION_LIST_OPTION } from "./mongodb-credentials"; import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials"; import { MYSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mysql-credentials"; import { OKTA_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./okta-client-secret"; @@ -37,7 +38,8 @@ const SECRET_ROTATION_LIST_OPTIONS: Record { diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-service.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-service.ts index 105232b91..a8f3a24f9 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-service.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-service.ts @@ -84,6 +84,7 @@ import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/se import { TGatewayV2ServiceFactory } from "../gateway-v2/gateway-v2-service"; import { awsIamUserSecretRotationFactory } from "./aws-iam-user-secret/aws-iam-user-secret-rotation-fns"; +import { mongodbCredentialsRotationFactory } from "./mongodb-credentials/mongodb-credentials-rotation-fns"; import { oktaClientSecretRotationFactory } from "./okta-client-secret/okta-client-secret-rotation-fns"; import { redisCredentialsRotationFactory } from "./redis-credentials/redis-credentials-rotation-fns"; import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal"; @@ -134,7 +135,8 @@ const SECRET_ROTATION_FACTORY_MAP: Record = async ( callback ) => { - // For SQL, since we get existing users, we change both their passwords - // on issue to invalidate their existing passwords // For SQL, since we get existing users, we change both their passwords // on issue to invalidate their existing passwords const credentialsSet = [ diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 15ec747ef..a0d823302 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2842,6 +2842,12 @@ export const SecretRotations = { }, REDIS_CREDENTIALS: { permissionScope: "The ACL permission scope to assign to the issued Redis users." + }, + MONGODB_CREDENTIALS: { + username1: + "The username of the first MongoDB user to rotate passwords for. This user must already exist in your database.", + username2: + "The username of the second MongoDB user to rotate passwords for. This user must already exist in your database." } }, SECRETS_MAPPING: { @@ -2872,6 +2878,10 @@ export const SecretRotations = { OKTA_CLIENT_SECRET: { clientId: "The name of the secret that the client ID will be mapped to.", clientSecret: "The name of the secret that the rotated client secret will be mapped to." + }, + MONGODB_CREDENTIALS: { + username: "The name of the secret that the active username will be mapped to.", + password: "The name of the secret that the generated password will be mapped to." } } }; diff --git a/backend/src/services/app-connection/mongodb/mongodb-connection-fns.ts b/backend/src/services/app-connection/mongodb/mongodb-connection-fns.ts index 5587fc388..78d8b5cff 100644 --- a/backend/src/services/app-connection/mongodb/mongodb-connection-fns.ts +++ b/backend/src/services/app-connection/mongodb/mongodb-connection-fns.ts @@ -34,6 +34,7 @@ export const validateMongoDBConnectionCredentials = async (config: TMongoDBConne const clientOptions: { auth?: { username: string; password?: string }; + authSource?: string; tls?: boolean; tlsInsecure?: boolean; ca?: string; @@ -43,10 +44,11 @@ export const validateMongoDBConnectionCredentials = async (config: TMongoDBConne username: config.credentials.username, password: config.credentials.password }, + authSource: config.credentials.database, directConnection: !isSrv }; - if (config.credentials.sslEnabled || isSrv) { + if (config.credentials.sslEnabled) { clientOptions.tls = true; clientOptions.tlsInsecure = !config.credentials.sslRejectUnauthorized; if (config.credentials.sslCertificate) { @@ -56,7 +58,6 @@ export const validateMongoDBConnectionCredentials = async (config: TMongoDBConne client = new MongoClient(uri, clientOptions); - // Validate connection by running ping command await client .db(config.credentials.database) .command({ ping: 1 }) diff --git a/docs/integrations/app-connections/mongodb.mdx b/docs/integrations/app-connections/mongodb.mdx index b32874350..baaff7127 100644 --- a/docs/integrations/app-connections/mongodb.mdx +++ b/docs/integrations/app-connections/mongodb.mdx @@ -11,8 +11,8 @@ Infisical supports the use of Username & Password authentication to connect with Infisical recommends creating a designated user in your MongoDB database for your connection. - ```javascript - use admin + ```bash + use [TARGET-DATABASE] db.createUser({ user: "infisical_manager", pwd: "[ENTER-YOUR-USER-PASSWORD]", @@ -32,7 +32,7 @@ Infisical supports the use of Username & Password authentication to connect with For Secret Rotations, your Infisical user will require the ability to create, update, and delete users in the target database: - ```javascript + ```bash use [TARGET-DATABASE] db.grantRolesToUser("infisical_manager", [ { role: "userAdmin", db: "[TARGET-DATABASE]" } @@ -40,7 +40,7 @@ Infisical supports the use of Username & Password authentication to connect with ``` - The `userAdmin` role allows managing users (create, update passwords, delete) within the specified database. If you need to rotate users across multiple databases, grant `userAdminAnyDatabase` on the `admin` database instead. + The `userAdmin` role allows managing users (create, update passwords, delete) within the specified database. diff --git a/frontend/src/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/ViewSecretRotationV2GeneratedCredentials.tsx b/frontend/src/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/ViewSecretRotationV2GeneratedCredentials.tsx index e8553f6d9..01904b852 100644 --- a/frontend/src/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/ViewSecretRotationV2GeneratedCredentials.tsx +++ b/frontend/src/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/ViewSecretRotationV2GeneratedCredentials.tsx @@ -67,6 +67,7 @@ const Content = ({ secretRotation }: ContentProps) => { case SecretRotation.MySqlCredentials: case SecretRotation.MsSqlCredentials: case SecretRotation.OracleDBCredentials: + case SecretRotation.MongoDBCredentials: Component = ( = { [SecretRotation.LdapPassword]: LdapPasswordRotationParametersFields, [SecretRotation.AwsIamUserSecret]: AwsIamUserSecretRotationParametersFields, [SecretRotation.OktaClientSecret]: OktaClientSecretRotationParametersFields, - [SecretRotation.RedisCredentials]: RedisCredentialsRotationParametersFields + [SecretRotation.RedisCredentials]: RedisCredentialsRotationParametersFields, + [SecretRotation.MongoDBCredentials]: SqlCredentialsRotationParametersFields }; export const SecretRotationV2ParametersFields = () => { diff --git a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ReviewFields/SecretRotationReviewFields.tsx b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ReviewFields/SecretRotationReviewFields.tsx index e484a64b1..d48c69cca 100644 --- a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ReviewFields/SecretRotationReviewFields.tsx +++ b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ReviewFields/SecretRotationReviewFields.tsx @@ -24,7 +24,8 @@ const COMPONENT_MAP: Record = { [SecretRotation.LdapPassword]: LdapPasswordRotationReviewFields, [SecretRotation.AwsIamUserSecret]: AwsIamUserSecretRotationReviewFields, [SecretRotation.OktaClientSecret]: OktaClientSecretRotationReviewFields, - [SecretRotation.RedisCredentials]: RedisCredentialsRotationReviewFields + [SecretRotation.RedisCredentials]: RedisCredentialsRotationReviewFields, + [SecretRotation.MongoDBCredentials]: SqlCredentialsRotationReviewFields }; export const SecretRotationV2ReviewFields = () => { diff --git a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2SecretsMappingFields/SecretRotationV2SecretsMappingFields.tsx b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2SecretsMappingFields/SecretRotationV2SecretsMappingFields.tsx index e05fd31f5..a211abff8 100644 --- a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2SecretsMappingFields/SecretRotationV2SecretsMappingFields.tsx +++ b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2SecretsMappingFields/SecretRotationV2SecretsMappingFields.tsx @@ -21,7 +21,8 @@ const COMPONENT_MAP: Record = { [SecretRotation.LdapPassword]: LdapPasswordRotationSecretsMappingFields, [SecretRotation.AwsIamUserSecret]: AwsIamUserSecretRotationSecretsMappingFields, [SecretRotation.OktaClientSecret]: OktaClientSecretRotationSecretsMappingFields, - [SecretRotation.RedisCredentials]: RedisCredentialsRotationSecretsMappingFields + [SecretRotation.RedisCredentials]: RedisCredentialsRotationSecretsMappingFields, + [SecretRotation.MongoDBCredentials]: SqlCredentialsRotationSecretsMappingFields }; export const SecretRotationV2SecretsMappingFields = () => { diff --git a/frontend/src/components/secret-rotations-v2/forms/schemas/index.ts b/frontend/src/components/secret-rotations-v2/forms/schemas/index.ts index 199036a8f..3ea0f75ed 100644 --- a/frontend/src/components/secret-rotations-v2/forms/schemas/index.ts +++ b/frontend/src/components/secret-rotations-v2/forms/schemas/index.ts @@ -4,6 +4,7 @@ import { Auth0ClientSecretRotationSchema } from "@app/components/secret-rotation import { AwsIamUserSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/aws-iam-user-secret-rotation-schema"; import { AzureClientSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/azure-client-secret-rotation-schema"; import { LdapPasswordRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/ldap-password-rotation-schema"; +import { MongoDBCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/mongodb-credentials-rotation-schema"; import { MsSqlCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/mssql-credentials-rotation-schema"; import { MySqlCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/mysql-credentials-rotation-schema"; import { PostgresCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/postgres-credentials-rotation-schema"; @@ -27,7 +28,8 @@ export const SecretRotationV2FormSchema = (isUpdate: boolean) => LdapPasswordRotationSchema, AwsIamUserSecretRotationSchema, OktaClientSecretRotationSchema, - RedisCredentialsRotationSchema + RedisCredentialsRotationSchema, + MongoDBCredentialsRotationSchema ]), z.object({ id: z.string().optional() }) ) diff --git a/frontend/src/components/secret-rotations-v2/forms/schemas/mongodb-credentials-rotation-schema.ts b/frontend/src/components/secret-rotations-v2/forms/schemas/mongodb-credentials-rotation-schema.ts new file mode 100644 index 000000000..24c22cfed --- /dev/null +++ b/frontend/src/components/secret-rotations-v2/forms/schemas/mongodb-credentials-rotation-schema.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; + +import { BaseSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/base-secret-rotation-v2-schema"; +import { SqlCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/shared"; +import { SecretRotation } from "@app/hooks/api/secretRotationsV2"; + +export const MongoDBCredentialsRotationSchema = z + .object({ + type: z.literal(SecretRotation.MongoDBCredentials) + }) + .merge(SqlCredentialsRotationSchema) + .merge(BaseSecretRotationSchema); diff --git a/frontend/src/helpers/secretRotationsV2.ts b/frontend/src/helpers/secretRotationsV2.ts index d3bb83f19..187177c68 100644 --- a/frontend/src/helpers/secretRotationsV2.ts +++ b/frontend/src/helpers/secretRotationsV2.ts @@ -54,6 +54,11 @@ export const SECRET_ROTATION_MAP: Record< name: "Redis Credentials", image: "Redis.png", size: 50 + }, + [SecretRotation.MongoDBCredentials]: { + name: "MongoDB Credentials", + image: "MongoDB.png", + size: 50 } }; @@ -67,7 +72,8 @@ export const SECRET_ROTATION_CONNECTION_MAP: Record = { [SecretRotation.LdapPassword]: false, [SecretRotation.AwsIamUserSecret]: true, [SecretRotation.OktaClientSecret]: true, - [SecretRotation.RedisCredentials]: true + [SecretRotation.RedisCredentials]: true, + [SecretRotation.MongoDBCredentials]: true }; export const getRotateAtLocal = ({ hours, minutes }: TSecretRotationV2["rotateAtUtc"]) => { diff --git a/frontend/src/hooks/api/secretRotationsV2/enums.ts b/frontend/src/hooks/api/secretRotationsV2/enums.ts index 264a6a4a4..d52de16fc 100644 --- a/frontend/src/hooks/api/secretRotationsV2/enums.ts +++ b/frontend/src/hooks/api/secretRotationsV2/enums.ts @@ -8,7 +8,8 @@ export enum SecretRotation { LdapPassword = "ldap-password", AwsIamUserSecret = "aws-iam-user-secret", OktaClientSecret = "okta-client-secret", - RedisCredentials = "redis-credentials" + RedisCredentials = "redis-credentials", + MongoDBCredentials = "mongodb-credentials" } export enum SecretRotationStatus { diff --git a/frontend/src/hooks/api/secretRotationsV2/types/index.ts b/frontend/src/hooks/api/secretRotationsV2/types/index.ts index a04b0e020..cc938ae05 100644 --- a/frontend/src/hooks/api/secretRotationsV2/types/index.ts +++ b/frontend/src/hooks/api/secretRotationsV2/types/index.ts @@ -31,6 +31,11 @@ import { TSqlCredentialsRotationOption } from "@app/hooks/api/secretRotationsV2/ import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types"; import { DiscriminativePick } from "@app/types"; +import { + TMongoDBCredentialsRotation, + TMongoDBCredentialsRotationGeneratedCredentialsResponse, + TMongoDBCredentialsRotationOption +} from "./mongodb-credentials-rotation"; import { TMySqlCredentialsRotation, TMySqlCredentialsRotationGeneratedCredentialsResponse @@ -61,6 +66,7 @@ export type TSecretRotationV2 = ( | TAwsIamUserSecretRotation | TOktaClientSecretRotation | TRedisCredentialsRotation + | TMongoDBCredentialsRotation ) & { secrets: (SecretV3RawSanitized | null)[]; }; @@ -72,7 +78,8 @@ export type TSecretRotationV2Option = | TLdapPasswordRotationOption | TAwsIamUserSecretRotationOption | TOktaClientSecretRotationOption - | TRedisCredentialsRotationOption; + | TRedisCredentialsRotationOption + | TMongoDBCredentialsRotationOption; export type TListSecretRotationV2Options = { secretRotationOptions: TSecretRotationV2Option[] }; @@ -88,7 +95,8 @@ export type TViewSecretRotationGeneratedCredentialsResponse = | TLdapPasswordRotationGeneratedCredentialsResponse | TAwsIamUserSecretRotationGeneratedCredentialsResponse | TOktaClientSecretRotationGeneratedCredentialsResponse - | TRedisCredentialsRotationGeneratedCredentialsResponse; + | TRedisCredentialsRotationGeneratedCredentialsResponse + | TMongoDBCredentialsRotationGeneratedCredentialsResponse; export type TCreateSecretRotationV2DTO = DiscriminativePick< TSecretRotationV2, @@ -142,6 +150,7 @@ export type TSecretRotationOptionMap = { [SecretRotation.AwsIamUserSecret]: TAwsIamUserSecretRotationOption; [SecretRotation.OktaClientSecret]: TOktaClientSecretRotationOption; [SecretRotation.RedisCredentials]: TRedisCredentialsRotationOption; + [SecretRotation.MongoDBCredentials]: TMongoDBCredentialsRotationOption; }; export type TSecretRotationGeneratedCredentialsResponseMap = { @@ -155,4 +164,5 @@ export type TSecretRotationGeneratedCredentialsResponseMap = { [SecretRotation.AwsIamUserSecret]: TAwsIamUserSecretRotationGeneratedCredentialsResponse; [SecretRotation.OktaClientSecret]: TOktaClientSecretRotationGeneratedCredentialsResponse; [SecretRotation.RedisCredentials]: TRedisCredentialsRotationGeneratedCredentialsResponse; + [SecretRotation.MongoDBCredentials]: TMongoDBCredentialsRotationGeneratedCredentialsResponse; }; diff --git a/frontend/src/hooks/api/secretRotationsV2/types/mongodb-credentials-rotation.ts b/frontend/src/hooks/api/secretRotationsV2/types/mongodb-credentials-rotation.ts new file mode 100644 index 000000000..425357d54 --- /dev/null +++ b/frontend/src/hooks/api/secretRotationsV2/types/mongodb-credentials-rotation.ts @@ -0,0 +1,28 @@ +import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { SecretRotation } from "@app/hooks/api/secretRotationsV2"; +import { + TSecretRotationV2Base, + TSecretRotationV2GeneratedCredentialsResponseBase, + TSqlCredentialsRotationGeneratedCredentials, + TSqlCredentialsRotationProperties +} from "@app/hooks/api/secretRotationsV2/types/shared"; + +export type TMongoDBCredentialsRotation = TSecretRotationV2Base & { + type: SecretRotation.MongoDBCredentials; +} & TSqlCredentialsRotationProperties; + +export type TMongoDBCredentialsRotationGeneratedCredentialsResponse = + TSecretRotationV2GeneratedCredentialsResponseBase< + SecretRotation.MongoDBCredentials, + TSqlCredentialsRotationGeneratedCredentials + >; + +export type TMongoDBCredentialsRotationOption = { + name: string; + type: SecretRotation.MongoDBCredentials; + connection: AppConnection.MongoDB; + template: { + createUserStatement: string; + secretsMapping: TMongoDBCredentialsRotation["secretsMapping"]; + }; +};