mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
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:
@@ -1,8 +1,6 @@
|
||||
import { AxiosError, RawAxiosRequestHeaders } from "axios";
|
||||
|
||||
import { ProjectType, 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 { SecretKeyEncoding } from "@app/db/schemas";
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { logger } from "@app/lib/logger";
|
||||
@@ -22,7 +20,6 @@ type TAuditLogQueueServiceFactoryDep = {
|
||||
queueService: TQueueServiceFactory;
|
||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
eventBusService: TEventBusService;
|
||||
};
|
||||
|
||||
export type TAuditLogQueueServiceFactory = {
|
||||
@@ -38,8 +35,7 @@ export const auditLogQueueServiceFactory = async ({
|
||||
queueService,
|
||||
projectDAL,
|
||||
licenseService,
|
||||
auditLogStreamDAL,
|
||||
eventBusService
|
||||
auditLogStreamDAL
|
||||
}: TAuditLogQueueServiceFactoryDep): Promise<TAuditLogQueueServiceFactory> => {
|
||||
const pushToLog = async (data: TCreateAuditLogDTO) => {
|
||||
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 {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { z } from "zod";
|
||||
|
||||
import { logger } from "@app/lib/logger";
|
||||
|
||||
import { EventSchema, TopicName } from "./types";
|
||||
import { BusEventSchema, TopicName } from "./types";
|
||||
|
||||
export const eventBusFactory = (redis: Redis) => {
|
||||
const publisher = redis.duplicate();
|
||||
@@ -28,7 +28,7 @@ export const eventBusFactory = (redis: Redis) => {
|
||||
* @param topic - The topic to publish the event to.
|
||||
* @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);
|
||||
|
||||
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.
|
||||
* @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.
|
||||
const listener = (channel: string, message: string) => {
|
||||
try {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { logger } from "@app/lib/logger";
|
||||
|
||||
import { TEventBusService } from "./event-bus-service";
|
||||
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 HEART_BEAT_INTERVAL = 15 * 1000;
|
||||
@@ -69,8 +69,8 @@ export const sseServiceFactory = (bus: TEventBusService, redis: Redis) => {
|
||||
}
|
||||
};
|
||||
|
||||
function filterEventsForClient(client: EventStreamClient, event: EventData, registered: RegisteredEvent[]) {
|
||||
const eventType = toBusEventName(event.data.eventType);
|
||||
function filterEventsForClient(client: EventStreamClient, event: BusEvent, registered: RegisteredEvent[]) {
|
||||
const eventType = event.data.event;
|
||||
const match = registered.find((r) => r.event === eventType);
|
||||
if (!match) return;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { KeyStorePrefixes } from "@app/keystore/keystore";
|
||||
import { conditionsMatcher } from "@app/lib/casl";
|
||||
import { logger } from "@app/lib/logger";
|
||||
|
||||
import { EventData, RegisteredEvent } from "./types";
|
||||
import { BusEvent, RegisteredEvent } from "./types";
|
||||
|
||||
export const getServerSentEventsHeaders = () =>
|
||||
({
|
||||
@@ -55,7 +55,7 @@ export type EventStreamClient = {
|
||||
id: string;
|
||||
stream: Readable;
|
||||
open: () => Promise<void>;
|
||||
send: (data: EventMessage | EventData) => void;
|
||||
send: (data: EventMessage | BusEvent) => void;
|
||||
ping: () => Promise<void>;
|
||||
refresh: () => Promise<void>;
|
||||
close: () => void;
|
||||
@@ -73,15 +73,12 @@ export function createEventStreamClient(redis: Redis, options: IEventStreamClien
|
||||
return {
|
||||
subject: options.type,
|
||||
action: "subscribe",
|
||||
conditions: {
|
||||
eventType: r.event,
|
||||
...(hasConditions
|
||||
? {
|
||||
environment: r.conditions?.environmentSlug ?? "",
|
||||
secretPath: { $glob: secretPath }
|
||||
}
|
||||
: {})
|
||||
}
|
||||
conditions: hasConditions
|
||||
? {
|
||||
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
|
||||
stream._read = () => {};
|
||||
|
||||
const send = (data: EventMessage | EventData) => {
|
||||
const send = (data: EventMessage | BusEvent) => {
|
||||
const chunk = serializeSseEvent(data);
|
||||
if (!stream.push(chunk)) {
|
||||
logger.debug("Backpressure detected: dropped manual event");
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { z } from "zod";
|
||||
|
||||
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 {
|
||||
CoreServers = "infisical::core-servers"
|
||||
@@ -10,84 +11,44 @@ export enum TopicName {
|
||||
export enum BusEventName {
|
||||
CreateSecret = "secret:create",
|
||||
UpdateSecret = "secret:update",
|
||||
DeleteSecret = "secret:delete"
|
||||
DeleteSecret = "secret:delete",
|
||||
ImportMutation = "secret:import-mutation"
|
||||
}
|
||||
|
||||
type PublisableEventTypes =
|
||||
| EventType.CREATE_SECRET
|
||||
| EventType.CREATE_SECRETS
|
||||
| EventType.DELETE_SECRET
|
||||
| EventType.DELETE_SECRETS
|
||||
| EventType.UPDATE_SECRETS
|
||||
| EventType.UPDATE_SECRET;
|
||||
|
||||
export function toBusEventName(input: EventType) {
|
||||
switch (input) {
|
||||
case EventType.CREATE_SECRET:
|
||||
case EventType.CREATE_SECRETS:
|
||||
return BusEventName.CreateSecret;
|
||||
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
|
||||
}
|
||||
export const Mappings = {
|
||||
BusEventToAction(input: BusEventName) {
|
||||
switch (input) {
|
||||
case BusEventName.CreateSecret:
|
||||
return ProjectPermissionSecretEventActions.SubscribeCreated;
|
||||
case BusEventName.DeleteSecret:
|
||||
return ProjectPermissionSecretEventActions.SubscribeDeleted;
|
||||
case BusEventName.ImportMutation:
|
||||
return ProjectPermissionSecretEventActions.SubscribeImportMutations;
|
||||
case BusEventName.UpdateSecret:
|
||||
return ProjectPermissionSecretEventActions.SubscribeUpdated;
|
||||
default:
|
||||
throw new Error("Unknown bus event name");
|
||||
}
|
||||
} as const;
|
||||
}
|
||||
};
|
||||
|
||||
export const EventName = z.nativeEnum(BusEventName);
|
||||
|
||||
const EventSecretPayload = z.object({
|
||||
secretPath: z.string().optional(),
|
||||
secretId: z.string(),
|
||||
secretPath: z.string().optional(),
|
||||
secretKey: z.string(),
|
||||
environment: z.string()
|
||||
});
|
||||
|
||||
const EventImportMutationPayload = z.object({
|
||||
secretPath: z.string(),
|
||||
environment: z.string()
|
||||
});
|
||||
|
||||
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"),
|
||||
type: z.nativeEnum(ProjectType),
|
||||
source: z.string(),
|
||||
@@ -95,25 +56,38 @@ export const EventSchema = z.object({
|
||||
.string()
|
||||
.optional()
|
||||
.default(() => new Date().toISOString()),
|
||||
data: z.discriminatedUnion("eventType", [
|
||||
data: z.discriminatedUnion("event", [
|
||||
z.object({
|
||||
specversion: z.number().optional().default(1),
|
||||
eventType: z.enum([EventType.CREATE_SECRET, EventType.UPDATE_SECRET, EventType.DELETE_SECRET]),
|
||||
payload: EventSecretPayload
|
||||
event: z.enum([BusEventName.CreateSecret, BusEventName.DeleteSecret, BusEventName.UpdateSecret]),
|
||||
payload: z.union([EventSecretPayload, EventSecretPayload.array()])
|
||||
}),
|
||||
z.object({
|
||||
specversion: z.number().optional().default(1),
|
||||
eventType: z.enum([EventType.CREATE_SECRETS, EventType.UPDATE_SECRETS, EventType.DELETE_SECRETS]),
|
||||
payload: EventSecretPayload.array()
|
||||
event: z.enum([BusEventName.ImportMutation]),
|
||||
payload: z.union([EventImportMutationPayload, EventImportMutationPayload.array()])
|
||||
})
|
||||
// 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({
|
||||
event: EventName,
|
||||
event: z.nativeEnum(BusEventName),
|
||||
conditions: z
|
||||
.object({
|
||||
secretPath: z.string().optional().default("/"),
|
||||
|
||||
@@ -161,8 +161,7 @@ const buildAdminPermissionRules = () => {
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionSecretActions.Create,
|
||||
ProjectPermissionSecretActions.Edit,
|
||||
ProjectPermissionSecretActions.Delete,
|
||||
ProjectPermissionSecretActions.Subscribe
|
||||
ProjectPermissionSecretActions.Delete
|
||||
],
|
||||
ProjectPermissionSub.Secrets
|
||||
);
|
||||
@@ -266,8 +265,7 @@ const buildMemberPermissionRules = () => {
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
ProjectPermissionSecretActions.Edit,
|
||||
ProjectPermissionSecretActions.Create,
|
||||
ProjectPermissionSecretActions.Delete,
|
||||
ProjectPermissionSecretActions.Subscribe
|
||||
ProjectPermissionSecretActions.Delete
|
||||
],
|
||||
ProjectPermissionSub.Secrets
|
||||
);
|
||||
|
||||
@@ -36,8 +36,7 @@ export enum ProjectPermissionSecretActions {
|
||||
ReadValue = "readValue",
|
||||
Create = "create",
|
||||
Edit = "edit",
|
||||
Delete = "delete",
|
||||
Subscribe = "subscribe"
|
||||
Delete = "delete"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionCmekActions {
|
||||
@@ -158,6 +157,13 @@ export enum ProjectPermissionSecretScanningConfigActions {
|
||||
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 {
|
||||
Role = "role",
|
||||
Member = "member",
|
||||
@@ -197,7 +203,8 @@ export enum ProjectPermissionSub {
|
||||
Kmip = "kmip",
|
||||
SecretScanningDataSources = "secret-scanning-data-sources",
|
||||
SecretScanningFindings = "secret-scanning-findings",
|
||||
SecretScanningConfigs = "secret-scanning-configs"
|
||||
SecretScanningConfigs = "secret-scanning-configs",
|
||||
SecretEvents = "secret-events"
|
||||
}
|
||||
|
||||
export type SecretSubjectFields = {
|
||||
@@ -205,7 +212,13 @@ export type SecretSubjectFields = {
|
||||
secretPath: string;
|
||||
secretName?: string;
|
||||
secretTags?: string[];
|
||||
eventType?: string;
|
||||
};
|
||||
|
||||
export type SecretEventSubjectFields = {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
secretName?: string;
|
||||
secretTags?: string[];
|
||||
};
|
||||
|
||||
export type SecretFolderSubjectFields = {
|
||||
@@ -344,7 +357,11 @@ export type ProjectPermissionSet =
|
||||
| [ProjectPermissionCommitsActions, ProjectPermissionSub.Commits]
|
||||
| [ProjectPermissionSecretScanningDataSourceActions, ProjectPermissionSub.SecretScanningDataSources]
|
||||
| [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_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."
|
||||
).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
|
||||
]);
|
||||
|
||||
|
||||
@@ -952,13 +952,39 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
if (!folder) {
|
||||
throw new NotFoundError({ message: `Folder with ID '${folderId}' not found in project with ID '${projectId}'` });
|
||||
}
|
||||
|
||||
const { secrets } = mergeStatus;
|
||||
|
||||
await secretQueueService.syncSecrets({
|
||||
projectId,
|
||||
orgId: actorOrgId,
|
||||
secretPath: folder.path,
|
||||
environmentSlug: folder.environmentSlug,
|
||||
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) {
|
||||
|
||||
@@ -560,8 +560,7 @@ export const registerRoutes = async (
|
||||
queueService,
|
||||
projectDAL,
|
||||
licenseService,
|
||||
auditLogStreamDAL,
|
||||
eventBusService
|
||||
auditLogStreamDAL
|
||||
});
|
||||
|
||||
const auditLogService = auditLogServiceFactory({ auditLogDAL, permissionService, auditLogQueue });
|
||||
@@ -1121,7 +1120,9 @@ export const registerRoutes = async (
|
||||
resourceMetadataDAL,
|
||||
folderCommitService,
|
||||
secretSyncQueue,
|
||||
reminderService
|
||||
reminderService,
|
||||
eventBusService,
|
||||
licenseService
|
||||
});
|
||||
|
||||
const projectService = projectServiceFactory({
|
||||
|
||||
@@ -5,8 +5,8 @@ import { z } from "zod";
|
||||
|
||||
import { ActionProjectType, ProjectType } from "@app/db/schemas";
|
||||
import { getServerSentEventsHeaders } from "@app/ee/services/event/event-sse-stream";
|
||||
import { EventRegisterSchema } from "@app/ee/services/event/types";
|
||||
import { ProjectPermissionSecretActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { EventRegisterSchema, Mappings } from "@app/ee/services/event/types";
|
||||
import { ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { ApiDocsTags, EventSubscriptions } from "@app/lib/api-docs";
|
||||
import { BadRequestError, ForbiddenRequestError, RateLimitError } from "@app/lib/errors";
|
||||
import { readLimit } from "@app/server/config/rateLimiter";
|
||||
@@ -82,21 +82,19 @@ export const registerEventRouter = async (server: FastifyZodProvider) => {
|
||||
req.body.register.forEach((r) => {
|
||||
const fields = {
|
||||
environment: r.conditions?.environmentSlug ?? "",
|
||||
secretPath: r.conditions?.secretPath ?? "/",
|
||||
eventType: r.event
|
||||
secretPath: r.conditions?.secretPath ?? "/"
|
||||
};
|
||||
|
||||
const allowed = info.permission.can(
|
||||
ProjectPermissionSecretActions.Subscribe,
|
||||
subject(ProjectPermissionSub.Secrets, fields)
|
||||
);
|
||||
const action = Mappings.BusEventToAction(r.event);
|
||||
|
||||
const allowed = info.permission.can(action, subject(ProjectPermissionSub.SecretEvents, fields));
|
||||
|
||||
if (!allowed) {
|
||||
throw new ForbiddenRequestError({
|
||||
name: "PermissionDenied",
|
||||
message: `You are not allowed to subscribe on secrets`,
|
||||
message: `You are not allowed to subscribe on ${ProjectPermissionSub.SecretEvents}`,
|
||||
details: {
|
||||
event: fields.eventType,
|
||||
action,
|
||||
environmentSlug: fields.environment,
|
||||
secretPath: fields.secretPath
|
||||
}
|
||||
|
||||
@@ -181,7 +181,13 @@ export const secretImportServiceFactory = ({
|
||||
projectId,
|
||||
environmentSlug: environment,
|
||||
actorId,
|
||||
actor
|
||||
actor,
|
||||
event: {
|
||||
importMutation: {
|
||||
secretPath,
|
||||
environment
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -356,7 +362,13 @@ export const secretImportServiceFactory = ({
|
||||
projectId,
|
||||
environmentSlug: environment,
|
||||
actor,
|
||||
actorId
|
||||
actorId,
|
||||
event: {
|
||||
importMutation: {
|
||||
secretPath,
|
||||
environment
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await secretV2BridgeDAL.invalidateSecretCacheByProjectId(projectId);
|
||||
|
||||
@@ -386,7 +386,15 @@ export const secretV2BridgeServiceFactory = ({
|
||||
actorId,
|
||||
actor,
|
||||
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,
|
||||
projectId,
|
||||
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,
|
||||
projectId,
|
||||
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,
|
||||
projectId,
|
||||
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) => {
|
||||
@@ -2075,7 +2107,15 @@ export const secretV2BridgeServiceFactory = ({
|
||||
secretPath: el.path,
|
||||
projectId,
|
||||
orgId: actorOrgId,
|
||||
environmentSlug: environment
|
||||
environmentSlug: environment,
|
||||
event: {
|
||||
updated: updatedSecrets.map((sec) => ({
|
||||
secretId: sec.id,
|
||||
secretKey: sec.key,
|
||||
secretPath: sec.secretPath,
|
||||
environment
|
||||
}))
|
||||
}
|
||||
})
|
||||
: undefined
|
||||
)
|
||||
@@ -2214,7 +2254,15 @@ export const secretV2BridgeServiceFactory = ({
|
||||
secretPath,
|
||||
projectId,
|
||||
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({
|
||||
@@ -2751,7 +2799,13 @@ export const secretV2BridgeServiceFactory = ({
|
||||
secretPath: destinationFolder.path,
|
||||
environmentSlug: destinationFolder.environment.slug,
|
||||
actorId,
|
||||
actor
|
||||
actor,
|
||||
event: {
|
||||
importMutation: {
|
||||
secretPath: sourceFolder.path,
|
||||
environment: sourceFolder.environment.slug
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2763,7 +2817,13 @@ export const secretV2BridgeServiceFactory = ({
|
||||
secretPath: sourceFolder.path,
|
||||
environmentSlug: sourceFolder.environment.slug,
|
||||
actorId,
|
||||
actor
|
||||
actor,
|
||||
event: {
|
||||
importMutation: {
|
||||
secretPath: sourceFolder.path,
|
||||
environment: sourceFolder.environment.slug
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Knex } from "knex";
|
||||
|
||||
import {
|
||||
ProjectMembershipRole,
|
||||
ProjectType,
|
||||
ProjectUpgradeStatus,
|
||||
ProjectVersion,
|
||||
SecretType,
|
||||
@@ -12,6 +13,9 @@ import {
|
||||
TSecretVersionsV2
|
||||
} from "@app/db/schemas";
|
||||
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 { TSecretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal";
|
||||
import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
|
||||
@@ -111,6 +115,8 @@ type TSecretQueueFactoryDep = {
|
||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||
secretSyncQueue: Pick<TSecretSyncQueueFactory, "queueSecretSyncsSyncSecretsByPath">;
|
||||
reminderService: Pick<TReminderServiceFactory, "createReminderInternal" | "deleteReminderBySecretId">;
|
||||
eventBusService: TEventBusService;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
export type TGetSecrets = {
|
||||
@@ -172,7 +178,9 @@ export const secretQueueFactory = ({
|
||||
resourceMetadataDAL,
|
||||
secretSyncQueue,
|
||||
folderCommitService,
|
||||
reminderService
|
||||
reminderService,
|
||||
eventBusService,
|
||||
licenseService
|
||||
}: TSecretQueueFactoryDep) => {
|
||||
const integrationMeter = opentelemetry.metrics.getMeter("Integrations");
|
||||
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>({
|
||||
// seperate de-dupe queue for integration sync and replication sync
|
||||
_deDupeQueue: deDupeQueue = {},
|
||||
_depth: depth = 0,
|
||||
_deDupeReplicationQueue: deDupeReplicationQueue = {},
|
||||
event,
|
||||
...dto
|
||||
}: TSyncSecretsDTO<T>) => {
|
||||
}: TSyncSecretsDTO<T> & { event?: PublishableEvent }) => {
|
||||
logger.info(
|
||||
`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);
|
||||
if (
|
||||
!dto.excludeReplication
|
||||
@@ -565,7 +626,7 @@ export const secretQueueFactory = ({
|
||||
_deDupeQueue: deDupeQueue,
|
||||
_deDupeReplicationQueue: deDupeReplicationQueue,
|
||||
_depth: depth
|
||||
} as TSyncSecretsDTO,
|
||||
} as unknown as TSyncSecretsDTO,
|
||||
{
|
||||
removeOnFail: true,
|
||||
removeOnComplete: true,
|
||||
@@ -689,6 +750,7 @@ export const secretQueueFactory = ({
|
||||
isManual,
|
||||
projectId,
|
||||
secretPath,
|
||||
|
||||
depth = 1,
|
||||
deDupeQueue = {}
|
||||
} = job.data as TIntegrationSyncPayload;
|
||||
@@ -738,7 +800,13 @@ export const secretQueueFactory = ({
|
||||
environmentSlug: foldersGroupedById[folderId][0]?.environmentSlug as string,
|
||||
_deDupeQueue: deDupeQueue,
|
||||
_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,
|
||||
_deDupeQueue: deDupeQueue,
|
||||
_depth: depth + 1,
|
||||
excludeReplication: true
|
||||
excludeReplication: true,
|
||||
event: {
|
||||
importMutation: {
|
||||
secretPath: referencedFoldersGroupedById[folderId][0]?.path as string,
|
||||
environment: referencedFoldersGroupedById[folderId][0]?.environmentSlug as string
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
@@ -142,12 +142,12 @@ Below is a comprehensive list of all available project-level subjects and their
|
||||
Supports conditions and permission inversion
|
||||
| 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. |
|
||||
| `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 | |
|
||||
| `edit` | Modify existing secret values | |
|
||||
| `delete` | Remove secrets from the project | |
|
||||
| `create` | Add new secrets to the project | |
|
||||
| `edit` | Modify existing secret values | |
|
||||
| `delete` | Remove secrets from the project | |
|
||||
|
||||
#### Subject: `secret-folders`
|
||||
|
||||
@@ -169,6 +169,15 @@ Supports conditions and permission inversion
|
||||
| `edit` | Modify 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`
|
||||
|
||||
| Action | Description |
|
||||
@@ -178,10 +187,10 @@ Supports conditions and permission inversion
|
||||
|
||||
#### Subject: `commits`
|
||||
|
||||
| Action | Description |
|
||||
| -------- | ---------------------------------- |
|
||||
| `read` | View commits and changes across folders |
|
||||
| `perform-rollback` | Roll back commits changes and restore folders to previous state|
|
||||
| Action | Description |
|
||||
| ------------------ | --------------------------------------------------------------- |
|
||||
| `read` | View commits and changes across folders |
|
||||
| `perform-rollback` | Roll back commits changes and restore folders to previous state |
|
||||
|
||||
#### Subject: `secret-approval`
|
||||
|
||||
@@ -197,14 +206,14 @@ Supports conditions and permission inversion
|
||||
#### Subject: `secret-rotation`
|
||||
|
||||
Supports conditions and permission inversion
|
||||
| Action | Description |
|
||||
| Action | Description |
|
||||
| ------------------------------ | ---------------------------------------------- |
|
||||
| `read` | View secret rotation configurations |
|
||||
| `read-generated-credentials` | View the generated credentials of a rotation |
|
||||
| `create` | Set up secret rotation configurations |
|
||||
| `edit` | Modify secret rotation configurations |
|
||||
| `rotate-secrets` | Rotate the generated credentials of a rotation |
|
||||
| `delete` | Remove secret rotation configurations |
|
||||
| `read` | View secret rotation configurations |
|
||||
| `read-generated-credentials` | View the generated credentials of a rotation |
|
||||
| `create` | Set up secret rotation configurations |
|
||||
| `edit` | Modify secret rotation configurations |
|
||||
| `rotate-secrets` | Rotate the generated credentials of a rotation |
|
||||
| `delete` | Remove secret rotation configurations |
|
||||
|
||||
#### Subject: `secret-syncs`
|
||||
|
||||
@@ -263,12 +272,12 @@ Supports conditions and permission inversion
|
||||
|
||||
#### Subject: `certificates`
|
||||
|
||||
| Action | Description |
|
||||
| -------------------- | ----------------------------- |
|
||||
| `read` | View certificates |
|
||||
| `read-private-key` | Read certificate private key |
|
||||
| `create` | Issue new certificates |
|
||||
| `delete` | Revoke or remove certificates |
|
||||
| Action | Description |
|
||||
| ------------------ | ----------------------------- |
|
||||
| `read` | View certificates |
|
||||
| `read-private-key` | Read certificate private key |
|
||||
| `create` | Issue new certificates |
|
||||
| `delete` | Revoke or remove certificates |
|
||||
|
||||
#### Subject: `certificate-templates`
|
||||
|
||||
@@ -330,8 +339,8 @@ Supports conditions and permission inversion
|
||||
|
||||
#### Subject: `secret-scanning-data-sources`
|
||||
|
||||
| Action | Description |
|
||||
| -------- | ---------------------------------------------------- |
|
||||
| Action | Description |
|
||||
| ---------------------------- | -------------------------------- |
|
||||
| `read-data-sources` | View Data Sources |
|
||||
| `create-data-sources` | Create new Data Sources |
|
||||
| `edit-data-sources` | Modify Data Sources |
|
||||
@@ -342,15 +351,14 @@ Supports conditions and permission inversion
|
||||
|
||||
#### Subject: `secret-scanning-findings`
|
||||
|
||||
| Action | Description |
|
||||
| -------- | --------------------------------- |
|
||||
| `read-findings` | View Secret Scanning Findings |
|
||||
| `update-findings` | Update Secret Scanning Findings |
|
||||
|
||||
| Action | Description |
|
||||
| ----------------- | ------------------------------- |
|
||||
| `read-findings` | View Secret Scanning Findings |
|
||||
| `update-findings` | Update Secret Scanning Findings |
|
||||
|
||||
#### Subject: `secret-scanning-configs`
|
||||
|
||||
| Action | Description |
|
||||
| ---------------- | ------------------------------------------------ |
|
||||
| `read-configs` | View Secret Scanning Project Configuration |
|
||||
| `update-configs` | Update Secret Scanning Project Configuration |
|
||||
| Action | Description |
|
||||
| ---------------- | -------------------------------------------- |
|
||||
| `read-configs` | View Secret Scanning Project Configuration |
|
||||
| `update-configs` | Update Secret Scanning Project Configuration |
|
||||
|
||||
@@ -143,6 +143,13 @@ export enum ProjectPermissionSecretScanningConfigActions {
|
||||
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 {
|
||||
$IN = "$in",
|
||||
$ALL = "$all",
|
||||
@@ -172,7 +179,8 @@ export type ConditionalProjectPermissionSubject =
|
||||
| ProjectPermissionSub.CertificateTemplates
|
||||
| ProjectPermissionSub.SecretFolders
|
||||
| ProjectPermissionSub.SecretImports
|
||||
| ProjectPermissionSub.SecretRotation;
|
||||
| ProjectPermissionSub.SecretRotation
|
||||
| ProjectPermissionSub.SecretEvents;
|
||||
|
||||
export const formatedConditionsOperatorNames: { [K in PermissionConditionOperators]: string } = {
|
||||
[PermissionConditionOperators.$EQ]: "equal to",
|
||||
@@ -250,7 +258,8 @@ export enum ProjectPermissionSub {
|
||||
Commits = "commits",
|
||||
SecretScanningDataSources = "secret-scanning-data-sources",
|
||||
SecretScanningFindings = "secret-scanning-findings",
|
||||
SecretScanningConfigs = "secret-scanning-configs"
|
||||
SecretScanningConfigs = "secret-scanning-configs",
|
||||
SecretEvents = "secret-events"
|
||||
}
|
||||
|
||||
export type SecretSubjectFields = {
|
||||
@@ -260,6 +269,14 @@ export type SecretSubjectFields = {
|
||||
secretTags: string[];
|
||||
};
|
||||
|
||||
export type SecretEventSubjectFields = {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
secretName: string;
|
||||
secretTags: string[];
|
||||
action: string;
|
||||
};
|
||||
|
||||
export type SecretFolderSubjectFields = {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
@@ -403,6 +420,13 @@ export type ProjectPermissionSet =
|
||||
ProjectPermissionSub.SecretScanningDataSources
|
||||
]
|
||||
| [ProjectPermissionSecretScanningFindingActions, ProjectPermissionSub.SecretScanningFindings]
|
||||
| [ProjectPermissionSecretScanningConfigActions, ProjectPermissionSub.SecretScanningConfigs];
|
||||
| [ProjectPermissionSecretScanningConfigActions, ProjectPermissionSub.SecretScanningConfigs]
|
||||
| [
|
||||
ProjectPermissionSecretEventActions,
|
||||
(
|
||||
| ProjectPermissionSub.SecretEvents
|
||||
| (ForcedSubject<ProjectPermissionSub.SecretEvents> & SecretEventSubjectFields)
|
||||
)
|
||||
];
|
||||
|
||||
export type TProjectPermission = MongoAbility<ProjectPermissionSet>;
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
ProjectPermissionPkiSubscriberActions,
|
||||
ProjectPermissionPkiTemplateActions,
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionSecretEventActions,
|
||||
ProjectPermissionSecretRotationActions,
|
||||
ProjectPermissionSecretScanningConfigActions,
|
||||
ProjectPermissionSecretScanningDataSourceActions,
|
||||
@@ -188,6 +189,13 @@ const PkiTemplatePolicyActionSchema = z.object({
|
||||
[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({
|
||||
read: z.boolean().optional(),
|
||||
create: z.boolean().optional()
|
||||
@@ -356,7 +364,12 @@ export const projectRoleFormSchema = z.object({
|
||||
[ProjectPermissionSub.SecretScanningFindings]:
|
||||
SecretScanningFindingPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.SecretScanningConfigs]:
|
||||
SecretScanningConfigPolicyActionSchema.array().default([])
|
||||
SecretScanningConfigPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.SecretEvents]: SecretEventsPolicyActionSchema.extend({
|
||||
conditions: ConditionSchema
|
||||
})
|
||||
.array()
|
||||
.default([])
|
||||
})
|
||||
.partial()
|
||||
.optional()
|
||||
@@ -374,7 +387,8 @@ type TConditionalFields =
|
||||
| ProjectPermissionSub.SshHosts
|
||||
| ProjectPermissionSub.SecretRotation
|
||||
| ProjectPermissionSub.Identity
|
||||
| ProjectPermissionSub.SecretSyncs;
|
||||
| ProjectPermissionSub.SecretSyncs
|
||||
| ProjectPermissionSub.SecretEvents;
|
||||
|
||||
export const isConditionalSubjects = (
|
||||
subject: ProjectPermissionSub
|
||||
@@ -388,7 +402,8 @@ export const isConditionalSubjects = (
|
||||
subject === ProjectPermissionSub.SecretRotation ||
|
||||
subject === ProjectPermissionSub.PkiSubscribers ||
|
||||
subject === ProjectPermissionSub.CertificateTemplates ||
|
||||
subject === ProjectPermissionSub.SecretSyncs;
|
||||
subject === ProjectPermissionSub.SecretSyncs ||
|
||||
subject === ProjectPermissionSub.SecretEvents;
|
||||
|
||||
const convertCaslConditionToFormOperator = (caslConditions: TPermissionCondition) => {
|
||||
const formConditions: z.infer<typeof ConditionSchema> = [];
|
||||
@@ -494,7 +509,8 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
||||
ProjectPermissionSub.SshCertificateAuthorities,
|
||||
ProjectPermissionSub.SshCertificates,
|
||||
ProjectPermissionSub.SshHostGroups,
|
||||
ProjectPermissionSub.SecretSyncs
|
||||
ProjectPermissionSub.SecretSyncs,
|
||||
ProjectPermissionSub.SecretEvents
|
||||
].includes(subject)
|
||||
) {
|
||||
// from above statement we are sure it won't be undefined
|
||||
@@ -607,6 +623,32 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
||||
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
|
||||
const canRead = action.includes(ProjectPermissionActions.Read);
|
||||
const canEdit = action.includes(ProjectPermissionActions.Edit);
|
||||
@@ -1114,8 +1156,7 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
||||
{ label: "Read Value", value: ProjectPermissionSecretActions.ReadValue },
|
||||
{ label: "Modify", value: ProjectPermissionSecretActions.Edit },
|
||||
{ label: "Remove", value: ProjectPermissionSecretActions.Delete },
|
||||
{ label: "Create", value: ProjectPermissionSecretActions.Create },
|
||||
{ label: "Subscribe", value: ProjectPermissionSecretActions.Subscribe }
|
||||
{ label: "Create", value: ProjectPermissionSecretActions.Create }
|
||||
]
|
||||
},
|
||||
[ProjectPermissionSub.SecretFolders]: {
|
||||
@@ -1535,6 +1576,27 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
||||
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.SecretRotation]: enabled,
|
||||
[ProjectPermissionSub.ServiceTokens]: enabled,
|
||||
[ProjectPermissionSub.Commits]: enabled
|
||||
[ProjectPermissionSub.Commits]: enabled,
|
||||
[ProjectPermissionSub.SecretEvents]: enabled
|
||||
});
|
||||
|
||||
const KmsPermissionSubjects = (enabled = false) => ({
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
rolePermission2Form,
|
||||
TFormSchema
|
||||
} from "./ProjectRoleModifySection.utils";
|
||||
import { SecretEventPermissionConditions } from "./SecretEventPermissionConditions";
|
||||
import { SecretPermissionConditions } from "./SecretPermissionConditions";
|
||||
import { SecretSyncPermissionConditions } from "./SecretSyncPermissionConditions";
|
||||
import { SshHostPermissionConditions } from "./SshHostPermissionConditions";
|
||||
@@ -72,6 +73,10 @@ export const renderConditionalComponents = (
|
||||
return <SecretSyncPermissionConditions isDisabled={isDisabled} />;
|
||||
}
|
||||
|
||||
if (subject === ProjectPermissionSub.SecretEvents) {
|
||||
return <SecretEventPermissionConditions isDisabled={isDisabled} />;
|
||||
}
|
||||
|
||||
return <GeneralPermissionConditions isDisabled={isDisabled} type={subject} />;
|
||||
}
|
||||
|
||||
|
||||
@@ -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" }
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -17,8 +17,7 @@ export const SecretPermissionConditions = ({ position = 0, isDisabled }: Props)
|
||||
{ value: "environment", label: "Environment Slug" },
|
||||
{ value: "secretPath", label: "Secret Path" },
|
||||
{ value: "secretName", label: "Secret Name" },
|
||||
{ value: "secretTags", label: "Secret Tags" },
|
||||
{ value: "eventType", label: "Event Type" }
|
||||
{ value: "secretTags", label: "Secret Tags" }
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user