mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #3045 from Infisical/daniel/auditlogs-secret-path-query
feat(audit-logs): query by secret path
This commit is contained in:
@@ -39,11 +39,13 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
offset = 0,
|
offset = 0,
|
||||||
actorId,
|
actorId,
|
||||||
actorType,
|
actorType,
|
||||||
|
secretPath,
|
||||||
eventType,
|
eventType,
|
||||||
eventMetadata
|
eventMetadata
|
||||||
}: Omit<TFindQuery, "actor" | "eventType"> & {
|
}: Omit<TFindQuery, "actor" | "eventType"> & {
|
||||||
actorId?: string;
|
actorId?: string;
|
||||||
actorType?: ActorType;
|
actorType?: ActorType;
|
||||||
|
secretPath?: string;
|
||||||
eventType?: EventType[];
|
eventType?: EventType[];
|
||||||
eventMetadata?: Record<string, string>;
|
eventMetadata?: Record<string, string>;
|
||||||
},
|
},
|
||||||
@@ -88,6 +90,10 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (projectId && secretPath) {
|
||||||
|
void sqlQuery.whereRaw(`"eventMetadata" @> jsonb_build_object('secretPath', ?::text)`, [secretPath]);
|
||||||
|
}
|
||||||
|
|
||||||
// Filter by actor type
|
// Filter by actor type
|
||||||
if (actorType) {
|
if (actorType) {
|
||||||
void sqlQuery.where("actor", actorType);
|
void sqlQuery.where("actor", actorType);
|
||||||
|
|||||||
@@ -46,10 +46,6 @@ export const auditLogServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* NOTE (dangtony98): Update this to organization-level audit log permission check once audit logs are moved
|
|
||||||
* to the organization level ✅
|
|
||||||
*/
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +60,7 @@ export const auditLogServiceFactory = ({
|
|||||||
actorId: filter.auditLogActorId,
|
actorId: filter.auditLogActorId,
|
||||||
actorType: filter.actorType,
|
actorType: filter.actorType,
|
||||||
eventMetadata: filter.eventMetadata,
|
eventMetadata: filter.eventMetadata,
|
||||||
|
secretPath: filter.secretPath,
|
||||||
...(filter.projectId ? { projectId: filter.projectId } : { orgId: actorOrgId })
|
...(filter.projectId ? { projectId: filter.projectId } : { orgId: actorOrgId })
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export type TListProjectAuditLogDTO = {
|
|||||||
projectId?: string;
|
projectId?: string;
|
||||||
auditLogActorId?: string;
|
auditLogActorId?: string;
|
||||||
actorType?: ActorType;
|
actorType?: ActorType;
|
||||||
|
secretPath?: string;
|
||||||
eventMetadata?: Record<string, string>;
|
eventMetadata?: Record<string, string>;
|
||||||
};
|
};
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|||||||
@@ -828,6 +828,8 @@ export const AUDIT_LOGS = {
|
|||||||
projectId:
|
projectId:
|
||||||
"Optionally filter logs by project ID. If not provided, logs from the entire organization will be returned.",
|
"Optionally filter logs by project ID. If not provided, logs from the entire organization will be returned.",
|
||||||
eventType: "The type of the event to export.",
|
eventType: "The type of the event to export.",
|
||||||
|
secretPath:
|
||||||
|
"The path 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.",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { AUDIT_LOGS, ORGANIZATIONS } from "@app/lib/api-docs";
|
import { AUDIT_LOGS, ORGANIZATIONS } from "@app/lib/api-docs";
|
||||||
import { getLastMidnightDateISO } from "@app/lib/fn";
|
import { getLastMidnightDateISO, removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { slugSchema } from "@app/server/lib/schemas";
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
@@ -113,6 +113,12 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
projectId: z.string().optional().describe(AUDIT_LOGS.EXPORT.projectId),
|
projectId: z.string().optional().describe(AUDIT_LOGS.EXPORT.projectId),
|
||||||
actorType: z.nativeEnum(ActorType).optional(),
|
actorType: z.nativeEnum(ActorType).optional(),
|
||||||
|
secretPath: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((val) => (!val ? val : removeTrailingSlash(val)))
|
||||||
|
.describe(AUDIT_LOGS.EXPORT.secretPath),
|
||||||
|
|
||||||
// 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
|
||||||
.string()
|
.string()
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export type TGetAuditLogsFilter = {
|
|||||||
actorType?: ActorType;
|
actorType?: ActorType;
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
actor?: string; // user ID format
|
actor?: string; // user ID format
|
||||||
|
secretPath?: string;
|
||||||
startDate?: Date;
|
startDate?: Date;
|
||||||
endDate?: Date;
|
endDate?: Date;
|
||||||
limit: number;
|
limit: number;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
FilterableSelect,
|
FilterableSelect,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
Input,
|
||||||
Select,
|
Select,
|
||||||
SelectItem
|
SelectItem
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
@@ -22,6 +23,7 @@ import { useGetAuditLogActorFilterOpts, useGetUserWorkspaces } from "@app/hooks/
|
|||||||
import { eventToNameMap, userAgentTTypeoNameMap } from "@app/hooks/api/auditLogs/constants";
|
import { eventToNameMap, userAgentTTypeoNameMap } from "@app/hooks/api/auditLogs/constants";
|
||||||
import { ActorType, EventType } from "@app/hooks/api/auditLogs/enums";
|
import { ActorType, EventType } from "@app/hooks/api/auditLogs/enums";
|
||||||
import { Actor } from "@app/hooks/api/auditLogs/types";
|
import { Actor } from "@app/hooks/api/auditLogs/types";
|
||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
import { AuditLogFilterFormData } from "./types";
|
import { AuditLogFilterFormData } from "./types";
|
||||||
|
|
||||||
@@ -50,6 +52,7 @@ export const LogsFilter = ({
|
|||||||
className,
|
className,
|
||||||
control,
|
control,
|
||||||
reset,
|
reset,
|
||||||
|
setValue,
|
||||||
watch
|
watch
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false);
|
const [isStartDatePickerOpen, setIsStartDatePickerOpen] = useState(false);
|
||||||
@@ -101,6 +104,7 @@ export const LogsFilter = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const selectedEventTypes = watch("eventType") as EventType[] | undefined;
|
const selectedEventTypes = watch("eventType") as EventType[] | undefined;
|
||||||
|
const selectedProject = watch("project");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -109,30 +113,48 @@ export const LogsFilter = ({
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isOrgAuditLogs && workspacesInOrg.length > 0 && (
|
<div className="flex items-center gap-4">
|
||||||
<Controller
|
{isOrgAuditLogs && workspacesInOrg.length > 0 && (
|
||||||
control={control}
|
<Controller
|
||||||
name="project"
|
control={control}
|
||||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
name="project"
|
||||||
<FormControl
|
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||||
label="Project"
|
<FormControl
|
||||||
errorText={error?.message}
|
label="Project"
|
||||||
isError={Boolean(error)}
|
errorText={error?.message}
|
||||||
className="mr-12 w-64"
|
isError={Boolean(error)}
|
||||||
>
|
className="w-64"
|
||||||
<FilterableSelect
|
>
|
||||||
value={value}
|
<FilterableSelect
|
||||||
isClearable
|
value={value}
|
||||||
onChange={onChange}
|
isClearable
|
||||||
placeholder="Select a project..."
|
onChange={(e) => {
|
||||||
options={workspacesInOrg.map(({ name, id }) => ({ name, id }))}
|
if (e === null) {
|
||||||
getOptionValue={(option) => option.id}
|
setValue("secretPath", "");
|
||||||
getOptionLabel={(option) => option.name}
|
}
|
||||||
/>
|
onChange(e);
|
||||||
</FormControl>
|
}}
|
||||||
)}
|
placeholder="Select a project..."
|
||||||
/>
|
options={workspacesInOrg.map(({ name, id, type }) => ({ name, id, type }))}
|
||||||
)}
|
getOptionValue={(option) => option.id}
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{selectedProject?.type === ProjectType.SecretManager && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="secretPath"
|
||||||
|
render={({ field: { onChange, value, ...field } }) => (
|
||||||
|
<FormControl label="Secret path" className="w-40">
|
||||||
|
<Input {...field} value={value} onChange={(e) => onChange(e.target.value)} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="mt-1 flex items-center space-x-2">
|
<div className="mt-1 flex items-center space-x-2">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
||||||
import { withPermission } from "@app/hoc";
|
import { withPermission } from "@app/hoc";
|
||||||
|
import { useDebounce } from "@app/hooks";
|
||||||
import { ActorType, EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums";
|
import { ActorType, EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums";
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
@@ -67,10 +68,13 @@ export const LogsSection = withPermission(
|
|||||||
const userAgentType = watch("userAgentType") as UserAgentType | undefined;
|
const userAgentType = watch("userAgentType") as UserAgentType | undefined;
|
||||||
const actor = watch("actor");
|
const actor = watch("actor");
|
||||||
const projectId = watch("project")?.id;
|
const projectId = watch("project")?.id;
|
||||||
|
const secretPath = watch("secretPath");
|
||||||
|
|
||||||
const startDate = watch("startDate");
|
const startDate = watch("startDate");
|
||||||
const endDate = watch("endDate");
|
const endDate = watch("endDate");
|
||||||
|
|
||||||
|
const [debouncedSecretPath] = useDebounce<string>(secretPath!, 500);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{showFilters && (
|
{showFilters && (
|
||||||
@@ -90,6 +94,7 @@ export const LogsSection = withPermission(
|
|||||||
isOrgAuditLogs={isOrgAuditLogs}
|
isOrgAuditLogs={isOrgAuditLogs}
|
||||||
showActorColumn={!!showActorColumn}
|
showActorColumn={!!showActorColumn}
|
||||||
filter={{
|
filter={{
|
||||||
|
secretPath: debouncedSecretPath || undefined,
|
||||||
eventMetadata: presets?.eventMetadata,
|
eventMetadata: presets?.eventMetadata,
|
||||||
projectId,
|
projectId,
|
||||||
actorType: presets?.actorType,
|
actorType: presets?.actorType,
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums";
|
import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums";
|
||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
export const auditLogFilterFormSchema = z
|
export const auditLogFilterFormSchema = z
|
||||||
.object({
|
.object({
|
||||||
eventMetadata: z.object({}).optional(),
|
eventMetadata: z.object({}).optional(),
|
||||||
project: z.object({ id: z.string(), name: z.string() }).optional().nullable(),
|
project: z
|
||||||
|
.object({ id: z.string(), name: z.string(), type: z.nativeEnum(ProjectType) })
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
eventType: z.nativeEnum(EventType).array(),
|
eventType: z.nativeEnum(EventType).array(),
|
||||||
actor: z.string().optional(),
|
actor: z.string().optional(),
|
||||||
userAgentType: z.nativeEnum(UserAgentType),
|
userAgentType: z.nativeEnum(UserAgentType),
|
||||||
|
secretPath: 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(),
|
||||||
|
|||||||
Reference in New Issue
Block a user