feat(rbac): added glob support in permission and revealed settings

This commit is contained in:
Akhil Mohan
2023-09-08 21:22:36 +05:30
parent 2ed079830a
commit a4ef829046
38 changed files with 2068 additions and 1831 deletions
+1
View File
@@ -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",
+1
View File
@@ -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,
+27 -5
View File
@@ -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 };
}
+6 -3
View File
@@ -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 };
}
+6 -2
View File
@@ -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()
+2 -2
View File
@@ -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()