fix: update event permissions system (#4355)

* fix: update project permission types

* fix: permissions

* fix: PR changes

* chore: update docs

* fix:  greptile changes

* fix: secret imports router changes

* fix: type change

* fix: lint

* fix: type change

* fix: check plan type in publish

* fix: permissions

* fix: import change

* fix: lint fix
This commit is contained in:
Sid
2025-08-14 13:47:59 +05:30
committed by GitHub
parent a3b6fa9a53
commit 273a7b9657
19 changed files with 461 additions and 188 deletions

View File

@@ -1,8 +1,6 @@
import { AxiosError, RawAxiosRequestHeaders } from "axios"; import { AxiosError, RawAxiosRequestHeaders } from "axios";
import { ProjectType, SecretKeyEncoding } from "@app/db/schemas"; import { SecretKeyEncoding } from "@app/db/schemas";
import { TEventBusService } from "@app/ee/services/event/event-bus-service";
import { TopicName, toPublishableEvent } from "@app/ee/services/event/types";
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
@@ -22,7 +20,6 @@ type TAuditLogQueueServiceFactoryDep = {
queueService: TQueueServiceFactory; queueService: TQueueServiceFactory;
projectDAL: Pick<TProjectDALFactory, "findById">; projectDAL: Pick<TProjectDALFactory, "findById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
eventBusService: TEventBusService;
}; };
export type TAuditLogQueueServiceFactory = { export type TAuditLogQueueServiceFactory = {
@@ -38,8 +35,7 @@ export const auditLogQueueServiceFactory = async ({
queueService, queueService,
projectDAL, projectDAL,
licenseService, licenseService,
auditLogStreamDAL, auditLogStreamDAL
eventBusService
}: TAuditLogQueueServiceFactoryDep): Promise<TAuditLogQueueServiceFactory> => { }: TAuditLogQueueServiceFactoryDep): Promise<TAuditLogQueueServiceFactory> => {
const pushToLog = async (data: TCreateAuditLogDTO) => { const pushToLog = async (data: TCreateAuditLogDTO) => {
await queueService.queue<QueueName.AuditLog>(QueueName.AuditLog, QueueJobs.AuditLog, data, { await queueService.queue<QueueName.AuditLog>(QueueName.AuditLog, QueueJobs.AuditLog, data, {
@@ -145,16 +141,6 @@ export const auditLogQueueServiceFactory = async ({
) )
); );
} }
const publishable = toPublishableEvent(event);
if (publishable) {
await eventBusService.publish(TopicName.CoreServers, {
type: ProjectType.SecretManager,
source: "infiscal",
data: publishable.data
});
}
}); });
return { return {

View File

@@ -3,7 +3,7 @@ import { z } from "zod";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { EventSchema, TopicName } from "./types"; import { BusEventSchema, TopicName } from "./types";
export const eventBusFactory = (redis: Redis) => { export const eventBusFactory = (redis: Redis) => {
const publisher = redis.duplicate(); const publisher = redis.duplicate();
@@ -28,7 +28,7 @@ export const eventBusFactory = (redis: Redis) => {
* @param topic - The topic to publish the event to. * @param topic - The topic to publish the event to.
* @param event - The event data to publish. * @param event - The event data to publish.
*/ */
const publish = async <T extends z.input<typeof EventSchema>>(topic: TopicName, event: T) => { const publish = async <T extends z.input<typeof BusEventSchema>>(topic: TopicName, event: T) => {
const json = JSON.stringify(event); const json = JSON.stringify(event);
return publisher.publish(topic, json, (err) => { return publisher.publish(topic, json, (err) => {
@@ -44,7 +44,7 @@ export const eventBusFactory = (redis: Redis) => {
* @template T - The type of the event data, which should match the schema defined in EventSchema. * @template T - The type of the event data, which should match the schema defined in EventSchema.
* @returns A function that can be called to unsubscribe from the event bus. * @returns A function that can be called to unsubscribe from the event bus.
*/ */
const subscribe = <T extends z.infer<typeof EventSchema>>(fn: (data: T) => Promise<void> | void) => { const subscribe = <T extends z.infer<typeof BusEventSchema>>(fn: (data: T) => Promise<void> | void) => {
// Not using async await cause redis client's `on` method does not expect async listeners. // Not using async await cause redis client's `on` method does not expect async listeners.
const listener = (channel: string, message: string) => { const listener = (channel: string, message: string) => {
try { try {

View File

@@ -7,7 +7,7 @@ import { logger } from "@app/lib/logger";
import { TEventBusService } from "./event-bus-service"; import { TEventBusService } from "./event-bus-service";
import { createEventStreamClient, EventStreamClient, IEventStreamClientOpts } from "./event-sse-stream"; import { createEventStreamClient, EventStreamClient, IEventStreamClientOpts } from "./event-sse-stream";
import { EventData, RegisteredEvent, toBusEventName } from "./types"; import { BusEvent, RegisteredEvent } from "./types";
const AUTH_REFRESH_INTERVAL = 60 * 1000; const AUTH_REFRESH_INTERVAL = 60 * 1000;
const HEART_BEAT_INTERVAL = 15 * 1000; const HEART_BEAT_INTERVAL = 15 * 1000;
@@ -69,8 +69,8 @@ export const sseServiceFactory = (bus: TEventBusService, redis: Redis) => {
} }
}; };
function filterEventsForClient(client: EventStreamClient, event: EventData, registered: RegisteredEvent[]) { function filterEventsForClient(client: EventStreamClient, event: BusEvent, registered: RegisteredEvent[]) {
const eventType = toBusEventName(event.data.eventType); const eventType = event.data.event;
const match = registered.find((r) => r.event === eventType); const match = registered.find((r) => r.event === eventType);
if (!match) return; if (!match) return;

View File

@@ -12,7 +12,7 @@ import { KeyStorePrefixes } from "@app/keystore/keystore";
import { conditionsMatcher } from "@app/lib/casl"; import { conditionsMatcher } from "@app/lib/casl";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { EventData, RegisteredEvent } from "./types"; import { BusEvent, RegisteredEvent } from "./types";
export const getServerSentEventsHeaders = () => export const getServerSentEventsHeaders = () =>
({ ({
@@ -55,7 +55,7 @@ export type EventStreamClient = {
id: string; id: string;
stream: Readable; stream: Readable;
open: () => Promise<void>; open: () => Promise<void>;
send: (data: EventMessage | EventData) => void; send: (data: EventMessage | BusEvent) => void;
ping: () => Promise<void>; ping: () => Promise<void>;
refresh: () => Promise<void>; refresh: () => Promise<void>;
close: () => void; close: () => void;
@@ -73,15 +73,12 @@ export function createEventStreamClient(redis: Redis, options: IEventStreamClien
return { return {
subject: options.type, subject: options.type,
action: "subscribe", action: "subscribe",
conditions: { conditions: hasConditions
eventType: r.event, ? {
...(hasConditions environment: r.conditions?.environmentSlug ?? "",
? { secretPath: { $glob: secretPath }
environment: r.conditions?.environmentSlug ?? "", }
secretPath: { $glob: secretPath } : undefined
}
: {})
}
}; };
}); });
@@ -98,7 +95,7 @@ export function createEventStreamClient(redis: Redis, options: IEventStreamClien
// We will manually push data to the stream // We will manually push data to the stream
stream._read = () => {}; stream._read = () => {};
const send = (data: EventMessage | EventData) => { const send = (data: EventMessage | BusEvent) => {
const chunk = serializeSseEvent(data); const chunk = serializeSseEvent(data);
if (!stream.push(chunk)) { if (!stream.push(chunk)) {
logger.debug("Backpressure detected: dropped manual event"); logger.debug("Backpressure detected: dropped manual event");

View File

@@ -1,7 +1,8 @@
import { z } from "zod"; import { z } from "zod";
import { ProjectType } from "@app/db/schemas"; import { ProjectType } from "@app/db/schemas";
import { Event, EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ProjectPermissionSecretEventActions } from "../permission/project-permission";
export enum TopicName { export enum TopicName {
CoreServers = "infisical::core-servers" CoreServers = "infisical::core-servers"
@@ -10,84 +11,44 @@ export enum TopicName {
export enum BusEventName { export enum BusEventName {
CreateSecret = "secret:create", CreateSecret = "secret:create",
UpdateSecret = "secret:update", UpdateSecret = "secret:update",
DeleteSecret = "secret:delete" DeleteSecret = "secret:delete",
ImportMutation = "secret:import-mutation"
} }
type PublisableEventTypes = export const Mappings = {
| EventType.CREATE_SECRET BusEventToAction(input: BusEventName) {
| EventType.CREATE_SECRETS switch (input) {
| EventType.DELETE_SECRET case BusEventName.CreateSecret:
| EventType.DELETE_SECRETS return ProjectPermissionSecretEventActions.SubscribeCreated;
| EventType.UPDATE_SECRETS case BusEventName.DeleteSecret:
| EventType.UPDATE_SECRET; return ProjectPermissionSecretEventActions.SubscribeDeleted;
case BusEventName.ImportMutation:
export function toBusEventName(input: EventType) { return ProjectPermissionSecretEventActions.SubscribeImportMutations;
switch (input) { case BusEventName.UpdateSecret:
case EventType.CREATE_SECRET: return ProjectPermissionSecretEventActions.SubscribeUpdated;
case EventType.CREATE_SECRETS: default:
return BusEventName.CreateSecret; throw new Error("Unknown bus event name");
case EventType.UPDATE_SECRET:
case EventType.UPDATE_SECRETS:
return BusEventName.UpdateSecret;
case EventType.DELETE_SECRET:
case EventType.DELETE_SECRETS:
return BusEventName.DeleteSecret;
default:
return null;
}
}
const isBulkEvent = (event: Event): event is Extract<Event, { metadata: { secrets: Array<unknown> } }> => {
return event.type.endsWith("-secrets"); // Feels so wrong
};
export const toPublishableEvent = (event: Event) => {
const name = toBusEventName(event.type);
if (!name) return null;
const e = event as Extract<Event, { type: PublisableEventTypes }>;
if (isBulkEvent(e)) {
return {
name,
isBulk: true,
data: {
eventType: e.type,
payload: e.metadata.secrets.map((s) => ({
environment: e.metadata.environment,
secretPath: e.metadata.secretPath,
...s
}))
}
} as const;
}
return {
name,
isBulk: false,
data: {
eventType: e.type,
payload: {
...e.metadata,
environment: e.metadata.environment
}
} }
} as const; }
}; };
export const EventName = z.nativeEnum(BusEventName); export const EventName = z.nativeEnum(BusEventName);
const EventSecretPayload = z.object({ const EventSecretPayload = z.object({
secretPath: z.string().optional(),
secretId: z.string(), secretId: z.string(),
secretPath: z.string().optional(),
secretKey: z.string(), secretKey: z.string(),
environment: z.string() environment: z.string()
}); });
const EventImportMutationPayload = z.object({
secretPath: z.string(),
environment: z.string()
});
export type EventSecret = z.infer<typeof EventSecretPayload>; export type EventSecret = z.infer<typeof EventSecretPayload>;
export const EventSchema = z.object({ export const BusEventSchema = z.object({
datacontenttype: z.literal("application/json").optional().default("application/json"), datacontenttype: z.literal("application/json").optional().default("application/json"),
type: z.nativeEnum(ProjectType), type: z.nativeEnum(ProjectType),
source: z.string(), source: z.string(),
@@ -95,25 +56,38 @@ export const EventSchema = z.object({
.string() .string()
.optional() .optional()
.default(() => new Date().toISOString()), .default(() => new Date().toISOString()),
data: z.discriminatedUnion("eventType", [ data: z.discriminatedUnion("event", [
z.object({ z.object({
specversion: z.number().optional().default(1), specversion: z.number().optional().default(1),
eventType: z.enum([EventType.CREATE_SECRET, EventType.UPDATE_SECRET, EventType.DELETE_SECRET]), event: z.enum([BusEventName.CreateSecret, BusEventName.DeleteSecret, BusEventName.UpdateSecret]),
payload: EventSecretPayload payload: z.union([EventSecretPayload, EventSecretPayload.array()])
}), }),
z.object({ z.object({
specversion: z.number().optional().default(1), specversion: z.number().optional().default(1),
eventType: z.enum([EventType.CREATE_SECRETS, EventType.UPDATE_SECRETS, EventType.DELETE_SECRETS]), event: z.enum([BusEventName.ImportMutation]),
payload: EventSecretPayload.array() payload: z.union([EventImportMutationPayload, EventImportMutationPayload.array()])
}) })
// Add more event types as needed // Add more event types as needed
]) ])
}); });
export type EventData = z.infer<typeof EventSchema>; export type BusEvent = z.infer<typeof BusEventSchema>;
type PublishableEventPayload = z.input<typeof BusEventSchema>["data"];
type PublishableSecretEvent = Extract<
PublishableEventPayload,
{ event: Exclude<BusEventName, BusEventName.ImportMutation> }
>["payload"];
export type PublishableEvent = {
created?: PublishableSecretEvent;
updated?: PublishableSecretEvent;
deleted?: PublishableSecretEvent;
importMutation?: Extract<PublishableEventPayload, { event: BusEventName.ImportMutation }>["payload"];
};
export const EventRegisterSchema = z.object({ export const EventRegisterSchema = z.object({
event: EventName, event: z.nativeEnum(BusEventName),
conditions: z conditions: z
.object({ .object({
secretPath: z.string().optional().default("/"), secretPath: z.string().optional().default("/"),

View File

@@ -161,8 +161,7 @@ const buildAdminPermissionRules = () => {
ProjectPermissionSecretActions.ReadValue, ProjectPermissionSecretActions.ReadValue,
ProjectPermissionSecretActions.Create, ProjectPermissionSecretActions.Create,
ProjectPermissionSecretActions.Edit, ProjectPermissionSecretActions.Edit,
ProjectPermissionSecretActions.Delete, ProjectPermissionSecretActions.Delete
ProjectPermissionSecretActions.Subscribe
], ],
ProjectPermissionSub.Secrets ProjectPermissionSub.Secrets
); );
@@ -266,8 +265,7 @@ const buildMemberPermissionRules = () => {
ProjectPermissionSecretActions.ReadValue, ProjectPermissionSecretActions.ReadValue,
ProjectPermissionSecretActions.Edit, ProjectPermissionSecretActions.Edit,
ProjectPermissionSecretActions.Create, ProjectPermissionSecretActions.Create,
ProjectPermissionSecretActions.Delete, ProjectPermissionSecretActions.Delete
ProjectPermissionSecretActions.Subscribe
], ],
ProjectPermissionSub.Secrets ProjectPermissionSub.Secrets
); );

View File

@@ -36,8 +36,7 @@ export enum ProjectPermissionSecretActions {
ReadValue = "readValue", ReadValue = "readValue",
Create = "create", Create = "create",
Edit = "edit", Edit = "edit",
Delete = "delete", Delete = "delete"
Subscribe = "subscribe"
} }
export enum ProjectPermissionCmekActions { export enum ProjectPermissionCmekActions {
@@ -158,6 +157,13 @@ export enum ProjectPermissionSecretScanningConfigActions {
Update = "update-configs" Update = "update-configs"
} }
export enum ProjectPermissionSecretEventActions {
SubscribeCreated = "subscribe-on-created",
SubscribeUpdated = "subscribe-on-updated",
SubscribeDeleted = "subscribe-on-deleted",
SubscribeImportMutations = "subscribe-on-import-mutations"
}
export enum ProjectPermissionSub { export enum ProjectPermissionSub {
Role = "role", Role = "role",
Member = "member", Member = "member",
@@ -197,7 +203,8 @@ export enum ProjectPermissionSub {
Kmip = "kmip", Kmip = "kmip",
SecretScanningDataSources = "secret-scanning-data-sources", SecretScanningDataSources = "secret-scanning-data-sources",
SecretScanningFindings = "secret-scanning-findings", SecretScanningFindings = "secret-scanning-findings",
SecretScanningConfigs = "secret-scanning-configs" SecretScanningConfigs = "secret-scanning-configs",
SecretEvents = "secret-events"
} }
export type SecretSubjectFields = { export type SecretSubjectFields = {
@@ -205,7 +212,13 @@ export type SecretSubjectFields = {
secretPath: string; secretPath: string;
secretName?: string; secretName?: string;
secretTags?: string[]; secretTags?: string[];
eventType?: string; };
export type SecretEventSubjectFields = {
environment: string;
secretPath: string;
secretName?: string;
secretTags?: string[];
}; };
export type SecretFolderSubjectFields = { export type SecretFolderSubjectFields = {
@@ -344,7 +357,11 @@ export type ProjectPermissionSet =
| [ProjectPermissionCommitsActions, ProjectPermissionSub.Commits] | [ProjectPermissionCommitsActions, ProjectPermissionSub.Commits]
| [ProjectPermissionSecretScanningDataSourceActions, ProjectPermissionSub.SecretScanningDataSources] | [ProjectPermissionSecretScanningDataSourceActions, ProjectPermissionSub.SecretScanningDataSources]
| [ProjectPermissionSecretScanningFindingActions, ProjectPermissionSub.SecretScanningFindings] | [ProjectPermissionSecretScanningFindingActions, ProjectPermissionSub.SecretScanningFindings]
| [ProjectPermissionSecretScanningConfigActions, ProjectPermissionSub.SecretScanningConfigs]; | [ProjectPermissionSecretScanningConfigActions, ProjectPermissionSub.SecretScanningConfigs]
| [
ProjectPermissionSecretEventActions,
ProjectPermissionSub.SecretEvents | (ForcedSubject<ProjectPermissionSub.SecretEvents> & SecretEventSubjectFields)
];
const SECRET_PATH_MISSING_SLASH_ERR_MSG = "Invalid Secret Path; it must start with a '/'"; const SECRET_PATH_MISSING_SLASH_ERR_MSG = "Invalid Secret Path; it must start with a '/'";
const SECRET_PATH_PERMISSION_OPERATOR_SCHEMA = z.union([ const SECRET_PATH_PERMISSION_OPERATOR_SCHEMA = z.union([
@@ -877,7 +894,16 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
"When specified, only matching conditions will be allowed to access given resource." "When specified, only matching conditions will be allowed to access given resource."
).optional() ).optional()
}), }),
z.object({
subject: z.literal(ProjectPermissionSub.SecretEvents).describe("The entity this permission pertains to."),
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretEventActions).describe(
"Describe what action an entity can take."
),
conditions: SecretSyncConditionV2Schema.describe(
"When specified, only matching conditions will be allowed to access given resource."
).optional()
}),
...GeneralPermissionSchema ...GeneralPermissionSchema
]); ]);

View File

@@ -952,13 +952,39 @@ export const secretApprovalRequestServiceFactory = ({
if (!folder) { if (!folder) {
throw new NotFoundError({ message: `Folder with ID '${folderId}' not found in project with ID '${projectId}'` }); throw new NotFoundError({ message: `Folder with ID '${folderId}' not found in project with ID '${projectId}'` });
} }
const { secrets } = mergeStatus;
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
projectId, projectId,
orgId: actorOrgId, orgId: actorOrgId,
secretPath: folder.path, secretPath: folder.path,
environmentSlug: folder.environmentSlug, environmentSlug: folder.environmentSlug,
actorId, actorId,
actor actor,
event: {
created: secrets.created.map((el) => ({
environment: folder.environmentSlug,
secretPath: folder.path,
secretId: el.id,
// @ts-expect-error - not present on V1 secrets
secretKey: el.key as string
})),
updated: secrets.updated.map((el) => ({
environment: folder.environmentSlug,
secretPath: folder.path,
secretId: el.id,
// @ts-expect-error - not present on V1 secrets
secretKey: el.key as string
})),
deleted: secrets.deleted.map((el) => ({
environment: folder.environmentSlug,
secretPath: folder.path,
secretId: el.id,
// @ts-expect-error - not present on V1 secrets
secretKey: el.key as string
}))
}
}); });
if (isSoftEnforcement) { if (isSoftEnforcement) {

View File

@@ -560,8 +560,7 @@ export const registerRoutes = async (
queueService, queueService,
projectDAL, projectDAL,
licenseService, licenseService,
auditLogStreamDAL, auditLogStreamDAL
eventBusService
}); });
const auditLogService = auditLogServiceFactory({ auditLogDAL, permissionService, auditLogQueue }); const auditLogService = auditLogServiceFactory({ auditLogDAL, permissionService, auditLogQueue });
@@ -1121,7 +1120,9 @@ export const registerRoutes = async (
resourceMetadataDAL, resourceMetadataDAL,
folderCommitService, folderCommitService,
secretSyncQueue, secretSyncQueue,
reminderService reminderService,
eventBusService,
licenseService
}); });
const projectService = projectServiceFactory({ const projectService = projectServiceFactory({

View File

@@ -5,8 +5,8 @@ import { z } from "zod";
import { ActionProjectType, ProjectType } from "@app/db/schemas"; import { ActionProjectType, ProjectType } from "@app/db/schemas";
import { getServerSentEventsHeaders } from "@app/ee/services/event/event-sse-stream"; import { getServerSentEventsHeaders } from "@app/ee/services/event/event-sse-stream";
import { EventRegisterSchema } from "@app/ee/services/event/types"; import { EventRegisterSchema, Mappings } from "@app/ee/services/event/types";
import { ProjectPermissionSecretActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { ApiDocsTags, EventSubscriptions } from "@app/lib/api-docs"; import { ApiDocsTags, EventSubscriptions } from "@app/lib/api-docs";
import { BadRequestError, ForbiddenRequestError, RateLimitError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, RateLimitError } from "@app/lib/errors";
import { readLimit } from "@app/server/config/rateLimiter"; import { readLimit } from "@app/server/config/rateLimiter";
@@ -82,21 +82,19 @@ export const registerEventRouter = async (server: FastifyZodProvider) => {
req.body.register.forEach((r) => { req.body.register.forEach((r) => {
const fields = { const fields = {
environment: r.conditions?.environmentSlug ?? "", environment: r.conditions?.environmentSlug ?? "",
secretPath: r.conditions?.secretPath ?? "/", secretPath: r.conditions?.secretPath ?? "/"
eventType: r.event
}; };
const allowed = info.permission.can( const action = Mappings.BusEventToAction(r.event);
ProjectPermissionSecretActions.Subscribe,
subject(ProjectPermissionSub.Secrets, fields) const allowed = info.permission.can(action, subject(ProjectPermissionSub.SecretEvents, fields));
);
if (!allowed) { if (!allowed) {
throw new ForbiddenRequestError({ throw new ForbiddenRequestError({
name: "PermissionDenied", name: "PermissionDenied",
message: `You are not allowed to subscribe on secrets`, message: `You are not allowed to subscribe on ${ProjectPermissionSub.SecretEvents}`,
details: { details: {
event: fields.eventType, action,
environmentSlug: fields.environment, environmentSlug: fields.environment,
secretPath: fields.secretPath secretPath: fields.secretPath
} }

View File

@@ -181,7 +181,13 @@ export const secretImportServiceFactory = ({
projectId, projectId,
environmentSlug: environment, environmentSlug: environment,
actorId, actorId,
actor actor,
event: {
importMutation: {
secretPath,
environment
}
}
}); });
} }
@@ -356,7 +362,13 @@ export const secretImportServiceFactory = ({
projectId, projectId,
environmentSlug: environment, environmentSlug: environment,
actor, actor,
actorId actorId,
event: {
importMutation: {
secretPath,
environment
}
}
}); });
await secretV2BridgeDAL.invalidateSecretCacheByProjectId(projectId); await secretV2BridgeDAL.invalidateSecretCacheByProjectId(projectId);

View File

@@ -386,7 +386,15 @@ export const secretV2BridgeServiceFactory = ({
actorId, actorId,
actor, actor,
projectId, projectId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug,
event: {
created: {
secretId: secret.id,
environment: folder.environment.slug,
secretKey: secret.key,
secretPath
}
}
}); });
} }
@@ -616,7 +624,15 @@ export const secretV2BridgeServiceFactory = ({
actor, actor,
projectId, projectId,
orgId: actorOrgId, orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug,
event: {
updated: {
secretId: secret.id,
environment: folder.environment.slug,
secretKey: secret.key,
secretPath
}
}
}); });
} }
@@ -728,7 +744,15 @@ export const secretV2BridgeServiceFactory = ({
actor, actor,
projectId, projectId,
orgId: actorOrgId, orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug,
event: {
deleted: {
secretId: secretToDelete.id,
environment: folder.environment.slug,
secretKey: secretToDelete.key,
secretPath
}
}
}); });
} }
@@ -1708,7 +1732,15 @@ export const secretV2BridgeServiceFactory = ({
secretPath, secretPath,
projectId, projectId,
orgId: actorOrgId, orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug,
event: {
created: newSecrets.map((el) => ({
secretId: el.id,
secretKey: el.key,
secretPath,
environment: folder.environment.slug
}))
}
}); });
return newSecrets.map((el) => { return newSecrets.map((el) => {
@@ -2075,7 +2107,15 @@ export const secretV2BridgeServiceFactory = ({
secretPath: el.path, secretPath: el.path,
projectId, projectId,
orgId: actorOrgId, orgId: actorOrgId,
environmentSlug: environment environmentSlug: environment,
event: {
updated: updatedSecrets.map((sec) => ({
secretId: sec.id,
secretKey: sec.key,
secretPath: sec.secretPath,
environment
}))
}
}) })
: undefined : undefined
) )
@@ -2214,7 +2254,15 @@ export const secretV2BridgeServiceFactory = ({
secretPath, secretPath,
projectId, projectId,
orgId: actorOrgId, orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug,
event: {
deleted: secretsDeleted.map((el) => ({
secretId: el.id,
secretKey: el.key,
secretPath,
environment: folder.environment.slug
}))
}
}); });
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
@@ -2751,7 +2799,13 @@ export const secretV2BridgeServiceFactory = ({
secretPath: destinationFolder.path, secretPath: destinationFolder.path,
environmentSlug: destinationFolder.environment.slug, environmentSlug: destinationFolder.environment.slug,
actorId, actorId,
actor actor,
event: {
importMutation: {
secretPath: sourceFolder.path,
environment: sourceFolder.environment.slug
}
}
}); });
} }
@@ -2763,7 +2817,13 @@ export const secretV2BridgeServiceFactory = ({
secretPath: sourceFolder.path, secretPath: sourceFolder.path,
environmentSlug: sourceFolder.environment.slug, environmentSlug: sourceFolder.environment.slug,
actorId, actorId,
actor actor,
event: {
importMutation: {
secretPath: sourceFolder.path,
environment: sourceFolder.environment.slug
}
}
}); });
} }

View File

@@ -5,6 +5,7 @@ import { Knex } from "knex";
import { import {
ProjectMembershipRole, ProjectMembershipRole,
ProjectType,
ProjectUpgradeStatus, ProjectUpgradeStatus,
ProjectVersion, ProjectVersion,
SecretType, SecretType,
@@ -12,6 +13,9 @@ import {
TSecretVersionsV2 TSecretVersionsV2
} from "@app/db/schemas"; } from "@app/db/schemas";
import { Actor, EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types"; import { Actor, EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
import { TEventBusService } from "@app/ee/services/event/event-bus-service";
import { BusEventName, PublishableEvent, TopicName } from "@app/ee/services/event/types";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal"; import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
import { TSecretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal"; import { TSecretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal";
import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal"; import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
@@ -111,6 +115,8 @@ type TSecretQueueFactoryDep = {
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">; folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
secretSyncQueue: Pick<TSecretSyncQueueFactory, "queueSecretSyncsSyncSecretsByPath">; secretSyncQueue: Pick<TSecretSyncQueueFactory, "queueSecretSyncsSyncSecretsByPath">;
reminderService: Pick<TReminderServiceFactory, "createReminderInternal" | "deleteReminderBySecretId">; reminderService: Pick<TReminderServiceFactory, "createReminderInternal" | "deleteReminderBySecretId">;
eventBusService: TEventBusService;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
}; };
export type TGetSecrets = { export type TGetSecrets = {
@@ -172,7 +178,9 @@ export const secretQueueFactory = ({
resourceMetadataDAL, resourceMetadataDAL,
secretSyncQueue, secretSyncQueue,
folderCommitService, folderCommitService,
reminderService reminderService,
eventBusService,
licenseService
}: TSecretQueueFactoryDep) => { }: TSecretQueueFactoryDep) => {
const integrationMeter = opentelemetry.metrics.getMeter("Integrations"); const integrationMeter = opentelemetry.metrics.getMeter("Integrations");
const errorHistogram = integrationMeter.createHistogram("integration_secret_sync_errors", { const errorHistogram = integrationMeter.createHistogram("integration_secret_sync_errors", {
@@ -534,17 +542,70 @@ export const secretQueueFactory = ({
}); });
}; };
const publishEvents = async (event: PublishableEvent) => {
if (event.created) {
await eventBusService.publish(TopicName.CoreServers, {
type: ProjectType.SecretManager,
source: "infiscal",
data: {
event: BusEventName.CreateSecret,
payload: event.created
}
});
}
if (event.updated) {
await eventBusService.publish(TopicName.CoreServers, {
type: ProjectType.SecretManager,
source: "infiscal",
data: {
event: BusEventName.UpdateSecret,
payload: event.updated
}
});
}
if (event.deleted) {
await eventBusService.publish(TopicName.CoreServers, {
type: ProjectType.SecretManager,
source: "infiscal",
data: {
event: BusEventName.DeleteSecret,
payload: event.deleted
}
});
}
if (event.importMutation) {
await eventBusService.publish(TopicName.CoreServers, {
type: ProjectType.SecretManager,
source: "infiscal",
data: {
event: BusEventName.ImportMutation,
payload: event.importMutation
}
});
}
};
const syncSecrets = async <T extends boolean = false>({ const syncSecrets = async <T extends boolean = false>({
// seperate de-dupe queue for integration sync and replication sync // seperate de-dupe queue for integration sync and replication sync
_deDupeQueue: deDupeQueue = {}, _deDupeQueue: deDupeQueue = {},
_depth: depth = 0, _depth: depth = 0,
_deDupeReplicationQueue: deDupeReplicationQueue = {}, _deDupeReplicationQueue: deDupeReplicationQueue = {},
event,
...dto ...dto
}: TSyncSecretsDTO<T>) => { }: TSyncSecretsDTO<T> & { event?: PublishableEvent }) => {
logger.info( logger.info(
`syncSecrets: syncing project secrets where [projectId=${dto.projectId}] [environment=${dto.environmentSlug}] [path=${dto.secretPath}]` `syncSecrets: syncing project secrets where [projectId=${dto.projectId}] [environment=${dto.environmentSlug}] [path=${dto.secretPath}]`
); );
const plan = await licenseService.getPlan(dto.orgId);
if (event && plan.eventSubscriptions) {
await publishEvents(event);
}
const deDuplicationKey = uniqueSecretQueueKey(dto.environmentSlug, dto.secretPath); const deDuplicationKey = uniqueSecretQueueKey(dto.environmentSlug, dto.secretPath);
if ( if (
!dto.excludeReplication !dto.excludeReplication
@@ -565,7 +626,7 @@ export const secretQueueFactory = ({
_deDupeQueue: deDupeQueue, _deDupeQueue: deDupeQueue,
_deDupeReplicationQueue: deDupeReplicationQueue, _deDupeReplicationQueue: deDupeReplicationQueue,
_depth: depth _depth: depth
} as TSyncSecretsDTO, } as unknown as TSyncSecretsDTO,
{ {
removeOnFail: true, removeOnFail: true,
removeOnComplete: true, removeOnComplete: true,
@@ -689,6 +750,7 @@ export const secretQueueFactory = ({
isManual, isManual,
projectId, projectId,
secretPath, secretPath,
depth = 1, depth = 1,
deDupeQueue = {} deDupeQueue = {}
} = job.data as TIntegrationSyncPayload; } = job.data as TIntegrationSyncPayload;
@@ -738,7 +800,13 @@ export const secretQueueFactory = ({
environmentSlug: foldersGroupedById[folderId][0]?.environmentSlug as string, environmentSlug: foldersGroupedById[folderId][0]?.environmentSlug as string,
_deDupeQueue: deDupeQueue, _deDupeQueue: deDupeQueue,
_depth: depth + 1, _depth: depth + 1,
excludeReplication: true excludeReplication: true,
event: {
importMutation: {
secretPath: foldersGroupedById[folderId][0]?.path as string,
environment: foldersGroupedById[folderId][0]?.environmentSlug as string
}
}
}) })
) )
); );
@@ -791,7 +859,13 @@ export const secretQueueFactory = ({
environmentSlug: referencedFoldersGroupedById[folderId][0]?.environmentSlug as string, environmentSlug: referencedFoldersGroupedById[folderId][0]?.environmentSlug as string,
_deDupeQueue: deDupeQueue, _deDupeQueue: deDupeQueue,
_depth: depth + 1, _depth: depth + 1,
excludeReplication: true excludeReplication: true,
event: {
importMutation: {
secretPath: referencedFoldersGroupedById[folderId][0]?.path as string,
environment: referencedFoldersGroupedById[folderId][0]?.environmentSlug as string
}
}
}) })
) )
); );

View File

@@ -142,12 +142,12 @@ Below is a comprehensive list of all available project-level subjects and their
Supports conditions and permission inversion Supports conditions and permission inversion
| Action | Description | Notes | | Action | Description | Notes |
| -------- | ------------------------------- | ----- | | -------- | ------------------------------- | ----- |
| `read` | View secrets and their values | This action is the equivalent of granting both `describeSecret` and `readValue`. The `read` action is considered **legacy**. You should use the `describeSecret` and/or `readValue` actions instead. | | `read` | View secrets and their values | This action is the equivalent of granting both `describeSecret` and `readValue`. The `read` action is considered **legacy**. You should use the `describeSecret` and/or `readValue` actions instead. |
| `describeSecret` | View secret details such as key, path, metadata, tags, and more | If you are using the API, you can pass `viewSecretValue: false` to the API call to retrieve secrets without their values. | | `describeSecret` | View secret details such as key, path, metadata, tags, and more | If you are using the API, you can pass `viewSecretValue: false` to the API call to retrieve secrets without their values. |
| `readValue` | View the value of a secret.| In order to read secret values, the `describeSecret` action must also be granted. | | `readValue` | View the value of a secret.| In order to read secret values, the `describeSecret` action must also be granted. |
| `create` | Add new secrets to the project | | | `create` | Add new secrets to the project | |
| `edit` | Modify existing secret values | | | `edit` | Modify existing secret values | |
| `delete` | Remove secrets from the project | | | `delete` | Remove secrets from the project | |
#### Subject: `secret-folders` #### Subject: `secret-folders`
@@ -169,6 +169,15 @@ Supports conditions and permission inversion
| `edit` | Modify secret imports | | `edit` | Modify secret imports |
| `delete` | Remove secret imports | | `delete` | Remove secret imports |
#### Subject: `secret-events`
| Action | Description |
| ------------------------------- | ------------------------------------------------------------- |
| `subscribe-on-created` | Subscribe to events when secrets are created |
| `subscribe-on-updated` | Subscribe to events when secrets are updated |
| `subscribe-on-deleted` | Subscribe to events when secrets are deleted |
| `subscribe-on-import-mutations` | Subscribe to events when secrets are modified through imports |
#### Subject: `secret-rollback` #### Subject: `secret-rollback`
| Action | Description | | Action | Description |
@@ -178,10 +187,10 @@ Supports conditions and permission inversion
#### Subject: `commits` #### Subject: `commits`
| Action | Description | | Action | Description |
| -------- | ---------------------------------- | | ------------------ | --------------------------------------------------------------- |
| `read` | View commits and changes across folders | | `read` | View commits and changes across folders |
| `perform-rollback` | Roll back commits changes and restore folders to previous state| | `perform-rollback` | Roll back commits changes and restore folders to previous state |
#### Subject: `secret-approval` #### Subject: `secret-approval`
@@ -197,14 +206,14 @@ Supports conditions and permission inversion
#### Subject: `secret-rotation` #### Subject: `secret-rotation`
Supports conditions and permission inversion Supports conditions and permission inversion
| Action | Description | | Action | Description |
| ------------------------------ | ---------------------------------------------- | | ------------------------------ | ---------------------------------------------- |
| `read` | View secret rotation configurations | | `read` | View secret rotation configurations |
| `read-generated-credentials` | View the generated credentials of a rotation | | `read-generated-credentials` | View the generated credentials of a rotation |
| `create` | Set up secret rotation configurations | | `create` | Set up secret rotation configurations |
| `edit` | Modify secret rotation configurations | | `edit` | Modify secret rotation configurations |
| `rotate-secrets` | Rotate the generated credentials of a rotation | | `rotate-secrets` | Rotate the generated credentials of a rotation |
| `delete` | Remove secret rotation configurations | | `delete` | Remove secret rotation configurations |
#### Subject: `secret-syncs` #### Subject: `secret-syncs`
@@ -263,12 +272,12 @@ Supports conditions and permission inversion
#### Subject: `certificates` #### Subject: `certificates`
| Action | Description | | Action | Description |
| -------------------- | ----------------------------- | | ------------------ | ----------------------------- |
| `read` | View certificates | | `read` | View certificates |
| `read-private-key` | Read certificate private key | | `read-private-key` | Read certificate private key |
| `create` | Issue new certificates | | `create` | Issue new certificates |
| `delete` | Revoke or remove certificates | | `delete` | Revoke or remove certificates |
#### Subject: `certificate-templates` #### Subject: `certificate-templates`
@@ -330,8 +339,8 @@ Supports conditions and permission inversion
#### Subject: `secret-scanning-data-sources` #### Subject: `secret-scanning-data-sources`
| Action | Description | | Action | Description |
| -------- | ---------------------------------------------------- | | ---------------------------- | -------------------------------- |
| `read-data-sources` | View Data Sources | | `read-data-sources` | View Data Sources |
| `create-data-sources` | Create new Data Sources | | `create-data-sources` | Create new Data Sources |
| `edit-data-sources` | Modify Data Sources | | `edit-data-sources` | Modify Data Sources |
@@ -342,15 +351,14 @@ Supports conditions and permission inversion
#### Subject: `secret-scanning-findings` #### Subject: `secret-scanning-findings`
| Action | Description | | Action | Description |
| -------- | --------------------------------- | | ----------------- | ------------------------------- |
| `read-findings` | View Secret Scanning Findings | | `read-findings` | View Secret Scanning Findings |
| `update-findings` | Update Secret Scanning Findings | | `update-findings` | Update Secret Scanning Findings |
#### Subject: `secret-scanning-configs` #### Subject: `secret-scanning-configs`
| Action | Description | | Action | Description |
| ---------------- | ------------------------------------------------ | | ---------------- | -------------------------------------------- |
| `read-configs` | View Secret Scanning Project Configuration | | `read-configs` | View Secret Scanning Project Configuration |
| `update-configs` | Update Secret Scanning Project Configuration | | `update-configs` | Update Secret Scanning Project Configuration |

View File

@@ -143,6 +143,13 @@ export enum ProjectPermissionSecretScanningConfigActions {
Update = "update-configs" Update = "update-configs"
} }
export enum ProjectPermissionSecretEventActions {
SubscribeCreated = "subscribe-on-created",
SubscribeUpdated = "subscribe-on-updated",
SubscribeDeleted = "subscribe-on-deleted",
SubscribeImportMutations = "subscribe-on-import-mutations"
}
export enum PermissionConditionOperators { export enum PermissionConditionOperators {
$IN = "$in", $IN = "$in",
$ALL = "$all", $ALL = "$all",
@@ -172,7 +179,8 @@ export type ConditionalProjectPermissionSubject =
| ProjectPermissionSub.CertificateTemplates | ProjectPermissionSub.CertificateTemplates
| ProjectPermissionSub.SecretFolders | ProjectPermissionSub.SecretFolders
| ProjectPermissionSub.SecretImports | ProjectPermissionSub.SecretImports
| ProjectPermissionSub.SecretRotation; | ProjectPermissionSub.SecretRotation
| ProjectPermissionSub.SecretEvents;
export const formatedConditionsOperatorNames: { [K in PermissionConditionOperators]: string } = { export const formatedConditionsOperatorNames: { [K in PermissionConditionOperators]: string } = {
[PermissionConditionOperators.$EQ]: "equal to", [PermissionConditionOperators.$EQ]: "equal to",
@@ -250,7 +258,8 @@ export enum ProjectPermissionSub {
Commits = "commits", Commits = "commits",
SecretScanningDataSources = "secret-scanning-data-sources", SecretScanningDataSources = "secret-scanning-data-sources",
SecretScanningFindings = "secret-scanning-findings", SecretScanningFindings = "secret-scanning-findings",
SecretScanningConfigs = "secret-scanning-configs" SecretScanningConfigs = "secret-scanning-configs",
SecretEvents = "secret-events"
} }
export type SecretSubjectFields = { export type SecretSubjectFields = {
@@ -260,6 +269,14 @@ export type SecretSubjectFields = {
secretTags: string[]; secretTags: string[];
}; };
export type SecretEventSubjectFields = {
environment: string;
secretPath: string;
secretName: string;
secretTags: string[];
action: string;
};
export type SecretFolderSubjectFields = { export type SecretFolderSubjectFields = {
environment: string; environment: string;
secretPath: string; secretPath: string;
@@ -403,6 +420,13 @@ export type ProjectPermissionSet =
ProjectPermissionSub.SecretScanningDataSources ProjectPermissionSub.SecretScanningDataSources
] ]
| [ProjectPermissionSecretScanningFindingActions, ProjectPermissionSub.SecretScanningFindings] | [ProjectPermissionSecretScanningFindingActions, ProjectPermissionSub.SecretScanningFindings]
| [ProjectPermissionSecretScanningConfigActions, ProjectPermissionSub.SecretScanningConfigs]; | [ProjectPermissionSecretScanningConfigActions, ProjectPermissionSub.SecretScanningConfigs]
| [
ProjectPermissionSecretEventActions,
(
| ProjectPermissionSub.SecretEvents
| (ForcedSubject<ProjectPermissionSub.SecretEvents> & SecretEventSubjectFields)
)
];
export type TProjectPermission = MongoAbility<ProjectPermissionSet>; export type TProjectPermission = MongoAbility<ProjectPermissionSet>;

View File

@@ -21,6 +21,7 @@ import {
ProjectPermissionPkiSubscriberActions, ProjectPermissionPkiSubscriberActions,
ProjectPermissionPkiTemplateActions, ProjectPermissionPkiTemplateActions,
ProjectPermissionSecretActions, ProjectPermissionSecretActions,
ProjectPermissionSecretEventActions,
ProjectPermissionSecretRotationActions, ProjectPermissionSecretRotationActions,
ProjectPermissionSecretScanningConfigActions, ProjectPermissionSecretScanningConfigActions,
ProjectPermissionSecretScanningDataSourceActions, ProjectPermissionSecretScanningDataSourceActions,
@@ -188,6 +189,13 @@ const PkiTemplatePolicyActionSchema = z.object({
[ProjectPermissionPkiTemplateActions.ListCerts]: z.boolean().optional() [ProjectPermissionPkiTemplateActions.ListCerts]: z.boolean().optional()
}); });
const SecretEventsPolicyActionSchema = z.object({
[ProjectPermissionSecretEventActions.SubscribeCreated]: z.boolean().optional(),
[ProjectPermissionSecretEventActions.SubscribeUpdated]: z.boolean().optional(),
[ProjectPermissionSecretEventActions.SubscribeDeleted]: z.boolean().optional(),
[ProjectPermissionSecretEventActions.SubscribeImportMutations]: z.boolean().optional()
});
const SecretRollbackPolicyActionSchema = z.object({ const SecretRollbackPolicyActionSchema = z.object({
read: z.boolean().optional(), read: z.boolean().optional(),
create: z.boolean().optional() create: z.boolean().optional()
@@ -356,7 +364,12 @@ export const projectRoleFormSchema = z.object({
[ProjectPermissionSub.SecretScanningFindings]: [ProjectPermissionSub.SecretScanningFindings]:
SecretScanningFindingPolicyActionSchema.array().default([]), SecretScanningFindingPolicyActionSchema.array().default([]),
[ProjectPermissionSub.SecretScanningConfigs]: [ProjectPermissionSub.SecretScanningConfigs]:
SecretScanningConfigPolicyActionSchema.array().default([]) SecretScanningConfigPolicyActionSchema.array().default([]),
[ProjectPermissionSub.SecretEvents]: SecretEventsPolicyActionSchema.extend({
conditions: ConditionSchema
})
.array()
.default([])
}) })
.partial() .partial()
.optional() .optional()
@@ -374,7 +387,8 @@ type TConditionalFields =
| ProjectPermissionSub.SshHosts | ProjectPermissionSub.SshHosts
| ProjectPermissionSub.SecretRotation | ProjectPermissionSub.SecretRotation
| ProjectPermissionSub.Identity | ProjectPermissionSub.Identity
| ProjectPermissionSub.SecretSyncs; | ProjectPermissionSub.SecretSyncs
| ProjectPermissionSub.SecretEvents;
export const isConditionalSubjects = ( export const isConditionalSubjects = (
subject: ProjectPermissionSub subject: ProjectPermissionSub
@@ -388,7 +402,8 @@ export const isConditionalSubjects = (
subject === ProjectPermissionSub.SecretRotation || subject === ProjectPermissionSub.SecretRotation ||
subject === ProjectPermissionSub.PkiSubscribers || subject === ProjectPermissionSub.PkiSubscribers ||
subject === ProjectPermissionSub.CertificateTemplates || subject === ProjectPermissionSub.CertificateTemplates ||
subject === ProjectPermissionSub.SecretSyncs; subject === ProjectPermissionSub.SecretSyncs ||
subject === ProjectPermissionSub.SecretEvents;
const convertCaslConditionToFormOperator = (caslConditions: TPermissionCondition) => { const convertCaslConditionToFormOperator = (caslConditions: TPermissionCondition) => {
const formConditions: z.infer<typeof ConditionSchema> = []; const formConditions: z.infer<typeof ConditionSchema> = [];
@@ -494,7 +509,8 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
ProjectPermissionSub.SshCertificateAuthorities, ProjectPermissionSub.SshCertificateAuthorities,
ProjectPermissionSub.SshCertificates, ProjectPermissionSub.SshCertificates,
ProjectPermissionSub.SshHostGroups, ProjectPermissionSub.SshHostGroups,
ProjectPermissionSub.SecretSyncs ProjectPermissionSub.SecretSyncs,
ProjectPermissionSub.SecretEvents
].includes(subject) ].includes(subject)
) { ) {
// from above statement we are sure it won't be undefined // from above statement we are sure it won't be undefined
@@ -607,6 +623,32 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
return; return;
} }
if (subject === ProjectPermissionSub.SecretEvents) {
const canSubscribeCreate = action.includes(
ProjectPermissionSecretEventActions.SubscribeCreated
);
const canSubscribeUpdate = action.includes(
ProjectPermissionSecretEventActions.SubscribeUpdated
);
const canSubscribeDelete = action.includes(
ProjectPermissionSecretEventActions.SubscribeDeleted
);
const canSubscribeImportMutations = action.includes(
ProjectPermissionSecretEventActions.SubscribeImportMutations
);
// from above statement we are sure it won't be undefined
formVal[subject]!.push({
"subscribe-on-created": canSubscribeCreate,
"subscribe-on-deleted": canSubscribeDelete,
"subscribe-on-updated": canSubscribeUpdate,
"subscribe-on-import-mutations": canSubscribeImportMutations,
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : []
});
return;
}
// for other subjects // for other subjects
const canRead = action.includes(ProjectPermissionActions.Read); const canRead = action.includes(ProjectPermissionActions.Read);
const canEdit = action.includes(ProjectPermissionActions.Edit); const canEdit = action.includes(ProjectPermissionActions.Edit);
@@ -1114,8 +1156,7 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
{ label: "Read Value", value: ProjectPermissionSecretActions.ReadValue }, { label: "Read Value", value: ProjectPermissionSecretActions.ReadValue },
{ label: "Modify", value: ProjectPermissionSecretActions.Edit }, { label: "Modify", value: ProjectPermissionSecretActions.Edit },
{ label: "Remove", value: ProjectPermissionSecretActions.Delete }, { label: "Remove", value: ProjectPermissionSecretActions.Delete },
{ label: "Create", value: ProjectPermissionSecretActions.Create }, { label: "Create", value: ProjectPermissionSecretActions.Create }
{ label: "Subscribe", value: ProjectPermissionSecretActions.Subscribe }
] ]
}, },
[ProjectPermissionSub.SecretFolders]: { [ProjectPermissionSub.SecretFolders]: {
@@ -1535,6 +1576,27 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
value: ProjectPermissionSecretScanningConfigActions.Update value: ProjectPermissionSecretScanningConfigActions.Update
} }
] ]
},
[ProjectPermissionSub.SecretEvents]: {
title: "Secret Events",
actions: [
{
label: "Subscribe on Created",
value: ProjectPermissionSecretEventActions.SubscribeCreated
},
{
label: "Subscribe on Deleted",
value: ProjectPermissionSecretEventActions.SubscribeDeleted
},
{
label: "Subscribe on Updated",
value: ProjectPermissionSecretEventActions.SubscribeUpdated
},
{
label: "Subscribe on Import Mutations",
value: ProjectPermissionSecretEventActions.SubscribeImportMutations
}
]
} }
}; };
@@ -1564,7 +1626,8 @@ const SecretsManagerPermissionSubjects = (enabled = false) => ({
[ProjectPermissionSub.SecretRollback]: enabled, [ProjectPermissionSub.SecretRollback]: enabled,
[ProjectPermissionSub.SecretRotation]: enabled, [ProjectPermissionSub.SecretRotation]: enabled,
[ProjectPermissionSub.ServiceTokens]: enabled, [ProjectPermissionSub.ServiceTokens]: enabled,
[ProjectPermissionSub.Commits]: enabled [ProjectPermissionSub.Commits]: enabled,
[ProjectPermissionSub.SecretEvents]: enabled
}); });
const KmsPermissionSubjects = (enabled = false) => ({ const KmsPermissionSubjects = (enabled = false) => ({

View File

@@ -32,6 +32,7 @@ import {
rolePermission2Form, rolePermission2Form,
TFormSchema TFormSchema
} from "./ProjectRoleModifySection.utils"; } from "./ProjectRoleModifySection.utils";
import { SecretEventPermissionConditions } from "./SecretEventPermissionConditions";
import { SecretPermissionConditions } from "./SecretPermissionConditions"; import { SecretPermissionConditions } from "./SecretPermissionConditions";
import { SecretSyncPermissionConditions } from "./SecretSyncPermissionConditions"; import { SecretSyncPermissionConditions } from "./SecretSyncPermissionConditions";
import { SshHostPermissionConditions } from "./SshHostPermissionConditions"; import { SshHostPermissionConditions } from "./SshHostPermissionConditions";
@@ -72,6 +73,10 @@ export const renderConditionalComponents = (
return <SecretSyncPermissionConditions isDisabled={isDisabled} />; return <SecretSyncPermissionConditions isDisabled={isDisabled} />;
} }
if (subject === ProjectPermissionSub.SecretEvents) {
return <SecretEventPermissionConditions isDisabled={isDisabled} />;
}
return <GeneralPermissionConditions isDisabled={isDisabled} type={subject} />; return <GeneralPermissionConditions isDisabled={isDisabled} type={subject} />;
} }

View File

@@ -0,0 +1,22 @@
import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types";
import { ConditionsFields } from "./ConditionsFields";
type Props = {
position?: number;
isDisabled?: boolean;
};
export const SecretEventPermissionConditions = ({ position = 0, isDisabled }: Props) => {
return (
<ConditionsFields
isDisabled={isDisabled}
subject={ProjectPermissionSub.SecretEvents}
position={position}
selectOptions={[
{ value: "environment", label: "Environment Slug" },
{ value: "secretPath", label: "Secret Path" }
]}
/>
);
};

View File

@@ -17,8 +17,7 @@ export const SecretPermissionConditions = ({ position = 0, isDisabled }: Props)
{ value: "environment", label: "Environment Slug" }, { value: "environment", label: "Environment Slug" },
{ value: "secretPath", label: "Secret Path" }, { value: "secretPath", label: "Secret Path" },
{ value: "secretName", label: "Secret Name" }, { value: "secretName", label: "Secret Name" },
{ value: "secretTags", label: "Secret Tags" }, { value: "secretTags", label: "Secret Tags" }
{ value: "eventType", label: "Event Type" }
]} ]}
/> />
); );