From 71ff01daf2e429a00dc8b4922761b81aee174d62 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Thu, 11 Sep 2025 00:45:27 +0800 Subject: [PATCH] misc: addressed comments --- backend/src/ee/routes/v1/relay-router.ts | 5 ++- backend/src/ee/routes/v2/gateway-router.ts | 5 ++- .../ee/services/gateway-v2/gateway-v2-dal.ts | 21 ++++++++++- .../services/gateway-v2/gateway-v2-service.ts | 1 + .../src/ee/services/relay/relay-service.ts | 21 +++++++---- docs/cli/commands/gateway.mdx | 13 +++++++ docs/cli/commands/relay.mdx | 10 ++--- .../platform/gateways/gateway-security.mdx | 37 ++++++++++++------- .../platform/gateways/overview.mdx | 30 ++++++++++++--- 9 files changed, 105 insertions(+), 38 deletions(-) diff --git a/backend/src/ee/routes/v1/relay-router.ts b/backend/src/ee/routes/v1/relay-router.ts index 4cfa2c160..e20480088 100644 --- a/backend/src/ee/routes/v1/relay-router.ts +++ b/backend/src/ee/routes/v1/relay-router.ts @@ -4,6 +4,7 @@ import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { writeLimit } from "@app/server/config/rateLimiter"; +import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -19,7 +20,7 @@ export const registerRelayRouter = async (server: FastifyZodProvider) => { schema: { body: z.object({ host: z.string(), - name: z.string() + name: slugSchema({ min: 1, max: 32, field: "name" }) }), response: { 200: z.object({ @@ -69,7 +70,7 @@ export const registerRelayRouter = async (server: FastifyZodProvider) => { schema: { body: z.object({ host: z.string(), - name: z.string() + name: slugSchema({ min: 1, max: 32, field: "name" }) }), response: { 200: z.object({ diff --git a/backend/src/ee/routes/v2/gateway-router.ts b/backend/src/ee/routes/v2/gateway-router.ts index e4d3b3e88..a7e656a64 100644 --- a/backend/src/ee/routes/v2/gateway-router.ts +++ b/backend/src/ee/routes/v2/gateway-router.ts @@ -2,6 +2,7 @@ import z from "zod"; import { GatewaysV2Schema } from "@app/db/schemas"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; +import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; @@ -20,8 +21,8 @@ export const registerGatewayV2Router = async (server: FastifyZodProvider) => { url: "/", schema: { body: z.object({ - relayName: z.string(), - name: z.string() + relayName: slugSchema({ min: 1, max: 32, field: "relayName" }), + name: slugSchema({ min: 1, max: 32, field: "name" }) }), response: { 200: z.object({ diff --git a/backend/src/ee/services/gateway-v2/gateway-v2-dal.ts b/backend/src/ee/services/gateway-v2/gateway-v2-dal.ts index 6154d3357..da9d3c1ef 100644 --- a/backend/src/ee/services/gateway-v2/gateway-v2-dal.ts +++ b/backend/src/ee/services/gateway-v2/gateway-v2-dal.ts @@ -1,3 +1,5 @@ +import { Knex } from "knex"; + import { TDbClient } from "@app/db"; import { GatewaysV2Schema, TableName, TGatewaysV2 } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; @@ -10,7 +12,7 @@ export const gatewayV2DalFactory = (db: TDbClient) => { const find = async (filter: TFindFilter, { offset, limit, sort, tx }: TFindOpt = {}) => { try { - const query = (tx || db)(TableName.GatewayV2) + const query = (tx || db.replicaNode())(TableName.GatewayV2) // eslint-disable-next-line @typescript-eslint/no-misused-promises .where(buildFindFilter(filter, TableName.GatewayV2)) .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.GatewayV2}.identityId`) @@ -39,5 +41,20 @@ export const gatewayV2DalFactory = (db: TDbClient) => { } }; - return { ...orm, find }; + const findById = async (id: string, tx?: Knex) => { + try { + const doc = await (tx || db.replicaNode())(TableName.GatewayV2) + .join(TableName.Organization, `${TableName.GatewayV2}.orgId`, `${TableName.Organization}.id`) + .where(`${TableName.GatewayV2}.id`, id) + .select(selectAllTableCols(TableName.GatewayV2)) + .select(db.ref("name").withSchema(TableName.Organization).as("orgName")) + .first(); + + return doc; + } catch (error) { + throw new DatabaseError({ error, name: `${TableName.GatewayV2}: Find by id` }); + } + }; + + return { ...orm, find, findById }; }; diff --git a/backend/src/ee/services/gateway-v2/gateway-v2-service.ts b/backend/src/ee/services/gateway-v2/gateway-v2-service.ts index 64c325177..35f8cdde5 100644 --- a/backend/src/ee/services/gateway-v2/gateway-v2-service.ts +++ b/backend/src/ee/services/gateway-v2/gateway-v2-service.ts @@ -394,6 +394,7 @@ export const gatewayV2ServiceFactory = ({ const relayCredentials = await relayService.getCredentialsForClient({ relayId: gateway.relayId, orgId: gateway.orgId, + orgName: gateway.orgName, gatewayId }); diff --git a/backend/src/ee/services/relay/relay-service.ts b/backend/src/ee/services/relay/relay-service.ts index 6bc938c77..7f0d19e47 100644 --- a/backend/src/ee/services/relay/relay-service.ts +++ b/backend/src/ee/services/relay/relay-service.ts @@ -13,6 +13,7 @@ import { import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { KmsDataKey } from "@app/services/kms/kms-types"; +import { verifyHostInputValidity } from "../dynamic-secret/dynamic-secret-fns"; import { createSshCert, createSshKeyPair } from "../ssh/ssh-certificate-authority-fns"; import { SshCertType } from "../ssh/ssh-certificate-authority-types"; import { SshCertKeyAlgorithm } from "../ssh-certificate/ssh-certificate-types"; @@ -689,6 +690,7 @@ export const relayServiceFactory = ({ const $generateRelayClientCredentials = async ({ gatewayId, orgId, + orgName, relayPkiClientCaCertificate, relayPkiClientCaPrivateKey, relayPkiServerCaCertificate, @@ -696,6 +698,7 @@ export const relayServiceFactory = ({ }: { gatewayId: string; orgId: string; + orgName: string; relayPkiClientCaCertificate: Buffer; relayPkiClientCaPrivateKey: Buffer; relayPkiServerCaCertificate: Buffer; @@ -742,7 +745,7 @@ export const relayServiceFactory = ({ const clientCert = await x509.X509CertificateGenerator.create({ serialNumber: clientCertSerialNumber, - subject: `O=${orgId},OU=relay-client,CN=${gatewayId}`, + subject: `O=${orgName}-${orgId},OU=relay-client,CN=${gatewayId}`, issuer: relayClientCaCert.subject, notAfter: clientCertExpiration, notBefore: clientCertIssuedAt, @@ -833,10 +836,12 @@ export const relayServiceFactory = ({ const getCredentialsForClient = async ({ relayId, orgId, + orgName, gatewayId }: { relayId: string; orgId: string; + orgName: string; gatewayId: string; }) => { const relay = await relayDAL.findOne({ @@ -849,11 +854,14 @@ export const relayServiceFactory = ({ }); } + await verifyHostInputValidity(relay.host); + if (relay.orgId === null) { const instanceCAs = await $getInstanceCAs(); const relayCertificateCredentials = await $generateRelayClientCredentials({ gatewayId, orgId, + orgName, relayPkiClientCaCertificate: instanceCAs.instanceRelayPkiClientCaCertificate, relayPkiClientCaPrivateKey: instanceCAs.instanceRelayPkiClientCaPrivateKey, relayPkiServerCaCertificate: instanceCAs.instanceRelayPkiServerCaCertificate, @@ -870,6 +878,7 @@ export const relayServiceFactory = ({ const relayCertificateCredentials = await $generateRelayClientCredentials({ gatewayId, orgId, + orgName, relayPkiClientCaCertificate: orgCAs.relayPkiClientCaCertificate, relayPkiClientCaPrivateKey: orgCAs.relayPkiClientCaPrivateKey, relayPkiServerCaCertificate: orgCAs.relayPkiServerCaCertificate, @@ -896,6 +905,8 @@ export const relayServiceFactory = ({ let relay: TRelays; const isOrgRelay = identityId && orgId; + await verifyHostInputValidity(host); + if (isOrgRelay) { relay = await relayDAL.transaction(async (tx) => { const existingRelay = await relayDAL.findOne( @@ -907,9 +918,7 @@ export const relayServiceFactory = ({ ); if (existingRelay && (existingRelay.host !== host || existingRelay.name !== name)) { - throw new BadRequestError({ - message: "Org relay with this machine identity already exists." - }); + return relayDAL.updateById(existingRelay.id, { host, name }, tx); } if (!existingRelay) { @@ -937,9 +946,7 @@ export const relayServiceFactory = ({ ); if (existingRelay && existingRelay.host !== host) { - throw new BadRequestError({ - message: "Instance relay with this name already exists with a different host" - }); + return relayDAL.updateById(existingRelay.id, { host }, tx); } if (!existingRelay) { diff --git a/docs/cli/commands/gateway.mdx b/docs/cli/commands/gateway.mdx index 1d66281b8..99d0e1086 100644 --- a/docs/cli/commands/gateway.mdx +++ b/docs/cli/commands/gateway.mdx @@ -22,6 +22,13 @@ The Infisical gateway provides secure access to private resources using modern T The gateway system uses SSH reverse tunnels over TCP, eliminating firewall complexity and providing excellent performance for enterprise environments. + +**Deprecation and Migration Notice:** The legacy `infisical gateway` command (v1) will be removed in a future release. Please migrate to `infisical gateway start` (Gateway v2). + +If you are moving from Gateway v1 to Gateway v2, this is NOT a drop-in switch. Gateway v2 creates new gateway instances with new gateway IDs. You must update any existing resources that reference gateway IDs (for example: dynamic secret configs, app connections, or other gateway-bound resources) to point to the new Gateway v2 gateway ID. Until you update those references, traffic will continue to target the old v1 gateway. + + + ## Subcommands & flags @@ -361,6 +368,9 @@ sudo systemctl disable infisical-gateway # Disable auto-start on boot **This command is deprecated and will be removed in a future release.** Please migrate to `infisical gateway start` for the new TCP-based SSH tunnel architecture. + +**Migration required:** If you are currently using Gateway v1 (via `infisical gateway`), moving to Gateway v2 is not in-place. Gateway v2 provisions new gateway instances with new gateway IDs. Update any resources that reference a gateway ID (for example: dynamic secret configs, app connections, or other gateway-bound resources) to use the new Gateway v2 gateway ID. Until you update those references, traffic will continue to target the old v1 gateway. + Run the legacy Infisical gateway in the foreground. The gateway will connect to the relay service and maintain a persistent connection. @@ -585,6 +595,9 @@ The Infisical CLI supports multiple authentication methods. Below are the availa **This command is deprecated and will be removed in a future release.** Please migrate to `infisical gateway systemd install` for the new TCP-based SSH tunnel architecture with enhanced security and better performance. + +**Migration required:** If you previously installed Gateway v1 via `infisical gateway install`, moving to Gateway v2 is not in-place. Gateway v2 provisions new gateway instances with new gateway IDs. Update any resources that reference a gateway ID (for example: dynamic secret configs, app connections, or other gateway-bound resources) to use the new Gateway v2 gateway ID. Until you update those references, traffic will continue to target the old v1 gateway. + Install and enable the legacy gateway as a systemd service. This command must be run with sudo on Linux. diff --git a/docs/cli/commands/relay.mdx b/docs/cli/commands/relay.mdx index 7fadfa8d2..46a061da3 100644 --- a/docs/cli/commands/relay.mdx +++ b/docs/cli/commands/relay.mdx @@ -1,6 +1,6 @@ --- title: "infisical relay" -description: "Relay-related commands for Infisical including proxy components" +description: "Relay-related commands for Infisical" --- @@ -33,7 +33,7 @@ infisical relay start --type= --host= --name= --auth-method= The type of relay to run. Must be either 'instance' or 'org'. - - **`instance`**: Shared relay server that can be used by all organizations on your Infisical instance. Set up by the instance administrator. Uses `INFISICAL_PROXY_AUTH_SECRET` environment variable for authentication, which must be configured by the instance admin. + - **`instance`**: Shared relay server that can be used by all organizations on your Infisical instance. Set up by the instance administrator. Uses `INFISICAL_RELAY_AUTH_SECRET` environment variable for authentication, which must be configured by the instance admin. - **`org`**: Dedicated relay server that individual organizations deploy and manage in their own infrastructure. Provides enhanced security, custom geographic placement, and compliance benefits. Uses standard Infisical authentication methods. ```bash @@ -41,7 +41,7 @@ infisical relay start --type= --host= --name= --auth-method= infisical relay start --type=instance --host=10.0.1.50 --name=shared-relay + INFISICAL_RELAY_AUTH_SECRET= infisical relay start --type=instance --host=10.0.1.50 --name=shared-relay ``` @@ -75,14 +75,14 @@ infisical relay start --type= --host= --name= --auth-method= --client-secret= # Instance relay (configured by instance admin) -INFISICAL_PROXY_AUTH_SECRET= infisical relay start --type=instance --host=10.0.1.50 --name=shared-relay +INFISICAL_RELAY_AUTH_SECRET= infisical relay start --type=instance --host=10.0.1.50 --name=shared-relay ``` ### Authentication Methods diff --git a/docs/documentation/platform/gateways/gateway-security.mdx b/docs/documentation/platform/gateways/gateway-security.mdx index 70671d164..6962c627e 100644 --- a/docs/documentation/platform/gateways/gateway-security.mdx +++ b/docs/documentation/platform/gateways/gateway-security.mdx @@ -68,26 +68,35 @@ Gateway ↔ Relay Server communication uses SSH certificate authentication: - Gateway validates certificate against appropriate SSH Server CA - Ensures gateway connects to legitimate relay infrastructure -### 3. Application Traffic Security +### 3. Platform-to-Gateway Direct Connection -End-to-end encryption for application data: +The platform establishes secure direct connections with gateways through a **TLS-pinned tunnel** mechanism: -1. **mTLS Layer**: +1. **TLS-Pinned Tunnel Establishment**: - - Infisical platform establishes mTLS connections directly with gateways - - Uses Organization Gateway certificates for authentication - - Application traffic is encrypted end-to-end between platform and gateway + - Gateway initiates outbound connection to platform through SSH reverse tunnel + - Platform establishes direct mTLS connection with gateway using Organization Gateway certificates + - TLS certificate pinning ensures the connection is bound to the specific gateway identity + - No inbound connections required - all communication flows through the outbound tunnel -2. **SSH Tunnel Layer**: +2. **Connection Flow**: - - mTLS-encrypted application traffic travels through SSH reverse tunnels - - Creates double encryption: mTLS payload within SSH tunnel - - Relay servers cannot decrypt either encryption layer + ``` + Platform ←→ [SSH Reverse Tunnel] ←→ Gateway + ``` -3. **Traffic Isolation**: - - Each gateway maintains separate SSH tunnels - - Organization's private keys never leave their environment - - Complete cryptographic isolation between organizations + - Gateway maintains persistent outbound SSH tunnel to relay server + - Platform connects directly to gateway through this tunnel + - TLS handshake occurs over the SSH tunnel, establishing mTLS connection + - Application traffic flows through the TLS-pinned tunnel + +3. **Security Benefits**: + + - **No inbound connections**: Gateway never needs to accept incoming connections + - **Certificate-based authentication**: Uses Organization Gateway certificates for mutual TLS + - **Double encryption**: TLS traffic within SSH tunnel provides layered security + - **Relay server isolation**: Relay cannot decrypt either TLS or application data + - **Tenant isolation**: Each organization's traffic flows through separate authenticated channels ## Tenant Isolation diff --git a/docs/documentation/platform/gateways/overview.mdx b/docs/documentation/platform/gateways/overview.mdx index 35274fd8f..b8ea0102a 100644 --- a/docs/documentation/platform/gateways/overview.mdx +++ b/docs/documentation/platform/gateways/overview.mdx @@ -102,6 +102,13 @@ Once authenticated, the Gateway establishes an SSH reverse tunnel to the specifi For production deployments on Linux, install the Gateway as a systemd service: + + + **Gateway v2:** The `infisical gateway systemd install` command deploys the new Gateway v2 component. + + If you are migrating from Gateway v1 (legacy `infisical gateway install` command), this is not in-place. Gateway v2 provisions new gateway instances with new gateway IDs. Update any resources that reference a gateway ID (for example: dynamic secret configs, app connections, or other gateway-bound resources) to use the new Gateway v2 gateway ID. + + ```bash sudo infisical gateway systemd install --token --domain --name --relay sudo systemctl start infisical-gateway @@ -369,8 +376,13 @@ Once authenticated, the Gateway establishes an SSH reverse tunnel to the specifi - ### Install the Infisical Gateway Helm Chart + + **Version mapping:** Helm chart versions `>= 1.0.0` contain the new Gateway v2 component. Helm chart versions `<= 0.0.5` contain the legacy Gateway v1 component. + + If you are moving from Gateway v1 (chart `<= 0.0.5`) to Gateway v2 (chart `>= 1.0.0`), this is not in-place. Gateway v2 provisions new gateway instances with new gateway IDs. Update any resources that reference a gateway ID (for example: dynamic secret configs, app connections, or other gateway-bound resources) to use the new Gateway v2 gateway ID. + + ```bash helm install infisical-gateway infisical-helm-charts/infisical-gateway ``` @@ -385,11 +397,17 @@ Once authenticated, the Gateway establishes an SSH reverse tunnel to the specifi You should see the following output which indicates the gateway is running as expected. ```bash $ kubectl logs deployment/infisical-gateway - INF Starting gateway - INF Starting gateway certificate renewal goroutine - INF Successfully registered gateway and received certificates - INF Connecting to relay server infisical-start on 152.42.218.156:2222... - INF Relay connection established for gateway + 12:43AM INF Starting gateway + 12:43AM INF Starting gateway certificate renewal goroutine + 12:43AM INF Successfully registered gateway and received certificates + 12:43AM INF Connecting to relay server infisical-start on 152.42.218.156:2222... + 12:43AM INF Relay connection established for gateway + 12:43AM INF Received incoming connection, starting TLS handshake + 12:43AM INF TLS handshake completed successfully + 12:43AM INF Negotiated ALPN protocol: infisical-ping + 12:43AM INF Starting ping handler + 12:43AM INF Ping handler completed + 12:43AM INF Gateway is reachable by Infisical ```