mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 11:38:55 +00:00
Merge pull request #3079 from Infisical/feat/kmip-client-management
feat: kmip
This commit is contained in:
@@ -21,6 +21,8 @@ import {
|
||||
TUpdateSecretSyncDTO
|
||||
} from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { KmipPermission } from "../kmip/kmip-enum";
|
||||
|
||||
export type TListProjectAuditLogDTO = {
|
||||
filter: {
|
||||
userAgentType?: UserAgentType;
|
||||
@@ -39,7 +41,14 @@ export type TListProjectAuditLogDTO = {
|
||||
|
||||
export type TCreateAuditLogDTO = {
|
||||
event: Event;
|
||||
actor: UserActor | IdentityActor | ServiceActor | ScimClientActor | PlatformActor | UnknownUserActor;
|
||||
actor:
|
||||
| UserActor
|
||||
| IdentityActor
|
||||
| ServiceActor
|
||||
| ScimClientActor
|
||||
| PlatformActor
|
||||
| UnknownUserActor
|
||||
| KmipClientActor;
|
||||
orgId?: string;
|
||||
projectId?: string;
|
||||
} & BaseAuthData;
|
||||
@@ -252,7 +261,26 @@ export enum EventType {
|
||||
SECRET_SYNC_IMPORT_SECRETS = "secret-sync-import-secrets",
|
||||
SECRET_SYNC_REMOVE_SECRETS = "secret-sync-remove-secrets",
|
||||
OIDC_GROUP_MEMBERSHIP_MAPPING_ASSIGN_USER = "oidc-group-membership-mapping-assign-user",
|
||||
OIDC_GROUP_MEMBERSHIP_MAPPING_REMOVE_USER = "oidc-group-membership-mapping-remove-user"
|
||||
OIDC_GROUP_MEMBERSHIP_MAPPING_REMOVE_USER = "oidc-group-membership-mapping-remove-user",
|
||||
CREATE_KMIP_CLIENT = "create-kmip-client",
|
||||
UPDATE_KMIP_CLIENT = "update-kmip-client",
|
||||
DELETE_KMIP_CLIENT = "delete-kmip-client",
|
||||
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_DESTROY = "kmip-operation-destroy",
|
||||
KMIP_OPERATION_GET_ATTRIBUTES = "kmip-operation-get-attributes",
|
||||
KMIP_OPERATION_ACTIVATE = "kmip-operation-activate",
|
||||
KMIP_OPERATION_REVOKE = "kmip-operation-revoke",
|
||||
KMIP_OPERATION_LOCATE = "kmip-operation-locate",
|
||||
KMIP_OPERATION_REGISTER = "kmip-operation-register"
|
||||
}
|
||||
|
||||
interface UserActorMetadata {
|
||||
@@ -275,6 +303,11 @@ interface ScimClientActorMetadata {}
|
||||
|
||||
interface PlatformActorMetadata {}
|
||||
|
||||
interface KmipClientActorMetadata {
|
||||
clientId: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface UnknownUserActorMetadata {}
|
||||
|
||||
export interface UserActor {
|
||||
@@ -292,6 +325,11 @@ export interface PlatformActor {
|
||||
metadata: PlatformActorMetadata;
|
||||
}
|
||||
|
||||
export interface KmipClientActor {
|
||||
type: ActorType.KMIP_CLIENT;
|
||||
metadata: KmipClientActorMetadata;
|
||||
}
|
||||
|
||||
export interface UnknownUserActor {
|
||||
type: ActorType.UNKNOWN_USER;
|
||||
metadata: UnknownUserActorMetadata;
|
||||
@@ -307,7 +345,7 @@ export interface ScimClientActor {
|
||||
metadata: ScimClientActorMetadata;
|
||||
}
|
||||
|
||||
export type Actor = UserActor | ServiceActor | IdentityActor | ScimClientActor | PlatformActor;
|
||||
export type Actor = UserActor | ServiceActor | IdentityActor | ScimClientActor | PlatformActor | KmipClientActor;
|
||||
|
||||
interface GetSecretsEvent {
|
||||
type: EventType.GET_SECRETS;
|
||||
@@ -2091,6 +2129,139 @@ interface OidcGroupMembershipMappingRemoveUserEvent {
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateKmipClientEvent {
|
||||
type: EventType.CREATE_KMIP_CLIENT;
|
||||
metadata: {
|
||||
name: string;
|
||||
id: string;
|
||||
permissions: KmipPermission[];
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateKmipClientEvent {
|
||||
type: EventType.UPDATE_KMIP_CLIENT;
|
||||
metadata: {
|
||||
name: string;
|
||||
id: string;
|
||||
permissions: KmipPermission[];
|
||||
};
|
||||
}
|
||||
|
||||
interface DeleteKmipClientEvent {
|
||||
type: EventType.DELETE_KMIP_CLIENT;
|
||||
metadata: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetKmipClientEvent {
|
||||
type: EventType.GET_KMIP_CLIENT;
|
||||
metadata: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetKmipClientsEvent {
|
||||
type: EventType.GET_KMIP_CLIENTS;
|
||||
metadata: {
|
||||
ids: string[];
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateKmipClientCertificateEvent {
|
||||
type: EventType.CREATE_KMIP_CLIENT_CERTIFICATE;
|
||||
metadata: {
|
||||
clientId: string;
|
||||
ttl: string;
|
||||
keyAlgorithm: string;
|
||||
serialNumber: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface KmipOperationGetEvent {
|
||||
type: EventType.KMIP_OPERATION_GET;
|
||||
metadata: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface KmipOperationDestroyEvent {
|
||||
type: EventType.KMIP_OPERATION_DESTROY;
|
||||
metadata: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface KmipOperationCreateEvent {
|
||||
type: EventType.KMIP_OPERATION_CREATE;
|
||||
metadata: {
|
||||
id: string;
|
||||
algorithm: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface KmipOperationGetAttributesEvent {
|
||||
type: EventType.KMIP_OPERATION_GET_ATTRIBUTES;
|
||||
metadata: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface KmipOperationActivateEvent {
|
||||
type: EventType.KMIP_OPERATION_ACTIVATE;
|
||||
metadata: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface KmipOperationRevokeEvent {
|
||||
type: EventType.KMIP_OPERATION_REVOKE;
|
||||
metadata: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface KmipOperationLocateEvent {
|
||||
type: EventType.KMIP_OPERATION_LOCATE;
|
||||
metadata: {
|
||||
ids: string[];
|
||||
};
|
||||
}
|
||||
|
||||
interface KmipOperationRegisterEvent {
|
||||
type: EventType.KMIP_OPERATION_REGISTER;
|
||||
metadata: {
|
||||
id: string;
|
||||
algorithm: string;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
@@ -2282,4 +2453,21 @@ export type Event =
|
||||
| SecretSyncImportSecretsEvent
|
||||
| SecretSyncRemoveSecretsEvent
|
||||
| OidcGroupMembershipMappingAssignUserEvent
|
||||
| OidcGroupMembershipMappingRemoveUserEvent;
|
||||
| OidcGroupMembershipMappingRemoveUserEvent
|
||||
| CreateKmipClientEvent
|
||||
| UpdateKmipClientEvent
|
||||
| DeleteKmipClientEvent
|
||||
| GetKmipClientEvent
|
||||
| GetKmipClientsEvent
|
||||
| CreateKmipClientCertificateEvent
|
||||
| SetupKmipEvent
|
||||
| GetKmipEvent
|
||||
| RegisterKmipServerEvent
|
||||
| KmipOperationGetEvent
|
||||
| KmipOperationDestroyEvent
|
||||
| KmipOperationCreateEvent
|
||||
| KmipOperationGetAttributesEvent
|
||||
| KmipOperationActivateEvent
|
||||
| KmipOperationRevokeEvent
|
||||
| KmipOperationLocateEvent
|
||||
| KmipOperationRegisterEvent;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
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,86 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName, TKmipClients } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||
import { OrderByDirection } from "@app/lib/types";
|
||||
|
||||
import { KmipClientOrderBy } from "./kmip-types";
|
||||
|
||||
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,
|
||||
offset = 0,
|
||||
limit,
|
||||
orderBy = KmipClientOrderBy.Name,
|
||||
orderDirection = OrderByDirection.ASC,
|
||||
search
|
||||
}: {
|
||||
projectId: string;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
orderBy?: KmipClientOrderBy;
|
||||
orderDirection?: OrderByDirection;
|
||||
search?: string;
|
||||
},
|
||||
tx?: Knex
|
||||
) => {
|
||||
try {
|
||||
const query = (tx || db.replicaNode())(TableName.KmipClient)
|
||||
.where("projectId", projectId)
|
||||
.where((qb) => {
|
||||
if (search) {
|
||||
void qb.whereILike("name", `%${search}%`);
|
||||
}
|
||||
})
|
||||
.select<
|
||||
(TKmipClients & {
|
||||
total_count: number;
|
||||
})[]
|
||||
>(selectAllTableCols(TableName.KmipClient), db.raw(`count(*) OVER() as total_count`))
|
||||
.orderBy(orderBy, orderDirection);
|
||||
|
||||
if (limit) {
|
||||
void query.limit(limit).offset(offset);
|
||||
}
|
||||
|
||||
const data = await query;
|
||||
|
||||
return { kmipClients: data, totalCount: Number(data?.[0]?.total_count ?? 0) };
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find KMIP clients by project id" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...kmipClientOrm,
|
||||
findByProjectId,
|
||||
findByProjectAndClientId
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
export enum KmipPermission {
|
||||
Create = "create",
|
||||
Locate = "locate",
|
||||
Check = "check",
|
||||
Get = "get",
|
||||
GetAttributes = "get-attributes",
|
||||
Activate = "activate",
|
||||
Revoke = "revoke",
|
||||
Destroy = "destroy",
|
||||
Register = "register"
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
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 {
|
||||
TKmipCreateDTO,
|
||||
TKmipDestroyDTO,
|
||||
TKmipGetAttributesDTO,
|
||||
TKmipGetDTO,
|
||||
TKmipLocateDTO,
|
||||
TKmipRegisterDTO,
|
||||
TKmipRevokeDTO
|
||||
} from "./kmip-types";
|
||||
|
||||
type TKmipOperationServiceFactoryDep = {
|
||||
kmsService: TKmsServiceFactory;
|
||||
kmsDAL: TKmsKeyDALFactory;
|
||||
kmipClientDAL: TKmipClientDALFactory;
|
||||
projectDAL: Pick<TProjectDALFactory, "getProjectFromSplitId" | "findById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
};
|
||||
|
||||
export type TKmipOperationServiceFactory = ReturnType<typeof kmipOperationServiceFactory>;
|
||||
|
||||
export const kmipOperationServiceFactory = ({
|
||||
kmsService,
|
||||
kmsDAL,
|
||||
projectDAL,
|
||||
kmipClientDAL,
|
||||
permissionService
|
||||
}: TKmipOperationServiceFactoryDep) => {
|
||||
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 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: algorithm,
|
||||
orgId: actorOrgId,
|
||||
projectId,
|
||||
isReserved: false
|
||||
});
|
||||
|
||||
return kmsKey;
|
||||
};
|
||||
|
||||
const destroy = async ({ projectId, id, clientId, actor, actorId, actorOrgId, actorAuthMethod }: TKmipDestroyDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
|
||||
|
||||
const kmipClient = await kmipClientDAL.findOne({
|
||||
id: clientId,
|
||||
projectId
|
||||
});
|
||||
|
||||
if (!kmipClient.permissions?.includes(KmipPermission.Destroy)) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Client does not have sufficient permission to perform KMIP destroy"
|
||||
});
|
||||
}
|
||||
|
||||
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 destroy reserved keys" });
|
||||
}
|
||||
|
||||
const completeKeyDetails = await kmsDAL.findByIdWithAssociatedKms(id);
|
||||
if (!completeKeyDetails.internalKms) {
|
||||
throw new BadRequestError({
|
||||
message: "Cannot destroy external keys"
|
||||
});
|
||||
}
|
||||
|
||||
if (!completeKeyDetails.isDisabled) {
|
||||
throw new BadRequestError({
|
||||
message: "Cannot destroy active keys"
|
||||
});
|
||||
}
|
||||
|
||||
const kms = kmsDAL.deleteById(id);
|
||||
|
||||
return kms;
|
||||
};
|
||||
|
||||
const get = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipGetDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
|
||||
|
||||
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 keys"
|
||||
});
|
||||
}
|
||||
|
||||
const kmsKey = await kmsService.getKeyMaterial({
|
||||
kmsId: key.id
|
||||
});
|
||||
|
||||
return {
|
||||
id: key.id,
|
||||
value: kmsKey.toString("base64"),
|
||||
algorithm: completeKeyDetails.internalKms.encryptionAlgorithm,
|
||||
isActive: !key.isDisabled,
|
||||
createdAt: key.createdAt,
|
||||
updatedAt: key.updatedAt
|
||||
};
|
||||
};
|
||||
|
||||
const activate = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipGetDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
|
||||
|
||||
const kmipClient = await kmipClientDAL.findOne({
|
||||
id: clientId,
|
||||
projectId
|
||||
});
|
||||
|
||||
if (!kmipClient.permissions?.includes(KmipPermission.Activate)) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Client does not have sufficient permission to perform KMIP activate"
|
||||
});
|
||||
}
|
||||
|
||||
const key = await kmsDAL.findOne({
|
||||
id,
|
||||
projectId
|
||||
});
|
||||
|
||||
if (!key) {
|
||||
throw new NotFoundError({ message: `Key with ID ${id} not found` });
|
||||
}
|
||||
|
||||
return {
|
||||
id: key.id,
|
||||
isActive: !key.isDisabled
|
||||
};
|
||||
};
|
||||
|
||||
const revoke = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipRevokeDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
|
||||
|
||||
const kmipClient = await kmipClientDAL.findOne({
|
||||
id: clientId,
|
||||
projectId
|
||||
});
|
||||
|
||||
if (!kmipClient.permissions?.includes(KmipPermission.Revoke)) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Client does not have sufficient permission to perform KMIP revoke"
|
||||
});
|
||||
}
|
||||
|
||||
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 revoke reserved keys" });
|
||||
}
|
||||
|
||||
const completeKeyDetails = await kmsDAL.findByIdWithAssociatedKms(id);
|
||||
|
||||
if (!completeKeyDetails.internalKms) {
|
||||
throw new BadRequestError({
|
||||
message: "Cannot revoke external keys"
|
||||
});
|
||||
}
|
||||
|
||||
const revokedKey = await kmsDAL.updateById(key.id, {
|
||||
isDisabled: true
|
||||
});
|
||||
|
||||
return {
|
||||
id: key.id,
|
||||
updatedAt: revokedKey.updatedAt
|
||||
};
|
||||
};
|
||||
|
||||
const getAttributes = async ({
|
||||
projectId,
|
||||
id,
|
||||
clientId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: TKmipGetAttributesDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
|
||||
|
||||
const kmipClient = await kmipClientDAL.findOne({
|
||||
id: clientId,
|
||||
projectId
|
||||
});
|
||||
|
||||
if (!kmipClient.permissions?.includes(KmipPermission.GetAttributes)) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Client does not have sufficient permission to perform KMIP get attributes"
|
||||
});
|
||||
}
|
||||
|
||||
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 keys"
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
id: key.id,
|
||||
algorithm: completeKeyDetails.internalKms.encryptionAlgorithm,
|
||||
isActive: !key.isDisabled,
|
||||
createdAt: key.createdAt,
|
||||
updatedAt: key.updatedAt
|
||||
};
|
||||
};
|
||||
|
||||
const locate = async ({ projectId, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipLocateDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
|
||||
|
||||
const kmipClient = await kmipClientDAL.findOne({
|
||||
id: clientId,
|
||||
projectId
|
||||
});
|
||||
|
||||
if (!kmipClient.permissions?.includes(KmipPermission.Locate)) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Client does not have sufficient permission to perform KMIP locate"
|
||||
});
|
||||
}
|
||||
|
||||
const keys = await kmsDAL.findProjectCmeks(projectId);
|
||||
|
||||
return keys;
|
||||
};
|
||||
|
||||
const register = async ({
|
||||
projectId,
|
||||
clientId,
|
||||
key,
|
||||
algorithm,
|
||||
name,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: TKmipRegisterDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
|
||||
|
||||
const kmipClient = await kmipClientDAL.findOne({
|
||||
id: clientId,
|
||||
projectId
|
||||
});
|
||||
|
||||
if (!kmipClient.permissions?.includes(KmipPermission.Register)) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Client does not have sufficient permission to perform KMIP register"
|
||||
});
|
||||
}
|
||||
|
||||
const project = await projectDAL.findById(projectId);
|
||||
|
||||
const kmsKey = await kmsService.importKeyMaterial({
|
||||
name,
|
||||
key: Buffer.from(key, "base64"),
|
||||
algorithm,
|
||||
isReserved: false,
|
||||
projectId,
|
||||
orgId: project.orgId
|
||||
});
|
||||
|
||||
return kmsKey;
|
||||
};
|
||||
|
||||
return {
|
||||
create,
|
||||
get,
|
||||
activate,
|
||||
getAttributes,
|
||||
destroy,
|
||||
revoke,
|
||||
locate,
|
||||
register
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TKmipOrgConfigDALFactory = ReturnType<typeof kmipOrgConfigDALFactory>;
|
||||
|
||||
export const kmipOrgConfigDALFactory = (db: TDbClient) => {
|
||||
const kmipOrgConfigOrm = ormify(db, TableName.KmipOrgConfig);
|
||||
|
||||
return kmipOrgConfigOrm;
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TKmipOrgServerCertificateDALFactory = ReturnType<typeof kmipOrgServerCertificateDALFactory>;
|
||||
|
||||
export const kmipOrgServerCertificateDALFactory = (db: TDbClient) => {
|
||||
const kmipOrgServerCertificateOrm = ormify(db, TableName.KmipOrgServerCertificates);
|
||||
|
||||
return kmipOrgServerCertificateOrm;
|
||||
};
|
||||
@@ -0,0 +1,817 @@
|
||||
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 { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||
import { isValidHostname, isValidIp } from "@app/lib/ip";
|
||||
import { constructPemChainFromCerts } from "@app/services/certificate/certificate-fns";
|
||||
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 { 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";
|
||||
import { TKmipClientCertificateDALFactory } from "./kmip-client-certificate-dal";
|
||||
import { TKmipClientDALFactory } from "./kmip-client-dal";
|
||||
import { TKmipOrgConfigDALFactory } from "./kmip-org-config-dal";
|
||||
import { TKmipOrgServerCertificateDALFactory } from "./kmip-org-server-certificate-dal";
|
||||
import {
|
||||
TCreateKmipClientCertificateDTO,
|
||||
TCreateKmipClientDTO,
|
||||
TDeleteKmipClientDTO,
|
||||
TGenerateOrgKmipServerCertificateDTO,
|
||||
TGetKmipClientDTO,
|
||||
TGetOrgKmipDTO,
|
||||
TListKmipClientsByProjectIdDTO,
|
||||
TRegisterServerDTO,
|
||||
TSetupOrgKmipDTO,
|
||||
TUpdateKmipClientDTO
|
||||
} from "./kmip-types";
|
||||
|
||||
type TKmipServiceFactoryDep = {
|
||||
kmipClientDAL: TKmipClientDALFactory;
|
||||
kmipClientCertificateDAL: TKmipClientCertificateDALFactory;
|
||||
kmipOrgServerCertificateDAL: TKmipOrgServerCertificateDALFactory;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
kmipOrgConfigDAL: TKmipOrgConfigDALFactory;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
export type TKmipServiceFactory = ReturnType<typeof kmipServiceFactory>;
|
||||
|
||||
export const kmipServiceFactory = ({
|
||||
kmipClientDAL,
|
||||
permissionService,
|
||||
kmipClientCertificateDAL,
|
||||
kmipOrgConfigDAL,
|
||||
kmsService,
|
||||
kmipOrgServerCertificateDAL,
|
||||
licenseService
|
||||
}: TKmipServiceFactoryDep) => {
|
||||
const createKmipClient = async ({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectId,
|
||||
name,
|
||||
description,
|
||||
permissions
|
||||
}: TCreateKmipClientDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionKmipActions.CreateClients,
|
||||
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,
|
||||
description,
|
||||
permissions
|
||||
});
|
||||
|
||||
return kmipClient;
|
||||
};
|
||||
|
||||
const updateKmipClient = async ({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
name,
|
||||
description,
|
||||
permissions,
|
||||
id
|
||||
}: TUpdateKmipClientDTO) => {
|
||||
const kmipClient = await kmipClientDAL.findById(id);
|
||||
|
||||
if (!kmipClient) {
|
||||
throw new NotFoundError({
|
||||
message: `KMIP client with ID ${id} does not exist`
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
projectId: kmipClient.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionKmipActions.UpdateClients,
|
||||
ProjectPermissionSub.Kmip
|
||||
);
|
||||
|
||||
const updatedKmipClient = await kmipClientDAL.updateById(id, {
|
||||
name,
|
||||
description,
|
||||
permissions
|
||||
});
|
||||
|
||||
return updatedKmipClient;
|
||||
};
|
||||
|
||||
const deleteKmipClient = async ({ actor, actorId, actorOrgId, actorAuthMethod, id }: TDeleteKmipClientDTO) => {
|
||||
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({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: kmipClient.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionKmipActions.DeleteClients,
|
||||
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;
|
||||
};
|
||||
|
||||
const getKmipClient = async ({ actor, actorId, actorOrgId, actorAuthMethod, id }: TGetKmipClientDTO) => {
|
||||
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({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: kmipClient.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip);
|
||||
|
||||
return kmipClient;
|
||||
};
|
||||
|
||||
const listKmipClientsByProjectId = async ({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectId,
|
||||
...rest
|
||||
}: TListKmipClientsByProjectIdDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip);
|
||||
|
||||
return kmipClientDAL.findByProjectId({ projectId, ...rest });
|
||||
};
|
||||
|
||||
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 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,
|
||||
projectId: kmipClient.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionKmipActions.GenerateClientCertificates,
|
||||
ProjectPermissionSub.Kmip
|
||||
);
|
||||
|
||||
const kmipConfig = await kmipOrgConfigDAL.findOne({
|
||||
orgId: actorOrgId
|
||||
});
|
||||
|
||||
if (!kmipConfig) {
|
||||
throw new InternalServerError({
|
||||
message: "KMIP has not been configured for the organization"
|
||||
});
|
||||
}
|
||||
|
||||
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.Organization,
|
||||
orgId: actorOrgId
|
||||
});
|
||||
|
||||
const caCertObj = new x509.X509Certificate(
|
||||
decryptor({ cipherTextBlob: kmipConfig.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(kmipConfig.caKeyAlgorithm as CertKeyAlgorithm);
|
||||
|
||||
const caSkObj = crypto.createPrivateKey({
|
||||
key: decryptor({ cipherTextBlob: kmipConfig.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 rootCaCert = new x509.X509Certificate(decryptor({ cipherTextBlob: kmipConfig.encryptedRootCaCertificate }));
|
||||
const serverIntermediateCaCert = new x509.X509Certificate(
|
||||
decryptor({ cipherTextBlob: kmipConfig.encryptedServerIntermediateCaCertificate })
|
||||
);
|
||||
|
||||
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: constructPemChainFromCerts([serverIntermediateCaCert, rootCaCert]),
|
||||
projectId: kmipClient.projectId
|
||||
};
|
||||
};
|
||||
|
||||
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,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Setup, OrgPermissionSubjects.Kmip);
|
||||
|
||||
const kmipConfig = await kmipOrgConfigDAL.findOne({
|
||||
orgId: actorOrgId
|
||||
});
|
||||
|
||||
if (kmipConfig) {
|
||||
throw new BadRequestError({
|
||||
message: "KMIP has already been configured for the organization"
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
const rootCaSerialNumber = createSerialNumber();
|
||||
const rootCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||
const rootCaSkObj = KeyObject.from(rootCaKeys.privateKey);
|
||||
const rootCaIssuedAt = new Date();
|
||||
const rootCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 20));
|
||||
|
||||
const rootCaCert = await x509.X509CertificateGenerator.createSelfSigned({
|
||||
name: `CN=KMIP Root CA,OU=${actorOrgId}`,
|
||||
serialNumber: rootCaSerialNumber,
|
||||
notBefore: rootCaIssuedAt,
|
||||
notAfter: rootCaExpiration,
|
||||
signingAlgorithm: alg,
|
||||
keys: rootCaKeys,
|
||||
extensions: [
|
||||
// eslint-disable-next-line no-bitwise
|
||||
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
|
||||
await x509.SubjectKeyIdentifierExtension.create(rootCaKeys.publicKey)
|
||||
]
|
||||
});
|
||||
|
||||
// generate intermediate server CA
|
||||
const serverIntermediateCaSerialNumber = createSerialNumber();
|
||||
const serverIntermediateCaIssuedAt = new Date();
|
||||
const serverIntermediateCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 10));
|
||||
const serverIntermediateCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||
const serverIntermediateCaSkObj = KeyObject.from(serverIntermediateCaKeys.privateKey);
|
||||
|
||||
const serverIntermediateCaCert = await x509.X509CertificateGenerator.create({
|
||||
serialNumber: serverIntermediateCaSerialNumber,
|
||||
subject: `CN=KMIP Server Intermediate CA,OU=${actorOrgId}`,
|
||||
issuer: rootCaCert.subject,
|
||||
notBefore: serverIntermediateCaIssuedAt,
|
||||
notAfter: serverIntermediateCaExpiration,
|
||||
signingKey: rootCaKeys.privateKey,
|
||||
publicKey: serverIntermediateCaKeys.publicKey,
|
||||
signingAlgorithm: alg,
|
||||
extensions: [
|
||||
new x509.KeyUsagesExtension(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
x509.KeyUsageFlags.keyCertSign |
|
||||
x509.KeyUsageFlags.cRLSign |
|
||||
x509.KeyUsageFlags.digitalSignature |
|
||||
x509.KeyUsageFlags.keyEncipherment,
|
||||
true
|
||||
),
|
||||
new x509.BasicConstraintsExtension(true, 0, true),
|
||||
await x509.AuthorityKeyIdentifierExtension.create(rootCaCert, false),
|
||||
await x509.SubjectKeyIdentifierExtension.create(serverIntermediateCaKeys.publicKey)
|
||||
]
|
||||
});
|
||||
|
||||
// generate intermediate client CA
|
||||
const clientIntermediateCaSerialNumber = createSerialNumber();
|
||||
const clientIntermediateCaIssuedAt = new Date();
|
||||
const clientIntermediateCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 10));
|
||||
const clientIntermediateCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||
const clientIntermediateCaSkObj = KeyObject.from(clientIntermediateCaKeys.privateKey);
|
||||
|
||||
const clientIntermediateCaCert = await x509.X509CertificateGenerator.create({
|
||||
serialNumber: clientIntermediateCaSerialNumber,
|
||||
subject: `CN=KMIP Client Intermediate CA,OU=${actorOrgId}`,
|
||||
issuer: rootCaCert.subject,
|
||||
notBefore: clientIntermediateCaIssuedAt,
|
||||
notAfter: clientIntermediateCaExpiration,
|
||||
signingKey: rootCaKeys.privateKey,
|
||||
publicKey: clientIntermediateCaKeys.publicKey,
|
||||
signingAlgorithm: alg,
|
||||
extensions: [
|
||||
new x509.KeyUsagesExtension(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
x509.KeyUsageFlags.keyCertSign |
|
||||
x509.KeyUsageFlags.cRLSign |
|
||||
x509.KeyUsageFlags.digitalSignature |
|
||||
x509.KeyUsageFlags.keyEncipherment,
|
||||
true
|
||||
),
|
||||
new x509.BasicConstraintsExtension(true, 0, true),
|
||||
await x509.AuthorityKeyIdentifierExtension.create(rootCaCert, false),
|
||||
await x509.SubjectKeyIdentifierExtension.create(clientIntermediateCaKeys.publicKey)
|
||||
]
|
||||
});
|
||||
|
||||
const { encryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.Organization,
|
||||
orgId: actorOrgId
|
||||
});
|
||||
|
||||
await kmipOrgConfigDAL.create({
|
||||
orgId: actorOrgId,
|
||||
caKeyAlgorithm,
|
||||
rootCaIssuedAt,
|
||||
rootCaExpiration,
|
||||
rootCaSerialNumber,
|
||||
encryptedRootCaCertificate: encryptor({ plainText: Buffer.from(rootCaCert.rawData) }).cipherTextBlob,
|
||||
encryptedRootCaPrivateKey: encryptor({
|
||||
plainText: rootCaSkObj.export({
|
||||
type: "pkcs8",
|
||||
format: "der"
|
||||
})
|
||||
}).cipherTextBlob,
|
||||
serverIntermediateCaIssuedAt,
|
||||
serverIntermediateCaExpiration,
|
||||
serverIntermediateCaSerialNumber,
|
||||
encryptedServerIntermediateCaCertificate: encryptor({
|
||||
plainText: Buffer.from(new Uint8Array(serverIntermediateCaCert.rawData))
|
||||
}).cipherTextBlob,
|
||||
encryptedServerIntermediateCaChain: encryptor({ plainText: Buffer.from(rootCaCert.toString("pem")) })
|
||||
.cipherTextBlob,
|
||||
encryptedServerIntermediateCaPrivateKey: encryptor({
|
||||
plainText: serverIntermediateCaSkObj.export({
|
||||
type: "pkcs8",
|
||||
format: "der"
|
||||
})
|
||||
}).cipherTextBlob,
|
||||
clientIntermediateCaIssuedAt,
|
||||
clientIntermediateCaExpiration,
|
||||
clientIntermediateCaSerialNumber,
|
||||
encryptedClientIntermediateCaCertificate: encryptor({
|
||||
plainText: Buffer.from(new Uint8Array(clientIntermediateCaCert.rawData))
|
||||
}).cipherTextBlob,
|
||||
encryptedClientIntermediateCaChain: encryptor({ plainText: Buffer.from(rootCaCert.toString("pem")) })
|
||||
.cipherTextBlob,
|
||||
encryptedClientIntermediateCaPrivateKey: encryptor({
|
||||
plainText: clientIntermediateCaSkObj.export({
|
||||
type: "pkcs8",
|
||||
format: "der"
|
||||
})
|
||||
}).cipherTextBlob
|
||||
});
|
||||
|
||||
return {
|
||||
serverCertificateChain: constructPemChainFromCerts([serverIntermediateCaCert, rootCaCert]),
|
||||
clientCertificateChain: constructPemChainFromCerts([clientIntermediateCaCert, rootCaCert])
|
||||
};
|
||||
};
|
||||
|
||||
const getOrgKmip = async ({ actorOrgId, actor, actorId, actorAuthMethod }: TGetOrgKmipDTO) => {
|
||||
await permissionService.getOrgPermission(actor, actorId, actorOrgId, actorAuthMethod, actorOrgId);
|
||||
|
||||
const kmipConfig = await kmipOrgConfigDAL.findOne({
|
||||
orgId: actorOrgId
|
||||
});
|
||||
|
||||
if (!kmipConfig) {
|
||||
throw new BadRequestError({
|
||||
message: "KMIP has not been configured for the organization"
|
||||
});
|
||||
}
|
||||
|
||||
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.Organization,
|
||||
orgId: actorOrgId
|
||||
});
|
||||
|
||||
const rootCaCert = new x509.X509Certificate(decryptor({ cipherTextBlob: kmipConfig.encryptedRootCaCertificate }));
|
||||
const serverIntermediateCaCert = new x509.X509Certificate(
|
||||
decryptor({ cipherTextBlob: kmipConfig.encryptedServerIntermediateCaCertificate })
|
||||
);
|
||||
|
||||
const clientIntermediateCaCert = new x509.X509Certificate(
|
||||
decryptor({ cipherTextBlob: kmipConfig.encryptedClientIntermediateCaCertificate })
|
||||
);
|
||||
|
||||
return {
|
||||
id: kmipConfig.id,
|
||||
serverCertificateChain: constructPemChainFromCerts([serverIntermediateCaCert, rootCaCert]),
|
||||
clientCertificateChain: constructPemChainFromCerts([clientIntermediateCaCert, rootCaCert])
|
||||
};
|
||||
};
|
||||
|
||||
const generateOrgKmipServerCertificate = async ({
|
||||
orgId,
|
||||
ttl,
|
||||
commonName,
|
||||
altNames,
|
||||
keyAlgorithm
|
||||
}: TGenerateOrgKmipServerCertificateDTO) => {
|
||||
const kmipOrgConfig = await kmipOrgConfigDAL.findOne({
|
||||
orgId
|
||||
});
|
||||
|
||||
if (!kmipOrgConfig) {
|
||||
throw new BadRequestError({
|
||||
message: "KMIP has not been configured for the organization"
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
const caCertObj = new x509.X509Certificate(
|
||||
decryptor({ cipherTextBlob: kmipOrgConfig.encryptedServerIntermediateCaCertificate })
|
||||
);
|
||||
|
||||
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],
|
||||
true
|
||||
),
|
||||
new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage[CertExtendedKeyUsage.SERVER_AUTH]], true)
|
||||
];
|
||||
|
||||
const altNamesArray: {
|
||||
type: "email" | "dns" | "ip";
|
||||
value: string;
|
||||
}[] = altNames
|
||||
.split(",")
|
||||
.map((name) => name.trim())
|
||||
.map((altName) => {
|
||||
if (isValidHostname(altName)) {
|
||||
return {
|
||||
type: "dns",
|
||||
value: altName
|
||||
};
|
||||
}
|
||||
|
||||
if (isValidIp(altName)) {
|
||||
return {
|
||||
type: "ip",
|
||||
value: altName
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Invalid altName: ${altName}`);
|
||||
});
|
||||
|
||||
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
||||
extensions.push(altNamesExtension);
|
||||
|
||||
const caAlg = keyAlgorithmToAlgCfg(kmipOrgConfig.caKeyAlgorithm as CertKeyAlgorithm);
|
||||
|
||||
const decryptedCaCertChain = decryptor({
|
||||
cipherTextBlob: kmipOrgConfig.encryptedServerIntermediateCaChain
|
||||
}).toString("utf-8");
|
||||
|
||||
const caSkObj = crypto.createPrivateKey({
|
||||
key: decryptor({ cipherTextBlob: kmipOrgConfig.encryptedServerIntermediateCaPrivateKey }),
|
||||
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: `CN=${commonName}`,
|
||||
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 kmipOrgServerCertificateDAL.create({
|
||||
orgId,
|
||||
keyAlgorithm,
|
||||
issuedAt: notBeforeDate,
|
||||
expiration: notAfterDate,
|
||||
serialNumber,
|
||||
commonName,
|
||||
altNames,
|
||||
encryptedCertificate: encryptor({ plainText: Buffer.from(new Uint8Array(leafCert.rawData)) }).cipherTextBlob,
|
||||
encryptedChain: encryptor({ plainText: Buffer.from(certificateChain) }).cipherTextBlob
|
||||
});
|
||||
|
||||
return {
|
||||
serialNumber,
|
||||
privateKey: skLeafObj.export({ format: "pem", type: "pkcs8" }) as string,
|
||||
certificate: leafCert.toString("pem"),
|
||||
certificateChain
|
||||
};
|
||||
};
|
||||
|
||||
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,
|
||||
deleteKmipClient,
|
||||
getKmipClient,
|
||||
listKmipClientsByProjectId,
|
||||
createKmipClientCertificate,
|
||||
setupOrgKmip,
|
||||
generateOrgKmipServerCertificate,
|
||||
getOrgKmip,
|
||||
getServerCertificateBySerialNumber,
|
||||
registerServer
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
|
||||
import { OrderByDirection, TOrgPermission, TProjectPermission } from "@app/lib/types";
|
||||
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
||||
|
||||
import { KmipPermission } from "./kmip-enum";
|
||||
|
||||
export type TCreateKmipClientCertificateDTO = {
|
||||
clientId: string;
|
||||
keyAlgorithm: CertKeyAlgorithm;
|
||||
ttl: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TCreateKmipClientDTO = {
|
||||
name: string;
|
||||
description?: string;
|
||||
permissions: KmipPermission[];
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TUpdateKmipClientDTO = {
|
||||
id: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
permissions?: KmipPermission[];
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TDeleteKmipClientDTO = {
|
||||
id: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TGetKmipClientDTO = {
|
||||
id: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export enum KmipClientOrderBy {
|
||||
Name = "name"
|
||||
}
|
||||
|
||||
export type TListKmipClientsByProjectIdDTO = {
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
orderBy?: KmipClientOrderBy;
|
||||
orderDirection?: OrderByDirection;
|
||||
search?: string;
|
||||
} & TProjectPermission;
|
||||
|
||||
type KmipOperationBaseDTO = {
|
||||
clientId: string;
|
||||
projectId: string;
|
||||
} & Omit<TOrgPermission, "orgId">;
|
||||
|
||||
export type TKmipCreateDTO = {
|
||||
algorithm: SymmetricEncryption;
|
||||
} & KmipOperationBaseDTO;
|
||||
|
||||
export type TKmipGetDTO = {
|
||||
id: string;
|
||||
} & KmipOperationBaseDTO;
|
||||
|
||||
export type TKmipGetAttributesDTO = {
|
||||
id: string;
|
||||
} & KmipOperationBaseDTO;
|
||||
|
||||
export type TKmipDestroyDTO = {
|
||||
id: string;
|
||||
} & KmipOperationBaseDTO;
|
||||
|
||||
export type TKmipActivateDTO = {
|
||||
id: string;
|
||||
} & KmipOperationBaseDTO;
|
||||
|
||||
export type TKmipRevokeDTO = {
|
||||
id: string;
|
||||
} & KmipOperationBaseDTO;
|
||||
|
||||
export type TKmipLocateDTO = KmipOperationBaseDTO;
|
||||
|
||||
export type TKmipRegisterDTO = {
|
||||
name: string;
|
||||
key: string;
|
||||
algorithm: SymmetricEncryption;
|
||||
} & KmipOperationBaseDTO;
|
||||
|
||||
export type TSetupOrgKmipDTO = {
|
||||
caKeyAlgorithm: CertKeyAlgorithm;
|
||||
} & Omit<TOrgPermission, "orgId">;
|
||||
|
||||
export type TGetOrgKmipDTO = Omit<TOrgPermission, "orgId">;
|
||||
|
||||
export type TGenerateOrgKmipServerCertificateDTO = {
|
||||
commonName: string;
|
||||
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 = {
|
||||
|
||||
@@ -23,6 +23,11 @@ export enum OrgPermissionAppConnectionActions {
|
||||
Connect = "connect"
|
||||
}
|
||||
|
||||
export enum OrgPermissionKmipActions {
|
||||
Proxy = "proxy",
|
||||
Setup = "setup"
|
||||
}
|
||||
|
||||
export enum OrgPermissionAdminConsoleAction {
|
||||
AccessAllProjects = "access-all-projects"
|
||||
}
|
||||
@@ -44,7 +49,8 @@ export enum OrgPermissionSubjects {
|
||||
AdminConsole = "organization-admin-console",
|
||||
AuditLogs = "audit-logs",
|
||||
ProjectTemplates = "project-templates",
|
||||
AppConnections = "app-connections"
|
||||
AppConnections = "app-connections",
|
||||
Kmip = "kmip"
|
||||
}
|
||||
|
||||
export type AppConnectionSubjectFields = {
|
||||
@@ -74,7 +80,8 @@ export type OrgPermissionSet =
|
||||
| (ForcedSubject<OrgPermissionSubjects.AppConnections> & AppConnectionSubjectFields)
|
||||
)
|
||||
]
|
||||
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole];
|
||||
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
|
||||
| [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip];
|
||||
|
||||
const AppConnectionConditionSchema = z
|
||||
.object({
|
||||
@@ -167,6 +174,12 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [
|
||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionAdminConsoleAction).describe(
|
||||
"Describe what action an entity can take."
|
||||
)
|
||||
}),
|
||||
z.object({
|
||||
subject: z.literal(OrgPermissionSubjects.Kmip).describe("The entity this permission pertains to."),
|
||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionKmipActions).describe(
|
||||
"Describe what action an entity can take."
|
||||
)
|
||||
})
|
||||
]);
|
||||
|
||||
@@ -253,6 +266,11 @@ const buildAdminPermission = () => {
|
||||
|
||||
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
@@ -44,6 +44,14 @@ export enum ProjectPermissionSecretSyncActions {
|
||||
RemoveSecrets = "remove-secrets"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionKmipActions {
|
||||
CreateClients = "create-clients",
|
||||
UpdateClients = "update-clients",
|
||||
DeleteClients = "delete-clients",
|
||||
ReadClients = "read-clients",
|
||||
GenerateClientCertificates = "generate-client-certificates"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionSub {
|
||||
Role = "role",
|
||||
Member = "member",
|
||||
@@ -75,7 +83,8 @@ export enum ProjectPermissionSub {
|
||||
PkiCollections = "pki-collections",
|
||||
Kms = "kms",
|
||||
Cmek = "cmek",
|
||||
SecretSyncs = "secret-syncs"
|
||||
SecretSyncs = "secret-syncs",
|
||||
Kmip = "kmip"
|
||||
}
|
||||
|
||||
export type SecretSubjectFields = {
|
||||
@@ -156,6 +165,7 @@ export type ProjectPermissionSet =
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
||||
| [ProjectPermissionSecretSyncActions, ProjectPermissionSub.SecretSyncs]
|
||||
| [ProjectPermissionKmipActions, ProjectPermissionSub.Kmip]
|
||||
| [ProjectPermissionCmekActions, ProjectPermissionSub.Cmek]
|
||||
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
||||
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Project]
|
||||
@@ -410,6 +420,12 @@ const GeneralPermissionSchema = [
|
||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretSyncActions).describe(
|
||||
"Describe what action an entity can take."
|
||||
)
|
||||
}),
|
||||
z.object({
|
||||
subject: z.literal(ProjectPermissionSub.Kmip).describe("The entity this permission pertains to."),
|
||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionKmipActions).describe(
|
||||
"Describe what action an entity can take."
|
||||
)
|
||||
})
|
||||
];
|
||||
|
||||
@@ -575,6 +591,18 @@ const buildAdminPermissionRules = () => {
|
||||
],
|
||||
ProjectPermissionSub.SecretSyncs
|
||||
);
|
||||
|
||||
can(
|
||||
[
|
||||
ProjectPermissionKmipActions.CreateClients,
|
||||
ProjectPermissionKmipActions.UpdateClients,
|
||||
ProjectPermissionKmipActions.DeleteClients,
|
||||
ProjectPermissionKmipActions.ReadClients,
|
||||
ProjectPermissionKmipActions.GenerateClientCertificates
|
||||
],
|
||||
ProjectPermissionSub.Kmip
|
||||
);
|
||||
|
||||
return rules;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user