Split requireBlindIndicesEnabled, E2EEOff, requireIPAllowlistCheck away from requireWorkspaceAuth

This commit is contained in:
Tuan Dang
2023-08-29 09:27:00 +01:00
committed by Akhil Mohan
parent 7c698e755a
commit 82d4c8f000
7 changed files with 190 additions and 118 deletions

View File

@@ -14,6 +14,9 @@ import requireServiceAccountAuth from "./requireServiceAccountAuth";
import requireServiceAccountWorkspacePermissionAuth from "./requireServiceAccountWorkspacePermissionAuth";
import requireSecretAuth from "./requireSecretAuth";
import requireSecretsAuth from "./requireSecretsAuth";
import requireBlindIndicesEnabled from "./requireBlindIndicesEnabled";
import requireE2EEOff from "./requireE2EEOff";
import requireIPAllowlistCheck from "./requireIPAllowlistCheck";
import validateRequest from "./validateRequest";
export {
@@ -33,5 +36,8 @@ export {
requireServiceAccountWorkspacePermissionAuth,
requireSecretAuth,
requireSecretsAuth,
requireBlindIndicesEnabled,
requireE2EEOff,
requireIPAllowlistCheck,
validateRequest,
};

View File

@@ -0,0 +1,34 @@
import { NextFunction, Request, Response } from "express";
import { Types } from "mongoose";
import { SecretBlindIndexData } from "../models";
import { UnauthorizedRequestError } from "../utils/errors";
type req = "params" | "body" | "query";
/**
* Validate if workspace with [workspaceId] has blind indices enabled
* @param {Object} obj
* @param {String} obj.locationWorkspaceId - location of [workspaceId] on request (e.g. params, body) for parsing
* @returns
*/
const requireBlindIndicesEnabled = ({
locationWorkspaceId
}: {
locationWorkspaceId: req;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
const workspaceId = req[locationWorkspaceId]?.workspaceId;
const secretBlindIndexData = await SecretBlindIndexData.exists({
workspace: new Types.ObjectId(workspaceId)
});
if (!secretBlindIndexData) throw UnauthorizedRequestError({
message: "Failed workspace authorization due to blind indices not being enabled"
});
return next();
}
}
export default requireBlindIndicesEnabled;

View File

@@ -0,0 +1,31 @@
import { NextFunction, Request, Response } from "express";
import { BadRequestError } from "../utils/errors";
import { BotService } from "../services";
type req = "params" | "body" | "query";
/**
* Validate if workspace with [workspaceId] has E2EE off/disabled
* @param {Object} obj
* @param {String} obj.locationWorkspaceId - location of [workspaceId] on request (e.g. params, body) for parsing
* @returns
*/
const requireE2EEOff = ({
locationWorkspaceId
}: {
locationWorkspaceId: req;
}) => {
return async (req: Request, _: Response, next: NextFunction) => {
const workspaceId = req[locationWorkspaceId]?.workspaceId;
const isWorkspaceE2EE = await BotService.getIsWorkspaceE2EE(workspaceId);
if (isWorkspaceE2EE) throw BadRequestError({
message: "Failed workspace authorization due to end-to-end encryption not being disabled"
});
return next();
}
}
export default requireE2EEOff;

View File

@@ -0,0 +1,55 @@
import net from "net";
import { NextFunction, Request, Response } from "express";
import { UnauthorizedRequestError } from "../utils/errors";
import { extractIPDetails } from "../utils/ip";
import { ActorType, TrustedIP } from "../ee/models";
type req = "params" | "body" | "query";
/**
* Validate if workspace with [workspaceId] has E2EE off/disabled
* @param {Object} obj
* @param {String} obj.locationWorkspaceId - location of [workspaceId] on request (e.g. params, body) for parsing
* @returns
*/
const requireIPAllowlistCheck = ({
locationWorkspaceId
}: {
locationWorkspaceId: req;
}) => {
return async (req: Request, _: Response, next: NextFunction) => {
const workspaceId = req[locationWorkspaceId]?.workspaceId;
if (req.authData.actor.type === ActorType.SERVICE) {
const trustedIps = await TrustedIP.find({
workspace: workspaceId
});
if (trustedIps.length > 0) {
// case: check the IP address of the inbound request against trusted IPs
const blockList = new net.BlockList();
for (const trustedIp of trustedIps) {
if (trustedIp.prefix !== undefined) {
blockList.addSubnet(trustedIp.ipAddress, trustedIp.prefix, trustedIp.type);
} else {
blockList.addAddress(trustedIp.ipAddress, trustedIp.type);
}
}
const { type } = extractIPDetails(req.authData.ipAddress);
const check = blockList.check(req.authData.ipAddress, type);
if (!check)
throw UnauthorizedRequestError({
message: "Failed workspace authorization"
});
}
}
return next();
}
}
export default requireIPAllowlistCheck;

View File

@@ -9,24 +9,18 @@ type req = "params" | "body" | "query";
* on request params.
* @param {Object} obj
* @param {String[]} obj.acceptedRoles - accepted workspace roles for JWT auth
* @param {String[]} obj.location - location of [workspaceId] on request (e.g. params, body) for parsing
* @param {String} obj.locationWorkspaceId - location of [workspaceId] on request (e.g. params, body) for parsing
*/
const requireWorkspaceAuth = ({
acceptedRoles,
locationWorkspaceId,
locationEnvironment = undefined,
requiredPermissions = [],
requireBlindIndicesEnabled = false,
requireE2EEOff = false,
checkIPAllowlist = false
}: {
acceptedRoles: Array<"admin" | "member">;
locationWorkspaceId: req;
locationEnvironment?: req | undefined;
requiredPermissions?: string[];
requireBlindIndicesEnabled?: boolean;
requireE2EEOff?: boolean;
checkIPAllowlist?: boolean;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
const workspaceId = req[locationWorkspaceId]?.workspaceId;
@@ -38,10 +32,7 @@ const requireWorkspaceAuth = ({
workspaceId: new Types.ObjectId(workspaceId),
environment,
acceptedRoles,
requiredPermissions,
requireBlindIndicesEnabled,
requireE2EEOff,
checkIPAllowlist
requiredPermissions
});
if (membership) {

View File

@@ -1,6 +1,12 @@
import express from "express";
const router = express.Router();
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware";
import {
requireAuth,
requireBlindIndicesEnabled,
requireE2EEOff,
requireWorkspaceAuth,
validateRequest
} from "../../middleware";
import { body, param, query } from "express-validator";
import { secretsController } from "../../controllers/v3";
import {
@@ -21,8 +27,6 @@ router.get(
secretsController.getSecretsRaw
);
// TODO(akhilmhdh): tony please split the requireWorkspaceAuth to multiple middlewares
// IP checking into another one
router.get(
"/raw/:secretName",
requireAuth({
@@ -32,10 +36,13 @@ router.get(
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "query",
locationEnvironment: "query",
requiredPermissions: [PERMISSION_READ_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: true,
checkIPAllowlist: false
requiredPermissions: [PERMISSION_READ_SECRETS]
}),
requireBlindIndicesEnabled({
locationWorkspaceId: "query"
}),
requireE2EEOff({
locationWorkspaceId: "query"
}),
secretsController.getSecretByNameRaw
);
@@ -49,10 +56,13 @@ router.post(
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: true,
checkIPAllowlist: false
requiredPermissions: [PERMISSION_WRITE_SECRETS]
}),
requireBlindIndicesEnabled({
locationWorkspaceId: "body"
}),
requireE2EEOff({
locationWorkspaceId: "body"
}),
secretsController.createSecretRaw
);
@@ -66,10 +76,13 @@ router.patch(
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: true,
checkIPAllowlist: false
requiredPermissions: [PERMISSION_WRITE_SECRETS]
}),
requireBlindIndicesEnabled({
locationWorkspaceId: "body"
}),
requireE2EEOff({
locationWorkspaceId: "body"
}),
secretsController.updateSecretByNameRaw
);
@@ -83,10 +96,13 @@ router.delete(
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: true,
checkIPAllowlist: false
requiredPermissions: [PERMISSION_WRITE_SECRETS]
}),
requireBlindIndicesEnabled({
locationWorkspaceId: "body"
}),
requireE2EEOff({
locationWorkspaceId: "body"
}),
secretsController.deleteSecretByNameRaw
);
@@ -105,10 +121,10 @@ router.get(
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "query",
locationEnvironment: "query",
requiredPermissions: [PERMISSION_READ_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: false,
checkIPAllowlist: false
requiredPermissions: [PERMISSION_READ_SECRETS]
}),
requireBlindIndicesEnabled({
locationWorkspaceId: "query"
}),
secretsController.getSecrets
);
@@ -122,10 +138,10 @@ router.post(
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: false,
checkIPAllowlist: false
requiredPermissions: [PERMISSION_WRITE_SECRETS]
}),
requireBlindIndicesEnabled({
locationWorkspaceId: "body"
}),
secretsController.createSecret
);
@@ -139,9 +155,10 @@ router.get(
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "query",
locationEnvironment: "query",
requiredPermissions: [PERMISSION_READ_SECRETS],
requireBlindIndicesEnabled: true,
checkIPAllowlist: false
requiredPermissions: [PERMISSION_READ_SECRETS]
}),
requireBlindIndicesEnabled({
locationWorkspaceId: "query"
}),
secretsController.getSecretByName
);
@@ -155,10 +172,10 @@ router.patch(
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: false,
checkIPAllowlist: false
requiredPermissions: [PERMISSION_WRITE_SECRETS]
}),
requireBlindIndicesEnabled({
locationWorkspaceId: "body"
}),
secretsController.updateSecretByName
);
@@ -178,10 +195,10 @@ router.delete(
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: false,
checkIPAllowlist: false
requiredPermissions: [PERMISSION_WRITE_SECRETS]
}),
requireBlindIndicesEnabled({
locationWorkspaceId: "body"
}),
secretsController.deleteSecretByName
);

