feat: kmip create and get

This commit is contained in:
Sheen Capadngan
2025-02-11 23:32:11 +08:00
parent 77fac45df1
commit 292c9051bd
9 changed files with 320 additions and 6 deletions
+2
View File
@@ -9,6 +9,7 @@ import { registerDynamicSecretRouter } from "./dynamic-secret-router";
import { registerExternalKmsRouter } from "./external-kms-router";
import { registerGroupRouter } from "./group-router";
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
import { registerKmipOperationRouter } from "./kmip-operation-router";
import { registerKmipRouter } from "./kmip-router";
import { registerLdapRouter } from "./ldap-router";
import { registerLicenseRouter } from "./license-router";
@@ -112,4 +113,5 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
await server.register(registerProjectTemplateRouter, { prefix: "/project-templates" });
await server.register(registerKmipRouter, { prefix: "/kmip" });
await server.register(registerKmipOperationRouter, { prefix: "/kmip-operations" });
};
@@ -0,0 +1,117 @@
import crypto from "crypto";
import jwt, { JwtPayload } from "jsonwebtoken";
import z from "zod";
import { KmsKeysSchema } from "@app/db/schemas";
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { writeLimit } from "@app/server/config/rateLimiter";
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
export const registerKmipOperationRouter = async (server: FastifyZodProvider) => {
server.decorateRequest("kmipUser", null);
server.addHook("preHandler", async (req) => {
const token = req.headers["x-kmip-jwt"] as string;
const serverCertSerialNumber = req.headers["x-server-certificate-serial-number"] as string;
if (!jwt) {
throw new ForbiddenRequestError({
message: "Missing KMIP JWT"
});
}
if (!serverCertSerialNumber) {
throw new ForbiddenRequestError({
message: "Missing server certificate serial number from request"
});
}
const serverCert = await server.services.kmip.getServerCertificateBySerialNumber(serverCertSerialNumber);
// TODO: assert that server certificate used is not revoked
// TODO: assert that client certificate used is not revoked
const publicKey = crypto.createPublicKey({
key: serverCert.publicKey,
format: "pem",
type: [CertKeyAlgorithm.ECDSA_P256, CertKeyAlgorithm.ECDSA_P384].includes(serverCert.keyAlgorithm)
? "spki"
: "pkcs1"
});
const decodedToken = jwt.verify(token, publicKey) as JwtPayload & { projectId: string; clientId: string };
const kmipClient = await server.store.kmipClient.findOne({
id: decodedToken.clientId,
projectId: decodedToken.projectId
});
if (!kmipClient) {
throw new NotFoundError({
message: "KMIP client cannot be found."
});
}
req.kmipUser = {
projectId: decodedToken.projectId,
clientId: decodedToken.clientId
};
});
server.route({
method: "POST",
url: "/create",
config: {
rateLimit: writeLimit
},
schema: {
description: "KMIP endpoint for creating managed objects",
body: z.object({
encryptionAlgorithm: z.nativeEnum(SymmetricEncryption)
}),
response: {
200: KmsKeysSchema
}
},
handler: async (req) => {
const object = await server.services.kmipOperation.create({
projectId: req.kmipUser.projectId,
clientId: req.kmipUser.clientId,
encryptionAlgorithm: req.body.encryptionAlgorithm
});
return object;
}
});
server.route({
method: "POST",
url: "/get",
config: {
rateLimit: writeLimit
},
schema: {
description: "KMIP endpoint for getting managed objects",
body: z.object({
id: z.string()
}),
response: {
200: z.object({
id: z.string(),
value: z.string(),
algorithm: z.string()
})
}
},
handler: async (req) => {
const object = await server.services.kmipOperation.get({
projectId: req.kmipUser.projectId,
clientId: req.kmipUser.clientId,
id: req.body.id
});
return object;
}
});
};
@@ -0,0 +1,110 @@
import { ProjectType } from "@app/db/schemas";
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 { TKmipClientDALFactory } from "./kmip-client-dal";
import { KmipPermission } from "./kmip-enum";
import { TKmipCreateDTO, TKmipGetDTO } from "./kmip-types";
type TKmipOperationServiceFactoryDep = {
kmsService: TKmsServiceFactory;
kmsDAL: TKmsKeyDALFactory;
kmipClientDAL: TKmipClientDALFactory;
projectDAL: Pick<TProjectDALFactory, "getProjectFromSplitId" | "findById">;
};
export type TKmipOperationServiceFactory = ReturnType<typeof kmipOperationServiceFactory>;
export const kmipOperationServiceFactory = ({
kmsService,
kmsDAL,
projectDAL,
kmipClientDAL
}: TKmipOperationServiceFactoryDep) => {
const create = async ({ projectId: preSplitProjectId, clientId, encryptionAlgorithm }: TKmipCreateDTO) => {
let projectId = preSplitProjectId;
const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(projectId, ProjectType.KMS);
if (cmekProjectFromSplit) {
projectId = cmekProjectFromSplit.id;
}
const project = await projectDAL.findById(projectId);
const kmipClient = await kmipClientDAL.findOne({
id: clientId,
projectId
});
if (!kmipClient.permissions?.includes(KmipPermission.Create)) {
throw new ForbiddenRequestError({
message: "Client does not have sufficient permission to perform KMIP create"
});
}
const kmsKey = await kmsService.generateKmsKey({
encryptionAlgorithm,
orgId: project.orgId,
projectId,
isReserved: false
});
return kmsKey;
};
const get = async ({ projectId: preSplitProjectId, id, clientId }: TKmipGetDTO) => {
let projectId = preSplitProjectId;
const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(projectId, ProjectType.KMS);
if (cmekProjectFromSplit) {
projectId = cmekProjectFromSplit.id;
}
const kmipClient = await kmipClientDAL.findOne({
id: clientId,
projectId
});
if (!kmipClient.permissions?.includes(KmipPermission.Get)) {
throw new ForbiddenRequestError({
message: "Client does not have sufficient permission to perform KMIP get"
});
}
const key = await kmsDAL.findOne({
id,
projectId
});
if (!key) {
throw new NotFoundError({ message: `Key with ID ${id} not found` });
}
if (key.isReserved) {
throw new BadRequestError({ message: "Cannot get reserved keys" });
}
const completeKeyDetails = await kmsDAL.findByIdWithAssociatedKms(id);
if (!completeKeyDetails.internalKms) {
throw new BadRequestError({
message: "Cannot get external key"
});
}
const kmsKey = await kmsService.getKeyMaterial({
kmsId: key.id
});
return {
id: key.id,
value: kmsKey.toString("base64"),
algorithm: completeKeyDetails.internalKms.encryptionAlgorithm
};
};
return {
create,
get
};
};
+26 -2
View File
@@ -18,6 +18,7 @@ import { TKmipClientCertificateDALFactory } from "./kmip-client-certificate-dal"
import { TKmipClientDALFactory } from "./kmip-client-dal";
import { INSTANCE_KMIP_CONFIG_ID } from "./kmip-constants";
import { TKmipInstanceConfigDALFactory } from "./kmip-instance-config-dal";
import { TKmipInstanceServerCertificateDALFactory } from "./kmip-instance-server-certificate-dal";
import {
TCreateKmipClientCertificateDTO,
TCreateKmipClientDTO,
@@ -30,6 +31,7 @@ import {
type TKmipServiceFactoryDep = {
kmipClientDAL: TKmipClientDALFactory;
kmipClientCertificateDAL: TKmipClientCertificateDALFactory;
kmipInstanceServerCertificateDAL: TKmipInstanceServerCertificateDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
kmsService: Pick<TKmsServiceFactory, "decryptWithRootKey">;
kmipInstanceConfigDAL: TKmipInstanceConfigDALFactory;
@@ -42,7 +44,8 @@ export const kmipServiceFactory = ({
permissionService,
kmipClientCertificateDAL,
kmipInstanceConfigDAL,
kmsService
kmsService,
kmipInstanceServerCertificateDAL
}: TKmipServiceFactoryDep) => {
const createKmipClient = async ({
actor,
@@ -325,12 +328,33 @@ export const kmipServiceFactory = ({
};
};
const getServerCertificateBySerialNumber = async (serialNumber: string) => {
const serverCert = await kmipInstanceServerCertificateDAL.findOne({
serialNumber
});
if (!serverCert) {
throw new NotFoundError({
message: "Server certificate not found"
});
}
const decryptWithRootKey = kmsService.decryptWithRootKey();
const parsedCertificate = new x509.X509Certificate(decryptWithRootKey(serverCert.encryptedCertificate));
return {
publicKey: parsedCertificate.publicKey.toString("pem"),
keyAlgorithm: serverCert.keyAlgorithm as CertKeyAlgorithm
};
};
return {
createKmipClient,
updateKmipClient,
deleteKmipClient,
getKmipClient,
listKmipClientsByProjectId,
createKmipClientCertificate
createKmipClientCertificate,
getServerCertificateBySerialNumber
};
};
@@ -1,3 +1,4 @@
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
import { OrderByDirection, TProjectPermission } from "@app/lib/types";
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
@@ -41,3 +42,15 @@ export type TListKmipClientsByProjectIdDTO = {
orderDirection?: OrderByDirection;
search?: string;
} & TProjectPermission;
export type TKmipCreateDTO = {
clientId: string;
projectId: string;
encryptionAlgorithm: SymmetricEncryption;
};
export type TKmipGetDTO = {
clientId: string;
projectId: string;
id: string;
};