misc: audit logs

This commit is contained in:
Sheen Capadngan
2025-02-12 01:34:29 +08:00
parent 292c9051bd
commit b8a07979c3
14 changed files with 258 additions and 27 deletions

View File

@@ -126,6 +126,7 @@ declare module "fastify" {
kmipUser: { kmipUser: {
projectId: string; projectId: string;
clientId: string; clientId: string;
name: string;
}; };
auditLogInfo: Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">; auditLogInfo: Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">;
ssoConfig: Awaited<ReturnType<TSamlConfigServiceFactory["getSaml"]>>; ssoConfig: Awaited<ReturnType<TSamlConfigServiceFactory["getSaml"]>>;

View File

@@ -3,9 +3,11 @@ import jwt, { JwtPayload } from "jsonwebtoken";
import z from "zod"; import z from "zod";
import { KmsKeysSchema } from "@app/db/schemas"; import { KmsKeysSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { SymmetricEncryption } from "@app/lib/crypto/cipher"; import { SymmetricEncryption } from "@app/lib/crypto/cipher";
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { writeLimit } from "@app/server/config/rateLimiter"; import { writeLimit } from "@app/server/config/rateLimiter";
import { ActorType } from "@app/services/auth/auth-type";
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types"; import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
export const registerKmipOperationRouter = async (server: FastifyZodProvider) => { export const registerKmipOperationRouter = async (server: FastifyZodProvider) => {
@@ -55,7 +57,8 @@ export const registerKmipOperationRouter = async (server: FastifyZodProvider) =>
req.kmipUser = { req.kmipUser = {
projectId: decodedToken.projectId, projectId: decodedToken.projectId,
clientId: decodedToken.clientId clientId: decodedToken.clientId,
name: kmipClient.name
}; };
}); });
@@ -68,7 +71,7 @@ export const registerKmipOperationRouter = async (server: FastifyZodProvider) =>
schema: { schema: {
description: "KMIP endpoint for creating managed objects", description: "KMIP endpoint for creating managed objects",
body: z.object({ body: z.object({
encryptionAlgorithm: z.nativeEnum(SymmetricEncryption) algorithm: z.nativeEnum(SymmetricEncryption)
}), }),
response: { response: {
200: KmsKeysSchema 200: KmsKeysSchema
@@ -76,9 +79,26 @@ export const registerKmipOperationRouter = async (server: FastifyZodProvider) =>
}, },
handler: async (req) => { handler: async (req) => {
const object = await server.services.kmipOperation.create({ const object = await server.services.kmipOperation.create({
...req.kmipUser,
algorithm: req.body.algorithm
});
await server.services.auditLog.createAuditLog({
projectId: req.kmipUser.projectId, projectId: req.kmipUser.projectId,
clientId: req.kmipUser.clientId, actor: {
encryptionAlgorithm: req.body.encryptionAlgorithm type: ActorType.KMIP_CLIENT,
metadata: {
clientId: req.kmipUser.clientId,
name: req.kmipUser.name
}
},
event: {
type: EventType.KMIP_OPERATION_CREATE,
metadata: {
id: object.id,
algorithm: req.body.algorithm
}
}
}); });
return object; return object;
@@ -106,11 +126,71 @@ export const registerKmipOperationRouter = async (server: FastifyZodProvider) =>
}, },
handler: async (req) => { handler: async (req) => {
const object = await server.services.kmipOperation.get({ const object = await server.services.kmipOperation.get({
projectId: req.kmipUser.projectId, ...req.kmipUser,
clientId: req.kmipUser.clientId,
id: req.body.id id: req.body.id
}); });
await server.services.auditLog.createAuditLog({
projectId: req.kmipUser.projectId,
actor: {
type: ActorType.KMIP_CLIENT,
metadata: {
clientId: req.kmipUser.clientId,
name: req.kmipUser.name
}
},
event: {
type: EventType.KMIP_OPERATION_GET,
metadata: {
id: object.id
}
}
});
return object;
}
});
server.route({
method: "POST",
url: "/delete",
config: {
rateLimit: writeLimit
},
schema: {
description: "KMIP endpoint for destroying managed objects",
body: z.object({
id: z.string()
}),
response: {
200: z.object({
id: z.string()
})
}
},
handler: async (req) => {
const object = await server.services.kmipOperation.deleteOp({
...req.kmipUser,
id: req.body.id
});
await server.services.auditLog.createAuditLog({
projectId: req.kmipUser.projectId,
actor: {
type: ActorType.KMIP_CLIENT,
metadata: {
clientId: req.kmipUser.clientId,
name: req.kmipUser.name
}
},
event: {
type: EventType.KMIP_OPERATION_DELETE,
metadata: {
id: object.id
}
}
});
return object; return object;
} }
}); });

