Merge pull request #3093 from Infisical/cmek-additions

Improvement: CMEK Additions and Normalization
This commit is contained in:
Scott Wilson
2025-02-07 10:04:55 -08:00
committed by GitHub
17 changed files with 422 additions and 143 deletions
@@ -1,4 +1,5 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { TableName } from "../schemas"; import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
@@ -0,0 +1,27 @@
import { Knex } from "knex";
import { TableName } from "@app/db/schemas";
export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.KmsKey)) {
const hasSlugCol = await knex.schema.hasColumn(TableName.KmsKey, "slug");
if (hasSlugCol) {
await knex.schema.alterTable(TableName.KmsKey, (t) => {
t.dropColumn("slug");
});
}
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.KmsKey)) {
const hasSlugCol = await knex.schema.hasColumn(TableName.KmsKey, "slug");
if (!hasSlugCol) {
await knex.schema.alterTable(TableName.KmsKey, (t) => {
t.string("slug", 32);
});
}
}
}
+1 -2
View File
@@ -16,8 +16,7 @@ export const KmsKeysSchema = z.object({
name: z.string(), name: z.string(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
projectId: z.string().nullable().optional(), projectId: z.string().nullable().optional()
slug: z.string().nullable().optional()
}); });
export type TKmsKeys = z.infer<typeof KmsKeysSchema>; export type TKmsKeys = z.infer<typeof KmsKeysSchema>;
@@ -223,6 +223,7 @@ export enum EventType {
UPDATE_CMEK = "update-cmek", UPDATE_CMEK = "update-cmek",
DELETE_CMEK = "delete-cmek", DELETE_CMEK = "delete-cmek",
GET_CMEKS = "get-cmeks", GET_CMEKS = "get-cmeks",
GET_CMEK = "get-cmek",
CMEK_ENCRYPT = "cmek-encrypt", CMEK_ENCRYPT = "cmek-encrypt",
CMEK_DECRYPT = "cmek-decrypt", CMEK_DECRYPT = "cmek-decrypt",
UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS = "update-external-group-org-role-mapping", UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS = "update-external-group-org-role-mapping",
@@ -1847,6 +1848,13 @@ interface GetCmeksEvent {
}; };
} }
interface GetCmekEvent {
type: EventType.GET_CMEK;
metadata: {
keyId: string;
};
}
interface CmekEncryptEvent { interface CmekEncryptEvent {
type: EventType.CMEK_ENCRYPT; type: EventType.CMEK_ENCRYPT;
metadata: { metadata: {
@@ -2237,6 +2245,7 @@ export type Event =
| CreateCmekEvent | CreateCmekEvent
| UpdateCmekEvent | UpdateCmekEvent
| DeleteCmekEvent | DeleteCmekEvent
| GetCmekEvent
| GetCmeksEvent | GetCmeksEvent
| CmekEncryptEvent | CmekEncryptEvent
| CmekDecryptEvent | CmekDecryptEvent
+7
View File
@@ -1593,6 +1593,13 @@ export const KMS = {
orderDirection: "The direction to order keys in.", orderDirection: "The direction to order keys in.",
search: "The text string to filter key names by." search: "The text string to filter key names by."
}, },
GET_KEY_BY_ID: {
keyId: "The ID of the KMS key to retrieve."
},
GET_KEY_BY_NAME: {
keyName: "The name of the KMS key to retrieve.",
projectId: "The ID of the project the key belongs to."
},
ENCRYPT: { ENCRYPT: {
keyId: "The ID of the key to encrypt the data with.", keyId: "The ID of the key to encrypt the data with.",
plaintext: "The plaintext to be encrypted (base64 encoded)." plaintext: "The plaintext to be encrypted (base64 encoded)."
+1
View File
@@ -7,6 +7,7 @@ import { buildDynamicKnexQuery, TKnexDynamicOperator } from "./dynamic";
export * from "./connection"; export * from "./connection";
export * from "./join"; export * from "./join";
export * from "./prependTableNameToFindFilter";
export * from "./select"; export * from "./select";
export const withTransaction = <K extends object>(db: Knex, dal: K) => ({ export const withTransaction = <K extends object>(db: Knex, dal: K) => ({
@@ -0,0 +1,13 @@
import { TableName } from "@app/db/schemas";
import { buildFindFilter } from "@app/lib/knex/index";
type TFindFilterParameters = Parameters<typeof buildFindFilter<object>>[0];
export const prependTableNameToFindFilter = (tableName: TableName, filterObj: object): TFindFilterParameters =>
Object.fromEntries(
Object.entries(filterObj).map(([key, value]) =>
key.startsWith("$")
? [key, prependTableNameToFindFilter(tableName, value as object)]
: [`${tableName}.${key}`, value]
)
);
+94 -4
View File
@@ -15,6 +15,10 @@ import { CmekOrderBy } from "@app/services/cmek/cmek-types";
const keyNameSchema = slugSchema({ min: 1, max: 32, field: "Name" }); const keyNameSchema = slugSchema({ min: 1, max: 32, field: "Name" });
const keyDescriptionSchema = z.string().trim().max(500).optional(); const keyDescriptionSchema = z.string().trim().max(500).optional();
const CmekSchema = KmsKeysSchema.merge(InternalKmsSchema.pick({ version: true, encryptionAlgorithm: true })).omit({
isReserved: true
});
const base64Schema = z.string().superRefine((val, ctx) => { const base64Schema = z.string().superRefine((val, ctx) => {
if (!isBase64(val)) { if (!isBase64(val)) {
ctx.addIssue({ ctx.addIssue({
@@ -53,7 +57,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
}), }),
response: { response: {
200: z.object({ 200: z.object({
key: KmsKeysSchema key: CmekSchema
}) })
} }
}, },
@@ -106,7 +110,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
}), }),
response: { response: {
200: z.object({ 200: z.object({
key: KmsKeysSchema key: CmekSchema
}) })
} }
}, },
@@ -150,7 +154,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
}), }),
response: { response: {
200: z.object({ 200: z.object({
key: KmsKeysSchema key: CmekSchema
}) })
} }
}, },
@@ -201,7 +205,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
}), }),
response: { response: {
200: z.object({ 200: z.object({
keys: KmsKeysSchema.merge(InternalKmsSchema.pick({ version: true, encryptionAlgorithm: true })).array(), keys: CmekSchema.array(),
totalCount: z.number() totalCount: z.number()
}) })
} }
@@ -230,6 +234,92 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
method: "GET",
url: "/keys/:keyId",
config: {
rateLimit: readLimit
},
schema: {
description: "Get KMS key by ID",
params: z.object({
keyId: z.string().uuid().describe(KMS.GET_KEY_BY_ID.keyId)
}),
response: {
200: z.object({
key: CmekSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
params: { keyId },
permission
} = req;
const key = await server.services.cmek.findCmekById(keyId, permission);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: key.projectId!,
event: {
type: EventType.GET_CMEK,
metadata: {
keyId: key.id
}
}
});
return { key };
}
});
server.route({
method: "GET",
url: "/keys/key-name/:keyName",
config: {
rateLimit: readLimit
},
schema: {
description: "Get KMS key by Name",
params: z.object({
keyName: slugSchema({ field: "Key name" }).describe(KMS.GET_KEY_BY_NAME.keyName)
}),
querystring: z.object({
projectId: z.string().min(1, "Project ID is required").describe(KMS.GET_KEY_BY_NAME.projectId)
}),
response: {
200: z.object({
key: CmekSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
params: { keyName },
query: { projectId },
permission
} = req;
const key = await server.services.cmek.findCmekByName(keyName, projectId, permission);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: key.projectId!,
event: {
type: EventType.GET_CMEK,
metadata: {
keyId: key.id
}
}
});
return { key };
}
});
// encrypt data // encrypt data
server.route({ server.route({
method: "POST", method: "POST",
+91 -17
View File
@@ -3,7 +3,8 @@ import { ForbiddenError } from "@casl/ability";
import { ActionProjectType, ProjectType } from "@app/db/schemas"; import { ActionProjectType, ProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionCmekActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionCmekActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { DatabaseErrorCode } from "@app/lib/error-codes";
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
import { OrgServiceActor } from "@app/lib/types"; import { OrgServiceActor } from "@app/lib/types";
import { import {
TCmekDecryptDTO, TCmekDecryptDTO,
@@ -44,17 +45,31 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Create, ProjectPermissionSub.Cmek); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Create, ProjectPermissionSub.Cmek);
const cmek = await kmsService.generateKmsKey({ try {
...dto, const cmek = await kmsService.generateKmsKey({
projectId, ...dto,
isReserved: false projectId,
}); isReserved: false
});
return cmek; return {
...cmek,
version: 1,
encryptionAlgorithm: dto.encryptionAlgorithm
};
} catch (err) {
if (err instanceof DatabaseError && (err.error as { code: string })?.code === DatabaseErrorCode.UniqueViolation) {
throw new BadRequestError({
message: `A KMS key with the name "${dto.name}" already exists for the project with ID "${projectId}"`
});
}
throw err;
}
}; };
const updateCmekById = async ({ keyId, ...data }: TUpdabteCmekByIdDTO, actor: OrgServiceActor) => { const updateCmekById = async ({ keyId, ...data }: TUpdabteCmekByIdDTO, actor: OrgServiceActor) => {
const key = await kmsDAL.findById(keyId); const key = await kmsDAL.findCmekById(keyId);
if (!key) throw new NotFoundError({ message: `Key with ID ${keyId} not found` }); if (!key) throw new NotFoundError({ message: `Key with ID ${keyId} not found` });
@@ -71,13 +86,27 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Edit, ProjectPermissionSub.Cmek); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Edit, ProjectPermissionSub.Cmek);
const cmek = await kmsDAL.updateById(keyId, data); try {
const cmek = await kmsDAL.updateById(keyId, data);
return cmek; return {
...cmek,
version: key.version,
encryptionAlgorithm: key.encryptionAlgorithm
};
} catch (err) {
if (err instanceof DatabaseError && (err.error as { code: string })?.code === DatabaseErrorCode.UniqueViolation) {
throw new BadRequestError({
message: `A KMS key with the name "${data.name!}" already exists for the project with ID "${key.projectId}"`
});
}
throw err;
}
}; };
const deleteCmekById = async (keyId: string, actor: OrgServiceActor) => { const deleteCmekById = async (keyId: string, actor: OrgServiceActor) => {
const key = await kmsDAL.findById(keyId); const key = await kmsDAL.findCmekById(keyId);
if (!key) throw new NotFoundError({ message: `Key with ID ${keyId} not found` }); if (!key) throw new NotFoundError({ message: `Key with ID ${keyId} not found` });
@@ -94,9 +123,9 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Delete, ProjectPermissionSub.Cmek); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Delete, ProjectPermissionSub.Cmek);
const cmek = kmsDAL.deleteById(keyId); await kmsDAL.deleteById(keyId);
return cmek; return key;
}; };
const listCmeksByProjectId = async ( const listCmeksByProjectId = async (
@@ -120,15 +149,58 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
const { keys: cmeks, totalCount } = await kmsDAL.findKmsKeysByProjectId({ projectId, ...filters }); const { keys: cmeks, totalCount } = await kmsDAL.listCmeksByProjectId({ projectId, ...filters });
return { cmeks, totalCount }; return { cmeks, totalCount };
}; };
const findCmekById = async (keyId: string, actor: OrgServiceActor) => {
const key = await kmsDAL.findCmekById(keyId);
if (!key) throw new NotFoundError({ message: `Key with ID "${keyId}" not found` });
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
return key;
};
const findCmekByName = async (keyName: string, projectId: string, actor: OrgServiceActor) => {
const key = await kmsDAL.findCmekByName(keyName, projectId);
if (!key)
throw new NotFoundError({ message: `Key with name "${keyName}" not found for project with ID "${projectId}"` });
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
return key;
};
const cmekEncrypt = async ({ keyId, plaintext }: TCmekEncryptDTO, actor: OrgServiceActor) => { const cmekEncrypt = async ({ keyId, plaintext }: TCmekEncryptDTO, actor: OrgServiceActor) => {
const key = await kmsDAL.findById(keyId); const key = await kmsDAL.findById(keyId);
if (!key) throw new NotFoundError({ message: `Key with ID ${keyId} not found` }); if (!key) throw new NotFoundError({ message: `Key with ID "${keyId}" not found` });
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" }); if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
@@ -155,7 +227,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
const cmekDecrypt = async ({ keyId, ciphertext }: TCmekDecryptDTO, actor: OrgServiceActor) => { const cmekDecrypt = async ({ keyId, ciphertext }: TCmekDecryptDTO, actor: OrgServiceActor) => {
const key = await kmsDAL.findById(keyId); const key = await kmsDAL.findById(keyId);
if (!key) throw new NotFoundError({ message: `Key with ID ${keyId} not found` }); if (!key) throw new NotFoundError({ message: `Key with ID "${keyId}" not found` });
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" }); if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
@@ -185,6 +257,8 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
deleteCmekById, deleteCmekById,
listCmeksByProjectId, listCmeksByProjectId,
cmekEncrypt, cmekEncrypt,
cmekDecrypt cmekDecrypt,
findCmekById,
findCmekByName
}; };
}; };
+52 -3
View File
@@ -3,12 +3,32 @@ import { Knex } from "knex";
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { KmsKeysSchema, TableName, TInternalKms, TKmsKeys } from "@app/db/schemas"; import { KmsKeysSchema, TableName, TInternalKms, TKmsKeys } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors"; import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex"; import { buildFindFilter, ormify, prependTableNameToFindFilter, selectAllTableCols } from "@app/lib/knex";
import { OrderByDirection } from "@app/lib/types"; import { OrderByDirection } from "@app/lib/types";
import { CmekOrderBy, TListCmeksByProjectIdDTO } from "@app/services/cmek/cmek-types"; import { CmekOrderBy, TListCmeksByProjectIdDTO } from "@app/services/cmek/cmek-types";
export type TKmsKeyDALFactory = ReturnType<typeof kmskeyDALFactory>; export type TKmsKeyDALFactory = ReturnType<typeof kmskeyDALFactory>;
type TCmekFindFilter = Parameters<typeof buildFindFilter<TKmsKeys>>[0];
const baseCmekQuery = ({ filter, db, tx }: { db: TDbClient; filter?: TCmekFindFilter; tx?: Knex }) => {
const query = (tx || db.replicaNode())(TableName.KmsKey)
.where(`${TableName.KmsKey}.isReserved`, false)
.join(TableName.InternalKms, `${TableName.InternalKms}.kmsKeyId`, `${TableName.KmsKey}.id`)
.select(
selectAllTableCols(TableName.KmsKey),
db.ref("encryptionAlgorithm").withSchema(TableName.InternalKms),
db.ref("version").withSchema(TableName.InternalKms)
);
if (filter) {
/* eslint-disable @typescript-eslint/no-misused-promises */
void query.where(buildFindFilter(prependTableNameToFindFilter(TableName.KmsKey, filter)));
}
return query;
};
export const kmskeyDALFactory = (db: TDbClient) => { export const kmskeyDALFactory = (db: TDbClient) => {
const kmsOrm = ormify(db, TableName.KmsKey); const kmsOrm = ormify(db, TableName.KmsKey);
@@ -73,7 +93,7 @@ export const kmskeyDALFactory = (db: TDbClient) => {
} }
}; };
const findKmsKeysByProjectId = async ( const listCmeksByProjectId = async (
{ {
projectId, projectId,
offset = 0, offset = 0,
@@ -92,6 +112,7 @@ export const kmskeyDALFactory = (db: TDbClient) => {
void qb.whereILike("name", `%${search}%`); void qb.whereILike("name", `%${search}%`);
} }
}) })
.where(`${TableName.KmsKey}.isReserved`, false)
.join(TableName.InternalKms, `${TableName.InternalKms}.kmsKeyId`, `${TableName.KmsKey}.id`) .join(TableName.InternalKms, `${TableName.InternalKms}.kmsKeyId`, `${TableName.KmsKey}.id`)
.select< .select<
(TKmsKeys & (TKmsKeys &
@@ -118,5 +139,33 @@ export const kmskeyDALFactory = (db: TDbClient) => {
} }
}; };
return { ...kmsOrm, findByIdWithAssociatedKms, findKmsKeysByProjectId }; const findCmekById = async (id: string, tx?: Knex) => {
try {
const key = await baseCmekQuery({
filter: { id },
db,
tx
}).first();
return key;
} catch (error) {
throw new DatabaseError({ error, name: "Find by ID - KMS Key" });
}
};
const findCmekByName = async (keyName: string, projectId: string, tx?: Knex) => {
try {
const key = await baseCmekQuery({
filter: { name: keyName, projectId },
db,
tx
}).first();
return key;
} catch (error) {
throw new DatabaseError({ error, name: "Find by Name - KMS Key" });
}
};
return { ...kmsOrm, findByIdWithAssociatedKms, listCmeksByProjectId, findCmekById, findCmekByName };
}; };
@@ -4,7 +4,7 @@ import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas"; import { TableName } from "@app/db/schemas";
import { TSecretSyncs } from "@app/db/schemas/secret-syncs"; import { TSecretSyncs } from "@app/db/schemas/secret-syncs";
import { DatabaseError } from "@app/lib/errors"; import { DatabaseError } from "@app/lib/errors";
import { buildFindFilter, ormify, selectAllTableCols } from "@app/lib/knex"; import { buildFindFilter, ormify, prependTableNameToFindFilter, selectAllTableCols } from "@app/lib/knex";
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
export type TSecretSyncDALFactory = ReturnType<typeof secretSyncDALFactory>; export type TSecretSyncDALFactory = ReturnType<typeof secretSyncDALFactory>;
@@ -34,17 +34,9 @@ const baseSecretSyncQuery = ({ filter, db, tx }: { db: TDbClient; filter?: Secre
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt") db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt")
); );
// prepends table name to filter keys to avoid ambiguous col references, skipping utility filters like $in, etc.
const prependTableName = (filterObj: object): SecretSyncFindFilter =>
Object.fromEntries(
Object.entries(filterObj).map(([key, value]) =>
key.startsWith("$") ? [key, prependTableName(value as object)] : [`${TableName.SecretSync}.${key}`, value]
)
);
if (filter) { if (filter) {
/* eslint-disable @typescript-eslint/no-misused-promises */ /* eslint-disable @typescript-eslint/no-misused-promises */
void query.where(buildFindFilter(prependTableName(filter))); void query.where(buildFindFilter(prependTableNameToFindFilter(TableName.SecretSync, filter)));
} }
return query; return query;
@@ -0,0 +1,4 @@
---
title: "Get Key by ID"
openapi: "Get /api/v1/kms/keys/{keyId}"
---
@@ -0,0 +1,4 @@
---
title: "Get Key by Name"
openapi: "Get /api/v1/kms/keys/key-name/{keyName}"
---
+2
View File
@@ -1039,6 +1039,8 @@
"group": "Keys", "group": "Keys",
"pages": [ "pages": [
"api-reference/endpoints/kms/keys/list", "api-reference/endpoints/kms/keys/list",
"api-reference/endpoints/kms/keys/get-by-id",
"api-reference/endpoints/kms/keys/get-by-name",
"api-reference/endpoints/kms/keys/create", "api-reference/endpoints/kms/keys/create",
"api-reference/endpoints/kms/keys/update", "api-reference/endpoints/kms/keys/update",
"api-reference/endpoints/kms/keys/delete", "api-reference/endpoints/kms/keys/delete",
@@ -92,6 +92,7 @@ export const eventToNameMap: { [K in EventType]: string } = {
[EventType.UPDATE_CMEK]: "Update KMS key", [EventType.UPDATE_CMEK]: "Update KMS key",
[EventType.DELETE_CMEK]: "Delete KMS key", [EventType.DELETE_CMEK]: "Delete KMS key",
[EventType.GET_CMEKS]: "List KMS keys", [EventType.GET_CMEKS]: "List KMS keys",
[EventType.GET_CMEK]: "Get KMS key",
[EventType.CMEK_ENCRYPT]: "Encrypt with KMS key", [EventType.CMEK_ENCRYPT]: "Encrypt with KMS key",
[EventType.CMEK_DECRYPT]: "Decrypt with KMS key", [EventType.CMEK_DECRYPT]: "Decrypt with KMS key",
[EventType.UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS]: [EventType.UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS]:
@@ -106,6 +106,7 @@ export enum EventType {
UPDATE_CMEK = "update-cmek", UPDATE_CMEK = "update-cmek",
DELETE_CMEK = "delete-cmek", DELETE_CMEK = "delete-cmek",
GET_CMEKS = "get-cmeks", GET_CMEKS = "get-cmeks",
GET_CMEK = "get-cmek",
CMEK_ENCRYPT = "cmek-encrypt", CMEK_ENCRYPT = "cmek-encrypt",
CMEK_DECRYPT = "cmek-decrypt", CMEK_DECRYPT = "cmek-decrypt",
UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS = "update-external-group-org-role-mapping", UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS = "update-external-group-org-role-mapping",
@@ -284,7 +284,8 @@ export const CmekTable = () => {
<IconButton <IconButton
ariaLabel="copy icon" ariaLabel="copy icon"
colorSchema="secondary" colorSchema="secondary"
className="group/copy duration:0 invisible relative ml-3 group-hover:visible" size="xs"
className="group/copy duration:0 invisible relative ml-3 rounded-md group-hover:visible"
onClick={() => { onClick={() => {
navigator.clipboard.writeText(id); navigator.clipboard.writeText(id);
setCopyCipherText("Copied"); setCopyCipherText("Copied");
@@ -299,112 +300,116 @@ export const CmekTable = () => {
<Badge variant={variant}>{label}</Badge> <Badge variant={variant}>{label}</Badge>
</Td> </Td>
<Td>{version}</Td> <Td>{version}</Td>
<Td className="flex justify-end"> <Td>
<DropdownMenu> <div className="flex justify-end">
<DropdownMenuTrigger asChild> <DropdownMenu>
<IconButton <DropdownMenuTrigger asChild>
variant="plain" <IconButton
colorSchema="primary" variant="plain"
className="ml-4 p-0 data-[state=open]:text-primary-400" colorSchema="primary"
ariaLabel="More options" className="ml-4 p-0 data-[state=open]:text-primary-400"
> ariaLabel="More options"
<FontAwesomeIcon size="lg" icon={faEllipsis} /> >
</IconButton> <FontAwesomeIcon size="lg" icon={faEllipsis} />
</DropdownMenuTrigger> </IconButton>
<DropdownMenuContent className="min-w-[160px]"> </DropdownMenuTrigger>
<Tooltip <DropdownMenuContent className="min-w-[160px]">
content={ <Tooltip
// eslint-disable-next-line no-nested-ternary content={
cannotEncryptData // eslint-disable-next-line no-nested-ternary
? "Access Restricted" cannotEncryptData
: isDisabled ? "Access Restricted"
? "Key Disabled" : isDisabled
: "" ? "Key Disabled"
} : ""
position="left" }
> position="left"
<div> >
<DropdownMenuItem <div>
onClick={() => handlePopUpOpen("encryptData", cmek)} <DropdownMenuItem
icon={<FontAwesomeIcon icon={faLock} />} onClick={() => handlePopUpOpen("encryptData", cmek)}
iconPos="left" icon={<FontAwesomeIcon icon={faLock} />}
isDisabled={cannotEncryptData || isDisabled} iconPos="left"
> isDisabled={cannotEncryptData || isDisabled}
Encrypt Data >
</DropdownMenuItem> Encrypt Data
</div> </DropdownMenuItem>
</Tooltip> </div>
<Tooltip </Tooltip>
content={ <Tooltip
// eslint-disable-next-line no-nested-ternary content={
cannotDecryptData // eslint-disable-next-line no-nested-ternary
? "Access Restricted" cannotDecryptData
: isDisabled ? "Access Restricted"
? "Key Disabled" : isDisabled
: "" ? "Key Disabled"
} : ""
position="left" }
> position="left"
<div> >
<DropdownMenuItem <div>
onClick={() => handlePopUpOpen("decryptData", cmek)} <DropdownMenuItem
icon={<FontAwesomeIcon icon={faLockOpen} />} onClick={() => handlePopUpOpen("decryptData", cmek)}
iconPos="left" icon={<FontAwesomeIcon icon={faLockOpen} />}
isDisabled={cannotDecryptData || isDisabled} iconPos="left"
> isDisabled={cannotDecryptData || isDisabled}
Decrypt Data >
</DropdownMenuItem> Decrypt Data
</div> </DropdownMenuItem>
</Tooltip> </div>
<Tooltip </Tooltip>
content={cannotEditKey ? "Access Restricted" : ""} <Tooltip
position="left" content={cannotEditKey ? "Access Restricted" : ""}
> position="left"
<div> >
<DropdownMenuItem <div>
onClick={() => handlePopUpOpen("upsertKey", cmek)} <DropdownMenuItem
icon={<FontAwesomeIcon icon={faEdit} />} onClick={() => handlePopUpOpen("upsertKey", cmek)}
iconPos="left" icon={<FontAwesomeIcon icon={faEdit} />}
isDisabled={cannotEditKey} iconPos="left"
> isDisabled={cannotEditKey}
Edit Key >
</DropdownMenuItem> Edit Key
</div> </DropdownMenuItem>
</Tooltip> </div>
<Tooltip </Tooltip>
content={cannotEditKey ? "Access Restricted" : ""} <Tooltip
position="left" content={cannotEditKey ? "Access Restricted" : ""}
> position="left"
<div> >
<DropdownMenuItem <div>
onClick={() => handleDisableCmek(cmek)} <DropdownMenuItem
icon={ onClick={() => handleDisableCmek(cmek)}
<FontAwesomeIcon icon={isDisabled ? faCheckCircle : faCancel} /> icon={
} <FontAwesomeIcon
iconPos="left" icon={isDisabled ? faCheckCircle : faCancel}
isDisabled={cannotEditKey} />
> }
{isDisabled ? "Enable" : "Disable"} Key iconPos="left"
</DropdownMenuItem> isDisabled={cannotEditKey}
</div> >
</Tooltip> {isDisabled ? "Enable" : "Disable"} Key
<Tooltip </DropdownMenuItem>
content={cannotDeleteKey ? "Access Restricted" : ""} </div>
position="left" </Tooltip>
> <Tooltip
<div> content={cannotDeleteKey ? "Access Restricted" : ""}
<DropdownMenuItem position="left"
onClick={() => handlePopUpOpen("deleteKey", cmek)} >
icon={<FontAwesomeIcon icon={faTrash} />} <div>
iconPos="left" <DropdownMenuItem
isDisabled={cannotDeleteKey} onClick={() => handlePopUpOpen("deleteKey", cmek)}
> icon={<FontAwesomeIcon icon={faTrash} />}
Delete Key iconPos="left"
</DropdownMenuItem> isDisabled={cannotDeleteKey}
</div> >
</Tooltip> Delete Key
</DropdownMenuContent> </DropdownMenuItem>
</DropdownMenu> </div>
</Tooltip>
</DropdownMenuContent>
</DropdownMenu>
</div>
</Td> </Td>
</Tr> </Tr>
); );