mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #4462 from Infisical/feat/gateway-v2
feat: gateway v2
This commit is contained in:
4
backend/src/@types/fastify.d.ts
vendored
4
backend/src/@types/fastify.d.ts
vendored
@@ -16,6 +16,7 @@ import { TEventBusService } from "@app/ee/services/event/event-bus-service";
|
||||
import { TServerSentEventsService } from "@app/ee/services/event/event-sse-service";
|
||||
import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||
import { TGithubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service";
|
||||
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
|
||||
import { TIdentityAuthTemplateServiceFactory } from "@app/ee/services/identity-auth-template";
|
||||
@@ -32,6 +33,7 @@ import { TPitServiceFactory } from "@app/ee/services/pit/pit-service";
|
||||
import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-types";
|
||||
import { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
|
||||
import { RateLimitConfiguration, TRateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-types";
|
||||
import { TRelayServiceFactory } from "@app/ee/services/relay/relay-service";
|
||||
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-types";
|
||||
import { TScimServiceFactory } from "@app/ee/services/scim/scim-types";
|
||||
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||
@@ -296,6 +298,8 @@ declare module "fastify" {
|
||||
secretRotationV2: TSecretRotationV2ServiceFactory;
|
||||
microsoftTeams: TMicrosoftTeamsServiceFactory;
|
||||
assumePrivileges: TAssumePrivilegeServiceFactory;
|
||||
relay: TRelayServiceFactory;
|
||||
gatewayV2: TGatewayV2ServiceFactory;
|
||||
githubOrgSync: TGithubOrgSyncServiceFactory;
|
||||
folderCommit: TFolderCommitServiceFactory;
|
||||
pit: TPitServiceFactory;
|
||||
|
||||
32
backend/src/@types/knex.d.ts
vendored
32
backend/src/@types/knex.d.ts
vendored
@@ -101,6 +101,9 @@ import {
|
||||
TGateways,
|
||||
TGatewaysInsert,
|
||||
TGatewaysUpdate,
|
||||
TGatewaysV2,
|
||||
TGatewaysV2Insert,
|
||||
TGatewaysV2Update,
|
||||
TGitAppInstallSessions,
|
||||
TGitAppInstallSessionsInsert,
|
||||
TGitAppInstallSessionsUpdate,
|
||||
@@ -179,6 +182,9 @@ import {
|
||||
TIncidentContacts,
|
||||
TIncidentContactsInsert,
|
||||
TIncidentContactsUpdate,
|
||||
TInstanceRelayConfig,
|
||||
TInstanceRelayConfigInsert,
|
||||
TInstanceRelayConfigUpdate,
|
||||
TIntegrationAuths,
|
||||
TIntegrationAuthsInsert,
|
||||
TIntegrationAuthsUpdate,
|
||||
@@ -233,9 +239,15 @@ import {
|
||||
TOrgGatewayConfig,
|
||||
TOrgGatewayConfigInsert,
|
||||
TOrgGatewayConfigUpdate,
|
||||
TOrgGatewayConfigV2,
|
||||
TOrgGatewayConfigV2Insert,
|
||||
TOrgGatewayConfigV2Update,
|
||||
TOrgMemberships,
|
||||
TOrgMembershipsInsert,
|
||||
TOrgMembershipsUpdate,
|
||||
TOrgRelayConfig,
|
||||
TOrgRelayConfigInsert,
|
||||
TOrgRelayConfigUpdate,
|
||||
TOrgRoles,
|
||||
TOrgRolesInsert,
|
||||
TOrgRolesUpdate,
|
||||
@@ -293,6 +305,9 @@ import {
|
||||
TRateLimit,
|
||||
TRateLimitInsert,
|
||||
TRateLimitUpdate,
|
||||
TRelays,
|
||||
TRelaysInsert,
|
||||
TRelaysUpdate,
|
||||
TResourceMetadata,
|
||||
TResourceMetadataInsert,
|
||||
TResourceMetadataUpdate,
|
||||
@@ -1241,6 +1256,17 @@ declare module "knex/types/tables" {
|
||||
TSecretScanningResourcesInsert,
|
||||
TSecretScanningResourcesUpdate
|
||||
>;
|
||||
[TableName.InstanceRelayConfig]: KnexOriginal.CompositeTableType<
|
||||
TInstanceRelayConfig,
|
||||
TInstanceRelayConfigInsert,
|
||||
TInstanceRelayConfigUpdate
|
||||
>;
|
||||
[TableName.OrgRelayConfig]: KnexOriginal.CompositeTableType<
|
||||
TOrgRelayConfig,
|
||||
TOrgRelayConfigInsert,
|
||||
TOrgRelayConfigUpdate
|
||||
>;
|
||||
[TableName.Relay]: KnexOriginal.CompositeTableType<TRelays, TRelaysInsert, TRelaysUpdate>;
|
||||
[TableName.SecretScanningScan]: KnexOriginal.CompositeTableType<
|
||||
TSecretScanningScans,
|
||||
TSecretScanningScansInsert,
|
||||
@@ -1262,6 +1288,12 @@ declare module "knex/types/tables" {
|
||||
TRemindersRecipientsInsert,
|
||||
TRemindersRecipientsUpdate
|
||||
>;
|
||||
[TableName.OrgGatewayConfigV2]: KnexOriginal.CompositeTableType<
|
||||
TOrgGatewayConfigV2,
|
||||
TOrgGatewayConfigV2Insert,
|
||||
TOrgGatewayConfigV2Update
|
||||
>;
|
||||
[TableName.GatewayV2]: KnexOriginal.CompositeTableType<TGatewaysV2, TGatewaysV2Insert, TGatewaysV2Update>;
|
||||
[TableName.UserNotifications]: KnexOriginal.CompositeTableType<
|
||||
TUserNotifications,
|
||||
TUserNotificationsInsert,
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (!(await knex.schema.hasTable(TableName.InstanceRelayConfig))) {
|
||||
await knex.schema.createTable(TableName.InstanceRelayConfig, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.timestamps(true, true, true);
|
||||
|
||||
// Root CA for relay PKI
|
||||
t.binary("encryptedRootRelayPkiCaPrivateKey").notNullable();
|
||||
t.binary("encryptedRootRelayPkiCaCertificate").notNullable();
|
||||
|
||||
// Instance CA for relay PKI
|
||||
t.binary("encryptedInstanceRelayPkiCaPrivateKey").notNullable();
|
||||
t.binary("encryptedInstanceRelayPkiCaCertificate").notNullable();
|
||||
t.binary("encryptedInstanceRelayPkiCaCertificateChain").notNullable();
|
||||
|
||||
// Instance client/server intermediates for relay PKI
|
||||
t.binary("encryptedInstanceRelayPkiClientCaPrivateKey").notNullable();
|
||||
t.binary("encryptedInstanceRelayPkiClientCaCertificate").notNullable();
|
||||
t.binary("encryptedInstanceRelayPkiClientCaCertificateChain").notNullable();
|
||||
t.binary("encryptedInstanceRelayPkiServerCaPrivateKey").notNullable();
|
||||
t.binary("encryptedInstanceRelayPkiServerCaCertificate").notNullable();
|
||||
t.binary("encryptedInstanceRelayPkiServerCaCertificateChain").notNullable();
|
||||
|
||||
// Org Parent CAs for relay
|
||||
t.binary("encryptedOrgRelayPkiCaPrivateKey").notNullable();
|
||||
t.binary("encryptedOrgRelayPkiCaCertificate").notNullable();
|
||||
t.binary("encryptedOrgRelayPkiCaCertificateChain").notNullable();
|
||||
|
||||
// Instance SSH CAs for relay
|
||||
t.binary("encryptedInstanceRelaySshClientCaPrivateKey").notNullable();
|
||||
t.binary("encryptedInstanceRelaySshClientCaPublicKey").notNullable();
|
||||
t.binary("encryptedInstanceRelaySshServerCaPrivateKey").notNullable();
|
||||
t.binary("encryptedInstanceRelaySshServerCaPublicKey").notNullable();
|
||||
});
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.InstanceRelayConfig);
|
||||
}
|
||||
|
||||
// Org-level relay configuration (one-to-one with organization)
|
||||
if (!(await knex.schema.hasTable(TableName.OrgRelayConfig))) {
|
||||
await knex.schema.createTable(TableName.OrgRelayConfig, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.timestamps(true, true, true);
|
||||
|
||||
t.uuid("orgId").notNullable().unique();
|
||||
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||
|
||||
// Org-scoped relay PKI (client + server)
|
||||
t.binary("encryptedRelayPkiClientCaPrivateKey").notNullable();
|
||||
t.binary("encryptedRelayPkiClientCaCertificate").notNullable();
|
||||
t.binary("encryptedRelayPkiClientCaCertificateChain").notNullable();
|
||||
t.binary("encryptedRelayPkiServerCaPrivateKey").notNullable();
|
||||
t.binary("encryptedRelayPkiServerCaCertificate").notNullable();
|
||||
t.binary("encryptedRelayPkiServerCaCertificateChain").notNullable();
|
||||
|
||||
// Org-scoped relay SSH (client + server)
|
||||
t.binary("encryptedRelaySshClientCaPrivateKey").notNullable();
|
||||
t.binary("encryptedRelaySshClientCaPublicKey").notNullable();
|
||||
t.binary("encryptedRelaySshServerCaPrivateKey").notNullable();
|
||||
t.binary("encryptedRelaySshServerCaPublicKey").notNullable();
|
||||
});
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.OrgRelayConfig);
|
||||
}
|
||||
|
||||
if (!(await knex.schema.hasTable(TableName.OrgGatewayConfigV2))) {
|
||||
await knex.schema.createTable(TableName.OrgGatewayConfigV2, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.uuid("orgId").notNullable().unique();
|
||||
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||
t.timestamps(true, true, true);
|
||||
t.binary("encryptedRootGatewayCaPrivateKey").notNullable();
|
||||
t.binary("encryptedRootGatewayCaCertificate").notNullable();
|
||||
t.binary("encryptedGatewayServerCaPrivateKey").notNullable();
|
||||
t.binary("encryptedGatewayServerCaCertificate").notNullable();
|
||||
t.binary("encryptedGatewayServerCaCertificateChain").notNullable();
|
||||
t.binary("encryptedGatewayClientCaPrivateKey").notNullable();
|
||||
t.binary("encryptedGatewayClientCaCertificate").notNullable();
|
||||
t.binary("encryptedGatewayClientCaCertificateChain").notNullable();
|
||||
});
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.OrgGatewayConfigV2);
|
||||
}
|
||||
|
||||
if (!(await knex.schema.hasTable(TableName.Relay))) {
|
||||
await knex.schema.createTable(TableName.Relay, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.timestamps(true, true, true);
|
||||
|
||||
t.uuid("orgId");
|
||||
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||
|
||||
t.uuid("identityId");
|
||||
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
|
||||
|
||||
t.string("name").notNullable();
|
||||
t.string("host").notNullable();
|
||||
|
||||
t.unique(["orgId", "name"]);
|
||||
});
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.Relay);
|
||||
}
|
||||
|
||||
if (!(await knex.schema.hasTable(TableName.GatewayV2))) {
|
||||
await knex.schema.createTable(TableName.GatewayV2, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.timestamps(true, true, true);
|
||||
|
||||
t.uuid("orgId").notNullable();
|
||||
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||
|
||||
t.uuid("identityId").notNullable().unique();
|
||||
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
|
||||
|
||||
t.uuid("relayId");
|
||||
t.foreign("relayId").references("id").inTable(TableName.Relay).onDelete("SET NULL");
|
||||
|
||||
t.string("name").notNullable();
|
||||
|
||||
t.unique(["orgId", "name"]);
|
||||
|
||||
t.dateTime("heartbeat");
|
||||
});
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.GatewayV2);
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await dropOnUpdateTrigger(knex, TableName.OrgRelayConfig);
|
||||
await knex.schema.dropTableIfExists(TableName.OrgRelayConfig);
|
||||
|
||||
await dropOnUpdateTrigger(knex, TableName.InstanceRelayConfig);
|
||||
await knex.schema.dropTableIfExists(TableName.InstanceRelayConfig);
|
||||
|
||||
await dropOnUpdateTrigger(knex, TableName.OrgGatewayConfigV2);
|
||||
await knex.schema.dropTableIfExists(TableName.OrgGatewayConfigV2);
|
||||
|
||||
await dropOnUpdateTrigger(knex, TableName.GatewayV2);
|
||||
await knex.schema.dropTableIfExists(TableName.GatewayV2);
|
||||
|
||||
await dropOnUpdateTrigger(knex, TableName.Relay);
|
||||
await knex.schema.dropTableIfExists(TableName.Relay);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (!(await knex.schema.hasColumn(TableName.DynamicSecret, "gatewayV2Id"))) {
|
||||
await knex.schema.alterTable(TableName.DynamicSecret, (table) => {
|
||||
table.uuid("gatewayV2Id");
|
||||
table.foreign("gatewayV2Id").references("id").inTable(TableName.GatewayV2).onDelete("SET NULL");
|
||||
});
|
||||
}
|
||||
|
||||
if (!(await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "gatewayV2Id"))) {
|
||||
await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (table) => {
|
||||
table.uuid("gatewayV2Id");
|
||||
table.foreign("gatewayV2Id").references("id").inTable(TableName.GatewayV2).onDelete("SET NULL");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasColumn(TableName.DynamicSecret, "gatewayV2Id")) {
|
||||
await knex.schema.alterTable(TableName.DynamicSecret, (table) => {
|
||||
table.dropColumn("gatewayV2Id");
|
||||
});
|
||||
}
|
||||
|
||||
if (await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "gatewayV2Id")) {
|
||||
await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (table) => {
|
||||
table.dropColumn("gatewayV2Id");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@ export const DynamicSecretsSchema = z.object({
|
||||
encryptedInput: zodBuffer,
|
||||
projectGatewayId: z.string().uuid().nullable().optional(),
|
||||
gatewayId: z.string().uuid().nullable().optional(),
|
||||
usernameTemplate: z.string().nullable().optional()
|
||||
usernameTemplate: z.string().nullable().optional(),
|
||||
gatewayV2Id: z.string().uuid().nullable().optional()
|
||||
});
|
||||
|
||||
export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>;
|
||||
|
||||
23
backend/src/db/schemas/gateways-v2.ts
Normal file
23
backend/src/db/schemas/gateways-v2.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// Code generated by automation script, DO NOT EDIT.
|
||||
// Automated by pulling database and generating zod schema
|
||||
// To update. Just run npm run generate:schema
|
||||
// Written by akhilmhdh.
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
import { TImmutableDBKeys } from "./models";
|
||||
|
||||
export const GatewaysV2Schema = z.object({
|
||||
id: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
orgId: z.string().uuid(),
|
||||
identityId: z.string().uuid(),
|
||||
relayId: z.string().uuid().nullable().optional(),
|
||||
name: z.string(),
|
||||
heartbeat: z.date().nullable().optional()
|
||||
});
|
||||
|
||||
export type TGatewaysV2 = z.infer<typeof GatewaysV2Schema>;
|
||||
export type TGatewaysV2Insert = Omit<z.input<typeof GatewaysV2Schema>, TImmutableDBKeys>;
|
||||
export type TGatewaysV2Update = Partial<Omit<z.input<typeof GatewaysV2Schema>, TImmutableDBKeys>>;
|
||||
@@ -32,7 +32,8 @@ export const IdentityKubernetesAuthsSchema = z.object({
|
||||
encryptedKubernetesCaCertificate: zodBuffer.nullable().optional(),
|
||||
gatewayId: z.string().uuid().nullable().optional(),
|
||||
accessTokenPeriod: z.coerce.number().default(0),
|
||||
tokenReviewMode: z.string().default("api")
|
||||
tokenReviewMode: z.string().default("api"),
|
||||
gatewayV2Id: z.string().uuid().nullable().optional()
|
||||
});
|
||||
|
||||
export type TIdentityKubernetesAuths = z.infer<typeof IdentityKubernetesAuthsSchema>;
|
||||
|
||||
@@ -31,6 +31,7 @@ export * from "./folder-commits";
|
||||
export * from "./folder-tree-checkpoint-resources";
|
||||
export * from "./folder-tree-checkpoints";
|
||||
export * from "./gateways";
|
||||
export * from "./gateways-v2";
|
||||
export * from "./git-app-install-sessions";
|
||||
export * from "./git-app-org";
|
||||
export * from "./github-org-sync-configs";
|
||||
@@ -57,6 +58,7 @@ export * from "./identity-token-auths";
|
||||
export * from "./identity-ua-client-secrets";
|
||||
export * from "./identity-universal-auths";
|
||||
export * from "./incident-contacts";
|
||||
export * from "./instance-relay-config";
|
||||
export * from "./integration-auths";
|
||||
export * from "./integrations";
|
||||
export * from "./internal-certificate-authorities";
|
||||
@@ -76,7 +78,9 @@ export * from "./models";
|
||||
export * from "./oidc-configs";
|
||||
export * from "./org-bots";
|
||||
export * from "./org-gateway-config";
|
||||
export * from "./org-gateway-config-v2";
|
||||
export * from "./org-memberships";
|
||||
export * from "./org-relay-config";
|
||||
export * from "./org-roles";
|
||||
export * from "./organizations";
|
||||
export * from "./pki-alerts";
|
||||
@@ -97,6 +101,7 @@ export * from "./project-user-additional-privilege";
|
||||
export * from "./project-user-membership-roles";
|
||||
export * from "./projects";
|
||||
export * from "./rate-limit";
|
||||
export * from "./relays";
|
||||
export * from "./resource-metadata";
|
||||
export * from "./saml-configs";
|
||||
export * from "./scim-tokens";
|
||||
|
||||
38
backend/src/db/schemas/instance-relay-config.ts
Normal file
38
backend/src/db/schemas/instance-relay-config.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// Code generated by automation script, DO NOT EDIT.
|
||||
// Automated by pulling database and generating zod schema
|
||||
// To update. Just run npm run generate:schema
|
||||
// Written by akhilmhdh.
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
import { zodBuffer } from "@app/lib/zod";
|
||||
|
||||
import { TImmutableDBKeys } from "./models";
|
||||
|
||||
export const InstanceRelayConfigSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
encryptedRootRelayPkiCaPrivateKey: zodBuffer,
|
||||
encryptedRootRelayPkiCaCertificate: zodBuffer,
|
||||
encryptedInstanceRelayPkiCaPrivateKey: zodBuffer,
|
||||
encryptedInstanceRelayPkiCaCertificate: zodBuffer,
|
||||
encryptedInstanceRelayPkiCaCertificateChain: zodBuffer,
|
||||
encryptedInstanceRelayPkiClientCaPrivateKey: zodBuffer,
|
||||
encryptedInstanceRelayPkiClientCaCertificate: zodBuffer,
|
||||
encryptedInstanceRelayPkiClientCaCertificateChain: zodBuffer,
|
||||
encryptedInstanceRelayPkiServerCaPrivateKey: zodBuffer,
|
||||
encryptedInstanceRelayPkiServerCaCertificate: zodBuffer,
|
||||
encryptedInstanceRelayPkiServerCaCertificateChain: zodBuffer,
|
||||
encryptedOrgRelayPkiCaPrivateKey: zodBuffer,
|
||||
encryptedOrgRelayPkiCaCertificate: zodBuffer,
|
||||
encryptedOrgRelayPkiCaCertificateChain: zodBuffer,
|
||||
encryptedInstanceRelaySshClientCaPrivateKey: zodBuffer,
|
||||
encryptedInstanceRelaySshClientCaPublicKey: zodBuffer,
|
||||
encryptedInstanceRelaySshServerCaPrivateKey: zodBuffer,
|
||||
encryptedInstanceRelaySshServerCaPublicKey: zodBuffer
|
||||
});
|
||||
|
||||
export type TInstanceRelayConfig = z.infer<typeof InstanceRelayConfigSchema>;
|
||||
export type TInstanceRelayConfigInsert = Omit<z.input<typeof InstanceRelayConfigSchema>, TImmutableDBKeys>;
|
||||
export type TInstanceRelayConfigUpdate = Partial<Omit<z.input<typeof InstanceRelayConfigSchema>, TImmutableDBKeys>>;
|
||||
@@ -180,6 +180,14 @@ export enum TableName {
|
||||
// reminders
|
||||
Reminder = "reminders",
|
||||
ReminderRecipient = "reminders_recipients",
|
||||
|
||||
// gateway v2
|
||||
InstanceRelayConfig = "instance_relay_config",
|
||||
OrgRelayConfig = "org_relay_config",
|
||||
OrgGatewayConfigV2 = "org_gateway_config_v2",
|
||||
Relay = "relays",
|
||||
GatewayV2 = "gateways_v2",
|
||||
|
||||
KeyValueStore = "key_value_store"
|
||||
}
|
||||
|
||||
|
||||
29
backend/src/db/schemas/org-gateway-config-v2.ts
Normal file
29
backend/src/db/schemas/org-gateway-config-v2.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
// Code generated by automation script, DO NOT EDIT.
|
||||
// Automated by pulling database and generating zod schema
|
||||
// To update. Just run npm run generate:schema
|
||||
// Written by akhilmhdh.
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
import { zodBuffer } from "@app/lib/zod";
|
||||
|
||||
import { TImmutableDBKeys } from "./models";
|
||||
|
||||
export const OrgGatewayConfigV2Schema = z.object({
|
||||
id: z.string().uuid(),
|
||||
orgId: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
encryptedRootGatewayCaPrivateKey: zodBuffer,
|
||||
encryptedRootGatewayCaCertificate: zodBuffer,
|
||||
encryptedGatewayServerCaPrivateKey: zodBuffer,
|
||||
encryptedGatewayServerCaCertificate: zodBuffer,
|
||||
encryptedGatewayServerCaCertificateChain: zodBuffer,
|
||||
encryptedGatewayClientCaPrivateKey: zodBuffer,
|
||||
encryptedGatewayClientCaCertificate: zodBuffer,
|
||||
encryptedGatewayClientCaCertificateChain: zodBuffer
|
||||
});
|
||||
|
||||
export type TOrgGatewayConfigV2 = z.infer<typeof OrgGatewayConfigV2Schema>;
|
||||
export type TOrgGatewayConfigV2Insert = Omit<z.input<typeof OrgGatewayConfigV2Schema>, TImmutableDBKeys>;
|
||||
export type TOrgGatewayConfigV2Update = Partial<Omit<z.input<typeof OrgGatewayConfigV2Schema>, TImmutableDBKeys>>;
|
||||
31
backend/src/db/schemas/org-relay-config.ts
Normal file
31
backend/src/db/schemas/org-relay-config.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
// Code generated by automation script, DO NOT EDIT.
|
||||
// Automated by pulling database and generating zod schema
|
||||
// To update. Just run npm run generate:schema
|
||||
// Written by akhilmhdh.
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
import { zodBuffer } from "@app/lib/zod";
|
||||
|
||||
import { TImmutableDBKeys } from "./models";
|
||||
|
||||
export const OrgRelayConfigSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
orgId: z.string().uuid(),
|
||||
encryptedRelayPkiClientCaPrivateKey: zodBuffer,
|
||||
encryptedRelayPkiClientCaCertificate: zodBuffer,
|
||||
encryptedRelayPkiClientCaCertificateChain: zodBuffer,
|
||||
encryptedRelayPkiServerCaPrivateKey: zodBuffer,
|
||||
encryptedRelayPkiServerCaCertificate: zodBuffer,
|
||||
encryptedRelayPkiServerCaCertificateChain: zodBuffer,
|
||||
encryptedRelaySshClientCaPrivateKey: zodBuffer,
|
||||
encryptedRelaySshClientCaPublicKey: zodBuffer,
|
||||
encryptedRelaySshServerCaPrivateKey: zodBuffer,
|
||||
encryptedRelaySshServerCaPublicKey: zodBuffer
|
||||
});
|
||||
|
||||
export type TOrgRelayConfig = z.infer<typeof OrgRelayConfigSchema>;
|
||||
export type TOrgRelayConfigInsert = Omit<z.input<typeof OrgRelayConfigSchema>, TImmutableDBKeys>;
|
||||
export type TOrgRelayConfigUpdate = Partial<Omit<z.input<typeof OrgRelayConfigSchema>, TImmutableDBKeys>>;
|
||||
22
backend/src/db/schemas/relays.ts
Normal file
22
backend/src/db/schemas/relays.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
// Code generated by automation script, DO NOT EDIT.
|
||||
// Automated by pulling database and generating zod schema
|
||||
// To update. Just run npm run generate:schema
|
||||
// Written by akhilmhdh.
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
import { TImmutableDBKeys } from "./models";
|
||||
|
||||
export const RelaysSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
orgId: z.string().uuid().nullable().optional(),
|
||||
identityId: z.string().uuid().nullable().optional(),
|
||||
name: z.string(),
|
||||
host: z.string()
|
||||
});
|
||||
|
||||
export type TRelays = z.infer<typeof RelaysSchema>;
|
||||
export type TRelaysInsert = Omit<z.input<typeof RelaysSchema>, TImmutableDBKeys>;
|
||||
export type TRelaysUpdate = Partial<Omit<z.input<typeof RelaysSchema>, TImmutableDBKeys>>;
|
||||
@@ -24,6 +24,7 @@ import { registerPITRouter } from "./pit-router";
|
||||
import { registerProjectRoleRouter } from "./project-role-router";
|
||||
import { registerProjectRouter } from "./project-router";
|
||||
import { registerRateLimitRouter } from "./rate-limit-router";
|
||||
import { registerRelayRouter } from "./relay-router";
|
||||
import { registerSamlRouter } from "./saml-router";
|
||||
import { registerScimRouter } from "./scim-router";
|
||||
import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-router";
|
||||
@@ -79,6 +80,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
||||
);
|
||||
|
||||
await server.register(registerGatewayRouter, { prefix: "/gateways" });
|
||||
await server.register(registerRelayRouter, { prefix: "/relays" });
|
||||
await server.register(registerGithubOrgSyncRouter, { prefix: "/github-org-sync-config" });
|
||||
|
||||
await server.register(
|
||||
|
||||
103
backend/src/ee/routes/v1/relay-router.ts
Normal file
103
backend/src/ee/routes/v1/relay-router.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { z } from "zod";
|
||||
|
||||
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";
|
||||
|
||||
export const registerRelayRouter = async (server: FastifyZodProvider) => {
|
||||
const appCfg = getConfig();
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/register-instance-relay",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
host: z.string(),
|
||||
name: slugSchema({ min: 1, max: 32, field: "name" })
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
pki: z.object({
|
||||
serverCertificate: z.string(),
|
||||
serverPrivateKey: z.string(),
|
||||
clientCertificateChain: z.string()
|
||||
}),
|
||||
ssh: z.object({
|
||||
serverCertificate: z.string(),
|
||||
serverPrivateKey: z.string(),
|
||||
clientCAPublicKey: z.string()
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: (req, _, next) => {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (appCfg.RELAY_AUTH_SECRET && authHeader) {
|
||||
const expectedHeader = `Bearer ${appCfg.RELAY_AUTH_SECRET}`;
|
||||
if (
|
||||
authHeader.length === expectedHeader.length &&
|
||||
crypto.nativeCrypto.timingSafeEqual(Buffer.from(authHeader), Buffer.from(expectedHeader))
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
throw new UnauthorizedError({
|
||||
message: "Invalid relay auth secret"
|
||||
});
|
||||
},
|
||||
handler: async (req) => {
|
||||
return server.services.relay.registerRelay({
|
||||
...req.body
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/register-org-relay",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
host: z.string(),
|
||||
name: slugSchema({ min: 1, max: 32, field: "name" })
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
pki: z.object({
|
||||
serverCertificate: z.string(),
|
||||
serverPrivateKey: z.string(),
|
||||
clientCertificateChain: z.string()
|
||||
}),
|
||||
ssh: z.object({
|
||||
serverCertificate: z.string(),
|
||||
serverPrivateKey: z.string(),
|
||||
clientCAPublicKey: z.string()
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
throw new BadRequestError({
|
||||
message: "Org relay registration is not yet supported"
|
||||
});
|
||||
|
||||
return server.services.relay.registerRelay({
|
||||
...req.body,
|
||||
identityId: req.permission.id,
|
||||
orgId: req.permission.orgId
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
133
backend/src/ee/routes/v2/gateway-router.ts
Normal file
133
backend/src/ee/routes/v2/gateway-router.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
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";
|
||||
|
||||
const SanitizedGatewayV2Schema = GatewaysV2Schema.pick({
|
||||
id: true,
|
||||
identityId: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
heartbeat: true
|
||||
});
|
||||
|
||||
export const registerGatewayV2Router = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/",
|
||||
schema: {
|
||||
body: z.object({
|
||||
relayName: slugSchema({ min: 1, max: 32, field: "relayName" }),
|
||||
name: slugSchema({ min: 1, max: 32, field: "name" })
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
gatewayId: z.string(),
|
||||
relayHost: z.string(),
|
||||
pki: z.object({
|
||||
serverCertificate: z.string(),
|
||||
serverPrivateKey: z.string(),
|
||||
clientCertificateChain: z.string()
|
||||
}),
|
||||
ssh: z.object({
|
||||
clientCertificate: z.string(),
|
||||
clientPrivateKey: z.string(),
|
||||
serverCAPublicKey: z.string()
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const gateway = await server.services.gatewayV2.registerGateway({
|
||||
orgId: req.permission.orgId,
|
||||
relayName: req.body.relayName,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
name: req.body.name
|
||||
});
|
||||
|
||||
return gateway;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/heartbeat",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
await server.services.gatewayV2.heartbeat({
|
||||
orgPermission: req.permission
|
||||
});
|
||||
|
||||
return { message: "Successfully triggered heartbeat" };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/",
|
||||
schema: {
|
||||
response: {
|
||||
200: SanitizedGatewayV2Schema.extend({
|
||||
identity: z.object({
|
||||
name: z.string(),
|
||||
id: z.string()
|
||||
})
|
||||
}).array()
|
||||
}
|
||||
},
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const gateways = await server.services.gatewayV2.listGateways({
|
||||
orgPermission: req.permission
|
||||
});
|
||||
|
||||
return gateways;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "DELETE",
|
||||
url: "/:id",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
id: z.string()
|
||||
}),
|
||||
response: {
|
||||
200: SanitizedGatewayV2Schema
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN, AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const gateway = await server.services.gatewayV2.deleteGatewayById({
|
||||
orgPermission: req.permission,
|
||||
id: req.params.id
|
||||
});
|
||||
return gateway;
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
|
||||
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
|
||||
import { registerProjectRoleRouter } from "./project-role-router";
|
||||
import { registerGatewayV2Router } from "./gateway-router";
|
||||
|
||||
export const registerV2EERoutes = async (server: FastifyZodProvider) => {
|
||||
// org role starts with organization
|
||||
@@ -23,6 +24,8 @@ export const registerV2EERoutes = async (server: FastifyZodProvider) => {
|
||||
prefix: "/identity-project-additional-privilege"
|
||||
});
|
||||
|
||||
await server.register(registerGatewayV2Router, { prefix: "/gateways" });
|
||||
|
||||
await server.register(
|
||||
async (secretRotationV2Router) => {
|
||||
// register generic secret rotation endpoints
|
||||
|
||||
@@ -19,6 +19,7 @@ import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-fold
|
||||
import { TDynamicSecretLeaseDALFactory } from "../dynamic-secret-lease/dynamic-secret-lease-dal";
|
||||
import { TDynamicSecretLeaseQueueServiceFactory } from "../dynamic-secret-lease/dynamic-secret-lease-queue";
|
||||
import { TGatewayDALFactory } from "../gateway/gateway-dal";
|
||||
import { TGatewayV2DALFactory } from "../gateway-v2/gateway-v2-dal";
|
||||
import { OrgPermissionGatewayActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TDynamicSecretDALFactory } from "./dynamic-secret-dal";
|
||||
import { DynamicSecretStatus, TDynamicSecretServiceFactory } from "./dynamic-secret-types";
|
||||
@@ -39,6 +40,7 @@ type TDynamicSecretServiceFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
gatewayDAL: Pick<TGatewayDALFactory, "findOne" | "find">;
|
||||
gatewayV2DAL: Pick<TGatewayV2DALFactory, "findOne" | "find">;
|
||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||
};
|
||||
|
||||
@@ -53,6 +55,7 @@ export const dynamicSecretServiceFactory = ({
|
||||
projectDAL,
|
||||
kmsService,
|
||||
gatewayDAL,
|
||||
gatewayV2DAL,
|
||||
resourceMetadataDAL
|
||||
}: TDynamicSecretServiceFactoryDep): TDynamicSecretServiceFactory => {
|
||||
const create: TDynamicSecretServiceFactory["create"] = async ({
|
||||
@@ -70,6 +73,7 @@ export const dynamicSecretServiceFactory = ({
|
||||
metadata,
|
||||
usernameTemplate
|
||||
}) => {
|
||||
let isGatewayV1 = true;
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||
|
||||
@@ -118,17 +122,22 @@ export const dynamicSecretServiceFactory = ({
|
||||
const gatewayId = inputs.gatewayId as string;
|
||||
|
||||
const [gateway] = await gatewayDAL.find({ id: gatewayId, orgId: actorOrgId });
|
||||
const [gatewayv2] = await gatewayV2DAL.find({ id: gatewayId, orgId: actorOrgId });
|
||||
|
||||
if (!gateway) {
|
||||
if (!gateway && !gatewayv2) {
|
||||
throw new NotFoundError({
|
||||
message: `Gateway with ID ${gatewayId} not found`
|
||||
});
|
||||
}
|
||||
|
||||
if (!gateway) {
|
||||
isGatewayV1 = false;
|
||||
}
|
||||
|
||||
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
gateway.orgId,
|
||||
gateway?.orgId ?? gatewayv2?.orgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
@@ -138,7 +147,7 @@ export const dynamicSecretServiceFactory = ({
|
||||
OrgPermissionSubjects.Gateway
|
||||
);
|
||||
|
||||
selectedGatewayId = gateway.id;
|
||||
selectedGatewayId = gateway?.id ?? gatewayv2?.id;
|
||||
}
|
||||
|
||||
const isConnected = await selectedProvider.validateConnection(provider.inputs, { projectId });
|
||||
@@ -159,7 +168,8 @@ export const dynamicSecretServiceFactory = ({
|
||||
defaultTTL,
|
||||
folderId: folder.id,
|
||||
name,
|
||||
gatewayId: selectedGatewayId,
|
||||
gatewayId: isGatewayV1 ? selectedGatewayId : undefined,
|
||||
gatewayV2Id: isGatewayV1 ? undefined : selectedGatewayId,
|
||||
usernameTemplate
|
||||
},
|
||||
tx
|
||||
@@ -270,20 +280,27 @@ export const dynamicSecretServiceFactory = ({
|
||||
const updatedInput = await selectedProvider.validateProviderInputs(newInput, { projectId });
|
||||
|
||||
let selectedGatewayId: string | null = null;
|
||||
let isGatewayV1 = true;
|
||||
if (updatedInput && typeof updatedInput === "object" && "gatewayId" in updatedInput && updatedInput?.gatewayId) {
|
||||
const gatewayId = updatedInput.gatewayId as string;
|
||||
|
||||
const [gateway] = await gatewayDAL.find({ id: gatewayId, orgId: actorOrgId });
|
||||
if (!gateway) {
|
||||
const [gatewayv2] = await gatewayV2DAL.find({ id: gatewayId, orgId: actorOrgId });
|
||||
|
||||
if (!gateway && !gatewayv2) {
|
||||
throw new NotFoundError({
|
||||
message: `Gateway with ID ${gatewayId} not found`
|
||||
});
|
||||
}
|
||||
|
||||
if (!gateway) {
|
||||
isGatewayV1 = false;
|
||||
}
|
||||
|
||||
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
gateway.orgId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
@@ -293,7 +310,7 @@ export const dynamicSecretServiceFactory = ({
|
||||
OrgPermissionSubjects.Gateway
|
||||
);
|
||||
|
||||
selectedGatewayId = gateway.id;
|
||||
selectedGatewayId = gateway?.id ?? gatewayv2?.id;
|
||||
}
|
||||
|
||||
const isConnected = await selectedProvider.validateConnection(newInput, { projectId });
|
||||
@@ -309,7 +326,8 @@ export const dynamicSecretServiceFactory = ({
|
||||
defaultTTL,
|
||||
name: newName ?? name,
|
||||
status: null,
|
||||
gatewayId: selectedGatewayId,
|
||||
gatewayId: isGatewayV1 ? selectedGatewayId : null,
|
||||
gatewayV2Id: isGatewayV1 ? null : selectedGatewayId,
|
||||
usernameTemplate
|
||||
},
|
||||
tx
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { SnowflakeProvider } from "@app/ee/services/dynamic-secret/providers/snowflake";
|
||||
|
||||
import { TGatewayServiceFactory } from "../../gateway/gateway-service";
|
||||
import { TGatewayV2ServiceFactory } from "../../gateway-v2/gateway-v2-service";
|
||||
import { AwsElastiCacheDatabaseProvider } from "./aws-elasticache";
|
||||
import { AwsIamProvider } from "./aws-iam";
|
||||
import { AzureEntraIDProvider } from "./azure-entra-id";
|
||||
@@ -24,12 +25,14 @@ import { VerticaProvider } from "./vertica";
|
||||
|
||||
type TBuildDynamicSecretProviderDTO = {
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">;
|
||||
};
|
||||
|
||||
export const buildDynamicSecretProviders = ({
|
||||
gatewayService
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
}: TBuildDynamicSecretProviderDTO): Record<DynamicSecretProviders, TDynamicProviderFns> => ({
|
||||
[DynamicSecretProviders.SqlDatabase]: SqlDatabaseProvider({ gatewayService }),
|
||||
[DynamicSecretProviders.SqlDatabase]: SqlDatabaseProvider({ gatewayService, gatewayV2Service }),
|
||||
[DynamicSecretProviders.Cassandra]: CassandraProvider(),
|
||||
[DynamicSecretProviders.AwsIam]: AwsIamProvider(),
|
||||
[DynamicSecretProviders.Redis]: RedisDatabaseProvider(),
|
||||
@@ -44,7 +47,7 @@ export const buildDynamicSecretProviders = ({
|
||||
[DynamicSecretProviders.Snowflake]: SnowflakeProvider(),
|
||||
[DynamicSecretProviders.Totp]: TotpProvider(),
|
||||
[DynamicSecretProviders.SapAse]: SapAseProvider(),
|
||||
[DynamicSecretProviders.Kubernetes]: KubernetesProvider({ gatewayService }),
|
||||
[DynamicSecretProviders.Kubernetes]: KubernetesProvider({ gatewayService, gatewayV2Service }),
|
||||
[DynamicSecretProviders.Vertica]: VerticaProvider({ gatewayService }),
|
||||
[DynamicSecretProviders.GcpIam]: GcpIamProvider(),
|
||||
[DynamicSecretProviders.Github]: GithubProvider(),
|
||||
|
||||
@@ -5,12 +5,14 @@ import https from "https";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { sanitizeString } from "@app/lib/fn";
|
||||
import { GatewayHttpProxyActions, GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
|
||||
import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||
import { TKubernetesTokenRequest } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-types";
|
||||
|
||||
import { TDynamicSecretKubernetesLeaseConfig } from "../../dynamic-secret-lease/dynamic-secret-lease-types";
|
||||
import { TGatewayServiceFactory } from "../../gateway/gateway-service";
|
||||
import { TGatewayV2ServiceFactory } from "../../gateway-v2/gateway-v2-service";
|
||||
import {
|
||||
DynamicSecretKubernetesSchema,
|
||||
KubernetesAuthMethod,
|
||||
@@ -26,6 +28,7 @@ const GATEWAY_AUTH_DEFAULT_URL = "https://kubernetes.default.svc.cluster.local";
|
||||
|
||||
type TKubernetesProviderDTO = {
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">;
|
||||
};
|
||||
|
||||
const generateUsername = (usernameTemplate?: string | null) => {
|
||||
@@ -38,7 +41,10 @@ const generateUsername = (usernameTemplate?: string | null) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO): TDynamicProviderFns => {
|
||||
export const KubernetesProvider = ({
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
}: TKubernetesProviderDTO): TDynamicProviderFns => {
|
||||
const validateProviderInputs = async (inputs: unknown) => {
|
||||
const providerInputs = await DynamicSecretKubernetesSchema.parseAsync(inputs);
|
||||
if (!providerInputs.gatewayId && providerInputs.url) {
|
||||
@@ -58,6 +64,32 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
},
|
||||
gatewayCallback: (host: string, port: number, httpsAgent?: https.Agent) => Promise<T>
|
||||
): Promise<T> => {
|
||||
const gatewayV2ConnectionDetails = await gatewayV2Service.getPlatformConnectionDetailsByGatewayId({
|
||||
gatewayId: inputs.gatewayId,
|
||||
targetHost: inputs.targetHost,
|
||||
targetPort: inputs.targetPort
|
||||
});
|
||||
if (gatewayV2ConnectionDetails) {
|
||||
const callbackResult = await withGatewayV2Proxy(
|
||||
async (port) => {
|
||||
return gatewayCallback(
|
||||
inputs.reviewTokenThroughGateway ? "http://localhost" : "https://localhost",
|
||||
port,
|
||||
inputs.httpsAgent
|
||||
);
|
||||
},
|
||||
{
|
||||
relayHost: gatewayV2ConnectionDetails.relayHost,
|
||||
gateway: gatewayV2ConnectionDetails.gateway,
|
||||
relay: gatewayV2ConnectionDetails.relay,
|
||||
protocol: inputs.reviewTokenThroughGateway ? GatewayProxyProtocol.Http : GatewayProxyProtocol.Tcp,
|
||||
httpsAgent: inputs.httpsAgent
|
||||
}
|
||||
);
|
||||
|
||||
return callbackResult;
|
||||
}
|
||||
|
||||
const relayDetails = await gatewayService.fnGetGatewayClientTlsByGatewayId(inputs.gatewayId);
|
||||
const [relayHost, relayPort] = relayDetails.relayAddress.split(":");
|
||||
|
||||
@@ -353,8 +385,18 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
return true;
|
||||
} catch (error) {
|
||||
let errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||
if (axios.isAxiosError(error) && (error.response?.data as { message: string })?.message) {
|
||||
errorMessage = (error.response?.data as { message: string }).message;
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (error.response) {
|
||||
let { message } = error?.response?.data as unknown as { message?: string };
|
||||
|
||||
if (!message && typeof error.response.data === "string") {
|
||||
message = error.response.data;
|
||||
}
|
||||
|
||||
if (message) {
|
||||
errorMessage = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sanitizedErrorMessage = sanitizeString({
|
||||
@@ -603,8 +645,18 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
};
|
||||
} catch (error) {
|
||||
let errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||
if (axios.isAxiosError(error) && (error.response?.data as { message: string })?.message) {
|
||||
errorMessage = (error.response?.data as { message: string }).message;
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (error.response) {
|
||||
let { message } = error?.response?.data as unknown as { message?: string };
|
||||
|
||||
if (!message && typeof error.response.data === "string") {
|
||||
message = error.response.data;
|
||||
}
|
||||
|
||||
if (message) {
|
||||
errorMessage = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sanitizedErrorMessage = sanitizeString({
|
||||
@@ -740,8 +792,18 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
}
|
||||
} catch (error) {
|
||||
let errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||
if (axios.isAxiosError(error) && (error.response?.data as { message: string })?.message) {
|
||||
errorMessage = (error.response?.data as { message: string }).message;
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (error.response) {
|
||||
let { message } = error?.response?.data as unknown as { message?: string };
|
||||
|
||||
if (!message && typeof error.response.data === "string") {
|
||||
message = error.response.data;
|
||||
}
|
||||
|
||||
if (message) {
|
||||
errorMessage = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sanitizedErrorMessage = sanitizeString({
|
||||
|
||||
@@ -7,10 +7,12 @@ import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { sanitizeString } from "@app/lib/fn";
|
||||
import { GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
|
||||
import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
|
||||
import { TGatewayServiceFactory } from "../../gateway/gateway-service";
|
||||
import { TGatewayV2ServiceFactory } from "../../gateway-v2/gateway-v2-service";
|
||||
import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
||||
import { DynamicSecretSqlDBSchema, PasswordRequirements, SqlProviders, TDynamicProviderFns } from "./models";
|
||||
import { compileUsernameTemplate } from "./templateUtils";
|
||||
@@ -129,9 +131,13 @@ const generateUsername = (provider: SqlProviders, usernameTemplate?: string | nu
|
||||
|
||||
type TSqlDatabaseProviderDTO = {
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">;
|
||||
};
|
||||
|
||||
export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO): TDynamicProviderFns => {
|
||||
export const SqlDatabaseProvider = ({
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
}: TSqlDatabaseProviderDTO): TDynamicProviderFns => {
|
||||
const validateProviderInputs = async (inputs: unknown) => {
|
||||
const providerInputs = await DynamicSecretSqlDBSchema.parseAsync(inputs);
|
||||
|
||||
@@ -208,6 +214,26 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
|
||||
providerInputs: z.infer<typeof DynamicSecretSqlDBSchema>,
|
||||
gatewayCallback: (host: string, port: number) => Promise<void>
|
||||
) => {
|
||||
const gatewayV2ConnectionDetails = await gatewayV2Service.getPlatformConnectionDetailsByGatewayId({
|
||||
gatewayId: providerInputs.gatewayId as string,
|
||||
targetHost: providerInputs.host,
|
||||
targetPort: providerInputs.port
|
||||
});
|
||||
|
||||
if (gatewayV2ConnectionDetails) {
|
||||
return withGatewayV2Proxy(
|
||||
async (port) => {
|
||||
await gatewayCallback("localhost", port);
|
||||
},
|
||||
{
|
||||
relayHost: gatewayV2ConnectionDetails.relayHost,
|
||||
gateway: gatewayV2ConnectionDetails.gateway,
|
||||
relay: gatewayV2ConnectionDetails.relay,
|
||||
protocol: GatewayProxyProtocol.Tcp
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const relayDetails = await gatewayService.fnGetGatewayClientTlsByGatewayId(providerInputs.gatewayId as string);
|
||||
const [relayHost, relayPort] = relayDetails.relayAddress.split(":");
|
||||
await withGatewayProxy(
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export const GATEWAY_ROUTING_INFO_OID = "1.3.6.1.4.1.12345.100.1";
|
||||
export const GATEWAY_ACTOR_OID = "1.3.6.1.4.1.12345.100.2";
|
||||
60
backend/src/ee/services/gateway-v2/gateway-v2-dal.ts
Normal file
60
backend/src/ee/services/gateway-v2/gateway-v2-dal.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { GatewaysV2Schema, TableName, TGatewaysV2 } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex";
|
||||
|
||||
export type TGatewayV2DALFactory = ReturnType<typeof gatewayV2DalFactory>;
|
||||
|
||||
export const gatewayV2DalFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.GatewayV2);
|
||||
|
||||
const find = async (filter: TFindFilter<TGatewaysV2>, { offset, limit, sort, tx }: TFindOpt<TGatewaysV2> = {}) => {
|
||||
try {
|
||||
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`)
|
||||
.join(
|
||||
TableName.IdentityOrgMembership,
|
||||
`${TableName.IdentityOrgMembership}.identityId`,
|
||||
`${TableName.GatewayV2}.identityId`
|
||||
)
|
||||
.select(selectAllTableCols(TableName.GatewayV2))
|
||||
.select(db.ref("name").withSchema(TableName.Identity).as("identityName"));
|
||||
|
||||
if (limit) void query.limit(limit);
|
||||
if (offset) void query.offset(offset);
|
||||
if (sort) {
|
||||
void query.orderBy(sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls })));
|
||||
}
|
||||
|
||||
const docs = await query;
|
||||
|
||||
return docs.map((el) => ({
|
||||
...GatewaysV2Schema.parse(el),
|
||||
identity: { id: el.identityId, name: el.identityName }
|
||||
}));
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: `${TableName.GatewayV2}: 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 };
|
||||
};
|
||||
656
backend/src/ee/services/gateway-v2/gateway-v2-service.ts
Normal file
656
backend/src/ee/services/gateway-v2/gateway-v2-service.ts
Normal file
@@ -0,0 +1,656 @@
|
||||
import net from "node:net";
|
||||
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import * as x509 from "@peculiar/x509";
|
||||
|
||||
import { TRelays } from "@app/db/schemas";
|
||||
import { PgSqlLock } from "@app/keystore/keystore";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
import { DatabaseErrorCode } from "@app/lib/error-codes";
|
||||
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
||||
import { GatewayProxyProtocol } from "@app/lib/gateway/types";
|
||||
import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||
import { constructPemChainFromCerts } from "@app/services/certificate/certificate-fns";
|
||||
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||
import {
|
||||
createSerialNumber,
|
||||
keyAlgorithmToAlgCfg
|
||||
} from "@app/services/certificate-authority/certificate-authority-fns";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionGatewayActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { TRelayDALFactory } from "../relay/relay-dal";
|
||||
import { TRelayServiceFactory } from "../relay/relay-service";
|
||||
import { GATEWAY_ACTOR_OID, GATEWAY_ROUTING_INFO_OID } from "./gateway-v2-constants";
|
||||
import { TGatewayV2DALFactory } from "./gateway-v2-dal";
|
||||
import { TOrgGatewayConfigV2DALFactory } from "./org-gateway-config-v2-dal";
|
||||
|
||||
type TGatewayV2ServiceFactoryDep = {
|
||||
orgGatewayConfigV2DAL: Pick<TOrgGatewayConfigV2DALFactory, "findOne" | "create" | "transaction" | "findById">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures" | "getPlan">;
|
||||
kmsService: TKmsServiceFactory;
|
||||
relayService: TRelayServiceFactory;
|
||||
gatewayV2DAL: TGatewayV2DALFactory;
|
||||
relayDAL: TRelayDALFactory;
|
||||
permissionService: TPermissionServiceFactory;
|
||||
};
|
||||
|
||||
export type TGatewayV2ServiceFactory = ReturnType<typeof gatewayV2ServiceFactory>;
|
||||
|
||||
export const gatewayV2ServiceFactory = ({
|
||||
orgGatewayConfigV2DAL,
|
||||
licenseService,
|
||||
kmsService,
|
||||
relayService,
|
||||
gatewayV2DAL,
|
||||
relayDAL,
|
||||
permissionService
|
||||
}: TGatewayV2ServiceFactoryDep) => {
|
||||
const $validateIdentityAccessToGateway = async (orgId: string, actorId: string, actorAuthMethod: ActorAuthMethod) => {
|
||||
const orgLicensePlan = await licenseService.getPlan(orgId);
|
||||
if (!orgLicensePlan.gateway) {
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Gateway operation failed due to organization plan restrictions. Please upgrade your instance to Infisical's Enterprise plan."
|
||||
});
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
ActorType.IDENTITY,
|
||||
actorId,
|
||||
orgId,
|
||||
actorAuthMethod,
|
||||
orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionGatewayActions.CreateGateways,
|
||||
OrgPermissionSubjects.Gateway
|
||||
);
|
||||
};
|
||||
|
||||
const $getOrgCAs = async (orgId: string) => {
|
||||
const { encryptor: orgKmsEncryptor, decryptor: orgKmsDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.Organization,
|
||||
orgId
|
||||
});
|
||||
|
||||
const orgCAs = await orgGatewayConfigV2DAL.transaction(async (tx) => {
|
||||
const orgGatewayConfigV2 = await orgGatewayConfigV2DAL.findOne({ orgId });
|
||||
if (orgGatewayConfigV2) return orgGatewayConfigV2;
|
||||
|
||||
await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.OrgGatewayV2Init(orgId)]);
|
||||
|
||||
// generate root CA
|
||||
const rootCaKeyAlgorithm = CertKeyAlgorithm.RSA_2048;
|
||||
const alg = keyAlgorithmToAlgCfg(rootCaKeyAlgorithm);
|
||||
const rootCaKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||
|
||||
const rootCaSerialNumber = createSerialNumber();
|
||||
const rootCaSkObj = crypto.nativeCrypto.KeyObject.from(rootCaKeys.privateKey);
|
||||
const rootCaIssuedAt = new Date();
|
||||
const rootCaExpiration = new Date(new Date().setFullYear(2045));
|
||||
|
||||
const rootCaCert = await x509.X509CertificateGenerator.createSelfSigned({
|
||||
name: `O=${orgId},CN=Infisical Gateway Root CA`,
|
||||
serialNumber: rootCaSerialNumber,
|
||||
notBefore: rootCaIssuedAt,
|
||||
notAfter: rootCaExpiration,
|
||||
signingAlgorithm: alg,
|
||||
keys: rootCaKeys,
|
||||
extensions: [
|
||||
// eslint-disable-next-line no-bitwise
|
||||
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
|
||||
await x509.SubjectKeyIdentifierExtension.create(rootCaKeys.publicKey)
|
||||
]
|
||||
});
|
||||
|
||||
// generate server CA
|
||||
const serverCaSerialNumber = createSerialNumber();
|
||||
const serverCaIssuedAt = new Date();
|
||||
const serverCaExpiration = new Date(new Date().setFullYear(2045));
|
||||
const serverCaKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||
const serverCaSkObj = crypto.nativeCrypto.KeyObject.from(serverCaKeys.privateKey);
|
||||
const serverCaCert = await x509.X509CertificateGenerator.create({
|
||||
serialNumber: serverCaSerialNumber,
|
||||
subject: `O=${orgId},CN=Infisical Gateway Server CA`,
|
||||
issuer: rootCaCert.subject,
|
||||
notBefore: serverCaIssuedAt,
|
||||
notAfter: serverCaExpiration,
|
||||
signingKey: rootCaKeys.privateKey,
|
||||
publicKey: serverCaKeys.publicKey,
|
||||
signingAlgorithm: alg,
|
||||
extensions: [
|
||||
new x509.KeyUsagesExtension(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
x509.KeyUsageFlags.keyCertSign |
|
||||
x509.KeyUsageFlags.cRLSign |
|
||||
x509.KeyUsageFlags.digitalSignature |
|
||||
x509.KeyUsageFlags.keyEncipherment,
|
||||
true
|
||||
),
|
||||
new x509.BasicConstraintsExtension(true, 0, true),
|
||||
await x509.AuthorityKeyIdentifierExtension.create(rootCaCert, false),
|
||||
await x509.SubjectKeyIdentifierExtension.create(serverCaKeys.publicKey)
|
||||
]
|
||||
});
|
||||
|
||||
// generate client CA
|
||||
const clientCaSerialNumber = createSerialNumber();
|
||||
const clientCaIssuedAt = new Date();
|
||||
const clientCaExpiration = new Date(new Date().setFullYear(2045));
|
||||
const clientCaKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||
const clientCaSkObj = crypto.nativeCrypto.KeyObject.from(clientCaKeys.privateKey);
|
||||
const clientCaCert = await x509.X509CertificateGenerator.create({
|
||||
serialNumber: clientCaSerialNumber,
|
||||
subject: `O=${orgId},CN=Infisical Gateway Client CA`,
|
||||
issuer: rootCaCert.subject,
|
||||
notBefore: clientCaIssuedAt,
|
||||
notAfter: clientCaExpiration,
|
||||
signingKey: rootCaKeys.privateKey,
|
||||
publicKey: clientCaKeys.publicKey,
|
||||
signingAlgorithm: alg,
|
||||
extensions: [
|
||||
new x509.KeyUsagesExtension(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
x509.KeyUsageFlags.keyCertSign |
|
||||
x509.KeyUsageFlags.cRLSign |
|
||||
x509.KeyUsageFlags.digitalSignature |
|
||||
x509.KeyUsageFlags.keyEncipherment,
|
||||
true
|
||||
),
|
||||
new x509.BasicConstraintsExtension(true, 0, true),
|
||||
await x509.AuthorityKeyIdentifierExtension.create(rootCaCert, false),
|
||||
await x509.SubjectKeyIdentifierExtension.create(clientCaKeys.publicKey)
|
||||
]
|
||||
});
|
||||
|
||||
const encryptedRootGatewayCaPrivateKey = orgKmsEncryptor({
|
||||
plainText: Buffer.from(
|
||||
rootCaSkObj.export({
|
||||
type: "pkcs8",
|
||||
format: "der"
|
||||
})
|
||||
)
|
||||
}).cipherTextBlob;
|
||||
const encryptedRootGatewayCaCertificate = orgKmsEncryptor({
|
||||
plainText: Buffer.from(rootCaCert.rawData)
|
||||
}).cipherTextBlob;
|
||||
|
||||
const encryptedGatewayServerCaPrivateKey = orgKmsEncryptor({
|
||||
plainText: Buffer.from(serverCaSkObj.export({ type: "pkcs8", format: "der" }))
|
||||
}).cipherTextBlob;
|
||||
const encryptedGatewayServerCaCertificate = orgKmsEncryptor({
|
||||
plainText: Buffer.from(serverCaCert.rawData)
|
||||
}).cipherTextBlob;
|
||||
const encryptedGatewayServerCaCertificateChain = orgKmsEncryptor({
|
||||
plainText: Buffer.from(constructPemChainFromCerts([rootCaCert]))
|
||||
}).cipherTextBlob;
|
||||
|
||||
const encryptedGatewayClientCaPrivateKey = orgKmsEncryptor({
|
||||
plainText: Buffer.from(clientCaSkObj.export({ type: "pkcs8", format: "der" }))
|
||||
}).cipherTextBlob;
|
||||
const encryptedGatewayClientCaCertificate = orgKmsEncryptor({
|
||||
plainText: Buffer.from(clientCaCert.rawData)
|
||||
}).cipherTextBlob;
|
||||
const encryptedGatewayClientCaCertificateChain = orgKmsEncryptor({
|
||||
plainText: Buffer.from(constructPemChainFromCerts([rootCaCert]))
|
||||
}).cipherTextBlob;
|
||||
|
||||
return orgGatewayConfigV2DAL.create({
|
||||
orgId,
|
||||
encryptedRootGatewayCaPrivateKey,
|
||||
encryptedRootGatewayCaCertificate,
|
||||
encryptedGatewayServerCaPrivateKey,
|
||||
encryptedGatewayServerCaCertificate,
|
||||
encryptedGatewayServerCaCertificateChain,
|
||||
encryptedGatewayClientCaPrivateKey,
|
||||
encryptedGatewayClientCaCertificate,
|
||||
encryptedGatewayClientCaCertificateChain
|
||||
});
|
||||
});
|
||||
|
||||
const rootGatewayCaPrivateKey = orgKmsDecryptor({ cipherTextBlob: orgCAs.encryptedRootGatewayCaPrivateKey });
|
||||
const rootGatewayCaCertificate = orgKmsDecryptor({ cipherTextBlob: orgCAs.encryptedRootGatewayCaCertificate });
|
||||
|
||||
const gatewayServerCaPrivateKey = orgKmsDecryptor({ cipherTextBlob: orgCAs.encryptedGatewayServerCaPrivateKey });
|
||||
const gatewayServerCaCertificate = orgKmsDecryptor({ cipherTextBlob: orgCAs.encryptedGatewayServerCaCertificate });
|
||||
const gatewayServerCaCertificateChain = orgKmsDecryptor({
|
||||
cipherTextBlob: orgCAs.encryptedGatewayServerCaCertificateChain
|
||||
});
|
||||
|
||||
const gatewayClientCaPrivateKey = orgKmsDecryptor({ cipherTextBlob: orgCAs.encryptedGatewayClientCaPrivateKey });
|
||||
const gatewayClientCaCertificate = orgKmsDecryptor({
|
||||
cipherTextBlob: orgCAs.encryptedGatewayClientCaCertificate
|
||||
});
|
||||
const gatewayClientCaCertificateChain = orgKmsDecryptor({
|
||||
cipherTextBlob: orgCAs.encryptedGatewayClientCaCertificateChain
|
||||
});
|
||||
|
||||
return {
|
||||
rootGatewayCaPrivateKey,
|
||||
rootGatewayCaCertificate,
|
||||
gatewayServerCaPrivateKey,
|
||||
gatewayServerCaCertificate,
|
||||
gatewayServerCaCertificateChain,
|
||||
gatewayClientCaPrivateKey,
|
||||
gatewayClientCaCertificate,
|
||||
gatewayClientCaCertificateChain
|
||||
};
|
||||
};
|
||||
|
||||
const listGateways = async ({ orgPermission }: { orgPermission: OrgServiceActor }) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
orgPermission.type,
|
||||
orgPermission.id,
|
||||
orgPermission.orgId,
|
||||
orgPermission.authMethod,
|
||||
orgPermission.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionGatewayActions.ListGateways,
|
||||
OrgPermissionSubjects.Gateway
|
||||
);
|
||||
|
||||
const gateways = await gatewayV2DAL.find({
|
||||
orgId: orgPermission.orgId
|
||||
});
|
||||
|
||||
return gateways;
|
||||
};
|
||||
|
||||
const getPlatformConnectionDetailsByGatewayId = async ({
|
||||
gatewayId,
|
||||
targetHost,
|
||||
targetPort
|
||||
}: {
|
||||
gatewayId: string;
|
||||
targetHost: string;
|
||||
targetPort: number;
|
||||
}) => {
|
||||
const gateway = await gatewayV2DAL.findById(gatewayId);
|
||||
if (!gateway) {
|
||||
return;
|
||||
}
|
||||
|
||||
const orgGatewayConfig = await orgGatewayConfigV2DAL.findOne({ orgId: gateway.orgId });
|
||||
if (!orgGatewayConfig) {
|
||||
throw new NotFoundError({ message: `Gateway Config for org ${gateway.orgId} not found.` });
|
||||
}
|
||||
|
||||
if (!gateway.relayId) {
|
||||
throw new BadRequestError({
|
||||
message: "Gateway is not associated with a relay"
|
||||
});
|
||||
}
|
||||
|
||||
const orgLicensePlan = await licenseService.getPlan(orgGatewayConfig.orgId);
|
||||
if (!orgLicensePlan.gateway) {
|
||||
throw new BadRequestError({
|
||||
message: "Please upgrade your instance to Infisical's Enterprise plan to use gateways."
|
||||
});
|
||||
}
|
||||
|
||||
const { decryptor: orgKmsDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.Organization,
|
||||
orgId: orgGatewayConfig.orgId
|
||||
});
|
||||
|
||||
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
|
||||
|
||||
const rootGatewayCaCert = new x509.X509Certificate(
|
||||
orgKmsDecryptor({
|
||||
cipherTextBlob: orgGatewayConfig.encryptedRootGatewayCaCertificate
|
||||
})
|
||||
);
|
||||
|
||||
const gatewayClientCaCert = new x509.X509Certificate(
|
||||
orgKmsDecryptor({
|
||||
cipherTextBlob: orgGatewayConfig.encryptedGatewayClientCaCertificate
|
||||
})
|
||||
);
|
||||
|
||||
const gatewayServerCaCert = new x509.X509Certificate(
|
||||
orgKmsDecryptor({
|
||||
cipherTextBlob: orgGatewayConfig.encryptedGatewayServerCaCertificate
|
||||
})
|
||||
);
|
||||
|
||||
const gatewayClientCaPrivateKey = orgKmsDecryptor({
|
||||
cipherTextBlob: orgGatewayConfig.encryptedGatewayClientCaPrivateKey
|
||||
});
|
||||
|
||||
const gatewayClientCaSkObj = crypto.nativeCrypto.createPrivateKey({
|
||||
key: gatewayClientCaPrivateKey,
|
||||
format: "der",
|
||||
type: "pkcs8"
|
||||
});
|
||||
|
||||
const importedGatewayClientCaPrivateKey = await crypto.nativeCrypto.subtle.importKey(
|
||||
"pkcs8",
|
||||
gatewayClientCaSkObj.export({ format: "der", type: "pkcs8" }),
|
||||
alg,
|
||||
true,
|
||||
["sign"]
|
||||
);
|
||||
|
||||
const clientCertIssuedAt = new Date();
|
||||
const clientCertExpiration = new Date(new Date().getTime() + 5 * 60 * 1000);
|
||||
const clientKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||
const clientCertSerialNumber = createSerialNumber();
|
||||
|
||||
const routingInfo = {
|
||||
targetHost,
|
||||
targetPort
|
||||
};
|
||||
|
||||
const routingExtension = new x509.Extension(
|
||||
GATEWAY_ROUTING_INFO_OID,
|
||||
false,
|
||||
Buffer.from(JSON.stringify(routingInfo))
|
||||
);
|
||||
|
||||
const actorExtension = new x509.Extension(
|
||||
GATEWAY_ACTOR_OID,
|
||||
false,
|
||||
Buffer.from(JSON.stringify({ type: ActorType.PLATFORM }))
|
||||
);
|
||||
|
||||
const clientCert = await x509.X509CertificateGenerator.create({
|
||||
serialNumber: clientCertSerialNumber,
|
||||
subject: `O=${orgGatewayConfig.orgId},OU=gateway-client,CN=${ActorType.PLATFORM}:${gatewayId}`,
|
||||
issuer: gatewayClientCaCert.subject,
|
||||
notAfter: clientCertExpiration,
|
||||
notBefore: clientCertIssuedAt,
|
||||
signingKey: importedGatewayClientCaPrivateKey,
|
||||
publicKey: clientKeys.publicKey,
|
||||
signingAlgorithm: alg,
|
||||
extensions: [
|
||||
new x509.BasicConstraintsExtension(false),
|
||||
await x509.AuthorityKeyIdentifierExtension.create(gatewayClientCaCert, false),
|
||||
await x509.SubjectKeyIdentifierExtension.create(clientKeys.publicKey),
|
||||
new x509.CertificatePolicyExtension(["2.5.29.32.0"]), // anyPolicy
|
||||
new x509.KeyUsagesExtension(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
x509.KeyUsageFlags[CertKeyUsage.DIGITAL_SIGNATURE] |
|
||||
x509.KeyUsageFlags[CertKeyUsage.KEY_ENCIPHERMENT] |
|
||||
x509.KeyUsageFlags[CertKeyUsage.KEY_AGREEMENT],
|
||||
true
|
||||
),
|
||||
new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage[CertExtendedKeyUsage.CLIENT_AUTH]], true),
|
||||
routingExtension,
|
||||
actorExtension
|
||||
]
|
||||
});
|
||||
|
||||
const gatewayClientCertPrivateKey = crypto.nativeCrypto.KeyObject.from(clientKeys.privateKey);
|
||||
|
||||
const relayCredentials = await relayService.getCredentialsForClient({
|
||||
relayId: gateway.relayId,
|
||||
orgId: gateway.orgId,
|
||||
orgName: gateway.orgName,
|
||||
gatewayId
|
||||
});
|
||||
|
||||
return {
|
||||
relayHost: relayCredentials.relayHost,
|
||||
gateway: {
|
||||
clientCertificate: clientCert.toString("pem"),
|
||||
clientPrivateKey: gatewayClientCertPrivateKey.export({ format: "pem", type: "pkcs8" }).toString(),
|
||||
serverCertificateChain: constructPemChainFromCerts([gatewayServerCaCert, rootGatewayCaCert])
|
||||
},
|
||||
relay: {
|
||||
clientCertificate: relayCredentials.clientCertificate,
|
||||
clientPrivateKey: relayCredentials.clientPrivateKey,
|
||||
serverCertificateChain: relayCredentials.serverCertificateChain
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const registerGateway = async ({
|
||||
orgId,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
relayName,
|
||||
name
|
||||
}: {
|
||||
orgId: string;
|
||||
actorId: string;
|
||||
actorAuthMethod: ActorAuthMethod;
|
||||
relayName: string;
|
||||
name: string;
|
||||
}) => {
|
||||
await $validateIdentityAccessToGateway(orgId, actorId, actorAuthMethod);
|
||||
const orgCAs = await $getOrgCAs(orgId);
|
||||
|
||||
let relay: TRelays = await relayDAL.findOne({ orgId, name: relayName });
|
||||
if (!relay) {
|
||||
relay = await relayDAL.findOne({ name: relayName, orgId: null });
|
||||
}
|
||||
|
||||
if (!relay) {
|
||||
throw new NotFoundError({ message: `Relay ${relayName} not found` });
|
||||
}
|
||||
|
||||
try {
|
||||
const [gateway] = await gatewayV2DAL.upsert(
|
||||
[
|
||||
{
|
||||
orgId,
|
||||
name,
|
||||
identityId: actorId,
|
||||
relayId: relay.id
|
||||
}
|
||||
],
|
||||
["identityId"]
|
||||
);
|
||||
|
||||
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
|
||||
const gatewayServerCaCert = new x509.X509Certificate(orgCAs.gatewayServerCaCertificate);
|
||||
const rootGatewayCaCert = new x509.X509Certificate(orgCAs.rootGatewayCaCertificate);
|
||||
const gatewayClientCaCert = new x509.X509Certificate(orgCAs.gatewayClientCaCertificate);
|
||||
|
||||
const gatewayServerCaSkObj = crypto.nativeCrypto.createPrivateKey({
|
||||
key: orgCAs.gatewayServerCaPrivateKey,
|
||||
format: "der",
|
||||
type: "pkcs8"
|
||||
});
|
||||
const gatewayServerCaPrivateKey = await crypto.nativeCrypto.subtle.importKey(
|
||||
"pkcs8",
|
||||
gatewayServerCaSkObj.export({ format: "der", type: "pkcs8" }),
|
||||
alg,
|
||||
true,
|
||||
["sign"]
|
||||
);
|
||||
|
||||
const gatewayServerKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||
const gatewayServerCertIssuedAt = new Date();
|
||||
const gatewayServerCertExpireAt = new Date(new Date().setDate(new Date().getDate() + 1));
|
||||
const gatewayServerCertPrivateKey = crypto.nativeCrypto.KeyObject.from(gatewayServerKeys.privateKey);
|
||||
|
||||
const gatewayServerCertExtensions: x509.Extension[] = [
|
||||
new x509.BasicConstraintsExtension(false),
|
||||
await x509.AuthorityKeyIdentifierExtension.create(gatewayServerCaCert, false),
|
||||
await x509.SubjectKeyIdentifierExtension.create(gatewayServerKeys.publicKey),
|
||||
new x509.CertificatePolicyExtension(["2.5.29.32.0"]), // anyPolicy
|
||||
new x509.KeyUsagesExtension(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
x509.KeyUsageFlags[CertKeyUsage.DIGITAL_SIGNATURE] | x509.KeyUsageFlags[CertKeyUsage.KEY_ENCIPHERMENT],
|
||||
true
|
||||
),
|
||||
new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage[CertExtendedKeyUsage.SERVER_AUTH]], true),
|
||||
new x509.SubjectAlternativeNameExtension([
|
||||
{ type: "dns", value: "localhost" },
|
||||
{ type: "ip", value: "127.0.0.1" },
|
||||
{ type: "ip", value: "::1" }
|
||||
])
|
||||
];
|
||||
|
||||
const gatewayServerSerialNumber = createSerialNumber();
|
||||
const gatewayServerCertificate = await x509.X509CertificateGenerator.create({
|
||||
serialNumber: gatewayServerSerialNumber,
|
||||
subject: `O=${orgId},CN=Gateway`,
|
||||
issuer: gatewayServerCaCert.subject,
|
||||
notBefore: gatewayServerCertIssuedAt,
|
||||
notAfter: gatewayServerCertExpireAt,
|
||||
signingKey: gatewayServerCaPrivateKey,
|
||||
publicKey: gatewayServerKeys.publicKey,
|
||||
signingAlgorithm: alg,
|
||||
extensions: gatewayServerCertExtensions
|
||||
});
|
||||
|
||||
const relayCredentials = await relayService.getCredentialsForGateway({
|
||||
relayName,
|
||||
orgId,
|
||||
gatewayId: gateway.id
|
||||
});
|
||||
|
||||
return {
|
||||
gatewayId: gateway.id,
|
||||
relayHost: relayCredentials.relayHost,
|
||||
pki: {
|
||||
serverCertificate: gatewayServerCertificate.toString("pem"),
|
||||
serverPrivateKey: gatewayServerCertPrivateKey.export({ format: "pem", type: "pkcs8" }).toString(),
|
||||
clientCertificateChain: constructPemChainFromCerts([gatewayClientCaCert, rootGatewayCaCert])
|
||||
},
|
||||
ssh: {
|
||||
clientCertificate: relayCredentials.clientSshCert,
|
||||
clientPrivateKey: relayCredentials.clientSshPrivateKey,
|
||||
serverCAPublicKey: relayCredentials.serverCAPublicKey
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof DatabaseError && (err.error as { code: string })?.code === DatabaseErrorCode.UniqueViolation) {
|
||||
throw new BadRequestError({ message: `Gateway with name "${name}" already exists` });
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const heartbeat = async ({ orgPermission }: { orgPermission: OrgServiceActor }) => {
|
||||
await $validateIdentityAccessToGateway(orgPermission.orgId, orgPermission.id, orgPermission.authMethod);
|
||||
|
||||
const gateway = await gatewayV2DAL.findOne({
|
||||
orgId: orgPermission.orgId,
|
||||
identityId: orgPermission.id
|
||||
});
|
||||
|
||||
if (!gateway) {
|
||||
throw new NotFoundError({ message: `Gateway for identity ${orgPermission.id} not found.` });
|
||||
}
|
||||
|
||||
const gatewayV2ConnectionDetails = await getPlatformConnectionDetailsByGatewayId({
|
||||
gatewayId: gateway.id,
|
||||
targetHost: "health-check",
|
||||
targetPort: 443
|
||||
});
|
||||
|
||||
if (!gatewayV2ConnectionDetails) {
|
||||
throw new NotFoundError({ message: `Gateway connection details for gateway ${gateway.id} not found.` });
|
||||
}
|
||||
|
||||
const isGatewayReachable = await withGatewayV2Proxy(
|
||||
async (port) => {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
const socket = new net.Socket();
|
||||
let responseReceived = false;
|
||||
let isResolved = false;
|
||||
|
||||
// Set socket timeout
|
||||
socket.setTimeout(10000);
|
||||
|
||||
const cleanup = () => {
|
||||
if (!socket.destroyed) {
|
||||
socket.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
socket.on("data", (data: Buffer) => {
|
||||
const response = data.toString().trim();
|
||||
if (response === "PONG" && !isResolved) {
|
||||
isResolved = true;
|
||||
responseReceived = true;
|
||||
cleanup();
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("error", (err: Error) => {
|
||||
if (!isResolved) {
|
||||
isResolved = true;
|
||||
cleanup();
|
||||
reject(new Error(`TCP connection error: ${err.message}`));
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("timeout", () => {
|
||||
if (!isResolved) {
|
||||
isResolved = true;
|
||||
cleanup();
|
||||
reject(new Error("TCP connection timeout"));
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("close", () => {
|
||||
if (!isResolved && !responseReceived) {
|
||||
isResolved = true;
|
||||
cleanup();
|
||||
reject(new Error("Connection closed without receiving PONG"));
|
||||
}
|
||||
});
|
||||
|
||||
socket.connect(port, "localhost");
|
||||
});
|
||||
},
|
||||
{
|
||||
protocol: GatewayProxyProtocol.Ping,
|
||||
relayHost: gatewayV2ConnectionDetails.relayHost,
|
||||
gateway: gatewayV2ConnectionDetails.gateway,
|
||||
relay: gatewayV2ConnectionDetails.relay
|
||||
}
|
||||
);
|
||||
|
||||
if (!isGatewayReachable) {
|
||||
throw new BadRequestError({ message: `Gateway ${gateway.id} is not reachable` });
|
||||
}
|
||||
|
||||
await gatewayV2DAL.updateById(gateway.id, { heartbeat: new Date() });
|
||||
};
|
||||
|
||||
const deleteGatewayById = async ({ orgPermission, id }: { orgPermission: OrgServiceActor; id: string }) => {
|
||||
const gateway = await gatewayV2DAL.findOne({ id, orgId: orgPermission.orgId });
|
||||
if (!gateway) {
|
||||
throw new NotFoundError({ message: `Gateway ${id} not found` });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
orgPermission.type,
|
||||
orgPermission.id,
|
||||
gateway.orgId,
|
||||
orgPermission.authMethod,
|
||||
orgPermission.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionGatewayActions.DeleteGateways,
|
||||
OrgPermissionSubjects.Gateway
|
||||
);
|
||||
|
||||
return gatewayV2DAL.deleteById(gateway.id);
|
||||
};
|
||||
|
||||
return {
|
||||
listGateways,
|
||||
registerGateway,
|
||||
getPlatformConnectionDetailsByGatewayId,
|
||||
deleteGatewayById,
|
||||
heartbeat
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TOrgGatewayConfigV2DALFactory = ReturnType<typeof orgGatewayConfigV2DalFactory>;
|
||||
|
||||
export const orgGatewayConfigV2DalFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.OrgGatewayConfigV2);
|
||||
|
||||
return orm;
|
||||
};
|
||||
11
backend/src/ee/services/relay/instance-relay-config-dal.ts
Normal file
11
backend/src/ee/services/relay/instance-relay-config-dal.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TInstanceRelayConfigDALFactory = ReturnType<typeof instanceRelayConfigDalFactory>;
|
||||
|
||||
export const instanceRelayConfigDalFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.InstanceRelayConfig);
|
||||
|
||||
return orm;
|
||||
};
|
||||
11
backend/src/ee/services/relay/org-relay-config-dal.ts
Normal file
11
backend/src/ee/services/relay/org-relay-config-dal.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TOrgRelayConfigDALFactory = ReturnType<typeof orgRelayConfigDalFactory>;
|
||||
|
||||
export const orgRelayConfigDalFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.OrgRelayConfig);
|
||||
|
||||
return orm;
|
||||
};
|
||||
11
backend/src/ee/services/relay/relay-dal.ts
Normal file
11
backend/src/ee/services/relay/relay-dal.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TRelayDALFactory = ReturnType<typeof relayDalFactory>;
|
||||
|
||||
export const relayDalFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.Relay);
|
||||
|
||||
return orm;
|
||||
};
|
||||
1003
backend/src/ee/services/relay/relay-service.ts
Normal file
1003
backend/src/ee/services/relay/relay-service.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -82,6 +82,7 @@ import {
|
||||
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
||||
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
||||
|
||||
import { TGatewayV2ServiceFactory } from "../gateway-v2/gateway-v2-service";
|
||||
import { awsIamUserSecretRotationFactory } from "./aws-iam-user-secret/aws-iam-user-secret-rotation-fns";
|
||||
import { oktaClientSecretRotationFactory } from "./okta-client-secret/okta-client-secret-rotation-fns";
|
||||
import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
|
||||
@@ -110,6 +111,7 @@ export type TSecretRotationV2ServiceFactoryDep = {
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">;
|
||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">;
|
||||
};
|
||||
|
||||
export type TSecretRotationV2ServiceFactory = ReturnType<typeof secretRotationV2ServiceFactory>;
|
||||
@@ -153,7 +155,8 @@ export const secretRotationV2ServiceFactory = ({
|
||||
queueService,
|
||||
folderCommitService,
|
||||
appConnectionDAL,
|
||||
gatewayService
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
}: TSecretRotationV2ServiceFactoryDep) => {
|
||||
const $queueSendSecretRotationStatusNotification = async (secretRotation: TSecretRotationV2Raw) => {
|
||||
const appCfg = getConfig();
|
||||
@@ -467,7 +470,8 @@ export const secretRotationV2ServiceFactory = ({
|
||||
} as TSecretRotationV2WithConnection,
|
||||
appConnectionDAL,
|
||||
kmsService,
|
||||
gatewayService
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
);
|
||||
|
||||
// even though we have a db constraint we want to check before any rotation of credentials is attempted
|
||||
@@ -831,7 +835,8 @@ export const secretRotationV2ServiceFactory = ({
|
||||
} as TSecretRotationV2WithConnection,
|
||||
appConnectionDAL,
|
||||
kmsService,
|
||||
gatewayService
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
);
|
||||
|
||||
const generatedCredentials = await decryptSecretRotationCredentials({
|
||||
@@ -915,7 +920,8 @@ export const secretRotationV2ServiceFactory = ({
|
||||
} as TSecretRotationV2WithConnection,
|
||||
appConnectionDAL,
|
||||
kmsService,
|
||||
gatewayService
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
);
|
||||
|
||||
const updatedRotation = await rotationFactory.rotateCredentials(
|
||||
|
||||
@@ -6,6 +6,7 @@ import { TAppConnectionDALFactory } from "@app/services/app-connection/app-conne
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||
|
||||
import { TGatewayV2ServiceFactory } from "../gateway-v2/gateway-v2-service";
|
||||
import {
|
||||
TAuth0ClientSecretRotation,
|
||||
TAuth0ClientSecretRotationGeneratedCredentials,
|
||||
@@ -253,7 +254,8 @@ export type TRotationFactory<
|
||||
secretRotation: T,
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">,
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||
) => {
|
||||
issueCredentials: TRotationFactoryIssueCredentials<C, P>;
|
||||
revokeCredentials: TRotationFactoryRevokeCredentials<C>;
|
||||
|
||||
@@ -41,7 +41,7 @@ const ORACLE_PASSWORD_REQUIREMENTS = {
|
||||
export const sqlCredentialsRotationFactory: TRotationFactory<
|
||||
TSqlCredentialsRotationWithConnection,
|
||||
TSqlCredentialsRotationGeneratedCredentials
|
||||
> = (secretRotation, _appConnectionDAL, _kmsService, gatewayService) => {
|
||||
> = (secretRotation, _appConnectionDAL, _kmsService, gatewayService, gatewayV2Service) => {
|
||||
const {
|
||||
connection,
|
||||
parameters: { username1, username2 },
|
||||
@@ -67,6 +67,7 @@ export const sqlCredentialsRotationFactory: TRotationFactory<
|
||||
credentials: finalCredentials
|
||||
},
|
||||
gatewayService,
|
||||
gatewayV2Service,
|
||||
(client) => operation(client)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,6 +20,9 @@ export const PgSqlLock = {
|
||||
CreateProject: (orgId: string) => pgAdvisoryLockHashText(`create-project:${orgId}`),
|
||||
CreateFolder: (envId: string, projectId: string) => pgAdvisoryLockHashText(`create-folder:${envId}-${projectId}`),
|
||||
SshInit: (projectId: string) => pgAdvisoryLockHashText(`ssh-bootstrap:${projectId}`),
|
||||
InstanceRelayConfigInit: () => pgAdvisoryLockHashText("instance-relay-config-init"),
|
||||
OrgGatewayV2Init: (orgId: string) => pgAdvisoryLockHashText(`org-gateway-v2-init:${orgId}`),
|
||||
OrgRelayConfigInit: (orgId: string) => pgAdvisoryLockHashText(`org-relay-config-init:${orgId}`),
|
||||
IdentityLogin: (identityId: string, nonce: string) => pgAdvisoryLockHashText(`identity-login:${identityId}:${nonce}`)
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
backend/src/lib/gateway-v2/gateway-v2.ts
Normal file
281
backend/src/lib/gateway-v2/gateway-v2.ts
Normal 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();
|
||||
}
|
||||
};
|
||||
@@ -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 {
|
||||
|
||||
@@ -122,6 +122,11 @@ export const injectIdentity = fp(
|
||||
return;
|
||||
}
|
||||
|
||||
// Authentication is handled on a route-level
|
||||
if (req.url === "/api/v1/relays/register-instance-relay") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Authentication is handled on a route-level here.
|
||||
if (req.url.includes("/api/v1/workflow-integrations/microsoft-teams/message-endpoint")) {
|
||||
return;
|
||||
|
||||
@@ -38,6 +38,9 @@ import { externalKmsServiceFactory } from "@app/ee/services/external-kms/externa
|
||||
import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal";
|
||||
import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal";
|
||||
import { gatewayV2DalFactory } from "@app/ee/services/gateway-v2/gateway-v2-dal";
|
||||
import { gatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||
import { orgGatewayConfigV2DalFactory } from "@app/ee/services/gateway-v2/org-gateway-config-v2-dal";
|
||||
import { githubOrgSyncDALFactory } from "@app/ee/services/github-org-sync/github-org-sync-dal";
|
||||
import { githubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service";
|
||||
import { groupDALFactory } from "@app/ee/services/group/group-dal";
|
||||
@@ -72,6 +75,10 @@ import { projectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/proje
|
||||
import { projectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service";
|
||||
import { rateLimitDALFactory } from "@app/ee/services/rate-limit/rate-limit-dal";
|
||||
import { rateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-service";
|
||||
import { instanceRelayConfigDalFactory } from "@app/ee/services/relay/instance-relay-config-dal";
|
||||
import { orgRelayConfigDalFactory } from "@app/ee/services/relay/org-relay-config-dal";
|
||||
import { relayDalFactory } from "@app/ee/services/relay/relay-dal";
|
||||
import { relayServiceFactory } from "@app/ee/services/relay/relay-service";
|
||||
import { samlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal";
|
||||
import { samlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
|
||||
import { scimDALFactory } from "@app/ee/services/scim/scim-dal";
|
||||
@@ -969,6 +976,13 @@ export const registerRoutes = async (
|
||||
const pkiSubscriberDAL = pkiSubscriberDALFactory(db);
|
||||
const pkiTemplatesDAL = pkiTemplatesDALFactory(db);
|
||||
|
||||
const instanceRelayConfigDAL = instanceRelayConfigDalFactory(db);
|
||||
const orgRelayConfigDAL = orgRelayConfigDalFactory(db);
|
||||
const relayDAL = relayDalFactory(db);
|
||||
const gatewayV2DAL = gatewayV2DalFactory(db);
|
||||
|
||||
const orgGatewayConfigV2DAL = orgGatewayConfigV2DalFactory(db);
|
||||
|
||||
const certificateService = certificateServiceFactory({
|
||||
certificateDAL,
|
||||
certificateBodyDAL,
|
||||
@@ -1087,6 +1101,23 @@ export const registerRoutes = async (
|
||||
keyStore
|
||||
});
|
||||
|
||||
const relayService = relayServiceFactory({
|
||||
instanceRelayConfigDAL,
|
||||
orgRelayConfigDAL,
|
||||
relayDAL,
|
||||
kmsService
|
||||
});
|
||||
|
||||
const gatewayV2Service = gatewayV2ServiceFactory({
|
||||
kmsService,
|
||||
licenseService,
|
||||
relayService,
|
||||
orgGatewayConfigV2DAL,
|
||||
gatewayV2DAL,
|
||||
relayDAL,
|
||||
permissionService
|
||||
});
|
||||
|
||||
const secretSyncQueue = secretSyncQueueFactory({
|
||||
queueService,
|
||||
secretSyncDAL,
|
||||
@@ -1111,7 +1142,8 @@ export const registerRoutes = async (
|
||||
resourceMetadataDAL,
|
||||
appConnectionDAL,
|
||||
licenseService,
|
||||
gatewayService
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
});
|
||||
|
||||
const secretQueueService = secretQueueFactory({
|
||||
@@ -1535,6 +1567,7 @@ export const registerRoutes = async (
|
||||
permissionService,
|
||||
licenseService
|
||||
});
|
||||
|
||||
const identityUaService = identityUaServiceFactory({
|
||||
identityOrgMembershipDAL,
|
||||
permissionService,
|
||||
@@ -1552,6 +1585,8 @@ export const registerRoutes = async (
|
||||
permissionService,
|
||||
licenseService,
|
||||
gatewayService,
|
||||
gatewayV2Service,
|
||||
gatewayV2DAL,
|
||||
gatewayDAL,
|
||||
kmsService
|
||||
});
|
||||
@@ -1648,8 +1683,10 @@ export const registerRoutes = async (
|
||||
});
|
||||
|
||||
const dynamicSecretProviders = buildDynamicSecretProviders({
|
||||
gatewayService
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
});
|
||||
|
||||
const dynamicSecretQueueService = dynamicSecretLeaseQueueServiceFactory({
|
||||
queueService,
|
||||
dynamicSecretLeaseDAL,
|
||||
@@ -1669,6 +1706,7 @@ export const registerRoutes = async (
|
||||
licenseService,
|
||||
kmsService,
|
||||
gatewayDAL,
|
||||
gatewayV2DAL,
|
||||
resourceMetadataDAL
|
||||
});
|
||||
|
||||
@@ -1796,7 +1834,9 @@ export const registerRoutes = async (
|
||||
kmsService,
|
||||
licenseService,
|
||||
gatewayService,
|
||||
gatewayDAL
|
||||
gatewayV2Service,
|
||||
gatewayDAL,
|
||||
gatewayV2DAL
|
||||
});
|
||||
|
||||
const secretSyncService = secretSyncServiceFactory({
|
||||
@@ -1895,7 +1935,8 @@ export const registerRoutes = async (
|
||||
secretQueueService,
|
||||
queueService,
|
||||
appConnectionDAL,
|
||||
gatewayService
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
});
|
||||
|
||||
const certificateAuthorityService = certificateAuthorityServiceFactory({
|
||||
@@ -2121,6 +2162,8 @@ export const registerRoutes = async (
|
||||
kmip: kmipService,
|
||||
kmipOperation: kmipOperationService,
|
||||
gateway: gatewayService,
|
||||
relay: relayService,
|
||||
gatewayV2: gatewayV2Service,
|
||||
secretRotationV2: secretRotationV2Service,
|
||||
microsoftTeams: microsoftTeamsService,
|
||||
assumePrivileges: assumePrivilegeService,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from "@app/ee/services/app-connections/oci";
|
||||
import { getOracleDBConnectionListItem, OracleDBConnectionMethod } from "@app/ee/services/app-connections/oracledb";
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
@@ -219,7 +220,8 @@ export const decryptAppConnectionCredentials = async ({
|
||||
|
||||
export const validateAppConnectionCredentials = async (
|
||||
appConnection: TAppConnectionConfig,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||
): Promise<TAppConnection["credentials"]> => {
|
||||
const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TAppConnectionCredentialsValidator> = {
|
||||
[AppConnection.AWS]: validateAwsConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
@@ -264,7 +266,7 @@ export const validateAppConnectionCredentials = async (
|
||||
[AppConnection.Netlify]: validateNetlifyConnectionCredentials as TAppConnectionCredentialsValidator
|
||||
};
|
||||
|
||||
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection, gatewayService);
|
||||
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection, gatewayService, gatewayV2Service);
|
||||
};
|
||||
|
||||
export const getAppConnectionMethodName = (method: TAppConnection["method"]) => {
|
||||
|
||||
@@ -5,6 +5,8 @@ import { ociConnectionService } from "@app/ee/services/app-connections/oci/oci-c
|
||||
import { ValidateOracleDBConnectionCredentialsSchema } from "@app/ee/services/app-connections/oracledb";
|
||||
import { TGatewayDALFactory } from "@app/ee/services/gateway/gateway-dal";
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TGatewayV2DALFactory } from "@app/ee/services/gateway-v2/gateway-v2-dal";
|
||||
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import {
|
||||
OrgPermissionAppConnectionActions,
|
||||
@@ -110,7 +112,9 @@ export type TAppConnectionServiceFactoryDep = {
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">;
|
||||
gatewayDAL: Pick<TGatewayDALFactory, "find">;
|
||||
gatewayV2DAL: Pick<TGatewayV2DALFactory, "find">;
|
||||
};
|
||||
|
||||
export type TAppConnectionServiceFactory = ReturnType<typeof appConnectionServiceFactory>;
|
||||
@@ -162,7 +166,9 @@ export const appConnectionServiceFactory = ({
|
||||
kmsService,
|
||||
licenseService,
|
||||
gatewayService,
|
||||
gatewayDAL
|
||||
gatewayV2Service,
|
||||
gatewayDAL,
|
||||
gatewayV2DAL
|
||||
}: TAppConnectionServiceFactoryDep) => {
|
||||
const listAppConnectionsByOrg = async (actor: OrgServiceActor, app?: AppConnection) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
@@ -266,7 +272,8 @@ export const appConnectionServiceFactory = ({
|
||||
);
|
||||
|
||||
const [gateway] = await gatewayDAL.find({ id: gatewayId, orgId: actor.orgId });
|
||||
if (!gateway) {
|
||||
const [gatewayV2] = await gatewayV2DAL.find({ id: gatewayId, orgId: actor.orgId });
|
||||
if (!gateway && !gatewayV2) {
|
||||
throw new NotFoundError({
|
||||
message: `Gateway with ID ${gatewayId} not found for org`
|
||||
});
|
||||
@@ -288,7 +295,8 @@ export const appConnectionServiceFactory = ({
|
||||
orgId: actor.orgId,
|
||||
gatewayId
|
||||
} as TAppConnectionConfig,
|
||||
gatewayService
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -321,7 +329,8 @@ export const appConnectionServiceFactory = ({
|
||||
gatewayId
|
||||
} as TAppConnectionConfig,
|
||||
(platformCredentials) => createConnection(platformCredentials),
|
||||
gatewayService
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
);
|
||||
} else {
|
||||
connection = await createConnection(validatedCredentials);
|
||||
@@ -377,7 +386,8 @@ export const appConnectionServiceFactory = ({
|
||||
|
||||
if (gatewayId) {
|
||||
const [gateway] = await gatewayDAL.find({ id: gatewayId, orgId: actor.orgId });
|
||||
if (!gateway) {
|
||||
const [gatewayV2] = await gatewayV2DAL.find({ id: gatewayId, orgId: actor.orgId });
|
||||
if (!gateway && !gatewayV2) {
|
||||
throw new NotFoundError({
|
||||
message: `Gateway with ID ${gatewayId} not found for org`
|
||||
});
|
||||
@@ -417,7 +427,8 @@ export const appConnectionServiceFactory = ({
|
||||
method,
|
||||
gatewayId
|
||||
} as TAppConnectionConfig,
|
||||
gatewayService
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
);
|
||||
|
||||
if (!updatedCredentials)
|
||||
@@ -458,7 +469,8 @@ export const appConnectionServiceFactory = ({
|
||||
gatewayId
|
||||
} as TAppConnectionConfig,
|
||||
(platformCredentials) => updateConnection(platformCredentials),
|
||||
gatewayService
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
);
|
||||
} else {
|
||||
updatedConnection = await updateConnection(updatedCredentials);
|
||||
@@ -588,7 +600,7 @@ export const appConnectionServiceFactory = ({
|
||||
deleteAppConnection,
|
||||
connectAppConnectionById,
|
||||
listAvailableAppConnectionsForUser,
|
||||
github: githubConnectionService(connectAppConnectionById, gatewayService),
|
||||
github: githubConnectionService(connectAppConnectionById, gatewayService, gatewayV2Service),
|
||||
githubRadar: githubRadarConnectionService(connectAppConnectionById),
|
||||
gcp: gcpConnectionService(connectAppConnectionById),
|
||||
databricks: databricksConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
TValidateOracleDBConnectionCredentialsSchema
|
||||
} from "@app/ee/services/app-connections/oracledb";
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||
import { TSqlConnectionConfig } from "@app/services/app-connection/shared/sql/sql-connection-types";
|
||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||
@@ -411,13 +412,15 @@ export type TListAwsConnectionIamUsers = {
|
||||
|
||||
export type TAppConnectionCredentialsValidator = (
|
||||
appConnection: TAppConnectionConfig,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||
) => Promise<TAppConnection["credentials"]>;
|
||||
|
||||
export type TAppConnectionTransitionCredentialsToPlatform = (
|
||||
appConnection: TAppConnectionConfig,
|
||||
callback: (credentials: TAppConnection["credentials"]) => Promise<TAppConnectionRaw>,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||
) => Promise<TAppConnectionRaw>;
|
||||
|
||||
export type TAppConnectionBaseConfig = {
|
||||
|
||||
@@ -4,11 +4,13 @@ import RE2 from "re2";
|
||||
|
||||
import { verifyHostInputValidity } from "@app/ee/services/dynamic-secret/dynamic-secret-fns";
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { request as httpRequest } from "@app/lib/config/request";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
import { BadRequestError, ForbiddenRequestError, InternalServerError } from "@app/lib/errors";
|
||||
import { GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
|
||||
import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||
import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns";
|
||||
@@ -49,6 +51,7 @@ export const getGitHubInstanceApiUrl = async (config: {
|
||||
export const requestWithGitHubGateway = async <T>(
|
||||
appConnection: { gatewayId?: string | null },
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">,
|
||||
requestConfig: AxiosRequestConfig
|
||||
): Promise<AxiosResponse<T>> => {
|
||||
const { gatewayId } = appConnection;
|
||||
@@ -63,6 +66,52 @@ export const requestWithGitHubGateway = async <T>(
|
||||
await blockLocalAndPrivateIpAddresses(url.toString());
|
||||
|
||||
const [targetHost] = await verifyHostInputValidity(url.host, true);
|
||||
const gatewayConnectionDetails = await gatewayV2Service.getPlatformConnectionDetailsByGatewayId({
|
||||
gatewayId,
|
||||
targetHost,
|
||||
targetPort: 443
|
||||
});
|
||||
|
||||
if (gatewayConnectionDetails) {
|
||||
return withGatewayV2Proxy(
|
||||
async (proxyPort) => {
|
||||
const httpsAgent = new https.Agent({
|
||||
servername: targetHost
|
||||
});
|
||||
|
||||
url.protocol = "https:";
|
||||
url.host = `localhost:${proxyPort}`;
|
||||
|
||||
const finalRequestConfig: AxiosRequestConfig = {
|
||||
...requestConfig,
|
||||
url: url.toString(),
|
||||
httpsAgent,
|
||||
headers: {
|
||||
...requestConfig.headers,
|
||||
Host: targetHost
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
return await httpRequest.request(finalRequestConfig);
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError;
|
||||
logger.error(
|
||||
{ message: axiosError.message, data: axiosError.response?.data },
|
||||
"Error during GitHub gateway request:"
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
{
|
||||
protocol: GatewayProxyProtocol.Tcp,
|
||||
relayHost: gatewayConnectionDetails.relayHost,
|
||||
gateway: gatewayConnectionDetails.gateway,
|
||||
relay: gatewayConnectionDetails.relay
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const relayDetails = await gatewayService.fnGetGatewayClientTlsByGatewayId(gatewayId);
|
||||
const [relayHost, relayPort] = relayDetails.relayAddress.split(":");
|
||||
|
||||
@@ -115,7 +164,8 @@ export const requestWithGitHubGateway = async <T>(
|
||||
|
||||
export const getGitHubAppAuthToken = async (
|
||||
appConnection: TGitHubConnection,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||
) => {
|
||||
const appCfg = getConfig();
|
||||
const appId = appCfg.INF_APP_CONNECTION_GITHUB_APP_ID;
|
||||
@@ -151,6 +201,7 @@ export const getGitHubAppAuthToken = async (
|
||||
const response = await requestWithGitHubGateway<{ token: string; expires_at: string }>(
|
||||
appConnection,
|
||||
gatewayService,
|
||||
gatewayV2Service,
|
||||
{
|
||||
url: `https://${apiBaseUrl}/app/installations/${installationId}/access_tokens`,
|
||||
method: "POST",
|
||||
@@ -191,6 +242,7 @@ function extractNextPageUrl(linkHeader: string | undefined): string | null {
|
||||
export const makePaginatedGitHubRequest = async <T, R = T[]>(
|
||||
appConnection: TGitHubConnection,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">,
|
||||
path: string,
|
||||
dataMapper?: (data: R) => T[]
|
||||
): Promise<T[]> => {
|
||||
@@ -199,7 +251,7 @@ export const makePaginatedGitHubRequest = async <T, R = T[]>(
|
||||
const token =
|
||||
method === GitHubConnectionMethod.OAuth
|
||||
? credentials.accessToken
|
||||
: await getGitHubAppAuthToken(appConnection, gatewayService);
|
||||
: await getGitHubAppAuthToken(appConnection, gatewayService, gatewayV2Service);
|
||||
|
||||
const baseUrl = `https://${await getGitHubInstanceApiUrl(appConnection)}${path}`;
|
||||
const initialUrlObj = new URL(baseUrl);
|
||||
@@ -209,15 +261,20 @@ export const makePaginatedGitHubRequest = async <T, R = T[]>(
|
||||
const maxIterations = 1000;
|
||||
|
||||
// Make initial request to get link header
|
||||
const firstResponse: AxiosResponse<R> = await requestWithGitHubGateway<R>(appConnection, gatewayService, {
|
||||
url: initialUrlObj.toString(),
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
"X-GitHub-Api-Version": "2022-11-28"
|
||||
const firstResponse: AxiosResponse<R> = await requestWithGitHubGateway<R>(
|
||||
appConnection,
|
||||
gatewayService,
|
||||
gatewayV2Service,
|
||||
{
|
||||
url: initialUrlObj.toString(),
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
"X-GitHub-Api-Version": "2022-11-28"
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
const firstPageItems = dataMapper ? dataMapper(firstResponse.data) : (firstResponse.data as unknown as T[]);
|
||||
results = results.concat(firstPageItems);
|
||||
@@ -237,7 +294,7 @@ export const makePaginatedGitHubRequest = async <T, R = T[]>(
|
||||
pageUrlObj.searchParams.set("page", pageNum.toString());
|
||||
|
||||
pageRequests.push(
|
||||
requestWithGitHubGateway<R>(appConnection, gatewayService, {
|
||||
requestWithGitHubGateway<R>(appConnection, gatewayService, gatewayV2Service, {
|
||||
url: pageUrlObj.toString(),
|
||||
method: "GET",
|
||||
headers: {
|
||||
@@ -261,15 +318,20 @@ export const makePaginatedGitHubRequest = async <T, R = T[]>(
|
||||
|
||||
while (url && i < maxIterations) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const response: AxiosResponse<R> = await requestWithGitHubGateway<R>(appConnection, gatewayService, {
|
||||
url,
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
"X-GitHub-Api-Version": "2022-11-28"
|
||||
const response: AxiosResponse<R> = await requestWithGitHubGateway<R>(
|
||||
appConnection,
|
||||
gatewayService,
|
||||
gatewayV2Service,
|
||||
{
|
||||
url,
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
"X-GitHub-Api-Version": "2022-11-28"
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
const items = dataMapper ? dataMapper(response.data) : (response.data as unknown as T[]);
|
||||
results = results.concat(items);
|
||||
@@ -308,30 +370,39 @@ type GitHubEnvironment = {
|
||||
|
||||
export const getGitHubRepositories = async (
|
||||
appConnection: TGitHubConnection,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||
) => {
|
||||
if (appConnection.method === GitHubConnectionMethod.App) {
|
||||
return makePaginatedGitHubRequest<GitHubRepository, { repositories: GitHubRepository[] }>(
|
||||
appConnection,
|
||||
gatewayService,
|
||||
gatewayV2Service,
|
||||
"/installation/repositories",
|
||||
(data) => data.repositories
|
||||
);
|
||||
}
|
||||
|
||||
const repos = await makePaginatedGitHubRequest<GitHubRepository>(appConnection, gatewayService, "/user/repos");
|
||||
const repos = await makePaginatedGitHubRequest<GitHubRepository>(
|
||||
appConnection,
|
||||
gatewayService,
|
||||
gatewayV2Service,
|
||||
"/user/repos"
|
||||
);
|
||||
|
||||
return repos.filter((repo) => repo.permissions?.admin);
|
||||
};
|
||||
|
||||
export const getGitHubOrganizations = async (
|
||||
appConnection: TGitHubConnection,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||
) => {
|
||||
if (appConnection.method === GitHubConnectionMethod.App) {
|
||||
const installationRepositories = await makePaginatedGitHubRequest<
|
||||
GitHubRepository,
|
||||
{ repositories: GitHubRepository[] }
|
||||
>(appConnection, gatewayService, "/installation/repositories", (data) => data.repositories);
|
||||
>(appConnection, gatewayService, gatewayV2Service, "/installation/repositories", (data) => data.repositories);
|
||||
|
||||
const organizationMap: Record<string, GitHubOrganization> = {};
|
||||
installationRepositories.forEach((repo) => {
|
||||
@@ -343,12 +414,13 @@ export const getGitHubOrganizations = async (
|
||||
return Object.values(organizationMap);
|
||||
}
|
||||
|
||||
return makePaginatedGitHubRequest<GitHubOrganization>(appConnection, gatewayService, "/user/orgs");
|
||||
return makePaginatedGitHubRequest<GitHubOrganization>(appConnection, gatewayService, gatewayV2Service, "/user/orgs");
|
||||
};
|
||||
|
||||
export const getGitHubEnvironments = async (
|
||||
appConnection: TGitHubConnection,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">,
|
||||
owner: string,
|
||||
repo: string
|
||||
) => {
|
||||
@@ -356,6 +428,7 @@ export const getGitHubEnvironments = async (
|
||||
return await makePaginatedGitHubRequest<GitHubEnvironment, { environments: GitHubEnvironment[] }>(
|
||||
appConnection,
|
||||
gatewayService,
|
||||
gatewayV2Service,
|
||||
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/environments`,
|
||||
(data) => data.environments
|
||||
);
|
||||
@@ -383,7 +456,8 @@ export function isGithubErrorResponse(data: GithubTokenRespData): data is Github
|
||||
|
||||
export const validateGitHubConnectionCredentials = async (
|
||||
config: TGitHubConnectionConfig,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||
) => {
|
||||
const { credentials, method } = config;
|
||||
const {
|
||||
@@ -419,7 +493,7 @@ export const validateGitHubConnectionCredentials = async (
|
||||
const host = credentials.host || "github.com";
|
||||
|
||||
try {
|
||||
tokenResp = await requestWithGitHubGateway<GithubTokenRespData>(config, gatewayService, {
|
||||
tokenResp = await requestWithGitHubGateway<GithubTokenRespData>(config, gatewayService, gatewayV2Service, {
|
||||
url: `https://${host}/login/oauth/access_token`,
|
||||
method: "POST",
|
||||
data: {
|
||||
@@ -471,7 +545,7 @@ export const validateGitHubConnectionCredentials = async (
|
||||
id: number;
|
||||
};
|
||||
}[];
|
||||
}>(config, gatewayService, {
|
||||
}>(config, gatewayService, gatewayV2Service, {
|
||||
url: `https://${await getGitHubInstanceApiUrl(config)}/user/installations`,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import {
|
||||
@@ -22,12 +23,13 @@ type TListGitHubEnvironmentsDTO = {
|
||||
|
||||
export const githubConnectionService = (
|
||||
getAppConnection: TGetAppConnectionFunc,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||
) => {
|
||||
const listRepositories = async (connectionId: string, actor: OrgServiceActor) => {
|
||||
const appConnection = await getAppConnection(AppConnection.GitHub, connectionId, actor);
|
||||
|
||||
const repositories = await getGitHubRepositories(appConnection, gatewayService);
|
||||
const repositories = await getGitHubRepositories(appConnection, gatewayService, gatewayV2Service);
|
||||
|
||||
return repositories;
|
||||
};
|
||||
@@ -35,7 +37,7 @@ export const githubConnectionService = (
|
||||
const listOrganizations = async (connectionId: string, actor: OrgServiceActor) => {
|
||||
const appConnection = await getAppConnection(AppConnection.GitHub, connectionId, actor);
|
||||
|
||||
const organizations = await getGitHubOrganizations(appConnection, gatewayService);
|
||||
const organizations = await getGitHubOrganizations(appConnection, gatewayService, gatewayV2Service);
|
||||
|
||||
return organizations;
|
||||
};
|
||||
@@ -46,7 +48,7 @@ export const githubConnectionService = (
|
||||
) => {
|
||||
const appConnection = await getAppConnection(AppConnection.GitHub, connectionId, actor);
|
||||
|
||||
const environments = await getGitHubEnvironments(appConnection, gatewayService, owner, repo);
|
||||
const environments = await getGitHubEnvironments(appConnection, gatewayService, gatewayV2Service, owner, repo);
|
||||
|
||||
return environments;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import https from "https";
|
||||
|
||||
import { verifyHostInputValidity } from "@app/ee/services/dynamic-secret/dynamic-secret-fns";
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
@@ -144,7 +145,9 @@ export const getHCVaultAccessToken = async (
|
||||
|
||||
export const validateHCVaultConnectionCredentials = async (
|
||||
connection: THCVaultConnection,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||
) => {
|
||||
const instanceUrl = await getHCVaultInstanceUrl(connection);
|
||||
|
||||
|
||||
@@ -2,12 +2,14 @@ import knex, { Knex } from "knex";
|
||||
|
||||
import { verifyHostInputValidity } from "@app/ee/services/dynamic-secret/dynamic-secret-fns";
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||
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 { GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
|
||||
import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2";
|
||||
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";
|
||||
@@ -104,12 +106,49 @@ export const getSqlConnectionClient = async (appConnection: Pick<TSqlConnection,
|
||||
export const executeWithPotentialGateway = async <T>(
|
||||
config: TSqlConnectionConfig,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">,
|
||||
operation: (client: Knex) => Promise<T>
|
||||
): Promise<T> => {
|
||||
const { credentials, app, gatewayId } = config;
|
||||
|
||||
if (gatewayId && gatewayService) {
|
||||
if (gatewayId && gatewayService && gatewayV2Service) {
|
||||
const [targetHost] = await verifyHostInputValidity(credentials.host, true);
|
||||
const platformConnectionDetails = await gatewayV2Service.getPlatformConnectionDetailsByGatewayId({
|
||||
gatewayId,
|
||||
targetHost,
|
||||
targetPort: credentials.port
|
||||
});
|
||||
|
||||
if (platformConnectionDetails) {
|
||||
return withGatewayV2Proxy(
|
||||
async (proxyPort) => {
|
||||
const client = knex({
|
||||
client: SQL_CONNECTION_CLIENT_MAP[app],
|
||||
connection: {
|
||||
database: credentials.database,
|
||||
port: proxyPort,
|
||||
host: "localhost",
|
||||
user: credentials.username,
|
||||
password: credentials.password,
|
||||
connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT,
|
||||
...getConnectionConfig({ app, credentials })
|
||||
}
|
||||
});
|
||||
try {
|
||||
return await operation(client);
|
||||
} finally {
|
||||
await client.destroy();
|
||||
}
|
||||
},
|
||||
{
|
||||
protocol: GatewayProxyProtocol.Tcp,
|
||||
relayHost: platformConnectionDetails.relayHost,
|
||||
gateway: platformConnectionDetails.gateway,
|
||||
relay: platformConnectionDetails.relay
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const relayDetails = await gatewayService.fnGetGatewayClientTlsByGatewayId(gatewayId);
|
||||
const [relayHost, relayPort] = relayDetails.relayAddress.split(":");
|
||||
|
||||
@@ -161,10 +200,11 @@ export const executeWithPotentialGateway = async <T>(
|
||||
|
||||
export const validateSqlConnectionCredentials = async (
|
||||
config: TSqlConnectionConfig,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||
) => {
|
||||
try {
|
||||
await executeWithPotentialGateway(config, gatewayService, async (client) => {
|
||||
await executeWithPotentialGateway(config, gatewayService, gatewayV2Service, async (client) => {
|
||||
await client.raw(config.app === AppConnection.OracleDB ? `SELECT 1 FROM DUAL` : `Select 1`);
|
||||
});
|
||||
return config.credentials;
|
||||
@@ -191,14 +231,15 @@ export const SQL_CONNECTION_ALTER_LOGIN_STATEMENT: Record<
|
||||
export const transferSqlConnectionCredentialsToPlatform = async (
|
||||
config: TSqlConnectionConfig,
|
||||
callback: (credentials: TSqlConnectionConfig["credentials"]) => Promise<TAppConnectionRaw>,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||
) => {
|
||||
const { credentials, app } = config;
|
||||
|
||||
const newPassword = alphaNumericNanoId(32);
|
||||
|
||||
try {
|
||||
return await executeWithPotentialGateway(config, gatewayService, (client) => {
|
||||
return await executeWithPotentialGateway(config, gatewayService, gatewayV2Service, (client) => {
|
||||
return client.transaction(async (tx) => {
|
||||
await tx.raw(
|
||||
...SQL_CONNECTION_ALTER_LOGIN_STATEMENT[app]({ username: credentials.username, password: newPassword })
|
||||
|
||||
@@ -52,6 +52,9 @@ export const constructPemChainFromCerts = (certificates: x509.X509Certificate[])
|
||||
.join("\n")
|
||||
.trim();
|
||||
|
||||
export const prependCertToPemChain = (cert: x509.X509Certificate, pemChain: string) =>
|
||||
`${cert.toString("pem")}\n${pemChain}`;
|
||||
|
||||
export const splitPemChain = (pemText: string) => {
|
||||
const re2Pattern = new RE2("-----BEGIN CERTIFICATE-----[^-]+-----END CERTIFICATE-----", "g");
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import RE2 from "re2";
|
||||
import { IdentityAuthMethod, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas";
|
||||
import { TGatewayDALFactory } from "@app/ee/services/gateway/gateway-dal";
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TGatewayV2DALFactory } from "@app/ee/services/gateway-v2/gateway-v2-dal";
|
||||
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import {
|
||||
OrgPermissionGatewayActions,
|
||||
@@ -21,6 +23,7 @@ import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { GatewayHttpProxyActions, GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
|
||||
import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2";
|
||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||
import { logger } from "@app/lib/logger";
|
||||
|
||||
@@ -54,11 +57,15 @@ type TIdentityKubernetesAuthServiceFactoryDep = {
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
gatewayService: TGatewayServiceFactory;
|
||||
gatewayV2Service: TGatewayV2ServiceFactory;
|
||||
gatewayDAL: Pick<TGatewayDALFactory, "find">;
|
||||
gatewayV2DAL: Pick<TGatewayV2DALFactory, "find">;
|
||||
};
|
||||
|
||||
export type TIdentityKubernetesAuthServiceFactory = ReturnType<typeof identityKubernetesAuthServiceFactory>;
|
||||
|
||||
const GATEWAY_AUTH_DEFAULT_HOST = "https://kubernetes.default.svc.cluster.local";
|
||||
|
||||
export const identityKubernetesAuthServiceFactory = ({
|
||||
identityKubernetesAuthDAL,
|
||||
identityOrgMembershipDAL,
|
||||
@@ -66,7 +73,9 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
permissionService,
|
||||
licenseService,
|
||||
gatewayService,
|
||||
gatewayV2Service,
|
||||
gatewayDAL,
|
||||
gatewayV2DAL,
|
||||
kmsService
|
||||
}: TIdentityKubernetesAuthServiceFactoryDep) => {
|
||||
const $gatewayProxyWrapper = async <T>(
|
||||
@@ -79,6 +88,42 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
},
|
||||
gatewayCallback: (host: string, port: number, httpsAgent?: https.Agent) => Promise<T>
|
||||
): Promise<T> => {
|
||||
const gatewayV2ConnectionDetails = await gatewayV2Service.getPlatformConnectionDetailsByGatewayId({
|
||||
gatewayId: inputs.gatewayId,
|
||||
targetHost: inputs.targetHost ?? GATEWAY_AUTH_DEFAULT_HOST,
|
||||
targetPort: inputs.targetPort ?? 443
|
||||
});
|
||||
|
||||
if (gatewayV2ConnectionDetails) {
|
||||
let httpsAgent: https.Agent | undefined;
|
||||
if (!inputs.reviewTokenThroughGateway) {
|
||||
httpsAgent = new https.Agent({
|
||||
ca: inputs.caCert,
|
||||
rejectUnauthorized: Boolean(inputs.caCert)
|
||||
});
|
||||
}
|
||||
|
||||
const callbackResult = await withGatewayV2Proxy(
|
||||
async (port) => {
|
||||
const res = await gatewayCallback(
|
||||
inputs.reviewTokenThroughGateway ? "http://localhost" : "https://localhost",
|
||||
port,
|
||||
httpsAgent
|
||||
);
|
||||
return res;
|
||||
},
|
||||
{
|
||||
protocol: inputs.reviewTokenThroughGateway ? GatewayProxyProtocol.Http : GatewayProxyProtocol.Tcp,
|
||||
relayHost: gatewayV2ConnectionDetails.relayHost,
|
||||
gateway: gatewayV2ConnectionDetails.gateway,
|
||||
relay: gatewayV2ConnectionDetails.relay,
|
||||
httpsAgent
|
||||
}
|
||||
);
|
||||
|
||||
return callbackResult;
|
||||
}
|
||||
|
||||
const relayDetails = await gatewayService.fnGetGatewayClientTlsByGatewayId(inputs.gatewayId);
|
||||
const [relayHost, relayPort] = relayDetails.relayAddress.split(":");
|
||||
|
||||
@@ -277,7 +322,7 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
let data: TCreateTokenReviewResponse | undefined;
|
||||
|
||||
if (identityKubernetesAuth.tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Gateway) {
|
||||
if (!identityKubernetesAuth.gatewayId) {
|
||||
if (!identityKubernetesAuth.gatewayId && !identityKubernetesAuth.gatewayV2Id) {
|
||||
throw new BadRequestError({
|
||||
message: "Gateway ID is required when token review mode is set to Gateway"
|
||||
});
|
||||
@@ -285,7 +330,7 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
|
||||
data = await $gatewayProxyWrapper(
|
||||
{
|
||||
gatewayId: identityKubernetesAuth.gatewayId,
|
||||
gatewayId: (identityKubernetesAuth.gatewayV2Id ?? identityKubernetesAuth.gatewayId) as string,
|
||||
reviewTokenThroughGateway: true
|
||||
},
|
||||
tokenReviewCallbackThroughGateway
|
||||
@@ -304,17 +349,18 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
|
||||
const [k8sHost, k8sPort] = kubernetesHost.split(":");
|
||||
|
||||
data = identityKubernetesAuth.gatewayId
|
||||
? await $gatewayProxyWrapper(
|
||||
{
|
||||
gatewayId: identityKubernetesAuth.gatewayId,
|
||||
targetHost: k8sHost,
|
||||
targetPort: k8sPort ? Number(k8sPort) : 443,
|
||||
reviewTokenThroughGateway: false
|
||||
},
|
||||
tokenReviewCallbackRaw
|
||||
)
|
||||
: await tokenReviewCallbackRaw();
|
||||
data =
|
||||
identityKubernetesAuth.gatewayId || identityKubernetesAuth.gatewayV2Id
|
||||
? await $gatewayProxyWrapper(
|
||||
{
|
||||
gatewayId: (identityKubernetesAuth.gatewayV2Id ?? identityKubernetesAuth.gatewayId) as string,
|
||||
targetHost: k8sHost,
|
||||
targetPort: k8sPort ? Number(k8sPort) : 443,
|
||||
reviewTokenThroughGateway: false
|
||||
},
|
||||
tokenReviewCallbackRaw
|
||||
)
|
||||
: await tokenReviewCallbackRaw();
|
||||
} else {
|
||||
throw new BadRequestError({
|
||||
message: `Invalid token review mode: ${identityKubernetesAuth.tokenReviewMode}`
|
||||
@@ -490,14 +536,20 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
||||
});
|
||||
|
||||
let isGatewayV1 = true;
|
||||
if (gatewayId) {
|
||||
const [gateway] = await gatewayDAL.find({ id: gatewayId, orgId: identityMembershipOrg.orgId });
|
||||
if (!gateway) {
|
||||
const [gatewayV2] = await gatewayV2DAL.find({ id: gatewayId, orgId: identityMembershipOrg.orgId });
|
||||
if (!gateway && !gatewayV2) {
|
||||
throw new NotFoundError({
|
||||
message: `Gateway with ID ${gatewayId} not found`
|
||||
});
|
||||
}
|
||||
|
||||
if (!gateway) {
|
||||
isGatewayV1 = false;
|
||||
}
|
||||
|
||||
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
@@ -528,7 +580,8 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
accessTokenMaxTTL,
|
||||
accessTokenTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
gatewayId,
|
||||
gatewayId: isGatewayV1 ? gatewayId : null,
|
||||
gatewayV2Id: isGatewayV1 ? null : gatewayId,
|
||||
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps),
|
||||
encryptedKubernetesTokenReviewerJwt: tokenReviewerJwt
|
||||
? encryptor({ plainText: Buffer.from(tokenReviewerJwt) }).cipherTextBlob
|
||||
@@ -608,14 +661,21 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
||||
});
|
||||
|
||||
let isGatewayV1 = true;
|
||||
if (gatewayId) {
|
||||
const [gateway] = await gatewayDAL.find({ id: gatewayId, orgId: identityMembershipOrg.orgId });
|
||||
if (!gateway) {
|
||||
const [gatewayV2] = await gatewayV2DAL.find({ id: gatewayId, orgId: identityMembershipOrg.orgId });
|
||||
|
||||
if (!gateway && !gatewayV2) {
|
||||
throw new NotFoundError({
|
||||
message: `Gateway with ID ${gatewayId} not found`
|
||||
});
|
||||
}
|
||||
|
||||
if (!gateway) {
|
||||
isGatewayV1 = false;
|
||||
}
|
||||
|
||||
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
@@ -629,13 +689,18 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
);
|
||||
}
|
||||
|
||||
const shouldUpdateGatewayId = Boolean(gatewayId);
|
||||
const gatewayIdValue = isGatewayV1 ? gatewayId : null;
|
||||
const gatewayV2IdValue = isGatewayV1 ? null : gatewayId;
|
||||
|
||||
const updateQuery: TIdentityKubernetesAuthsUpdate = {
|
||||
kubernetesHost,
|
||||
tokenReviewMode,
|
||||
allowedNamespaces,
|
||||
allowedNames,
|
||||
allowedAudience,
|
||||
gatewayId,
|
||||
gatewayId: shouldUpdateGatewayId ? gatewayIdValue : undefined,
|
||||
gatewayV2Id: shouldUpdateGatewayId ? gatewayV2IdValue : undefined,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
@@ -730,7 +795,13 @@ export const identityKubernetesAuthServiceFactory = ({
|
||||
}).toString();
|
||||
}
|
||||
|
||||
return { ...identityKubernetesAuth, caCert, tokenReviewerJwt, orgId: identityMembershipOrg.orgId };
|
||||
return {
|
||||
...identityKubernetesAuth,
|
||||
caCert,
|
||||
tokenReviewerJwt,
|
||||
orgId: identityMembershipOrg.orgId,
|
||||
gatewayId: identityKubernetesAuth.gatewayId ?? identityKubernetesAuth.gatewayV2Id
|
||||
};
|
||||
};
|
||||
|
||||
const revokeIdentityKubernetesAuth = async ({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import sodium from "libsodium-wrappers";
|
||||
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||
import {
|
||||
getGitHubAppAuthToken,
|
||||
getGitHubInstanceApiUrl,
|
||||
@@ -20,7 +21,8 @@ import { TGitHubPublicKey, TGitHubSecret, TGitHubSecretPayload, TGitHubSyncWithC
|
||||
|
||||
const getEncryptedSecrets = async (
|
||||
secretSync: TGitHubSyncWithCredentials,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||
) => {
|
||||
const { destinationConfig, connection } = secretSync;
|
||||
|
||||
@@ -44,6 +46,7 @@ const getEncryptedSecrets = async (
|
||||
return makePaginatedGitHubRequest<TGitHubSecret, { secrets: TGitHubSecret[] }>(
|
||||
connection,
|
||||
gatewayService,
|
||||
gatewayV2Service,
|
||||
path,
|
||||
(data) => data.secrets
|
||||
);
|
||||
@@ -52,6 +55,7 @@ const getEncryptedSecrets = async (
|
||||
const getPublicKey = async (
|
||||
secretSync: TGitHubSyncWithCredentials,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">,
|
||||
token: string
|
||||
) => {
|
||||
const { destinationConfig, connection } = secretSync;
|
||||
@@ -73,7 +77,7 @@ const getPublicKey = async (
|
||||
}
|
||||
}
|
||||
|
||||
const response = await requestWithGitHubGateway<TGitHubPublicKey>(connection, gatewayService, {
|
||||
const response = await requestWithGitHubGateway<TGitHubPublicKey>(connection, gatewayService, gatewayV2Service, {
|
||||
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
|
||||
method: "GET",
|
||||
headers: {
|
||||
@@ -89,6 +93,7 @@ const getPublicKey = async (
|
||||
const deleteSecret = async (
|
||||
secretSync: TGitHubSyncWithCredentials,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">,
|
||||
token: string,
|
||||
encryptedSecret: TGitHubSecret
|
||||
) => {
|
||||
@@ -111,7 +116,7 @@ const deleteSecret = async (
|
||||
}
|
||||
}
|
||||
|
||||
await requestWithGitHubGateway(connection, gatewayService, {
|
||||
await requestWithGitHubGateway(connection, gatewayService, gatewayV2Service, {
|
||||
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
@@ -125,6 +130,7 @@ const deleteSecret = async (
|
||||
const putSecret = async (
|
||||
secretSync: TGitHubSyncWithCredentials,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">,
|
||||
token: string,
|
||||
payload: TGitHubSecretPayload
|
||||
) => {
|
||||
@@ -157,7 +163,7 @@ const putSecret = async (
|
||||
}
|
||||
}
|
||||
|
||||
await requestWithGitHubGateway(connection, gatewayService, {
|
||||
await requestWithGitHubGateway(connection, gatewayService, gatewayV2Service, {
|
||||
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
|
||||
method: "PUT",
|
||||
headers: {
|
||||
@@ -173,7 +179,8 @@ export const GithubSyncFns = {
|
||||
syncSecrets: async (
|
||||
secretSync: TGitHubSyncWithCredentials,
|
||||
ogSecretMap: TSecretMap,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||
) => {
|
||||
const secretMap = Object.fromEntries(Object.entries(ogSecretMap).map(([i, v]) => [i.toUpperCase(), v]));
|
||||
|
||||
@@ -207,10 +214,10 @@ export const GithubSyncFns = {
|
||||
const token =
|
||||
connection.method === GitHubConnectionMethod.OAuth
|
||||
? connection.credentials.accessToken
|
||||
: await getGitHubAppAuthToken(connection, gatewayService);
|
||||
: await getGitHubAppAuthToken(connection, gatewayService, gatewayV2Service);
|
||||
|
||||
const encryptedSecrets = await getEncryptedSecrets(secretSync, gatewayService);
|
||||
const publicKey = await getPublicKey(secretSync, gatewayService, token);
|
||||
const encryptedSecrets = await getEncryptedSecrets(secretSync, gatewayService, gatewayV2Service);
|
||||
const publicKey = await getPublicKey(secretSync, gatewayService, gatewayV2Service, token);
|
||||
|
||||
await sodium.ready;
|
||||
for await (const key of Object.keys(secretMap)) {
|
||||
@@ -225,7 +232,7 @@ export const GithubSyncFns = {
|
||||
const encryptedSecretValue = sodium.to_base64(encryptedBytes, sodium.base64_variants.ORIGINAL);
|
||||
|
||||
try {
|
||||
await putSecret(secretSync, gatewayService, token, {
|
||||
await putSecret(secretSync, gatewayService, gatewayV2Service, token, {
|
||||
secret_name: key,
|
||||
encrypted_value: encryptedSecretValue,
|
||||
key_id: publicKey.key_id
|
||||
@@ -246,7 +253,7 @@ export const GithubSyncFns = {
|
||||
continue;
|
||||
|
||||
if (!(encryptedSecret.name in secretMap)) {
|
||||
await deleteSecret(secretSync, gatewayService, token, encryptedSecret);
|
||||
await deleteSecret(secretSync, gatewayService, gatewayV2Service, token, encryptedSecret);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -256,7 +263,8 @@ export const GithubSyncFns = {
|
||||
removeSecrets: async (
|
||||
secretSync: TGitHubSyncWithCredentials,
|
||||
ogSecretMap: TSecretMap,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||
) => {
|
||||
const secretMap = Object.fromEntries(Object.entries(ogSecretMap).map(([i, v]) => [i.toUpperCase(), v]));
|
||||
|
||||
@@ -264,13 +272,13 @@ export const GithubSyncFns = {
|
||||
const token =
|
||||
connection.method === GitHubConnectionMethod.OAuth
|
||||
? connection.credentials.accessToken
|
||||
: await getGitHubAppAuthToken(connection, gatewayService);
|
||||
: await getGitHubAppAuthToken(connection, gatewayService, gatewayV2Service);
|
||||
|
||||
const encryptedSecrets = await getEncryptedSecrets(secretSync, gatewayService);
|
||||
const encryptedSecrets = await getEncryptedSecrets(secretSync, gatewayService, gatewayV2Service);
|
||||
|
||||
for await (const encryptedSecret of encryptedSecrets) {
|
||||
if (encryptedSecret.name in secretMap) {
|
||||
await deleteSecret(secretSync, gatewayService, token, encryptedSecret);
|
||||
await deleteSecret(secretSync, gatewayService, gatewayV2Service, token, encryptedSecret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AxiosError } from "axios";
|
||||
import handlebars from "handlebars";
|
||||
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { OCI_VAULT_SYNC_LIST_OPTION, OCIVaultSyncFns } from "@app/ee/services/secret-sync/oci-vault";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
@@ -101,6 +102,7 @@ type TSyncSecretDeps = {
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">;
|
||||
};
|
||||
|
||||
// Add schema to secret keys
|
||||
@@ -195,7 +197,7 @@ export const SecretSyncFns = {
|
||||
syncSecrets: (
|
||||
secretSync: TSecretSyncWithCredentials,
|
||||
secretMap: TSecretMap,
|
||||
{ kmsService, appConnectionDAL, gatewayService }: TSyncSecretDeps
|
||||
{ kmsService, appConnectionDAL, gatewayService, gatewayV2Service }: TSyncSecretDeps
|
||||
): Promise<void> => {
|
||||
const schemaSecretMap = addSchema(secretMap, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema);
|
||||
|
||||
@@ -205,7 +207,7 @@ export const SecretSyncFns = {
|
||||
case SecretSync.AWSSecretsManager:
|
||||
return AwsSecretsManagerSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.GitHub:
|
||||
return GithubSyncFns.syncSecrets(secretSync, schemaSecretMap, gatewayService);
|
||||
return GithubSyncFns.syncSecrets(secretSync, schemaSecretMap, gatewayService, gatewayV2Service);
|
||||
case SecretSync.GCPSecretManager:
|
||||
return GcpSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.AzureKeyVault:
|
||||
@@ -404,7 +406,7 @@ export const SecretSyncFns = {
|
||||
removeSecrets: (
|
||||
secretSync: TSecretSyncWithCredentials,
|
||||
secretMap: TSecretMap,
|
||||
{ kmsService, appConnectionDAL, gatewayService }: TSyncSecretDeps
|
||||
{ kmsService, appConnectionDAL, gatewayService, gatewayV2Service }: TSyncSecretDeps
|
||||
): Promise<void> => {
|
||||
const schemaSecretMap = addSchema(secretMap, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema);
|
||||
|
||||
@@ -414,7 +416,7 @@ export const SecretSyncFns = {
|
||||
case SecretSync.AWSSecretsManager:
|
||||
return AwsSecretsManagerSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.GitHub:
|
||||
return GithubSyncFns.removeSecrets(secretSync, schemaSecretMap, gatewayService);
|
||||
return GithubSyncFns.removeSecrets(secretSync, schemaSecretMap, gatewayService, gatewayV2Service);
|
||||
case SecretSync.GCPSecretManager:
|
||||
return GcpSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.AzureKeyVault:
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Job } from "bullmq";
|
||||
import { ProjectMembershipRole, SecretType } from "@app/db/schemas";
|
||||
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
@@ -98,6 +99,7 @@ type TSecretSyncQueueFactoryDep = {
|
||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">;
|
||||
};
|
||||
|
||||
type SecretSyncActionJob = Job<
|
||||
@@ -139,7 +141,8 @@ export const secretSyncQueueFactory = ({
|
||||
resourceMetadataDAL,
|
||||
folderCommitService,
|
||||
licenseService,
|
||||
gatewayService
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
}: TSecretSyncQueueFactoryDep) => {
|
||||
const appCfg = getConfig();
|
||||
|
||||
@@ -395,7 +398,8 @@ export const secretSyncQueueFactory = ({
|
||||
const importedSecrets = await SecretSyncFns.getSecrets(secretSync, {
|
||||
appConnectionDAL,
|
||||
kmsService,
|
||||
gatewayService
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
});
|
||||
|
||||
if (!Object.keys(importedSecrets).length) return {};
|
||||
@@ -520,7 +524,8 @@ export const secretSyncQueueFactory = ({
|
||||
await SecretSyncFns.syncSecrets(secretSyncWithCredentials, secretMap, {
|
||||
appConnectionDAL,
|
||||
kmsService,
|
||||
gatewayService
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
});
|
||||
|
||||
isSynced = true;
|
||||
@@ -762,7 +767,8 @@ export const secretSyncQueueFactory = ({
|
||||
{
|
||||
appConnectionDAL,
|
||||
kmsService,
|
||||
gatewayService
|
||||
gatewayService,
|
||||
gatewayV2Service
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -4,34 +4,384 @@ description: "Run the Infisical gateway or manage its systemd service"
|
||||
---
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Run gateway">
|
||||
<Tab title="Start gateway">
|
||||
```bash
|
||||
infisical gateway --token=<token>
|
||||
infisical gateway start --name=<name> --relay=<relay-name> --auth-method=<auth-method>
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Install service">
|
||||
<Tab title="Install gateway service">
|
||||
```bash
|
||||
sudo infisical gateway install --token=<token> --domain=<domain>
|
||||
sudo infisical gateway systemd install --token=<token> --domain=<domain> --name=<name> --relay=<relay-name>
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Description
|
||||
|
||||
Run the Infisical gateway in the foreground or manage its systemd service installation. The gateway allows secure communication between your self-hosted Infisical instance and client applications.
|
||||
The Infisical gateway provides secure access to private resources using modern TCP-based SSH tunnel architecture with enhanced security and flexible deployment options.
|
||||
|
||||
The gateway system uses SSH reverse tunnels over TCP, eliminating firewall complexity and providing excellent performance for enterprise environments.
|
||||
|
||||
<Warning>
|
||||
**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.
|
||||
|
||||
</Warning>
|
||||
|
||||
## Subcommands & flags
|
||||
|
||||
<Accordion title="infisical gateway" defaultOpen="true">
|
||||
Run the Infisical gateway in the foreground. The gateway will connect to the relay service and maintain a persistent connection.
|
||||
<Accordion title="infisical gateway start" defaultOpen="true">
|
||||
Run the Infisical gateway component within your VPC. The gateway establishes an SSH reverse tunnel to the specified relay server and provides secure access to private resources.
|
||||
|
||||
```bash
|
||||
infisical gateway --domain=<domain> --auth-method=<auth-method>
|
||||
```
|
||||
```bash
|
||||
infisical gateway start --relay=<relay-name> --name=<name> --auth-method=<auth-method>
|
||||
```
|
||||
|
||||
### Authentication
|
||||
The gateway component:
|
||||
|
||||
The Infisical CLI supports multiple authentication methods. Below are the available authentication methods, with their respective flags.
|
||||
- Establishes outbound SSH reverse tunnels to relay servers (no inbound firewall rules needed)
|
||||
- Authenticates using SSH certificates issued by Infisical
|
||||
- Automatically reconnects if the connection is lost
|
||||
- Provides access to private resources within your network
|
||||
|
||||
### Authentication
|
||||
|
||||
The Infisical CLI supports multiple authentication methods. Below are the available authentication methods, with their respective flags.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Universal Auth">
|
||||
The Universal Auth method is a simple and secure way to authenticate with Infisical. It requires a client ID and a client secret to authenticate with Infisical.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="client-id" type="string" required>
|
||||
Your machine identity client ID.
|
||||
</ParamField>
|
||||
<ParamField query="client-secret" type="string" required>
|
||||
Your machine identity client secret.
|
||||
</ParamField>
|
||||
<ParamField query="auth-method" type="string" required>
|
||||
The authentication method to use. Must be `universal-auth` when using Universal Auth.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
infisical gateway start --auth-method=universal-auth --client-id=<client-id> --client-secret=<client-secret> --relay=<relay-name> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Native Kubernetes">
|
||||
The Native Kubernetes method is used to authenticate with Infisical when running in a Kubernetes environment. It requires a service account token to authenticate with Infisical.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="machine-identity-id" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="service-account-token-path" type="string" optional>
|
||||
Path to the Kubernetes service account token to use. Default: `/var/run/secrets/kubernetes.io/serviceaccount/token`.
|
||||
</ParamField>
|
||||
<ParamField query="auth-method" type="string" required>
|
||||
The authentication method to use. Must be `kubernetes` when using Native Kubernetes.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
|
||||
</ParamField>
|
||||
|
||||
|
||||
```bash
|
||||
infisical gateway start --auth-method=kubernetes --machine-identity-id=<machine-identity-id> --relay=<relay-name> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Native Azure">
|
||||
The Native Azure method is used to authenticate with Infisical when running in an Azure environment.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="machine-identity-id" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="auth-method" type="string" required>
|
||||
The authentication method to use. Must be `azure` when using Native Azure.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
|
||||
|
||||
```bash
|
||||
infisical gateway start --auth-method=azure --machine-identity-id=<machine-identity-id> --relay=<relay-name> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Native GCP ID Token">
|
||||
The Native GCP ID Token method is used to authenticate with Infisical when running in a GCP environment.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="machine-identity-id" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="auth-method" type="string" required>
|
||||
The authentication method to use. Must be `gcp-id-token` when using Native GCP ID Token.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
|
||||
|
||||
```bash
|
||||
infisical gateway start --auth-method=gcp-id-token --machine-identity-id=<machine-identity-id> --relay=<relay-name> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="GCP IAM">
|
||||
The GCP IAM method is used to authenticate with Infisical with a GCP service account key.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="machine-identity-id" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="service-account-key-file-path" type="string" required>
|
||||
Path to your GCP service account key file _(Must be in JSON format!)_
|
||||
</ParamField>
|
||||
<ParamField query="auth-method" type="string" required>
|
||||
The authentication method to use. Must be `gcp-iam` when using GCP IAM.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
infisical gateway start --auth-method=gcp-iam --machine-identity-id=<machine-identity-id> --service-account-key-file-path=<service-account-key-file-path> --relay=<relay-name> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Native AWS IAM">
|
||||
The AWS IAM method is used to authenticate with Infisical with an AWS IAM role while running in an AWS environment like EC2, Lambda, etc.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="machine-identity-id" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="auth-method" type="string" required>
|
||||
The authentication method to use. Must be `aws-iam` when using Native AWS IAM.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
infisical gateway start --auth-method=aws-iam --machine-identity-id=<machine-identity-id> --relay=<relay-name> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="OIDC Auth">
|
||||
The OIDC Auth method is used to authenticate with Infisical via identity tokens with OIDC.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="machine-identity-id" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="jwt" type="string" required>
|
||||
The OIDC JWT from the identity provider.
|
||||
</ParamField>
|
||||
<ParamField query="auth-method" type="string" required>
|
||||
The authentication method to use. Must be `oidc-auth` when using OIDC Auth.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
infisical gateway start --auth-method=oidc-auth --machine-identity-id=<machine-identity-id> --jwt=<oidc-jwt> --relay=<relay-name> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="JWT Auth">
|
||||
The JWT Auth method is used to authenticate with Infisical via a JWT token.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="jwt" type="string" required>
|
||||
The JWT token to use for authentication.
|
||||
</ParamField>
|
||||
<ParamField query="machine-identity-id" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="auth-method" type="string" required>
|
||||
The authentication method to use. Must be `jwt-auth` when using JWT Auth.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
|
||||
```bash
|
||||
infisical gateway start --auth-method=jwt-auth --jwt=<jwt> --machine-identity-id=<machine-identity-id> --relay=<relay-name> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Token Auth">
|
||||
You can use the `INFISICAL_TOKEN` environment variable to authenticate with Infisical with a raw machine identity access token.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="token" type="string" required>
|
||||
The machine identity access token to use for authentication.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
infisical gateway start --token=<token> --relay=<relay-name> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### Other Flags
|
||||
|
||||
<Accordion title="--relay">
|
||||
The name of the relay that this gateway should connect to. The relay must be running and registered before starting the gateway.
|
||||
|
||||
```bash
|
||||
# Example
|
||||
infisical gateway start --relay=my-relay --name=my-gateway --token=<token>
|
||||
```
|
||||
|
||||
**Note:** If using organization relays or self-hosted instance relays, you must first start a relay server using `infisical relay start` before connecting gateways to it. For Infisical Cloud users using instance relays, the relay infrastructure is already running and managed by Infisical.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="--name">
|
||||
The name of the gateway instance.
|
||||
|
||||
```bash
|
||||
# Example
|
||||
infisical gateway start --name=my-gateway --relay=my-relay --token=<token>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="--domain">
|
||||
Domain of your self-hosted Infisical instance.
|
||||
|
||||
```bash
|
||||
# Example
|
||||
infisical gateway start --domain=https://app.your-domain.com --relay=<relay-name> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="infisical gateway systemd install">
|
||||
Install and enable the gateway as a systemd service. This command must be run with sudo on Linux.
|
||||
|
||||
```bash
|
||||
sudo infisical gateway systemd install --token=<token> --domain=<domain> --name=<name> --relay=<relay-name>
|
||||
```
|
||||
|
||||
### Requirements
|
||||
|
||||
- Must be run on Linux
|
||||
- Must be run with root/sudo privileges
|
||||
- Requires systemd
|
||||
|
||||
### Flags
|
||||
|
||||
<Accordion title="--token">
|
||||
The machine identity access token to authenticate with Infisical.
|
||||
|
||||
```bash
|
||||
# Example
|
||||
sudo infisical gateway systemd install --token=<token> --name=<name> --relay=<relay-name>
|
||||
```
|
||||
|
||||
You may also expose the token to the CLI by setting the environment variable `INFISICAL_TOKEN` before executing the install command.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="--domain">
|
||||
Domain of your self-hosted Infisical instance.
|
||||
|
||||
```bash
|
||||
# Example
|
||||
sudo infisical gateway systemd install --domain=https://app.your-domain.com --name=<name> --relay=<relay-name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="--name">
|
||||
The name of the gateway instance.
|
||||
|
||||
```bash
|
||||
# Example
|
||||
sudo infisical gateway systemd install --name=my-gateway --token=<token> --relay=<relay-name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="--relay">
|
||||
The name of the relay that this gateway should connect to.
|
||||
|
||||
```bash
|
||||
# Example
|
||||
sudo infisical gateway systemd install --relay=my-relay --token=<token> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
### Service Details
|
||||
|
||||
The systemd service is installed with secure defaults:
|
||||
|
||||
- Service file: `/etc/systemd/system/infisical-gateway.service`
|
||||
- Config file: `/etc/infisical/gateway.conf`
|
||||
- Runs with restricted privileges:
|
||||
- InaccessibleDirectories=/home
|
||||
- PrivateTmp=yes
|
||||
- Resource limits configured for stability
|
||||
- Automatically restarts on failure
|
||||
- Enabled to start on boot
|
||||
- Maintains persistent SSH reverse tunnel connections to the specified relay
|
||||
- Handles certificate rotation and connection recovery automatically
|
||||
|
||||
After installation, manage the service with standard systemd commands:
|
||||
|
||||
```bash
|
||||
sudo systemctl start infisical-gateway # Start the service
|
||||
sudo systemctl stop infisical-gateway # Stop the service
|
||||
sudo systemctl status infisical-gateway # Check service status
|
||||
sudo systemctl disable infisical-gateway # Disable auto-start on boot
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
## Legacy Gateway Commands (Deprecated)
|
||||
|
||||
<Accordion title="infisical gateway (deprecated)">
|
||||
<Warning>
|
||||
**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.
|
||||
|
||||
</Warning>
|
||||
|
||||
Run the legacy Infisical gateway in the foreground. The gateway will connect to the relay service and maintain a persistent connection.
|
||||
|
||||
```bash
|
||||
infisical gateway --domain=<domain> --auth-method=<auth-method>
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
The Infisical CLI supports multiple authentication methods. Below are the available authentication methods, with their respective flags.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Universal Auth">
|
||||
@@ -121,7 +471,6 @@ Run the Infisical gateway in the foreground or manage its systemd service instal
|
||||
infisical gateway --auth-method=gcp-id-token --machine-identity-id=<machine-identity-id>
|
||||
```
|
||||
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="GCP IAM">
|
||||
The GCP IAM method is used to authenticate with Infisical with a GCP service account key.
|
||||
@@ -163,7 +512,6 @@ Run the Infisical gateway in the foreground or manage its systemd service instal
|
||||
infisical gateway --auth-method=aws-iam --machine-identity-id=<machine-identity-id>
|
||||
```
|
||||
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="OIDC Auth">
|
||||
The OIDC Auth method is used to authenticate with Infisical via identity tokens with OIDC.
|
||||
@@ -185,6 +533,7 @@ Run the Infisical gateway in the foreground or manage its systemd service instal
|
||||
```bash
|
||||
infisical gateway --auth-method=oidc-auth --machine-identity-id=<machine-identity-id> --jwt=<oidc-jwt>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="JWT Auth">
|
||||
@@ -208,6 +557,7 @@ Run the Infisical gateway in the foreground or manage its systemd service instal
|
||||
```bash
|
||||
infisical gateway --auth-method=jwt-auth --jwt=<jwt> --machine-identity-id=<machine-identity-id>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Token Auth">
|
||||
You can use the `INFISICAL_TOKEN` environment variable to authenticate with Infisical with a raw machine identity access token.
|
||||
@@ -227,7 +577,7 @@ Run the Infisical gateway in the foreground or manage its systemd service instal
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### Other Flags
|
||||
### Other Flags
|
||||
|
||||
<Accordion title="--domain">
|
||||
Domain of your self-hosted Infisical instance.
|
||||
@@ -236,22 +586,33 @@ Run the Infisical gateway in the foreground or manage its systemd service instal
|
||||
# Example
|
||||
infisical gateway --domain=https://app.your-domain.com
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="infisical gateway install">
|
||||
Install and enable the gateway as a systemd service. This command must be run with sudo on Linux.
|
||||
<Accordion title="infisical gateway install (deprecated)">
|
||||
<Warning>
|
||||
**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.
|
||||
|
||||
```bash
|
||||
sudo infisical gateway install --token=<token> --domain=<domain>
|
||||
```
|
||||
**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.
|
||||
|
||||
### Requirements
|
||||
- Must be run on Linux
|
||||
- Must be run with root/sudo privileges
|
||||
- Requires systemd
|
||||
</Warning>
|
||||
|
||||
### Flags
|
||||
Install and enable the legacy gateway as a systemd service. This command must be run with sudo on Linux.
|
||||
|
||||
```bash
|
||||
sudo infisical gateway install --token=<token> --domain=<domain>
|
||||
```
|
||||
|
||||
### Requirements
|
||||
|
||||
- Must be run on Linux
|
||||
- Must be run with root/sudo privileges
|
||||
- Requires systemd
|
||||
|
||||
### Flags
|
||||
|
||||
<Accordion title="--token">
|
||||
The machine identity access token to authenticate with Infisical.
|
||||
@@ -262,6 +623,7 @@ Run the Infisical gateway in the foreground or manage its systemd service instal
|
||||
```
|
||||
|
||||
You may also expose the token to the CLI by setting the environment variable `INFISICAL_TOKEN` before executing the install command.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="--domain">
|
||||
@@ -271,24 +633,29 @@ Run the Infisical gateway in the foreground or manage its systemd service instal
|
||||
# Example
|
||||
sudo infisical gateway install --domain=https://app.your-domain.com
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
### Service Details
|
||||
The systemd service is installed with secure defaults:
|
||||
- Service file: `/etc/systemd/system/infisical-gateway.service`
|
||||
- Config file: `/etc/infisical/gateway.conf`
|
||||
- Runs with restricted privileges:
|
||||
- InaccessibleDirectories=/home
|
||||
- PrivateTmp=yes
|
||||
- Resource limits configured for stability
|
||||
- Automatically restarts on failure
|
||||
- Enabled to start on boot
|
||||
### Service Details
|
||||
|
||||
The systemd service is installed with secure defaults:
|
||||
|
||||
- Service file: `/etc/systemd/system/infisical-gateway.service`
|
||||
- Config file: `/etc/infisical/gateway.conf`
|
||||
- Runs with restricted privileges:
|
||||
- InaccessibleDirectories=/home
|
||||
- PrivateTmp=yes
|
||||
- Resource limits configured for stability
|
||||
- Automatically restarts on failure
|
||||
- Enabled to start on boot
|
||||
|
||||
After installation, manage the service with standard systemd commands:
|
||||
|
||||
```bash
|
||||
sudo systemctl start infisical-gateway # Start the service
|
||||
sudo systemctl stop infisical-gateway # Stop the service
|
||||
sudo systemctl status infisical-gateway # Check service status
|
||||
sudo systemctl disable infisical-gateway # Disable auto-start on boot
|
||||
```
|
||||
|
||||
After installation, manage the service with standard systemd commands:
|
||||
```bash
|
||||
sudo systemctl start infisical-gateway # Start the service
|
||||
sudo systemctl stop infisical-gateway # Stop the service
|
||||
sudo systemctl status infisical-gateway # Check service status
|
||||
sudo systemctl disable infisical-gateway # Disable auto-start on boot
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
306
docs/cli/commands/relay.mdx
Normal file
306
docs/cli/commands/relay.mdx
Normal file
@@ -0,0 +1,306 @@
|
||||
---
|
||||
title: "infisical relay"
|
||||
description: "Relay-related commands for Infisical"
|
||||
---
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Start relay">
|
||||
```bash
|
||||
infisical relay start --type=<type> --host=<host> --name=<name> --auth-method=<auth-method>
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Description
|
||||
|
||||
Relay-related commands for Infisical that provide identity-aware relay infrastructure for routing encrypted traffic:
|
||||
|
||||
- **Relay**: Identity-aware server that routes encrypted traffic (can be instance-wide or organization-specific)
|
||||
|
||||
The relay system uses SSH reverse tunnels over TCP, eliminating firewall complexity and providing excellent performance for enterprise environments.
|
||||
|
||||
## Subcommands & flags
|
||||
|
||||
<Accordion title="infisical relay start" defaultOpen="true">
|
||||
Run the Infisical relay component. The relay handles network traffic routing and can operate in different modes.
|
||||
|
||||
```bash
|
||||
infisical relay start --type=<type> --host=<host> --name=<name> --auth-method=<auth-method>
|
||||
```
|
||||
|
||||
### Flags
|
||||
|
||||
<Accordion title="--type">
|
||||
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_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
|
||||
# Organization relay (customer-deployed)
|
||||
infisical relay start --type=org --host=192.168.1.100 --name=my-org-relay
|
||||
|
||||
# Instance relay (configured by instance admin)
|
||||
INFISICAL_RELAY_AUTH_SECRET=<secret> infisical relay start --type=instance --host=10.0.1.50 --name=shared-relay
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="--host">
|
||||
The host (IP address or hostname) of the instance where the relay is deployed. This must be a static public IP or resolvable hostname that gateways can reach.
|
||||
|
||||
```bash
|
||||
# Example with IP address
|
||||
infisical relay start --host=203.0.113.100 --type=org --name=my-relay
|
||||
|
||||
# Example with hostname
|
||||
infisical relay start --host=relay.example.com --type=org --name=my-relay
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="--name">
|
||||
The name of the relay.
|
||||
|
||||
```bash
|
||||
# Example
|
||||
infisical relay start --name=my-relay --type=org --host=192.168.1.100
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
### Authentication
|
||||
|
||||
**Organization Relays (`--type=org`):**
|
||||
Deploy your own relay server in your infrastructure for enhanced security and reduced latency. Supports all standard Infisical authentication methods documented below.
|
||||
|
||||
**Instance Relays (`--type=instance`):**
|
||||
Shared relay servers that serve all organizations on your Infisical instance. For Infisical Cloud, these are already running and ready to use. For self-hosted deployments, they're set up by the instance administrator. Authentication is handled via the `INFISICAL_RELAY_AUTH_SECRET` environment variable.
|
||||
|
||||
```bash
|
||||
# Organization relay with Universal Auth (customer-deployed)
|
||||
infisical relay start --type=org --host=192.168.1.100 --name=my-org-relay --auth-method=universal-auth --client-id=<client-id> --client-secret=<client-secret>
|
||||
|
||||
# Instance relay (configured by instance admin)
|
||||
INFISICAL_RELAY_AUTH_SECRET=<secret> infisical relay start --type=instance --host=10.0.1.50 --name=shared-relay
|
||||
```
|
||||
|
||||
### Authentication Methods
|
||||
|
||||
The Infisical CLI supports multiple authentication methods for organization relays. Below are the available authentication methods, with their respective flags.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Universal Auth">
|
||||
The Universal Auth method is a simple and secure way to authenticate with Infisical. It requires a client ID and a client secret to authenticate with Infisical.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="client-id" type="string" required>
|
||||
Your machine identity client ID.
|
||||
</ParamField>
|
||||
<ParamField query="client-secret" type="string" required>
|
||||
Your machine identity client secret.
|
||||
</ParamField>
|
||||
<ParamField query="auth-method" type="string" required>
|
||||
The authentication method to use. Must be `universal-auth` when using Universal Auth.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
infisical relay start --auth-method=universal-auth --client-id=<client-id> --client-secret=<client-secret> --type=org --host=<host> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Native Kubernetes">
|
||||
The Native Kubernetes method is used to authenticate with Infisical when running in a Kubernetes environment. It requires a service account token to authenticate with Infisical.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="machine-identity-id" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="service-account-token-path" type="string" optional>
|
||||
Path to the Kubernetes service account token to use. Default: `/var/run/secrets/kubernetes.io/serviceaccount/token`.
|
||||
</ParamField>
|
||||
<ParamField query="auth-method" type="string" required>
|
||||
The authentication method to use. Must be `kubernetes` when using Native Kubernetes.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
|
||||
</ParamField>
|
||||
|
||||
|
||||
```bash
|
||||
infisical relay start --auth-method=kubernetes --machine-identity-id=<machine-identity-id> --type=org --host=<host> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Native Azure">
|
||||
The Native Azure method is used to authenticate with Infisical when running in an Azure environment.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="machine-identity-id" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="auth-method" type="string" required>
|
||||
The authentication method to use. Must be `azure` when using Native Azure.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
|
||||
|
||||
```bash
|
||||
infisical relay start --auth-method=azure --machine-identity-id=<machine-identity-id> --type=org --host=<host> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Native GCP ID Token">
|
||||
The Native GCP ID Token method is used to authenticate with Infisical when running in a GCP environment.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="machine-identity-id" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="auth-method" type="string" required>
|
||||
The authentication method to use. Must be `gcp-id-token` when using Native GCP ID Token.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
|
||||
|
||||
```bash
|
||||
infisical relay start --auth-method=gcp-id-token --machine-identity-id=<machine-identity-id> --type=org --host=<host> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="GCP IAM">
|
||||
The GCP IAM method is used to authenticate with Infisical with a GCP service account key.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="machine-identity-id" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="service-account-key-file-path" type="string" required>
|
||||
Path to your GCP service account key file _(Must be in JSON format!)_
|
||||
</ParamField>
|
||||
<ParamField query="auth-method" type="string" required>
|
||||
The authentication method to use. Must be `gcp-iam` when using GCP IAM.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
infisical relay start --auth-method=gcp-iam --machine-identity-id=<machine-identity-id> --service-account-key-file-path=<service-account-key-file-path> --type=org --host=<host> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Native AWS IAM">
|
||||
The AWS IAM method is used to authenticate with Infisical with an AWS IAM role while running in an AWS environment like EC2, Lambda, etc.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="machine-identity-id" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="auth-method" type="string" required>
|
||||
The authentication method to use. Must be `aws-iam` when using Native AWS IAM.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
infisical relay start --auth-method=aws-iam --machine-identity-id=<machine-identity-id> --type=org --host=<host> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="OIDC Auth">
|
||||
The OIDC Auth method is used to authenticate with Infisical via identity tokens with OIDC.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="machine-identity-id" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="jwt" type="string" required>
|
||||
The OIDC JWT from the identity provider.
|
||||
</ParamField>
|
||||
<ParamField query="auth-method" type="string" required>
|
||||
The authentication method to use. Must be `oidc-auth` when using OIDC Auth.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
infisical relay start --auth-method=oidc-auth --machine-identity-id=<machine-identity-id> --jwt=<oidc-jwt> --type=org --host=<host> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="JWT Auth">
|
||||
The JWT Auth method is used to authenticate with Infisical via a JWT token.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="jwt" type="string" required>
|
||||
The JWT token to use for authentication.
|
||||
</ParamField>
|
||||
<ParamField query="machine-identity-id" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="auth-method" type="string" required>
|
||||
The authentication method to use. Must be `jwt-auth` when using JWT Auth.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
|
||||
```bash
|
||||
infisical relay start --auth-method=jwt-auth --jwt=<jwt> --machine-identity-id=<machine-identity-id> --type=org --host=<host> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Token Auth">
|
||||
You can use the `INFISICAL_TOKEN` environment variable to authenticate with Infisical with a raw machine identity access token.
|
||||
|
||||
<ParamField query="Flags">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="token" type="string" required>
|
||||
The machine identity access token to use for authentication.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
infisical relay start --token=<token> --type=org --host=<host> --name=<name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### Deployment Considerations
|
||||
|
||||
**When to use Instance Relays (`--type=instance`):**
|
||||
|
||||
- You want to get started quickly without setting up your own relay infrastructure
|
||||
- You're using Infisical Cloud and want to leverage the existing relay infrastructure
|
||||
- You're on a self-hosted instance where the admin has already set up shared relays
|
||||
- You don't need custom geographic placement of relay servers
|
||||
- You don't have specific compliance requirements that require dedicated infrastructure
|
||||
- You want to minimize operational overhead by using shared infrastructure
|
||||
|
||||
**When to use Organization Relays (`--type=org`):**
|
||||
|
||||
- You need lower latency by deploying relay servers closer to your resources
|
||||
- You have security requirements that mandate running infrastructure in your own environment
|
||||
- You have compliance requirements such as data sovereignty or air-gapped environments
|
||||
- You need custom network policies or specific networking configurations
|
||||
- You have high-scale performance requirements that shared infrastructure can't meet
|
||||
- You want full control over your relay infrastructure and its configuration
|
||||
|
||||
</Accordion>
|
||||
@@ -174,7 +174,15 @@
|
||||
"pages": [
|
||||
"documentation/platform/gateways/overview",
|
||||
"documentation/platform/gateways/gateway-security",
|
||||
"documentation/platform/gateways/networking"
|
||||
"documentation/platform/gateways/networking",
|
||||
{
|
||||
"group": "Gateway (Deprecated)",
|
||||
"pages": [
|
||||
"documentation/platform/gateways-deprecated/overview",
|
||||
"documentation/platform/gateways-deprecated/gateway-security",
|
||||
"documentation/platform/gateways-deprecated/networking"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -346,7 +354,10 @@
|
||||
},
|
||||
{
|
||||
"group": "Architecture",
|
||||
"pages": ["internals/architecture/components", "internals/architecture/cloud"]
|
||||
"pages": [
|
||||
"internals/architecture/components",
|
||||
"internals/architecture/cloud"
|
||||
]
|
||||
},
|
||||
"internals/security",
|
||||
"internals/service-tokens"
|
||||
@@ -564,7 +575,10 @@
|
||||
"integrations/cloud/gcp-secret-manager",
|
||||
{
|
||||
"group": "Cloudflare",
|
||||
"pages": ["integrations/cloud/cloudflare-pages", "integrations/cloud/cloudflare-workers"]
|
||||
"pages": [
|
||||
"integrations/cloud/cloudflare-pages",
|
||||
"integrations/cloud/cloudflare-workers"
|
||||
]
|
||||
},
|
||||
"integrations/cloud/terraform-cloud",
|
||||
"integrations/cloud/databricks",
|
||||
@@ -659,7 +673,9 @@
|
||||
"documentation/platform/secret-scanning/overview",
|
||||
{
|
||||
"group": "Concepts",
|
||||
"pages": ["documentation/platform/secret-scanning/concepts/secret-scanning"]
|
||||
"pages": [
|
||||
"documentation/platform/secret-scanning/concepts/secret-scanning"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -709,13 +725,18 @@
|
||||
"documentation/platform/ssh/overview",
|
||||
{
|
||||
"group": "Concepts",
|
||||
"pages": ["documentation/platform/ssh/concepts/ssh-certificates"]
|
||||
"pages": [
|
||||
"documentation/platform/ssh/concepts/ssh-certificates"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Platform Reference",
|
||||
"pages": ["documentation/platform/ssh/usage", "documentation/platform/ssh/host-groups"]
|
||||
"pages": [
|
||||
"documentation/platform/ssh/usage",
|
||||
"documentation/platform/ssh/host-groups"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -753,6 +774,7 @@
|
||||
"cli/commands/dynamic-secrets",
|
||||
"cli/commands/ssh",
|
||||
"cli/commands/gateway",
|
||||
"cli/commands/relay",
|
||||
"cli/commands/bootstrap",
|
||||
"cli/commands/export",
|
||||
"cli/commands/token",
|
||||
@@ -762,7 +784,11 @@
|
||||
"cli/commands/reset",
|
||||
{
|
||||
"group": "infisical scan",
|
||||
"pages": ["cli/commands/scan", "cli/commands/scan-git-changes", "cli/commands/scan-install"]
|
||||
"pages": [
|
||||
"cli/commands/scan",
|
||||
"cli/commands/scan-git-changes",
|
||||
"cli/commands/scan-install"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1096,7 +1122,9 @@
|
||||
"pages": [
|
||||
{
|
||||
"group": "Kubernetes",
|
||||
"pages": ["api-reference/endpoints/dynamic-secrets/kubernetes/create-lease"]
|
||||
"pages": [
|
||||
"api-reference/endpoints/dynamic-secrets/kubernetes/create-lease"
|
||||
]
|
||||
},
|
||||
"api-reference/endpoints/dynamic-secrets/create",
|
||||
"api-reference/endpoints/dynamic-secrets/update",
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
title: "Gateway Security Architecture"
|
||||
sidebarTitle: "Architecture"
|
||||
description: "Understand the security model and tenant isolation of Infisical's Gateway"
|
||||
---
|
||||
|
||||
# Gateway Security Architecture
|
||||
|
||||
The Infisical Gateway enables Infisical Cloud to securely interact with private resources using mutual TLS authentication and private PKI (Public Key Infrastructure) system to ensure secure, isolated communication between multiple tenants.
|
||||
This document explains the internal security architecture and how tenant isolation is maintained.
|
||||
|
||||
## Security Model Overview
|
||||
|
||||
### Private PKI System
|
||||
Each organization (tenant) in Infisical has its own private PKI system consisting of:
|
||||
|
||||
1. **Root CA**: The ultimate trust anchor for the organization
|
||||
2. **Intermediate CAs**:
|
||||
- Client CA: Issues certificates for cloud components
|
||||
- Gateway CA: Issues certificates for gateway instances
|
||||
|
||||
This hierarchical structure ensures complete isolation between organizations as each has its own independent certificate chain.
|
||||
|
||||
### Certificate Hierarchy
|
||||
```
|
||||
Root CA (Organization Specific)
|
||||
├── Client CA
|
||||
│ └── Client Certificates (Cloud Components)
|
||||
└── Gateway CA
|
||||
└── Gateway Certificates (Gateway Instances)
|
||||
```
|
||||
|
||||
## Communication Security
|
||||
|
||||
### 1. Gateway Registration
|
||||
When a gateway is first deployed:
|
||||
|
||||
1. Establishes initial connection using machine identity token
|
||||
2. Allocates a relay address for communication
|
||||
3. Exchanges certificates through a secure handshake:
|
||||
- Gateway receives a unique certificate signed by organization's Gateway CA along with certificate chain for verification
|
||||
|
||||
### 2. Mutual TLS Authentication
|
||||
All communication between gateway and cloud uses mutual TLS (mTLS):
|
||||
|
||||
- **Gateway Authentication**:
|
||||
- Presents certificate signed by organization's Gateway CA
|
||||
- Certificate contains unique identifiers (Organization ID, Gateway ID)
|
||||
- Cloud validates complete certificate chain
|
||||
|
||||
- **Cloud Authentication**:
|
||||
- Presents certificate signed by organization's Client CA
|
||||
- Certificate includes required organizational unit ("gateway-client")
|
||||
- Gateway validates certificate chain back to organization's root CA
|
||||
|
||||
### 3. Relay Communication
|
||||
The relay system provides secure tunneling:
|
||||
|
||||
1. **Connection Establishment**:
|
||||
- Uses QUIC protocol over UDP for efficient, secure communication
|
||||
- Provides built-in encryption, congestion control, and multiplexing
|
||||
- Enables faster connection establishment and reduced latency
|
||||
- Each organization's traffic is isolated using separate relay sessions
|
||||
|
||||
2. **Traffic Isolation**:
|
||||
- Each gateway gets unique relay credentials
|
||||
- Traffic is end-to-end encrypted using QUIC's TLS 1.3
|
||||
- Organization's private keys never leave their environment
|
||||
|
||||
## Tenant Isolation
|
||||
|
||||
### Certificate-Based Isolation
|
||||
- Each organization has unique root CA and intermediate CAs
|
||||
- Certificates contain organization-specific identifiers
|
||||
- Cross-tenant communication is cryptographically impossible
|
||||
|
||||
### Gateway-Project Mapping
|
||||
- Gateways are explicitly mapped to specific projects
|
||||
- Access controls enforce organization boundaries
|
||||
- Project-level permissions determine resource accessibility
|
||||
|
||||
### Resource Access Control
|
||||
1. **Project Verification**:
|
||||
- Gateway verifies project membership
|
||||
- Validates organization ownership
|
||||
- Enforces project-level permissions
|
||||
|
||||
2. **Resource Restrictions**:
|
||||
- Gateways only accept connections to approved resources
|
||||
- Each connection requires explicit project authorization
|
||||
- Resources remain private to their assigned organization
|
||||
|
Before Width: | Height: | Size: 324 KiB After Width: | Height: | Size: 324 KiB |
168
docs/documentation/platform/gateways-deprecated/networking.mdx
Normal file
168
docs/documentation/platform/gateways-deprecated/networking.mdx
Normal file
@@ -0,0 +1,168 @@
|
||||
---
|
||||
title: "Networking"
|
||||
description: "Network configuration and firewall requirements for Infisical Gateway"
|
||||
---
|
||||
|
||||
The Infisical Gateway requires outbound network connectivity to establish secure communication with Infisical's relay infrastructure.
|
||||
This page outlines the required ports, protocols, and firewall configurations needed for optimal gateway usage.
|
||||
|
||||
## Network Architecture
|
||||
|
||||
The gateway uses a relay-based architecture to establish secure connections:
|
||||
|
||||
1. **Gateway** connects outbound to **Relay Servers** using UDP/QUIC protocol
|
||||
2. **Relay Servers** facilitate secure communication between Gateway and Infisical Cloud
|
||||
3. All traffic is end-to-end encrypted using mutual TLS over QUIC
|
||||
|
||||
## Required Network Connectivity
|
||||
|
||||
### Outbound Connections (Required)
|
||||
|
||||
The gateway requires the following outbound connectivity:
|
||||
|
||||
| Protocol | Destination | Ports | Purpose |
|
||||
|----------|-------------|-------|---------|
|
||||
| UDP | Relay Servers | 49152-65535 | Allocated relay communication (TLS) |
|
||||
| TCP | app.infisical.com / eu.infisical.com | 443 | API communication and relay allocation |
|
||||
|
||||
### Relay Server IP Addresses
|
||||
|
||||
Your firewall must allow outbound connectivity to the following Infisical relay servers on dynamically allocated ports.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Infisical cloud (US)">
|
||||
```
|
||||
54.235.197.91:49152-65535
|
||||
18.215.196.229:49152-65535
|
||||
3.222.120.233:49152-65535
|
||||
34.196.115.157:49152-65535
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Infisical cloud (EU)">
|
||||
```
|
||||
3.125.237.40:49152-65535
|
||||
52.28.157.98:49152-65535
|
||||
3.125.176.90:49152-65535
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Infisical dedicated">
|
||||
Please contact your Infisical account manager for dedicated relay server IP addresses.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Warning>
|
||||
These IP addresses are static and managed by Infisical. Any changes will be communicated with 60-day advance notice.
|
||||
</Warning>
|
||||
|
||||
## Protocol Details
|
||||
|
||||
### QUIC over UDP
|
||||
|
||||
The gateway uses QUIC (Quick UDP Internet Connections) for primary communication:
|
||||
|
||||
- **Port 5349**: STUN/TURN over TLS (secure relay communication)
|
||||
- **Built-in features**: Connection migration, multiplexing, reduced latency
|
||||
- **Encryption**: TLS 1.3 with certificate pinning
|
||||
|
||||
## Understanding Firewall Behavior with UDP
|
||||
|
||||
Unlike TCP connections, UDP is a stateless protocol, and depending on your organization's firewall configuration, you may need to adjust network rules accordingly.
|
||||
When the gateway sends UDP packets to a relay server, the return responses need to be allowed back through the firewall.
|
||||
Modern firewalls handle this through "connection tracking" (also called "stateful inspection"), but the behavior can vary depending on your firewall configuration.
|
||||
|
||||
|
||||
### Connection Tracking
|
||||
|
||||
Modern firewalls automatically track UDP connections and allow return responses. This is the preferred configuration as it:
|
||||
- Automatically handles return responses
|
||||
- Reduces firewall rule complexity
|
||||
- Avoids the need for manual IP whitelisting
|
||||
|
||||
In the event that your firewall does not support connection tracking, you will need to whitelist the relay IPs to explicitly define return traffic manually.
|
||||
|
||||
## Common Network Scenarios
|
||||
|
||||
### Corporate Firewalls
|
||||
|
||||
For corporate environments with strict egress filtering:
|
||||
|
||||
1. **Whitelist relay IP addresses** (listed above)
|
||||
2. **Allow UDP port 5349** outbound
|
||||
3. **Configure connection tracking** for UDP return traffic
|
||||
4. **Allow ephemeral port range** 49152-65535 for return traffic if connection tracking is disabled
|
||||
|
||||
### Cloud Environments (AWS/GCP/Azure)
|
||||
|
||||
Configure security groups to allow:
|
||||
- **Outbound UDP** to relay IPs on port 5349
|
||||
- **Outbound HTTPS** to app.infisical.com/eu.infisical.com on port 443
|
||||
- **Inbound UDP** on ephemeral ports (if not using stateful rules)
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
<Accordion title="What happens if there is a network interruption?">
|
||||
The gateway is designed to handle network interruptions gracefully:
|
||||
|
||||
- **Automatic reconnection**: The gateway will automatically attempt to reconnect to relay servers every 5 seconds if the connection is lost
|
||||
- **Connection retry logic**: Built-in retry mechanisms handle temporary network outages without manual intervention
|
||||
- **Multiple relay servers**: If one relay server is unavailable, the gateway can connect to alternative relay servers
|
||||
- **Persistent sessions**: Existing connections are maintained where possible during brief network interruptions
|
||||
- **Graceful degradation**: The gateway logs connection issues and continues attempting to restore connectivity
|
||||
|
||||
No manual intervention is typically required during network interruptions.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Why does the gateway use QUIC instead of TCP?">
|
||||
QUIC (Quick UDP Internet Connections) provides several advantages over traditional TCP for gateway communication:
|
||||
|
||||
- **Faster connection establishment**: QUIC combines transport and security handshakes, reducing connection setup time
|
||||
- **Built-in encryption**: TLS 1.3 is integrated into the protocol, ensuring all traffic is encrypted by default
|
||||
- **Connection migration**: QUIC connections can survive IP address changes (useful for NAT rebinding)
|
||||
- **Reduced head-of-line blocking**: Multiple data streams can be multiplexed without blocking each other
|
||||
- **Better performance over unreliable networks**: Advanced congestion control and packet loss recovery
|
||||
- **Lower latency**: Optimized for real-time communication between gateway and cloud services
|
||||
|
||||
While TCP is stateful and easier for firewalls to track, QUIC's performance benefits outweigh the additional firewall configuration requirements.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Do I need to open any inbound ports on my firewall?">
|
||||
No inbound ports need to be opened. The gateway only makes outbound connections:
|
||||
|
||||
- **Outbound UDP** to relay servers on ports 49152-65535
|
||||
- **Outbound HTTPS** to Infisical API endpoints
|
||||
- **Return responses** are handled by connection tracking or explicit IP whitelisting
|
||||
|
||||
This design maintains security by avoiding the need for inbound firewall rules that could expose your network to external threats.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="What if my firewall blocks the required UDP ports?">
|
||||
If your firewall has strict UDP restrictions:
|
||||
|
||||
1. **Work with your network team** to allow outbound UDP to the specific relay IP addresses
|
||||
2. **Use explicit IP whitelisting** if connection tracking is disabled
|
||||
3. **Consider network policy exceptions** for the gateway host
|
||||
4. **Monitor firewall logs** to identify which specific rules are blocking traffic
|
||||
|
||||
The gateway requires UDP connectivity to function - TCP-only configurations are not supported.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="How many relay servers does the gateway connect to?">
|
||||
The gateway connects to **one relay server at a time**:
|
||||
|
||||
- **Single active connection**: Only one relay connection is established per gateway instance
|
||||
- **Automatic failover**: If the current relay becomes unavailable, the gateway will connect to an alternative relay
|
||||
- **Load distribution**: Different gateway instances may connect to different relay servers for load balancing
|
||||
- **No manual selection**: The Infisical API automatically assigns the optimal relay server based on availability and proximity
|
||||
|
||||
You should whitelist all relay IP addresses to ensure proper failover functionality.
|
||||
</Accordion>
|
||||
<Accordion title="Can the relay servers decrypt traffic going through them?">
|
||||
No, relay servers cannot decrypt any traffic passing through them:
|
||||
|
||||
- **End-to-end encryption**: All traffic between the gateway and Infisical Cloud is encrypted using mutual TLS with certificate pinning
|
||||
- **Relay acts as a tunnel**: The relay server only forwards encrypted packets - it has no access to encryption keys
|
||||
- **No data storage**: Relay servers do not store any traffic or network-identifiable information
|
||||
- **Certificate isolation**: Each organization has its own private PKI system, ensuring complete tenant isolation
|
||||
|
||||
The relay infrastructure is designed as a secure forwarding mechanism, similar to a VPN tunnel, where the relay provider cannot see the contents of the traffic flowing through it.
|
||||
</Accordion>
|
||||
352
docs/documentation/platform/gateways-deprecated/overview.mdx
Normal file
352
docs/documentation/platform/gateways-deprecated/overview.mdx
Normal file
@@ -0,0 +1,352 @@
|
||||
---
|
||||
title: "Gateway"
|
||||
sidebarTitle: "Overview"
|
||||
description: "How to access private network resources from Infisical"
|
||||
---
|
||||
|
||||

|
||||
|
||||
The Infisical Gateway provides secure access to private resources within your network without needing direct inbound connections to your environment.
|
||||
This method keeps your resources fully protected from external access while enabling Infisical to securely interact with resources like databases.
|
||||
Common use cases include generating dynamic credentials or rotating credentials for private databases.
|
||||
|
||||
<Info>
|
||||
Gateway is a paid feature available under the Enterprise Tier for Infisical
|
||||
Cloud users. Self-hosted Infisical users can contact
|
||||
[sales@infisical.com](mailto:sales@infisical.com) to purchase an enterprise
|
||||
license.
|
||||
</Info>
|
||||
|
||||
## How It Works
|
||||
|
||||
The Gateway serves as a secure intermediary that facilitates direct communication between the Infisical server and your private network.
|
||||
It’s a lightweight daemon packaged within the Infisical CLI, making it easy to deploy and manage. Once set up, the Gateway establishes a connection with a relay server, ensuring that all communication between Infisical and your Gateway is fully end-to-end encrypted.
|
||||
This setup guarantees that only the platform and your Gateway can decrypt the transmitted information, keeping communication with your resources secure, private and isolated.
|
||||
|
||||
## Deployment
|
||||
|
||||
The Infisical Gateway is seamlessly integrated into the Infisical CLI under the `gateway` command, making it simple to deploy and manage.
|
||||
You can install the Gateway in all the same ways you install the Infisical CLI—whether via npm, Docker, or a binary.
|
||||
For detailed installation instructions, refer to the Infisical [CLI Installation instructions](/cli/overview).
|
||||
|
||||
To function, the Gateway must authenticate with Infisical. This requires a machine identity configured with the appropriate permissions to create and manage a Gateway.
|
||||
Once authenticated, the Gateway establishes a secure connection with Infisical to allow your private resources to be reachable.
|
||||
|
||||
### Get started
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a Gateway Identity">
|
||||
1. Navigate to **Organization Access Control** in your Infisical dashboard.
|
||||
2. Create a dedicated machine identity for your Gateway.
|
||||
3. **Best Practice:** Assign a unique identity to each Gateway for better security and management.
|
||||

|
||||
</Step>
|
||||
|
||||
<Step title="Configure Authentication Method">
|
||||
You'll need to choose an authentication method to initiate communication with Infisical. View the available machine identity authentication methods [here](/documentation/platform/identities/machine-identities).
|
||||
</Step>
|
||||
|
||||
<Step title="Deploy the Gateway">
|
||||
Use the Infisical CLI to deploy the Gateway. You can run it directly or install it as a systemd service for production:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Production (systemd)">
|
||||
For production deployments on Linux, install the Gateway as a systemd service:
|
||||
```bash
|
||||
sudo infisical gateway install --token <your-machine-identity-token> --domain <your-infisical-domain>
|
||||
sudo systemctl start infisical-gateway
|
||||
```
|
||||
This will install and start the Gateway as a secure systemd service that:
|
||||
- Runs with restricted privileges:
|
||||
- Runs as root user (required for secure token management)
|
||||
- Restricted access to home directories
|
||||
- Private temporary directory
|
||||
- Automatically restarts on failure
|
||||
- Starts on system boot
|
||||
- Manages token and domain configuration securely in `/etc/infisical/gateway.conf`
|
||||
|
||||
<Warning>
|
||||
The install command requires:
|
||||
- Linux operating system
|
||||
- Root/sudo privileges
|
||||
- Systemd
|
||||
</Warning>
|
||||
</Tab>
|
||||
|
||||
<Tab title="Production (Helm)">
|
||||
|
||||
The Gateway can be installed via [Helm](https://helm.sh/). Helm is a package manager for Kubernetes that allows you to define, install, and upgrade Kubernetes applications.
|
||||
|
||||
For production deployments on Kubernetes, install the Gateway using the Infisical Helm chart:
|
||||
|
||||
### Install the latest Helm Chart repository
|
||||
```bash
|
||||
helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/'
|
||||
```
|
||||
|
||||
### Update the Helm Chart repository
|
||||
```bash
|
||||
helm repo update
|
||||
```
|
||||
|
||||
### Create a Kubernetes Secret containing gateway environment variables
|
||||
|
||||
The gateway supports all identity authentication methods through the use of environment variables.
|
||||
The environment variables must be set in the `infisical-gateway-environment` Kubernetes secret.
|
||||
|
||||
|
||||
#### Supported authentication methods
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Universal Auth">
|
||||
The Universal Auth method is a simple and secure way to authenticate with Infisical. It requires a client ID and a client secret to authenticate with Infisical.
|
||||
|
||||
<ParamField query="Environment Variables">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="INFISICAL_UNIVERSAL_AUTH_CLIENT_ID" type="string" required>
|
||||
Your machine identity client ID.
|
||||
</ParamField>
|
||||
<ParamField query="INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET" type="string" required>
|
||||
Your machine identity client secret.
|
||||
</ParamField>
|
||||
<ParamField query="INFISICAL_AUTH_METHOD" type="string" required>
|
||||
The authentication method to use. Must be `universal-auth` when using Universal Auth.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=universal-auth --from-literal=INFISICAL_UNIVERSAL_AUTH_CLIENT_ID=<client-id> --from-literal=INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET=<client-secret>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Native Kubernetes">
|
||||
The Native Kubernetes method is used to authenticate with Infisical when running in a Kubernetes environment. It requires a service account token to authenticate with Infisical.
|
||||
|
||||
<ParamField query="Environment Variables">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="INFISICAL_MACHINE_IDENTITY_ID" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH" type="string" optional>
|
||||
Path to the Kubernetes service account token to use. Default: `/var/run/secrets/kubernetes.io/serviceaccount/token`.
|
||||
</ParamField>
|
||||
<ParamField query="INFISICAL_AUTH_METHOD" type="string" required>
|
||||
The authentication method to use. Must be `kubernetes` when using Native Kubernetes.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
|
||||
```bash
|
||||
kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=kubernetes --from-literal=INFISICAL_MACHINE_IDENTITY_ID=<machine-identity-id>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Native Azure">
|
||||
The Native Azure method is used to authenticate with Infisical when running in an Azure environment.
|
||||
|
||||
<ParamField query="Environment Variables">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="INFISICAL_MACHINE_IDENTITY_ID" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="INFISICAL_AUTH_METHOD" type="string" required>
|
||||
The authentication method to use. Must be `azure` when using Native Azure.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=azure --from-literal=INFISICAL_MACHINE_IDENTITY_ID=<machine-identity-id>
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="Native GCP ID Token">
|
||||
The Native GCP ID Token method is used to authenticate with Infisical when running in a GCP environment.
|
||||
|
||||
<ParamField query="Environment Variables">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="INFISICAL_MACHINE_IDENTITY_ID" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="INFISICAL_AUTH_METHOD" type="string" required>
|
||||
The authentication method to use. Must be `gcp-id-token` when using Native GCP ID Token.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=gcp-id-token --from-literal=INFISICAL_MACHINE_IDENTITY_ID=<machine-identity-id>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="GCP IAM">
|
||||
The GCP IAM method is used to authenticate with Infisical with a GCP service account key.
|
||||
|
||||
<ParamField query="Environment Variables">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="INFISICAL_MACHINE_IDENTITY_ID" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="INFISICAL_GCP_SERVICE_ACCOUNT_KEY_FILE_PATH" type="string" required>
|
||||
Path to your GCP service account key file _(Must be in JSON format!)_
|
||||
</ParamField>
|
||||
<ParamField query="INFISICAL_AUTH_METHOD" type="string" required>
|
||||
The authentication method to use. Must be `gcp-iam` when using GCP IAM.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=gcp-iam --from-literal=INFISICAL_MACHINE_IDENTITY_ID=<machine-identity-id> --from-literal=INFISICAL_GCP_SERVICE_ACCOUNT_KEY_FILE_PATH=<service-account-key-file-path>
|
||||
```
|
||||
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Native AWS IAM">
|
||||
The AWS IAM method is used to authenticate with Infisical with an AWS IAM role while running in an AWS environment like EC2, Lambda, etc.
|
||||
|
||||
<ParamField query="Environment Variables">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="INFISICAL_MACHINE_IDENTITY_ID" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="INFISICAL_AUTH_METHOD" type="string" required>
|
||||
The authentication method to use. Must be `aws-iam` when using Native AWS IAM.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=aws-iam --from-literal=INFISICAL_MACHINE_IDENTITY_ID=<machine-identity-id>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="OIDC Auth">
|
||||
The OIDC Auth method is used to authenticate with Infisical via identity tokens with OIDC.
|
||||
|
||||
<ParamField query="Environment Variables">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="INFISICAL_MACHINE_IDENTITY_ID" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="INFISICAL_JWT" type="string" required>
|
||||
The OIDC JWT from the identity provider.
|
||||
</ParamField>
|
||||
<ParamField query="INFISICAL_AUTH_METHOD" type="string" required>
|
||||
The authentication method to use. Must be `oidc-auth` when using OIDC Auth.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=oidc-auth --from-literal=INFISICAL_MACHINE_IDENTITY_ID=<machine-identity-id> --from-literal=INFISICAL_JWT=<oidc-jwt>
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="JWT Auth">
|
||||
The JWT Auth method is used to authenticate with Infisical via a JWT token.
|
||||
|
||||
<ParamField query="Environment Variables">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="INFISICAL_JWT" type="string" required>
|
||||
The JWT token to use for authentication.
|
||||
</ParamField>
|
||||
<ParamField query="INFISICAL_MACHINE_IDENTITY_ID" type="string" required>
|
||||
Your machine identity ID.
|
||||
</ParamField>
|
||||
<ParamField query="INFISICAL_AUTH_METHOD" type="string" required>
|
||||
The authentication method to use. Must be `jwt-auth` when using JWT Auth.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=jwt-auth --from-literal=INFISICAL_JWT=<jwt> --from-literal=INFISICAL_MACHINE_IDENTITY_ID=<machine-identity-id>
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="Token Auth">
|
||||
You can use the `INFISICAL_TOKEN` environment variable to authenticate with Infisical with a raw machine identity access token.
|
||||
|
||||
<ParamField query="Environment Variables">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="INFISICAL_TOKEN" type="string" required>
|
||||
The machine identity access token to use for authentication.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_TOKEN=<token>
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
|
||||
#### Other environment variables
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="INFISICAL_API_URL">
|
||||
The API URL to use for the gateway. By default, `INFISICAL_API_URL` is set to `https://app.infisical.com`.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
|
||||
### Install the Infisical Gateway Helm Chart
|
||||
```bash
|
||||
helm install infisical-gateway infisical-helm-charts/infisical-gateway
|
||||
```
|
||||
|
||||
### Check the gateway logs
|
||||
After installing the gateway, you can check the logs to ensure it's running as expected.
|
||||
|
||||
```bash
|
||||
kubectl logs deployment/infisical-gateway
|
||||
```
|
||||
|
||||
You should see the following output which indicates the gateway is running as expected.
|
||||
```bash
|
||||
$ kubectl logs deployment/infisical-gateway
|
||||
INF Provided relay port 5349. Using TLS
|
||||
INF Connected with relay
|
||||
INF 10.0.101.112:56735
|
||||
INF Starting relay connection health check
|
||||
INF Gateway started successfully
|
||||
INF New connection from: 10.0.1.8:34051
|
||||
INF Gateway is reachable by Infisical
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab title="Local Installation (testing)">
|
||||
For development or testing, you can run the Gateway directly. Log in with your machine identity and start the Gateway in one command:
|
||||
```bash
|
||||
infisical gateway --token $(infisical login --method=universal-auth --client-id=<> --client-secret=<> --plain)
|
||||
```
|
||||
|
||||
Alternatively, if you already have the token, use it directly with the `--token` flag:
|
||||
```bash
|
||||
infisical gateway --token <your-machine-identity-token>
|
||||
```
|
||||
|
||||
Or set it as an environment variable:
|
||||
```bash
|
||||
export INFISICAL_TOKEN=<your-machine-identity-token>
|
||||
infisical gateway
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
For detailed information about the gateway command and its options, see the [gateway command documentation](/cli/commands/gateway).
|
||||
|
||||
<Note>
|
||||
Ensure the deployed Gateway has network access to the private resources you intend to connect with Infisical.
|
||||
</Note>
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Verify Gateway Deployment">
|
||||
To confirm your Gateway is working, check the deployment status by looking for the message **"Gateway started successfully"** in the Gateway logs. This indicates the Gateway is running properly. Next, verify its registration by opening your Infisical dashboard, navigating to **Organization Access Control**, and selecting the **Gateways** tab. Your newly deployed Gateway should appear in the list.
|
||||

|
||||
</Step>
|
||||
</Steps>
|
||||
@@ -6,86 +6,133 @@ description: "Understand the security model and tenant isolation of Infisical's
|
||||
|
||||
# Gateway Security Architecture
|
||||
|
||||
The Infisical Gateway enables Infisical Cloud to securely interact with private resources using mutual TLS authentication and private PKI (Public Key Infrastructure) system to ensure secure, isolated communication between multiple tenants.
|
||||
The Infisical Gateway enables secure access to private resources using SSH reverse tunnels, certificate-based authentication, and a comprehensive PKI (Public Key Infrastructure) system. The architecture provides end-to-end encryption and complete tenant isolation through multiple certificate authorities.
|
||||
This document explains the internal security architecture and how tenant isolation is maintained.
|
||||
|
||||
## Security Model Overview
|
||||
|
||||
### Private PKI System
|
||||
Each organization (tenant) in Infisical has its own private PKI system consisting of:
|
||||
### Certificate Architecture
|
||||
|
||||
1. **Root CA**: The ultimate trust anchor for the organization
|
||||
2. **Intermediate CAs**:
|
||||
- Client CA: Issues certificates for cloud components
|
||||
- Gateway CA: Issues certificates for gateway instances
|
||||
The gateway system uses multiple certificate authorities depending on deployment configuration:
|
||||
|
||||
This hierarchical structure ensures complete isolation between organizations as each has its own independent certificate chain.
|
||||
**For Organizations Using Infisical-Managed Relays:**
|
||||
|
||||
- **Instance relay SSH Client CA & Server CA** - Gateway ↔ Infisical Relay Server authentication
|
||||
- **Instance relay PKI Client CA & Server CA** - Platform ↔ Infisical Relay Server authentication
|
||||
- **Organization Gateway Client CA & Server CA** - Platform ↔ Gateway authentication
|
||||
|
||||
**For Organizations Using Customer-Deployed Relays:**
|
||||
|
||||
- **Organization relay SSH Client CA & Server CA** - Gateway ↔ Customer Relay Server authentication
|
||||
- **Organization relay PKI Client CA & Server CA** - Platform ↔ Customer Relay Server authentication
|
||||
- **Organization Gateway Client CA & Server CA** - Platform ↔ Gateway authentication
|
||||
|
||||
### Certificate Hierarchy
|
||||
|
||||
```
|
||||
Root CA (Organization Specific)
|
||||
├── Client CA
|
||||
│ └── Client Certificates (Cloud Components)
|
||||
└── Gateway CA
|
||||
└── Gateway Certificates (Gateway Instances)
|
||||
Instance Level (Shared Relays):
|
||||
├── Instance Relay SSH CA (Gateway ↔ Relay)
|
||||
├── Instance Relay PKI CA (Platform ↔ Relay)
|
||||
|
||||
Organization Level:
|
||||
├── Organization Relay SSH CA (Gateway ↔ Org Relay)
|
||||
├── Organization Relay PKI CA (Platform ↔ Org Relay)
|
||||
└── Organization Gateway CA (Platform ↔ Gateway)
|
||||
```
|
||||
|
||||
## Communication Security
|
||||
|
||||
### 1. Gateway Registration
|
||||
|
||||
When a gateway is first deployed:
|
||||
|
||||
1. Establishes initial connection using machine identity token
|
||||
2. Allocates a relay address for communication
|
||||
3. Exchanges certificates through a secure handshake:
|
||||
- Gateway receives a unique certificate signed by organization's Gateway CA along with certificate chain for verification
|
||||
1. Authenticates with Infisical using machine identity token
|
||||
2. Receives SSH certificates for relay server authentication
|
||||
3. Establishes SSH reverse tunnel to assigned relay server
|
||||
4. Certificate issuance varies by relay configuration:
|
||||
- **Infisical-managed relay**: Receives Instance relay SSH client certificate + Instance relay SSH Server CA
|
||||
- **Customer-deployed relay**: Receives Organization relay SSH client certificate + Organization relay SSH Server CA
|
||||
|
||||
### 2. Mutual TLS Authentication
|
||||
All communication between gateway and cloud uses mutual TLS (mTLS):
|
||||
### 2. SSH Tunnel Authentication
|
||||
|
||||
Gateway ↔ Relay Server communication uses SSH certificate authentication:
|
||||
|
||||
- **Gateway Authentication**:
|
||||
- Presents certificate signed by organization's Gateway CA
|
||||
- Certificate contains unique identifiers (Organization ID, Gateway ID)
|
||||
- Cloud validates complete certificate chain
|
||||
|
||||
- **Cloud Authentication**:
|
||||
- Presents certificate signed by organization's Client CA
|
||||
- Certificate includes required organizational unit ("gateway-client")
|
||||
- Gateway validates certificate chain back to organization's root CA
|
||||
- Presents SSH client certificate (Instance or Organization relay SSH Client CA)
|
||||
- Certificate contains gateway identification and permissions
|
||||
- Relay server validates certificate against appropriate SSH Client CA
|
||||
|
||||
### 3. Relay Communication
|
||||
The relay system provides secure tunneling:
|
||||
- **Relay Server Authentication**:
|
||||
- Presents SSH server certificate (Instance or Organization relay SSH Server CA)
|
||||
- Gateway validates certificate against appropriate SSH Server CA
|
||||
- Ensures gateway connects to legitimate relay infrastructure
|
||||
|
||||
1. **Connection Establishment**:
|
||||
- Uses QUIC protocol over UDP for efficient, secure communication
|
||||
- Provides built-in encryption, congestion control, and multiplexing
|
||||
- Enables faster connection establishment and reduced latency
|
||||
- Each organization's traffic is isolated using separate relay sessions
|
||||
### 3. Platform-to-Gateway Direct Connection
|
||||
|
||||
2. **Traffic Isolation**:
|
||||
- Each gateway gets unique relay credentials
|
||||
- Traffic is end-to-end encrypted using QUIC's TLS 1.3
|
||||
- Organization's private keys never leave their environment
|
||||
The platform establishes secure direct connections with gateways through a **TLS-pinned tunnel** mechanism:
|
||||
|
||||
1. **TLS-Pinned Tunnel Establishment**:
|
||||
|
||||
- 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. **Connection Flow**:
|
||||
|
||||
```
|
||||
Platform ←→ [SSH Reverse Tunnel] ←→ Gateway
|
||||
```
|
||||
|
||||
- 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
|
||||
|
||||
### Certificate-Based Isolation
|
||||
- Each organization has unique root CA and intermediate CAs
|
||||
- Certificates contain organization-specific identifiers
|
||||
- Cross-tenant communication is cryptographically impossible
|
||||
### Multi-Layer Certificate Isolation
|
||||
|
||||
### Gateway-Project Mapping
|
||||
- Gateways are explicitly mapped to specific projects
|
||||
- Access controls enforce organization boundaries
|
||||
- Project-level permissions determine resource accessibility
|
||||
The architecture provides tenant isolation through multiple certificate authority layers:
|
||||
|
||||
- **Instance-level CAs**: Shared relay infrastructure uses instance-level certificates
|
||||
- **Organization-level CAs**: Each organization has unique certificate authorities
|
||||
- **Relay deployment flexibility**: Organizations can choose shared or dedicated relay infrastructure
|
||||
- **Cryptographic separation**: Cross-tenant communication is cryptographically impossible
|
||||
|
||||
### Authentication Flows by Deployment Type
|
||||
|
||||
**Infisical-Managed Relay Deployments:**
|
||||
|
||||
- Gateway authenticates with relay using Instance relay SSH certificates
|
||||
- Platform authenticates with relay using Instance relay PKI certificates
|
||||
- Platform authenticates with gateway using Organization Gateway certificates
|
||||
|
||||
**Customer-Deployed Relay Deployments:**
|
||||
|
||||
- Gateway authenticates with relay using Organization relay SSH certificates
|
||||
- Platform authenticates with relay using Organization relay PKI certificates
|
||||
- Platform authenticates with gateway using Organization Gateway certificates
|
||||
|
||||
### Resource Access Control
|
||||
1. **Project Verification**:
|
||||
- Gateway verifies project membership
|
||||
- Validates organization ownership
|
||||
- Enforces project-level permissions
|
||||
|
||||
2. **Resource Restrictions**:
|
||||
- Gateways only accept connections to approved resources
|
||||
- Each connection requires explicit project authorization
|
||||
- Resources remain private to their assigned organization
|
||||
1. **Certificate Validation**:
|
||||
|
||||
- All connections require valid certificates from appropriate CAs
|
||||
- Embedded certificate details control access permissions
|
||||
- Ephemeral certificate validation ensures time-bound access
|
||||
|
||||
2. **Network Isolation**:
|
||||
|
||||
- Each organization's traffic flows through isolated certificate-authenticated channels
|
||||
- Relay servers route traffic based on certificate validation without content access
|
||||
- Gateway validates all incoming connections against Organization Gateway Client CA
|
||||
|
||||
@@ -3,16 +3,17 @@ title: "Networking"
|
||||
description: "Network configuration and firewall requirements for Infisical Gateway"
|
||||
---
|
||||
|
||||
The Infisical Gateway requires outbound network connectivity to establish secure communication with Infisical's relay infrastructure.
|
||||
The Infisical Gateway requires outbound network connectivity to establish secure SSH reverse tunnels with relay servers.
|
||||
This page outlines the required ports, protocols, and firewall configurations needed for optimal gateway usage.
|
||||
|
||||
## Network Architecture
|
||||
|
||||
The gateway uses a relay-based architecture to establish secure connections:
|
||||
The gateway uses SSH reverse tunnels to establish secure connections with end-to-end encryption:
|
||||
|
||||
1. **Gateway** connects outbound to **Relay Servers** using UDP/QUIC protocol
|
||||
2. **Relay Servers** facilitate secure communication between Gateway and Infisical Cloud
|
||||
3. All traffic is end-to-end encrypted using mutual TLS over QUIC
|
||||
1. **Gateway** connects outbound to **Relay Servers** using SSH over TCP
|
||||
2. **Infisical platform** establishes mTLS connections with gateways for application traffic
|
||||
3. **Relay Servers** route the doubly-encrypted traffic (mTLS payload within SSH tunnels) between the platform and gateways
|
||||
4. **Double encryption** ensures relay servers cannot access application data - only the platform and gateway can decrypt traffic
|
||||
|
||||
## Required Network Connectivity
|
||||
|
||||
@@ -20,65 +21,70 @@ The gateway uses a relay-based architecture to establish secure connections:
|
||||
|
||||
The gateway requires the following outbound connectivity:
|
||||
|
||||
| Protocol | Destination | Ports | Purpose |
|
||||
|----------|-------------|-------|---------|
|
||||
| UDP | Relay Servers | 49152-65535 | Allocated relay communication (TLS) |
|
||||
| TCP | app.infisical.com / eu.infisical.com | 443 | API communication and relay allocation |
|
||||
| Protocol | Destination | Ports | Purpose |
|
||||
| -------- | ------------------------------------ | ----- | ------------------------------------------ |
|
||||
| TCP | Relay Servers | 2222 | SSH reverse tunnel establishment |
|
||||
| TCP | app.infisical.com / eu.infisical.com | 443 | API communication and certificate requests |
|
||||
|
||||
### Relay Server IP Addresses
|
||||
### Relay Server Connectivity
|
||||
|
||||
Your firewall must allow outbound connectivity to the following Infisical relay servers on dynamically allocated ports.
|
||||
**For Instance Relays (Infisical Cloud):** Your firewall must allow outbound connectivity to Infisical-managed relay servers.
|
||||
|
||||
**For Organization Relays:** Your firewall must allow outbound connectivity to your own relay server IP addresses or hostnames.
|
||||
|
||||
**For Self-hosted Instance Relays:** Your firewall must allow outbound connectivity to relay servers configured by your instance administrator.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Infisical cloud (US)">
|
||||
```
|
||||
54.235.197.91:49152-65535
|
||||
18.215.196.229:49152-65535
|
||||
3.222.120.233:49152-65535
|
||||
34.196.115.157:49152-65535
|
||||
```
|
||||
<Tab title="Instance Relays (Infisical Cloud)">
|
||||
Infisical provides multiple managed relay servers with static IP addresses.
|
||||
You can whitelist these IPs ahead of time based on which relay server you
|
||||
choose to connect to. **Firewall requirements:** Allow outbound TCP
|
||||
connections to the desired relay server IP on port 2222.
|
||||
</Tab>
|
||||
<Tab title="Infisical cloud (EU)">
|
||||
```
|
||||
3.125.237.40:49152-65535
|
||||
52.28.157.98:49152-65535
|
||||
3.125.176.90:49152-65535
|
||||
```
|
||||
<Tab title="Organization Relays">
|
||||
You control the relay server IP addresses or hostnames when deploying your
|
||||
own organization relays. **Firewall requirements:** Allow outbound TCP
|
||||
connections to your relay server IP or hostname on port 2222. For example,
|
||||
if your relay is at `203.0.113.100` or `relay.example.com`, allow TCP to
|
||||
`203.0.113.100:2222` or `relay.example.com:2222`.
|
||||
</Tab>
|
||||
<Tab title="Infisical dedicated">
|
||||
Please contact your Infisical account manager for dedicated relay server IP addresses.
|
||||
<Tab title="Self-hosted Instance Relays">
|
||||
Contact your instance administrator for the relay server IP addresses or
|
||||
hostnames configured for your deployment. **Firewall requirements:** Allow
|
||||
outbound TCP connections to instance relay servers on port 2222.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Warning>
|
||||
These IP addresses are static and managed by Infisical. Any changes will be communicated with 60-day advance notice.
|
||||
</Warning>
|
||||
|
||||
## Protocol Details
|
||||
|
||||
### QUIC over UDP
|
||||
### SSH over TCP
|
||||
|
||||
The gateway uses QUIC (Quick UDP Internet Connections) for primary communication:
|
||||
The gateway uses SSH reverse tunnels for primary communication:
|
||||
|
||||
- **Port 5349**: STUN/TURN over TLS (secure relay communication)
|
||||
- **Built-in features**: Connection migration, multiplexing, reduced latency
|
||||
- **Encryption**: TLS 1.3 with certificate pinning
|
||||
- **Port 2222**: SSH connection to relay servers
|
||||
- **Built-in features**: Automatic reconnection, certificate-based authentication, encrypted tunneling
|
||||
- **Encryption**: SSH with certificate-based authentication and key exchange
|
||||
|
||||
## Understanding Firewall Behavior with UDP
|
||||
## Firewall Configuration for SSH
|
||||
|
||||
Unlike TCP connections, UDP is a stateless protocol, and depending on your organization's firewall configuration, you may need to adjust network rules accordingly.
|
||||
When the gateway sends UDP packets to a relay server, the return responses need to be allowed back through the firewall.
|
||||
Modern firewalls handle this through "connection tracking" (also called "stateful inspection"), but the behavior can vary depending on your firewall configuration.
|
||||
The gateway uses standard SSH over TCP, making firewall configuration straightforward.
|
||||
|
||||
### TCP Connection Handling
|
||||
|
||||
### Connection Tracking
|
||||
SSH connections over TCP are stateful and handled seamlessly by all modern firewalls:
|
||||
|
||||
Modern firewalls automatically track UDP connections and allow return responses. This is the preferred configuration as it:
|
||||
- Automatically handles return responses
|
||||
- Reduces firewall rule complexity
|
||||
- Avoids the need for manual IP whitelisting
|
||||
- **Established connections** are automatically tracked
|
||||
- **Return traffic** is allowed for established outbound connections
|
||||
- **No special configuration** needed for connection tracking
|
||||
- **Standard SSH protocol** that enterprise firewalls handle well
|
||||
|
||||
In the event that your firewall does not support connection tracking, you will need to whitelist the relay IPs to explicitly define return traffic manually.
|
||||
### Simplified Firewall Rules
|
||||
|
||||
Since SSH uses TCP, you only need simple outbound rules:
|
||||
|
||||
1. **Allow outbound TCP** to relay servers (IP addresses or hostnames) on port 2222
|
||||
2. **Allow outbound HTTPS** to Infisical API endpoints on port 443
|
||||
3. **No inbound rules required** - all connections are outbound only
|
||||
|
||||
## Common Network Scenarios
|
||||
|
||||
@@ -86,83 +92,87 @@ In the event that your firewall does not support connection tracking, you will n
|
||||
|
||||
For corporate environments with strict egress filtering:
|
||||
|
||||
1. **Whitelist relay IP addresses** (listed above)
|
||||
2. **Allow UDP port 5349** outbound
|
||||
3. **Configure connection tracking** for UDP return traffic
|
||||
4. **Allow ephemeral port range** 49152-65535 for return traffic if connection tracking is disabled
|
||||
1. **Allow outbound TCP** to relay servers (IP addresses or hostnames) on port 2222
|
||||
2. **Allow outbound HTTPS** to the Infisical API server on port 443
|
||||
3. **No inbound rules required** - all connections are outbound only
|
||||
4. **Standard TCP rules** - simple and straightforward configuration
|
||||
|
||||
### Cloud Environments (AWS/GCP/Azure)
|
||||
|
||||
Configure security groups to allow:
|
||||
- **Outbound UDP** to relay IPs on port 5349
|
||||
|
||||
- **Outbound TCP** to relay servers (IP addresses or hostnames) on port 2222
|
||||
- **Outbound HTTPS** to app.infisical.com/eu.infisical.com on port 443
|
||||
- **Inbound UDP** on ephemeral ports (if not using stateful rules)
|
||||
- **No inbound rules required** - SSH reverse tunnels are outbound only
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
<Accordion title="What happens if there is a network interruption?">
|
||||
The gateway is designed to handle network interruptions gracefully:
|
||||
|
||||
- **Automatic reconnection**: The gateway will automatically attempt to reconnect to relay servers every 5 seconds if the connection is lost
|
||||
- **Automatic reconnection**: The gateway will automatically attempt to reconnect to relay servers if the SSH connection is lost
|
||||
- **Connection retry logic**: Built-in retry mechanisms handle temporary network outages without manual intervention
|
||||
- **Multiple relay servers**: If one relay server is unavailable, the gateway can connect to alternative relay servers
|
||||
- **Persistent sessions**: Existing connections are maintained where possible during brief network interruptions
|
||||
- **Persistent SSH tunnels**: SSH connections are automatically re-established when connectivity is restored
|
||||
- **Certificate rotation**: The gateway handles certificate renewal automatically during reconnection
|
||||
- **Graceful degradation**: The gateway logs connection issues and continues attempting to restore connectivity
|
||||
|
||||
No manual intervention is typically required during network interruptions.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Why does the gateway use QUIC instead of TCP?">
|
||||
QUIC (Quick UDP Internet Connections) provides several advantages over traditional TCP for gateway communication:
|
||||
<Accordion title="Why does the gateway use SSH over TCP?">
|
||||
SSH over TCP provides several advantages for enterprise gateway communication:
|
||||
|
||||
- **Faster connection establishment**: QUIC combines transport and security handshakes, reducing connection setup time
|
||||
- **Built-in encryption**: TLS 1.3 is integrated into the protocol, ensuring all traffic is encrypted by default
|
||||
- **Connection migration**: QUIC connections can survive IP address changes (useful for NAT rebinding)
|
||||
- **Reduced head-of-line blocking**: Multiple data streams can be multiplexed without blocking each other
|
||||
- **Better performance over unreliable networks**: Advanced congestion control and packet loss recovery
|
||||
- **Lower latency**: Optimized for real-time communication between gateway and cloud services
|
||||
- **Firewall-friendly**: TCP is stateful and handled seamlessly by all enterprise firewalls
|
||||
- **Standard protocol**: SSH is a well-established protocol that network teams are familiar with
|
||||
- **Certificate-based security**: Uses SSH certificates for strong authentication without shared secrets
|
||||
- **Automatic tunneling**: SSH reverse tunnels handle all the complexity of secure communication
|
||||
- **Enterprise compatibility**: Works reliably across all enterprise network configurations
|
||||
|
||||
TCP's reliability and firewall compatibility make it ideal for enterprise environments where network policies are strictly managed.
|
||||
|
||||
While TCP is stateful and easier for firewalls to track, QUIC's performance benefits outweigh the additional firewall configuration requirements.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Do I need to open any inbound ports on my firewall?">
|
||||
No inbound ports need to be opened. The gateway only makes outbound connections:
|
||||
|
||||
- **Outbound UDP** to relay servers on ports 49152-65535
|
||||
- **Outbound HTTPS** to Infisical API endpoints
|
||||
- **Return responses** are handled by connection tracking or explicit IP whitelisting
|
||||
- **Outbound SSH** to relay servers on port 2222
|
||||
- **Outbound HTTPS** to Infisical API endpoints on port 443
|
||||
- **SSH reverse tunnels** handle all communication - no return traffic configuration needed
|
||||
|
||||
This design maintains security by avoiding the need for inbound firewall rules that could expose your network to external threats.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="What if my firewall blocks the required UDP ports?">
|
||||
If your firewall has strict UDP restrictions:
|
||||
<Accordion title="What if my firewall blocks SSH connections?">
|
||||
If your firewall has strict outbound restrictions:
|
||||
|
||||
1. **Work with your network team** to allow outbound UDP to the specific relay IP addresses
|
||||
2. **Use explicit IP whitelisting** if connection tracking is disabled
|
||||
3. **Consider network policy exceptions** for the gateway host
|
||||
1. **Work with your network team** to allow outbound TCP connections on port 2222 to relay servers (IP addresses or hostnames)
|
||||
2. **Allow standard SSH traffic** - most enterprises already have SSH policies in place
|
||||
3. **Consider network policy exceptions** for the gateway host if needed
|
||||
4. **Monitor firewall logs** to identify which specific rules are blocking traffic
|
||||
|
||||
The gateway requires UDP connectivity to function - TCP-only configurations are not supported.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="How many relay servers does the gateway connect to?">
|
||||
The gateway connects to **one relay server at a time**:
|
||||
The gateway connects to **one relay server**:
|
||||
|
||||
- **Single active connection**: Only one relay connection is established per gateway instance
|
||||
- **Automatic failover**: If the current relay becomes unavailable, the gateway will connect to an alternative relay
|
||||
- **Load distribution**: Different gateway instances may connect to different relay servers for load balancing
|
||||
- **No manual selection**: The Infisical API automatically assigns the optimal relay server based on availability and proximity
|
||||
- **Single SSH connection**: Each gateway establishes one SSH reverse tunnel to its assigned relay server
|
||||
- **Named relay assignment**: Gateways connect to the specific relay server specified by `--relay`
|
||||
- **Automatic reconnection**: If the relay connection is lost, the gateway automatically reconnects to the same relay
|
||||
- **Certificate-based authentication**: Each connection uses SSH certificates issued by Infisical for secure authentication
|
||||
|
||||
You should whitelist all relay IP addresses to ensure proper failover functionality.
|
||||
</Accordion>
|
||||
<Accordion title="Can the relay servers decrypt traffic going through them?">
|
||||
No, relay servers cannot decrypt any traffic passing through them:
|
||||
No, relay servers cannot decrypt any traffic passing through them due to end-to-end encryption:
|
||||
|
||||
- **End-to-end encryption**: All traffic between the gateway and Infisical Cloud is encrypted using mutual TLS with certificate pinning
|
||||
- **Relay acts as a tunnel**: The relay server only forwards encrypted packets - it has no access to encryption keys
|
||||
- **No data storage**: Relay servers do not store any traffic or network-identifiable information
|
||||
- **Certificate isolation**: Each organization has its own private PKI system, ensuring complete tenant isolation
|
||||
- **Client-to-Gateway mTLS (via TLS-pinned tunnel)**: Clients connect via a proxy that establishes a TLS-pinned tunnel to the gateway; mTLS between the client and gateway is negotiated inside this tunnel, encrypting all application traffic
|
||||
- **SSH tunnel encryption**: The mTLS-encrypted traffic is then transmitted through SSH reverse tunnels to relay servers
|
||||
- **Double encryption**: Traffic is encrypted twice - once by client mTLS and again by SSH tunnels
|
||||
- **Relay only routes traffic**: The relay server only routes the doubly-encrypted traffic without access to either encryption layer
|
||||
- **No data storage**: Relay servers do not store any traffic or sensitive information
|
||||
- **Certificate isolation**: Each connection uses unique certificates, ensuring complete tenant isolation
|
||||
|
||||
The relay infrastructure is designed as a secure forwarding mechanism, similar to a VPN tunnel, where the relay provider cannot see the contents of the traffic flowing through it.
|
||||
</Accordion>
|
||||
The relay infrastructure is designed as a secure routing mechanism where only the client and gateway can decrypt the actual application traffic.
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -4,33 +4,53 @@ sidebarTitle: "Overview"
|
||||
description: "How to access private network resources from Infisical"
|
||||
---
|
||||
|
||||

|
||||

|
||||
|
||||
The Infisical Gateway provides secure access to private resources within your network without needing direct inbound connections to your environment. This method keeps your resources fully protected from external access while enabling Infisical to securely interact with resources like databases.
|
||||
|
||||
**Architecture Components:**
|
||||
|
||||
- **Gateway**: Lightweight agent deployed within your VPCs that provides access to private resources
|
||||
- **Relay**: Infrastructure that routes encrypted traffic (instance-wide or organization-specific)
|
||||
|
||||
The Infisical Gateway provides secure access to private resources within your network without needing direct inbound connections to your environment.
|
||||
This method keeps your resources fully protected from external access while enabling Infisical to securely interact with resources like databases.
|
||||
Common use cases include generating dynamic credentials or rotating credentials for private databases.
|
||||
|
||||
<Info>
|
||||
**Note:** Gateway is a paid feature. - **Infisical Cloud users:** Gateway is
|
||||
available under the **Enterprise Tier**. - **Self-Hosted Infisical:** Please
|
||||
contact [sales@infisical.com](mailto:sales@infisical.com) to purchase an
|
||||
enterprise license.
|
||||
Gateway is a paid feature available under the Enterprise Tier for Infisical
|
||||
Cloud users. Self-hosted Infisical users can contact
|
||||
[sales@infisical.com](mailto:sales@infisical.com) to purchase an enterprise
|
||||
license.
|
||||
</Info>
|
||||
|
||||
## How It Works
|
||||
|
||||
The Gateway serves as a secure intermediary that facilitates direct communication between the Infisical server and your private network.
|
||||
It’s a lightweight daemon packaged within the Infisical CLI, making it easy to deploy and manage. Once set up, the Gateway establishes a connection with a relay server, ensuring that all communication between Infisical and your Gateway is fully end-to-end encrypted.
|
||||
This setup guarantees that only the platform and your Gateway can decrypt the transmitted information, keeping communication with your resources secure, private and isolated.
|
||||
The Gateway system uses SSH reverse tunnels for secure, firewall-friendly connectivity:
|
||||
|
||||
1. **Gateway Registration**: The gateway establishes an outbound SSH reverse tunnel to a relay server using SSH certificates issued by Infisical
|
||||
2. **Relay Routing**: The relay server routes encrypted traffic between the Infisical platform and gateways
|
||||
3. **Resource Access**: The Infisical platform connects to your private resources through the established gateway connections
|
||||
|
||||
**Key Benefits:**
|
||||
|
||||
- **No inbound firewall rules needed** - all connections are outbound from your network
|
||||
- **Firewall-friendly** - uses standard SSH over TCP
|
||||
- **Certificate-based authentication** provides enhanced security
|
||||
- **Automatic reconnection** if connections are lost
|
||||
|
||||
## Deployment
|
||||
|
||||
The Infisical Gateway is seamlessly integrated into the Infisical CLI under the `gateway` command, making it simple to deploy and manage.
|
||||
The Infisical Gateway is integrated into the Infisical CLI under the `gateway` command, making it simple to deploy and manage.
|
||||
You can install the Gateway in all the same ways you install the Infisical CLI—whether via npm, Docker, or a binary.
|
||||
For detailed installation instructions, refer to the Infisical [CLI Installation instructions](/cli/overview).
|
||||
|
||||
To function, the Gateway must authenticate with Infisical. This requires a machine identity configured with the appropriate permissions to create and manage a Gateway.
|
||||
Once authenticated, the Gateway establishes a secure connection with Infisical to allow your private resources to be reachable.
|
||||
**Prerequisites:**
|
||||
|
||||
1. **Relay Server**: Before deploying gateways, you need a running relay server:
|
||||
- **Infisical Cloud**: Instance relays are already available - no setup needed
|
||||
- **Self-hosted**: Instance admin must set up shared instance relays, or organizations can deploy their own
|
||||
2. **Machine Identity**: Configure a machine identity with appropriate permissions to create and manage gateways
|
||||
|
||||
Once authenticated, the Gateway establishes an SSH reverse tunnel to the specified relay server, allowing secure access to your private resources.
|
||||
|
||||
### Get started
|
||||
|
||||
@@ -46,14 +66,51 @@ Once authenticated, the Gateway establishes a secure connection with Infisical t
|
||||
You'll need to choose an authentication method to initiate communication with Infisical. View the available machine identity authentication methods [here](/documentation/platform/identities/machine-identities).
|
||||
</Step>
|
||||
|
||||
<Step title="Choose Your Relay Setup">
|
||||
You have two options for relay infrastructure:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Use Instance Relays (Easiest)">
|
||||
**Infisical Cloud:** Instance relays are already running and available - **no setup required**. You can immediately proceed to deploy gateways using these shared relays.
|
||||
|
||||
**Self-hosted:** If your instance admin has set up shared instance relays, you can use them directly. If not, the instance admin can set them up:
|
||||
```bash
|
||||
# Instance admin sets up shared relay (one-time setup)
|
||||
export INFISICAL_RELAY_AUTH_SECRET=<instance-relay-secret>
|
||||
infisical relay start --type=instance --ip=<public-ip> --name=<relay-name>
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Deploy Your Own Organization Relay">
|
||||
**Available for all users:** Deploy your own dedicated relay infrastructure for enhanced control:
|
||||
```bash
|
||||
# Deploy organization-specific relay
|
||||
infisical relay start --type=org --ip=<public-ip> --name=<relay-name> --auth-method=universal-auth --client-id=<client-id> --client-secret=<client-secret>
|
||||
```
|
||||
|
||||
**When to choose this:**
|
||||
- You need lower latency (deploy closer to your resources)
|
||||
- Enhanced security requirements
|
||||
- Compliance needs (data sovereignty, air-gapped environments)
|
||||
- Custom network policies
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Step>
|
||||
|
||||
<Step title="Deploy the Gateway">
|
||||
Use the Infisical CLI to deploy the Gateway. You can run it directly or install it as a systemd service for production:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Production (systemd)">
|
||||
For production deployments on Linux, install the Gateway as a systemd service:
|
||||
|
||||
<Warning>
|
||||
**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.
|
||||
</Warning>
|
||||
|
||||
```bash
|
||||
sudo infisical gateway install --token <your-machine-identity-token> --domain <your-infisical-domain>
|
||||
sudo infisical gateway systemd install --token <your-machine-identity-token> --domain <your-infisical-domain> --name <gateway-name> --relay <relay-name>
|
||||
sudo systemctl start infisical-gateway
|
||||
```
|
||||
This will install and start the Gateway as a secure systemd service that:
|
||||
@@ -81,7 +138,7 @@ Once authenticated, the Gateway establishes a secure connection with Infisical t
|
||||
|
||||
### Install the latest Helm Chart repository
|
||||
```bash
|
||||
helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/'
|
||||
helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/'
|
||||
```
|
||||
|
||||
### Update the Helm Chart repository
|
||||
@@ -116,7 +173,12 @@ Once authenticated, the Gateway establishes a secure connection with Infisical t
|
||||
</ParamField>
|
||||
|
||||
```bash
|
||||
kubectl create secret generic infisical-gateway-environment --from-literal=INFISICAL_AUTH_METHOD=universal-auth --from-literal=INFISICAL_UNIVERSAL_AUTH_CLIENT_ID=<client-id> --from-literal=INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET=<client-secret>
|
||||
kubectl create secret generic infisical-gateway-environment \
|
||||
--from-literal=INFISICAL_AUTH_METHOD=universal-auth \
|
||||
--from-literal=INFISICAL_UNIVERSAL_AUTH_CLIENT_ID=<client-id> \
|
||||
--from-literal=INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET=<client-secret> \
|
||||
--from-literal=INFISICAL_RELAY_NAME=<relay-name> \
|
||||
--from-literal=INFISICAL_GATEWAY_NAME=<gateway-name>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
@@ -283,6 +345,29 @@ Once authenticated, the Gateway establishes a secure connection with Infisical t
|
||||
</AccordionGroup>
|
||||
|
||||
|
||||
#### Required environment variables
|
||||
|
||||
In addition to the authentication method above, you **must** include these required variables:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="INFISICAL_RELAY_NAME">
|
||||
The name of the relay server that this gateway should connect to.
|
||||
</Accordion>
|
||||
<Accordion title="INFISICAL_GATEWAY_NAME">
|
||||
The name of this gateway instance.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
**Complete example with required variables:**
|
||||
```bash
|
||||
kubectl create secret generic infisical-gateway-environment \
|
||||
--from-literal=INFISICAL_AUTH_METHOD=universal-auth \
|
||||
--from-literal=INFISICAL_UNIVERSAL_AUTH_CLIENT_ID=<client-id> \
|
||||
--from-literal=INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET=<client-secret> \
|
||||
--from-literal=INFISICAL_RELAY_NAME=<relay-name> \
|
||||
--from-literal=INFISICAL_GATEWAY_NAME=<gateway-name>
|
||||
```
|
||||
|
||||
#### Other environment variables
|
||||
|
||||
<AccordionGroup>
|
||||
@@ -291,8 +376,13 @@ Once authenticated, the Gateway establishes a secure connection with Infisical t
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
|
||||
### Install the Infisical Gateway Helm Chart
|
||||
<Warning>
|
||||
**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.
|
||||
</Warning>
|
||||
|
||||
```bash
|
||||
helm install infisical-gateway infisical-helm-charts/infisical-gateway
|
||||
```
|
||||
@@ -306,14 +396,18 @@ Once authenticated, the Gateway establishes a secure connection with Infisical t
|
||||
|
||||
You should see the following output which indicates the gateway is running as expected.
|
||||
```bash
|
||||
$ kubectl logs deployment/infisical-gateway
|
||||
INF Provided relay port 5349. Using TLS
|
||||
INF Connected with relay
|
||||
INF 10.0.101.112:56735
|
||||
INF Starting relay connection health check
|
||||
INF Gateway started successfully
|
||||
INF New connection from: 10.0.1.8:34051
|
||||
INF Gateway is reachable by Infisical
|
||||
$ kubectl logs deployment/infisical-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
|
||||
```
|
||||
|
||||
</Tab>
|
||||
@@ -321,27 +415,31 @@ Once authenticated, the Gateway establishes a secure connection with Infisical t
|
||||
<Tab title="Local Installation (testing)">
|
||||
For development or testing, you can run the Gateway directly. Log in with your machine identity and start the Gateway in one command:
|
||||
```bash
|
||||
infisical gateway --token $(infisical login --method=universal-auth --client-id=<> --client-secret=<> --plain)
|
||||
infisical gateway start --token $(infisical login --method=universal-auth --client-id=<> --client-secret=<> --plain) --relay=<relay-name> --name=<gateway-name>
|
||||
```
|
||||
|
||||
Alternatively, if you already have the token, use it directly with the `--token` flag:
|
||||
```bash
|
||||
infisical gateway --token <your-machine-identity-token>
|
||||
infisical gateway start --token <your-machine-identity-token> --relay=<relay-name> --name=<gateway-name>
|
||||
```
|
||||
|
||||
Or set it as an environment variable:
|
||||
```bash
|
||||
export INFISICAL_TOKEN=<your-machine-identity-token>
|
||||
infisical gateway
|
||||
infisical gateway start --relay=<relay-name> --name=<gateway-name>
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
For detailed information about the gateway command and its options, see the [gateway command documentation](/cli/commands/gateway).
|
||||
For detailed information about the gateway commands and their options, see the [gateway command documentation](/cli/commands/gateway).
|
||||
|
||||
<Note>
|
||||
Ensure the deployed Gateway has network access to the private resources you intend to connect with Infisical.
|
||||
**Requirements:**
|
||||
- Ensure the deployed Gateway has network access to the private resources you intend to connect with Infisical
|
||||
- The gateway must be able to reach the relay server (outbound connection only)
|
||||
- Replace `<relay-name>` with the name of your relay server and `<gateway-name>` with a unique name for this gateway
|
||||
</Note>
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Verify Gateway Deployment">
|
||||
|
||||
BIN
docs/images/platform/gateways/gateway-highlevel-diagram.png
Normal file
BIN
docs/images/platform/gateways/gateway-highlevel-diagram.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 324 KiB |
1
frontend/src/hooks/api/gateways-v2/index.tsx
Normal file
1
frontend/src/hooks/api/gateways-v2/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./mutations";
|
||||
17
frontend/src/hooks/api/gateways-v2/mutations.tsx
Normal file
17
frontend/src/hooks/api/gateways-v2/mutations.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { gatewaysQueryKeys } from "../gateways/queries";
|
||||
|
||||
export const useDeleteGatewayV2ById = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => {
|
||||
return apiRequest.delete(`/api/v2/gateways/${id}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(gatewaysQueryKeys.list());
|
||||
}
|
||||
});
|
||||
};
|
||||
12
frontend/src/hooks/api/gateways-v2/types.ts
Normal file
12
frontend/src/hooks/api/gateways-v2/types.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export type TGatewayV2 = {
|
||||
id: string;
|
||||
identityId: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
heartbeat: string;
|
||||
identity: {
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { queryOptions } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { TGatewayV2 } from "../gateways-v2/types";
|
||||
import { TGateway } from "./types";
|
||||
|
||||
export const gatewaysQueryKeys = {
|
||||
@@ -11,8 +12,21 @@ export const gatewaysQueryKeys = {
|
||||
queryOptions({
|
||||
queryKey: gatewaysQueryKeys.listKey(),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{ gateways: TGateway[] }>("/api/v1/gateways");
|
||||
return data.gateways;
|
||||
const [{ data }, { data: dataV2 }] = await Promise.all([
|
||||
apiRequest.get<{ gateways: TGateway[] }>("/api/v1/gateways"),
|
||||
apiRequest.get<TGatewayV2[]>("/api/v2/gateways")
|
||||
]);
|
||||
|
||||
return [
|
||||
...data.gateways.map((g) => ({
|
||||
...g,
|
||||
isV1: true
|
||||
})),
|
||||
...dataV2.map((g) => ({
|
||||
...g,
|
||||
isV1: false
|
||||
}))
|
||||
];
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { format, formatRelative } from "date-fns";
|
||||
import { formatRelative } from "date-fns";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
@@ -48,13 +48,14 @@ import {
|
||||
import { withPermission } from "@app/hoc";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { gatewaysQueryKeys, useDeleteGatewayById } from "@app/hooks/api/gateways";
|
||||
import { useDeleteGatewayV2ById } from "@app/hooks/api/gateways-v2";
|
||||
|
||||
import { EditGatewayDetailsModal } from "./components/EditGatewayDetailsModal";
|
||||
|
||||
export const GatewayListPage = withPermission(
|
||||
() => {
|
||||
const [search, setSearch] = useState("");
|
||||
const { data: gateways, isPending: isGatewayLoading } = useQuery(gatewaysQueryKeys.list());
|
||||
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"deleteGateway",
|
||||
@@ -62,16 +63,20 @@ export const GatewayListPage = withPermission(
|
||||
] as const);
|
||||
|
||||
const deleteGatewayById = useDeleteGatewayById();
|
||||
const deleteGatewayV2ById = useDeleteGatewayV2ById();
|
||||
|
||||
const handleDeleteGateway = async () => {
|
||||
await deleteGatewayById.mutateAsync((popUp.deleteGateway.data as { id: string }).id, {
|
||||
onSuccess: () => {
|
||||
handlePopUpToggle("deleteGateway");
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Successfully delete gateway"
|
||||
});
|
||||
}
|
||||
const data = popUp.deleteGateway.data as { id: string; isV1: boolean };
|
||||
if (data.isV1) {
|
||||
await deleteGatewayById.mutateAsync(data.id);
|
||||
} else {
|
||||
await deleteGatewayV2ById.mutateAsync(data.id);
|
||||
}
|
||||
|
||||
handlePopUpToggle("deleteGateway");
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Successfully deleted gateway"
|
||||
});
|
||||
};
|
||||
|
||||
@@ -127,7 +132,6 @@ export const GatewayListPage = withPermission(
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th className="w-1/3">Name</Th>
|
||||
<Th>Cert Issued At</Th>
|
||||
<Th>Identity</Th>
|
||||
<Th>
|
||||
Health Check
|
||||
@@ -143,13 +147,19 @@ export const GatewayListPage = withPermission(
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isGatewayLoading && (
|
||||
{isGatewaysLoading && (
|
||||
<TableSkeleton innerKey="gateway-table" columns={4} key="gateway-table" />
|
||||
)}
|
||||
{filteredGateway?.map((el) => (
|
||||
<Tr key={el.id}>
|
||||
<Td>{el.name}</Td>
|
||||
<Td>{format(new Date(el.issuedAt), "yyyy-MM-dd hh:mm:ss aaa")}</Td>
|
||||
<Td>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{el.name}</span>
|
||||
<span className="rounded bg-mineshaft-700 px-1.5 py-0.5 text-xs text-mineshaft-400">
|
||||
Gateway v{el.isV1 ? "1" : "2"}
|
||||
</span>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>{el.identity.name}</Td>
|
||||
<Td>
|
||||
{el.heartbeat
|
||||
@@ -176,20 +186,22 @@ export const GatewayListPage = withPermission(
|
||||
>
|
||||
Copy ID
|
||||
</DropdownMenuItem>
|
||||
<OrgPermissionCan
|
||||
I={OrgGatewayPermissionActions.EditGateways}
|
||||
a={OrgPermissionSubjects.Gateway}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
isDisabled={!isAllowed}
|
||||
icon={<FontAwesomeIcon icon={faEdit} />}
|
||||
onClick={() => handlePopUpOpen("editDetails", el)}
|
||||
>
|
||||
Edit Details
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
{el.isV1 && (
|
||||
<OrgPermissionCan
|
||||
I={OrgGatewayPermissionActions.EditGateways}
|
||||
a={OrgPermissionSubjects.Gateway}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
isDisabled={!isAllowed}
|
||||
icon={<FontAwesomeIcon icon={faEdit} />}
|
||||
onClick={() => handlePopUpOpen("editDetails", el)}
|
||||
>
|
||||
Edit Details
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionAppConnectionActions.Delete}
|
||||
a={OrgPermissionSubjects.AppConnections}
|
||||
@@ -224,7 +236,7 @@ export const GatewayListPage = withPermission(
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
{!isGatewayLoading && !filteredGateway?.length && (
|
||||
{!isGatewaysLoading && !filteredGateway?.length && (
|
||||
<EmptyState
|
||||
title={
|
||||
gateways?.length
|
||||
@@ -251,8 +263,5 @@ export const GatewayListPage = withPermission(
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{
|
||||
action: OrgPermissionAppConnectionActions.Read,
|
||||
subject: OrgPermissionSubjects.AppConnections
|
||||
}
|
||||
{ action: OrgGatewayPermissionActions.ListGateways, subject: OrgPermissionSubjects.Gateway }
|
||||
);
|
||||
|
||||
@@ -15,10 +15,10 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.0.5
|
||||
version: 1.0.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "0.0.5"
|
||||
appVersion: "1.0.0"
|
||||
|
||||
@@ -39,6 +39,7 @@ spec:
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
args:
|
||||
- gateway
|
||||
- start
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ .Values.secret.name }}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
image:
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "0.41.84"
|
||||
tag: "0.42.0"
|
||||
|
||||
secret:
|
||||
# The secret that contains the environment variables to be used by the gateway, such as INFISICAL_API_URL and TOKEN
|
||||
|
||||
Reference in New Issue
Block a user