mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-16 13:56:43 +00:00
feature secret rotations v2
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
export * from "./sql-connection-fns";
|
||||
export * from "./sql-connection-schemas";
|
||||
@@ -0,0 +1,113 @@
|
||||
import knex, { Knex } from "knex";
|
||||
|
||||
import { verifyHostInputValidity } from "@app/ee/services/dynamic-secret/dynamic-secret-fns";
|
||||
import {
|
||||
TSqlCredentialsRotationGeneratedCredentials,
|
||||
TSqlCredentialsRotationWithConnection
|
||||
} from "@app/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-types";
|
||||
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import { TAppConnectionRaw, TSqlConnection } from "@app/services/app-connection/app-connection-types";
|
||||
import { TSqlConnectionConfig } from "@app/services/app-connection/shared/sql/sql-connection-types";
|
||||
|
||||
const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
|
||||
|
||||
const SQL_CONNECTION_CLIENT_MAP = {
|
||||
[AppConnection.Postgres]: "pg",
|
||||
[AppConnection.MsSql]: "mssql"
|
||||
};
|
||||
|
||||
export const getSqlConnectionClient = async (
|
||||
appConnection: Pick<TSqlConnection, "credentials" | "app">,
|
||||
options?: Record<string, unknown>
|
||||
) => {
|
||||
const {
|
||||
app,
|
||||
credentials: { host: baseHost, database, port, sslCertificate, password, username }
|
||||
} = appConnection;
|
||||
|
||||
const ssl = sslCertificate ? { rejectUnauthorized: false, ca: sslCertificate } : undefined;
|
||||
|
||||
const [host] = await verifyHostInputValidity(baseHost);
|
||||
|
||||
const client = knex({
|
||||
client: SQL_CONNECTION_CLIENT_MAP[app],
|
||||
connection: {
|
||||
database,
|
||||
port,
|
||||
host,
|
||||
user: username,
|
||||
password,
|
||||
connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT,
|
||||
ssl,
|
||||
options
|
||||
}
|
||||
});
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
export const validateSqlConnectionCredentials = async (config: TSqlConnectionConfig) => {
|
||||
const { credentials, app } = config;
|
||||
|
||||
const client = await getSqlConnectionClient({ app, credentials });
|
||||
|
||||
try {
|
||||
await client.raw(`Select 1`);
|
||||
|
||||
return credentials;
|
||||
} catch (error) {
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
(error as Error)?.message?.replaceAll(credentials.password, "********************") ??
|
||||
"Unable to validate connection: verify credentials"
|
||||
});
|
||||
} finally {
|
||||
await client.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
export const SQL_CONNECTION_ALTER_LOGIN_STATEMENT: Record<
|
||||
TSqlCredentialsRotationWithConnection["connection"]["app"],
|
||||
(credentials: TSqlCredentialsRotationGeneratedCredentials[number]) => [string, Knex.RawBinding]
|
||||
> = {
|
||||
[AppConnection.Postgres]: ({ username, password }) => [`ALTER USER ?? WITH PASSWORD '${password}';`, [username]],
|
||||
[AppConnection.MsSql]: ({ username, password }) => [`ALTER LOGIN ?? WITH PASSWORD = '${password}';`, [username]]
|
||||
};
|
||||
|
||||
export const transferSqlConnectionCredentialsToPlatform = async (
|
||||
config: TSqlConnectionConfig,
|
||||
callback: (credentials: TSqlConnectionConfig["credentials"]) => Promise<TAppConnectionRaw>
|
||||
) => {
|
||||
const { credentials, app } = config;
|
||||
|
||||
const client = await getSqlConnectionClient({ app, credentials });
|
||||
|
||||
const newPassword = alphaNumericNanoId(32);
|
||||
|
||||
try {
|
||||
return await client.transaction(async (tx) => {
|
||||
await tx.raw(
|
||||
...SQL_CONNECTION_ALTER_LOGIN_STATEMENT[app]({ username: credentials.username, password: newPassword })
|
||||
);
|
||||
return callback({
|
||||
...credentials,
|
||||
password: newPassword
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
// update/create service function will handle
|
||||
if (error instanceof DatabaseError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
(error as Error)?.message?.replaceAll(newPassword, "********************") ??
|
||||
"Encountered an error transferring credentials to platform"
|
||||
});
|
||||
} finally {
|
||||
await client.destroy();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { AppConnections } from "@app/lib/api-docs";
|
||||
|
||||
export const BaseSqlUsernameAndPasswordConnectionSchema = z.object({
|
||||
host: z.string().trim().min(1, "Host required").describe(AppConnections.CREDENTIALS.SQL_CONNECTION.host),
|
||||
port: z.coerce.number().describe(AppConnections.CREDENTIALS.SQL_CONNECTION.port),
|
||||
database: z.string().trim().min(1, "Database required").describe(AppConnections.CREDENTIALS.SQL_CONNECTION.database),
|
||||
username: z.string().trim().min(1, "Username required").describe(AppConnections.CREDENTIALS.SQL_CONNECTION.username),
|
||||
password: z.string().trim().min(1, "Password required").describe(AppConnections.CREDENTIALS.SQL_CONNECTION.password),
|
||||
sslCertificate: z.string().trim().optional().describe(AppConnections.CREDENTIALS.SQL_CONNECTION.sslCertificate)
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import { DiscriminativePick } from "@app/lib/types";
|
||||
import { TSqlConnectionInput } from "@app/services/app-connection/app-connection-types";
|
||||
|
||||
export type TSqlConnectionConfig = DiscriminativePick<TSqlConnectionInput, "method" | "app" | "credentials"> & {
|
||||
orgId: string;
|
||||
};
|
||||
Reference in New Issue
Block a user