Skip to content
Open
4 changes: 3 additions & 1 deletion backend/src/ee/routes/v2/secret-rotation-v2-routers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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
};
Original file line number Diff line number Diff line change
@@ -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
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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";
Expand All @@ -27,7 +28,8 @@
AwsIamUserSecretRotationListItemSchema,
LdapPasswordRotationListItemSchema,
OktaClientSecretRotationListItemSchema,
RedisCredentialsRotationListItemSchema
RedisCredentialsRotationListItemSchema,
MongoDBCredentialsRotationListItemSchema
]);

export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
Expand Down Expand Up @@ -98,4 +100,4 @@
return { secretRotations };
}
});
};
};

Check failure on line 103 in backend/src/ee/routes/v2/secret-rotation-v2-routers/secret-rotation-v2-router.ts

View workflow job for this annotation

GitHub Actions / Check TS and Lint

Insert `⏎`
Original file line number Diff line number Diff line change
@@ -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";
Original file line number Diff line number Diff line change
@@ -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"
}
}
};

Check failure on line 28 in backend/src/ee/services/secret-rotation-v2/mongodb-credentials/mongodb-credentials-rotation-constants.ts

View workflow job for this annotation

GitHub Actions / Check TS and Lint

Delete `⏎`
Original file line number Diff line number Diff line change
@@ -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<MongoClient> => {
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<TMongoDBCredentialsRotationGeneratedCredentials> = 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<TMongoDBCredentialsRotationGeneratedCredentials> = async (
credentialsToRevoke,
callback
) => {
const revokedCredentials = credentialsToRevoke.map(({ username }) => ({
username,
password: generatePassword(passwordRequirement)
}));

let client: MongoClient | null = null;
try {
client = await $getClient();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for both issue and revoke I think we want to use a mongodb session (transaction) - that way if either command fails neither commits

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<TMongoDBCredentialsRotationGeneratedCredentials> = 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<TMongoDBCredentialsRotationGeneratedCredentials> = (
generatedCredentials
) => {
const { username, password } = secretsMapping;

const secrets = [
{
key: username,
value: generatedCredentials.username
},
{
key: password,
value: generatedCredentials.password
}
];

return secrets;
};

return {
issueCredentials,
revokeCredentials,
rotateCredentials,
getSecretsPayload
};
};
Original file line number Diff line number Diff line change
@@ -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
});
Loading
Loading