mirror of
https://github.com/Infisical/infisical.git
synced 2026-01-07 22:53:55 -05:00
refactor(mongodb-credentials): replace SSL terminology with TLS and enhance MongoDB client creation logic
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { MongoClient } from "mongodb";
|
||||
import RE2 from "re2";
|
||||
|
||||
import { verifyHostInputValidity } from "@app/ee/services/dynamic-secret/dynamic-secret-fns";
|
||||
import {
|
||||
TRotationFactory,
|
||||
TRotationFactoryGetSecretsPayload,
|
||||
@@ -10,6 +8,7 @@ import {
|
||||
TRotationFactoryRevokeCredentials,
|
||||
TRotationFactoryRotateCredentials
|
||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||
import { createMongoClient } from "@app/services/app-connection/mongodb/mongodb-connection-fns";
|
||||
|
||||
import { DEFAULT_PASSWORD_REQUIREMENTS, generatePassword } from "../shared/utils";
|
||||
import {
|
||||
@@ -44,67 +43,10 @@ export const mongodbCredentialsRotationFactory: TRotationFactory<
|
||||
|
||||
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 }
|
||||
): Promise<MongoClient> => {
|
||||
let normalizedHost = connection.credentials.host.trim();
|
||||
const srvRegex = new RE2("^mongodb\\+srv:\\/\\/");
|
||||
const protocolRegex = new RE2("^mongodb:\\/\\/");
|
||||
|
||||
const isSrvFromHost = srvRegex.test(normalizedHost);
|
||||
if (isSrvFromHost) {
|
||||
normalizedHost = srvRegex.replace(normalizedHost, "");
|
||||
} else if (protocolRegex.test(normalizedHost)) {
|
||||
normalizedHost = protocolRegex.replace(normalizedHost, "");
|
||||
}
|
||||
|
||||
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
|
||||
},
|
||||
authSource: isSrv ? undefined : connection.credentials.database,
|
||||
directConnection: !isSrv
|
||||
};
|
||||
|
||||
if (connection.credentials.sslCertificate) {
|
||||
clientOptions.tls = true;
|
||||
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 }
|
||||
);
|
||||
client = await createMongoClient(connection.credentials, { validateConnection: true });
|
||||
return client;
|
||||
} catch (err) {
|
||||
if (client) await client.close();
|
||||
@@ -115,13 +57,13 @@ export const mongodbCredentialsRotationFactory: TRotationFactory<
|
||||
const $validateCredentials = async (credentials: TMongoDBCredentialsRotationGeneratedCredentials[number]) => {
|
||||
let client: MongoClient | null = null;
|
||||
try {
|
||||
client = await $createMongoClient(
|
||||
{
|
||||
client = await createMongoClient(connection.credentials, {
|
||||
authCredentials: {
|
||||
username: credentials.username,
|
||||
password: credentials.password
|
||||
},
|
||||
{ validateConnection: true }
|
||||
);
|
||||
validateConnection: true
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(redactPasswords(error, [credentials]));
|
||||
} finally {
|
||||
|
||||
@@ -17,11 +17,32 @@ export const getMongoDBConnectionListItem = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const validateMongoDBConnectionCredentials = async (config: TMongoDBConnectionConfig) => {
|
||||
export type TMongoDBConnectionCredentials = {
|
||||
host: string;
|
||||
port?: number;
|
||||
database: string;
|
||||
username: string;
|
||||
password: string;
|
||||
tlsEnabled?: boolean;
|
||||
tlsRejectUnauthorized?: boolean;
|
||||
tlsCertificate?: string;
|
||||
};
|
||||
|
||||
export type TCreateMongoClientOptions = {
|
||||
authCredentials?: { username: string; password: string };
|
||||
validateConnection?: boolean;
|
||||
};
|
||||
|
||||
const DEFAULT_CONNECTION_TIMEOUT_MS = 10_000;
|
||||
|
||||
export const createMongoClient = async (
|
||||
credentials: TMongoDBConnectionCredentials,
|
||||
options?: TCreateMongoClientOptions
|
||||
): Promise<MongoClient> => {
|
||||
const srvRegex = new RE2("^mongodb\\+srv:\\/\\/");
|
||||
const protocolRegex = new RE2("^mongodb:\\/\\/");
|
||||
|
||||
let normalizedHost = config.credentials.host.trim();
|
||||
let normalizedHost = credentials.host.trim();
|
||||
const isSrvFromHost = srvRegex.test(normalizedHost);
|
||||
if (isSrvFromHost) {
|
||||
normalizedHost = srvRegex.replace(normalizedHost, "");
|
||||
@@ -31,41 +52,60 @@ export const validateMongoDBConnectionCredentials = async (config: TMongoDBConne
|
||||
|
||||
const [hostIp] = await verifyHostInputValidity(normalizedHost);
|
||||
|
||||
let client: MongoClient | null = null;
|
||||
try {
|
||||
const isSrv = !config.credentials.port || isSrvFromHost;
|
||||
const uri = isSrv ? `mongodb+srv://${hostIp}` : `mongodb://${hostIp}:${config.credentials.port}`;
|
||||
const isSrv = !credentials.port || isSrvFromHost;
|
||||
const uri = isSrv ? `mongodb+srv://${hostIp}` : `mongodb://${hostIp}:${credentials.port}`;
|
||||
|
||||
const clientOptions: {
|
||||
auth?: { username: string; password?: string };
|
||||
authSource?: string;
|
||||
tls?: boolean;
|
||||
tlsInsecure?: boolean;
|
||||
ca?: string;
|
||||
directConnection?: boolean;
|
||||
} = {
|
||||
auth: {
|
||||
username: config.credentials.username,
|
||||
password: config.credentials.password
|
||||
},
|
||||
authSource: isSrv ? undefined : config.credentials.database,
|
||||
directConnection: !isSrv
|
||||
};
|
||||
const authCredentials = options?.authCredentials ?? {
|
||||
username: credentials.username,
|
||||
password: credentials.password
|
||||
};
|
||||
|
||||
if (config.credentials.sslEnabled) {
|
||||
clientOptions.tls = true;
|
||||
clientOptions.tlsInsecure = !config.credentials.sslRejectUnauthorized;
|
||||
if (config.credentials.sslCertificate) {
|
||||
clientOptions.ca = config.credentials.sslCertificate;
|
||||
}
|
||||
const clientOptions: {
|
||||
auth?: { username: string; password?: string };
|
||||
authSource?: string;
|
||||
tls?: boolean;
|
||||
tlsInsecure?: boolean;
|
||||
ca?: string;
|
||||
directConnection?: boolean;
|
||||
connectTimeoutMS?: number;
|
||||
serverSelectionTimeoutMS?: number;
|
||||
socketTimeoutMS?: number;
|
||||
} = {
|
||||
auth: {
|
||||
username: authCredentials.username,
|
||||
password: authCredentials.password
|
||||
},
|
||||
authSource: isSrv ? undefined : credentials.database,
|
||||
directConnection: !isSrv,
|
||||
connectTimeoutMS: DEFAULT_CONNECTION_TIMEOUT_MS,
|
||||
serverSelectionTimeoutMS: DEFAULT_CONNECTION_TIMEOUT_MS,
|
||||
socketTimeoutMS: DEFAULT_CONNECTION_TIMEOUT_MS
|
||||
};
|
||||
|
||||
if (credentials.tlsEnabled) {
|
||||
clientOptions.tls = true;
|
||||
clientOptions.tlsInsecure = !credentials.tlsRejectUnauthorized;
|
||||
if (credentials.tlsCertificate) {
|
||||
clientOptions.ca = credentials.tlsCertificate;
|
||||
}
|
||||
}
|
||||
|
||||
client = new MongoClient(uri, clientOptions);
|
||||
const client = new MongoClient(uri, clientOptions);
|
||||
|
||||
if (options?.validateConnection) {
|
||||
await client
|
||||
.db(config.credentials.database)
|
||||
.db(credentials.database)
|
||||
.command({ ping: 1 })
|
||||
.then(() => true);
|
||||
}
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
export const validateMongoDBConnectionCredentials = async (config: TMongoDBConnectionConfig) => {
|
||||
let client: MongoClient | null = null;
|
||||
try {
|
||||
client = await createMongoClient(config.credentials, { validateConnection: true });
|
||||
|
||||
if (client) await client.close();
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@ export const BaseMongoDBUsernameAndPasswordConnectionSchema = z.object({
|
||||
password: z.string().min(1),
|
||||
database: z.string().min(1).trim(),
|
||||
|
||||
sslRejectUnauthorized: z.boolean(),
|
||||
sslEnabled: z.boolean(),
|
||||
sslCertificate: z
|
||||
tlsRejectUnauthorized: z.boolean(),
|
||||
tlsEnabled: z.boolean(),
|
||||
tlsCertificate: z
|
||||
.string()
|
||||
.trim()
|
||||
.transform((value) => value || undefined)
|
||||
@@ -43,9 +43,9 @@ export const SanitizedMongoDBConnectionSchema = z.discriminatedUnion("method", [
|
||||
port: true,
|
||||
username: true,
|
||||
database: true,
|
||||
sslEnabled: true,
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: true
|
||||
tlsEnabled: true,
|
||||
tlsRejectUnauthorized: true,
|
||||
tlsCertificate: true
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
@@ -11,9 +11,9 @@ export type TMongoDBConnectionCredentials = {
|
||||
username: string;
|
||||
password: string;
|
||||
database: string;
|
||||
sslEnabled: boolean;
|
||||
sslRejectUnauthorized: boolean;
|
||||
sslCertificate?: string;
|
||||
tlsEnabled: boolean;
|
||||
tlsRejectUnauthorized: boolean;
|
||||
tlsCertificate?: string;
|
||||
};
|
||||
|
||||
export type TMongoDBConnection = TRootAppConnection & { app: AppConnection.MongoDB } & {
|
||||
|
||||
@@ -45,9 +45,9 @@ const formSchema = z.discriminatedUnion("method", [
|
||||
username: z.string().trim().min(1, "Username required"),
|
||||
password: z.string().trim().min(1, "Password required"),
|
||||
database: z.string().trim().min(1, "Database required"),
|
||||
sslEnabled: z.boolean().default(false),
|
||||
sslRejectUnauthorized: z.boolean().default(true),
|
||||
sslCertificate: z
|
||||
tlsEnabled: z.boolean().default(false),
|
||||
tlsRejectUnauthorized: z.boolean().default(true),
|
||||
tlsCertificate: z
|
||||
.string()
|
||||
.trim()
|
||||
.transform((value) => value || undefined)
|
||||
@@ -73,9 +73,9 @@ export const MongoDBConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
username: "",
|
||||
password: "",
|
||||
database: "",
|
||||
sslEnabled: false,
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: undefined
|
||||
tlsEnabled: false,
|
||||
tlsRejectUnauthorized: true,
|
||||
tlsCertificate: undefined
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -87,7 +87,7 @@ export const MongoDBConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
const sslEnabled = watch("credentials.sslEnabled");
|
||||
const tlsEnabled = watch("credentials.tlsEnabled");
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
@@ -147,7 +147,7 @@ export const MongoDBConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
}`
|
||||
}
|
||||
>
|
||||
SSL ({sslEnabled ? "Enabled" : "Disabled"})
|
||||
TLS ({tlsEnabled ? "Enabled" : "Disabled"})
|
||||
</Tab>
|
||||
</Tab.List>
|
||||
<Tab.Panels className="mb-4 rounded-sm border border-mineshaft-600 bg-mineshaft-700/70 p-3 pb-0">
|
||||
@@ -233,53 +233,53 @@ export const MongoDBConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<Controller
|
||||
name="credentials.sslEnabled"
|
||||
name="credentials.tlsEnabled"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl isError={Boolean(error?.message)} errorText={error?.message}>
|
||||
<Switch
|
||||
className="bg-mineshaft-400/50 shadow-inner data-[state=checked]:bg-green/80"
|
||||
id="ssl-enabled"
|
||||
id="tls-enabled"
|
||||
thumbClassName="bg-mineshaft-800"
|
||||
isChecked={value}
|
||||
onCheckedChange={onChange}
|
||||
>
|
||||
Enable SSL
|
||||
Enable TLS
|
||||
</Switch>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.sslCertificate"
|
||||
name="credentials.tlsCertificate"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
className={sslEnabled ? "" : "opacity-50"}
|
||||
label="SSL Certificate"
|
||||
className={tlsEnabled ? "" : "opacity-50"}
|
||||
label="TLS Certificate"
|
||||
isOptional
|
||||
>
|
||||
<TextArea className="h-14 resize-none!" {...field} isDisabled={!sslEnabled} />
|
||||
<TextArea className="h-14 resize-none!" {...field} isDisabled={!tlsEnabled} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.sslRejectUnauthorized"
|
||||
name="credentials.tlsRejectUnauthorized"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className={sslEnabled ? "" : "opacity-50"}
|
||||
className={tlsEnabled ? "" : "opacity-50"}
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Switch
|
||||
className="bg-mineshaft-400/50 shadow-inner data-[state=checked]:bg-green/80"
|
||||
id="ssl-reject-unauthorized"
|
||||
id="tls-reject-unauthorized"
|
||||
thumbClassName="bg-mineshaft-800"
|
||||
isChecked={sslEnabled ? value : false}
|
||||
isChecked={tlsEnabled ? value : false}
|
||||
onCheckedChange={onChange}
|
||||
isDisabled={!sslEnabled}
|
||||
isDisabled={!tlsEnabled}
|
||||
>
|
||||
<p className="w-38">
|
||||
Reject Unauthorized
|
||||
@@ -288,7 +288,7 @@ export const MongoDBConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
content={
|
||||
<p>
|
||||
If enabled, Infisical will only connect to the server if it has a
|
||||
valid, trusted SSL certificate.
|
||||
valid, trusted TLS certificate.
|
||||
</p>
|
||||
}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user