feat(audit-logs): filter audit logs by secret key

This commit is contained in:
Daniel Hougaard
2025-03-26 05:54:33 +04:00
parent 6482e88dfc
commit 4654a17e5f
9 changed files with 50 additions and 5 deletions
@@ -40,12 +40,14 @@ export const auditLogDALFactory = (db: TDbClient) => {
actorId, actorId,
actorType, actorType,
secretPath, secretPath,
secretKey,
eventType, eventType,
eventMetadata eventMetadata
}: Omit<TFindQuery, "actor" | "eventType"> & { }: Omit<TFindQuery, "actor" | "eventType"> & {
actorId?: string; actorId?: string;
actorType?: ActorType; actorType?: ActorType;
secretPath?: string; secretPath?: string;
secretKey?: string;
eventType?: EventType[]; eventType?: EventType[];
eventMetadata?: Record<string, string>; eventMetadata?: Record<string, string>;
}, },
@@ -90,8 +92,24 @@ export const auditLogDALFactory = (db: TDbClient) => {
}); });
} }
if (projectId && secretPath) { if (projectId) {
void sqlQuery.whereRaw(`"eventMetadata" @> jsonb_build_object('secretPath', ?::text)`, [secretPath]); if (secretPath) {
void sqlQuery.whereRaw(`"eventMetadata"->>'secretPath' = ?`, [secretPath]);
}
if (secretKey) {
void sqlQuery.whereRaw(
`(
"eventMetadata"->>'secretKey' = ?
OR
EXISTS (
SELECT 1
FROM jsonb_array_elements("eventMetadata"->'secrets') AS element
WHERE element->>'secretKey' = ?
)
)`,
[secretKey, secretKey]
);
}
} }
// Filter by actor type // Filter by actor type
@@ -63,6 +63,7 @@ export const auditLogServiceFactory = ({
actorType: filter.actorType, actorType: filter.actorType,
eventMetadata: filter.eventMetadata, eventMetadata: filter.eventMetadata,
secretPath: filter.secretPath, secretPath: filter.secretPath,
secretKey: filter.secretKey,
...(filter.projectId ? { projectId: filter.projectId } : { orgId: actorOrgId }) ...(filter.projectId ? { projectId: filter.projectId } : { orgId: actorOrgId })
}); });
@@ -36,6 +36,7 @@ export type TListProjectAuditLogDTO = {
auditLogActorId?: string; auditLogActorId?: string;
actorType?: ActorType; actorType?: ActorType;
secretPath?: string; secretPath?: string;
secretKey?: string;
eventMetadata?: Record<string, string>; eventMetadata?: Record<string, string>;
}; };
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
+2
View File
@@ -843,6 +843,8 @@ export const AUDIT_LOGS = {
eventType: "The type of the event to export.", eventType: "The type of the event to export.",
secretPath: secretPath:
"The path of the secret to query audit logs for. Note that the projectId parameter must also be provided.", "The path of the secret to query audit logs for. Note that the projectId parameter must also be provided.",
secretKey:
"The key of the secret to query audit logs for. Note that the projectId parameter must also be provided.",
userAgentType: "Choose which consuming application to export audit logs for.", userAgentType: "Choose which consuming application to export audit logs for.",
eventMetadata: eventMetadata:
"Filter by event metadata key-value pairs. Formatted as `key1=value1,key2=value2`, with comma-separation.", "Filter by event metadata key-value pairs. Formatted as `key1=value1,key2=value2`, with comma-separation.",
@@ -118,6 +118,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
.optional() .optional()
.transform((val) => (!val ? val : removeTrailingSlash(val))) .transform((val) => (!val ? val : removeTrailingSlash(val)))
.describe(AUDIT_LOGS.EXPORT.secretPath), .describe(AUDIT_LOGS.EXPORT.secretPath),
secretKey: z.string().optional().describe(AUDIT_LOGS.EXPORT.secretKey),
// eventType is split with , for multiple values, we need to transform it to array // eventType is split with , for multiple values, we need to transform it to array
eventType: z eventType: z
@@ -11,6 +11,7 @@ export type TGetAuditLogsFilter = {
projectId?: string; projectId?: string;
actor?: string; // user ID format actor?: string; // user ID format
secretPath?: string; secretPath?: string;
secretKey?: string;
startDate?: Date; startDate?: Date;
endDate?: Date; endDate?: Date;
limit: number; limit: number;
@@ -157,7 +157,24 @@ export const LogsFilter = ({
control={control} control={control}
name="secretPath" name="secretPath"
render={({ field: { onChange, value, ...field } }) => ( render={({ field: { onChange, value, ...field } }) => (
<FormControl label="Secret path" className="w-40"> <FormControl label="Secret Path" className="w-40">
<Input
placeholder="/folder"
{...field}
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</FormControl>
)}
/>
)}
{selectedProject?.type === ProjectType.SecretManager && (
<Controller
control={control}
name="secretKey"
render={({ field: { onChange, value, ...field } }) => (
<FormControl label="Secret Key" className="w-40">
<Input {...field} value={value} onChange={(e) => onChange(e.target.value)} /> <Input {...field} value={value} onChange={(e) => onChange(e.target.value)} />
</FormControl> </FormControl>
)} )}
@@ -289,7 +306,7 @@ export const LogsFilter = ({
control={control} control={control}
render={({ field: { onChange, ...field }, fieldState: { error } }) => { render={({ field: { onChange, ...field }, fieldState: { error } }) => {
return ( return (
<FormControl label="Start date" errorText={error?.message} isError={Boolean(error)}> <FormControl label="Start Date" errorText={error?.message} isError={Boolean(error)}>
<DatePicker <DatePicker
value={field.value || undefined} value={field.value || undefined}
onChange={onChange} onChange={onChange}
@@ -309,7 +326,7 @@ export const LogsFilter = ({
control={control} control={control}
render={({ field: { onChange, ...field }, fieldState: { error } }) => { render={({ field: { onChange, ...field }, fieldState: { error } }) => {
return ( return (
<FormControl label="End date" errorText={error?.message} isError={Boolean(error)}> <FormControl label="End Date" errorText={error?.message} isError={Boolean(error)}>
<DatePicker <DatePicker
value={field.value || undefined} value={field.value || undefined}
onChange={onChange} onChange={onChange}
@@ -58,11 +58,13 @@ export const LogsSection = withPermission(
const actor = watch("actor"); const actor = watch("actor");
const projectId = watch("project")?.id; const projectId = watch("project")?.id;
const secretPath = watch("secretPath"); const secretPath = watch("secretPath");
const secretKey = watch("secretKey");
const startDate = watch("startDate"); const startDate = watch("startDate");
const endDate = watch("endDate"); const endDate = watch("endDate");
const [debouncedSecretPath] = useDebounce<string>(secretPath!, 500); const [debouncedSecretPath] = useDebounce<string>(secretPath!, 500);
const [debouncedSecretKey] = useDebounce<string>(secretKey!, 500);
return ( return (
<div> <div>
@@ -81,6 +83,7 @@ export const LogsSection = withPermission(
refetchInterval={refetchInterval} refetchInterval={refetchInterval}
filter={{ filter={{
secretPath: debouncedSecretPath || undefined, secretPath: debouncedSecretPath || undefined,
secretKey: debouncedSecretKey || undefined,
eventMetadata: presets?.eventMetadata, eventMetadata: presets?.eventMetadata,
projectId, projectId,
actorType: presets?.actorType, actorType: presets?.actorType,
@@ -14,6 +14,7 @@ export const auditLogFilterFormSchema = z
actor: z.string().optional(), actor: z.string().optional(),
userAgentType: z.nativeEnum(UserAgentType), userAgentType: z.nativeEnum(UserAgentType),
secretPath: z.string().optional(), secretPath: z.string().optional(),
secretKey: z.string().optional(),
startDate: z.date().optional(), startDate: z.date().optional(),
endDate: z.date().optional(), endDate: z.date().optional(),
page: z.coerce.number().optional(), page: z.coerce.number().optional(),