mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 11:29:21 +00:00
Merge pull request #3447 from akhilmhdh/feat/assume-role
Implemented project permission impersonation
This commit is contained in:
Vendored
+3
@@ -5,6 +5,7 @@ import { Redis } from "ioredis";
|
||||
import { TUsers } from "@app/db/schemas";
|
||||
import { TAccessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
|
||||
import { TAccessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service";
|
||||
import { TAssumePrivilegeServiceFactory } from "@app/ee/services/assume-privilege/assume-privilege-service";
|
||||
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service";
|
||||
@@ -109,6 +110,7 @@ declare module "@fastify/request-context" {
|
||||
};
|
||||
};
|
||||
identityPermissionMetadata?: Record<string, unknown>; // filled by permission service
|
||||
assumedPrivilegeDetails?: { requesterId: string; actorId: string; actorType: ActorType; projectId: string };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,6 +243,7 @@ declare module "fastify" {
|
||||
kmipOperation: TKmipOperationServiceFactory;
|
||||
gateway: TGatewayServiceFactory;
|
||||
secretRotationV2: TSecretRotationV2ServiceFactory;
|
||||
assumePrivileges: TAssumePrivilegeServiceFactory;
|
||||
};
|
||||
// this is exclusive use for middlewares in which we need to inject data
|
||||
// everywhere else access using service layer
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
import { z } from "zod";
|
||||
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerAssumePrivilegeRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/:projectId/assume-privileges",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
projectId: z.string()
|
||||
}),
|
||||
body: z.object({
|
||||
actorType: z.enum([ActorType.USER, ActorType.IDENTITY]),
|
||||
actorId: z.string()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req, res) => {
|
||||
if (req.auth.authMode === AuthMode.JWT) {
|
||||
const payload = await server.services.assumePrivileges.assumeProjectPrivileges({
|
||||
targetActorType: req.body.actorType,
|
||||
targetActorId: req.body.actorId,
|
||||
projectId: req.params.projectId,
|
||||
actorPermissionDetails: req.permission,
|
||||
tokenVersionId: req.auth.tokenVersionId
|
||||
});
|
||||
|
||||
const appCfg = getConfig();
|
||||
void res.setCookie("infisical-project-assume-privileges", payload.assumePrivilegesToken, {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: appCfg.HTTPS_ENABLED,
|
||||
maxAge: 3600 // 1 hour in seconds
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
event: {
|
||||
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_START,
|
||||
metadata: {
|
||||
projectId: req.params.projectId,
|
||||
requesterEmail: req.auth.user.username,
|
||||
requesterId: req.auth.user.id,
|
||||
targetActorType: req.body.actorType,
|
||||
targetActorId: req.body.actorId,
|
||||
duration: "1hr"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { message: "Successfully assumed role" };
|
||||
}
|
||||
|
||||
throw new BadRequestError({ message: "Invalid auth mode" });
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "DELETE",
|
||||
url: "/:projectId/assume-privileges",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
projectId: z.string()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req, res) => {
|
||||
const assumedPrivilegeDetails = requestContext.get("assumedPrivilegeDetails");
|
||||
if (req.auth.authMode === AuthMode.JWT && assumedPrivilegeDetails) {
|
||||
const appCfg = getConfig();
|
||||
void res.setCookie("infisical-project-assume-privileges", "", {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: appCfg.HTTPS_ENABLED,
|
||||
expires: new Date(0)
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
event: {
|
||||
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_END,
|
||||
metadata: {
|
||||
projectId: req.params.projectId,
|
||||
requesterEmail: req.auth.user.username,
|
||||
requesterId: req.auth.user.id,
|
||||
targetActorId: assumedPrivilegeDetails.actorId,
|
||||
targetActorType: assumedPrivilegeDetails.actorType
|
||||
}
|
||||
}
|
||||
});
|
||||
return { message: "Successfully exited assumed role" };
|
||||
}
|
||||
|
||||
throw new BadRequestError({ message: "Invalid auth mode" });
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { registerProjectTemplateRouter } from "@app/ee/routes/v1/project-templat
|
||||
|
||||
import { registerAccessApprovalPolicyRouter } from "./access-approval-policy-router";
|
||||
import { registerAccessApprovalRequestRouter } from "./access-approval-request-router";
|
||||
import { registerAssumePrivilegeRouter } from "./assume-privilege-router";
|
||||
import { registerAuditLogStreamRouter } from "./audit-log-stream-router";
|
||||
import { registerCaCrlRouter } from "./certificate-authority-crl-router";
|
||||
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
|
||||
@@ -45,6 +46,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
||||
await projectRouter.register(registerProjectRoleRouter);
|
||||
await projectRouter.register(registerProjectRouter);
|
||||
await projectRouter.register(registerTrustedIpRouter);
|
||||
await projectRouter.register(registerAssumePrivilegeRouter);
|
||||
},
|
||||
{ prefix: "/workspace" }
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { packRules } from "@casl/ability/extra";
|
||||
import { z } from "zod";
|
||||
|
||||
import { ProjectMembershipRole, ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas";
|
||||
import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
|
||||
import {
|
||||
backfillPermissionV1SchemaToV2Schema,
|
||||
ProjectPermissionV1Schema
|
||||
@@ -245,13 +245,22 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
response: {
|
||||
200: z.object({
|
||||
data: z.object({
|
||||
membership: ProjectMembershipsSchema.extend({
|
||||
membership: z.object({
|
||||
id: z.string(),
|
||||
roles: z
|
||||
.object({
|
||||
role: z.string()
|
||||
})
|
||||
.array()
|
||||
}),
|
||||
assumedPrivilegeDetails: z
|
||||
.object({
|
||||
actorId: z.string(),
|
||||
actorType: z.string(),
|
||||
actorName: z.string(),
|
||||
actorEmail: z.string().optional()
|
||||
})
|
||||
.optional(),
|
||||
permissions: z.any().array()
|
||||
})
|
||||
})
|
||||
@@ -259,14 +268,20 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const { permissions, membership } = await server.services.projectRole.getUserPermission(
|
||||
const { permissions, membership, assumedPrivilegeDetails } = await server.services.projectRole.getUserPermission(
|
||||
req.permission.id,
|
||||
req.params.projectId,
|
||||
req.permission.authMethod,
|
||||
req.permission.orgId
|
||||
);
|
||||
|
||||
return { data: { permissions, membership } };
|
||||
return {
|
||||
data: {
|
||||
permissions,
|
||||
membership,
|
||||
assumedPrivilegeDetails
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import {
|
||||
ProjectPermissionIdentityActions,
|
||||
ProjectPermissionMemberActions,
|
||||
ProjectPermissionSub
|
||||
} from "../permission/project-permission";
|
||||
import { TAssumeProjectPrivilegeDTO } from "./assume-privilege-types";
|
||||
|
||||
type TAssumePrivilegeServiceFactoryDep = {
|
||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
};
|
||||
|
||||
export type TAssumePrivilegeServiceFactory = ReturnType<typeof assumePrivilegeServiceFactory>;
|
||||
|
||||
export const assumePrivilegeServiceFactory = ({ projectDAL, permissionService }: TAssumePrivilegeServiceFactoryDep) => {
|
||||
const assumeProjectPrivileges = async ({
|
||||
targetActorType,
|
||||
targetActorId,
|
||||
projectId,
|
||||
actorPermissionDetails,
|
||||
tokenVersionId
|
||||
}: TAssumeProjectPrivilegeDTO) => {
|
||||
const project = await projectDAL.findById(projectId);
|
||||
if (!project) throw new NotFoundError({ message: `Project with ID '${projectId}' not found` });
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actorPermissionDetails.type,
|
||||
actorId: actorPermissionDetails.id,
|
||||
projectId,
|
||||
actorAuthMethod: actorPermissionDetails.authMethod,
|
||||
actorOrgId: actorPermissionDetails.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
if (targetActorType === ActorType.USER) {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionMemberActions.AssumePrivileges,
|
||||
ProjectPermissionSub.Member
|
||||
);
|
||||
} else {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.AssumePrivileges,
|
||||
ProjectPermissionSub.Identity
|
||||
);
|
||||
}
|
||||
|
||||
// check entity is part of project
|
||||
await permissionService.getProjectPermission({
|
||||
actor: targetActorType,
|
||||
actorId: targetActorId,
|
||||
projectId,
|
||||
actorAuthMethod: actorPermissionDetails.authMethod,
|
||||
actorOrgId: actorPermissionDetails.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
const appCfg = getConfig();
|
||||
const assumePrivilegesToken = jwt.sign(
|
||||
{
|
||||
tokenVersionId,
|
||||
actorType: targetActorType,
|
||||
actorId: targetActorId,
|
||||
projectId,
|
||||
requesterId: actorPermissionDetails.id
|
||||
},
|
||||
appCfg.AUTH_SECRET,
|
||||
{ expiresIn: "1hr" }
|
||||
);
|
||||
|
||||
return { actorType: targetActorType, actorId: targetActorId, projectId, assumePrivilegesToken };
|
||||
};
|
||||
|
||||
const verifyAssumePrivilegeToken = (token: string, tokenVersionId: string) => {
|
||||
const appCfg = getConfig();
|
||||
const decodedToken = jwt.verify(token, appCfg.AUTH_SECRET) as {
|
||||
tokenVersionId: string;
|
||||
projectId: string;
|
||||
requesterId: string;
|
||||
actorType: ActorType;
|
||||
actorId: string;
|
||||
};
|
||||
if (decodedToken.tokenVersionId !== tokenVersionId) {
|
||||
throw new ForbiddenRequestError({ message: "Invalid token version" });
|
||||
}
|
||||
return decodedToken;
|
||||
};
|
||||
|
||||
return {
|
||||
assumeProjectPrivileges,
|
||||
verifyAssumePrivilegeToken
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
|
||||
export type TAssumeProjectPrivilegeDTO = {
|
||||
targetActorType: ActorType.USER | ActorType.IDENTITY;
|
||||
targetActorId: string;
|
||||
projectId: string;
|
||||
tokenVersionId: string;
|
||||
actorPermissionDetails: OrgServiceActor;
|
||||
};
|
||||
@@ -320,7 +320,9 @@ export enum EventType {
|
||||
DELETE_SECRET_ROTATION = "delete-secret-rotation",
|
||||
SECRET_ROTATION_ROTATE_SECRETS = "secret-rotation-rotate-secrets",
|
||||
|
||||
PROJECT_ACCESS_REQUEST = "project-access-request"
|
||||
PROJECT_ACCESS_REQUEST = "project-access-request",
|
||||
PROJECT_ASSUME_PRIVILEGE_SESSION_START = "project-assume-privileges-session-start",
|
||||
PROJECT_ASSUME_PRIVILEGE_SESSION_END = "project-assume-privileges-session-end"
|
||||
}
|
||||
|
||||
export const filterableSecretEvents: EventType[] = [
|
||||
@@ -2454,6 +2456,29 @@ interface ProjectAccessRequestEvent {
|
||||
};
|
||||
}
|
||||
|
||||
interface ProjectAssumePrivilegesEvent {
|
||||
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_START;
|
||||
metadata: {
|
||||
projectId: string;
|
||||
requesterId: string;
|
||||
requesterEmail: string;
|
||||
targetActorType: ActorType;
|
||||
targetActorId: string;
|
||||
duration: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ProjectAssumePrivilegesExitEvent {
|
||||
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_END;
|
||||
metadata: {
|
||||
projectId: string;
|
||||
requesterId: string;
|
||||
requesterEmail: string;
|
||||
targetActorType: ActorType;
|
||||
targetActorId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface SetupKmipEvent {
|
||||
type: EventType.SETUP_KMIP;
|
||||
metadata: {
|
||||
@@ -2759,6 +2784,8 @@ export type Event =
|
||||
| KmipOperationLocateEvent
|
||||
| KmipOperationRegisterEvent
|
||||
| ProjectAccessRequestEvent
|
||||
| ProjectAssumePrivilegesEvent
|
||||
| ProjectAssumePrivilegesExitEvent
|
||||
| CreateSecretRequestEvent
|
||||
| SecretApprovalRequestReview
|
||||
| GetSecretRotationsEvent
|
||||
|
||||
@@ -551,13 +551,26 @@ export const permissionServiceFactory = ({
|
||||
};
|
||||
|
||||
const getProjectPermission = async <T extends ActorType>({
|
||||
actor,
|
||||
actorId,
|
||||
actor: inputActor,
|
||||
actorId: inputActorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType
|
||||
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
|
||||
let actor = inputActor;
|
||||
let actorId = inputActorId;
|
||||
const assumedPrivilegeDetailsCtx = requestContext.get("assumedPrivilegeDetails");
|
||||
if (
|
||||
assumedPrivilegeDetailsCtx &&
|
||||
actor === ActorType.USER &&
|
||||
actorId === assumedPrivilegeDetailsCtx.requesterId &&
|
||||
projectId === assumedPrivilegeDetailsCtx.projectId
|
||||
) {
|
||||
actor = assumedPrivilegeDetailsCtx.actorType;
|
||||
actorId = assumedPrivilegeDetailsCtx.actorId;
|
||||
}
|
||||
|
||||
switch (actor) {
|
||||
case ActorType.USER:
|
||||
return getUserProjectPermission({
|
||||
|
||||
@@ -50,7 +50,8 @@ export enum ProjectPermissionIdentityActions {
|
||||
Create = "create",
|
||||
Edit = "edit",
|
||||
Delete = "delete",
|
||||
GrantPrivileges = "grant-privileges"
|
||||
GrantPrivileges = "grant-privileges",
|
||||
AssumePrivileges = "assume-privileges"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionMemberActions {
|
||||
@@ -58,7 +59,8 @@ export enum ProjectPermissionMemberActions {
|
||||
Create = "create",
|
||||
Edit = "edit",
|
||||
Delete = "delete",
|
||||
GrantPrivileges = "grant-privileges"
|
||||
GrantPrivileges = "grant-privileges",
|
||||
AssumePrivileges = "assume-privileges"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionGroupActions {
|
||||
@@ -714,7 +716,8 @@ const buildAdminPermissionRules = () => {
|
||||
ProjectPermissionMemberActions.Edit,
|
||||
ProjectPermissionMemberActions.Delete,
|
||||
ProjectPermissionMemberActions.Read,
|
||||
ProjectPermissionMemberActions.GrantPrivileges
|
||||
ProjectPermissionMemberActions.GrantPrivileges,
|
||||
ProjectPermissionMemberActions.AssumePrivileges
|
||||
],
|
||||
ProjectPermissionSub.Member
|
||||
);
|
||||
@@ -736,7 +739,8 @@ const buildAdminPermissionRules = () => {
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
ProjectPermissionIdentityActions.Delete,
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionIdentityActions.AssumePrivileges
|
||||
],
|
||||
ProjectPermissionSub.Identity
|
||||
);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
import fp from "fastify-plugin";
|
||||
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const injectAssumePrivilege = fp(async (server: FastifyZodProvider) => {
|
||||
server.addHook("onRequest", async (req, res) => {
|
||||
const assumeRoleCookie = req.cookies["infisical-project-assume-privileges"];
|
||||
try {
|
||||
if (req?.auth?.authMode === AuthMode.JWT && assumeRoleCookie) {
|
||||
const decodedToken = server.services.assumePrivileges.verifyAssumePrivilegeToken(
|
||||
assumeRoleCookie,
|
||||
req.auth.tokenVersionId
|
||||
);
|
||||
if (decodedToken) {
|
||||
requestContext.set("assumedPrivilegeDetails", decodedToken);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
req.log.error({ error }, "Failed to verify assume privilege token");
|
||||
void res.clearCookie("infisical-project-assume-privileges");
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -12,6 +12,7 @@ import { accessApprovalPolicyServiceFactory } from "@app/ee/services/access-appr
|
||||
import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-dal";
|
||||
import { accessApprovalRequestReviewerDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-reviewer-dal";
|
||||
import { accessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service";
|
||||
import { assumePrivilegeServiceFactory } from "@app/ee/services/assume-privilege/assume-privilege-service";
|
||||
import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
|
||||
import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue";
|
||||
import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||
@@ -248,6 +249,7 @@ import { workflowIntegrationDALFactory } from "@app/services/workflow-integratio
|
||||
import { workflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
||||
|
||||
import { injectAuditLogInfo } from "../plugins/audit-log";
|
||||
import { injectAssumePrivilege } from "../plugins/auth/inject-assume-privilege";
|
||||
import { injectIdentity } from "../plugins/auth/inject-identity";
|
||||
import { injectPermission } from "../plugins/auth/inject-permission";
|
||||
import { injectRateLimits } from "../plugins/inject-rate-limits";
|
||||
@@ -427,6 +429,11 @@ export const registerRoutes = async (
|
||||
serviceTokenDAL,
|
||||
projectDAL
|
||||
});
|
||||
const assumePrivilegeService = assumePrivilegeServiceFactory({
|
||||
projectDAL,
|
||||
permissionService
|
||||
});
|
||||
|
||||
const licenseService = licenseServiceFactory({
|
||||
permissionService,
|
||||
orgDAL,
|
||||
@@ -1022,7 +1029,9 @@ export const registerRoutes = async (
|
||||
projectRoleDAL,
|
||||
projectUserMembershipRoleDAL,
|
||||
identityProjectMembershipRoleDAL,
|
||||
projectDAL
|
||||
projectDAL,
|
||||
identityDAL,
|
||||
userDAL
|
||||
});
|
||||
|
||||
const snapshotService = secretSnapshotServiceFactory({
|
||||
@@ -1675,7 +1684,8 @@ export const registerRoutes = async (
|
||||
kmip: kmipService,
|
||||
kmipOperation: kmipOperationService,
|
||||
gateway: gatewayService,
|
||||
secretRotationV2: secretRotationV2Service
|
||||
secretRotationV2: secretRotationV2Service,
|
||||
assumePrivileges: assumePrivilegeService
|
||||
});
|
||||
|
||||
const cronJobs: CronJob[] = [];
|
||||
@@ -1696,6 +1706,7 @@ export const registerRoutes = async (
|
||||
});
|
||||
|
||||
await server.register(injectIdentity, { userDAL, serviceTokenDAL });
|
||||
await server.register(injectAssumePrivilege);
|
||||
await server.register(injectPermission);
|
||||
await server.register(injectRateLimits);
|
||||
await server.register(injectAuditLogInfo);
|
||||
|
||||
@@ -33,6 +33,14 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
|
||||
secure: appCfg.HTTPS_ENABLED
|
||||
});
|
||||
|
||||
void res.cookie("infisical-project-assume-privileges", "", {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: appCfg.HTTPS_ENABLED,
|
||||
maxAge: 0
|
||||
});
|
||||
|
||||
return { message: "Successfully logged out" };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -77,6 +77,14 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
||||
secure: cfg.HTTPS_ENABLED
|
||||
});
|
||||
|
||||
void res.cookie("infisical-project-assume-privileges", "", {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: cfg.HTTPS_ENABLED,
|
||||
maxAge: 0
|
||||
});
|
||||
|
||||
return { token: tokens.access, isMfaEnabled: false };
|
||||
}
|
||||
});
|
||||
@@ -131,6 +139,14 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
||||
secure: appCfg.HTTPS_ENABLED
|
||||
});
|
||||
|
||||
void res.cookie("infisical-project-assume-privileges", "", {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: appCfg.HTTPS_ENABLED,
|
||||
maxAge: 0
|
||||
});
|
||||
|
||||
return {
|
||||
encryptionVersion: data.user.encryptionVersion,
|
||||
token: data.token.access,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
|
||||
import { ActionProjectType, ProjectMembershipRole, TableName } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
@@ -12,10 +13,12 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
||||
|
||||
import { ActorAuthMethod } from "../auth/auth-type";
|
||||
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
||||
import { TIdentityDALFactory } from "../identity/identity-dal";
|
||||
import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
|
||||
import { TUserDALFactory } from "../user/user-dal";
|
||||
import { TProjectRoleDALFactory } from "./project-role-dal";
|
||||
import { getPredefinedRoles } from "./project-role-fns";
|
||||
import {
|
||||
@@ -29,6 +32,8 @@ import {
|
||||
|
||||
type TProjectRoleServiceFactoryDep = {
|
||||
projectRoleDAL: TProjectRoleDALFactory;
|
||||
identityDAL: Pick<TIdentityDALFactory, "findById">;
|
||||
userDAL: Pick<TUserDALFactory, "findById">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getUserProjectPermission">;
|
||||
identityProjectMembershipRoleDAL: TIdentityProjectMembershipRoleDALFactory;
|
||||
@@ -47,7 +52,9 @@ export const projectRoleServiceFactory = ({
|
||||
permissionService,
|
||||
identityProjectMembershipRoleDAL,
|
||||
projectUserMembershipRoleDAL,
|
||||
projectDAL
|
||||
projectDAL,
|
||||
identityDAL,
|
||||
userDAL
|
||||
}: TProjectRoleServiceFactoryDep) => {
|
||||
const createRole = async ({ data, actor, actorId, actorAuthMethod, actorOrgId, filter }: TCreateRoleDTO) => {
|
||||
let projectId = "";
|
||||
@@ -220,14 +227,42 @@ export const projectRoleServiceFactory = ({
|
||||
actorAuthMethod: ActorAuthMethod,
|
||||
actorOrgId: string | undefined
|
||||
) => {
|
||||
const { permission, membership } = await permissionService.getUserProjectPermission({
|
||||
userId,
|
||||
const { permission, membership } = await permissionService.getProjectPermission({
|
||||
actor: ActorType.USER,
|
||||
actorId: userId,
|
||||
projectId,
|
||||
authMethod: actorAuthMethod,
|
||||
userOrgId: actorOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
return { permissions: packRules(permission.rules), membership };
|
||||
// just to satisfy ts
|
||||
if (!("roles" in membership)) throw new BadRequestError({ message: "Service token not allowed" });
|
||||
|
||||
const assumedPrivilegeDetailsCtx = requestContext.get("assumedPrivilegeDetails");
|
||||
const isAssumingPrivilege = assumedPrivilegeDetailsCtx?.projectId === projectId;
|
||||
const assumedPrivilegeDetails = isAssumingPrivilege
|
||||
? {
|
||||
actorId: assumedPrivilegeDetailsCtx?.actorId,
|
||||
actorType: assumedPrivilegeDetailsCtx?.actorType,
|
||||
actorName: "",
|
||||
actorEmail: ""
|
||||
}
|
||||
: undefined;
|
||||
|
||||
if (assumedPrivilegeDetails?.actorType === ActorType.IDENTITY) {
|
||||
const identityDetails = await identityDAL.findById(assumedPrivilegeDetails.actorId);
|
||||
if (!identityDetails)
|
||||
throw new NotFoundError({ message: `Identity with ID ${assumedPrivilegeDetails.actorId} not found` });
|
||||
assumedPrivilegeDetails.actorName = identityDetails.name;
|
||||
} else if (assumedPrivilegeDetails?.actorType === ActorType.USER) {
|
||||
const userDetails = await userDAL.findById(assumedPrivilegeDetails?.actorId);
|
||||
if (!userDetails)
|
||||
throw new NotFoundError({ message: `User with ID ${assumedPrivilegeDetails.actorId} not found` });
|
||||
assumedPrivilegeDetails.actorName = `${userDetails?.firstName} ${userDetails?.lastName || ""}`;
|
||||
assumedPrivilegeDetails.actorEmail = userDetails?.email || "";
|
||||
}
|
||||
|
||||
return { permissions: packRules(permission.rules), membership, assumedPrivilegeDetails };
|
||||
};
|
||||
|
||||
return { createRole, updateRole, deleteRole, listRoles, getUserPermission, getRoleBySlug };
|
||||
|
||||
Reference in New Issue
Block a user