misc: finalize KMIP management

This commit is contained in:
Sheen Capadngan
2025-02-18 02:22:59 +08:00
parent 603b740bbe
commit 943c2b0e69
20 changed files with 490 additions and 317 deletions
@@ -267,6 +267,11 @@ export enum EventType {
GET_KMIP_CLIENT = "get-kmip-client",
GET_KMIP_CLIENTS = "get-kmip-clients",
CREATE_KMIP_CLIENT_CERTIFICATE = "create-kmip-client-certificate",
SETUP_KMIP = "setup-kmip",
GET_KMIP = "get-kmip",
REGISTER_KMIP_SERVER = "register-kmip-server",
KMIP_OPERATION_CREATE = "kmip-operation-create",
KMIP_OPERATION_GET = "kmip-operation-get",
KMIP_OPERATION_DELETE = "kmip-operation-delete",
@@ -2207,6 +2212,31 @@ interface KmipOperationRegisterEvent {
};
}
interface SetupKmipEvent {
type: EventType.SETUP_KMIP;
metadata: {
keyAlgorithm: CertKeyAlgorithm;
};
}
interface GetKmipEvent {
type: EventType.GET_KMIP;
metadata: {
id: string;
};
}
interface RegisterKmipServerEvent {
type: EventType.REGISTER_KMIP_SERVER;
metadata: {
serverCertificateSerialNumber: string;
hostnamesOrIps: string;
commonName: string;
keyAlgorithm: CertKeyAlgorithm;
ttl: string;
};
}
export type Event =
| GetSecretsEvent
| GetSecretEvent
@@ -2404,6 +2434,9 @@ export type Event =
| GetKmipClientEvent
| GetKmipClientsEvent
| CreateKmipClientCertificateEvent
| SetupKmipEvent
| GetKmipEvent
| RegisterKmipServerEvent
| KmipOperationGetEvent
| KmipOperationDeleteEvent
| KmipOperationCreateEvent
@@ -13,6 +13,26 @@ export type TKmipClientDALFactory = ReturnType<typeof kmipClientDALFactory>;
export const kmipClientDALFactory = (db: TDbClient) => {
const kmipClientOrm = ormify(db, TableName.KmipClient);
const findByProjectAndClientId = async (projectId: string, clientId: string) => {
try {
const client = await db
.replicaNode()(TableName.KmipClient)
.join(TableName.Project, `${TableName.Project}.id`, `${TableName.KmipClient}.projectId`)
.join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.Project}.orgId`)
.where({
[`${TableName.KmipClient}.projectId` as "projectId"]: projectId,
[`${TableName.KmipClient}.id` as "id"]: clientId
})
.select(selectAllTableCols(TableName.KmipClient))
.select(db.ref("id").withSchema(TableName.Organization).as("orgId"))
.first();
return client;
} catch (error) {
throw new DatabaseError({ error, name: "Find by project and client ID" });
}
};
const findByProjectId = async (
{
projectId,
@@ -60,6 +80,7 @@ export const kmipClientDALFactory = (db: TDbClient) => {
return {
...kmipClientOrm,
findByProjectId
findByProjectId,
findByProjectAndClientId
};
};
@@ -1,9 +1,12 @@
import { ProjectType } from "@app/db/schemas";
import { ForbiddenError } from "@casl/ability";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { OrgPermissionKmipActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TKmipClientDALFactory } from "./kmip-client-dal";
import { KmipPermission } from "./kmip-enum";
import {
@@ -21,6 +24,7 @@ type TKmipOperationServiceFactoryDep = {
kmsDAL: TKmsKeyDALFactory;
kmipClientDAL: TKmipClientDALFactory;
projectDAL: Pick<TProjectDALFactory, "getProjectFromSplitId" | "findById">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
};
export type TKmipOperationServiceFactory = ReturnType<typeof kmipOperationServiceFactory>;
@@ -29,16 +33,28 @@ export const kmipOperationServiceFactory = ({
kmsService,
kmsDAL,
projectDAL,
kmipClientDAL
kmipClientDAL,
permissionService
}: TKmipOperationServiceFactoryDep) => {
const create = async ({ projectId: preSplitProjectId, clientId, algorithm }: TKmipCreateDTO) => {
let projectId = preSplitProjectId;
const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(projectId, ProjectType.KMS);
if (cmekProjectFromSplit) {
projectId = cmekProjectFromSplit.id;
}
const create = async ({
projectId,
clientId,
algorithm,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TKmipCreateDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
const project = await projectDAL.findById(projectId);
const kmipClient = await kmipClientDAL.findOne({
id: clientId,
projectId
@@ -52,7 +68,7 @@ export const kmipOperationServiceFactory = ({
const kmsKey = await kmsService.generateKmsKey({
encryptionAlgorithm: algorithm,
orgId: project.orgId,
orgId: actorOrgId,
projectId,
isReserved: false
});
@@ -60,13 +76,16 @@ export const kmipOperationServiceFactory = ({
return kmsKey;
};
const deleteOp = async ({ projectId: preSplitProjectId, id, clientId }: TKmipDeleteDTO) => {
let projectId = preSplitProjectId;
const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(projectId, ProjectType.KMS);
const deleteOp = async ({ projectId, id, clientId, actor, actorId, actorOrgId, actorAuthMethod }: TKmipDeleteDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
if (cmekProjectFromSplit) {
projectId = cmekProjectFromSplit.id;
}
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
const kmipClient = await kmipClientDAL.findOne({
id: clientId,
@@ -104,13 +123,16 @@ export const kmipOperationServiceFactory = ({
return kms;
};
const get = async ({ projectId: preSplitProjectId, id, clientId }: TKmipGetDTO) => {
let projectId = preSplitProjectId;
const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(projectId, ProjectType.KMS);
const get = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipGetDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
if (cmekProjectFromSplit) {
projectId = cmekProjectFromSplit.id;
}
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
const kmipClient = await kmipClientDAL.findOne({
id: clientId,
@@ -158,13 +180,16 @@ export const kmipOperationServiceFactory = ({
};
};
const activate = async ({ projectId: preSplitProjectId, id, clientId }: TKmipGetDTO) => {
let projectId = preSplitProjectId;
const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(projectId, ProjectType.KMS);
const activate = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipGetDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
if (cmekProjectFromSplit) {
projectId = cmekProjectFromSplit.id;
}
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
const kmipClient = await kmipClientDAL.findOne({
id: clientId,
@@ -192,13 +217,16 @@ export const kmipOperationServiceFactory = ({
};
};
const revoke = async ({ projectId: preSplitProjectId, id, clientId }: TKmipRevokeDTO) => {
let projectId = preSplitProjectId;
const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(projectId, ProjectType.KMS);
const revoke = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipRevokeDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
if (cmekProjectFromSplit) {
projectId = cmekProjectFromSplit.id;
}
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
const kmipClient = await kmipClientDAL.findOne({
id: clientId,
@@ -242,13 +270,24 @@ export const kmipOperationServiceFactory = ({
};
};
const getAttributes = async ({ projectId: preSplitProjectId, id, clientId }: TKmipGetAttributesDTO) => {
let projectId = preSplitProjectId;
const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(projectId, ProjectType.KMS);
const getAttributes = async ({
projectId,
id,
clientId,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TKmipGetAttributesDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
if (cmekProjectFromSplit) {
projectId = cmekProjectFromSplit.id;
}
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
const kmipClient = await kmipClientDAL.findOne({
id: clientId,
@@ -291,13 +330,16 @@ export const kmipOperationServiceFactory = ({
};
};
const locate = async ({ projectId: preSplitProjectId, clientId }: TKmipLocateDTO) => {
let projectId = preSplitProjectId;
const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(projectId, ProjectType.KMS);
const locate = async ({ projectId, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipLocateDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
if (cmekProjectFromSplit) {
projectId = cmekProjectFromSplit.id;
}
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
const kmipClient = await kmipClientDAL.findOne({
id: clientId,
@@ -315,13 +357,26 @@ export const kmipOperationServiceFactory = ({
return keys;
};
const register = async ({ projectId: preSplitProjectId, clientId, key, algorithm, name }: TKmipRegisterDTO) => {
let projectId = preSplitProjectId;
const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(projectId, ProjectType.KMS);
const register = async ({
projectId,
clientId,
key,
algorithm,
name,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TKmipRegisterDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
if (cmekProjectFromSplit) {
projectId = cmekProjectFromSplit.id;
}
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
const kmipClient = await kmipClientDAL.findOne({
id: clientId,
+137 -19
View File
@@ -15,6 +15,7 @@ import { hostnameRegex } from "@app/services/certificate-authority/certificate-a
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionKmipActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { ProjectPermissionKmipActions, ProjectPermissionSub } from "../permission/project-permission";
@@ -30,6 +31,7 @@ import {
TGetKmipClientDTO,
TGetOrgKmipDTO,
TListKmipClientsByProjectIdDTO,
TRegisterServerDTO,
TSetupOrgKmipDTO,
TUpdateKmipClientDTO
} from "./kmip-types";
@@ -41,6 +43,7 @@ type TKmipServiceFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
kmipOrgConfigDAL: TKmipOrgConfigDALFactory;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
};
export type TKmipServiceFactory = ReturnType<typeof kmipServiceFactory>;
@@ -51,7 +54,8 @@ export const kmipServiceFactory = ({
kmipClientCertificateDAL,
kmipOrgConfigDAL,
kmsService,
kmipOrgServerCertificateDAL
kmipOrgServerCertificateDAL,
licenseService
}: TKmipServiceFactoryDep) => {
const createKmipClient = async ({
actor,
@@ -77,6 +81,12 @@ export const kmipServiceFactory = ({
ProjectPermissionSub.Kmip
);
const plan = await licenseService.getPlan(actorOrgId);
if (!plan.kmip)
throw new BadRequestError({
message: "Failed to create KMIP client. Upgrade your plan to enterprise."
});
const kmipClient = await kmipClientDAL.create({
projectId,
name,
@@ -105,6 +115,12 @@ export const kmipServiceFactory = ({
});
}
const plan = await licenseService.getPlan(actorOrgId);
if (!plan.kmip)
throw new BadRequestError({
message: "Failed to update KMIP client. Upgrade your plan to enterprise."
});
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
@@ -151,6 +167,12 @@ export const kmipServiceFactory = ({
ProjectPermissionSub.Kmip
);
const plan = await licenseService.getPlan(actorOrgId);
if (!plan.kmip)
throw new BadRequestError({
message: "Failed to delete KMIP client. Upgrade your plan to enterprise."
});
const deletedKmipClient = await kmipClientDAL.deleteById(id);
return deletedKmipClient;
@@ -218,6 +240,12 @@ export const kmipServiceFactory = ({
});
}
const plan = await licenseService.getPlan(actorOrgId);
if (!plan.kmip)
throw new BadRequestError({
message: "Failed to create KMIP client. Upgrade your plan to enterprise."
});
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
@@ -340,6 +368,31 @@ export const kmipServiceFactory = ({
};
};
const getServerCertificateBySerialNumber = async (orgId: string, serialNumber: string) => {
const serverCert = await kmipOrgServerCertificateDAL.findOne({
serialNumber,
orgId
});
if (!serverCert) {
throw new NotFoundError({
message: "Server certificate not found"
});
}
const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId
});
const parsedCertificate = new x509.X509Certificate(decryptor({ cipherTextBlob: serverCert.encryptedCertificate }));
return {
publicKey: parsedCertificate.publicKey.toString("pem"),
keyAlgorithm: serverCert.keyAlgorithm as CertKeyAlgorithm
};
};
const setupOrgKmip = async ({ caKeyAlgorithm, actorOrgId, actor, actorId, actorAuthMethod }: TSetupOrgKmipDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
@@ -360,6 +413,12 @@ export const kmipServiceFactory = ({
});
}
const plan = await licenseService.getPlan(actorOrgId);
if (!plan.kmip)
throw new BadRequestError({
message: "Failed to setup KMIP. Upgrade your plan to enterprise."
});
const alg = keyAlgorithmToAlgCfg(caKeyAlgorithm);
// generate root CA
@@ -499,7 +558,9 @@ export const kmipServiceFactory = ({
};
};
const getOrgKmip = async ({ actorOrgId }: TGetOrgKmipDTO) => {
const getOrgKmip = async ({ actorOrgId, actor, actorId, actorAuthMethod }: TGetOrgKmipDTO) => {
await permissionService.getOrgPermission(actor, actorId, actorOrgId, actorAuthMethod, actorOrgId);
const kmipConfig = await kmipOrgConfigDAL.findOne({
orgId: actorOrgId
});
@@ -525,32 +586,21 @@ export const kmipServiceFactory = ({
);
return {
id: kmipConfig.id,
serverCertificateChain: `${serverIntermediateCaCert.toString("pem")}\n${rootCaCert.toString("pem")}`.trim(),
clientCertificateChain: `${clientIntermediateCaCert.toString("pem")}\n${rootCaCert.toString("pem")}`.trim()
};
};
const generateOrgKmipServerCertificate = async ({
actorOrgId,
actor,
actorId,
actorAuthMethod,
orgId,
ttl,
commonName,
altNames,
keyAlgorithm
}: TGenerateOrgKmipServerCertificateDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Setup, OrgPermissionSubjects.Kmip);
const kmipOrgConfig = await kmipOrgConfigDAL.findOne({
orgId: actorOrgId
orgId
});
if (!kmipOrgConfig) {
@@ -559,9 +609,15 @@ export const kmipServiceFactory = ({
});
}
const plan = await licenseService.getPlan(orgId);
if (!plan.kmip)
throw new BadRequestError({
message: "Failed to generate KMIP server certificate. Upgrade your plan to enterprise."
});
const { decryptor, encryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: actorOrgId
orgId
});
const caCertObj = new x509.X509Certificate(
@@ -668,7 +724,7 @@ export const kmipServiceFactory = ({
const certificateChain = `${caCertObj.toString("pem")}\n${decryptedCaCertChain}`.trim();
await kmipOrgServerCertificateDAL.create({
orgId: actorOrgId,
orgId,
keyAlgorithm,
issuedAt: notBeforeDate,
expiration: notAfterDate,
@@ -687,6 +743,66 @@ export const kmipServiceFactory = ({
};
};
const registerServer = async ({
actorOrgId,
actor,
actorId,
actorAuthMethod,
ttl,
commonName,
keyAlgorithm,
hostnamesOrIps
}: TRegisterServerDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
const kmipConfig = await kmipOrgConfigDAL.findOne({
orgId: actorOrgId
});
if (!kmipConfig) {
throw new BadRequestError({
message: "KMIP has not been configured for the organization"
});
}
const plan = await licenseService.getPlan(actorOrgId);
if (!plan.kmip)
throw new BadRequestError({
message: "Failed to register KMIP server. Upgrade your plan to enterprise."
});
const { privateKey, certificate, certificateChain, serialNumber } = await generateOrgKmipServerCertificate({
orgId: actorOrgId,
commonName: commonName ?? "kmip-server",
altNames: hostnamesOrIps,
keyAlgorithm: keyAlgorithm ?? (kmipConfig.caKeyAlgorithm as CertKeyAlgorithm),
ttl
});
const { clientCertificateChain } = await getOrgKmip({
actor,
actorAuthMethod,
actorId,
actorOrgId
});
return {
serverCertificateSerialNumber: serialNumber,
clientCertificateChain,
privateKey,
certificate,
certificateChain
};
};
return {
createKmipClient,
updateKmipClient,
@@ -696,6 +812,8 @@ export const kmipServiceFactory = ({
createKmipClientCertificate,
setupOrgKmip,
generateOrgKmipServerCertificate,
getOrgKmip
getOrgKmip,
getServerCertificateBySerialNumber,
registerServer
};
};
+9 -1
View File
@@ -46,7 +46,7 @@ export type TListKmipClientsByProjectIdDTO = {
type KmipOperationBaseDTO = {
clientId: string;
projectId: string;
};
} & Omit<TOrgPermission, "orgId">;
export type TKmipCreateDTO = {
algorithm: SymmetricEncryption;
@@ -91,4 +91,12 @@ export type TGenerateOrgKmipServerCertificateDTO = {
altNames: string;
keyAlgorithm: CertKeyAlgorithm;
ttl: string;
orgId: string;
};
export type TRegisterServerDTO = {
hostnamesOrIps: string;
commonName?: string;
keyAlgorithm?: CertKeyAlgorithm;
ttl: string;
} & Omit<TOrgPermission, "orgId">;
@@ -50,7 +50,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
},
pkiEst: false,
enforceMfa: false,
projectTemplates: false
projectTemplates: false,
kmip: false
});
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
@@ -68,6 +68,7 @@ export type TFeatureSet = {
pkiEst: boolean;
enforceMfa: boolean;
projectTemplates: false;
kmip: false;
};
export type TOrgPlansTableDTO = {
@@ -268,6 +268,9 @@ const buildAdminPermission = () => {
can(OrgPermissionKmipActions.Setup, OrgPermissionSubjects.Kmip);
// the proxy assignment is temporary in order to prevent "more privilege" error during role assignment to MI
can(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
return rules;
};