Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions backend/src/ee/services/audit-log/audit-log-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,8 @@ export enum EventType {
CMEK_LIST_SIGNING_ALGORITHMS = "cmek-list-signing-algorithms",
CMEK_GET_PUBLIC_KEY = "cmek-get-public-key",
CMEK_GET_PRIVATE_KEY = "cmek-get-private-key",
CMEK_BULK_EXPORT_PRIVATE_KEYS = "cmek-bulk-export-private-keys",
CMEK_BULK_IMPORT_KEYS = "cmek-bulk-import-keys",

UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS = "update-external-group-org-role-mapping",
GET_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS = "get-external-group-org-role-mapping",
Expand Down Expand Up @@ -3648,6 +3650,23 @@ interface CmekGetPrivateKeyEvent {
};
}

interface CmekBulkGetPrivateKeysEvent {
type: EventType.CMEK_BULK_EXPORT_PRIVATE_KEYS;
metadata: {
keyIds: string[];
keyNames: string[];
};
}

interface CmekBulkImportKeysEvent {
type: EventType.CMEK_BULK_IMPORT_KEYS;
metadata: {
keyNames: string[];
failedKeyNames: string[];
projectId: string;
};
}

interface GetExternalGroupOrgRoleMappingsEvent {
type: EventType.GET_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS;
metadata?: Record<string, never>; // not needed, based off orgId
Expand Down Expand Up @@ -6288,6 +6307,8 @@ export type Event =
| CmekListSigningAlgorithmsEvent
| CmekGetPublicKeyEvent
| CmekGetPrivateKeyEvent
| CmekBulkGetPrivateKeysEvent
| CmekBulkImportKeysEvent
| GetExternalGroupOrgRoleMappingsEvent
| UpdateExternalGroupOrgRoleMappingsEvent
| GetProjectTemplatesEvent
Expand Down
58 changes: 29 additions & 29 deletions backend/src/ee/services/license/license-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,43 +35,43 @@ export type TFeatureSet = {
_id: null;
slug: string | null;
tier: -1;
workspaceLimit: null;
workspaceLimit: null | number;
workspacesUsed: number;
dynamicSecret: false;
dynamicSecret: boolean;
memberLimit: null;
membersUsed: number;
identityLimit: null;
identityLimit: null | number;
identitiesUsed: number;
subOrganization: false;
environmentLimit: null;
subOrganization: boolean;
environmentLimit: null | number;
environmentsUsed: 0;
secretVersioning: true;
pitRecovery: false;
ipAllowlisting: false;
rbac: false;
customRateLimits: false;
customAlerts: false;
auditLogs: false;
auditLogsRetentionDays: 0;
auditLogStreams: false;
pitRecovery: boolean;
ipAllowlisting: boolean;
rbac: boolean;
customRateLimits: boolean;
customAlerts: boolean;
auditLogs: boolean;
auditLogsRetentionDays: number;
auditLogStreams: boolean;
auditLogStreamLimit: 3;
githubOrgSync: false;
samlSSO: false;
enforceGoogleSSO: false;
hsm: false;
oidcSSO: false;
secretAccessInsights: false;
scim: false;
ldap: false;
groups: false;
githubOrgSync: boolean;
samlSSO: boolean;
enforceGoogleSSO: boolean;
hsm: boolean;
oidcSSO: boolean;
secretAccessInsights: boolean;
scim: boolean;
ldap: boolean;
groups: boolean;
status: null;
trial_end: null;
has_used_trial: true;
secretApproval: false;
secretRotation: false;
secretApproval: boolean;
secretRotation: boolean;
caCrl: false;
instanceUserManagement: false;
externalKms: false;
instanceUserManagement: boolean;
externalKms: boolean;
rateLimits: {
readLimit: number;
writeLimit: number;
Expand All @@ -91,9 +91,9 @@ export type TFeatureSet = {
enterpriseAppConnections: false;
machineIdentityAuthTemplates: false;
pkiLegacyTemplates: false;
fips: false;
eventSubscriptions: false;
secretShareExternalBranding: false;
fips: boolean;
eventSubscriptions: boolean;
secretShareExternalBranding: boolean;
};

export type TOrgPlansTableDTO = {
Expand Down
4 changes: 4 additions & 0 deletions backend/src/lib/api-docs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2471,6 +2471,10 @@ export const KMS = {
keyId: "The ID of the key to export the private key or key material for."
},

BULK_EXPORT_PRIVATE_KEYS: {
keyIds: "An array of KMS key IDs to export. Maximum 100 keys per request."
},

SIGN: {
keyId: "The ID of the key to sign the data with.",
data: "The data in string format to be signed (base64 encoded).",
Expand Down
143 changes: 143 additions & 0 deletions backend/src/server/routes/v1/cmek-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,149 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
}
});

server.route({
method: "POST",
url: "/keys/bulk-import",
config: { rateLimit: writeLimit },
schema: {
hide: false,
operationId: "bulkImportKmsKeys",
tags: [ApiDocsTags.KmsKeys],
description: "Bulk import KMS keys with provided key material into a project.",
body: z.object({
projectId: z.string().uuid(),
keys: z
.array(
z
.object({
name: keyNameSchema,
keyUsage: z.nativeEnum(KmsKeyUsage),
encryptionAlgorithm: z.enum(AllowedEncryptionKeyAlgorithms),
keyMaterial: z.string().min(1)
})
.superRefine((data, ctx) => {
if (
data.keyUsage === KmsKeyUsage.ENCRYPT_DECRYPT &&
!Object.values(SymmetricKeyAlgorithm).includes(data.encryptionAlgorithm as SymmetricKeyAlgorithm)
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `encryptionAlgorithm must be a symmetric algorithm for encrypt-decrypt keys`
});
}
if (
data.keyUsage === KmsKeyUsage.SIGN_VERIFY &&
!Object.values(AsymmetricKeyAlgorithm).includes(data.encryptionAlgorithm as AsymmetricKeyAlgorithm)
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `encryptionAlgorithm must be an asymmetric algorithm for sign-verify keys`
});
}
})
)
.min(1)
.max(100)
}),
response: {
200: z.object({
keys: z.array(z.object({ id: z.string(), name: z.string() })),
errors: z.array(z.object({ name: z.string(), message: z.string() }))
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
body: { projectId, keys },
permission
} = req;

const { keys: importedKeys, errors } = await server.services.cmek.bulkImportKeys(
{
projectId,
keys: keys.map((k) => ({
name: k.name,
algorithm: k.encryptionAlgorithm as TCmekKeyEncryptionAlgorithm,
keyUsage: k.keyUsage,
keyMaterial: k.keyMaterial
}))
},
permission
);

await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId,
event: {
type: EventType.CMEK_BULK_IMPORT_KEYS,
metadata: {
keyNames: importedKeys.map((k) => k.name),
failedKeyNames: errors.map((e) => e.name),
projectId
}
}
});

return { keys: importedKeys, errors };
}
});

