feat: gateway registration + start of gateway v2 integration

This commit is contained in:
Sheen Capadngan
2025-08-30 05:51:07 +08:00
parent 2fb13463bc
commit 81dfcb5de1
16 changed files with 577 additions and 130 deletions

View File

@@ -101,6 +101,9 @@ import {
TGateways,
TGatewaysInsert,
TGatewaysUpdate,
TGatewaysV2,
TGatewaysV2Insert,
TGatewaysV2Update,
TGitAppInstallSessions,
TGitAppInstallSessionsInsert,
TGitAppInstallSessionsUpdate,
@@ -1282,5 +1285,6 @@ declare module "knex/types/tables" {
TOrgGatewayConfigV2Update
>;
[TableName.Proxy]: KnexOriginal.CompositeTableType<TProxies, TProxiesInsert, TProxiesUpdate>;
[TableName.GatewayV2]: KnexOriginal.CompositeTableType<TGatewaysV2, TGatewaysV2Insert, TGatewaysV2Update>;
}
}

View File

@@ -110,14 +110,14 @@ export async function up(knex: Knex): Promise<void> {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.timestamps(true, true, true);
t.uuid("orgId");
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.uuid("identityId").unique();
t.uuid("identityId").notNullable().unique();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
t.uuid("proxyId");
t.foreign("proxyId").references("id").inTable(TableName.Proxy).onDelete("CASCADE");
t.foreign("proxyId").references("id").inTable(TableName.Proxy).onDelete("SET NULL");
t.string("name").notNullable().unique();
});
@@ -136,9 +136,9 @@ export async function down(knex: Knex): Promise<void> {
await dropOnUpdateTrigger(knex, TableName.OrgGatewayConfigV2);
await knex.schema.dropTableIfExists(TableName.OrgGatewayConfigV2);
await dropOnUpdateTrigger(knex, TableName.Proxy);
await knex.schema.dropTableIfExists(TableName.Proxy);
await dropOnUpdateTrigger(knex, TableName.GatewayV2);
await knex.schema.dropTableIfExists(TableName.GatewayV2);
await dropOnUpdateTrigger(knex, TableName.Proxy);
await knex.schema.dropTableIfExists(TableName.Proxy);
}

View 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 GatewaysV2Schema = z.object({
id: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
orgId: z.string().uuid(),
identityId: z.string().uuid(),
proxyId: z.string().uuid().nullable().optional(),
name: z.string()
});
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>>;

View File

@@ -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";
@@ -98,6 +99,7 @@ export * from "./project-templates";
export * from "./project-user-additional-privilege";
export * from "./project-user-membership-roles";
export * from "./projects";
export * from "./proxies";
export * from "./rate-limit";
export * from "./resource-metadata";
export * from "./saml-configs";
@@ -165,4 +167,3 @@ export * from "./user-group-membership";
export * from "./users";
export * from "./webhooks";
export * from "./workflow-integrations";
export * from "./proxies";

View File

@@ -7,23 +7,43 @@ export const registerGatewayV2Router = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
body: z.object({
proxyName: z.string()
proxyName: z.string(),
name: z.string()
}),
response: {
200: z.any()
}
},
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const gateway = await server.services.gatewayV2.registerGateway({
orgId: req.permission.orgId,
proxyName: req.body.proxyName,
actorId: req.permission.id
actorId: req.permission.id,
name: req.body.name
});
return gateway;
}
});
server.route({
method: "GET",
url: "/",
schema: {
response: {
200: z.any()
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const gateways = await server.services.gatewayV2.listGateways({
orgPermission: req.permission
});
return gateways;
}
});
};

View File