View File

@@ -41,7 +41,14 @@ export type TListProjectAuditLogDTO = {
export type TCreateAuditLogDTO = { export type TCreateAuditLogDTO = {
event: Event; event: Event;
actor: UserActor | IdentityActor | ServiceActor | ScimClientActor | PlatformActor | UnknownUserActor; actor:
| UserActor
| IdentityActor
| ServiceActor
| ScimClientActor
| PlatformActor
| UnknownUserActor
| KmipClientActor;
orgId?: string; orgId?: string;
projectId?: string; projectId?: string;
} & BaseAuthData; } & BaseAuthData;
@@ -259,7 +266,10 @@ export enum EventType {
DELETE_KMIP_CLIENT = "delete-kmip-client", DELETE_KMIP_CLIENT = "delete-kmip-client",
GET_KMIP_CLIENT = "get-kmip-client", GET_KMIP_CLIENT = "get-kmip-client",
GET_KMIP_CLIENTS = "get-kmip-clients", GET_KMIP_CLIENTS = "get-kmip-clients",
CREATE_KMIP_CLIENT_CERTIFICATE = "create-kmip-client-certificate" CREATE_KMIP_CLIENT_CERTIFICATE = "create-kmip-client-certificate",
KMIP_OPERATION_CREATE = "kmip-operation-create",
KMIP_OPERATION_GET = "kmip-operation-get",
KMIP_OPERATION_DELETE = "kmip-operation-delete"
} }
interface UserActorMetadata { interface UserActorMetadata {
@@ -282,6 +292,11 @@ interface ScimClientActorMetadata {}
interface PlatformActorMetadata {} interface PlatformActorMetadata {}
interface KmipClientActorMetadata {
clientId: string;
name: string;
}
interface UnknownUserActorMetadata {} interface UnknownUserActorMetadata {}
export interface UserActor { export interface UserActor {
@@ -299,6 +314,11 @@ export interface PlatformActor {
metadata: PlatformActorMetadata; metadata: PlatformActorMetadata;
} }
export interface KmipClientActor {
type: ActorType.KMIP_CLIENT;
metadata: KmipClientActorMetadata;
}
export interface UnknownUserActor { export interface UnknownUserActor {
type: ActorType.UNKNOWN_USER; type: ActorType.UNKNOWN_USER;
metadata: UnknownUserActorMetadata; metadata: UnknownUserActorMetadata;
@@ -314,7 +334,7 @@ export interface ScimClientActor {
metadata: ScimClientActorMetadata; metadata: ScimClientActorMetadata;
} }
export type Actor = UserActor | ServiceActor | IdentityActor | ScimClientActor | PlatformActor; export type Actor = UserActor | ServiceActor | IdentityActor | ScimClientActor | PlatformActor | KmipClientActor;
interface GetSecretsEvent { interface GetSecretsEvent {
type: EventType.GET_SECRETS; type: EventType.GET_SECRETS;
@@ -2123,6 +2143,28 @@ interface CreateKmipClientCertificateEvent {
}; };
} }
interface KmipOperationGetEvent {
type: EventType.KMIP_OPERATION_GET;
metadata: {
id: string;
};
}
interface KmipOperationDeleteEvent {
type: EventType.KMIP_OPERATION_DELETE;
metadata: {
id: string;
};
}
interface KmipOperationCreateEvent {
type: EventType.KMIP_OPERATION_CREATE;
metadata: {
id: string;
algorithm: string;
};
}
export type Event = export type Event =
| GetSecretsEvent | GetSecretsEvent
| GetSecretEvent | GetSecretEvent
@@ -2319,4 +2361,7 @@ export type Event =
| DeleteKmipClientEvent | DeleteKmipClientEvent
| GetKmipClientEvent | GetKmipClientEvent
| GetKmipClientsEvent | GetKmipClientsEvent
| CreateKmipClientCertificateEvent; | CreateKmipClientCertificateEvent
| KmipOperationGetEvent
| KmipOperationDeleteEvent
| KmipOperationCreateEvent;

View File

@@ -2,5 +2,6 @@ export enum KmipPermission {
Create = "create", Create = "create",
Locate = "locate", Locate = "locate",
Check = "check", Check = "check",
Get = "get" Get = "get",
Delete = "delete"
} }

View File

@@ -6,7 +6,7 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TKmipClientDALFactory } from "./kmip-client-dal"; import { TKmipClientDALFactory } from "./kmip-client-dal";
import { KmipPermission } from "./kmip-enum"; import { KmipPermission } from "./kmip-enum";
import { TKmipCreateDTO, TKmipGetDTO } from "./kmip-types"; import { TKmipCreateDTO, TKmipDeleteDTO, TKmipGetDTO } from "./kmip-types";
type TKmipOperationServiceFactoryDep = { type TKmipOperationServiceFactoryDep = {
kmsService: TKmsServiceFactory; kmsService: TKmsServiceFactory;
@@ -23,7 +23,7 @@ export const kmipOperationServiceFactory = ({
projectDAL, projectDAL,
kmipClientDAL kmipClientDAL
}: TKmipOperationServiceFactoryDep) => { }: TKmipOperationServiceFactoryDep) => {
const create = async ({ projectId: preSplitProjectId, clientId, encryptionAlgorithm }: TKmipCreateDTO) => { const create = async ({ projectId: preSplitProjectId, clientId, algorithm }: TKmipCreateDTO) => {
let projectId = preSplitProjectId; let projectId = preSplitProjectId;
const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(projectId, ProjectType.KMS); const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(projectId, ProjectType.KMS);
if (cmekProjectFromSplit) { if (cmekProjectFromSplit) {
@@ -43,7 +43,7 @@ export const kmipOperationServiceFactory = ({
} }
const kmsKey = await kmsService.generateKmsKey({ const kmsKey = await kmsService.generateKmsKey({
encryptionAlgorithm, encryptionAlgorithm: algorithm,
orgId: project.orgId, orgId: project.orgId,
projectId, projectId,
isReserved: false isReserved: false
@@ -52,6 +52,50 @@ export const kmipOperationServiceFactory = ({
return kmsKey; return kmsKey;
}; };
const deleteOp = async ({ projectId: preSplitProjectId, id, clientId }: TKmipDeleteDTO) => {
let projectId = preSplitProjectId;
const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(projectId, ProjectType.KMS);
if (cmekProjectFromSplit) {
projectId = cmekProjectFromSplit.id;
}
const kmipClient = await kmipClientDAL.findOne({
id: clientId,
projectId
});
if (!kmipClient.permissions?.includes(KmipPermission.Delete)) {
throw new ForbiddenRequestError({
message: "Client does not have sufficient permission to perform KMIP delete"
});
}
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 delete reserved keys" });
}
const completeKeyDetails = await kmsDAL.findByIdWithAssociatedKms(id);
if (!completeKeyDetails.internalKms) {
throw new BadRequestError({
message: "Cannot delete external keys"
});
}
const kms = kmsDAL.deleteById(id);
return kms;
};
const get = async ({ projectId: preSplitProjectId, id, clientId }: TKmipGetDTO) => { const get = async ({ projectId: preSplitProjectId, id, clientId }: TKmipGetDTO) => {
let projectId = preSplitProjectId; let projectId = preSplitProjectId;
const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(projectId, ProjectType.KMS); const cmekProjectFromSplit = await projectDAL.getProjectFromSplitId(projectId, ProjectType.KMS);
@@ -88,7 +132,7 @@ export const kmipOperationServiceFactory = ({
if (!completeKeyDetails.internalKms) { if (!completeKeyDetails.internalKms) {
throw new BadRequestError({ throw new BadRequestError({
message: "Cannot get external key" message: "Cannot get external keys"
}); });
} }
@@ -105,6 +149,7 @@ export const kmipOperationServiceFactory = ({
return { return {
create, create,
get get,
deleteOp
}; };
}; };

View File

@@ -43,14 +43,19 @@ export type TListKmipClientsByProjectIdDTO = {
search?: string; search?: string;
} & TProjectPermission; } & TProjectPermission;
export type TKmipCreateDTO = { type KmipOperationBaseDTO = {
clientId: string; clientId: string;
projectId: string; projectId: string;
encryptionAlgorithm: SymmetricEncryption;
}; };
export type TKmipCreateDTO = {
algorithm: SymmetricEncryption;
} & KmipOperationBaseDTO;
export type TKmipGetDTO = { export type TKmipGetDTO = {
clientId: string;
projectId: string;
id: string; id: string;
}; } & KmipOperationBaseDTO;
export type TKmipDeleteDTO = {
id: string;
} & KmipOperationBaseDTO;

View File

@@ -35,6 +35,7 @@ export enum AuthMode {
export enum ActorType { // would extend to AWS, Azure, ... export enum ActorType { // would extend to AWS, Azure, ...
PLATFORM = "platform", // Useful for when we want to perform logging on automated actions such as integration syncs. PLATFORM = "platform", // Useful for when we want to perform logging on automated actions such as integration syncs.
KMIP_CLIENT = "kmipClient",
USER = "user", // userIdentity USER = "user", // userIdentity
SERVICE = "service", SERVICE = "service",
IDENTITY = "identity", IDENTITY = "identity",

View File

@@ -118,7 +118,16 @@ export const eventToNameMap: { [K in EventType]: string } = {
[EventType.OIDC_GROUP_MEMBERSHIP_MAPPING_ASSIGN_USER]: [EventType.OIDC_GROUP_MEMBERSHIP_MAPPING_ASSIGN_USER]:
"OIDC group membership mapping assigned user to groups", "OIDC group membership mapping assigned user to groups",
[EventType.OIDC_GROUP_MEMBERSHIP_MAPPING_REMOVE_USER]: [EventType.OIDC_GROUP_MEMBERSHIP_MAPPING_REMOVE_USER]:
"OIDC group membership mapping removed user from groups" "OIDC group membership mapping removed user from groups",
[EventType.CREATE_KMIP_CLIENT]: "Create KMIP client",
[EventType.UPDATE_KMIP_CLIENT]: "Update KMIP client",
[EventType.DELETE_KMIP_CLIENT]: "Delete KMIP client",
[EventType.GET_KMIP_CLIENT]: "Get KMIP client",
[EventType.GET_KMIP_CLIENTS]: "Get KMIP clients",
[EventType.CREATE_KMIP_CLIENT_CERTIFICATE]: "Create KMIP client certificate",
[EventType.KMIP_OPERATION_CREATE]: "KMIP operation create",
[EventType.KMIP_OPERATION_GET]: "KMIP operation get",
[EventType.KMIP_OPERATION_DELETE]: "KMIP operation delete"
}; };
export const userAgentTTypeoNameMap: { [K in UserAgentType]: string } = { export const userAgentTTypeoNameMap: { [K in UserAgentType]: string } = {

View File

@@ -1,5 +1,6 @@
export enum ActorType { export enum ActorType {
PLATFORM = "platform", PLATFORM = "platform",
KMIP_CLIENT = "kmipClient",
USER = "user", USER = "user",
SERVICE = "service", SERVICE = "service",
IDENTITY = "identity", IDENTITY = "identity",
@@ -129,5 +130,14 @@ export enum EventType {
SECRET_SYNC_IMPORT_SECRETS = "secret-sync-import-secrets", SECRET_SYNC_IMPORT_SECRETS = "secret-sync-import-secrets",
SECRET_SYNC_REMOVE_SECRETS = "secret-sync-remove-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_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",
KMIP_OPERATION_CREATE = "kmip-operation-create",
KMIP_OPERATION_GET = "kmip-operation-get",
KMIP_OPERATION_DELETE = "kmip-operation-delete"
} }

View File

@@ -30,6 +30,10 @@ interface IdentityActorMetadata {
identityId: string; identityId: string;
name: string; name: string;
} }
interface KmipClientActorMetadata {
clientId: string;
name: string;
}
interface UserActor { interface UserActor {
type: ActorType.USER; type: ActorType.USER;
@@ -51,11 +55,22 @@ export interface PlatformActor {
metadata: object; metadata: object;
} }
export interface KmipClientActor {
type: ActorType.KMIP_CLIENT;
metadata: KmipClientActorMetadata;
}
export interface UnknownUserActor { export interface UnknownUserActor {
type: ActorType.UNKNOWN_USER; type: ActorType.UNKNOWN_USER;
} }
export type Actor = UserActor | ServiceActor | IdentityActor | PlatformActor | UnknownUserActor; export type Actor =
| UserActor
| ServiceActor
| IdentityActor
| PlatformActor
| UnknownUserActor
| KmipClientActor;
interface GetSecretsEvent { interface GetSecretsEvent {
type: EventType.GET_SECRETS; type: EventType.GET_SECRETS;

View File

@@ -5,7 +5,8 @@ export enum KmipPermission {
Create = "create", Create = "create",
Locate = "locate", Locate = "locate",
Check = "check", Check = "check",
Get = "get" Get = "get",
Delete = "delete"
} }
export type TKmipClient = { export type TKmipClient = {

View File

@@ -21,7 +21,8 @@ const KMIP_PERMISSIONS_OPTIONS = [
{ value: KmipPermission.Check, label: "Check" }, { value: KmipPermission.Check, label: "Check" },
{ value: KmipPermission.Create, label: "Create" }, { value: KmipPermission.Create, label: "Create" },
{ value: KmipPermission.Get, label: "Get" }, { value: KmipPermission.Get, label: "Get" },
{ value: KmipPermission.Locate, label: "Locate" } { value: KmipPermission.Locate, label: "Locate" },
{ value: KmipPermission.Delete, label: "Delete" }
] as const; ] as const;
const formSchema = z.object({ const formSchema = z.object({
@@ -31,7 +32,8 @@ const formSchema = z.object({
[KmipPermission.Check]: z.boolean().optional(), [KmipPermission.Check]: z.boolean().optional(),
[KmipPermission.Create]: z.boolean().optional(), [KmipPermission.Create]: z.boolean().optional(),
[KmipPermission.Get]: z.boolean().optional(), [KmipPermission.Get]: z.boolean().optional(),
[KmipPermission.Locate]: z.boolean().optional() [KmipPermission.Locate]: z.boolean().optional(),
[KmipPermission.Delete]: z.boolean().optional()
}) })
}); });

View File

@@ -94,6 +94,15 @@ export const LogsFilter = ({
{actor.metadata.name} {actor.metadata.name}
</SelectItem> </SelectItem>
); );
case ActorType.KMIP_CLIENT:
return (
<SelectItem
value={`${actor.type}-${actor.metadata.clientId}`}
key={`kmip-client-filter-${actor.metadata.clientId}`}
>
{actor.metadata.name}
</SelectItem>
);
default: default:
return ( return (
<SelectItem value="actor-none" key="actor-none"> <SelectItem value="actor-none" key="actor-none">

View File

@@ -46,6 +46,13 @@ export const LogsTableRow = ({ auditLog, isOrgAuditLogs, showActorColumn }: Prop
<p>Platform</p> <p>Platform</p>
</Td> </Td>
); );
case ActorType.KMIP_CLIENT:
return (
<Td>
<p>{actor.metadata.name}</p>
<p>KMIP Client</p>
</Td>
);
case ActorType.UNKNOWN_USER: case ActorType.UNKNOWN_USER:
return ( return (
<Td> <Td>