server.route({
method: "POST",
url: "/keys/bulk-export-private-keys",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
operationId: "bulkExportKmsKeyPrivateKeys",
tags: [ApiDocsTags.KmsKeys],
description:
"Bulk export multiple KMS keys. For asymmetric keys (sign/verify), both private and public keys are returned. For symmetric keys (encrypt/decrypt), the key material is returned.",
body: z.object({
keyIds: z.array(z.string().uuid().describe(KMS.BULK_EXPORT_PRIVATE_KEYS.keyIds)).min(1).max(100)
}),
response: {
200: z.object({
keys: z.array(
z.object({
keyId: z.string(),
name: z.string(),
keyUsage: z.string(),
algorithm: z.string(),
privateKey: z.string(),
publicKey: z.string().optional()
})
)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
body: { keyIds },
permission
} = req;

const { keys, projectId } = await server.services.cmek.bulkGetPrivateKeys({ keyIds }, permission);

await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId,
event: {
type: EventType.CMEK_BULK_EXPORT_PRIVATE_KEYS,
metadata: {
keyIds: keys.map((k) => k.keyId),
keyNames: keys.map((k) => k.name)
}
}
});

return { keys };
}
});

server.route({
method: "GET",
url: "/keys/:keyId/signing-algorithms",
Expand Down
Loading
Loading