mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 07:27:58 +00:00
feat(rbac): added glob support in permission and revealed settings
This commit is contained in:
Generated
+1
@@ -19,6 +19,7 @@
|
||||
"@sentry/tracing": "^7.48.0",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/libsodium-wrappers": "^0.7.10",
|
||||
"@ucast/mongo2js": "^1.3.4",
|
||||
"argon2": "^0.30.3",
|
||||
"aws-sdk": "^2.1364.0",
|
||||
"axios": "^1.3.5",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"@sentry/tracing": "^7.48.0",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/libsodium-wrappers": "^0.7.10",
|
||||
"@ucast/mongo2js": "^1.3.4",
|
||||
"argon2": "^0.30.3",
|
||||
"aws-sdk": "^2.1364.0",
|
||||
"axios": "^1.3.5",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { Request, Response } from "express";
|
||||
import { validateRequest } from "../../../helpers/validation";
|
||||
import { Secret } from "../../../models";
|
||||
import { Folder, Secret } from "../../../models";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
@@ -11,6 +11,7 @@ import { BadRequestError } from "../../../utils/errors";
|
||||
import * as reqValidator from "../../../validation";
|
||||
import { SecretVersion } from "../../models";
|
||||
import { EESecretService } from "../../services";
|
||||
import { getFolderWithPathFromId } from "../../../services/FolderService";
|
||||
|
||||
/**
|
||||
* Return secret versions for secret with id [secretId]
|
||||
@@ -164,10 +165,6 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.SecretRollback
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, { environment: toBeUpdatedSec.environment })
|
||||
);
|
||||
|
||||
// validate secret version
|
||||
const oldSecretVersion = await SecretVersion.findOne({
|
||||
@@ -194,6 +191,15 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||
keyEncoding
|
||||
} = oldSecretVersion;
|
||||
|
||||
let secretPath = "/";
|
||||
const folders = await Folder.findOne({ workspace, environment });
|
||||
if (folders)
|
||||
secretPath = getFolderWithPathFromId(folders.nodes, folder || "root")?.folderPath || "/";
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, { environment: toBeUpdatedSec.environment, secretPath })
|
||||
);
|
||||
|
||||
// update secret
|
||||
const secret = await Secret.findByIdAndUpdate(
|
||||
secretId,
|
||||
|
||||
@@ -3,11 +3,31 @@ import {
|
||||
ForcedSubject,
|
||||
MongoAbility,
|
||||
RawRuleOf,
|
||||
buildMongoQueryMatcher,
|
||||
createMongoAbility
|
||||
} from "@casl/ability";
|
||||
import { Membership } from "../models";
|
||||
import { IRole } from "../models/role";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
|
||||
import { FieldCondition, FieldInstruction, JsInterpreter } from "@ucast/mongo2js";
|
||||
import picomatch from "picomatch";
|
||||
|
||||
const $glob: FieldInstruction<string> = {
|
||||
type: "field",
|
||||
validate(instruction, value) {
|
||||
if (typeof value !== "string") {
|
||||
throw new Error(`"${instruction.name}" expects value to be a string`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const glob: JsInterpreter<FieldCondition<string>> = (node, object, context) => {
|
||||
const secretPath = context.get(object, node.field);
|
||||
const permissionSecretGlobPath = node.value;
|
||||
return picomatch.isMatch(secretPath, permissionSecretGlobPath, { strictSlashes: false });
|
||||
};
|
||||
|
||||
export const conditionsMatcher = buildMongoQueryMatcher({ $glob }, { glob });
|
||||
|
||||
export enum ProjectPermissionActions {
|
||||
Read = "read",
|
||||
@@ -36,7 +56,7 @@ export enum ProjectPermissionSub {
|
||||
|
||||
type SubjectFields = {
|
||||
environment: string;
|
||||
secretPath?: string;
|
||||
secretPath: string;
|
||||
};
|
||||
|
||||
export type ProjectPermissionSet =
|
||||
@@ -144,7 +164,7 @@ const buildAdminPermission = () => {
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Workspace);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Workspace);
|
||||
|
||||
return build();
|
||||
return build({ conditionsMatcher });
|
||||
};
|
||||
|
||||
export const adminProjectPermissions = buildAdminPermission();
|
||||
@@ -180,7 +200,7 @@ const buildMemberPermission = () => {
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
||||
|
||||
return build();
|
||||
return build({ conditionsMatcher });
|
||||
};
|
||||
|
||||
export const memberProjectPermissions = buildMemberPermission();
|
||||
@@ -203,7 +223,7 @@ const buildViewerPermission = () => {
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
||||
|
||||
return build();
|
||||
return build({ conditionsMatcher });
|
||||
};
|
||||
|
||||
export const viewerProjectPermission = buildViewerPermission();
|
||||
@@ -228,7 +248,9 @@ export const getUserProjectPermissions = async (userId: string, workspaceId: str
|
||||
if (membership.role === "viewer") return { permission: viewerProjectPermission, membership };
|
||||
|
||||
if (membership.role === "custom") {
|
||||
const permission = createMongoAbility<ProjectPermissionSet>(membership.customRole.permissions);
|
||||
const permission = createMongoAbility<ProjectPermissionSet>(membership.customRole.permissions, {
|
||||
conditionsMatcher
|
||||
});
|
||||
return { permission, membership };
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { MembershipOrg } from "../models";
|
||||
import { IRole } from "../models/role";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
|
||||
import { ACCEPTED } from "../variables";
|
||||
import { conditionsMatcher } from "./ProjectRoleService";
|
||||
|
||||
export enum OrgPermissionActions {
|
||||
Read = "read",
|
||||
@@ -74,7 +75,7 @@ const buildAdminPermission = () => {
|
||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
|
||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Billing);
|
||||
|
||||
return build();
|
||||
return build({ conditionsMatcher });
|
||||
};
|
||||
|
||||
export const adminPermissions = buildAdminPermission();
|
||||
@@ -92,7 +93,7 @@ const buildMemberPermission = () => {
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
|
||||
|
||||
return build();
|
||||
return build({ conditionsMatcher });
|
||||
};
|
||||
|
||||
export const memberPermissions = buildMemberPermission();
|
||||
@@ -119,7 +120,9 @@ export const getUserOrgPermissions = async (userId: string, orgId: string) => {
|
||||
if (membership.role === "member") return { permission: memberPermissions, membership };
|
||||
|
||||
if (membership.role === "custom") {
|
||||
const permission = createMongoAbility<OrgPermissionSet>(membership.customRole.permissions);
|
||||
const permission = createMongoAbility<OrgPermissionSet>(membership.customRole.permissions, {
|
||||
conditionsMatcher
|
||||
});
|
||||
return { permission, membership };
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ export const CreateRoleSchema = z.object({
|
||||
.object({
|
||||
subject: z.string().trim(),
|
||||
action: z.string().trim(),
|
||||
conditions: z.record(z.union([z.string().trim(), z.number()])).optional()
|
||||
conditions: z
|
||||
.record(z.union([z.string().trim(), z.number(), z.object({ $glob: z.string() })]))
|
||||
.optional()
|
||||
})
|
||||
.array()
|
||||
})
|
||||
@@ -31,7 +33,9 @@ export const UpdateRoleSchema = z.object({
|
||||
.object({
|
||||
subject: z.string().trim(),
|
||||
action: z.string().trim(),
|
||||
conditions: z.record(z.union([z.string().trim(), z.number()])).optional()
|
||||
conditions: z
|
||||
.record(z.union([z.string().trim(), z.number(), z.object({ $glob: z.string() })]))
|
||||
.optional()
|
||||
})
|
||||
.array()
|
||||
.optional()
|
||||
|
||||
@@ -189,7 +189,7 @@ export const BatchSecretsV2 = z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
folderId: z.string().trim().default("root"),
|
||||
environment: z.string().trim(),
|
||||
secretPath: z.string().trim().optional(),
|
||||
secretPath: z.string().trim().default("/"),
|
||||
requests: z
|
||||
.discriminatedUnion("method", [
|
||||
z.object({
|
||||
@@ -328,7 +328,7 @@ export const CreateSecretV3 = z.object({
|
||||
secretCommentCiphertext: z.string().trim().optional(),
|
||||
secretCommentIV: z.string().trim().optional(),
|
||||
secretCommentTag: z.string().trim().optional(),
|
||||
metadata: z.record(z.string()).optional(),
|
||||
metadata: z.record(z.string()).optional()
|
||||
}),
|
||||
params: z.object({
|
||||
secretName: z.string().trim()
|
||||
|
||||
Reference in New Issue
Block a user