Merge branch 'main' into ENG-3506-LDAP

This commit is contained in:
x032205
2025-09-15 11:52:18 -04:00
190 changed files with 7343 additions and 645 deletions
+3
View File
@@ -259,6 +259,8 @@ const envSchema = z
GATEWAY_RELAY_REALM: zpStr(z.string().optional()),
GATEWAY_RELAY_AUTH_SECRET: zpStr(z.string().optional()),
RELAY_AUTH_SECRET: zpStr(z.string().optional()),
DYNAMIC_SECRET_ALLOW_INTERNAL_IP: zodStrBool.default("false"),
DYNAMIC_SECRET_AWS_ACCESS_KEY_ID: zpStr(z.string().optional()).default(
process.env.INF_APP_CONNECTION_AWS_ACCESS_KEY_ID
@@ -410,6 +412,7 @@ const envSchema = z
Boolean(data.INF_APP_CONNECTION_GITHUB_RADAR_APP_CLIENT_ID) &&
Boolean(data.INF_APP_CONNECTION_GITHUB_RADAR_APP_CLIENT_SECRET) &&
Boolean(data.INF_APP_CONNECTION_GITHUB_RADAR_APP_WEBHOOK_SECRET),
isSecondaryInstance: Boolean(data.INFISICAL_PRIMARY_INSTANCE_URL),
isHsmConfigured:
Boolean(data.HSM_LIB_PATH) && Boolean(data.HSM_PIN) && Boolean(data.HSM_KEY_LABEL) && data.HSM_SLOT !== undefined,
samlDefaultOrgSlug: data.DEFAULT_SAML_ORG_SLUG,
@@ -424,7 +424,8 @@ const cryptographyFactory = () => {
constants: crypto.constants,
X509Certificate: crypto.X509Certificate,
KeyObject: crypto.KeyObject,
Hash: crypto.Hash
Hash: crypto.Hash,
timingSafeEqual: crypto.timingSafeEqual
}
};
};
+281
View File
@@ -0,0 +1,281 @@
import net from "node:net";
import tls from "node:tls";
import axios from "axios";
import https from "https";
import { verifyHostInputValidity } from "@app/ee/services/dynamic-secret/dynamic-secret-fns";
import { splitPemChain } from "@app/services/certificate/certificate-fns";
import { BadRequestError } from "../errors";
import { GatewayProxyProtocol } from "../gateway/types";
import { logger } from "../logger";
interface IGatewayRelayServer {
server: net.Server;
port: number;
cleanup: () => Promise<void>;
getRelayError: () => string;
}
const createRelayConnection = async ({
relayHost,
clientCertificate,
clientPrivateKey,
serverCertificateChain
}: {
relayHost: string;
clientCertificate: string;
clientPrivateKey: string;
serverCertificateChain: string;
}): Promise<net.Socket> => {
const [targetHost] = await verifyHostInputValidity(relayHost);
const [, portStr] = relayHost.split(":");
const port = parseInt(portStr, 10) || 8443;
const serverCAs = splitPemChain(serverCertificateChain);
const tlsOptions: tls.ConnectionOptions = {
host: targetHost,
servername: relayHost,
port,
cert: clientCertificate,
key: clientPrivateKey,
ca: serverCAs,
minVersion: "TLSv1.2",
rejectUnauthorized: true
};
return new Promise((resolve, reject) => {
try {
const socket = tls.connect(tlsOptions, () => {
logger.info("Relay TLS connection established successfully");
resolve(socket);
});
socket.on("error", (err: Error) => {
reject(new Error(`TLS connection error: ${err.message}`));
});
socket.on("close", (hadError: boolean) => {
if (hadError) {
logger.error("TLS connection closed with error");
}
});
socket.on("timeout", () => {
logger.error(`TLS connection timeout after 30 seconds`);
socket.destroy();
reject(new Error("TLS connection timeout"));
});
socket.setTimeout(30000);
} catch (error: unknown) {
reject(new Error(`Failed to create TLS connection: ${error instanceof Error ? error.message : String(error)}`));
}
});
};
const createGatewayConnection = async (
relayConn: net.Socket,
gateway: { clientCertificate: string; clientPrivateKey: string; serverCertificateChain: string },
protocol: GatewayProxyProtocol
): Promise<net.Socket> => {
const protocolToAlpn = {
[GatewayProxyProtocol.Http]: "infisical-http-proxy",
[GatewayProxyProtocol.Tcp]: "infisical-tcp-proxy",
[GatewayProxyProtocol.Ping]: "infisical-ping"
};
const tlsOptions: tls.ConnectionOptions = {
socket: relayConn,
cert: gateway.clientCertificate,
key: gateway.clientPrivateKey,
ca: splitPemChain(gateway.serverCertificateChain),
minVersion: "TLSv1.2",
maxVersion: "TLSv1.3",
rejectUnauthorized: true,
ALPNProtocols: [protocolToAlpn[protocol]]
};
return new Promise((resolve, reject) => {
try {
const gatewaySocket = tls.connect(tlsOptions, () => {
if (!gatewaySocket.authorized) {
const error = gatewaySocket.authorizationError;
gatewaySocket.destroy();
reject(new Error(`Gateway TLS authorization failed: ${error?.message}`));
return;
}
logger.info("Gateway mTLS connection established successfully");
resolve(gatewaySocket);
});
gatewaySocket.on("error", (err: Error) => {
reject(new Error(`Failed to establish gateway mTLS: ${err.message}`));
});
gatewaySocket.setTimeout(30000);
gatewaySocket.on("timeout", () => {
gatewaySocket.destroy();
reject(new Error("Gateway connection timeout"));
});
} catch (error: unknown) {
reject(
new Error(`Failed to create gateway TLS connection: ${error instanceof Error ? error.message : String(error)}`)
);
}
});
};
const setupRelayServer = async ({
protocol,
relayHost,
gateway,
relay,
httpsAgent
}: {
protocol: GatewayProxyProtocol;
relayHost: string;
gateway: { clientCertificate: string; clientPrivateKey: string; serverCertificateChain: string };
relay: { clientCertificate: string; clientPrivateKey: string; serverCertificateChain: string };
httpsAgent?: https.Agent;
}): Promise<IGatewayRelayServer> => {
const relayErrorMsg: string[] = [];
return new Promise((resolve, reject) => {
const server = net.createServer();
server.on("connection", (clientConn) => {
void (async () => {
try {
clientConn.setKeepAlive(true, 30000);
clientConn.setNoDelay(true);
// Stage 1: Connect to relay with TLS
const relayConn = await createRelayConnection({
relayHost,
clientCertificate: relay.clientCertificate,
clientPrivateKey: relay.clientPrivateKey,
serverCertificateChain: relay.serverCertificateChain
});
// Stage 2: Establish mTLS connection to gateway through the relay
const gatewayConn = await createGatewayConnection(relayConn, gateway, protocol);
// Send protocol-specific configuration for HTTP requests
if (protocol === GatewayProxyProtocol.Http) {
if (httpsAgent) {
const agentOptions = httpsAgent.options;
if (agentOptions && agentOptions.ca) {
const caCert = Array.isArray(agentOptions.ca) ? agentOptions.ca.join("\n") : agentOptions.ca;
const caB64 = Buffer.from(caCert as string).toString("base64");
const rejectUnauthorized = agentOptions.rejectUnauthorized !== false;
const configCommand = `CONFIG ca=${caB64} verify=${rejectUnauthorized}\n`;
gatewayConn.write(Buffer.from(configCommand));
} else {
// Send empty config to signal end of configuration
gatewayConn.write(Buffer.from("CONFIG\n"));
}
} else {
// Send empty config to signal end of configuration
gatewayConn.write(Buffer.from("CONFIG\n"));
}
}
// Bidirectional data forwarding
clientConn.pipe(gatewayConn);
gatewayConn.pipe(clientConn);
// Handle connection closure
clientConn.on("close", () => {
relayConn.destroy();
gatewayConn.destroy();
});
relayConn.on("close", () => {
clientConn.destroy();
gatewayConn.destroy();
});
gatewayConn.on("close", () => {
clientConn.destroy();
relayConn.destroy();
});
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);
relayErrorMsg.push(errorMsg);
clientConn.destroy();
}
})();
});
server.on("error", (err) => {
reject(err);
});
server.listen(0, () => {
const address = server.address();
if (!address || typeof address === "string") {
server.close();
reject(new Error("Failed to get server port"));
return;
}
resolve({
server,
port: address.port,
cleanup: async () => {
try {
server.close();
} catch (err) {
logger.debug("Error closing server:", err instanceof Error ? err.message : String(err));
}
},
getRelayError: () => relayErrorMsg.join(",")
});
});
});
};
export const withGatewayV2Proxy = async <T>(
callback: (port: number) => Promise<T>,
options: {
protocol: GatewayProxyProtocol;
relayHost: string;
gateway: { clientCertificate: string; clientPrivateKey: string; serverCertificateChain: string };
relay: { clientCertificate: string; clientPrivateKey: string; serverCertificateChain: string };
httpsAgent?: https.Agent;
}
): Promise<T> => {
const { protocol, relayHost, gateway, relay, httpsAgent } = options;
const { port, cleanup, getRelayError } = await setupRelayServer({
protocol,
relayHost,
gateway,
relay,
httpsAgent
});
try {
// Execute the callback with the allocated port
return await callback(port);
} catch (err) {
const relayErrorMessage = getRelayError();
if (relayErrorMessage) {
logger.error("Relay error:", relayErrorMessage);
}
logger.error("Gateway error:", err instanceof Error ? err.message : String(err));
let errorMessage = relayErrorMessage || (err instanceof Error ? err.message : String(err));
if (axios.isAxiosError(err) && (err.response?.data as { message?: string })?.message) {
errorMessage = (err.response?.data as { message: string }).message;
}
throw new BadRequestError({ message: errorMessage });
} finally {
// Ensure cleanup happens regardless of success or failure
await cleanup();
}
};
+2 -1
View File
@@ -6,7 +6,8 @@ export type TGatewayTlsOptions = { ca: string; cert: string; key: string };
export enum GatewayProxyProtocol {
Http = "http",
Tcp = "tcp"
Tcp = "tcp",
Ping = "ping"
}
export enum GatewayHttpProxyActions {
+4 -4
View File
@@ -250,12 +250,12 @@ export const ormify = <DbOps extends object, Tname extends keyof Tables>(
.returning("*");
if ($incr) {
Object.entries($incr).forEach(([incrementField, incrementValue]) => {
void query.increment(incrementField, incrementValue);
void query.increment(incrementField, incrementValue as number);
});
}
if ($decr) {
Object.entries($decr).forEach(([incrementField, incrementValue]) => {
void query.decrement(incrementField, incrementValue);
void query.decrement(incrementField, incrementValue as number);
});
}
const [docs] = await query;
@@ -273,12 +273,12 @@ export const ormify = <DbOps extends object, Tname extends keyof Tables>(
// increment and decrement operation in update
if ($incr) {
Object.entries($incr).forEach(([incrementField, incrementValue]) => {
void query.increment(incrementField, incrementValue);
void query.increment(incrementField, incrementValue as number);
});
}
if ($decr) {
Object.entries($decr).forEach(([incrementField, incrementValue]) => {
void query.increment(incrementField, incrementValue);
void query.decrement(incrementField, incrementValue as number);
});
}
return (await query) as Tables[Tname]["base"][];