mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat: kmip create and get
This commit is contained in:
8
backend/src/@types/fastify.d.ts
vendored
8
backend/src/@types/fastify.d.ts
vendored
@@ -16,6 +16,8 @@ import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/extern
|
||||
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 { 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 { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
@@ -121,6 +123,10 @@ declare module "fastify" {
|
||||
isUserCompleted: string;
|
||||
providerAuthToken: string;
|
||||
};
|
||||
kmipUser: {
|
||||
projectId: string;
|
||||
clientId: string;
|
||||
};
|
||||
auditLogInfo: Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">;
|
||||
ssoConfig: Awaited<ReturnType<TSamlConfigServiceFactory["getSaml"]>>;
|
||||
ldapConfig: Awaited<ReturnType<TLdapConfigServiceFactory["getLdapCfg"]>>;
|
||||
@@ -214,11 +220,13 @@ declare module "fastify" {
|
||||
appConnection: TAppConnectionServiceFactory;
|
||||
secretSync: TSecretSyncServiceFactory;
|
||||
kmip: TKmipServiceFactory;
|
||||
kmipOperation: TKmipOperationServiceFactory;
|
||||
};
|
||||
// this is exclusive use for middlewares in which we need to inject data
|
||||
// everywhere else access using service layer
|
||||
store: {
|
||||
user: Pick<TUserDALFactory, "findById">;
|
||||
kmipClient: Pick<TKmipClientDALFactory, "findOne">;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" });
|
||||
};
|
||||
|
||||
117
backend/src/ee/routes/v1/kmip-operation-router.ts
Normal file
117
backend/src/ee/routes/v1/kmip-operation-router.ts
Normal file
@@ -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;
|
||||
}
|
||||
});
|
||||
};
|
||||
110
backend/src/ee/services/kmip/kmip-operation-service.ts
Normal file
110
backend/src/ee/services/kmip/kmip-operation-service.ts
Normal file
@@ -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
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -39,6 +39,7 @@ import { kmipClientCertificateDALFactory } from "@app/ee/services/kmip/kmip-clie
|
||||
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 { kmipOperationServiceFactory } from "@app/ee/services/kmip/kmip-operation-service";
|
||||
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";
|
||||
@@ -1434,7 +1435,15 @@ export const registerRoutes = async (
|
||||
permissionService,
|
||||
kmipClientCertificateDAL,
|
||||
kmipInstanceConfigDAL,
|
||||
kmsService
|
||||
kmsService,
|
||||
kmipInstanceServerCertificateDAL
|
||||
});
|
||||
|
||||
const kmipOperationService = kmipOperationServiceFactory({
|
||||
kmsService,
|
||||
kmsDAL,
|
||||
projectDAL,
|
||||
kmipClientDAL
|
||||
});
|
||||
|
||||
await superAdminService.initServerCfg();
|
||||
@@ -1536,7 +1545,8 @@ export const registerRoutes = async (
|
||||
totp: totpService,
|
||||
appConnection: appConnectionService,
|
||||
secretSync: secretSyncService,
|
||||
kmip: kmipService
|
||||
kmip: kmipService,
|
||||
kmipOperation: kmipOperationService
|
||||
});
|
||||
|
||||
const cronJobs: CronJob[] = [];
|
||||
@@ -1548,7 +1558,8 @@ export const registerRoutes = async (
|
||||
}
|
||||
|
||||
server.decorate<FastifyZodProvider["store"]>("store", {
|
||||
user: userDAL
|
||||
user: userDAL,
|
||||
kmipClient: kmipClientDAL
|
||||
});
|
||||
|
||||
await server.register(injectIdentity, { userDAL, serviceTokenDAL });
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
TEncryptWithKmsDataKeyDTO,
|
||||
TEncryptWithKmsDTO,
|
||||
TGenerateKMSDTO,
|
||||
TGetKeyMaterialDTO,
|
||||
TUpdateProjectSecretManagerKmsKeyDTO
|
||||
} 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 kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId, tx);
|
||||
if (!kmsDoc) {
|
||||
@@ -967,6 +992,7 @@ export const kmsServiceFactory = ({
|
||||
getProjectKeyBackup,
|
||||
loadProjectKeyBackup,
|
||||
getKmsById,
|
||||
createCipherPairWithDataKey
|
||||
createCipherPairWithDataKey,
|
||||
getKeyMaterial
|
||||
};
|
||||
};
|
||||
|
||||
@@ -61,3 +61,6 @@ export enum RootKeyEncryptionStrategy {
|
||||
Software = "SOFTWARE",
|
||||
HSM = "HSM"
|
||||
}
|
||||
export type TGetKeyMaterialDTO = {
|
||||
kmsId: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user