mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 02:26:40 +00:00
feat: added api for admin and org gateway management
This commit is contained in:
Vendored
+2
@@ -95,6 +95,7 @@ import { TUserServiceFactory } from "@app/services/user/user-service";
|
|||||||
import { TUserEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
import { TUserEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
||||||
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
|
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
|
||||||
import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
||||||
|
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||||
|
|
||||||
declare module "@fastify/request-context" {
|
declare module "@fastify/request-context" {
|
||||||
interface RequestContextData {
|
interface RequestContextData {
|
||||||
@@ -228,6 +229,7 @@ declare module "fastify" {
|
|||||||
secretSync: TSecretSyncServiceFactory;
|
secretSync: TSecretSyncServiceFactory;
|
||||||
kmip: TKmipServiceFactory;
|
kmip: TKmipServiceFactory;
|
||||||
kmipOperation: TKmipOperationServiceFactory;
|
kmipOperation: TKmipOperationServiceFactory;
|
||||||
|
gateway: TGatewayServiceFactory;
|
||||||
};
|
};
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
// everywhere else access using service layer
|
// everywhere else access using service layer
|
||||||
|
|||||||
Vendored
+20
@@ -68,6 +68,12 @@ import {
|
|||||||
TExternalKms,
|
TExternalKms,
|
||||||
TExternalKmsInsert,
|
TExternalKmsInsert,
|
||||||
TExternalKmsUpdate,
|
TExternalKmsUpdate,
|
||||||
|
TGatewayInstanceConfig,
|
||||||
|
TGatewayInstanceConfigInsert,
|
||||||
|
TGatewayInstanceConfigUpdate,
|
||||||
|
TGateways,
|
||||||
|
TGatewaysInsert,
|
||||||
|
TGatewaysUpdate,
|
||||||
TGitAppInstallSessions,
|
TGitAppInstallSessions,
|
||||||
TGitAppInstallSessionsInsert,
|
TGitAppInstallSessionsInsert,
|
||||||
TGitAppInstallSessionsUpdate,
|
TGitAppInstallSessionsUpdate,
|
||||||
@@ -179,6 +185,9 @@ import {
|
|||||||
TOrgBots,
|
TOrgBots,
|
||||||
TOrgBotsInsert,
|
TOrgBotsInsert,
|
||||||
TOrgBotsUpdate,
|
TOrgBotsUpdate,
|
||||||
|
TOrgGatewayRootCa,
|
||||||
|
TOrgGatewayRootCaInsert,
|
||||||
|
TOrgGatewayRootCaUpdate,
|
||||||
TOrgMemberships,
|
TOrgMemberships,
|
||||||
TOrgMembershipsInsert,
|
TOrgMembershipsInsert,
|
||||||
TOrgMembershipsUpdate,
|
TOrgMembershipsUpdate,
|
||||||
@@ -930,5 +939,16 @@ declare module "knex/types/tables" {
|
|||||||
TKmipClientCertificatesInsert,
|
TKmipClientCertificatesInsert,
|
||||||
TKmipClientCertificatesUpdate
|
TKmipClientCertificatesUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.Gateway]: KnexOriginal.CompositeTableType<TGateways, TGatewaysInsert, TGatewaysUpdate>;
|
||||||
|
[TableName.GatewayInstanceConfig]: KnexOriginal.CompositeTableType<
|
||||||
|
TGatewayInstanceConfig,
|
||||||
|
TGatewayInstanceConfigInsert,
|
||||||
|
TGatewayInstanceConfigUpdate
|
||||||
|
>;
|
||||||
|
[TableName.OrgGatewayRootCa]: KnexOriginal.CompositeTableType<
|
||||||
|
TOrgGatewayRootCa,
|
||||||
|
TOrgGatewayRootCaInsert,
|
||||||
|
TOrgGatewayRootCaUpdate
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
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.GatewayInstanceConfig))) {
|
||||||
|
await knex.schema.createTable(TableName.GatewayInstanceConfig, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("caKeyAlgorithm").notNullable();
|
||||||
|
t.boolean("isDisabled").defaultTo(false);
|
||||||
|
|
||||||
|
t.string("infisicalClientCaSerialNumber").notNullable();
|
||||||
|
t.datetime("infisicalClientCaIssuedAt").notNullable();
|
||||||
|
t.datetime("infisicalClientCaExpiration").notNullable();
|
||||||
|
t.binary("encryptedInfisicalClientCaCertificate").notNullable();
|
||||||
|
t.binary("encryptedInfisicalClientCaPrivateKey").notNullable();
|
||||||
|
|
||||||
|
t.string("infisicalClientCertSerialNumber").notNullable();
|
||||||
|
t.string("infisicalClientCertKeyAlgorithm").notNullable();
|
||||||
|
t.datetime("infisicalClientCertIssuedAt").notNullable();
|
||||||
|
t.datetime("infisicalClientCertExpiration").notNullable();
|
||||||
|
t.binary("encryptedInfisicalClientCertificate").notNullable();
|
||||||
|
t.binary("encryptedInfisicalClientPrivateKey").notNullable();
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.GatewayInstanceConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.OrgGatewayRootCa))) {
|
||||||
|
await knex.schema.createTable(TableName.OrgGatewayRootCa, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("caKeyAlgorithm").notNullable();
|
||||||
|
t.string("caSerialNumber").notNullable();
|
||||||
|
t.datetime("caIssuedAt").notNullable();
|
||||||
|
t.datetime("caExpiration").notNullable();
|
||||||
|
t.binary("encryptedCaCertificate").notNullable();
|
||||||
|
t.binary("encryptedCaPrivateKey").notNullable();
|
||||||
|
|
||||||
|
t.uuid("orgId").notNullable();
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.OrgGatewayRootCa);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.Gateway))) {
|
||||||
|
await knex.schema.createTable(TableName.Gateway, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
|
||||||
|
t.string("name").notNullable();
|
||||||
|
t.string("serialNumber").notNullable();
|
||||||
|
t.string("keyAlgorithm").notNullable();
|
||||||
|
t.datetime("issuedAt").notNullable();
|
||||||
|
t.datetime("expiration").notNullable();
|
||||||
|
|
||||||
|
t.binary("relayAddress").notNullable();
|
||||||
|
|
||||||
|
t.uuid("orgGatewayRootCaId").notNullable();
|
||||||
|
t.foreign("orgGatewayRootCaId").references("id").inTable(TableName.OrgGatewayRootCa).onDelete("CASCADE");
|
||||||
|
|
||||||
|
t.uuid("identityId").notNullable();
|
||||||
|
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
|
||||||
|
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.Gateway);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 dropOnUpdateTrigger(knex, TableName.Gateway);
|
||||||
|
|
||||||
|
await knex.schema.dropTableIfExists(TableName.OrgGatewayRootCa);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.OrgGatewayRootCa);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// 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>>;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// 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 GatewaysSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
name: z.string(),
|
||||||
|
serialNumber: z.string(),
|
||||||
|
keyAlgorithm: z.string(),
|
||||||
|
issuedAt: z.date(),
|
||||||
|
expiration: z.date(),
|
||||||
|
relayAddress: zodBuffer,
|
||||||
|
orgGatewayRootCaId: z.string().uuid(),
|
||||||
|
identityId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TGateways = z.infer<typeof GatewaysSchema>;
|
||||||
|
export type TGatewaysInsert = Omit<z.input<typeof GatewaysSchema>, TImmutableDBKeys>;
|
||||||
|
export type TGatewaysUpdate = Partial<Omit<z.input<typeof GatewaysSchema>, TImmutableDBKeys>>;
|
||||||
@@ -20,6 +20,8 @@ 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 "./git-app-install-sessions";
|
export * from "./git-app-install-sessions";
|
||||||
export * from "./git-app-org";
|
export * from "./git-app-org";
|
||||||
export * from "./group-project-membership-roles";
|
export * from "./group-project-membership-roles";
|
||||||
@@ -57,6 +59,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-memberships";
|
export * from "./org-memberships";
|
||||||
export * from "./org-roles";
|
export * from "./org-roles";
|
||||||
export * from "./organizations";
|
export * from "./organizations";
|
||||||
|
|||||||
@@ -113,6 +113,10 @@ export enum TableName {
|
|||||||
SecretApprovalRequestSecretTagV2 = "secret_approval_request_secret_tags_v2",
|
SecretApprovalRequestSecretTagV2 = "secret_approval_request_secret_tags_v2",
|
||||||
SnapshotSecretV2 = "secret_snapshot_secrets_v2",
|
SnapshotSecretV2 = "secret_snapshot_secrets_v2",
|
||||||
ProjectSplitBackfillIds = "project_split_backfill_ids",
|
ProjectSplitBackfillIds = "project_split_backfill_ids",
|
||||||
|
// Gateway
|
||||||
|
GatewayInstanceConfig = "gateway_instance_config",
|
||||||
|
OrgGatewayRootCa = "org_gateway_root_ca",
|
||||||
|
Gateway = "gateways",
|
||||||
// junction tables with tags
|
// junction tables with tags
|
||||||
SecretV2JnTag = "secret_v2_tag_junction",
|
SecretV2JnTag = "secret_v2_tag_junction",
|
||||||
JnSecretTag = "secret_tag_junction",
|
JnSecretTag = "secret_tag_junction",
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// 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>>;
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { GatewaysSchema } from "@app/db/schemas";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
const SanitizedGatewaySchema = GatewaysSchema.pick({
|
||||||
|
id: true,
|
||||||
|
identityId: true,
|
||||||
|
name: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
issuedAt: true,
|
||||||
|
serialNumber: true
|
||||||
|
});
|
||||||
|
|
||||||
|
export const registerGatewayRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/register-identity",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
turnServerUsername: z.string(),
|
||||||
|
turnServerPassword: z.string(),
|
||||||
|
turnServerAddress: z.string(),
|
||||||
|
infisicalStaticIp: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const relayDetails = await server.services.gateway.getGatewayRelayDetails(
|
||||||
|
req.permission.id,
|
||||||
|
req.permission.orgId
|
||||||
|
);
|
||||||
|
return relayDetails;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/exchange-cert",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
relayAddress: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
serialNumber: z.string(),
|
||||||
|
privateKey: z.string(),
|
||||||
|
certificate: z.string(),
|
||||||
|
certificateChain: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const gatewayCertificates = await server.services.gateway.exchangeAllocatedRelayAddress({
|
||||||
|
identityOrg: req.permission.orgId,
|
||||||
|
identityId: req.permission.id,
|
||||||
|
relayAddress: req.body.relayAddress
|
||||||
|
});
|
||||||
|
return gatewayCertificates;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
gateways: SanitizedGatewaySchema.extend({
|
||||||
|
identity: z.object({
|
||||||
|
name: z.string(),
|
||||||
|
id: z.string()
|
||||||
|
})
|
||||||
|
}).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN, AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const gateways = await server.services.gateway.listGateways({
|
||||||
|
orgPermission: req.permission
|
||||||
|
});
|
||||||
|
return { gateways };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
gateway: SanitizedGatewaySchema.extend({
|
||||||
|
identity: z.object({
|
||||||
|
name: z.string(),
|
||||||
|
id: z.string()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN, AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const gateway = await server.services.gateway.getGatewayById({
|
||||||
|
orgPermission: req.permission,
|
||||||
|
id: req.params.id
|
||||||
|
});
|
||||||
|
return { gateway };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
gateway: SanitizedGatewaySchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN, AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const gateway = await server.services.gateway.deleteGatewayById({
|
||||||
|
orgPermission: req.permission,
|
||||||
|
id: req.params.id
|
||||||
|
});
|
||||||
|
return { gateway };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -7,6 +7,7 @@ import { registerCaCrlRouter } from "./certificate-authority-crl-router";
|
|||||||
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
|
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
|
||||||
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
|
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
|
||||||
import { registerExternalKmsRouter } from "./external-kms-router";
|
import { registerExternalKmsRouter } from "./external-kms-router";
|
||||||
|
import { registerGatewayRouter } from "./gateway-router";
|
||||||
import { registerGroupRouter } from "./group-router";
|
import { registerGroupRouter } from "./group-router";
|
||||||
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
|
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
|
||||||
import { registerKmipRouter } from "./kmip-router";
|
import { registerKmipRouter } from "./kmip-router";
|
||||||
@@ -67,6 +68,8 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
{ prefix: "/dynamic-secrets" }
|
{ prefix: "/dynamic-secrets" }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await server.register(registerGatewayRouter, { prefix: "/gateways" });
|
||||||
|
|
||||||
await server.register(
|
await server.register(
|
||||||
async (pkiRouter) => {
|
async (pkiRouter) => {
|
||||||
await pkiRouter.register(registerCaCrlRouter, { prefix: "/crl" });
|
await pkiRouter.register(registerCaCrlRouter, { prefix: "/crl" });
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName, TGateways } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TGatewayDALFactory = ReturnType<typeof gatewayDALFactory>;
|
||||||
|
|
||||||
|
export const gatewayDALFactory = (db: TDbClient) => {
|
||||||
|
const orm = ormify(db, TableName.Gateway);
|
||||||
|
|
||||||
|
const find = async (filter: TFindFilter<TGateways>, { offset, limit, sort, tx }: TFindOpt<TGateways> = {}) => {
|
||||||
|
try {
|
||||||
|
const query = (tx || db)(TableName.Gateway)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
.where(buildFindFilter(filter))
|
||||||
|
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Gateway}.identityId`)
|
||||||
|
.select(selectAllTableCols(TableName.Gateway))
|
||||||
|
.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) => ({ ...el, identity: { id: el.identityId, name: el.identityName } }));
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: `${TableName.Gateway}: Find` });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...orm, find };
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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;
|
||||||
|
};
|
||||||
@@ -0,0 +1,284 @@
|
|||||||
|
import crypto from "node:crypto";
|
||||||
|
|
||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import * as x509 from "@peculiar/x509";
|
||||||
|
|
||||||
|
import { PgSqlLock } from "@app/keystore/keystore";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
import { getTurnCredentials } from "@app/lib/turn/credentials";
|
||||||
|
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 { OrgGatewayPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import { TGatewayDALFactory } from "./gateway-dal";
|
||||||
|
import { TExchangeAllocatedRelayAddressDTO, TGetGatewayByIdDTO, TListGatewaysDTO } from "./gateway-types";
|
||||||
|
import { TOrgGatewayRootCaDALFactory } from "./org-gateway-root-ca-dal";
|
||||||
|
|
||||||
|
type TGatewayServiceFactoryDep = {
|
||||||
|
gatewayDAL: TGatewayDALFactory;
|
||||||
|
orgGatewayRootCaDAL: Pick<TOrgGatewayRootCaDALFactory, "findOne" | "create" | "transaction">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures" | "getPlan">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGatewayServiceFactory = ReturnType<typeof gatewayServiceFactory>;
|
||||||
|
|
||||||
|
// TODO(gateway): missing permission check
|
||||||
|
export const gatewayServiceFactory = ({
|
||||||
|
gatewayDAL,
|
||||||
|
licenseService,
|
||||||
|
orgGatewayRootCaDAL,
|
||||||
|
kmsService,
|
||||||
|
permissionService
|
||||||
|
}: TGatewayServiceFactoryDep) => {
|
||||||
|
const $validateOrgAccessToGateway = async (orgId: string) => {
|
||||||
|
if (!licenseService.onPremFeatures.gateway) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Gateway handshake failed due to instance plan restrictions. Please upgrade your instance to Infisical's Enterprise plan."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const orgLicensePlan = await licenseService.getPlan(orgId);
|
||||||
|
if (!orgLicensePlan.gateway) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Gateway handshake failed due to organization plan restrictions. Please upgrade your instance to Infisical's Enterprise plan."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGatewayRelayDetails = async (actorId: string, actorOrgId: string) => {
|
||||||
|
const envCfg = getConfig();
|
||||||
|
await $validateOrgAccessToGateway(actorOrgId);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!envCfg.GATEWAY_RELAY_AUTH_SECRET ||
|
||||||
|
!envCfg.GATEWAY_RELAY_ADDRESS ||
|
||||||
|
!envCfg.GATEWAY_INFISICAL_STATIC_IP_ADDRESS
|
||||||
|
) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Gateway handshake failed due to missing instance config."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// TODO(gateway): keep it in 30mins redis after encryption to avoid multiple credentials spinning up
|
||||||
|
const { username: turnServerUsername, password: turnServerPassword } = getTurnCredentials(
|
||||||
|
actorId,
|
||||||
|
envCfg.GATEWAY_RELAY_AUTH_SECRET
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
turnServerUsername,
|
||||||
|
turnServerPassword,
|
||||||
|
turnServerAddress: envCfg.GATEWAY_RELAY_ADDRESS,
|
||||||
|
infisicalStaticIp: envCfg.GATEWAY_INFISICAL_STATIC_IP_ADDRESS
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const exchangeAllocatedRelayAddress = async ({
|
||||||
|
identityId,
|
||||||
|
identityOrg,
|
||||||
|
relayAddress
|
||||||
|
}: TExchangeAllocatedRelayAddressDTO) => {
|
||||||
|
const { encryptor: orgKmsEncryptor, decryptor: orgKmsDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: identityOrg
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgGatewayRootCa = await orgGatewayRootCaDAL.transaction(async (tx) => {
|
||||||
|
await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.OrgGatewayRootCaInit(identityOrg)]);
|
||||||
|
const existingGateway = await orgGatewayRootCaDAL.findOne({ orgId: identityOrg });
|
||||||
|
if (existingGateway) return existingGateway;
|
||||||
|
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
|
||||||
|
// generate root CA
|
||||||
|
const orgGatewayRootCaSerialNumber = createSerialNumber();
|
||||||
|
const orgGatewayRootCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||||
|
const orgGatewayCaSkObj = crypto.KeyObject.from(orgGatewayRootCaKeys.privateKey);
|
||||||
|
const orgGatewayCaIssuedAt = new Date();
|
||||||
|
const orgGatewayCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 25));
|
||||||
|
|
||||||
|
const orgGatewayCaCert = await x509.X509CertificateGenerator.createSelfSigned({
|
||||||
|
name: `CN=Org Gateway Root CA,O=${identityOrg}`,
|
||||||
|
serialNumber: orgGatewayRootCaSerialNumber,
|
||||||
|
notBefore: orgGatewayCaIssuedAt,
|
||||||
|
notAfter: orgGatewayCaExpiration,
|
||||||
|
signingAlgorithm: alg,
|
||||||
|
keys: orgGatewayRootCaKeys,
|
||||||
|
extensions: [
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
|
||||||
|
await x509.SubjectKeyIdentifierExtension.create(orgGatewayRootCaKeys.publicKey)
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return orgGatewayRootCaDAL.create({
|
||||||
|
orgId: identityOrg,
|
||||||
|
caIssuedAt: orgGatewayCaIssuedAt,
|
||||||
|
caExpiration: orgGatewayCaExpiration,
|
||||||
|
caSerialNumber: orgGatewayRootCaSerialNumber,
|
||||||
|
encryptedCaCertificate: orgKmsEncryptor({ plainText: Buffer.from(orgGatewayCaCert.rawData) }).cipherTextBlob,
|
||||||
|
encryptedCaPrivateKey: orgKmsEncryptor({
|
||||||
|
plainText: orgGatewayCaSkObj.export({
|
||||||
|
type: "pkcs8",
|
||||||
|
format: "der"
|
||||||
|
})
|
||||||
|
}).cipherTextBlob,
|
||||||
|
caKeyAlgorithm: CertKeyAlgorithm.RSA_2048
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const caCertObj = new x509.X509Certificate(
|
||||||
|
orgKmsDecryptor({
|
||||||
|
cipherTextBlob: orgGatewayRootCa.encryptedCaCertificate
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const caAlg = keyAlgorithmToAlgCfg(orgGatewayRootCa.caKeyAlgorithm as CertKeyAlgorithm);
|
||||||
|
const caSkObj = crypto.createPrivateKey({
|
||||||
|
key: orgKmsDecryptor({ cipherTextBlob: orgGatewayRootCa.encryptedCaPrivateKey }),
|
||||||
|
format: "der",
|
||||||
|
type: "pkcs8"
|
||||||
|
});
|
||||||
|
const caPrivateKey = await crypto.subtle.importKey(
|
||||||
|
"pkcs8",
|
||||||
|
caSkObj.export({ format: "der", type: "pkcs8" }),
|
||||||
|
caAlg,
|
||||||
|
true,
|
||||||
|
["sign"]
|
||||||
|
);
|
||||||
|
|
||||||
|
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
|
||||||
|
const gatewayKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||||
|
const certIssuedAt = new Date();
|
||||||
|
// then need to periodically init
|
||||||
|
const certExpireAt = new Date(new Date().setMonth(new Date().getMonth() + 1));
|
||||||
|
const extensions: x509.Extension[] = [
|
||||||
|
new x509.BasicConstraintsExtension(false),
|
||||||
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
||||||
|
await x509.SubjectKeyIdentifierExtension.create(gatewayKeys.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),
|
||||||
|
// san
|
||||||
|
new x509.SubjectAlternativeNameExtension([{ type: "ip", value: relayAddress }], false)
|
||||||
|
];
|
||||||
|
|
||||||
|
const serialNumber = createSerialNumber();
|
||||||
|
const privateKey = crypto.KeyObject.from(gatewayKeys.privateKey);
|
||||||
|
const gatewayCertificate = await x509.X509CertificateGenerator.create({
|
||||||
|
serialNumber,
|
||||||
|
subject: `CN=${identityId},O=${identityOrg}`,
|
||||||
|
issuer: caCertObj.subject,
|
||||||
|
notBefore: certIssuedAt,
|
||||||
|
notAfter: certExpireAt,
|
||||||
|
signingKey: caPrivateKey,
|
||||||
|
publicKey: gatewayKeys.publicKey,
|
||||||
|
signingAlgorithm: alg,
|
||||||
|
extensions
|
||||||
|
});
|
||||||
|
|
||||||
|
await gatewayDAL.transaction(async (tx) => {
|
||||||
|
await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.OrgGatewayCertExchange(identityOrg)]);
|
||||||
|
const existingGateway = await gatewayDAL.findOne({ identityId, orgGatewayRootCaId: orgGatewayRootCa.id });
|
||||||
|
if (existingGateway) {
|
||||||
|
return gatewayDAL.updateById(existingGateway.id, {
|
||||||
|
keyAlgorithm: CertKeyAlgorithm.RSA_2048,
|
||||||
|
issuedAt: certIssuedAt,
|
||||||
|
expiration: certExpireAt,
|
||||||
|
serialNumber,
|
||||||
|
relayAddress: orgKmsEncryptor({ plainText: Buffer.from(relayAddress) }).cipherTextBlob
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return gatewayDAL.create({
|
||||||
|
keyAlgorithm: CertKeyAlgorithm.RSA_2048,
|
||||||
|
issuedAt: certIssuedAt,
|
||||||
|
expiration: certExpireAt,
|
||||||
|
serialNumber,
|
||||||
|
relayAddress: orgKmsEncryptor({ plainText: Buffer.from(relayAddress) }).cipherTextBlob,
|
||||||
|
identityId,
|
||||||
|
orgGatewayRootCaId: orgGatewayRootCa.id,
|
||||||
|
name: alphaNumericNanoId(8)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
serialNumber,
|
||||||
|
privateKey: privateKey.export({ format: "pem", type: "pkcs8" }) as string,
|
||||||
|
certificate: gatewayCertificate.toString("pem"),
|
||||||
|
certificateChain: caCertObj.toString("pem")
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const listGateways = async ({ orgPermission }: TListGatewaysDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
orgPermission.type,
|
||||||
|
orgPermission.id,
|
||||||
|
orgPermission.orgId,
|
||||||
|
orgPermission.authMethod,
|
||||||
|
orgPermission.orgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgGatewayPermissionActions.Create, OrgPermissionSubjects.Gateway);
|
||||||
|
const rootCa = await orgGatewayRootCaDAL.findOne({ orgId: orgPermission.orgId });
|
||||||
|
if (!rootCa) return [];
|
||||||
|
|
||||||
|
const gateways = await gatewayDAL.find({
|
||||||
|
orgGatewayRootCaId: rootCa.id
|
||||||
|
});
|
||||||
|
return gateways;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGatewayById = async ({ orgPermission, id }: TGetGatewayByIdDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
orgPermission.type,
|
||||||
|
orgPermission.id,
|
||||||
|
orgPermission.orgId,
|
||||||
|
orgPermission.authMethod,
|
||||||
|
orgPermission.orgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgGatewayPermissionActions.Create, OrgPermissionSubjects.Gateway);
|
||||||
|
const rootCa = await orgGatewayRootCaDAL.findOne({ orgId: orgPermission.orgId });
|
||||||
|
if (!rootCa) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
|
||||||
|
|
||||||
|
const [gateway] = await gatewayDAL.find({ id, orgGatewayRootCaId: rootCa.id });
|
||||||
|
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
|
||||||
|
return gateway;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteGatewayById = async ({ orgPermission, id }: TGetGatewayByIdDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
orgPermission.type,
|
||||||
|
orgPermission.id,
|
||||||
|
orgPermission.orgId,
|
||||||
|
orgPermission.authMethod,
|
||||||
|
orgPermission.orgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgGatewayPermissionActions.Create, OrgPermissionSubjects.Gateway);
|
||||||
|
const rootCa = await orgGatewayRootCaDAL.findOne({ orgId: orgPermission.orgId });
|
||||||
|
if (!rootCa) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
|
||||||
|
|
||||||
|
const [gateway] = await gatewayDAL.delete({ id, orgGatewayRootCaId: rootCa.id });
|
||||||
|
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
|
||||||
|
return gateway;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getGatewayRelayDetails,
|
||||||
|
exchangeAllocatedRelayAddress,
|
||||||
|
listGateways,
|
||||||
|
getGatewayById,
|
||||||
|
deleteGatewayById
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
|
export type TExchangeAllocatedRelayAddressDTO = {
|
||||||
|
identityId: string;
|
||||||
|
identityOrg: string;
|
||||||
|
relayAddress: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TListGatewaysDTO = {
|
||||||
|
orgPermission: OrgServiceActor;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetGatewayByIdDTO = {
|
||||||
|
id: string;
|
||||||
|
orgPermission: OrgServiceActor;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDeleteGatewayByIdDTO = {
|
||||||
|
id: string;
|
||||||
|
orgPermission: OrgServiceActor;
|
||||||
|
};
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
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;
|
||||||
|
};
|
||||||
@@ -51,7 +51,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
pkiEst: false,
|
pkiEst: false,
|
||||||
enforceMfa: false,
|
enforceMfa: false,
|
||||||
projectTemplates: false,
|
projectTemplates: false,
|
||||||
kmip: false
|
kmip: false,
|
||||||
|
gateway: true
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ export type TFeatureSet = {
|
|||||||
enforceMfa: boolean;
|
enforceMfa: boolean;
|
||||||
projectTemplates: false;
|
projectTemplates: false;
|
||||||
kmip: false;
|
kmip: false;
|
||||||
|
gateway: true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TOrgPlansTableDTO = {
|
export type TOrgPlansTableDTO = {
|
||||||
|
|||||||
@@ -32,6 +32,14 @@ export enum OrgPermissionAdminConsoleAction {
|
|||||||
AccessAllProjects = "access-all-projects"
|
AccessAllProjects = "access-all-projects"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum OrgGatewayPermissionActions {
|
||||||
|
// is there a better word for this. This mean can an identity be a gateway
|
||||||
|
Create = "create",
|
||||||
|
Read = "read",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete"
|
||||||
|
}
|
||||||
|
|
||||||
export enum OrgPermissionSubjects {
|
export enum OrgPermissionSubjects {
|
||||||
Workspace = "workspace",
|
Workspace = "workspace",
|
||||||
Role = "role",
|
Role = "role",
|
||||||
@@ -50,7 +58,8 @@ export enum OrgPermissionSubjects {
|
|||||||
AuditLogs = "audit-logs",
|
AuditLogs = "audit-logs",
|
||||||
ProjectTemplates = "project-templates",
|
ProjectTemplates = "project-templates",
|
||||||
AppConnections = "app-connections",
|
AppConnections = "app-connections",
|
||||||
Kmip = "kmip"
|
Kmip = "kmip",
|
||||||
|
Gateway = "gateway"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AppConnectionSubjectFields = {
|
export type AppConnectionSubjectFields = {
|
||||||
@@ -73,6 +82,7 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
||||||
|
| [OrgGatewayPermissionActions, OrgPermissionSubjects.Gateway]
|
||||||
| [
|
| [
|
||||||
OrgPermissionAppConnectionActions,
|
OrgPermissionAppConnectionActions,
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ import { Redis } from "ioredis";
|
|||||||
|
|
||||||
import { Redlock, Settings } from "@app/lib/red-lock";
|
import { Redlock, Settings } from "@app/lib/red-lock";
|
||||||
|
|
||||||
export enum PgSqlLock {
|
export const PgSqlLock = {
|
||||||
BootUpMigration = 2023,
|
BootUpMigration: 2023,
|
||||||
SuperAdminInit = 2024,
|
SuperAdminInit: 2024,
|
||||||
KmsRootKeyInit = 2025
|
KmsRootKeyInit: 2025,
|
||||||
}
|
OrgGatewayRootCaInit: (orgId: string) => `org-gateway-root-ca:${orgId}`,
|
||||||
|
OrgGatewayCertExchange: (orgId: string) => `org-gateway-cert-exchange:${orgId}`
|
||||||
|
} as const;
|
||||||
|
|
||||||
export type TKeyStoreFactory = ReturnType<typeof keyStoreFactory>;
|
export type TKeyStoreFactory = ReturnType<typeof keyStoreFactory>;
|
||||||
|
|
||||||
|
|||||||
@@ -184,6 +184,13 @@ const envSchema = z
|
|||||||
USE_PG_QUEUE: zodStrBool.default("false"),
|
USE_PG_QUEUE: zodStrBool.default("false"),
|
||||||
SHOULD_INIT_PG_QUEUE: zodStrBool.default("false"),
|
SHOULD_INIT_PG_QUEUE: zodStrBool.default("false"),
|
||||||
|
|
||||||
|
/* Gateway----------------------------------------------------------------------------- */
|
||||||
|
GATEWAY_INFISICAL_STATIC_IP_ADDRESS: zpStr(z.string().optional()),
|
||||||
|
GATEWAY_RELAY_ADDRESS: zpStr(z.string().optional()),
|
||||||
|
GATEWAY_RELAY_AUTH_SECRET: zpStr(z.string().optional()),
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/* App Connections ----------------------------------------------------------------------------- */
|
/* App Connections ----------------------------------------------------------------------------- */
|
||||||
|
|
||||||
// aws
|
// aws
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// used for postgres lock
|
||||||
|
// this is something postgres does under the hood
|
||||||
|
// convert any string to a unique number
|
||||||
|
export const hashtext = (text: string) => {
|
||||||
|
// Convert text to UTF8 bytes array for consistent behavior with PostgreSQL
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const bytes = encoder.encode(text);
|
||||||
|
|
||||||
|
// Implementation of hash_any
|
||||||
|
let result = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < bytes.length; i += 1) {
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
result = ((result << 5) + result) ^ bytes[i];
|
||||||
|
// Keep within 32-bit integer range
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
result >>>= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to signed 32-bit integer like PostgreSQL
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
return result | 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pgAdvisoryLockHashText = (text: string) => {
|
||||||
|
const hash = hashtext(text);
|
||||||
|
// Ensure positive value within PostgreSQL integer range
|
||||||
|
return Math.abs(hash) % 2 ** 31;
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import crypto from "node:crypto";
|
||||||
|
|
||||||
|
const TURN_TOKEN_TTL = 60 * 60 * 1000; // 24 hours in milliseconds
|
||||||
|
export const getTurnCredentials = (id: string, authSecret: string, ttl = TURN_TOKEN_TTL) => {
|
||||||
|
const timestamp = Math.floor((Date.now() + ttl) / 1000);
|
||||||
|
const username = `${timestamp}:${id}`;
|
||||||
|
|
||||||
|
const hmac = crypto.createHmac("sha1", authSecret);
|
||||||
|
hmac.update(username);
|
||||||
|
const password = hmac.digest("base64");
|
||||||
|
|
||||||
|
return {
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -27,6 +27,10 @@ import { dynamicSecretLeaseQueueServiceFactory } from "@app/ee/services/dynamic-
|
|||||||
import { dynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
|
import { dynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
|
||||||
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 { gatewayInstanceConfigDALFactory } from "@app/ee/services/gateway/gateway-instance-config-dal";
|
||||||
|
import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||||
|
import { orgGatewayRootCaDALFactory } from "@app/ee/services/gateway/org-gateway-root-ca-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";
|
||||||
@@ -393,6 +397,10 @@ 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 orgGatewayRootCaDAL = orgGatewayRootCaDALFactory(db);
|
||||||
|
const gatewayDAL = gatewayDALFactory(db);
|
||||||
|
|
||||||
const permissionService = permissionServiceFactory({
|
const permissionService = permissionServiceFactory({
|
||||||
permissionDAL,
|
permissionDAL,
|
||||||
orgRoleDAL,
|
orgRoleDAL,
|
||||||
@@ -631,6 +639,7 @@ export const registerRoutes = async (
|
|||||||
authService: loginService,
|
authService: loginService,
|
||||||
serverCfgDAL: superAdminDAL,
|
serverCfgDAL: superAdminDAL,
|
||||||
kmsRootConfigDAL,
|
kmsRootConfigDAL,
|
||||||
|
gatewayInstanceConfigDAL,
|
||||||
orgService,
|
orgService,
|
||||||
keyStore,
|
keyStore,
|
||||||
licenseService,
|
licenseService,
|
||||||
@@ -1457,6 +1466,14 @@ export const registerRoutes = async (
|
|||||||
permissionService
|
permissionService
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const gatewayService = gatewayServiceFactory({
|
||||||
|
orgGatewayRootCaDAL,
|
||||||
|
permissionService,
|
||||||
|
gatewayDAL,
|
||||||
|
kmsService,
|
||||||
|
licenseService
|
||||||
|
});
|
||||||
|
|
||||||
await superAdminService.initServerCfg();
|
await superAdminService.initServerCfg();
|
||||||
|
|
||||||
// setup the communication with license key server
|
// setup the communication with license key server
|
||||||
@@ -1557,7 +1574,8 @@ export const registerRoutes = async (
|
|||||||
appConnection: appConnectionService,
|
appConnection: appConnectionService,
|
||||||
secretSync: secretSyncService,
|
secretSync: secretSyncService,
|
||||||
kmip: kmipService,
|
kmip: kmipService,
|
||||||
kmipOperation: kmipOperationService
|
kmipOperation: kmipOperationService,
|
||||||
|
gateway: gatewayService
|
||||||
});
|
});
|
||||||
|
|
||||||
const cronJobs: CronJob[] = [];
|
const cronJobs: CronJob[] = [];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas";
|
import { GatewayInstanceConfigSchema, 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,6 +12,13 @@ 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",
|
||||||
@@ -247,6 +254,82 @@ 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,6 +1,9 @@
|
|||||||
|
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";
|
||||||
@@ -10,6 +13,8 @@ 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";
|
||||||
@@ -31,6 +36,7 @@ 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>;
|
||||||
@@ -47,6 +53,7 @@ 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,
|
||||||
@@ -57,7 +64,8 @@ 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
|
||||||
@@ -373,6 +381,149 @@ 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,
|
||||||
@@ -381,6 +532,9 @@ export const superAdminServiceFactory = ({
|
|||||||
deleteUser,
|
deleteUser,
|
||||||
getAdminSlackConfig,
|
getAdminSlackConfig,
|
||||||
updateRootEncryptionStrategy,
|
updateRootEncryptionStrategy,
|
||||||
getConfiguredEncryptionStrategies
|
getConfiguredEncryptionStrategies,
|
||||||
|
setupInstanceGateway,
|
||||||
|
updateInstanceGateway,
|
||||||
|
getInstanceGateway
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user