View File

@@ -1,13 +1,10 @@
import net from "net";
import { Types } from "mongoose";
import { IServiceTokenData, IUser, SecretBlindIndexData, Workspace } from "../models";
import { ActorType, TrustedIP } from "../ee/models";
import { IServiceTokenData, IUser, Workspace } from "../models";
import { ActorType } from "../ee/models";
import { validateUserClientForWorkspace } from "./user";
import { validateServiceTokenDataClientForWorkspace } from "./serviceTokenData";
import { BadRequestError, UnauthorizedRequestError, WorkspaceNotFoundError } from "../utils/errors";
import { BotService } from "../services";
import { WorkspaceNotFoundError } from "../utils/errors";
import { AuthData } from "../interfaces/middleware";
import { extractIPDetails } from "../utils/ip";
import { z } from "zod";
import { EventType, UserAgentType } from "../ee/models";
@@ -26,50 +23,19 @@ export const validateClientForWorkspace = async ({
workspaceId,
environment,
acceptedRoles,
requiredPermissions,
requireBlindIndicesEnabled,
requireE2EEOff,
checkIPAllowlist
requiredPermissions
}: {
authData: AuthData;
workspaceId: Types.ObjectId;
environment?: string;
acceptedRoles: Array<"admin" | "member">;
requiredPermissions?: string[];
requireBlindIndicesEnabled: boolean;
requireE2EEOff: boolean;
checkIPAllowlist: boolean;
}) => {
const workspace = await Workspace.findById(workspaceId);
if (!workspace)
throw WorkspaceNotFoundError({
message: "Failed to find workspace"
});
if (requireBlindIndicesEnabled) {
// case: blind indices are not enabled for secrets in this workspace
// (i.e. workspace was created before blind indices were introduced
// and no admin has enabled it)
const secretBlindIndexData = await SecretBlindIndexData.exists({
workspace: new Types.ObjectId(workspaceId)
});
if (!secretBlindIndexData)
throw UnauthorizedRequestError({
message: "Failed workspace authorization due to blind indices not being enabled"
});
}
if (requireE2EEOff) {
const isWorkspaceE2EE = await BotService.getIsWorkspaceE2EE(workspaceId);
if (isWorkspaceE2EE)
throw BadRequestError({
message: "Failed workspace authorization due to end-to-end encryption not being disabled"
});
}
if (!workspace) throw WorkspaceNotFoundError({
message: "Failed to find workspace"
});
let membership;
switch (authData.actor.type) {
@@ -84,34 +50,6 @@ export const validateClientForWorkspace = async ({
return { membership, workspace };
case ActorType.SERVICE:
if (checkIPAllowlist) {
const trustedIps = await TrustedIP.find({
workspace: workspaceId
});
if (trustedIps.length > 0) {
// case: check the IP address of the inbound request against trusted IPs
const blockList = new net.BlockList();
for (const trustedIp of trustedIps) {
if (trustedIp.prefix !== undefined) {
blockList.addSubnet(trustedIp.ipAddress, trustedIp.prefix, trustedIp.type);
} else {
blockList.addAddress(trustedIp.ipAddress, trustedIp.type);
}
}
const { type } = extractIPDetails(authData.ipAddress);
const check = blockList.check(authData.ipAddress, type);
if (!check)
throw UnauthorizedRequestError({
message: "Failed workspace authorization"
});
}
}
await validateServiceTokenDataClientForWorkspace({
serviceTokenData: authData.authPayload as IServiceTokenData,
workspaceId,