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