@@ -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 ({
@@ -118,8 +121,9 @@ 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`
});
@@ -128,7 +132,7 @@ export const dynamicSecretServiceFactory = ({
const { permission: orgPermission } = await permissionService.getOrgPermission(
actor,
actorId,
gateway.orgId,
gateway?.orgId ?? gatewayv2?.orgId,
actorAuthMethod,
actorOrgId
);
@@ -138,7 +142,7 @@ export const dynamicSecretServiceFactory = ({
OrgPermissionSubjects.Gateway
);
selectedGatewayId = gateway.id;
selectedGatewayId = gateway?.id ?? gatewayv2?.id;
}
const isConnected = await selectedProvider.validateConnection(provider.inputs, { projectId });

View File

@@ -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,10 +25,12 @@ 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.Cassandra]: CassandraProvider(),
@@ -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(),

View File

@@ -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,26 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
},
gatewayCallback: (host: string, port: number, httpsAgent?: https.Agent) => Promise<T>
): Promise<T> => {
const gatewayV2ConnectionDetails = await gatewayV2Service.getPlatformConnectionDetailsByGatewayId(inputs.gatewayId);
if (gatewayV2ConnectionDetails) {
const callbackResult = await withGatewayV2Proxy(
async (port) => {
return gatewayCallback(
inputs.reviewTokenThroughGateway ? "http://localhost" : "https://localhost",
port,
inputs.httpsAgent
);
},
{
proxyIp: gatewayV2ConnectionDetails.proxyIp,
gateway: gatewayV2ConnectionDetails.gateway,
proxy: gatewayV2ConnectionDetails.proxy
}
);
return callbackResult;
}
const relayDetails = await gatewayService.fnGetGatewayClientTlsByGatewayId(inputs.gatewayId);
const [relayHost, relayPort] = relayDetails.relayAddress.split(":");

View File

@@ -0,0 +1,11 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TGatewayV2DALFactory = ReturnType<typeof gatewayV2DalFactory>;
export const gatewayV2DalFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.GatewayV2);
return orm;
};

View File

@@ -1,7 +1,11 @@
import * as x509 from "@peculiar/x509";
import { TProxies } from "@app/db/schemas";
import { PgSqlLock } from "@app/keystore/keystore";
import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { OrgServiceActor } from "@app/lib/types";
import { 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 {
@@ -11,13 +15,18 @@ import {
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { TProxyDALFactory } from "../proxy/proxy-dal";
import { isInstanceProxy } from "../proxy/proxy-fns";
import { TProxyServiceFactory } from "../proxy/proxy-service";
import { TGatewayV2DALFactory } from "./gateway-v2-dal";
import { TOrgGatewayConfigV2DALFactory } from "./org-gateway-config-v2-dal";
type TGatewayV2ServiceFactoryDep = {
orgGatewayConfigV2DAL: Pick<TOrgGatewayConfigV2DALFactory, "findOne" | "create" | "transaction" | "findById">;
kmsService: TKmsServiceFactory;
proxyService: TProxyServiceFactory;
gatewayV2DAL: TGatewayV2DALFactory;
proxyDAL: TProxyDALFactory;
};
export type TGatewayV2ServiceFactory = ReturnType<typeof gatewayV2ServiceFactory>;
@@ -25,7 +34,9 @@ export type TGatewayV2ServiceFactory = ReturnType<typeof gatewayV2ServiceFactory
export const gatewayV2ServiceFactory = ({
orgGatewayConfigV2DAL,
kmsService,
proxyService
proxyService,
gatewayV2DAL,
proxyDAL
}: TGatewayV2ServiceFactoryDep) => {
const $getOrgCAs = async (orgId: string) => {
const { encryptor: orgKmsEncryptor, decryptor: orgKmsDecryptor } = await kmsService.createCipherPairWithDataKey({
@@ -197,11 +208,179 @@ export const gatewayV2ServiceFactory = ({
};
};
const registerGateway = async ({ orgId, proxyName }: { orgId: string; actorId: string; proxyName: string }) => {
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 orgGatewayConfig = await orgGatewayConfigV2DAL.findOne({ orgId: orgPermission.orgId });
if (!orgGatewayConfig) return [];
const gateways = await gatewayV2DAL.find({
orgId: orgPermission.orgId
});
return gateways;
};
const getPlatformConnectionDetailsByGatewayId = async (gatewayId: string) => {
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.proxyId) {
throw new BadRequestError({
message: "Gateway is not associated with a proxy"
});
}
// 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 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 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)
]
});
const gatewayClientCertPrivateKey = crypto.nativeCrypto.KeyObject.from(clientKeys.privateKey);
const proxyCredentials = await proxyService.getCredentialsForClient({
proxyId: gateway.proxyId,
orgId: gateway.orgId,
gatewayId,
actor: ActorType.PLATFORM
});
return {
proxyIp: proxyCredentials.proxyIp,
gateway: {
clientCertificate: clientCert.toString("pem"),
clientPrivateKey: gatewayClientCertPrivateKey.export({ format: "pem", type: "pkcs8" }).toString(),
clientCertificateChain: constructPemChainFromCerts([gatewayClientCaCert, rootGatewayCaCert]),
serverCA: rootGatewayCaCert.toString("pem")
},
proxy: {
clientCertificate: proxyCredentials.clientCertificate,
clientPrivateKey: proxyCredentials.clientPrivateKey,
serverCertificateChain: proxyCredentials.serverCertificateChain
}
};
};
const registerGateway = async ({
orgId,
actorId,
proxyName,
name
}: {
orgId: string;
actorId: string;
proxyName: string;
name: string;
}) => {
const orgCAs = await $getOrgCAs(orgId);
// TODO: Save gateway to DB and set Gateway ID as principal in SSH certificate
// only throw error if proxy is different from existing DB record
let proxy: TProxies;
if (isInstanceProxy(proxyName)) {
proxy = await proxyDAL.findOne({ name: proxyName });
} else {
proxy = await proxyDAL.findOne({ orgId, name: proxyName });
}
if (!proxy) {
throw new Error("Proxy not found");
}
const [gateway] = await gatewayV2DAL.upsert(
[
{
orgId,
name,
identityId: actorId,
proxyId: proxy.id
}
],
["identityId"]
);
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
const gatewayServerCaCert = new x509.X509Certificate(orgCAs.gatewayServerCaCertificate);
@@ -253,11 +432,12 @@ export const gatewayV2ServiceFactory = ({
const proxyCredentials = await proxyService.getCredentialsForGateway({
proxyName,
orgId
orgId,
gatewayId: gateway.id
});
return {
// TODO: return gateway ID
gatewayId: gateway.id,
proxyIp: proxyCredentials.proxyIp,
pki: {
serverCertificate: gatewayServerCertificate.toString("pem"),
@@ -274,6 +454,8 @@ export const gatewayV2ServiceFactory = ({
};
return {
registerGateway
listGateways,
registerGateway,
getPlatformConnectionDetailsByGatewayId
};
};

View File

@@ -4,6 +4,7 @@ import { TProxies } from "@app/db/schemas";
import { PgSqlLock } from "@app/keystore/keystore";
import { crypto } from "@app/lib/crypto";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type";
import { constructPemChainFromCerts, prependCertToPemChain } from "@app/services/certificate/certificate-fns";
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
import {
@@ -89,7 +90,7 @@ export const proxyServiceFactory = ({
x509.KeyUsageFlags.keyEncipherment,
true
),
new x509.BasicConstraintsExtension(true, 0, true),
new x509.BasicConstraintsExtension(true, 2, true),
await x509.AuthorityKeyIdentifierExtension.create(rootCaCert, false),
await x509.SubjectKeyIdentifierExtension.create(orgProxyCaKeys.publicKey)
]
@@ -120,7 +121,7 @@ export const proxyServiceFactory = ({
x509.KeyUsageFlags.keyEncipherment,
true
),
new x509.BasicConstraintsExtension(true, 0, true),
new x509.BasicConstraintsExtension(true, 1, true),
await x509.AuthorityKeyIdentifierExtension.create(rootCaCert, false),
await x509.SubjectKeyIdentifierExtension.create(instanceProxyCaKeys.publicKey)
]
@@ -587,88 +588,28 @@ export const proxyServiceFactory = ({
};
};
const getCredentialsForGateway = async ({ proxyName, orgId }: { proxyName: string; orgId: string }) => {
let proxy: TProxies | null;
if (isInstanceProxy(proxyName)) {
proxy = await proxyDAL.findOne({
name: proxyName
});
} else {
proxy = await proxyDAL.findOne({
orgId,
name: proxyName
});
}
if (!proxy) {
throw new NotFoundError({
message: "Proxy not found"
});
}
const keyAlgorithm = SshCertKeyAlgorithm.RSA_2048;
const { publicKey: proxyClientSshPublicKey, privateKey: proxyClientSshPrivateKey } =
await createSshKeyPair(keyAlgorithm);
if (isInstanceProxy(proxyName)) {
const instanceCAs = await $getInstanceCAs();
const proxyClientSshCert = await createSshCert({
caPrivateKey: instanceCAs.instanceProxySshServerCaPrivateKey.toString("utf8"),
clientPublicKey: proxyClientSshPublicKey,
keyId: `proxy-client-${proxy.id}`,
principals: ["gateway ID"], // TODO: set gateway ID as principal in SSH certificate
certType: SshCertType.USER,
requestedTtl: "30d"
});
return {
proxyIp: proxy.ip,
clientSshCert: proxyClientSshCert.signedPublicKey,
clientSshPrivateKey: proxyClientSshPrivateKey,
serverCAPublicKey: instanceCAs.instanceProxySshServerCaPublicKey.toString("utf8")
};
}
const orgCAs = await $getOrgCAs(orgId);
const proxyClientSshCert = await createSshCert({
caPrivateKey: orgCAs.proxySshServerCaPrivateKey.toString("utf8"),
clientPublicKey: proxyClientSshPublicKey,
keyId: `proxy-client-${proxy.id}`,
principals: [orgId],
certType: SshCertType.USER,
requestedTtl: "30d"
});
return {
proxyIp: proxy.ip,
clientSshCert: proxyClientSshCert.signedPublicKey,
clientSshPrivateKey: proxyClientSshPrivateKey,
serverCAPublicKey: orgCAs.proxySshServerCaPublicKey.toString("utf8")
};
};
const $generateProxyCredentials = async ({
const $generateProxyServerCredentials = async ({
ip,
orgId,
rootProxyPkiCaCertificate,
proxyPkiServerCaCertificate,
proxyPkiServerCaPrivateKey,
proxySshServerCaPrivateKey,
proxyPkiServerCaCertificateChain,
proxySshClientCaPublicKey
proxyPkiClientCaCertificate,
proxyPkiClientCaCertificateChain,
proxySshClientCaPublicKey,
proxySshServerCaPrivateKey
}: {
ip: string;
rootProxyPkiCaCertificate: Buffer;
proxyPkiServerCaCertificate: Buffer;
proxyPkiServerCaPrivateKey: Buffer;
proxyPkiClientCaCertificateChain: Buffer;
proxyPkiClientCaCertificate: Buffer;
proxySshServerCaPrivateKey: Buffer;
proxyPkiServerCaCertificateChain: Buffer;
proxySshClientCaPublicKey: Buffer;
orgId?: string;
}) => {
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
const proxyServerCaCert = new x509.X509Certificate(proxyPkiServerCaCertificate);
const rootProxyCaCert = new x509.X509Certificate(rootProxyPkiCaCertificate);
const proxyClientCaCert = new x509.X509Certificate(proxyPkiClientCaCertificate);
const proxyServerCaSkObj = crypto.nativeCrypto.createPrivateKey({
key: proxyPkiServerCaPrivateKey,
format: "der",
@@ -733,12 +674,11 @@ export const proxyServiceFactory = ({
return {
pki: {
serverCertificate: proxyServerCertificate.toString("pem"),
serverCertificateChain: prependCertToPemChain(
proxyServerCaCert,
proxyPkiServerCaCertificateChain.toString("utf8")
),
serverPrivateKey: proxyServerCertPrivateKey.export({ format: "pem", type: "pkcs8" }).toString(),
clientCA: rootProxyCaCert.toString("pem")
clientCertificateChain: prependCertToPemChain(
proxyClientCaCert,
proxyPkiClientCaCertificateChain.toString("utf8")
)
},
ssh: {
serverCertificate: proxyServerSshCert.signedPublicKey,
@@ -748,6 +688,205 @@ export const proxyServiceFactory = ({
};
};
const $generateProxyClientCredentials = async ({
actor,
gatewayId,
orgId,
proxyPkiClientCaCertificate,
proxyPkiClientCaPrivateKey,
proxyPkiServerCaCertificate,
proxyPkiServerCaCertificateChain
}: {
actor: ActorType;
gatewayId: string;
orgId: string;
proxyPkiClientCaCertificate: Buffer;
proxyPkiClientCaPrivateKey: Buffer;
proxyPkiServerCaCertificate: Buffer;
proxyPkiServerCaCertificateChain: Buffer;
}) => {
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
const proxyClientCaCert = new x509.X509Certificate(proxyPkiClientCaCertificate);
const proxyServerCaCert = new x509.X509Certificate(proxyPkiServerCaCertificate);
const proxyClientCaSkObj = crypto.nativeCrypto.createPrivateKey({
key: proxyPkiClientCaPrivateKey,
format: "der",
type: "pkcs8"
});
const importedProxyClientCaPrivateKey = await crypto.nativeCrypto.subtle.importKey(
"pkcs8",
proxyClientCaSkObj.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 clientCertPrivateKey = crypto.nativeCrypto.KeyObject.from(clientKeys.privateKey);
const clientCertSerialNumber = createSerialNumber();
const clientCert = await x509.X509CertificateGenerator.create({
serialNumber: clientCertSerialNumber,
subject: `O=${orgId},OU=proxy-client,CN=${actor}:${gatewayId}`,
issuer: proxyClientCaCert.subject,
notAfter: clientCertExpiration,
notBefore: clientCertIssuedAt,
signingKey: importedProxyClientCaPrivateKey,
publicKey: clientKeys.publicKey,
signingAlgorithm: alg,
extensions: [
new x509.BasicConstraintsExtension(false),
await x509.AuthorityKeyIdentifierExtension.create(proxyClientCaCert, 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)
]
});
return {
clientCertificate: clientCert.toString("pem"),
clientPrivateKey: clientCertPrivateKey.export({ format: "pem", type: "pkcs8" }).toString(),
serverCertificateChain: prependCertToPemChain(
proxyServerCaCert,
proxyPkiServerCaCertificateChain.toString("utf8")
)
};
};
const getCredentialsForGateway = async ({
proxyName,
orgId,
gatewayId
}: {
proxyName: string;
orgId: string;
gatewayId: string;
}) => {
let proxy: TProxies | null;
if (isInstanceProxy(proxyName)) {
proxy = await proxyDAL.findOne({
name: proxyName
});
} else {
proxy = await proxyDAL.findOne({
orgId,
name: proxyName
});
}
if (!proxy) {
throw new NotFoundError({
message: "Proxy not found"
});
}
const keyAlgorithm = SshCertKeyAlgorithm.RSA_2048;
const { publicKey: proxyClientSshPublicKey, privateKey: proxyClientSshPrivateKey } =
await createSshKeyPair(keyAlgorithm);
if (isInstanceProxy(proxyName)) {
const instanceCAs = await $getInstanceCAs();
const proxyClientSshCert = await createSshCert({
caPrivateKey: instanceCAs.instanceProxySshServerCaPrivateKey.toString("utf8"),
clientPublicKey: proxyClientSshPublicKey,
keyId: `proxy-client-${proxy.id}`,
principals: [gatewayId],
certType: SshCertType.USER,
requestedTtl: "30d"
});
return {
proxyIp: proxy.ip,
clientSshCert: proxyClientSshCert.signedPublicKey,
clientSshPrivateKey: proxyClientSshPrivateKey,
serverCAPublicKey: instanceCAs.instanceProxySshServerCaPublicKey.toString("utf8")
};
}
const orgCAs = await $getOrgCAs(orgId);
const proxyClientSshCert = await createSshCert({
caPrivateKey: orgCAs.proxySshServerCaPrivateKey.toString("utf8"),
clientPublicKey: proxyClientSshPublicKey,
keyId: `proxy-client-${proxy.id}`,
principals: [gatewayId],
certType: SshCertType.USER,
requestedTtl: "30d"
});
return {
proxyIp: proxy.ip,
clientSshCert: proxyClientSshCert.signedPublicKey,
clientSshPrivateKey: proxyClientSshPrivateKey,
serverCAPublicKey: orgCAs.proxySshServerCaPublicKey.toString("utf8")
};
};
const getCredentialsForClient = async ({
proxyId,
orgId,
gatewayId,
actor
}: {
proxyId: string;
orgId: string;
gatewayId: string;
actor: ActorType;
}) => {
const proxy = await proxyDAL.findOne({
id: proxyId
});
if (!proxy) {
throw new NotFoundError({
message: "Proxy not found"
});
}
if (isInstanceProxy(proxy.name)) {
const instanceCAs = await $getInstanceCAs();
const proxyCertificateCredentials = await $generateProxyClientCredentials({
actor,
gatewayId,
orgId,
proxyPkiClientCaCertificate: instanceCAs.instanceProxyPkiClientCaCertificate,
proxyPkiClientCaPrivateKey: instanceCAs.instanceProxyPkiClientCaPrivateKey,
proxyPkiServerCaCertificate: instanceCAs.instanceProxyPkiServerCaCertificate,
proxyPkiServerCaCertificateChain: instanceCAs.instanceProxyPkiServerCaCertificateChain
});
return {
...proxyCertificateCredentials,
proxyIp: proxy.ip
};
}
const orgCAs = await $getOrgCAs(orgId);
const proxyCertificateCredentials = await $generateProxyClientCredentials({
actor,
gatewayId,
orgId,
proxyPkiClientCaCertificate: orgCAs.proxyPkiClientCaCertificate,
proxyPkiClientCaPrivateKey: orgCAs.proxyPkiClientCaPrivateKey,
proxyPkiServerCaCertificate: orgCAs.proxyPkiServerCaCertificate,
proxyPkiServerCaCertificateChain: orgCAs.proxyPkiServerCaCertificateChain
});
return {
...proxyCertificateCredentials,
proxyIp: proxy.ip
};
};
const registerProxy = async ({
ip,
name,
@@ -837,14 +976,12 @@ export const proxyServiceFactory = ({
if (isInstanceProxy(name)) {
const instanceCAs = await $getInstanceCAs();
return $generateProxyCredentials({
return $generateProxyServerCredentials({
ip,
rootProxyPkiCaCertificate: instanceCAs.rootProxyPkiCaCertificate,
proxyPkiServerCaCertificate: instanceCAs.instanceProxyPkiServerCaCertificate,
proxyPkiServerCaPrivateKey: instanceCAs.instanceProxyPkiServerCaPrivateKey,
proxyPkiServerCaCertificateChain: instanceCAs.instanceProxyPkiServerCaCertificateChain,
proxyPkiClientCaCertificate: instanceCAs.instanceProxyPkiClientCaCertificate,
proxyPkiClientCaCertificateChain: instanceCAs.instanceProxyPkiClientCaCertificateChain,
proxySshServerCaPrivateKey: instanceCAs.instanceProxySshServerCaPrivateKey,
proxySshClientCaPublicKey: instanceCAs.instanceProxySshClientCaPublicKey
});
@@ -852,17 +989,13 @@ export const proxyServiceFactory = ({
if (proxy.orgId) {
const orgCAs = await $getOrgCAs(proxy.orgId);
const instanceCAs = await $getInstanceCAs();
return $generateProxyCredentials({
return $generateProxyServerCredentials({
ip,
orgId: proxy.orgId,
rootProxyPkiCaCertificate: instanceCAs.rootProxyPkiCaCertificate,
proxyPkiServerCaCertificate: orgCAs.proxyPkiServerCaCertificate,
proxyPkiServerCaPrivateKey: orgCAs.proxyPkiServerCaPrivateKey,
proxyPkiServerCaCertificateChain: orgCAs.proxyPkiServerCaCertificateChain,
proxyPkiClientCaCertificate: orgCAs.proxyPkiClientCaCertificate,
proxyPkiClientCaCertificateChain: orgCAs.proxyPkiClientCaCertificateChain,
proxySshServerCaPrivateKey: orgCAs.proxySshServerCaPrivateKey,
proxySshClientCaPublicKey: orgCAs.proxySshClientCaPublicKey
});
@@ -875,6 +1008,7 @@ export const proxyServiceFactory = ({
return {
registerProxy,
getCredentialsForGateway
getCredentialsForGateway,
getCredentialsForClient
};
};

View File

@@ -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,7 @@ import { projectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/proje
import { projectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service";
import { instanceProxyConfigDalFactory } from "@app/ee/services/proxy/instance-proxy-config-dal";
import { orgProxyConfigDalFactory } from "@app/ee/services/proxy/org-proxy-config-dal";
import { proxyDalFactory } from "@app/ee/services/proxy/proxy-dal";
import { proxyServiceFactory } from "@app/ee/services/proxy/proxy-service";
import { rateLimitDALFactory } from "@app/ee/services/rate-limit/rate-limit-dal";
import { rateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-service";
@@ -147,8 +151,6 @@ import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service
import { certificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
import { certificateDALFactory } from "@app/services/certificate/certificate-dal";
import { certificateSecretDALFactory } from "@app/services/certificate/certificate-secret-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 { certificateServiceFactory } from "@app/services/certificate/certificate-service";
import { certificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal";
import { certificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
@@ -319,7 +321,6 @@ import { registerV1Routes } from "./v1";
import { initializeOauthConfigSync } from "./v1/sso-router";
import { registerV2Routes } from "./v2";
import { registerV3Routes } from "./v3";
import { proxyDalFactory } from "@app/ee/services/proxy/proxy-dal";
const histogram = monitorEventLoopDelay({ resolution: 20 });
histogram.enable();
@@ -948,6 +949,7 @@ export const registerRoutes = async (
const instanceProxyConfigDAL = instanceProxyConfigDalFactory(db);
const orgProxyConfigDAL = orgProxyConfigDalFactory(db);
const proxyDAL = proxyDalFactory(db);
const gatewayV2DAL = gatewayV2DalFactory(db);
const orgGatewayConfigV2DAL = orgGatewayConfigV2DalFactory(db);
@@ -1626,9 +1628,26 @@ export const registerRoutes = async (
identityAuthTemplateDAL
});
const dynamicSecretProviders = buildDynamicSecretProviders({
gatewayService
const proxyService = proxyServiceFactory({
instanceProxyConfigDAL,
orgProxyConfigDAL,
proxyDAL,
kmsService
});
const gatewayV2Service = gatewayV2ServiceFactory({
kmsService,
proxyService,
orgGatewayConfigV2DAL,
gatewayV2DAL,
proxyDAL
});
const dynamicSecretProviders = buildDynamicSecretProviders({
gatewayService,
gatewayV2Service
});
const dynamicSecretQueueService = dynamicSecretLeaseQueueServiceFactory({
queueService,
dynamicSecretLeaseDAL,
@@ -1648,6 +1667,7 @@ export const registerRoutes = async (
licenseService,
kmsService,
gatewayDAL,
gatewayV2DAL,
resourceMetadataDAL
});
@@ -1972,19 +1992,6 @@ export const registerRoutes = async (
appConnectionDAL
});
const proxyService = proxyServiceFactory({
instanceProxyConfigDAL,
orgProxyConfigDAL,
proxyDAL,
kmsService
});
const gatewayV2Service = gatewayV2ServiceFactory({
kmsService,
proxyService,
orgGatewayConfigV2DAL
});
// setup the communication with license key server
await licenseService.init();

View File

@@ -0,0 +1 @@
export { gatewaysV2QueryKeys } from "./queries";

View File

@@ -0,0 +1,18 @@
import { queryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { TGatewayV2 } from "./types";
export const gatewaysV2QueryKeys = {
allKey: () => ["gateways-v2"],
listKey: () => [...gatewaysV2QueryKeys.allKey(), "list"],
list: () =>
queryOptions({
queryKey: gatewaysV2QueryKeys.listKey(),
queryFn: async () => {
const { data } = await apiRequest.get<{ gateways: TGatewayV2[] }>("/api/v2/gateways");
return data.gateways;
}
})
};

View File

@@ -0,0 +1,11 @@
export type TGatewayV2 = {
id: string;
identityId: string;
name: string;
createdAt: string;
updatedAt: string;
identity: {
name: string;
id: string;
};
};

View File

@@ -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 = {
@@ -12,7 +13,9 @@ export const gatewaysQueryKeys = {
queryKey: gatewaysQueryKeys.listKey(),
queryFn: async () => {
const { data } = await apiRequest.get<{ gateways: TGateway[] }>("/api/v1/gateways");
return data.gateways;
const { data: dataV2 } = await apiRequest.get<TGatewayV2[]>("/api/v2/gateways");
return [...data.gateways, ...dataV2];
}
})
};