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
+8
View File
@@ -16,6 +16,8 @@ import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/extern
import { TGroupServiceFactory } from "@app/ee/services/group/group-service"; import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service"; import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
import { TIdentityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service"; import { TIdentityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
import { TKmipClientDALFactory } from "@app/ee/services/kmip/kmip-client-dal";
import { TKmipOperationServiceFactory } from "@app/ee/services/kmip/kmip-operation-service";
import { TKmipServiceFactory } from "@app/ee/services/kmip/kmip-service"; import { TKmipServiceFactory } from "@app/ee/services/kmip/kmip-service";
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service"; import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -121,6 +123,10 @@ declare module "fastify" {
isUserCompleted: string; isUserCompleted: string;
providerAuthToken: string; providerAuthToken: string;
}; };
kmipUser: {
projectId: string;
clientId: string;
};
auditLogInfo: Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">; auditLogInfo: Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">;
ssoConfig: Awaited<ReturnType<TSamlConfigServiceFactory["getSaml"]>>; ssoConfig: Awaited<ReturnType<TSamlConfigServiceFactory["getSaml"]>>;
ldapConfig: Awaited<ReturnType<TLdapConfigServiceFactory["getLdapCfg"]>>; ldapConfig: Awaited<ReturnType<TLdapConfigServiceFactory["getLdapCfg"]>>;
@@ -214,11 +220,13 @@ declare module "fastify" {
appConnection: TAppConnectionServiceFactory; appConnection: TAppConnectionServiceFactory;
secretSync: TSecretSyncServiceFactory; secretSync: TSecretSyncServiceFactory;
kmip: TKmipServiceFactory; kmip: TKmipServiceFactory;
kmipOperation: TKmipOperationServiceFactory;
}; };
// this is exclusive use for middlewares in which we need to inject data // this is exclusive use for middlewares in which we need to inject data
// everywhere else access using service layer // everywhere else access using service layer
store: { store: {
user: Pick<TUserDALFactory, "findById">; user: Pick<TUserDALFactory, "findById">;
kmipClient: Pick<TKmipClientDALFactory, "findOne">;
}; };
} }
} }
+2
View File
@@ -9,6 +9,7 @@ import { registerDynamicSecretRouter } from "./dynamic-secret-router";
import { registerExternalKmsRouter } from "./external-kms-router"; import { registerExternalKmsRouter } from "./external-kms-router";
import { registerGroupRouter } from "./group-router"; import { registerGroupRouter } from "./group-router";
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router"; import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
import { registerKmipOperationRouter } from "./kmip-operation-router";
import { registerKmipRouter } from "./kmip-router"; import { registerKmipRouter } from "./kmip-router";
import { registerLdapRouter } from "./ldap-router"; import { registerLdapRouter } from "./ldap-router";
import { registerLicenseRouter } from "./license-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(registerProjectTemplateRouter, { prefix: "/project-templates" });
await server.register(registerKmipRouter, { prefix: "/kmip" }); 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 { TKmipClientDALFactory } from "./kmip-client-dal";
import { INSTANCE_KMIP_CONFIG_ID } from "./kmip-constants"; import { INSTANCE_KMIP_CONFIG_ID } from "./kmip-constants";
import { TKmipInstanceConfigDALFactory } from "./kmip-instance-config-dal"; import { TKmipInstanceConfigDALFactory } from "./kmip-instance-config-dal";
import { TKmipInstanceServerCertificateDALFactory } from "./kmip-instance-server-certificate-dal";
import { import {
TCreateKmipClientCertificateDTO, TCreateKmipClientCertificateDTO,
TCreateKmipClientDTO, TCreateKmipClientDTO,
@@ -30,6 +31,7 @@ import {
type TKmipServiceFactoryDep = { type TKmipServiceFactoryDep = {
kmipClientDAL: TKmipClientDALFactory; kmipClientDAL: TKmipClientDALFactory;
kmipClientCertificateDAL: TKmipClientCertificateDALFactory; kmipClientCertificateDAL: TKmipClientCertificateDALFactory;
kmipInstanceServerCertificateDAL: TKmipInstanceServerCertificateDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
kmsService: Pick<TKmsServiceFactory, "decryptWithRootKey">; kmsService: Pick<TKmsServiceFactory, "decryptWithRootKey">;
kmipInstanceConfigDAL: TKmipInstanceConfigDALFactory; kmipInstanceConfigDAL: TKmipInstanceConfigDALFactory;
@@ -42,7 +44,8 @@ export const kmipServiceFactory = ({
permissionService, permissionService,
kmipClientCertificateDAL, kmipClientCertificateDAL,
kmipInstanceConfigDAL, kmipInstanceConfigDAL,
kmsService kmsService,
kmipInstanceServerCertificateDAL
}: TKmipServiceFactoryDep) => { }: TKmipServiceFactoryDep) => {
const createKmipClient = async ({ const createKmipClient = async ({
actor, 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 { return {
createKmipClient, createKmipClient,
updateKmipClient, updateKmipClient,
deleteKmipClient, deleteKmipClient,
getKmipClient, getKmipClient,
listKmipClientsByProjectId, listKmipClientsByProjectId,
createKmipClientCertificate createKmipClientCertificate,
getServerCertificateBySerialNumber
}; };
}; };
@@ -1,3 +1,4 @@
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
import { OrderByDirection, TProjectPermission } from "@app/lib/types"; import { OrderByDirection, TProjectPermission } from "@app/lib/types";
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types"; import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
@@ -41,3 +42,15 @@ export type TListKmipClientsByProjectIdDTO = {
orderDirection?: OrderByDirection; orderDirection?: OrderByDirection;
search?: string; search?: string;
} & TProjectPermission; } & TProjectPermission;
export type TKmipCreateDTO = {
clientId: string;
projectId: string;
encryptionAlgorithm: SymmetricEncryption;
};
export type TKmipGetDTO = {
clientId: string;
projectId: string;
id: string;
};
+14 -3
View File
@@ -39,6 +39,7 @@ import { kmipClientCertificateDALFactory } from "@app/ee/services/kmip/kmip-clie
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 { kmipInstanceConfigDALFactory } from "@app/ee/services/kmip/kmip-instance-config-dal";
import { kmipInstanceServerCertificateDALFactory } from "@app/ee/services/kmip/kmip-instance-server-certificate-dal"; import { kmipInstanceServerCertificateDALFactory } from "@app/ee/services/kmip/kmip-instance-server-certificate-dal";
import { kmipOperationServiceFactory } from "@app/ee/services/kmip/kmip-operation-service";
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";
@@ -1434,7 +1435,15 @@ export const registerRoutes = async (
permissionService, permissionService,
kmipClientCertificateDAL, kmipClientCertificateDAL,
kmipInstanceConfigDAL, kmipInstanceConfigDAL,
kmsService kmsService,
kmipInstanceServerCertificateDAL
});
const kmipOperationService = kmipOperationServiceFactory({
kmsService,
kmsDAL,
projectDAL,
kmipClientDAL
}); });
await superAdminService.initServerCfg(); await superAdminService.initServerCfg();
@@ -1536,7 +1545,8 @@ export const registerRoutes = async (
totp: totpService, totp: totpService,
appConnection: appConnectionService, appConnection: appConnectionService,
secretSync: secretSyncService, secretSync: secretSyncService,
kmip: kmipService kmip: kmipService,
kmipOperation: kmipOperationService
}); });
const cronJobs: CronJob[] = []; const cronJobs: CronJob[] = [];
@@ -1548,7 +1558,8 @@ export const registerRoutes = async (
} }
server.decorate<FastifyZodProvider["store"]>("store", { server.decorate<FastifyZodProvider["store"]>("store", {
user: userDAL user: userDAL,
kmipClient: kmipClientDAL
}); });
await server.register(injectIdentity, { userDAL, serviceTokenDAL }); await server.register(injectIdentity, { userDAL, serviceTokenDAL });
+27 -1
View File
@@ -37,6 +37,7 @@ import {
TEncryptWithKmsDataKeyDTO, TEncryptWithKmsDataKeyDTO,
TEncryptWithKmsDTO, TEncryptWithKmsDTO,
TGenerateKMSDTO, TGenerateKMSDTO,
TGetKeyMaterialDTO,
TUpdateProjectSecretManagerKmsKeyDTO TUpdateProjectSecretManagerKmsKeyDTO
} from "./kms-types"; } from "./kms-types";
@@ -326,6 +327,30 @@ export const kmsServiceFactory = ({
}; };
}; };
const getKeyMaterial = async ({ kmsId }: TGetKeyMaterialDTO) => {
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
if (!kmsDoc) {
throw new NotFoundError({ message: `KMS with ID '${kmsId}' not found` });
}
if (kmsDoc.isReserved) {
throw new BadRequestError({
message: "Cannot get key material for reserved key"
});
}
if (kmsDoc.externalKms) {
throw new BadRequestError({
message: "Cannot get key material for external key"
});
}
const keyCipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
return kmsKey;
};
const encryptWithKmsKey = async ({ kmsId }: Omit<TEncryptWithKmsDTO, "plainText">, tx?: Knex) => { const encryptWithKmsKey = async ({ kmsId }: Omit<TEncryptWithKmsDTO, "plainText">, tx?: Knex) => {
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId, tx); const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId, tx);
if (!kmsDoc) { if (!kmsDoc) {
@@ -967,6 +992,7 @@ export const kmsServiceFactory = ({
getProjectKeyBackup, getProjectKeyBackup,
loadProjectKeyBackup, loadProjectKeyBackup,
getKmsById, getKmsById,
createCipherPairWithDataKey createCipherPairWithDataKey,
getKeyMaterial
}; };
}; };
+3
View File
@@ -61,3 +61,6 @@ export enum RootKeyEncryptionStrategy {
Software = "SOFTWARE", Software = "SOFTWARE",
HSM = "HSM" HSM = "HSM"
} }
export type TGetKeyMaterialDTO = {
kmsId: string;
};