Merge pull request #991 from akhilmhdh/feat/rbac-migratio

feat(rbac): migration script for permission from old permission to new
This commit is contained in:
Maidul Islam
2023-09-15 17:09:51 -04:00
committed by GitHub
6 changed files with 220 additions and 106 deletions
@@ -148,7 +148,12 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
const membership = await Membership.findByIdAndUpdate(
membershipId,
{
role
$set: {
role
},
$unset: {
customRole: 1
}
},
{
new: true
@@ -159,7 +159,12 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
const membership = await MembershipOrg.findByIdAndUpdate(
membershipId,
{
role
$set: {
role
},
$unset: {
customRole: 1
}
},
{
new: true
+3
View File
@@ -0,0 +1,3 @@
import { Redis } from "ioredis"
export const redisClient = new Redis(process.env.REDIS_URL as string);
+130 -25
View File
@@ -3,6 +3,7 @@ import crypto from "crypto";
import { Types } from "mongoose";
import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto";
import { EESecretService } from "../../ee/services";
import { redisClient } from "../../services/RedisService"
import { IPType, ISecretVersion, SecretSnapshot, SecretVersion, TrustedIP } from "../../ee/models";
import {
AuthMethod,
@@ -10,6 +11,7 @@ import {
Bot,
BotOrg,
ISecret,
IWorkspace,
Integration,
IntegrationAuth,
Membership,
@@ -26,14 +28,20 @@ import { client, getEncryptionKey, getRootEncryptionKey } from "../../config";
import {
ADMIN,
ALGORITHM_AES_256_GCM,
CUSTOM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8,
MEMBER,
OWNER,
VIEWER
OWNER
} from "../../variables";
import { InternalServerError } from "../errors";
import {
ProjectPermissionActions,
ProjectPermissionSub,
memberProjectPermissions
} from "../../ee/services/ProjectRoleService";
import Role from "../../ee/models/role";
/**
* Backfill secrets to ensure that they're all versioned and have
@@ -679,32 +687,129 @@ export const backfillUserAuthMethods = async () => {
};
export const backfillPermission = async () => {
await Membership.updateMany(
{
deniedPermissions: {
$exists: true,
$ne: []
},
role: MEMBER
},
[
{
$set: {
role: VIEWER
const lockKey = "backfill_permission_lock";
const timeout = 5000; // Lock timeout in milliseconds
const lock = await redisClient.set(lockKey, 1, "PX", timeout, "NX");
if (lock) {
try {
console.info("Lock acquired for script [backfillPermission]");
const memberships = await Membership.find({
deniedPermissions: {
$exists: true,
$ne: []
},
role: MEMBER,
})
.populate<{ workspace: IWorkspace }>("workspace")
.lean();
// group memberships that need the same permission set
const roleMap = new Map<string, { membershipIds: string[], permissions: any[], organizationId: string, workspaceId: string }>();
for (const membership of memberships) {
// get permissions of members except secret permission
const customPermissions = memberProjectPermissions.rules.filter(
({ subject }) => subject !== ProjectPermissionSub.Secrets
);
const secretAccessRule: Record<string, { read: boolean; write: boolean }> = {};
// iterate and record true and false ones
membership.deniedPermissions.forEach(({ ability, environmentSlug }) => {
if (!secretAccessRule?.[environmentSlug])
secretAccessRule[environmentSlug] = { read: true, write: true };
if (ability === "write") secretAccessRule[environmentSlug].write = false;
if (ability === "read") secretAccessRule[environmentSlug].read = false;
});
const secretPermissions: any = [];
Object.entries(secretAccessRule).forEach(([envSlug, { read, write }]) => {
if (read) {
secretPermissions.push({
subject: ProjectPermissionSub.Secrets,
action: ProjectPermissionActions.Read,
conditions: { environment: envSlug }
});
}
if (write) {
secretPermissions.push(
{
subject: ProjectPermissionSub.Secrets,
action: ProjectPermissionActions.Edit,
conditions: { environment: envSlug }
},
{
subject: ProjectPermissionSub.Secrets,
action: ProjectPermissionActions.Delete,
conditions: { environment: envSlug }
},
{
subject: ProjectPermissionSub.Secrets,
action: ProjectPermissionActions.Create,
conditions: { environment: envSlug }
}
);
}
});
const key = `${JSON.stringify(secretPermissions)}-${membership.workspace.organization.toString()}-${membership.workspace._id.toString()}`; // group roles that have same permission with in the same org and workspace
const value = roleMap.get(key);
if (value) {
value.membershipIds.push(membership._id.toString());
value.organizationId = membership.workspace.organization.toString()
value.workspaceId = membership.workspace._id.toString()
} else {
roleMap.set(key, { membershipIds: [membership._id.toString()], permissions: [...customPermissions, ...secretPermissions], organizationId: membership.workspace.organization.toString(), workspaceId: membership.workspace._id.toString() });
}
}
]
);
await MembershipOrg.updateMany(
{
role: OWNER
},
{
$set: {
role: ADMIN
for (const [key, value] of roleMap.entries()) {
const { membershipIds, permissions, workspaceId, organizationId } = value
const role = new Role({
name: "Migrated Role",
organization: organizationId,
workspace: workspaceId,
description: "This role was auto generated by Infisical in effort to migrate your project members to our new permission system",
isOrgRole: false,
slug: `custom-role-${crypto.randomBytes(3).toString("hex")}`,
permissions: permissions
});
await role.save();
for (const id of membershipIds) {
await Membership.findByIdAndUpdate(id, { // document db doesn't support update many so we must loop
$set: {
role: CUSTOM,
customRole: role
}
});
}
}
console.info("Backfill: Finished converting old denied permission in workspace to viewers");
await MembershipOrg.updateMany(
{
role: OWNER
},
{
$set: {
role: ADMIN
}
}
);
console.info("Backfill: Finished converting owner role to member");
} catch (error) {
console.error("An error occurred when running script [backfillPermission]:", error);
} finally {
await redisClient.del(lockKey);
}
);
console.log("Backfill: Finishing converting old denied permission in workspace to viewers");
} else {
console.info("Could not acquire lock for script [backfillPermission], skipping");
}
};