mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
misc: initial instance KMIP PKI setup
This commit is contained in:
16
backend/src/@types/knex.d.ts
vendored
16
backend/src/@types/knex.d.ts
vendored
@@ -146,6 +146,12 @@ import {
|
||||
TKmipClients,
|
||||
TKmipClientsInsert,
|
||||
TKmipClientsUpdate,
|
||||
TKmipInstanceConfigs,
|
||||
TKmipInstanceConfigsInsert,
|
||||
TKmipInstanceConfigsUpdate,
|
||||
TKmipInstanceServerCertificates,
|
||||
TKmipInstanceServerCertificatesInsert,
|
||||
TKmipInstanceServerCertificatesUpdate,
|
||||
TKmsKeys,
|
||||
TKmsKeysInsert,
|
||||
TKmsKeysUpdate,
|
||||
@@ -906,5 +912,15 @@ declare module "knex/types/tables" {
|
||||
>;
|
||||
[TableName.SecretSync]: KnexOriginal.CompositeTableType<TSecretSyncs, TSecretSyncsInsert, TSecretSyncsUpdate>;
|
||||
[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 { TableName } from "../schemas";
|
||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
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");
|
||||
});
|
||||
}
|
||||
|
||||
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> {
|
||||
@@ -21,4 +71,15 @@ export async function down(knex: Knex): Promise<void> {
|
||||
if (hasKmipClientTable) {
|
||||
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 "./internal-kms";
|
||||
export * from "./kmip-clients";
|
||||
export * from "./kmip-instance-configs";
|
||||
export * from "./kmip-instance-server-certificates";
|
||||
export * from "./kms-key-versions";
|
||||
export * from "./kms-keys";
|
||||
export * from "./kms-root-config";
|
||||
|
||||
38
backend/src/db/schemas/kmip-instance-configs.ts
Normal file
38
backend/src/db/schemas/kmip-instance-configs.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// Code generated by automation script, DO NOT EDIT.
|
||||
// Automated by pulling database and generating zod schema
|
||||
// To update. Just run npm run generate:schema
|
||||
// Written by akhilmhdh.
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
import { zodBuffer } from "@app/lib/zod";
|
||||
|
||||
import { TImmutableDBKeys } from "./models";
|
||||
|
||||
export const 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>>;
|
||||
32
backend/src/db/schemas/kmip-instance-server-certificates.ts
Normal file
32
backend/src/db/schemas/kmip-instance-server-certificates.ts
Normal file
@@ -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",
|
||||
AppConnection = "app_connections",
|
||||
SecretSync = "secret_syncs",
|
||||
KmipClient = "kmip_clients"
|
||||
KmipClient = "kmip_clients",
|
||||
KmipInstanceConfig = "kmip_instance_configs",
|
||||
KmipInstanceServerCertificates = "kmip_instance_server_certificates"
|
||||
}
|
||||
|
||||
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
||||
|
||||
13
backend/src/ee/services/kmip/kmip-instance-config-dal.ts
Normal file
13
backend/src/ee/services/kmip/kmip-instance-config-dal.ts
Normal file
@@ -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 { 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 { 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 { ldapConfigDALFactory } from "@app/ee/services/ldap-config/ldap-config-dal";
|
||||
import { ldapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
||||
@@ -383,6 +385,8 @@ export const registerRoutes = async (
|
||||
const projectTemplateDAL = projectTemplateDALFactory(db);
|
||||
const resourceMetadataDAL = resourceMetadataDALFactory(db);
|
||||
const kmipClientDAL = kmipClientDALFactory(db);
|
||||
const kmipInstanceConfigDAL = kmipInstanceConfigDALFactory(db);
|
||||
const kmipInstanceServerCertificateDAL = kmipInstanceServerCertificateDALFactory(db);
|
||||
|
||||
const permissionService = permissionServiceFactory({
|
||||
permissionDAL,
|
||||
@@ -623,7 +627,9 @@ export const registerRoutes = async (
|
||||
orgService,
|
||||
keyStore,
|
||||
licenseService,
|
||||
kmsService
|
||||
kmsService,
|
||||
kmipInstanceConfigDAL,
|
||||
kmipInstanceServerCertificateDAL
|
||||
});
|
||||
|
||||
const orgAdminService = orgAdminServiceFactory({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import ms from "ms";
|
||||
import { z } from "zod";
|
||||
|
||||
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 { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
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 { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||
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 crypto, { KeyObject } from "crypto";
|
||||
import ms from "ms";
|
||||
import z from "zod";
|
||||
|
||||
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 { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
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 { 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 { TKmsRootConfigDALFactory } from "../kms/kms-root-config-dal";
|
||||
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 { UserAliasType } from "../user-alias/user-alias-types";
|
||||
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 = {
|
||||
serverCfgDAL: TSuperAdminDALFactory;
|
||||
@@ -31,6 +46,8 @@ type TSuperAdminServiceFactoryDep = {
|
||||
orgService: Pick<TOrgServiceFactory, "createOrganization">;
|
||||
keyStore: Pick<TKeyStoreFactory, "getItem" | "setItemWithExpiry" | "deleteItem">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures">;
|
||||
kmipInstanceConfigDAL: TKmipInstanceConfigDALFactory;
|
||||
kmipInstanceServerCertificateDAL: TKmipInstanceServerCertificateDALFactory;
|
||||
};
|
||||
|
||||
export type TSuperAdminServiceFactory = ReturnType<typeof superAdminServiceFactory>;
|
||||
@@ -57,7 +74,9 @@ export const superAdminServiceFactory = ({
|
||||
keyStore,
|
||||
kmsRootConfigDAL,
|
||||
kmsService,
|
||||
licenseService
|
||||
licenseService,
|
||||
kmipInstanceConfigDAL,
|
||||
kmipInstanceServerCertificateDAL
|
||||
}: TSuperAdminServiceFactoryDep) => {
|
||||
const initServerCfg = async () => {
|
||||
// TODO(akhilmhdh): bad pattern time less change this later to me itself
|
||||
@@ -369,6 +388,302 @@ export const superAdminServiceFactory = ({
|
||||
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 {
|
||||
initServerCfg,
|
||||
updateServerCfg,
|
||||
@@ -377,6 +692,9 @@ export const superAdminServiceFactory = ({
|
||||
deleteUser,
|
||||
getAdminSlackConfig,
|
||||
updateRootEncryptionStrategy,
|
||||
getConfiguredEncryptionStrategies
|
||||
getConfiguredEncryptionStrategies,
|
||||
setupInstanceKmip,
|
||||
getInstanceKmip,
|
||||
generateInstanceKmipServerCertificate
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { CertKeyAlgorithm } from "../certificate/certificate-types";
|
||||
|
||||
export type TAdminSignUpDTO = {
|
||||
email: string;
|
||||
password: string;
|
||||
@@ -31,3 +33,14 @@ export enum LoginMethod {
|
||||
LDAP = "ldap",
|
||||
OIDC = "oidc"
|
||||
}
|
||||
|
||||
export type TSetupInstanceKmipDTO = {
|
||||
caKeyAlgorithm: CertKeyAlgorithm;
|
||||
};
|
||||
|
||||
export type TGenerateInstanceKmipServerCertificateDTO = {
|
||||
commonName: string;
|
||||
altNames: string;
|
||||
keyAlgorithm: CertKeyAlgorithm;
|
||||
ttl: string;
|
||||
};
|
||||
|
||||
6
frontend/src/helpers/download.ts
Normal file
6
frontend/src/helpers/download.ts
Normal file
@@ -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 {
|
||||
useAdminDeleteUser,
|
||||
useCreateAdminUser,
|
||||
useSetupInstanceKmip,
|
||||
useUpdateAdminSlackConfig,
|
||||
useUpdateServerConfig,
|
||||
useUpdateServerEncryptionStrategy
|
||||
@@ -8,6 +9,7 @@ export {
|
||||
export {
|
||||
useAdminGetUsers,
|
||||
useGetAdminSlackConfig,
|
||||
useGetInstanceKmipConfig,
|
||||
useGetServerConfig,
|
||||
useGetServerRootKmsEncryptionDetails
|
||||
} from "./queries";
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
RootKeyEncryptionStrategy,
|
||||
TCreateAdminUserDTO,
|
||||
TServerConfig,
|
||||
TSetupInstanceKmipDTO,
|
||||
TUpdateAdminSlackConfigDTO
|
||||
} 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 {
|
||||
AdminGetUsersFilters,
|
||||
AdminSlackConfig,
|
||||
InstanceKmipConfig,
|
||||
TGetServerRootKmsEncryptionDetails,
|
||||
TServerConfig
|
||||
} from "./types";
|
||||
@@ -18,7 +19,8 @@ export const adminQueryKeys = {
|
||||
serverConfig: () => ["server-config"] as const,
|
||||
getUsers: (filters: AdminGetUsersFilters) => [adminStandaloneKeys.getUsers, { filters }] 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 () => {
|
||||
@@ -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 {
|
||||
EMAIL = "email",
|
||||
GOOGLE = "google",
|
||||
@@ -62,7 +64,15 @@ export type TGetServerRootKmsEncryptionDetails = {
|
||||
}[];
|
||||
};
|
||||
|
||||
export type InstanceKmipConfig = {
|
||||
serverCertificateChain: string;
|
||||
};
|
||||
|
||||
export enum RootKeyEncryptionStrategy {
|
||||
Software = "SOFTWARE",
|
||||
HSM = "HSM"
|
||||
}
|
||||
|
||||
export type TSetupInstanceKmipDTO = {
|
||||
caKeyAlgorithm: CertKeyAlgorithm;
|
||||
};
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
import { AuthPanel } from "./components/AuthPanel";
|
||||
import { EncryptionPanel } from "./components/EncryptionPanel";
|
||||
import { IntegrationPanel } from "./components/IntegrationPanel";
|
||||
import { KmipPanel } from "./components/KmipPanel";
|
||||
import { RateLimitPanel } from "./components/RateLimitPanel";
|
||||
import { UserPanel } from "./components/UserPanel";
|
||||
|
||||
@@ -40,7 +41,8 @@ enum TabSections {
|
||||
Auth = "auth",
|
||||
RateLimit = "rate-limit",
|
||||
Integrations = "integrations",
|
||||
Users = "users"
|
||||
Users = "users",
|
||||
Kmip = "kmip"
|
||||
}
|
||||
|
||||
enum SignUpModes {
|
||||
@@ -149,6 +151,7 @@ export const OverviewPage = () => {
|
||||
<Tab value={TabSections.RateLimit}>Rate Limit</Tab>
|
||||
<Tab value={TabSections.Integrations}>Integrations</Tab>
|
||||
<Tab value={TabSections.Users}>Users</Tab>
|
||||
<Tab value={TabSections.Kmip}>KMIP</Tab>
|
||||
</div>
|
||||
</TabList>
|
||||
<TabPanel value={TabSections.Settings}>
|
||||
@@ -347,6 +350,9 @@ export const OverviewPage = () => {
|
||||
<TabPanel value={TabSections.Users}>
|
||||
<UserPanel />
|
||||
</TabPanel>
|
||||
<TabPanel value={TabSections.Kmip}>
|
||||
<KmipPanel />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
|
||||
214
frontend/src/pages/admin/OverviewPage/components/KmipPanel.tsx
Normal file
214
frontend/src/pages/admin/OverviewPage/components/KmipPanel.tsx
Normal file
@@ -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"
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
||||
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">
|
||||
{KMIP_PERMISSIONS_OPTIONS.map(({ label, value: optionValue }) => {
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user