mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 07:28:50 +00:00
misc: initial instance KMIP PKI setup
This commit is contained in:
Vendored
+16
@@ -146,6 +146,12 @@ import {
|
|||||||
TKmipClients,
|
TKmipClients,
|
||||||
TKmipClientsInsert,
|
TKmipClientsInsert,
|
||||||
TKmipClientsUpdate,
|
TKmipClientsUpdate,
|
||||||
|
TKmipInstanceConfigs,
|
||||||
|
TKmipInstanceConfigsInsert,
|
||||||
|
TKmipInstanceConfigsUpdate,
|
||||||
|
TKmipInstanceServerCertificates,
|
||||||
|
TKmipInstanceServerCertificatesInsert,
|
||||||
|
TKmipInstanceServerCertificatesUpdate,
|
||||||
TKmsKeys,
|
TKmsKeys,
|
||||||
TKmsKeysInsert,
|
TKmsKeysInsert,
|
||||||
TKmsKeysUpdate,
|
TKmsKeysUpdate,
|
||||||
@@ -906,5 +912,15 @@ declare module "knex/types/tables" {
|
|||||||
>;
|
>;
|
||||||
[TableName.SecretSync]: KnexOriginal.CompositeTableType<TSecretSyncs, TSecretSyncsInsert, TSecretSyncsUpdate>;
|
[TableName.SecretSync]: KnexOriginal.CompositeTableType<TSecretSyncs, TSecretSyncsInsert, TSecretSyncsUpdate>;
|
||||||
[TableName.KmipClient]: KnexOriginal.CompositeTableType<TKmipClients, TKmipClientsInsert, TKmipClientsUpdate>;
|
[TableName.KmipClient]: KnexOriginal.CompositeTableType<TKmipClients, TKmipClientsInsert, TKmipClientsUpdate>;
|
||||||
|
[TableName.KmipInstanceConfig]: KnexOriginal.CompositeTableType<
|
||||||
|
TKmipInstanceConfigs,
|
||||||
|
TKmipInstanceConfigsInsert,
|
||||||
|
TKmipInstanceConfigsUpdate
|
||||||
|
>;
|
||||||
|
[TableName.KmipInstanceServerCertificates]: KnexOriginal.CompositeTableType<
|
||||||
|
TKmipInstanceServerCertificates,
|
||||||
|
TKmipInstanceServerCertificatesInsert,
|
||||||
|
TKmipInstanceServerCertificatesUpdate
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TableName } from "../schemas";
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
export async function up(knex: Knex): Promise<void> {
|
||||||
const hasKmipClientTable = await knex.schema.hasTable(TableName.KmipClient);
|
const hasKmipClientTable = await knex.schema.hasTable(TableName.KmipClient);
|
||||||
@@ -14,6 +15,55 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasKmipInstanceConfigTable = await knex.schema.hasTable(TableName.KmipInstanceConfig);
|
||||||
|
if (!hasKmipInstanceConfigTable) {
|
||||||
|
await knex.schema.createTable(TableName.KmipInstanceConfig, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
|
||||||
|
t.string("caKeyAlgorithm").notNullable();
|
||||||
|
|
||||||
|
t.datetime("rootCaIssuedAt").notNullable();
|
||||||
|
t.datetime("rootCaExpiration").notNullable();
|
||||||
|
t.string("rootCaSerialNumber").notNullable();
|
||||||
|
t.binary("encryptedRootCaCertificate").notNullable();
|
||||||
|
t.binary("encryptedRootCaPrivateKey").notNullable();
|
||||||
|
|
||||||
|
t.datetime("serverIntermediateCaIssuedAt").notNullable();
|
||||||
|
t.datetime("serverIntermediateCaExpiration").notNullable();
|
||||||
|
t.string("serverIntermediateCaSerialNumber");
|
||||||
|
t.binary("encryptedServerIntermediateCaCertificate").notNullable();
|
||||||
|
t.binary("encryptedServerIntermediateCaChain").notNullable();
|
||||||
|
t.binary("encryptedServerIntermediateCaPrivateKey").notNullable();
|
||||||
|
|
||||||
|
t.datetime("clientIntermediateCaIssuedAt").notNullable();
|
||||||
|
t.datetime("clientIntermediateCaExpiration").notNullable();
|
||||||
|
t.string("clientIntermediateCaSerialNumber").notNullable();
|
||||||
|
t.binary("encryptedClientIntermediateCaCertificate").notNullable();
|
||||||
|
t.binary("encryptedClientIntermediateCaChain").notNullable();
|
||||||
|
t.binary("encryptedClientIntermediateCaPrivateKey").notNullable();
|
||||||
|
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.KmipInstanceConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasKmipInstanceServerCertTable = await knex.schema.hasTable(TableName.KmipInstanceServerCertificates);
|
||||||
|
if (!hasKmipInstanceServerCertTable) {
|
||||||
|
await knex.schema.createTable(TableName.KmipInstanceServerCertificates, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("commonName").notNullable();
|
||||||
|
t.string("altNames").notNullable();
|
||||||
|
t.string("serialNumber").notNullable();
|
||||||
|
t.string("keyAlgorithm").notNullable();
|
||||||
|
t.datetime("issuedAt").notNullable();
|
||||||
|
t.datetime("expiration").notNullable();
|
||||||
|
t.binary("encryptedCertificate").notNullable();
|
||||||
|
t.binary("encryptedChain").notNullable();
|
||||||
|
t.binary("encryptedPrivateKey").notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
@@ -21,4 +71,15 @@ export async function down(knex: Knex): Promise<void> {
|
|||||||
if (hasKmipClientTable) {
|
if (hasKmipClientTable) {
|
||||||
await knex.schema.dropTable(TableName.KmipClient);
|
await knex.schema.dropTable(TableName.KmipClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasKmipInstanceConfigTable = await knex.schema.hasTable(TableName.KmipInstanceConfig);
|
||||||
|
if (hasKmipInstanceConfigTable) {
|
||||||
|
await knex.schema.dropTable(TableName.KmipInstanceConfig);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.KmipInstanceConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasKmipInstanceServerCertTable = await knex.schema.hasTable(TableName.KmipInstanceServerCertificates);
|
||||||
|
if (hasKmipInstanceServerCertTable) {
|
||||||
|
await knex.schema.dropTable(TableName.KmipInstanceServerCertificates);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ export * from "./integration-auths";
|
|||||||
export * from "./integrations";
|
export * from "./integrations";
|
||||||
export * from "./internal-kms";
|
export * from "./internal-kms";
|
||||||
export * from "./kmip-clients";
|
export * from "./kmip-clients";
|
||||||
|
export * from "./kmip-instance-configs";
|
||||||
|
export * from "./kmip-instance-server-certificates";
|
||||||
export * from "./kms-key-versions";
|
export * from "./kms-key-versions";
|
||||||
export * from "./kms-keys";
|
export * from "./kms-keys";
|
||||||
export * from "./kms-root-config";
|
export * from "./kms-root-config";
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const KmipInstanceConfigsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
caKeyAlgorithm: z.string(),
|
||||||
|
rootCaIssuedAt: z.date(),
|
||||||
|
rootCaExpiration: z.date(),
|
||||||
|
rootCaSerialNumber: z.string(),
|
||||||
|
encryptedRootCaCertificate: zodBuffer,
|
||||||
|
encryptedRootCaPrivateKey: zodBuffer,
|
||||||
|
serverIntermediateCaIssuedAt: z.date(),
|
||||||
|
serverIntermediateCaExpiration: z.date(),
|
||||||
|
serverIntermediateCaSerialNumber: z.string().nullable().optional(),
|
||||||
|
encryptedServerIntermediateCaCertificate: zodBuffer,
|
||||||
|
encryptedServerIntermediateCaChain: zodBuffer,
|
||||||
|
encryptedServerIntermediateCaPrivateKey: zodBuffer,
|
||||||
|
clientIntermediateCaIssuedAt: z.date(),
|
||||||
|
clientIntermediateCaExpiration: z.date(),
|
||||||
|
clientIntermediateCaSerialNumber: z.string(),
|
||||||
|
encryptedClientIntermediateCaCertificate: zodBuffer,
|
||||||
|
encryptedClientIntermediateCaChain: zodBuffer,
|
||||||
|
encryptedClientIntermediateCaPrivateKey: zodBuffer,
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TKmipInstanceConfigs = z.infer<typeof KmipInstanceConfigsSchema>;
|
||||||
|
export type TKmipInstanceConfigsInsert = Omit<z.input<typeof KmipInstanceConfigsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TKmipInstanceConfigsUpdate = Partial<Omit<z.input<typeof KmipInstanceConfigsSchema>, TImmutableDBKeys>>;
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// 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 KmipInstanceServerCertificatesSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
commonName: z.string(),
|
||||||
|
altNames: z.string(),
|
||||||
|
serialNumber: z.string(),
|
||||||
|
keyAlgorithm: z.string(),
|
||||||
|
issuedAt: z.date(),
|
||||||
|
expiration: z.date(),
|
||||||
|
encryptedCertificate: zodBuffer,
|
||||||
|
encryptedChain: zodBuffer,
|
||||||
|
encryptedPrivateKey: zodBuffer
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TKmipInstanceServerCertificates = z.infer<typeof KmipInstanceServerCertificatesSchema>;
|
||||||
|
export type TKmipInstanceServerCertificatesInsert = Omit<
|
||||||
|
z.input<typeof KmipInstanceServerCertificatesSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TKmipInstanceServerCertificatesUpdate = Partial<
|
||||||
|
Omit<z.input<typeof KmipInstanceServerCertificatesSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
||||||
@@ -133,7 +133,9 @@ export enum TableName {
|
|||||||
ProjectSlackConfigs = "project_slack_configs",
|
ProjectSlackConfigs = "project_slack_configs",
|
||||||
AppConnection = "app_connections",
|
AppConnection = "app_connections",
|
||||||
SecretSync = "secret_syncs",
|
SecretSync = "secret_syncs",
|
||||||
KmipClient = "kmip_clients"
|
KmipClient = "kmip_clients",
|
||||||
|
KmipInstanceConfig = "kmip_instance_configs",
|
||||||
|
KmipInstanceServerCertificates = "kmip_instance_server_certificates"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TKmipInstanceConfigDALFactory = ReturnType<typeof kmipInstanceConfigDALFactory>;
|
||||||
|
|
||||||
|
export const kmipInstanceConfigDALFactory = (db: TDbClient) => {
|
||||||
|
const kmipInstanceConfigOrm = ormify(db, TableName.KmipInstanceConfig);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...kmipInstanceConfigOrm
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TKmipInstanceServerCertificateDALFactory = ReturnType<typeof kmipInstanceServerCertificateDALFactory>;
|
||||||
|
|
||||||
|
export const kmipInstanceServerCertificateDALFactory = (db: TDbClient) => {
|
||||||
|
const kmipInstanceServerCertificateOrm = ormify(db, TableName.KmipInstanceServerCertificates);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...kmipInstanceServerCertificateOrm
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -36,6 +36,8 @@ import { identityProjectAdditionalPrivilegeDALFactory } from "@app/ee/services/i
|
|||||||
import { identityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
import { identityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
||||||
import { identityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
|
import { identityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
|
||||||
import { kmipClientDALFactory } from "@app/ee/services/kmip/kmip-client-dal";
|
import { kmipClientDALFactory } from "@app/ee/services/kmip/kmip-client-dal";
|
||||||
|
import { kmipInstanceConfigDALFactory } from "@app/ee/services/kmip/kmip-instance-config-dal";
|
||||||
|
import { kmipInstanceServerCertificateDALFactory } from "@app/ee/services/kmip/kmip-instance-server-certificate-dal";
|
||||||
import { kmipServiceFactory } from "@app/ee/services/kmip/kmip-service";
|
import { kmipServiceFactory } from "@app/ee/services/kmip/kmip-service";
|
||||||
import { ldapConfigDALFactory } from "@app/ee/services/ldap-config/ldap-config-dal";
|
import { ldapConfigDALFactory } from "@app/ee/services/ldap-config/ldap-config-dal";
|
||||||
import { ldapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
import { ldapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
||||||
@@ -383,6 +385,8 @@ export const registerRoutes = async (
|
|||||||
const projectTemplateDAL = projectTemplateDALFactory(db);
|
const projectTemplateDAL = projectTemplateDALFactory(db);
|
||||||
const resourceMetadataDAL = resourceMetadataDALFactory(db);
|
const resourceMetadataDAL = resourceMetadataDALFactory(db);
|
||||||
const kmipClientDAL = kmipClientDALFactory(db);
|
const kmipClientDAL = kmipClientDALFactory(db);
|
||||||
|
const kmipInstanceConfigDAL = kmipInstanceConfigDALFactory(db);
|
||||||
|
const kmipInstanceServerCertificateDAL = kmipInstanceServerCertificateDALFactory(db);
|
||||||
|
|
||||||
const permissionService = permissionServiceFactory({
|
const permissionService = permissionServiceFactory({
|
||||||
permissionDAL,
|
permissionDAL,
|
||||||
@@ -623,7 +627,9 @@ export const registerRoutes = async (
|
|||||||
orgService,
|
orgService,
|
||||||
keyStore,
|
keyStore,
|
||||||
licenseService,
|
licenseService,
|
||||||
kmsService
|
kmsService,
|
||||||
|
kmipInstanceConfigDAL,
|
||||||
|
kmipInstanceServerCertificateDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const orgAdminService = orgAdminServiceFactory({
|
const orgAdminService = orgAdminServiceFactory({
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas";
|
import { OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas";
|
||||||
@@ -7,6 +8,8 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
|
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
||||||
|
import { validateAltNamesField } from "@app/services/certificate-authority/certificate-authority-validators";
|
||||||
import { RootKeyEncryptionStrategy } from "@app/services/kms/kms-types";
|
import { RootKeyEncryptionStrategy } from "@app/services/kms/kms-types";
|
||||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
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";
|
||||||
@@ -316,4 +319,88 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/kmip",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
caKeyAlgorithm: z.nativeEnum(CertKeyAlgorithm)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
serverCertificateChain: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: (req, res, done) => {
|
||||||
|
verifyAuth([AuthMode.JWT])(req, res, () => {
|
||||||
|
verifySuperAdmin(req, res, done);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
return server.services.superAdmin.setupInstanceKmip({
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/kmip",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
serverCertificateChain: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: (req, res, done) => {
|
||||||
|
verifyAuth([AuthMode.JWT])(req, res, () => {
|
||||||
|
verifySuperAdmin(req, res, done);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handler: async () => {
|
||||||
|
return server.services.superAdmin.getInstanceKmip();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/kmip/server-certificates",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
commonName: z.string().trim().min(1),
|
||||||
|
altNames: validateAltNamesField,
|
||||||
|
keyAlgorithm: z.nativeEnum(CertKeyAlgorithm),
|
||||||
|
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificateChain: z.string(),
|
||||||
|
certificate: z.string(),
|
||||||
|
privateKey: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: (req, res, done) => {
|
||||||
|
verifyAuth([AuthMode.JWT])(req, res, () => {
|
||||||
|
verifySuperAdmin(req, res, done);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
return server.services.superAdmin.generateInstanceKmipServerCertificate({
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
|
import * as x509 from "@peculiar/x509";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
|
import crypto, { KeyObject } from "crypto";
|
||||||
|
import ms from "ms";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
|
import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
|
||||||
|
import { TKmipInstanceConfigDALFactory } from "@app/ee/services/kmip/kmip-instance-config-dal";
|
||||||
|
import { TKmipInstanceServerCertificateDALFactory } from "@app/ee/services/kmip/kmip-instance-server-certificate-dal";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
@@ -10,6 +16,9 @@ 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 { hostnameRegex } from "../certificate-authority/certificate-authority-validators";
|
||||||
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";
|
||||||
@@ -19,7 +28,13 @@ import { TUserDALFactory } from "../user/user-dal";
|
|||||||
import { TUserAliasDALFactory } from "../user-alias/user-alias-dal";
|
import { TUserAliasDALFactory } from "../user-alias/user-alias-dal";
|
||||||
import { UserAliasType } from "../user-alias/user-alias-types";
|
import { UserAliasType } from "../user-alias/user-alias-types";
|
||||||
import { TSuperAdminDALFactory } from "./super-admin-dal";
|
import { TSuperAdminDALFactory } from "./super-admin-dal";
|
||||||
import { LoginMethod, TAdminGetUsersDTO, TAdminSignUpDTO } from "./super-admin-types";
|
import {
|
||||||
|
LoginMethod,
|
||||||
|
TAdminGetUsersDTO,
|
||||||
|
TAdminSignUpDTO,
|
||||||
|
TGenerateInstanceKmipServerCertificateDTO,
|
||||||
|
TSetupInstanceKmipDTO
|
||||||
|
} from "./super-admin-types";
|
||||||
|
|
||||||
type TSuperAdminServiceFactoryDep = {
|
type TSuperAdminServiceFactoryDep = {
|
||||||
serverCfgDAL: TSuperAdminDALFactory;
|
serverCfgDAL: TSuperAdminDALFactory;
|
||||||
@@ -31,6 +46,8 @@ 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">;
|
||||||
|
kmipInstanceConfigDAL: TKmipInstanceConfigDALFactory;
|
||||||
|
kmipInstanceServerCertificateDAL: TKmipInstanceServerCertificateDALFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSuperAdminServiceFactory = ReturnType<typeof superAdminServiceFactory>;
|
export type TSuperAdminServiceFactory = ReturnType<typeof superAdminServiceFactory>;
|
||||||
@@ -57,7 +74,9 @@ export const superAdminServiceFactory = ({
|
|||||||
keyStore,
|
keyStore,
|
||||||
kmsRootConfigDAL,
|
kmsRootConfigDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
licenseService
|
licenseService,
|
||||||
|
kmipInstanceConfigDAL,
|
||||||
|
kmipInstanceServerCertificateDAL
|
||||||
}: 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
|
||||||
@@ -369,6 +388,302 @@ export const superAdminServiceFactory = ({
|
|||||||
await kmsService.updateEncryptionStrategy(strategy);
|
await kmsService.updateEncryptionStrategy(strategy);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setupInstanceKmip = async ({ caKeyAlgorithm }: TSetupInstanceKmipDTO) => {
|
||||||
|
const kmipInstanceConfig = await kmipInstanceConfigDAL.findById(ADMIN_CONFIG_DB_UUID);
|
||||||
|
if (kmipInstanceConfig) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "KMIP has already been configured for the instance"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const alg = keyAlgorithmToAlgCfg(caKeyAlgorithm);
|
||||||
|
|
||||||
|
// generate root CA
|
||||||
|
const rootCaSerialNumber = createSerialNumber();
|
||||||
|
const rootCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||||
|
const rootCaSkObj = KeyObject.from(rootCaKeys.privateKey);
|
||||||
|
const rootCaIssuedAt = new Date();
|
||||||
|
const rootCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 20));
|
||||||
|
|
||||||
|
const rootCaCert = await x509.X509CertificateGenerator.createSelfSigned({
|
||||||
|
name: "CN=KMIP Root CA",
|
||||||
|
serialNumber: rootCaSerialNumber,
|
||||||
|
notBefore: rootCaIssuedAt,
|
||||||
|
notAfter: rootCaExpiration,
|
||||||
|
signingAlgorithm: alg,
|
||||||
|
keys: rootCaKeys,
|
||||||
|
extensions: [
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
|
||||||
|
await x509.SubjectKeyIdentifierExtension.create(rootCaKeys.publicKey)
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// generate intermediate server CA
|
||||||
|
const serverIntermediateCaSerialNumber = createSerialNumber();
|
||||||
|
const serverIntermediateCaIssuedAt = new Date();
|
||||||
|
const serverIntermediateCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 10));
|
||||||
|
const serverIntermediateCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||||
|
const serverIntermediateCaSkObj = KeyObject.from(serverIntermediateCaKeys.privateKey);
|
||||||
|
|
||||||
|
const serverIntermediateCaCert = await x509.X509CertificateGenerator.create({
|
||||||
|
serialNumber: serverIntermediateCaSerialNumber,
|
||||||
|
subject: "CN=KMIP Server Intermediate CA",
|
||||||
|
issuer: rootCaCert.subject,
|
||||||
|
notBefore: serverIntermediateCaIssuedAt,
|
||||||
|
notAfter: serverIntermediateCaExpiration,
|
||||||
|
signingKey: rootCaKeys.privateKey,
|
||||||
|
publicKey: serverIntermediateCaKeys.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(serverIntermediateCaKeys.publicKey)
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// generate intermediate client CA
|
||||||
|
const clientIntermediateCaSerialNumber = createSerialNumber();
|
||||||
|
const clientIntermediateCaIssuedAt = new Date();
|
||||||
|
const clientIntermediateCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 10));
|
||||||
|
const clientIntermediateCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||||
|
const clientIntermediateCaSkObj = KeyObject.from(clientIntermediateCaKeys.privateKey);
|
||||||
|
|
||||||
|
const clientIntermediateCaCert = await x509.X509CertificateGenerator.create({
|
||||||
|
serialNumber: clientIntermediateCaSerialNumber,
|
||||||
|
subject: "CN=KMIP Client Intermediate CA",
|
||||||
|
issuer: rootCaCert.subject,
|
||||||
|
notBefore: clientIntermediateCaIssuedAt,
|
||||||
|
notAfter: clientIntermediateCaExpiration,
|
||||||
|
signingKey: rootCaKeys.privateKey,
|
||||||
|
publicKey: clientIntermediateCaKeys.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(clientIntermediateCaKeys.publicKey)
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
const encryptWithRoot = kmsService.encryptWithRootKey();
|
||||||
|
|
||||||
|
await kmipInstanceConfigDAL.create({
|
||||||
|
// @ts-expect-error id is kept as fixed for idempotence and to avoid race condition
|
||||||
|
id: ADMIN_CONFIG_DB_UUID,
|
||||||
|
caKeyAlgorithm,
|
||||||
|
rootCaIssuedAt,
|
||||||
|
rootCaExpiration,
|
||||||
|
rootCaSerialNumber,
|
||||||
|
encryptedRootCaCertificate: encryptWithRoot(Buffer.from(rootCaCert.rawData)),
|
||||||
|
encryptedRootCaPrivateKey: encryptWithRoot(
|
||||||
|
rootCaSkObj.export({
|
||||||
|
type: "pkcs8",
|
||||||
|
format: "der"
|
||||||
|
})
|
||||||
|
),
|
||||||
|
serverIntermediateCaIssuedAt,
|
||||||
|
serverIntermediateCaExpiration,
|
||||||
|
serverIntermediateCaSerialNumber,
|
||||||
|
encryptedServerIntermediateCaCertificate: encryptWithRoot(
|
||||||
|
Buffer.from(new Uint8Array(serverIntermediateCaCert.rawData))
|
||||||
|
),
|
||||||
|
encryptedServerIntermediateCaChain: encryptWithRoot(Buffer.from(rootCaCert.toString("pem"))),
|
||||||
|
encryptedServerIntermediateCaPrivateKey: encryptWithRoot(
|
||||||
|
serverIntermediateCaSkObj.export({
|
||||||
|
type: "pkcs8",
|
||||||
|
format: "der"
|
||||||
|
})
|
||||||
|
),
|
||||||
|
clientIntermediateCaIssuedAt,
|
||||||
|
clientIntermediateCaExpiration,
|
||||||
|
clientIntermediateCaSerialNumber,
|
||||||
|
encryptedClientIntermediateCaCertificate: encryptWithRoot(
|
||||||
|
Buffer.from(new Uint8Array(clientIntermediateCaCert.rawData))
|
||||||
|
),
|
||||||
|
encryptedClientIntermediateCaChain: encryptWithRoot(Buffer.from(rootCaCert.toString("pem"))),
|
||||||
|
encryptedClientIntermediateCaPrivateKey: encryptWithRoot(
|
||||||
|
clientIntermediateCaSkObj.export({
|
||||||
|
type: "pkcs8",
|
||||||
|
format: "der"
|
||||||
|
})
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
// the order of the cert is intentional - for client chains, ordering should be from intermediate to root
|
||||||
|
serverCertificateChain: `${serverIntermediateCaCert.toString("pem")}\n${rootCaCert.toString("pem")}`.trim()
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInstanceKmip = async () => {
|
||||||
|
const kmipInstanceConfig = await kmipInstanceConfigDAL.findById(ADMIN_CONFIG_DB_UUID);
|
||||||
|
if (!kmipInstanceConfig) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "KMIP has not been configured for the instance"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const decryptWithRoot = kmsService.decryptWithRootKey();
|
||||||
|
const rootCaCert = new x509.X509Certificate(decryptWithRoot(kmipInstanceConfig.encryptedRootCaCertificate));
|
||||||
|
const serverIntermediateCaCert = new x509.X509Certificate(
|
||||||
|
decryptWithRoot(kmipInstanceConfig.encryptedServerIntermediateCaCertificate)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
// the order of the cert is intentional - for client chains, ordering should be from intermediate to root
|
||||||
|
serverCertificateChain: `${serverIntermediateCaCert.toString("pem")}\n${rootCaCert.toString("pem")}`.trim()
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateInstanceKmipServerCertificate = async ({
|
||||||
|
ttl,
|
||||||
|
commonName,
|
||||||
|
altNames,
|
||||||
|
keyAlgorithm
|
||||||
|
}: TGenerateInstanceKmipServerCertificateDTO) => {
|
||||||
|
const kmipInstanceConfig = await kmipInstanceConfigDAL.findById(ADMIN_CONFIG_DB_UUID);
|
||||||
|
if (!kmipInstanceConfig) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "KMIP has not been configured for the instance"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const decryptWithRoot = kmsService.decryptWithRootKey();
|
||||||
|
const caCertObj = new x509.X509Certificate(
|
||||||
|
decryptWithRoot(kmipInstanceConfig.encryptedServerIntermediateCaCertificate)
|
||||||
|
);
|
||||||
|
|
||||||
|
const notBeforeDate = new Date();
|
||||||
|
const notAfterDate = new Date(new Date().getTime() + ms(ttl));
|
||||||
|
|
||||||
|
const caCertNotBeforeDate = new Date(caCertObj.notBefore);
|
||||||
|
const caCertNotAfterDate = new Date(caCertObj.notAfter);
|
||||||
|
|
||||||
|
// check not before constraint
|
||||||
|
if (notBeforeDate < caCertNotBeforeDate) {
|
||||||
|
throw new BadRequestError({ message: "notBefore date is before CA certificate's notBefore date" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notBeforeDate > notAfterDate) throw new BadRequestError({ message: "notBefore date is after notAfter date" });
|
||||||
|
|
||||||
|
// check not after constraint
|
||||||
|
if (notAfterDate > caCertNotAfterDate) {
|
||||||
|
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const alg = keyAlgorithmToAlgCfg(keyAlgorithm);
|
||||||
|
const leafKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||||
|
|
||||||
|
const extensions: x509.Extension[] = [
|
||||||
|
new x509.BasicConstraintsExtension(false),
|
||||||
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
||||||
|
await x509.SubjectKeyIdentifierExtension.create(leafKeys.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)
|
||||||
|
];
|
||||||
|
|
||||||
|
const altNamesArray: {
|
||||||
|
type: "email" | "dns";
|
||||||
|
value: string;
|
||||||
|
}[] = altNames
|
||||||
|
.split(",")
|
||||||
|
.map((name) => name.trim())
|
||||||
|
.map((altName) => {
|
||||||
|
// check if the altName is a valid email
|
||||||
|
if (z.string().email().safeParse(altName).success) {
|
||||||
|
return {
|
||||||
|
type: "email",
|
||||||
|
value: altName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the altName is a valid hostname
|
||||||
|
if (hostnameRegex.test(altName)) {
|
||||||
|
return {
|
||||||
|
type: "dns",
|
||||||
|
value: altName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If altName is neither a valid email nor a valid hostname, throw an error or handle it accordingly
|
||||||
|
throw new Error(`Invalid altName: ${altName}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
||||||
|
extensions.push(altNamesExtension);
|
||||||
|
|
||||||
|
const caAlg = keyAlgorithmToAlgCfg(kmipInstanceConfig.caKeyAlgorithm as CertKeyAlgorithm);
|
||||||
|
|
||||||
|
const decryptedCaCertChain = decryptWithRoot(kmipInstanceConfig.encryptedServerIntermediateCaChain).toString(
|
||||||
|
"utf-8"
|
||||||
|
);
|
||||||
|
|
||||||
|
const caSkObj = crypto.createPrivateKey({
|
||||||
|
key: decryptWithRoot(kmipInstanceConfig.encryptedServerIntermediateCaPrivateKey),
|
||||||
|
format: "der",
|
||||||
|
type: "pkcs8"
|
||||||
|
});
|
||||||
|
|
||||||
|
const caPrivateKey = await crypto.subtle.importKey(
|
||||||
|
"pkcs8",
|
||||||
|
caSkObj.export({ format: "der", type: "pkcs8" }),
|
||||||
|
caAlg,
|
||||||
|
true,
|
||||||
|
["sign"]
|
||||||
|
);
|
||||||
|
|
||||||
|
const serialNumber = createSerialNumber();
|
||||||
|
const leafCert = await x509.X509CertificateGenerator.create({
|
||||||
|
serialNumber,
|
||||||
|
subject: `CN=${commonName}`,
|
||||||
|
issuer: caCertObj.subject,
|
||||||
|
notBefore: notBeforeDate,
|
||||||
|
notAfter: notAfterDate,
|
||||||
|
signingKey: caPrivateKey,
|
||||||
|
publicKey: leafKeys.publicKey,
|
||||||
|
signingAlgorithm: alg,
|
||||||
|
extensions
|
||||||
|
});
|
||||||
|
|
||||||
|
const encryptWithRoot = kmsService.encryptWithRootKey();
|
||||||
|
const skLeafObj = KeyObject.from(leafKeys.privateKey);
|
||||||
|
|
||||||
|
const serverCert = await kmipInstanceServerCertificateDAL.create({
|
||||||
|
keyAlgorithm,
|
||||||
|
issuedAt: notBeforeDate,
|
||||||
|
expiration: notAfterDate,
|
||||||
|
serialNumber,
|
||||||
|
commonName,
|
||||||
|
altNames,
|
||||||
|
encryptedCertificate: encryptWithRoot(Buffer.from(new Uint8Array(leafCert.rawData))),
|
||||||
|
encryptedPrivateKey: encryptWithRoot(skLeafObj.export({ format: "der", type: "pkcs8" })),
|
||||||
|
encryptedChain: encryptWithRoot(Buffer.from(`${decryptedCaCertChain}\n${caCertObj.toString("pem")}`.trim()))
|
||||||
|
});
|
||||||
|
|
||||||
|
return serverCert;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
initServerCfg,
|
initServerCfg,
|
||||||
updateServerCfg,
|
updateServerCfg,
|
||||||
@@ -377,6 +692,9 @@ export const superAdminServiceFactory = ({
|
|||||||
deleteUser,
|
deleteUser,
|
||||||
getAdminSlackConfig,
|
getAdminSlackConfig,
|
||||||
updateRootEncryptionStrategy,
|
updateRootEncryptionStrategy,
|
||||||
getConfiguredEncryptionStrategies
|
getConfiguredEncryptionStrategies,
|
||||||
|
setupInstanceKmip,
|
||||||
|
getInstanceKmip,
|
||||||
|
generateInstanceKmipServerCertificate
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { CertKeyAlgorithm } from "../certificate/certificate-types";
|
||||||
|
|
||||||
export type TAdminSignUpDTO = {
|
export type TAdminSignUpDTO = {
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
@@ -31,3 +33,14 @@ export enum LoginMethod {
|
|||||||
LDAP = "ldap",
|
LDAP = "ldap",
|
||||||
OIDC = "oidc"
|
OIDC = "oidc"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TSetupInstanceKmipDTO = {
|
||||||
|
caKeyAlgorithm: CertKeyAlgorithm;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGenerateInstanceKmipServerCertificateDTO = {
|
||||||
|
commonName: string;
|
||||||
|
altNames: string;
|
||||||
|
keyAlgorithm: CertKeyAlgorithm;
|
||||||
|
ttl: string;
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import FileSaver from "file-saver";
|
||||||
|
|
||||||
|
export const downloadTxtFile = (filename: string, content: string) => {
|
||||||
|
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
||||||
|
FileSaver.saveAs(blob, filename);
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
export {
|
export {
|
||||||
useAdminDeleteUser,
|
useAdminDeleteUser,
|
||||||
useCreateAdminUser,
|
useCreateAdminUser,
|
||||||
|
useSetupInstanceKmip,
|
||||||
useUpdateAdminSlackConfig,
|
useUpdateAdminSlackConfig,
|
||||||
useUpdateServerConfig,
|
useUpdateServerConfig,
|
||||||
useUpdateServerEncryptionStrategy
|
useUpdateServerEncryptionStrategy
|
||||||
@@ -8,6 +9,7 @@ export {
|
|||||||
export {
|
export {
|
||||||
useAdminGetUsers,
|
useAdminGetUsers,
|
||||||
useGetAdminSlackConfig,
|
useGetAdminSlackConfig,
|
||||||
|
useGetInstanceKmipConfig,
|
||||||
useGetServerConfig,
|
useGetServerConfig,
|
||||||
useGetServerRootKmsEncryptionDetails
|
useGetServerRootKmsEncryptionDetails
|
||||||
} from "./queries";
|
} from "./queries";
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
RootKeyEncryptionStrategy,
|
RootKeyEncryptionStrategy,
|
||||||
TCreateAdminUserDTO,
|
TCreateAdminUserDTO,
|
||||||
TServerConfig,
|
TServerConfig,
|
||||||
|
TSetupInstanceKmipDTO,
|
||||||
TUpdateAdminSlackConfigDTO
|
TUpdateAdminSlackConfigDTO
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
@@ -98,3 +99,15 @@ export const useUpdateServerEncryptionStrategy = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useSetupInstanceKmip = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (payload: TSetupInstanceKmipDTO) => {
|
||||||
|
await apiRequest.post("/api/v1/admin/kmip", payload);
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: adminQueryKeys.getInstanceKmip() });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { User } from "../types";
|
|||||||
import {
|
import {
|
||||||
AdminGetUsersFilters,
|
AdminGetUsersFilters,
|
||||||
AdminSlackConfig,
|
AdminSlackConfig,
|
||||||
|
InstanceKmipConfig,
|
||||||
TGetServerRootKmsEncryptionDetails,
|
TGetServerRootKmsEncryptionDetails,
|
||||||
TServerConfig
|
TServerConfig
|
||||||
} from "./types";
|
} from "./types";
|
||||||
@@ -18,7 +19,8 @@ export const adminQueryKeys = {
|
|||||||
serverConfig: () => ["server-config"] as const,
|
serverConfig: () => ["server-config"] as const,
|
||||||
getUsers: (filters: AdminGetUsersFilters) => [adminStandaloneKeys.getUsers, { filters }] as const,
|
getUsers: (filters: AdminGetUsersFilters) => [adminStandaloneKeys.getUsers, { filters }] as const,
|
||||||
getAdminSlackConfig: () => ["admin-slack-config"] as const,
|
getAdminSlackConfig: () => ["admin-slack-config"] as const,
|
||||||
getServerEncryptionStrategies: () => ["server-encryption-strategies"] as const
|
getServerEncryptionStrategies: () => ["server-encryption-strategies"] as const,
|
||||||
|
getInstanceKmip: () => ["instance-kmip"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchServerConfig = async () => {
|
export const fetchServerConfig = async () => {
|
||||||
@@ -93,3 +95,14 @@ export const useGetServerRootKmsEncryptionDetails = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetInstanceKmipConfig = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: adminQueryKeys.getInstanceKmip(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiRequest.get<InstanceKmipConfig>("/api/v1/admin/kmip");
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { CertKeyAlgorithm } from "../certificates/enums";
|
||||||
|
|
||||||
export enum LoginMethod {
|
export enum LoginMethod {
|
||||||
EMAIL = "email",
|
EMAIL = "email",
|
||||||
GOOGLE = "google",
|
GOOGLE = "google",
|
||||||
@@ -62,7 +64,15 @@ export type TGetServerRootKmsEncryptionDetails = {
|
|||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type InstanceKmipConfig = {
|
||||||
|
serverCertificateChain: string;
|
||||||
|
};
|
||||||
|
|
||||||
export enum RootKeyEncryptionStrategy {
|
export enum RootKeyEncryptionStrategy {
|
||||||
Software = "SOFTWARE",
|
Software = "SOFTWARE",
|
||||||
HSM = "HSM"
|
HSM = "HSM"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TSetupInstanceKmipDTO = {
|
||||||
|
caKeyAlgorithm: CertKeyAlgorithm;
|
||||||
|
};
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
import { AuthPanel } from "./components/AuthPanel";
|
import { AuthPanel } from "./components/AuthPanel";
|
||||||
import { EncryptionPanel } from "./components/EncryptionPanel";
|
import { EncryptionPanel } from "./components/EncryptionPanel";
|
||||||
import { IntegrationPanel } from "./components/IntegrationPanel";
|
import { IntegrationPanel } from "./components/IntegrationPanel";
|
||||||
|
import { KmipPanel } from "./components/KmipPanel";
|
||||||
import { RateLimitPanel } from "./components/RateLimitPanel";
|
import { RateLimitPanel } from "./components/RateLimitPanel";
|
||||||
import { UserPanel } from "./components/UserPanel";
|
import { UserPanel } from "./components/UserPanel";
|
||||||
|
|
||||||
@@ -40,7 +41,8 @@ enum TabSections {
|
|||||||
Auth = "auth",
|
Auth = "auth",
|
||||||
RateLimit = "rate-limit",
|
RateLimit = "rate-limit",
|
||||||
Integrations = "integrations",
|
Integrations = "integrations",
|
||||||
Users = "users"
|
Users = "users",
|
||||||
|
Kmip = "kmip"
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SignUpModes {
|
enum SignUpModes {
|
||||||
@@ -149,6 +151,7 @@ export const OverviewPage = () => {
|
|||||||
<Tab value={TabSections.RateLimit}>Rate Limit</Tab>
|
<Tab value={TabSections.RateLimit}>Rate Limit</Tab>
|
||||||
<Tab value={TabSections.Integrations}>Integrations</Tab>
|
<Tab value={TabSections.Integrations}>Integrations</Tab>
|
||||||
<Tab value={TabSections.Users}>Users</Tab>
|
<Tab value={TabSections.Users}>Users</Tab>
|
||||||
|
<Tab value={TabSections.Kmip}>KMIP</Tab>
|
||||||
</div>
|
</div>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanel value={TabSections.Settings}>
|
<TabPanel value={TabSections.Settings}>
|
||||||
@@ -347,6 +350,9 @@ export const OverviewPage = () => {
|
|||||||
<TabPanel value={TabSections.Users}>
|
<TabPanel value={TabSections.Users}>
|
||||||
<UserPanel />
|
<UserPanel />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
<TabPanel value={TabSections.Kmip}>
|
||||||
|
<KmipPanel />
|
||||||
|
</TabPanel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { faCheck, faCopy, faDownload } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
IconButton,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
Select,
|
||||||
|
SelectItem,
|
||||||
|
Spinner,
|
||||||
|
TextArea,
|
||||||
|
Tooltip
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { downloadTxtFile } from "@app/helpers/download";
|
||||||
|
import { usePopUp, useTimedReset } from "@app/hooks";
|
||||||
|
import { useGetInstanceKmipConfig, useSetupInstanceKmip } from "@app/hooks/api";
|
||||||
|
import { InstanceKmipConfig } from "@app/hooks/api/admin/types";
|
||||||
|
import { certKeyAlgorithms } from "@app/hooks/api/certificates/constants";
|
||||||
|
import { CertKeyAlgorithm } from "@app/hooks/api/certificates/enums";
|
||||||
|
|
||||||
|
const kmipInstanceConfigFormSchema = z.object({
|
||||||
|
caKeyAlgorithm: z.nativeEnum(CertKeyAlgorithm)
|
||||||
|
});
|
||||||
|
|
||||||
|
type TKmipInstanceConfigForm = z.infer<typeof kmipInstanceConfigFormSchema>;
|
||||||
|
|
||||||
|
const KmipInstanceConfigSection = ({
|
||||||
|
kmipConfig,
|
||||||
|
isKmipConfigLoading
|
||||||
|
}: {
|
||||||
|
kmipConfig?: InstanceKmipConfig;
|
||||||
|
isKmipConfigLoading: boolean;
|
||||||
|
}) => {
|
||||||
|
const { popUp, handlePopUpToggle, handlePopUpClose, handlePopUpOpen } = usePopUp([
|
||||||
|
"configureKmip"
|
||||||
|
] as const);
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
control,
|
||||||
|
formState: { isSubmitting }
|
||||||
|
} = useForm<TKmipInstanceConfigForm>({
|
||||||
|
resolver: zodResolver(kmipInstanceConfigFormSchema)
|
||||||
|
});
|
||||||
|
const { mutateAsync: setupInstanceKmip } = useSetupInstanceKmip();
|
||||||
|
|
||||||
|
const onFormSubmit = async (formData: TKmipInstanceConfigForm) => {
|
||||||
|
await setupInstanceKmip(formData);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully configured KMIP"
|
||||||
|
});
|
||||||
|
|
||||||
|
handlePopUpClose("configureKmip");
|
||||||
|
};
|
||||||
|
const [copyTextCertificate, isCopyingCertificate, setCopyTextCertificate] = useTimedReset<string>(
|
||||||
|
{
|
||||||
|
initialState: "Copy to clipboard"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col justify-start">
|
||||||
|
<div className="mb-2 text-xl font-semibold text-mineshaft-100">KMIP configuration</div>
|
||||||
|
{isKmipConfigLoading && (
|
||||||
|
<div className="mt-8 flex justify-center">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isKmipConfigLoading && kmipConfig && (
|
||||||
|
<div className="mt-2">
|
||||||
|
<div className="text-lg">KMIP CA Certificate for Clients</div>
|
||||||
|
<div className="mt-2 max-w-lg text-sm text-mineshaft-400">
|
||||||
|
This certificate chain should be used by KMIP clients to verify the identity of the
|
||||||
|
KMIP servers and establish a secure TLS connection for encrypted communication.
|
||||||
|
</div>
|
||||||
|
<div className="flex max-w-2xl">
|
||||||
|
<div className="flex w-full justify-end">
|
||||||
|
<Tooltip content={copyTextCertificate}>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(kmipConfig.serverCertificateChain);
|
||||||
|
setCopyTextCertificate("Copied");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={isCopyingCertificate ? faCheck : faCopy} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Download">
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative ml-2"
|
||||||
|
onClick={() => {
|
||||||
|
downloadTxtFile("ca-chain.pem", kmipConfig.serverCertificateChain);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faDownload} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TextArea
|
||||||
|
value={kmipConfig.serverCertificateChain}
|
||||||
|
reSize="none"
|
||||||
|
className="mt-2 h-48 max-w-2xl"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isKmipConfigLoading && !kmipConfig && (
|
||||||
|
<div className="mt-2">
|
||||||
|
<div>KMIP has not yet been configured for the instance.</div>
|
||||||
|
<Button
|
||||||
|
className="mt-2"
|
||||||
|
onClick={() => {
|
||||||
|
handlePopUpOpen("configureKmip");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Setup KMIP
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Modal
|
||||||
|
isOpen={popUp.configureKmip.isOpen}
|
||||||
|
onOpenChange={(state) => handlePopUpToggle("configureKmip", state)}
|
||||||
|
>
|
||||||
|
<ModalContent title="Configure KMIP for the instance">
|
||||||
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="caKeyAlgorithm"
|
||||||
|
defaultValue={CertKeyAlgorithm.RSA_2048}
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="CA Key Algorithm"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
helperText="This defines the key algorithm used for generating the KMIP Root CA and Intermediate CAs, which sign all KMIP server and client certificates."
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{certKeyAlgorithms.map(({ label, value }) => (
|
||||||
|
<SelectItem value={String(value || "")} key={label}>
|
||||||
|
{label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="mt-6 flex w-full gap-4">
|
||||||
|
<Button
|
||||||
|
className=""
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting}
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className=""
|
||||||
|
size="sm"
|
||||||
|
variant="outline_bg"
|
||||||
|
type="button"
|
||||||
|
onClick={() => handlePopUpClose("configureKmip")}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const KmipServerConfigSection = () => {
|
||||||
|
return (
|
||||||
|
<div className="mt-8 flex flex-col justify-start">
|
||||||
|
<div className="text-lg">KMIP Server Certificates</div>
|
||||||
|
<div className="mt-2 max-w-lg text-sm text-mineshaft-400">
|
||||||
|
These certificates should be used to configure TLS for the KMIP servers.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const KmipPanel = () => {
|
||||||
|
const { data: kmipConfig, isPending } = useGetInstanceKmipConfig();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<KmipInstanceConfigSection kmipConfig={kmipConfig} isKmipConfigLoading={isPending} />
|
||||||
|
{kmipConfig && <KmipServerConfigSection />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -128,7 +128,7 @@ const KmipClientForm = ({ onComplete, kmipClient }: FormProps) => {
|
|||||||
name="permissions"
|
name="permissions"
|
||||||
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
||||||
return (
|
return (
|
||||||
<FormControl label="Key Usage" errorText={error?.message} isError={Boolean(error)}>
|
<FormControl label="Permissions" errorText={error?.message} isError={Boolean(error)}>
|
||||||
<div className="mb-7 mt-2 grid grid-cols-2 gap-2">
|
<div className="mb-7 mt-2 grid grid-cols-2 gap-2">
|
||||||
{KMIP_PERMISSIONS_OPTIONS.map(({ label, value: optionValue }) => {
|
{KMIP_PERMISSIONS_OPTIONS.map(({ label, value: optionValue }) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user