feat: updated backend to new data structure for org

This commit is contained in:
=
2025-02-26 20:45:37 +05:30
parent 7905017121
commit 8520029958
17 changed files with 310 additions and 439 deletions
+7 -15
View File
@@ -68,9 +68,6 @@ import {
TExternalKms, TExternalKms,
TExternalKmsInsert, TExternalKmsInsert,
TExternalKmsUpdate, TExternalKmsUpdate,
TGatewayInstanceConfig,
TGatewayInstanceConfigInsert,
TGatewayInstanceConfigUpdate,
TGateways, TGateways,
TGatewaysInsert, TGatewaysInsert,
TGatewaysUpdate, TGatewaysUpdate,
@@ -185,9 +182,9 @@ import {
TOrgBots, TOrgBots,
TOrgBotsInsert, TOrgBotsInsert,
TOrgBotsUpdate, TOrgBotsUpdate,
TOrgGatewayRootCa, TOrgGatewayConfig,
TOrgGatewayRootCaInsert, TOrgGatewayConfigInsert,
TOrgGatewayRootCaUpdate, TOrgGatewayConfigUpdate,
TOrgMemberships, TOrgMemberships,
TOrgMembershipsInsert, TOrgMembershipsInsert,
TOrgMembershipsUpdate, TOrgMembershipsUpdate,
@@ -940,15 +937,10 @@ declare module "knex/types/tables" {
TKmipClientCertificatesUpdate TKmipClientCertificatesUpdate
>; >;
[TableName.Gateway]: KnexOriginal.CompositeTableType<TGateways, TGatewaysInsert, TGatewaysUpdate>; [TableName.Gateway]: KnexOriginal.CompositeTableType<TGateways, TGatewaysInsert, TGatewaysUpdate>;
[TableName.GatewayInstanceConfig]: KnexOriginal.CompositeTableType< [TableName.OrgGatewayConfig]: KnexOriginal.CompositeTableType<
TGatewayInstanceConfig, TOrgGatewayConfig,
TGatewayInstanceConfigInsert, TOrgGatewayConfigInsert,
TGatewayInstanceConfigUpdate TOrgGatewayConfigUpdate
>;
[TableName.OrgGatewayRootCa]: KnexOriginal.CompositeTableType<
TOrgGatewayRootCa,
TOrgGatewayRootCaInsert,
TOrgGatewayRootCaUpdate
>; >;
} }
} }
@@ -4,47 +4,43 @@ import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils"; import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.GatewayInstanceConfig))) { if (!(await knex.schema.hasTable(TableName.OrgGatewayConfig))) {
await knex.schema.createTable(TableName.GatewayInstanceConfig, (t) => { await knex.schema.createTable(TableName.OrgGatewayConfig, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("caKeyAlgorithm").notNullable(); t.string("rootCaKeyAlgorithm").notNullable();
t.boolean("isDisabled").defaultTo(false);
t.string("infisicalClientCaSerialNumber").notNullable(); t.datetime("rootCaIssuedAt").notNullable();
t.datetime("infisicalClientCaIssuedAt").notNullable(); t.datetime("rootCaExpiration").notNullable();
t.datetime("infisicalClientCaExpiration").notNullable(); t.string("rootCaSerialNumber").notNullable();
t.binary("encryptedInfisicalClientCaCertificate").notNullable(); t.binary("encryptedRootCaCertificate").notNullable();
t.binary("encryptedInfisicalClientCaPrivateKey").notNullable(); t.binary("encryptedRootCaPrivateKey").notNullable();
t.string("infisicalClientCertSerialNumber").notNullable(); t.datetime("clientCaIssuedAt").notNullable();
t.string("infisicalClientCertKeyAlgorithm").notNullable(); t.datetime("clientCaExpiration").notNullable();
t.datetime("infisicalClientCertIssuedAt").notNullable(); t.string("clientCaSerialNumber");
t.datetime("infisicalClientCertExpiration").notNullable(); t.binary("encryptedClientCaCertificate").notNullable();
t.binary("encryptedInfisicalClientCertificate").notNullable(); t.binary("encryptedClientCaPrivateKey").notNullable();
t.binary("encryptedInfisicalClientPrivateKey").notNullable();
t.timestamps(true, true, true);
});
await createOnUpdateTrigger(knex, TableName.GatewayInstanceConfig); t.string("clientCertSerialNumber").notNullable();
} t.string("clientCertKeyAlgorithm").notNullable();
t.datetime("clientCertIssuedAt").notNullable();
t.datetime("clientCertExpiration").notNullable();
t.binary("encryptedClientCertificate").notNullable();
t.binary("encryptedClientPrivateKey").notNullable();
if (!(await knex.schema.hasTable(TableName.OrgGatewayRootCa))) { t.datetime("gatewayCaIssuedAt").notNullable();
await knex.schema.createTable(TableName.OrgGatewayRootCa, (t) => { t.datetime("gatewayCaExpiration").notNullable();
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.string("gatewayCaSerialNumber").notNullable();
t.string("caKeyAlgorithm").notNullable(); t.binary("encryptedGatewayCaCertificate").notNullable();
t.string("caSerialNumber").notNullable(); t.binary("encryptedGatewayCaPrivateKey").notNullable();
t.datetime("caIssuedAt").notNullable();
t.datetime("caExpiration").notNullable();
t.binary("encryptedCaCertificate").notNullable();
t.binary("encryptedCaPrivateKey").notNullable();
t.uuid("orgId").notNullable(); t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE"); t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.unique("orgId");
t.timestamps(true, true, true); t.timestamps(true, true, true);
}); });
await createOnUpdateTrigger(knex, TableName.OrgGatewayRootCa); await createOnUpdateTrigger(knex, TableName.OrgGatewayConfig);
} }
if (!(await knex.schema.hasTable(TableName.Gateway))) { if (!(await knex.schema.hasTable(TableName.Gateway))) {
@@ -60,7 +56,7 @@ export async function up(knex: Knex): Promise<void> {
t.binary("relayAddress").notNullable(); t.binary("relayAddress").notNullable();
t.uuid("orgGatewayRootCaId").notNullable(); t.uuid("orgGatewayRootCaId").notNullable();
t.foreign("orgGatewayRootCaId").references("id").inTable(TableName.OrgGatewayRootCa).onDelete("CASCADE"); t.foreign("orgGatewayRootCaId").references("id").inTable(TableName.OrgGatewayConfig).onDelete("CASCADE");
t.uuid("identityId").notNullable(); t.uuid("identityId").notNullable();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE"); t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
@@ -73,12 +69,9 @@ export async function up(knex: Knex): Promise<void> {
} }
export async function down(knex: Knex): Promise<void> { export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.GatewayInstanceConfig);
await dropOnUpdateTrigger(knex, TableName.GatewayInstanceConfig);
await knex.schema.dropTableIfExists(TableName.Gateway); await knex.schema.dropTableIfExists(TableName.Gateway);
await dropOnUpdateTrigger(knex, TableName.Gateway); await dropOnUpdateTrigger(knex, TableName.Gateway);
await knex.schema.dropTableIfExists(TableName.OrgGatewayRootCa); await knex.schema.dropTableIfExists(TableName.OrgGatewayConfig);
await dropOnUpdateTrigger(knex, TableName.OrgGatewayRootCa); await dropOnUpdateTrigger(knex, TableName.OrgGatewayConfig);
} }
@@ -1,33 +0,0 @@
// 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 GatewayInstanceConfigSchema = z.object({
id: z.string().uuid(),
caKeyAlgorithm: z.string(),
isDisabled: z.boolean().default(false).nullable().optional(),
infisicalClientCaSerialNumber: z.string(),
infisicalClientCaIssuedAt: z.date(),
infisicalClientCaExpiration: z.date(),
encryptedInfisicalClientCaCertificate: zodBuffer,
encryptedInfisicalClientCaPrivateKey: zodBuffer,
infisicalClientCertSerialNumber: z.string(),
infisicalClientCertKeyAlgorithm: z.string(),
infisicalClientCertIssuedAt: z.date(),
infisicalClientCertExpiration: z.date(),
encryptedInfisicalClientCertificate: zodBuffer,
encryptedInfisicalClientPrivateKey: zodBuffer,
createdAt: z.date(),
updatedAt: z.date()
});
export type TGatewayInstanceConfig = z.infer<typeof GatewayInstanceConfigSchema>;
export type TGatewayInstanceConfigInsert = Omit<z.input<typeof GatewayInstanceConfigSchema>, TImmutableDBKeys>;
export type TGatewayInstanceConfigUpdate = Partial<Omit<z.input<typeof GatewayInstanceConfigSchema>, TImmutableDBKeys>>;
+1 -2
View File
@@ -20,7 +20,6 @@ export * from "./certificates";
export * from "./dynamic-secret-leases"; export * from "./dynamic-secret-leases";
export * from "./dynamic-secrets"; export * from "./dynamic-secrets";
export * from "./external-kms"; export * from "./external-kms";
export * from "./gateway-instance-config";
export * from "./gateways"; export * from "./gateways";
export * from "./git-app-install-sessions"; export * from "./git-app-install-sessions";
export * from "./git-app-org"; export * from "./git-app-org";
@@ -59,7 +58,7 @@ export * from "./ldap-group-maps";
export * from "./models"; export * from "./models";
export * from "./oidc-configs"; export * from "./oidc-configs";
export * from "./org-bots"; export * from "./org-bots";
export * from "./org-gateway-root-ca"; export * from "./org-gateway-config";
export * from "./org-memberships"; export * from "./org-memberships";
export * from "./org-roles"; export * from "./org-roles";
export * from "./organizations"; export * from "./organizations";
+1 -2
View File
@@ -114,8 +114,7 @@ export enum TableName {
SnapshotSecretV2 = "secret_snapshot_secrets_v2", SnapshotSecretV2 = "secret_snapshot_secrets_v2",
ProjectSplitBackfillIds = "project_split_backfill_ids", ProjectSplitBackfillIds = "project_split_backfill_ids",
// Gateway // Gateway
GatewayInstanceConfig = "gateway_instance_config", OrgGatewayConfig = "org_gateway_config",
OrgGatewayRootCa = "org_gateway_root_ca",
Gateway = "gateways", Gateway = "gateways",
// junction tables with tags // junction tables with tags
SecretV2JnTag = "secret_v2_tag_junction", SecretV2JnTag = "secret_v2_tag_junction",
@@ -0,0 +1,43 @@
// 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 OrgGatewayConfigSchema = z.object({
id: z.string().uuid(),
rootCaKeyAlgorithm: z.string(),
rootCaIssuedAt: z.date(),
rootCaExpiration: z.date(),
rootCaSerialNumber: z.string(),
encryptedRootCaCertificate: zodBuffer,
encryptedRootCaPrivateKey: zodBuffer,
clientCaIssuedAt: z.date(),
clientCaExpiration: z.date(),
clientCaSerialNumber: z.string().nullable().optional(),
encryptedClientCaCertificate: zodBuffer,
encryptedClientCaPrivateKey: zodBuffer,
clientCertSerialNumber: z.string(),
clientCertKeyAlgorithm: z.string(),
clientCertIssuedAt: z.date(),
clientCertExpiration: z.date(),
encryptedClientCertificate: zodBuffer,
encryptedClientPrivateKey: zodBuffer,
gatewayCaIssuedAt: z.date(),
gatewayCaExpiration: z.date(),
gatewayCaSerialNumber: z.string(),
encryptedGatewayCaCertificate: zodBuffer,
encryptedGatewayCaPrivateKey: zodBuffer,
orgId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TOrgGatewayConfig = z.infer<typeof OrgGatewayConfigSchema>;
export type TOrgGatewayConfigInsert = Omit<z.input<typeof OrgGatewayConfigSchema>, TImmutableDBKeys>;
export type TOrgGatewayConfigUpdate = Partial<Omit<z.input<typeof OrgGatewayConfigSchema>, TImmutableDBKeys>>;
@@ -1,27 +0,0 @@
// 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 OrgGatewayRootCaSchema = z.object({
id: z.string().uuid(),
caKeyAlgorithm: z.string(),
caSerialNumber: z.string(),
caIssuedAt: z.date(),
caExpiration: z.date(),
encryptedCaCertificate: zodBuffer,
encryptedCaPrivateKey: zodBuffer,
orgId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TOrgGatewayRootCa = z.infer<typeof OrgGatewayRootCaSchema>;
export type TOrgGatewayRootCaInsert = Omit<z.input<typeof OrgGatewayRootCaSchema>, TImmutableDBKeys>;
export type TOrgGatewayRootCaUpdate = Partial<Omit<z.input<typeof OrgGatewayRootCaSchema>, TImmutableDBKeys>>;
@@ -27,6 +27,7 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => {
200: z.object({ 200: z.object({
turnServerUsername: z.string(), turnServerUsername: z.string(),
turnServerPassword: z.string(), turnServerPassword: z.string(),
turnServerRealm: z.string(),
turnServerAddress: z.string(), turnServerAddress: z.string(),
infisicalStaticIp: z.string() infisicalStaticIp: z.string()
}) })
@@ -1,11 +0,0 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TGatewayInstanceConfigDALFactory = ReturnType<typeof gatewayInstanceConfigDALFactory>;
export const gatewayInstanceConfigDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.GatewayInstanceConfig);
return orm;
};
@@ -2,6 +2,8 @@ import crypto from "node:crypto";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import fs from "fs/promises";
import path from "path/posix";
import { PgSqlLock } from "@app/keystore/keystore"; import { PgSqlLock } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
@@ -21,13 +23,13 @@ import { OrgGatewayPermissionActions, OrgPermissionSubjects } from "../permissio
import { TPermissionServiceFactory } from "../permission/permission-service"; import { TPermissionServiceFactory } from "../permission/permission-service";
import { TGatewayDALFactory } from "./gateway-dal"; import { TGatewayDALFactory } from "./gateway-dal";
import { TExchangeAllocatedRelayAddressDTO, TGetGatewayByIdDTO, TListGatewaysDTO } from "./gateway-types"; import { TExchangeAllocatedRelayAddressDTO, TGetGatewayByIdDTO, TListGatewaysDTO } from "./gateway-types";
import { TOrgGatewayRootCaDALFactory } from "./org-gateway-root-ca-dal"; import { TOrgGatewayConfigDALFactory } from "./org-gateway-config-dal";
type TGatewayServiceFactoryDep = { type TGatewayServiceFactoryDep = {
gatewayDAL: TGatewayDALFactory; gatewayDAL: TGatewayDALFactory;
orgGatewayRootCaDAL: Pick<TOrgGatewayRootCaDALFactory, "findOne" | "create" | "transaction">; orgGatewayConfigDAL: Pick<TOrgGatewayConfigDALFactory, "findOne" | "create" | "transaction">;
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures" | "getPlan">; licenseService: Pick<TLicenseServiceFactory, "onPremFeatures" | "getPlan">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">; kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "decryptWithRootKey">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
}; };
@@ -37,9 +39,9 @@ export type TGatewayServiceFactory = ReturnType<typeof gatewayServiceFactory>;
export const gatewayServiceFactory = ({ export const gatewayServiceFactory = ({
gatewayDAL, gatewayDAL,
licenseService, licenseService,
orgGatewayRootCaDAL,
kmsService, kmsService,
permissionService permissionService,
orgGatewayConfigDAL
}: TGatewayServiceFactoryDep) => { }: TGatewayServiceFactoryDep) => {
const $validateOrgAccessToGateway = async (orgId: string) => { const $validateOrgAccessToGateway = async (orgId: string) => {
if (!licenseService.onPremFeatures.gateway) { if (!licenseService.onPremFeatures.gateway) {
@@ -64,7 +66,8 @@ export const gatewayServiceFactory = ({
if ( if (
!envCfg.GATEWAY_RELAY_AUTH_SECRET || !envCfg.GATEWAY_RELAY_AUTH_SECRET ||
!envCfg.GATEWAY_RELAY_ADDRESS || !envCfg.GATEWAY_RELAY_ADDRESS ||
!envCfg.GATEWAY_INFISICAL_STATIC_IP_ADDRESS !envCfg.GATEWAY_INFISICAL_STATIC_IP_ADDRESS ||
!envCfg.GATEWAY_RELAY_REALM
) { ) {
throw new BadRequestError({ throw new BadRequestError({
message: "Gateway handshake failed due to missing instance config." message: "Gateway handshake failed due to missing instance config."
@@ -79,6 +82,7 @@ export const gatewayServiceFactory = ({
return { return {
turnServerUsername, turnServerUsername,
turnServerPassword, turnServerPassword,
turnServerRealm: envCfg.GATEWAY_RELAY_REALM,
turnServerAddress: envCfg.GATEWAY_RELAY_ADDRESS, turnServerAddress: envCfg.GATEWAY_RELAY_ADDRESS,
infisicalStaticIp: envCfg.GATEWAY_INFISICAL_STATIC_IP_ADDRESS infisicalStaticIp: envCfg.GATEWAY_INFISICAL_STATIC_IP_ADDRESS
}; };
@@ -94,63 +98,208 @@ export const gatewayServiceFactory = ({
orgId: identityOrg orgId: identityOrg
}); });
const orgGatewayRootCa = await orgGatewayRootCaDAL.transaction(async (tx) => { const orgGatewayConfig = await orgGatewayConfigDAL.transaction(async (tx) => {
await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.OrgGatewayRootCaInit(identityOrg)]); await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.OrgGatewayRootCaInit(identityOrg)]);
const existingGateway = await orgGatewayRootCaDAL.findOne({ orgId: identityOrg }); const existingGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: identityOrg });
if (existingGateway) return existingGateway; if (existingGatewayConfig) return existingGatewayConfig;
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048); const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
// generate root CA // generate root CA
const orgGatewayRootCaSerialNumber = createSerialNumber(); const rootCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const orgGatewayRootCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); const rootCaSerialNumber = createSerialNumber();
const orgGatewayCaSkObj = crypto.KeyObject.from(orgGatewayRootCaKeys.privateKey); const rootCaSkObj = crypto.KeyObject.from(rootCaKeys.privateKey);
const orgGatewayCaIssuedAt = new Date(); const rootCaIssuedAt = new Date();
const orgGatewayCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 25)); const rootCaKeyAlgorithm = CertKeyAlgorithm.RSA_2048;
const rootCaExpiration = new Date(new Date().setFullYear(2045));
const orgGatewayCaCert = await x509.X509CertificateGenerator.createSelfSigned({ const rootCaCert = await x509.X509CertificateGenerator.createSelfSigned({
name: `CN=Org Gateway Root CA,O=${identityOrg}`, name: "CN=Infisical Gateway Root CA",
serialNumber: orgGatewayRootCaSerialNumber, serialNumber: rootCaSerialNumber,
notBefore: orgGatewayCaIssuedAt, notBefore: rootCaIssuedAt,
notAfter: orgGatewayCaExpiration, notAfter: rootCaExpiration,
signingAlgorithm: alg, signingAlgorithm: alg,
keys: orgGatewayRootCaKeys, keys: rootCaKeys,
extensions: [ extensions: [
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true), new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
await x509.SubjectKeyIdentifierExtension.create(orgGatewayRootCaKeys.publicKey) await x509.SubjectKeyIdentifierExtension.create(rootCaKeys.publicKey)
] ]
}); });
return orgGatewayRootCaDAL.create({ // generate client ca
const clientCaSerialNumber = createSerialNumber();
const clientCaIssuedAt = new Date();
const clientCaExpiration = new Date(new Date().setFullYear(2045));
const clientCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const clientCaSkObj = crypto.KeyObject.from(clientCaKeys.privateKey);
const clientCaCert = await x509.X509CertificateGenerator.create({
serialNumber: clientCaSerialNumber,
subject: "CN=Client Intermediate 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 clientKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const clientCertSerialNumber = createSerialNumber();
const clientCert = await x509.X509CertificateGenerator.create({
serialNumber: clientCertSerialNumber,
subject: "O=infisical,OU=gateway,CN=cloud-client",
issuer: clientCaCert.subject,
notAfter: clientCaExpiration,
notBefore: clientCaIssuedAt,
signingKey: clientCaKeys.privateKey,
publicKey: clientKeys.publicKey,
signingAlgorithm: alg,
extensions: [
new x509.BasicConstraintsExtension(false),
await x509.AuthorityKeyIdentifierExtension.create(clientCaCert, 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 clientSkObj = crypto.KeyObject.from(clientKeys.privateKey);
// generate gateway ca
const gatewayCaSerialNumber = createSerialNumber();
const gatewayCaIssuedAt = new Date();
const gatewayCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 10));
const gatewayCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const gatewayCaSkObj = crypto.KeyObject.from(gatewayCaKeys.privateKey);
const gatewayCaCert = await x509.X509CertificateGenerator.create({
serialNumber: gatewayCaSerialNumber,
subject: "CN=KMIP Server Intermediate CA",
issuer: rootCaCert.subject,
notBefore: gatewayCaIssuedAt,
notAfter: gatewayCaExpiration,
signingKey: rootCaKeys.privateKey,
publicKey: gatewayCaKeys.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(gatewayCaKeys.publicKey)
]
});
await fs.writeFile(path.join(__dirname, "./root-ca-cert"), rootCaCert.toString("pem"));
await fs.writeFile(path.join(__dirname, "./client-ca-cert"), clientCaCert.toString("pem"));
await fs.writeFile(path.join(__dirname, "./gateway-ca-cert"), gatewayCaCert.toString("pem"));
await fs.writeFile(path.join(__dirname, "./client-cert"), clientCert.toString("pem"));
await fs.writeFile(
path.join(__dirname, "./client-key"),
clientSkObj.export({ type: "pkcs8", format: "pem" }) as string
);
return orgGatewayConfigDAL.create({
orgId: identityOrg, orgId: identityOrg,
caIssuedAt: orgGatewayCaIssuedAt, rootCaIssuedAt,
caExpiration: orgGatewayCaExpiration, rootCaExpiration,
caSerialNumber: orgGatewayRootCaSerialNumber, rootCaSerialNumber,
encryptedCaCertificate: orgKmsEncryptor({ plainText: Buffer.from(orgGatewayCaCert.rawData) }).cipherTextBlob, rootCaKeyAlgorithm,
encryptedCaPrivateKey: orgKmsEncryptor({ encryptedRootCaPrivateKey: orgKmsEncryptor({
plainText: orgGatewayCaSkObj.export({ plainText: rootCaSkObj.export({
type: "pkcs8", type: "pkcs8",
format: "der" format: "der"
}) })
}).cipherTextBlob, }).cipherTextBlob,
caKeyAlgorithm: CertKeyAlgorithm.RSA_2048 encryptedRootCaCertificate: orgKmsEncryptor({ plainText: Buffer.from(rootCaCert.rawData) }).cipherTextBlob,
clientCaIssuedAt,
clientCaExpiration,
clientCaSerialNumber,
encryptedClientCaPrivateKey: orgKmsEncryptor({
plainText: clientCaSkObj.export({
type: "pkcs8",
format: "der"
})
}).cipherTextBlob,
encryptedClientCaCertificate: orgKmsEncryptor({
plainText: Buffer.from(clientCaCert.rawData)
}).cipherTextBlob,
clientCertIssuedAt: clientCaIssuedAt,
clientCertExpiration: clientCaExpiration,
clientCertKeyAlgorithm: CertKeyAlgorithm.RSA_2048,
clientCertSerialNumber,
encryptedClientPrivateKey: orgKmsEncryptor({
plainText: clientSkObj.export({
type: "pkcs8",
format: "der"
})
}).cipherTextBlob,
encryptedClientCertificate: orgKmsEncryptor({
plainText: Buffer.from(clientCert.rawData)
}).cipherTextBlob,
gatewayCaIssuedAt,
gatewayCaExpiration,
gatewayCaSerialNumber,
encryptedGatewayCaPrivateKey: orgKmsEncryptor({
plainText: gatewayCaSkObj.export({
type: "pkcs8",
format: "der"
})
}).cipherTextBlob,
encryptedGatewayCaCertificate: orgKmsEncryptor({
plainText: Buffer.from(gatewayCaCert.rawData)
}).cipherTextBlob
}); });
}); });
const caCertObj = new x509.X509Certificate( const rootCaCert = new x509.X509Certificate(
orgKmsDecryptor({ orgKmsDecryptor({
cipherTextBlob: orgGatewayRootCa.encryptedCaCertificate cipherTextBlob: orgGatewayConfig.encryptedRootCaCertificate
}) })
); );
const caAlg = keyAlgorithmToAlgCfg(orgGatewayRootCa.caKeyAlgorithm as CertKeyAlgorithm); const clientCaCert = new x509.X509Certificate(
const caSkObj = crypto.createPrivateKey({ orgKmsDecryptor({
key: orgKmsDecryptor({ cipherTextBlob: orgGatewayRootCa.encryptedCaPrivateKey }), cipherTextBlob: orgGatewayConfig.encryptedClientCaCertificate
})
);
const gatewayCaAlg = keyAlgorithmToAlgCfg(orgGatewayConfig.rootCaKeyAlgorithm as CertKeyAlgorithm);
const gatewayCaSkObj = crypto.createPrivateKey({
key: orgKmsDecryptor({ cipherTextBlob: orgGatewayConfig.encryptedGatewayCaPrivateKey }),
format: "der", format: "der",
type: "pkcs8" type: "pkcs8"
}); });
const caPrivateKey = await crypto.subtle.importKey( const gatewayCaPrivateKey = await crypto.subtle.importKey(
"pkcs8", "pkcs8",
caSkObj.export({ format: "der", type: "pkcs8" }), gatewayCaSkObj.export({ format: "der", type: "pkcs8" }),
caAlg, gatewayCaAlg,
true, true,
["sign"] ["sign"]
); );
@@ -160,9 +309,10 @@ export const gatewayServiceFactory = ({
const certIssuedAt = new Date(); const certIssuedAt = new Date();
// then need to periodically init // then need to periodically init
const certExpireAt = new Date(new Date().setMonth(new Date().getMonth() + 1)); const certExpireAt = new Date(new Date().setMonth(new Date().getMonth() + 1));
const extensions: x509.Extension[] = [ const extensions: x509.Extension[] = [
new x509.BasicConstraintsExtension(false), new x509.BasicConstraintsExtension(false),
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false), await x509.AuthorityKeyIdentifierExtension.create(rootCaCert, false),
await x509.SubjectKeyIdentifierExtension.create(gatewayKeys.publicKey), await x509.SubjectKeyIdentifierExtension.create(gatewayKeys.publicKey),
new x509.CertificatePolicyExtension(["2.5.29.32.0"]), // anyPolicy new x509.CertificatePolicyExtension(["2.5.29.32.0"]), // anyPolicy
new x509.KeyUsagesExtension( new x509.KeyUsagesExtension(
@@ -172,7 +322,8 @@ export const gatewayServiceFactory = ({
), ),
new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage[CertExtendedKeyUsage.SERVER_AUTH]], true), new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage[CertExtendedKeyUsage.SERVER_AUTH]], true),
// san // san
new x509.SubjectAlternativeNameExtension([{ type: "ip", value: relayAddress }], false) // TODO(gateway): change this later
new x509.SubjectAlternativeNameExtension([{ type: "ip", value: "127.0.0.1" }], false)
]; ];
const serialNumber = createSerialNumber(); const serialNumber = createSerialNumber();
@@ -180,10 +331,10 @@ export const gatewayServiceFactory = ({
const gatewayCertificate = await x509.X509CertificateGenerator.create({ const gatewayCertificate = await x509.X509CertificateGenerator.create({
serialNumber, serialNumber,
subject: `CN=${identityId},O=${identityOrg}`, subject: `CN=${identityId},O=${identityOrg}`,
issuer: caCertObj.subject, issuer: rootCaCert.subject,
notBefore: certIssuedAt, notBefore: certIssuedAt,
notAfter: certExpireAt, notAfter: certExpireAt,
signingKey: caPrivateKey, signingKey: gatewayCaPrivateKey,
publicKey: gatewayKeys.publicKey, publicKey: gatewayKeys.publicKey,
signingAlgorithm: alg, signingAlgorithm: alg,
extensions extensions
@@ -191,7 +342,7 @@ export const gatewayServiceFactory = ({
await gatewayDAL.transaction(async (tx) => { await gatewayDAL.transaction(async (tx) => {
await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.OrgGatewayCertExchange(identityOrg)]); await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.OrgGatewayCertExchange(identityOrg)]);
const existingGateway = await gatewayDAL.findOne({ identityId, orgGatewayRootCaId: orgGatewayRootCa.id }); const existingGateway = await gatewayDAL.findOne({ identityId, orgGatewayRootCaId: orgGatewayConfig.id });
if (existingGateway) { if (existingGateway) {
return gatewayDAL.updateById(existingGateway.id, { return gatewayDAL.updateById(existingGateway.id, {
keyAlgorithm: CertKeyAlgorithm.RSA_2048, keyAlgorithm: CertKeyAlgorithm.RSA_2048,
@@ -209,16 +360,18 @@ export const gatewayServiceFactory = ({
serialNumber, serialNumber,
relayAddress: orgKmsEncryptor({ plainText: Buffer.from(relayAddress) }).cipherTextBlob, relayAddress: orgKmsEncryptor({ plainText: Buffer.from(relayAddress) }).cipherTextBlob,
identityId, identityId,
orgGatewayRootCaId: orgGatewayRootCa.id, orgGatewayRootCaId: orgGatewayConfig.id,
name: alphaNumericNanoId(8) name: alphaNumericNanoId(8)
}); });
}); });
const gatewayCertificateChain = `${clientCaCert.toString("pem")}\n${rootCaCert.toString("pem")}`.trim();
return { return {
serialNumber, serialNumber,
privateKey: privateKey.export({ format: "pem", type: "pkcs8" }) as string, privateKey: privateKey.export({ format: "pem", type: "pkcs8" }) as string,
certificate: gatewayCertificate.toString("pem"), certificate: gatewayCertificate.toString("pem"),
certificateChain: caCertObj.toString("pem") certificateChain: gatewayCertificateChain
}; };
}; };
@@ -231,11 +384,11 @@ export const gatewayServiceFactory = ({
orgPermission.orgId orgPermission.orgId
); );
ForbiddenError.from(permission).throwUnlessCan(OrgGatewayPermissionActions.Read, OrgPermissionSubjects.Gateway); ForbiddenError.from(permission).throwUnlessCan(OrgGatewayPermissionActions.Read, OrgPermissionSubjects.Gateway);
const rootCa = await orgGatewayRootCaDAL.findOne({ orgId: orgPermission.orgId }); const orgGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: orgPermission.orgId });
if (!rootCa) return []; if (!orgGatewayConfig) return [];
const gateways = await gatewayDAL.find({ const gateways = await gatewayDAL.find({
orgGatewayRootCaId: rootCa.id orgGatewayRootCaId: orgGatewayConfig.id
}); });
return gateways; return gateways;
}; };
@@ -249,10 +402,10 @@ export const gatewayServiceFactory = ({
orgPermission.orgId orgPermission.orgId
); );
ForbiddenError.from(permission).throwUnlessCan(OrgGatewayPermissionActions.Read, OrgPermissionSubjects.Gateway); ForbiddenError.from(permission).throwUnlessCan(OrgGatewayPermissionActions.Read, OrgPermissionSubjects.Gateway);
const rootCa = await orgGatewayRootCaDAL.findOne({ orgId: orgPermission.orgId }); const orgGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: orgPermission.orgId });
if (!rootCa) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` }); if (!orgGatewayConfig) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
const [gateway] = await gatewayDAL.find({ id, orgGatewayRootCaId: rootCa.id }); const [gateway] = await gatewayDAL.find({ id, orgGatewayRootCaId: orgGatewayConfig.id });
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` }); if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
return gateway; return gateway;
}; };
@@ -266,10 +419,10 @@ export const gatewayServiceFactory = ({
orgPermission.orgId orgPermission.orgId
); );
ForbiddenError.from(permission).throwUnlessCan(OrgGatewayPermissionActions.Delete, OrgPermissionSubjects.Gateway); ForbiddenError.from(permission).throwUnlessCan(OrgGatewayPermissionActions.Delete, OrgPermissionSubjects.Gateway);
const rootCa = await orgGatewayRootCaDAL.findOne({ orgId: orgPermission.orgId }); const orgGatewayConfig = await orgGatewayConfigDAL.findOne({ orgId: orgPermission.orgId });
if (!rootCa) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` }); if (!orgGatewayConfig) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
const [gateway] = await gatewayDAL.delete({ id, orgGatewayRootCaId: rootCa.id }); const [gateway] = await gatewayDAL.delete({ id, orgGatewayRootCaId: orgGatewayConfig.id });
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` }); if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
return gateway; return gateway;
}; };
@@ -0,0 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TOrgGatewayConfigDALFactory = ReturnType<typeof orgGatewayConfigDALFactory>;
export const orgGatewayConfigDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.OrgGatewayConfig);
return orm;
};
@@ -1,10 +0,0 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TOrgGatewayRootCaDALFactory = ReturnType<typeof orgGatewayRootCaDALFactory>;
export const orgGatewayRootCaDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.OrgGatewayRootCa);
return orm;
};
+3 -2
View File
@@ -1,13 +1,14 @@
import { Redis } from "ioredis"; import { Redis } from "ioredis";
import { Redlock, Settings } from "@app/lib/red-lock"; import { Redlock, Settings } from "@app/lib/red-lock";
import { pgAdvisoryLockHashText } from "@app/lib/crypto/hashtext";
export const PgSqlLock = { export const PgSqlLock = {
BootUpMigration: 2023, BootUpMigration: 2023,
SuperAdminInit: 2024, SuperAdminInit: 2024,
KmsRootKeyInit: 2025, KmsRootKeyInit: 2025,
OrgGatewayRootCaInit: (orgId: string) => `org-gateway-root-ca:${orgId}`, OrgGatewayRootCaInit: (orgId: string) => pgAdvisoryLockHashText(`org-gateway-root-ca:${orgId}`),
OrgGatewayCertExchange: (orgId: string) => `org-gateway-cert-exchange:${orgId}` OrgGatewayCertExchange: (orgId: string) => pgAdvisoryLockHashText(`org-gateway-cert-exchange:${orgId}`)
} as const; } as const;
export type TKeyStoreFactory = ReturnType<typeof keyStoreFactory>; export type TKeyStoreFactory = ReturnType<typeof keyStoreFactory>;
+1
View File
@@ -187,6 +187,7 @@ const envSchema = z
/* Gateway----------------------------------------------------------------------------- */ /* Gateway----------------------------------------------------------------------------- */
GATEWAY_INFISICAL_STATIC_IP_ADDRESS: zpStr(z.string().optional()), GATEWAY_INFISICAL_STATIC_IP_ADDRESS: zpStr(z.string().optional()),
GATEWAY_RELAY_ADDRESS: zpStr(z.string().optional()), GATEWAY_RELAY_ADDRESS: zpStr(z.string().optional()),
GATEWAY_RELAY_REALM: zpStr(z.string().optional()),
GATEWAY_RELAY_AUTH_SECRET: zpStr(z.string().optional()), GATEWAY_RELAY_AUTH_SECRET: zpStr(z.string().optional()),
/* ----------------------------------------------------------------------------- */ /* ----------------------------------------------------------------------------- */
+4 -7
View File
@@ -28,9 +28,8 @@ import { dynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secre
import { externalKmsDALFactory } from "@app/ee/services/external-kms/external-kms-dal"; import { externalKmsDALFactory } from "@app/ee/services/external-kms/external-kms-dal";
import { externalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service"; import { externalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal"; import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal";
import { gatewayInstanceConfigDALFactory } from "@app/ee/services/gateway/gateway-instance-config-dal";
import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
import { orgGatewayRootCaDALFactory } from "@app/ee/services/gateway/org-gateway-root-ca-dal"; import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal";
import { groupDALFactory } from "@app/ee/services/group/group-dal"; import { groupDALFactory } from "@app/ee/services/group/group-dal";
import { groupServiceFactory } from "@app/ee/services/group/group-service"; import { groupServiceFactory } from "@app/ee/services/group/group-service";
import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
@@ -397,8 +396,7 @@ export const registerRoutes = async (
const kmipOrgConfigDAL = kmipOrgConfigDALFactory(db); const kmipOrgConfigDAL = kmipOrgConfigDALFactory(db);
const kmipOrgServerCertificateDAL = kmipOrgServerCertificateDALFactory(db); const kmipOrgServerCertificateDAL = kmipOrgServerCertificateDALFactory(db);
const gatewayInstanceConfigDAL = gatewayInstanceConfigDALFactory(db); const orgGatewayConfigDAL = orgGatewayConfigDALFactory(db);
const orgGatewayRootCaDAL = orgGatewayRootCaDALFactory(db);
const gatewayDAL = gatewayDALFactory(db); const gatewayDAL = gatewayDALFactory(db);
const permissionService = permissionServiceFactory({ const permissionService = permissionServiceFactory({
@@ -639,7 +637,6 @@ export const registerRoutes = async (
authService: loginService, authService: loginService,
serverCfgDAL: superAdminDAL, serverCfgDAL: superAdminDAL,
kmsRootConfigDAL, kmsRootConfigDAL,
gatewayInstanceConfigDAL,
orgService, orgService,
keyStore, keyStore,
licenseService, licenseService,
@@ -1467,11 +1464,11 @@ export const registerRoutes = async (
}); });
const gatewayService = gatewayServiceFactory({ const gatewayService = gatewayServiceFactory({
orgGatewayRootCaDAL,
permissionService, permissionService,
gatewayDAL, gatewayDAL,
kmsService, kmsService,
licenseService licenseService,
orgGatewayConfigDAL
}); });
await superAdminService.initServerCfg(); await superAdminService.initServerCfg();
+1 -84
View File
@@ -1,6 +1,6 @@
import { z } from "zod"; import { z } from "zod";
import { GatewayInstanceConfigSchema, OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas"; import { OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
@@ -12,13 +12,6 @@ import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { LoginMethod } from "@app/services/super-admin/super-admin-types"; import { LoginMethod } from "@app/services/super-admin/super-admin-types";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
const SanitizedInstanceGatewaySchema = GatewayInstanceConfigSchema.pick({
isDisabled: true,
infisicalClientCaIssuedAt: true,
infisicalClientCaSerialNumber: true,
caKeyAlgorithm: true
});
export const registerAdminRouter = async (server: FastifyZodProvider) => { export const registerAdminRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
method: "GET", method: "GET",
@@ -254,82 +247,6 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
method: "POST",
url: "/gateway",
config: {
rateLimit: writeLimit
},
schema: {
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async () => {
await server.services.superAdmin.setupInstanceGateway();
return { message: "Gateway setup completed" };
}
});
server.route({
method: "PATCH",
url: "/gateway",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
isDisabled: z.boolean().optional()
}),
response: {
200: z.object({
message: z.string(),
gateway: SanitizedInstanceGatewaySchema
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
const gateway = await server.services.superAdmin.updateInstanceGateway(req.body);
return { message: "Gateway updated Successfully.", gateway };
}
});
server.route({
method: "GET",
url: "/gateway",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: z.object({
gateway: SanitizedInstanceGatewaySchema
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async () => {
const gateway = await server.services.superAdmin.getInstanceGateway();
return { gateway };
}
});
server.route({ server.route({
method: "POST", method: "POST",
url: "/signup", url: "/signup",
@@ -1,9 +1,6 @@
import * as x509 from "@peculiar/x509";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import crypto, { KeyObject } from "crypto";
import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas"; import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
import { TGatewayInstanceConfigDALFactory } from "@app/ee/services/gateway/gateway-instance-config-dal";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
@@ -13,8 +10,6 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TAuthLoginFactory } from "../auth/auth-login-service"; import { TAuthLoginFactory } from "../auth/auth-login-service";
import { AuthMethod } from "../auth/auth-type"; import { AuthMethod } from "../auth/auth-type";
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "../certificate/certificate-types";
import { createSerialNumber, keyAlgorithmToAlgCfg } from "../certificate-authority/certificate-authority-fns";
import { KMS_ROOT_CONFIG_UUID } from "../kms/kms-fns"; import { KMS_ROOT_CONFIG_UUID } from "../kms/kms-fns";
import { TKmsRootConfigDALFactory } from "../kms/kms-root-config-dal"; import { TKmsRootConfigDALFactory } from "../kms/kms-root-config-dal";
import { TKmsServiceFactory } from "../kms/kms-service"; import { TKmsServiceFactory } from "../kms/kms-service";
@@ -36,7 +31,6 @@ type TSuperAdminServiceFactoryDep = {
orgService: Pick<TOrgServiceFactory, "createOrganization">; orgService: Pick<TOrgServiceFactory, "createOrganization">;
keyStore: Pick<TKeyStoreFactory, "getItem" | "setItemWithExpiry" | "deleteItem">; keyStore: Pick<TKeyStoreFactory, "getItem" | "setItemWithExpiry" | "deleteItem">;
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures">; licenseService: Pick<TLicenseServiceFactory, "onPremFeatures">;
gatewayInstanceConfigDAL: Pick<TGatewayInstanceConfigDALFactory, "create" | "findById" | "updateById">;
}; };
export type TSuperAdminServiceFactory = ReturnType<typeof superAdminServiceFactory>; export type TSuperAdminServiceFactory = ReturnType<typeof superAdminServiceFactory>;
@@ -53,7 +47,6 @@ export let getServerCfg: () => Promise<
const ADMIN_CONFIG_KEY = "infisical-admin-cfg"; const ADMIN_CONFIG_KEY = "infisical-admin-cfg";
const ADMIN_CONFIG_KEY_EXP = 60; // 60s const ADMIN_CONFIG_KEY_EXP = 60; // 60s
const ADMIN_CONFIG_DB_UUID = "00000000-0000-0000-0000-000000000000"; const ADMIN_CONFIG_DB_UUID = "00000000-0000-0000-0000-000000000000";
const GATEWAY_INSTANCE_CONFIG_UUID = "00000000-0000-0000-0000-000000000000";
export const superAdminServiceFactory = ({ export const superAdminServiceFactory = ({
serverCfgDAL, serverCfgDAL,
@@ -64,8 +57,7 @@ export const superAdminServiceFactory = ({
keyStore, keyStore,
kmsRootConfigDAL, kmsRootConfigDAL,
kmsService, kmsService,
licenseService, licenseService
gatewayInstanceConfigDAL
}: TSuperAdminServiceFactoryDep) => { }: TSuperAdminServiceFactoryDep) => {
const initServerCfg = async () => { const initServerCfg = async () => {
// TODO(akhilmhdh): bad pattern time less change this later to me itself // TODO(akhilmhdh): bad pattern time less change this later to me itself
@@ -381,149 +373,6 @@ export const superAdminServiceFactory = ({
await kmsService.updateEncryptionStrategy(strategy); await kmsService.updateEncryptionStrategy(strategy);
}; };
const setupInstanceGateway = async () => {
if (!licenseService.onPremFeatures.gateway) {
throw new BadRequestError({
message: "Failed to setup gateway ca due to plan restriction. Upgrade to Infisical's Enterprise plan."
});
}
const existingConfig = await gatewayInstanceConfigDAL.findById(GATEWAY_INSTANCE_CONFIG_UUID);
if (existingConfig) {
throw new BadRequestError({
message: "Gateway has already been configured for the instance"
});
}
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
// generate root CA
const infisicalClientRootCaSerialNumber = createSerialNumber();
const infisicalClientRootCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const infisicalClientCaSkObj = KeyObject.from(infisicalClientRootCaKeys.privateKey);
const infisicalClientCaIssuedAt = new Date();
const infisicalClientCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 25));
const infisicalClientCaCert = await x509.X509CertificateGenerator.createSelfSigned({
name: "CN=Infisical Gateway Client Root CA",
serialNumber: infisicalClientRootCaSerialNumber,
notBefore: infisicalClientCaIssuedAt,
notAfter: infisicalClientCaExpiration,
signingAlgorithm: alg,
keys: infisicalClientRootCaKeys,
extensions: [
// eslint-disable-next-line no-bitwise
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
await x509.SubjectKeyIdentifierExtension.create(infisicalClientRootCaKeys.publicKey)
]
});
const infisicalClientLeafCertserialNumber = createSerialNumber();
const infisicalClientLeafKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const extensions: x509.Extension[] = [
new x509.BasicConstraintsExtension(false),
await x509.AuthorityKeyIdentifierExtension.create(infisicalClientCaCert, false),
await x509.SubjectKeyIdentifierExtension.create(infisicalClientLeafKeys.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 infisicalClientLeafCert = await x509.X509CertificateGenerator.create({
serialNumber: infisicalClientLeafCertserialNumber,
subject: `OU=infisical,CN=infisical`,
issuer: infisicalClientCaCert.issuer,
notBefore: infisicalClientCaIssuedAt,
notAfter: infisicalClientCaExpiration,
signingKey: infisicalClientRootCaKeys.privateKey,
publicKey: infisicalClientLeafKeys.publicKey,
signingAlgorithm: alg,
extensions
});
const infisicalClientLeafCertSkObj = KeyObject.from(infisicalClientLeafKeys.privateKey);
const encryptWithRootKey = kmsService.encryptWithRootKey();
await gatewayInstanceConfigDAL.create({
// @ts-expect-error id is kept as fixed for idempotence and to avoid race condition
id: GATEWAY_INSTANCE_CONFIG_UUID,
isDisabled: false,
caKeyAlgorithm: CertKeyAlgorithm.RSA_2048,
infisicalClientCaIssuedAt,
infisicalClientCaExpiration,
infisicalClientCaSerialNumber: infisicalClientRootCaSerialNumber,
encryptedInfisicalClientCaCertificate: encryptWithRootKey(Buffer.from(infisicalClientCaCert.rawData)),
encryptedInfisicalClientCaPrivateKey: encryptWithRootKey(
infisicalClientCaSkObj.export({
type: "pkcs8",
format: "der"
})
),
infisicalClientCertIssuedAt: infisicalClientCaIssuedAt,
infisicalClientCertExpiration: infisicalClientCaExpiration,
infisicalClientCertSerialNumber: infisicalClientLeafCertserialNumber,
infisicalClientCertKeyAlgorithm: CertKeyAlgorithm.RSA_2048,
encryptedInfisicalClientCertificate: encryptWithRootKey(Buffer.from(infisicalClientLeafCert.rawData)),
encryptedInfisicalClientPrivateKey: encryptWithRootKey(
infisicalClientLeafCertSkObj.export({
type: "pkcs8",
format: "der"
})
)
});
};
const updateInstanceGateway = async ({ isDisabled }: { isDisabled?: boolean }) => {
if (!licenseService.onPremFeatures.gateway) {
throw new BadRequestError({
message: "Failed to update gateway ca due to plan restriction. Upgrade to Infisical's Enterprise plan."
});
}
const existingConfig = await gatewayInstanceConfigDAL.findById(GATEWAY_INSTANCE_CONFIG_UUID);
if (!existingConfig) {
throw new NotFoundError({
message: "Gateway instance config not found"
});
}
const updatedGatewayInstanceConfig = await gatewayInstanceConfigDAL.updateById(GATEWAY_INSTANCE_CONFIG_UUID, {
isDisabled
});
return {
infisicalClientCaSerialNumber: updatedGatewayInstanceConfig.infisicalClientCaSerialNumber,
isDisabled: updatedGatewayInstanceConfig?.isDisabled,
caKeyAlgorithm: CertKeyAlgorithm.RSA_2048,
infisicalClientCaIssuedAt: updatedGatewayInstanceConfig.infisicalClientCaIssuedAt
};
};
const getInstanceGateway = async () => {
if (!licenseService.onPremFeatures.gateway) {
throw new BadRequestError({
message: "Failed to update gateway ca due to plan restriction. Upgrade to Infisical's Enterprise plan."
});
}
const existingConfig = await gatewayInstanceConfigDAL.findById(GATEWAY_INSTANCE_CONFIG_UUID);
if (!existingConfig) {
throw new NotFoundError({
message: "Gateway instance config not found"
});
}
return {
infisicalClientCaSerialNumber: existingConfig.infisicalClientCaSerialNumber,
isDisabled: existingConfig?.isDisabled,
caKeyAlgorithm: CertKeyAlgorithm.RSA_2048,
infisicalClientCaIssuedAt: existingConfig.infisicalClientCaIssuedAt
};
};
return { return {
initServerCfg, initServerCfg,
updateServerCfg, updateServerCfg,
@@ -532,9 +381,6 @@ export const superAdminServiceFactory = ({
deleteUser, deleteUser,
getAdminSlackConfig, getAdminSlackConfig,
updateRootEncryptionStrategy, updateRootEncryptionStrategy,
getConfiguredEncryptionStrategies, getConfiguredEncryptionStrategies
setupInstanceGateway,
updateInstanceGateway,
getInstanceGateway
}; };
}; };