mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 21:26:17 +00:00
feat: finished up client cert generation
This commit is contained in:
Vendored
+8
@@ -143,6 +143,9 @@ import {
|
|||||||
TInternalKms,
|
TInternalKms,
|
||||||
TInternalKmsInsert,
|
TInternalKmsInsert,
|
||||||
TInternalKmsUpdate,
|
TInternalKmsUpdate,
|
||||||
|
TKmipClientCertificates,
|
||||||
|
TKmipClientCertificatesInsert,
|
||||||
|
TKmipClientCertificatesUpdate,
|
||||||
TKmipClients,
|
TKmipClients,
|
||||||
TKmipClientsInsert,
|
TKmipClientsInsert,
|
||||||
TKmipClientsUpdate,
|
TKmipClientsUpdate,
|
||||||
@@ -922,5 +925,10 @@ declare module "knex/types/tables" {
|
|||||||
TKmipInstanceServerCertificatesInsert,
|
TKmipInstanceServerCertificatesInsert,
|
||||||
TKmipInstanceServerCertificatesUpdate
|
TKmipInstanceServerCertificatesUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.KmipClientCertificates]: KnexOriginal.CompositeTableType<
|
||||||
|
TKmipClientCertificates,
|
||||||
|
TKmipClientCertificatesInsert,
|
||||||
|
TKmipClientCertificatesUpdate
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,19 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.binary("encryptedChain").notNullable();
|
t.binary("encryptedChain").notNullable();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasKmipClientCertTable = await knex.schema.hasTable(TableName.KmipClientCertificates);
|
||||||
|
if (!hasKmipClientCertTable) {
|
||||||
|
await knex.schema.createTable(TableName.KmipClientCertificates, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.uuid("kmipClientId").notNullable();
|
||||||
|
t.foreign("kmipClientId").references("id").inTable(TableName.KmipClient).onDelete("CASCADE");
|
||||||
|
t.string("serialNumber").notNullable();
|
||||||
|
t.string("keyAlgorithm").notNullable();
|
||||||
|
t.datetime("issuedAt").notNullable();
|
||||||
|
t.datetime("expiration").notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
@@ -81,4 +94,9 @@ export async function down(knex: Knex): Promise<void> {
|
|||||||
if (hasKmipInstanceServerCertTable) {
|
if (hasKmipInstanceServerCertTable) {
|
||||||
await knex.schema.dropTable(TableName.KmipInstanceServerCertificates);
|
await knex.schema.dropTable(TableName.KmipInstanceServerCertificates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasKmipClientCertTable = await knex.schema.hasTable(TableName.KmipClientCertificates);
|
||||||
|
if (hasKmipClientCertTable) {
|
||||||
|
await knex.schema.dropTable(TableName.KmipClientCertificates);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export * from "./incident-contacts";
|
|||||||
export * from "./integration-auths";
|
export * from "./integration-auths";
|
||||||
export * from "./integrations";
|
export * from "./integrations";
|
||||||
export * from "./internal-kms";
|
export * from "./internal-kms";
|
||||||
|
export * from "./kmip-client-certificates";
|
||||||
export * from "./kmip-clients";
|
export * from "./kmip-clients";
|
||||||
export * from "./kmip-instance-configs";
|
export * from "./kmip-instance-configs";
|
||||||
export * from "./kmip-instance-server-certificates";
|
export * from "./kmip-instance-server-certificates";
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// 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 { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const KmipClientCertificatesSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
kmipClientId: z.string().uuid(),
|
||||||
|
serialNumber: z.string(),
|
||||||
|
keyAlgorithm: z.string(),
|
||||||
|
issuedAt: z.date(),
|
||||||
|
expiration: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TKmipClientCertificates = z.infer<typeof KmipClientCertificatesSchema>;
|
||||||
|
export type TKmipClientCertificatesInsert = Omit<z.input<typeof KmipClientCertificatesSchema>, TImmutableDBKeys>;
|
||||||
|
export type TKmipClientCertificatesUpdate = Partial<
|
||||||
|
Omit<z.input<typeof KmipClientCertificatesSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
||||||
@@ -135,7 +135,8 @@ export enum TableName {
|
|||||||
SecretSync = "secret_syncs",
|
SecretSync = "secret_syncs",
|
||||||
KmipClient = "kmip_clients",
|
KmipClient = "kmip_clients",
|
||||||
KmipInstanceConfig = "kmip_instance_configs",
|
KmipInstanceConfig = "kmip_instance_configs",
|
||||||
KmipInstanceServerCertificates = "kmip_instance_server_certificates"
|
KmipInstanceServerCertificates = "kmip_instance_server_certificates",
|
||||||
|
KmipClientCertificates = "kmip_client_certificates"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { KmipClientsSchema } from "@app/db/schemas";
|
import { KmipClientsSchema } from "@app/db/schemas";
|
||||||
@@ -8,6 +9,7 @@ import { OrderByDirection } from "@app/lib/types";
|
|||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
||||||
|
|
||||||
const KmipClientResponseSchema = KmipClientsSchema.pick({
|
const KmipClientResponseSchema = KmipClientsSchema.pick({
|
||||||
projectId: true,
|
projectId: true,
|
||||||
@@ -230,4 +232,57 @@ export const registerKmipRouter = async (server: FastifyZodProvider) => {
|
|||||||
return { kmipClients, totalCount };
|
return { kmipClients, totalCount };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/clients/:id/certificates",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
keyAlgorithm: z.nativeEnum(CertKeyAlgorithm),
|
||||||
|
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
serialNumber: z.string(),
|
||||||
|
certificateChain: z.string(),
|
||||||
|
certificate: z.string(),
|
||||||
|
privateKey: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const certificate = await server.services.kmip.createKmipClientCertificate({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
clientId: req.params.id,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
projectId: certificate.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.CREATE_KMIP_CLIENT_CERTIFICATE,
|
||||||
|
metadata: {
|
||||||
|
clientId: req.params.id,
|
||||||
|
serialNumber: certificate.serialNumber,
|
||||||
|
ttl: req.body.ttl,
|
||||||
|
keyAlgorithm: req.body.keyAlgorithm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -258,7 +258,8 @@ export enum EventType {
|
|||||||
UPDATE_KMIP_CLIENT = "update-kmip-client",
|
UPDATE_KMIP_CLIENT = "update-kmip-client",
|
||||||
DELETE_KMIP_CLIENT = "delete-kmip-client",
|
DELETE_KMIP_CLIENT = "delete-kmip-client",
|
||||||
GET_KMIP_CLIENT = "get-kmip-client",
|
GET_KMIP_CLIENT = "get-kmip-client",
|
||||||
GET_KMIP_CLIENTS = "get-kmip-clients"
|
GET_KMIP_CLIENTS = "get-kmip-clients",
|
||||||
|
CREATE_KMIP_CLIENT_CERTIFICATE = "create-kmip-client-certificate"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
@@ -2112,6 +2113,16 @@ interface GetKmipClientsEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CreateKmipClientCertificateEvent {
|
||||||
|
type: EventType.CREATE_KMIP_CLIENT_CERTIFICATE;
|
||||||
|
metadata: {
|
||||||
|
clientId: string;
|
||||||
|
ttl: string;
|
||||||
|
keyAlgorithm: string;
|
||||||
|
serialNumber: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| GetSecretsEvent
|
| GetSecretsEvent
|
||||||
| GetSecretEvent
|
| GetSecretEvent
|
||||||
@@ -2307,4 +2318,5 @@ export type Event =
|
|||||||
| UpdateKmipClientEvent
|
| UpdateKmipClientEvent
|
||||||
| DeleteKmipClientEvent
|
| DeleteKmipClientEvent
|
||||||
| GetKmipClientEvent
|
| GetKmipClientEvent
|
||||||
| GetKmipClientsEvent;
|
| GetKmipClientsEvent
|
||||||
|
| CreateKmipClientCertificateEvent;
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TKmipClientCertificateDALFactory = ReturnType<typeof kmipClientCertificateDALFactory>;
|
||||||
|
|
||||||
|
export const kmipClientCertificateDALFactory = (db: TDbClient) => {
|
||||||
|
const kmipClientCertOrm = ormify(db, TableName.KmipClientCertificates);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...kmipClientCertOrm
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export const INSTANCE_KMIP_CONFIG_ID = "00000000-0000-0000-0000-000000000000";
|
||||||
@@ -1,11 +1,25 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import * as x509 from "@peculiar/x509";
|
||||||
|
import crypto, { KeyObject } from "crypto";
|
||||||
|
import ms from "ms";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
|
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||||
|
import {
|
||||||
|
createSerialNumber,
|
||||||
|
keyAlgorithmToAlgCfg
|
||||||
|
} from "@app/services/certificate-authority/certificate-authority-fns";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { ProjectPermissionKmipActions, ProjectPermissionSub } from "../permission/project-permission";
|
import { ProjectPermissionKmipActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
|
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 { TKmipInstanceConfigDALFactory } from "./kmip-instance-config-dal";
|
||||||
import {
|
import {
|
||||||
|
TCreateKmipClientCertificateDTO,
|
||||||
TCreateKmipClientDTO,
|
TCreateKmipClientDTO,
|
||||||
TDeleteKmipClientDTO,
|
TDeleteKmipClientDTO,
|
||||||
TGetKmipClientDTO,
|
TGetKmipClientDTO,
|
||||||
@@ -15,12 +29,21 @@ import {
|
|||||||
|
|
||||||
type TKmipServiceFactoryDep = {
|
type TKmipServiceFactoryDep = {
|
||||||
kmipClientDAL: TKmipClientDALFactory;
|
kmipClientDAL: TKmipClientDALFactory;
|
||||||
|
kmipClientCertificateDAL: TKmipClientCertificateDALFactory;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "decryptWithRootKey">;
|
||||||
|
kmipInstanceConfigDAL: TKmipInstanceConfigDALFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TKmipServiceFactory = ReturnType<typeof kmipServiceFactory>;
|
export type TKmipServiceFactory = ReturnType<typeof kmipServiceFactory>;
|
||||||
|
|
||||||
export const kmipServiceFactory = ({ kmipClientDAL, permissionService }: TKmipServiceFactoryDep) => {
|
export const kmipServiceFactory = ({
|
||||||
|
kmipClientDAL,
|
||||||
|
permissionService,
|
||||||
|
kmipClientCertificateDAL,
|
||||||
|
kmipInstanceConfigDAL,
|
||||||
|
kmsService
|
||||||
|
}: TKmipServiceFactoryDep) => {
|
||||||
const createKmipClient = async ({
|
const createKmipClient = async ({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -67,6 +90,12 @@ export const kmipServiceFactory = ({ kmipClientDAL, permissionService }: TKmipSe
|
|||||||
}: TUpdateKmipClientDTO) => {
|
}: TUpdateKmipClientDTO) => {
|
||||||
const kmipClient = await kmipClientDAL.findById(id);
|
const kmipClient = await kmipClientDAL.findById(id);
|
||||||
|
|
||||||
|
if (!kmipClient) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `KMIP client with ID ${id} does not exist`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -93,6 +122,12 @@ export const kmipServiceFactory = ({ kmipClientDAL, permissionService }: TKmipSe
|
|||||||
const deleteKmipClient = async ({ actor, actorId, actorOrgId, actorAuthMethod, id }: TDeleteKmipClientDTO) => {
|
const deleteKmipClient = async ({ actor, actorId, actorOrgId, actorAuthMethod, id }: TDeleteKmipClientDTO) => {
|
||||||
const kmipClient = await kmipClientDAL.findById(id);
|
const kmipClient = await kmipClientDAL.findById(id);
|
||||||
|
|
||||||
|
if (!kmipClient) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `KMIP client with ID ${id} does not exist`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -115,6 +150,12 @@ export const kmipServiceFactory = ({ kmipClientDAL, permissionService }: TKmipSe
|
|||||||
const getKmipClient = async ({ actor, actorId, actorOrgId, actorAuthMethod, id }: TGetKmipClientDTO) => {
|
const getKmipClient = async ({ actor, actorId, actorOrgId, actorAuthMethod, id }: TGetKmipClientDTO) => {
|
||||||
const kmipClient = await kmipClientDAL.findById(id);
|
const kmipClient = await kmipClientDAL.findById(id);
|
||||||
|
|
||||||
|
if (!kmipClient) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `KMIP client with ID ${id} does not exist`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -151,5 +192,145 @@ export const kmipServiceFactory = ({ kmipClientDAL, permissionService }: TKmipSe
|
|||||||
return kmipClientDAL.findByProjectId({ projectId, ...rest });
|
return kmipClientDAL.findByProjectId({ projectId, ...rest });
|
||||||
};
|
};
|
||||||
|
|
||||||
return { createKmipClient, updateKmipClient, deleteKmipClient, getKmipClient, listKmipClientsByProjectId };
|
const createKmipClientCertificate = async ({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
ttl,
|
||||||
|
keyAlgorithm,
|
||||||
|
clientId
|
||||||
|
}: TCreateKmipClientCertificateDTO) => {
|
||||||
|
const kmipClient = await kmipClientDAL.findById(clientId);
|
||||||
|
|
||||||
|
if (!kmipClient) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `KMIP client with ID ${clientId} does not exist`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: kmipClient.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.KMS
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionKmipActions.GenerateClientCertificates,
|
||||||
|
ProjectPermissionSub.Kmip
|
||||||
|
);
|
||||||
|
|
||||||
|
const kmipInstanceConfig = await kmipInstanceConfigDAL.findById(INSTANCE_KMIP_CONFIG_ID);
|
||||||
|
if (!kmipInstanceConfig) {
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: "KMIP has not been configured for the instance."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const decryptWithRoot = kmsService.decryptWithRootKey();
|
||||||
|
|
||||||
|
const caCertObj = new x509.X509Certificate(
|
||||||
|
decryptWithRoot(kmipInstanceConfig.encryptedClientIntermediateCaCertificate)
|
||||||
|
);
|
||||||
|
|
||||||
|
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] |
|
||||||
|
x509.KeyUsageFlags[CertKeyUsage.KEY_AGREEMENT],
|
||||||
|
true
|
||||||
|
),
|
||||||
|
new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage[CertExtendedKeyUsage.CLIENT_AUTH]], true)
|
||||||
|
];
|
||||||
|
|
||||||
|
const caAlg = keyAlgorithmToAlgCfg(kmipInstanceConfig.caKeyAlgorithm as CertKeyAlgorithm);
|
||||||
|
|
||||||
|
const decryptedCaCertChain = decryptWithRoot(kmipInstanceConfig.encryptedClientIntermediateCaChain).toString(
|
||||||
|
"utf-8"
|
||||||
|
);
|
||||||
|
|
||||||
|
const caSkObj = crypto.createPrivateKey({
|
||||||
|
key: decryptWithRoot(kmipInstanceConfig.encryptedClientIntermediateCaPrivateKey),
|
||||||
|
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: `OU=${kmipClient.projectId},CN=${clientId}`,
|
||||||
|
issuer: caCertObj.subject,
|
||||||
|
notBefore: notBeforeDate,
|
||||||
|
notAfter: notAfterDate,
|
||||||
|
signingKey: caPrivateKey,
|
||||||
|
publicKey: leafKeys.publicKey,
|
||||||
|
signingAlgorithm: alg,
|
||||||
|
extensions
|
||||||
|
});
|
||||||
|
|
||||||
|
const skLeafObj = KeyObject.from(leafKeys.privateKey);
|
||||||
|
const certificateChain = `${caCertObj.toString("pem")}\n${decryptedCaCertChain}`.trim();
|
||||||
|
|
||||||
|
await kmipClientCertificateDAL.create({
|
||||||
|
kmipClientId: clientId,
|
||||||
|
keyAlgorithm,
|
||||||
|
issuedAt: notBeforeDate,
|
||||||
|
expiration: notAfterDate,
|
||||||
|
serialNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
serialNumber,
|
||||||
|
privateKey: skLeafObj.export({ format: "pem", type: "pkcs8" }) as string,
|
||||||
|
certificate: leafCert.toString("pem"),
|
||||||
|
certificateChain,
|
||||||
|
projectId: kmipClient.projectId
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
createKmipClient,
|
||||||
|
updateKmipClient,
|
||||||
|
deleteKmipClient,
|
||||||
|
getKmipClient,
|
||||||
|
listKmipClientsByProjectId,
|
||||||
|
createKmipClientCertificate
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { OrderByDirection, TProjectPermission } from "@app/lib/types";
|
import { OrderByDirection, TProjectPermission } from "@app/lib/types";
|
||||||
|
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
||||||
|
|
||||||
import { KmipPermission } from "./kmip-enum";
|
import { KmipPermission } from "./kmip-enum";
|
||||||
|
|
||||||
|
export type TCreateKmipClientCertificateDTO = {
|
||||||
|
clientId: string;
|
||||||
|
keyAlgorithm: CertKeyAlgorithm;
|
||||||
|
ttl: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TCreateKmipClientDTO = {
|
export type TCreateKmipClientDTO = {
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ export enum ProjectPermissionKmipActions {
|
|||||||
CreateClients = "create-clients",
|
CreateClients = "create-clients",
|
||||||
UpdateClients = "update-clients",
|
UpdateClients = "update-clients",
|
||||||
DeleteClients = "delete-clients",
|
DeleteClients = "delete-clients",
|
||||||
ReadClients = "read-clients"
|
ReadClients = "read-clients",
|
||||||
|
GenerateClientCertificates = "generate-client-certificates"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSub {
|
export enum ProjectPermissionSub {
|
||||||
@@ -596,7 +597,8 @@ const buildAdminPermissionRules = () => {
|
|||||||
ProjectPermissionKmipActions.CreateClients,
|
ProjectPermissionKmipActions.CreateClients,
|
||||||
ProjectPermissionKmipActions.UpdateClients,
|
ProjectPermissionKmipActions.UpdateClients,
|
||||||
ProjectPermissionKmipActions.DeleteClients,
|
ProjectPermissionKmipActions.DeleteClients,
|
||||||
ProjectPermissionKmipActions.ReadClients
|
ProjectPermissionKmipActions.ReadClients,
|
||||||
|
ProjectPermissionKmipActions.GenerateClientCertificates
|
||||||
],
|
],
|
||||||
ProjectPermissionSub.Kmip
|
ProjectPermissionSub.Kmip
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import { HsmModule } from "@app/ee/services/hsm/hsm-types";
|
|||||||
import { identityProjectAdditionalPrivilegeDALFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-dal";
|
import { identityProjectAdditionalPrivilegeDALFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-dal";
|
||||||
import { identityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
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 { identityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
|
||||||
|
import { kmipClientCertificateDALFactory } from "@app/ee/services/kmip/kmip-client-certificate-dal";
|
||||||
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";
|
||||||
@@ -385,6 +386,7 @@ export const registerRoutes = async (
|
|||||||
const projectTemplateDAL = projectTemplateDALFactory(db);
|
const projectTemplateDAL = projectTemplateDALFactory(db);
|
||||||
const resourceMetadataDAL = resourceMetadataDALFactory(db);
|
const resourceMetadataDAL = resourceMetadataDALFactory(db);
|
||||||
const kmipClientDAL = kmipClientDALFactory(db);
|
const kmipClientDAL = kmipClientDALFactory(db);
|
||||||
|
const kmipClientCertificateDAL = kmipClientCertificateDALFactory(db);
|
||||||
const kmipInstanceConfigDAL = kmipInstanceConfigDALFactory(db);
|
const kmipInstanceConfigDAL = kmipInstanceConfigDALFactory(db);
|
||||||
const kmipInstanceServerCertificateDAL = kmipInstanceServerCertificateDALFactory(db);
|
const kmipInstanceServerCertificateDAL = kmipInstanceServerCertificateDALFactory(db);
|
||||||
|
|
||||||
@@ -1429,7 +1431,10 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
const kmipService = kmipServiceFactory({
|
const kmipService = kmipServiceFactory({
|
||||||
kmipClientDAL,
|
kmipClientDAL,
|
||||||
permissionService
|
permissionService,
|
||||||
|
kmipClientCertificateDAL,
|
||||||
|
kmipInstanceConfigDAL,
|
||||||
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
await superAdminService.initServerCfg();
|
await superAdminService.initServerCfg();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { TKeyStoreFactory } from "@app/keystore/keystore";
|
|||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { getUserPrivateKey } from "@app/lib/crypto/srp";
|
import { getUserPrivateKey } from "@app/lib/crypto/srp";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||||
import { isValidIp } from "@app/lib/ip";
|
import { isValidIp } from "@app/lib/ip";
|
||||||
|
|
||||||
import { TAuthLoginFactory } from "../auth/auth-login-service";
|
import { TAuthLoginFactory } from "../auth/auth-login-service";
|
||||||
@@ -562,7 +562,7 @@ export const superAdminServiceFactory = ({
|
|||||||
}: TGenerateInstanceKmipServerCertificateDTO) => {
|
}: TGenerateInstanceKmipServerCertificateDTO) => {
|
||||||
const kmipInstanceConfig = await kmipInstanceConfigDAL.findById(ADMIN_CONFIG_DB_UUID);
|
const kmipInstanceConfig = await kmipInstanceConfigDAL.findById(ADMIN_CONFIG_DB_UUID);
|
||||||
if (!kmipInstanceConfig) {
|
if (!kmipInstanceConfig) {
|
||||||
throw new BadRequestError({
|
throw new InternalServerError({
|
||||||
message: "KMIP has not been configured for the instance"
|
message: "KMIP has not been configured for the instance"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ export enum ProjectPermissionKmipActions {
|
|||||||
CreateClients = "create-clients",
|
CreateClients = "create-clients",
|
||||||
UpdateClients = "update-clients",
|
UpdateClients = "update-clients",
|
||||||
DeleteClients = "delete-clients",
|
DeleteClients = "delete-clients",
|
||||||
ReadClients = "read-clients"
|
ReadClients = "read-clients",
|
||||||
|
GenerateClientCertificates = "generate-client-certificates"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSecretSyncActions {
|
export enum ProjectPermissionSecretSyncActions {
|
||||||
|
|||||||
@@ -3,7 +3,13 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import { kmipKeys } from "./queries";
|
import { kmipKeys } from "./queries";
|
||||||
import { TCreateKmipClient, TDeleteKmipClient, TUpdateKmipClient } from "./types";
|
import {
|
||||||
|
KmipClientCertificate,
|
||||||
|
TCreateKmipClient,
|
||||||
|
TDeleteKmipClient,
|
||||||
|
TGenerateKmipClientCertificate,
|
||||||
|
TUpdateKmipClient
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
export const useCreateKmipClient = () => {
|
export const useCreateKmipClient = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -56,3 +62,16 @@ export const useDeleteKmipClients = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGenerateKmipClientCertificate = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (payload: TGenerateKmipClientCertificate) => {
|
||||||
|
const { data } = await apiRequest.post<KmipClientCertificate>(
|
||||||
|
`/api/v1/kmip/clients/${payload.clientId}/certificates`,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { CertKeyAlgorithm } from "../certificates/enums";
|
||||||
import { OrderByDirection } from "../generic/types";
|
import { OrderByDirection } from "../generic/types";
|
||||||
|
|
||||||
export enum KmipPermission {
|
export enum KmipPermission {
|
||||||
@@ -30,6 +31,19 @@ export type TProjectKmipClientList = {
|
|||||||
totalCount: number;
|
totalCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TGenerateKmipClientCertificate = {
|
||||||
|
keyAlgorithm: CertKeyAlgorithm;
|
||||||
|
ttl: string;
|
||||||
|
clientId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type KmipClientCertificate = {
|
||||||
|
serialNumber: string;
|
||||||
|
certificate: string;
|
||||||
|
certificateChain: string;
|
||||||
|
privateKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TDeleteKmipClient = KeyRef & ProjectRef;
|
export type TDeleteKmipClient = KeyRef & ProjectRef;
|
||||||
|
|
||||||
export type TListProjectKmipClientsDTO = {
|
export type TListProjectKmipClientsDTO = {
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalClose,
|
||||||
|
ModalContent,
|
||||||
|
Select,
|
||||||
|
SelectItem
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { certKeyAlgorithms } from "@app/hooks/api/certificates/constants";
|
||||||
|
import { CertKeyAlgorithm } from "@app/hooks/api/certificates/enums";
|
||||||
|
import { useGenerateKmipClientCertificate } from "@app/hooks/api/kmip";
|
||||||
|
import { KmipClientCertificate, TKmipClient } from "@app/hooks/api/kmip/types";
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
keyAlgorithm: z.nativeEnum(CertKeyAlgorithm),
|
||||||
|
ttl: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FormData = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen: boolean;
|
||||||
|
onOpenChange: (isOpen: boolean) => void;
|
||||||
|
kmipClient?: TKmipClient | null;
|
||||||
|
displayNewClientCertificate: (certificate: KmipClientCertificate) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FormProps = Pick<Props, "kmipClient" | "displayNewClientCertificate"> & {
|
||||||
|
onComplete: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const KmipClientCertificateForm = ({
|
||||||
|
displayNewClientCertificate,
|
||||||
|
kmipClient,
|
||||||
|
onComplete
|
||||||
|
}: FormProps) => {
|
||||||
|
const { mutateAsync: createKmipClientCertificate } = useGenerateKmipClientCertificate();
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { isSubmitting }
|
||||||
|
} = useForm<FormData>({
|
||||||
|
resolver: zodResolver(formSchema)
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleKmipClientSubmit = async (payload: FormData) => {
|
||||||
|
if (!kmipClient) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const certificate = await createKmipClientCertificate({
|
||||||
|
...payload,
|
||||||
|
clientId: kmipClient?.id
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully created KMIP client certificate",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
displayNewClientCertificate(certificate);
|
||||||
|
onComplete();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(handleKmipClientSubmit)}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="ttl"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl label="TTL" isError={Boolean(error)} errorText={error?.message} isRequired>
|
||||||
|
<Input {...field} placeholder="2 days, 1d, 2h, 1y, ..." />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="keyAlgorithm"
|
||||||
|
defaultValue={CertKeyAlgorithm.RSA_2048}
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Key Algorithm"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
helperText="This defines the key algorithm to use for signing the client certificate."
|
||||||
|
>
|
||||||
|
<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="flex items-center">
|
||||||
|
<Button
|
||||||
|
className="mr-4"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting}
|
||||||
|
>
|
||||||
|
Generate Client Certificate
|
||||||
|
</Button>
|
||||||
|
<ModalClose asChild>
|
||||||
|
<Button colorSchema="secondary" variant="plain">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalClose>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CreateKmipClientCertificateModal = ({
|
||||||
|
isOpen,
|
||||||
|
onOpenChange,
|
||||||
|
kmipClient,
|
||||||
|
displayNewClientCertificate
|
||||||
|
}: Props) => {
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||||
|
<ModalContent title="Generate KMIP client certificate">
|
||||||
|
<KmipClientCertificateForm
|
||||||
|
onComplete={() => onOpenChange(false)}
|
||||||
|
displayNewClientCertificate={displayNewClientCertificate}
|
||||||
|
kmipClient={kmipClient}
|
||||||
|
/>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Modal, ModalContent } from "@app/components/v2";
|
||||||
|
import { KmipClientCertificate } from "@app/hooks/api/kmip/types";
|
||||||
|
import { CertificateContent } from "@app/pages/cert-manager/CertificatesPage/components/CertificatesTab/components/CertificateContent";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen: boolean;
|
||||||
|
onOpenChange: (isOpen: boolean) => void;
|
||||||
|
certificate: KmipClientCertificate;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const KmipClientCertificateModal = ({ isOpen, onOpenChange, certificate }: Props) => {
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||||
|
<ModalContent title="KMIP client certificate">
|
||||||
|
<CertificateContent {...certificate} />
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
faArrowDown,
|
faArrowDown,
|
||||||
faArrowUp,
|
faArrowUp,
|
||||||
faArrowUpRightFromSquare,
|
faArrowUpRightFromSquare,
|
||||||
|
faCertificate,
|
||||||
faEdit,
|
faEdit,
|
||||||
faEllipsis,
|
faEllipsis,
|
||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
@@ -45,7 +46,9 @@ import { OrderByDirection } from "@app/hooks/api/generic/types";
|
|||||||
import { useGetKmipClientsByProjectId } from "@app/hooks/api/kmip";
|
import { useGetKmipClientsByProjectId } from "@app/hooks/api/kmip";
|
||||||
import { KmipClientOrderBy, TKmipClient } from "@app/hooks/api/kmip/types";
|
import { KmipClientOrderBy, TKmipClient } from "@app/hooks/api/kmip/types";
|
||||||
|
|
||||||
|
import { CreateKmipClientCertificateModal } from "./CreateKmipClientCertificateModal";
|
||||||
import { DeleteKmipClientModal } from "./DeleteKmipClientModal";
|
import { DeleteKmipClientModal } from "./DeleteKmipClientModal";
|
||||||
|
import { KmipClientCertificateModal } from "./KmipClientCertificateModal";
|
||||||
import { KmipClientModal } from "./KmipClientModal";
|
import { KmipClientModal } from "./KmipClientModal";
|
||||||
|
|
||||||
export const KmipClientTable = () => {
|
export const KmipClientTable = () => {
|
||||||
@@ -88,7 +91,9 @@ export const KmipClientTable = () => {
|
|||||||
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||||
"upsertKmipClient",
|
"upsertKmipClient",
|
||||||
"deleteKmipClient"
|
"deleteKmipClient",
|
||||||
|
"generateKmipClientCert",
|
||||||
|
"displayKmipClientCert"
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
const handleSort = () => {
|
const handleSort = () => {
|
||||||
@@ -107,6 +112,11 @@ export const KmipClientTable = () => {
|
|||||||
ProjectPermissionSub.Kmip
|
ProjectPermissionSub.Kmip
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const cannotGenerateKmipClientCertificate = permission.cannot(
|
||||||
|
ProjectPermissionKmipActions.GenerateClientCertificates,
|
||||||
|
ProjectPermissionSub.Kmip
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
key="kmip-clients-tab"
|
key="kmip-clients-tab"
|
||||||
@@ -206,6 +216,25 @@ export const KmipClientTable = () => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="min-w-[160px]">
|
<DropdownMenuContent className="min-w-[160px]">
|
||||||
|
<Tooltip
|
||||||
|
content={
|
||||||
|
cannotGenerateKmipClientCertificate ? "Access Restricted" : ""
|
||||||
|
}
|
||||||
|
position="left"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() =>
|
||||||
|
handlePopUpOpen("generateKmipClientCert", kmipClient)
|
||||||
|
}
|
||||||
|
icon={<FontAwesomeIcon icon={faCertificate} />}
|
||||||
|
iconPos="left"
|
||||||
|
isDisabled={cannotGenerateKmipClientCertificate}
|
||||||
|
>
|
||||||
|
Generate Certificate
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={cannotEditKmipClient ? "Access Restricted" : ""}
|
content={cannotEditKmipClient ? "Access Restricted" : ""}
|
||||||
position="left"
|
position="left"
|
||||||
@@ -274,6 +303,19 @@ export const KmipClientTable = () => {
|
|||||||
onOpenChange={(isOpen) => handlePopUpToggle("upsertKmipClient", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("upsertKmipClient", isOpen)}
|
||||||
kmipClient={popUp.upsertKmipClient.data as TKmipClient | null}
|
kmipClient={popUp.upsertKmipClient.data as TKmipClient | null}
|
||||||
/>
|
/>
|
||||||
|
<CreateKmipClientCertificateModal
|
||||||
|
isOpen={popUp.generateKmipClientCert.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("generateKmipClientCert", isOpen)}
|
||||||
|
kmipClient={popUp.generateKmipClientCert.data as TKmipClient | null}
|
||||||
|
displayNewClientCertificate={(certificate) =>
|
||||||
|
handlePopUpOpen("displayKmipClientCert", certificate)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<KmipClientCertificateModal
|
||||||
|
isOpen={popUp.displayKmipClientCert.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("displayKmipClientCert", isOpen)}
|
||||||
|
certificate={popUp.displayKmipClientCert.data}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
|
|||||||
+11
-1
@@ -53,7 +53,8 @@ const KmipPolicyActionSchema = z.object({
|
|||||||
[ProjectPermissionKmipActions.ReadClients]: z.boolean().optional(),
|
[ProjectPermissionKmipActions.ReadClients]: z.boolean().optional(),
|
||||||
[ProjectPermissionKmipActions.CreateClients]: z.boolean().optional(),
|
[ProjectPermissionKmipActions.CreateClients]: z.boolean().optional(),
|
||||||
[ProjectPermissionKmipActions.UpdateClients]: z.boolean().optional(),
|
[ProjectPermissionKmipActions.UpdateClients]: z.boolean().optional(),
|
||||||
[ProjectPermissionKmipActions.DeleteClients]: z.boolean().optional()
|
[ProjectPermissionKmipActions.DeleteClients]: z.boolean().optional(),
|
||||||
|
[ProjectPermissionKmipActions.GenerateClientCertificates]: z.boolean().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
const SecretRollbackPolicyActionSchema = z.object({
|
const SecretRollbackPolicyActionSchema = z.object({
|
||||||
@@ -373,6 +374,9 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
const canEditClients = action.includes(ProjectPermissionKmipActions.UpdateClients);
|
const canEditClients = action.includes(ProjectPermissionKmipActions.UpdateClients);
|
||||||
const canDeleteClients = action.includes(ProjectPermissionKmipActions.DeleteClients);
|
const canDeleteClients = action.includes(ProjectPermissionKmipActions.DeleteClients);
|
||||||
const canCreateClients = action.includes(ProjectPermissionKmipActions.CreateClients);
|
const canCreateClients = action.includes(ProjectPermissionKmipActions.CreateClients);
|
||||||
|
const canGenerateClientCerts = action.includes(
|
||||||
|
ProjectPermissionKmipActions.GenerateClientCertificates
|
||||||
|
);
|
||||||
|
|
||||||
if (!formVal[subject]) formVal[subject] = [{}];
|
if (!formVal[subject]) formVal[subject] = [{}];
|
||||||
|
|
||||||
@@ -381,6 +385,8 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
if (canEditClients) formVal[subject]![0][ProjectPermissionKmipActions.UpdateClients] = true;
|
if (canEditClients) formVal[subject]![0][ProjectPermissionKmipActions.UpdateClients] = true;
|
||||||
if (canCreateClients) formVal[subject]![0][ProjectPermissionKmipActions.CreateClients] = true;
|
if (canCreateClients) formVal[subject]![0][ProjectPermissionKmipActions.CreateClients] = true;
|
||||||
if (canDeleteClients) formVal[subject]![0][ProjectPermissionKmipActions.DeleteClients] = true;
|
if (canDeleteClients) formVal[subject]![0][ProjectPermissionKmipActions.DeleteClients] = true;
|
||||||
|
if (canGenerateClientCerts)
|
||||||
|
formVal[subject]![0][ProjectPermissionKmipActions.GenerateClientCertificates] = true;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -783,6 +789,10 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
|||||||
{
|
{
|
||||||
label: "Delete clients",
|
label: "Delete clients",
|
||||||
value: ProjectPermissionKmipActions.DeleteClients
|
value: ProjectPermissionKmipActions.DeleteClients
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Generate client certificates",
|
||||||
|
value: ProjectPermissionKmipActions.GenerateClientCertificates
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user