diff --git a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts index 6215937c2..20e954d44 100644 --- a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts +++ b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts @@ -23,6 +23,7 @@ import { TAccessApprovalPolicyApproverDALFactory } from "../access-approval-poli import { TAccessApprovalPolicyDALFactory } from "../access-approval-policy/access-approval-policy-dal"; import { TGroupDALFactory } from "../group/group-dal"; import { TPermissionServiceFactory } from "../permission/permission-service"; +import { ProjectPermissionApprovalActions, ProjectPermissionSub } from "../permission/project-permission"; import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal"; import { ProjectUserAdditionalPrivilegeTemporaryMode } from "../project-user-additional-privilege/project-user-additional-privilege-types"; import { TAccessApprovalRequestDALFactory } from "./access-approval-request-dal"; @@ -340,17 +341,7 @@ export const accessApprovalRequestServiceFactory = ({ }); } - if ( - !policy.allowedSelfApprovals && - actorId === accessApprovalRequest.requestedByUserId && - policy.enforcementLevel !== EnforcementLevel.Soft - ) { - throw new BadRequestError({ - message: "Failed to review access approval request. Users are not authorized to review their own request." - }); - } - - const { membership, hasRole } = await permissionService.getProjectPermission({ + const { membership, hasRole, permission } = await permissionService.getProjectPermission({ actor, actorId, projectId: accessApprovalRequest.projectId, @@ -363,6 +354,19 @@ export const accessApprovalRequestServiceFactory = ({ throw new ForbiddenRequestError({ message: "You are not a member of this project" }); } + if ( + !policy.allowedSelfApprovals && + actorId === accessApprovalRequest.requestedByUserId && + !( + policy.enforcementLevel === EnforcementLevel.Soft && + permission.can(ProjectPermissionApprovalActions.AllowAccessBypass, ProjectPermissionSub.SecretApproval) + ) + ) { + throw new BadRequestError({ + message: "Failed to review access approval request. Users are not authorized to review their own request." + }); + } + if ( !hasRole(ProjectMembershipRole.Admin) && accessApprovalRequest.requestedByUserId !== actorId && // The request wasn't made by the current user diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index a8807c61a..d91529dd2 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -18,33 +18,33 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ environmentsUsed: 0, identityLimit: null, identitiesUsed: 0, - dynamicSecret: true, + dynamicSecret: false, secretVersioning: true, - pitRecovery: true, - ipAllowlisting: true, - rbac: true, - githubOrgSync: true, + pitRecovery: false, + ipAllowlisting: false, + rbac: false, + githubOrgSync: false, customRateLimits: false, - customAlerts: true, - secretAccessInsights: true, - auditLogs: true, - auditLogsRetentionDays: 3, - auditLogStreams: true, + customAlerts: false, + secretAccessInsights: false, + auditLogs: false, + auditLogsRetentionDays: 0, + auditLogStreams: false, auditLogStreamLimit: 3, - samlSSO: true, - hsm: true, - oidcSSO: true, - scim: true, - ldap: true, - groups: true, + samlSSO: false, + hsm: false, + oidcSSO: false, + scim: false, + ldap: false, + groups: false, status: null, trial_end: null, has_used_trial: true, - secretApproval: true, - secretRotation: true, - caCrl: true, - instanceUserManagement: true, - externalKms: true, + secretApproval: false, + secretRotation: false, + caCrl: false, + instanceUserManagement: false, + externalKms: false, rateLimits: { readLimit: 60, writeLimit: 200, @@ -52,10 +52,10 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ }, pkiEst: true, enforceMfa: true, - projectTemplates: true, - kmip: true, - gateway: true, - sshHostGroups: true + projectTemplates: false, + kmip: false, + gateway: false, + sshHostGroups: false }); export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => { diff --git a/backend/src/ee/services/permission/default-roles.ts b/backend/src/ee/services/permission/default-roles.ts index 9461227da..2a422d55f 100644 --- a/backend/src/ee/services/permission/default-roles.ts +++ b/backend/src/ee/services/permission/default-roles.ts @@ -61,7 +61,8 @@ const buildAdminPermissionRules = () => { ProjectPermissionApprovalActions.Edit, ProjectPermissionApprovalActions.Create, ProjectPermissionApprovalActions.Delete, - ProjectPermissionApprovalActions.AllowChangeBypass + ProjectPermissionApprovalActions.AllowChangeBypass, + ProjectPermissionApprovalActions.AllowAccessBypass ], ProjectPermissionSub.SecretApproval ); diff --git a/backend/src/ee/services/permission/project-permission.ts b/backend/src/ee/services/permission/project-permission.ts index 27cd33855..5d06e085d 100644 --- a/backend/src/ee/services/permission/project-permission.ts +++ b/backend/src/ee/services/permission/project-permission.ts @@ -39,7 +39,8 @@ export enum ProjectPermissionApprovalActions { Create = "create", Edit = "edit", Delete = "delete", - AllowChangeBypass = "allow-change-bypass" + AllowChangeBypass = "allow-change-bypass", + AllowAccessBypass = "allow-access-bypass" } export enum ProjectPermissionCmekActions { diff --git a/docs/documentation/platform/access-controls/access-requests.mdx b/docs/documentation/platform/access-controls/access-requests.mdx index 76cc4b74e..bdbabf9f7 100644 --- a/docs/documentation/platform/access-controls/access-requests.mdx +++ b/docs/documentation/platform/access-controls/access-requests.mdx @@ -3,10 +3,10 @@ title: "Access Requests" description: "Learn how to request access to sensitive resources in Infisical." --- -In certain situations, developers need to expand their access to a certain new project or a sensitive environment. For those use cases, it is helpful to utilize Infisical's **Access Requests** functionality. +In certain situations, developers need to expand their access to a certain new project or a sensitive environment. For those use cases, it is helpful to utilize Infisical's **Access Requests** functionality. -This functionality works in the following way: -1. A project administrator sets up an access policy that assigns access managers (also known as eligible approvers) to a certain sensitive folder or environment. +This functionality works in the following way: +1. A project administrator sets up an access policy that assigns access managers (also known as eligible approvers) to a certain sensitive folder or environment. ![Create Access Request Policy Modal](/images/platform/access-controls/create-access-request-policy.png) ![Access Request Policies](/images/platform/access-controls/access-request-policies.png) @@ -19,9 +19,8 @@ This functionality works in the following way: ![Access Request Bypass](/images/platform/access-controls/access-request-bypass.png) - If the access request matches with a policy that has a **Soft** enforcement level, the requester may bypass the policy and get access to the resource without full approval. + If the access request matches with a policy that has allows break-glass approval bypasses, the requester may bypass the policy and get access to the resource without full approval. -5. As soon as the request is approved, developer is able to access the sought resources. +5. As soon as the request is approved, developer is able to access the sought resources. ![Access Request Dashboard](/images/platform/access-controls/access-requests-completed.png) - diff --git a/docs/documentation/platform/pr-workflows.mdx b/docs/documentation/platform/pr-workflows.mdx index ffa85f6c5..2649e0454 100644 --- a/docs/documentation/platform/pr-workflows.mdx +++ b/docs/documentation/platform/pr-workflows.mdx @@ -33,6 +33,8 @@ First, you would need to create a set of policies for a certain environment. In The enforcement level determines how strict the policy is. A **Hard** enforcement level means that any change that matches the policy will need full approval prior merging. A **Soft** enforcement level allows for break glass functionality on the request. If a change request is bypassed, the approvers will be notified via email. +You can use the **Soft** enforcement level by enabling "Bypass Approvals". + ### Self approvals If the **Self Approvals** option is enabled, users who are designated as approvers on the policy can approve requests that they themselves have submitted. diff --git a/docs/images/platform/pr-workflows/create-change-policy.png b/docs/images/platform/pr-workflows/create-change-policy.png index afe945b0a..fabcb6716 100644 Binary files a/docs/images/platform/pr-workflows/create-change-policy.png and b/docs/images/platform/pr-workflows/create-change-policy.png differ diff --git a/docs/internals/permissions/project-permissions.mdx b/docs/internals/permissions/project-permissions.mdx index dabe0e786..313a60c3c 100644 --- a/docs/internals/permissions/project-permissions.mdx +++ b/docs/internals/permissions/project-permissions.mdx @@ -185,6 +185,7 @@ Supports conditions and permission inversion | `edit` | Modify approval policies | | `delete` | Remove approval policies | | `allow-change-bypass` | Allow request creators to bypass policy in break-glass situations | +| `allow-access-bypass` | Allow request creators to bypass policy in break-glass situations | #### Subject: `secret-rotation` diff --git a/frontend/src/context/ProjectPermissionContext/types.ts b/frontend/src/context/ProjectPermissionContext/types.ts index 96ad12ac3..d49b8bc8e 100644 --- a/frontend/src/context/ProjectPermissionContext/types.ts +++ b/frontend/src/context/ProjectPermissionContext/types.ts @@ -29,7 +29,8 @@ export enum ProjectPermissionApprovalActions { Create = "create", Edit = "edit", Delete = "delete", - AllowChangeBypass = "allow-change-bypass" + AllowChangeBypass = "allow-change-bypass", + AllowAccessBypass = "allow-access-bypass" } export enum ProjectPermissionDynamicSecretActions { diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx index cb3cd015d..b2429fdfc 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx @@ -58,7 +58,8 @@ const ApprovalPolicyActionSchema = z.object({ [ProjectPermissionApprovalActions.Edit]: z.boolean().optional(), [ProjectPermissionApprovalActions.Delete]: z.boolean().optional(), [ProjectPermissionApprovalActions.Create]: z.boolean().optional(), - [ProjectPermissionApprovalActions.AllowChangeBypass]: z.boolean().optional() + [ProjectPermissionApprovalActions.AllowChangeBypass]: z.boolean().optional(), + [ProjectPermissionApprovalActions.AllowAccessBypass]: z.boolean().optional() }); const CmekPolicyActionSchema = z.object({ @@ -578,6 +579,7 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => { const canEdit = action.includes(ProjectPermissionApprovalActions.Edit); const canRead = action.includes(ProjectPermissionApprovalActions.Read); const canChangeBypass = action.includes(ProjectPermissionApprovalActions.AllowChangeBypass); + const canAccessBypass = action.includes(ProjectPermissionApprovalActions.AllowAccessBypass); if (!formVal[subject]) formVal[subject] = [{}]; @@ -588,6 +590,8 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => { if (canRead) formVal[subject]![0][ProjectPermissionApprovalActions.Read] = true; if (canChangeBypass) formVal[subject]![0][ProjectPermissionApprovalActions.AllowChangeBypass] = true; + if (canAccessBypass) + formVal[subject]![0][ProjectPermissionApprovalActions.AllowAccessBypass] = true; return; } @@ -1212,7 +1216,8 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = { { label: "Create", value: ProjectPermissionApprovalActions.Create }, { label: "Modify", value: ProjectPermissionApprovalActions.Edit }, { label: "Remove", value: ProjectPermissionApprovalActions.Delete }, - { label: "Allow Change Bypass", value: ProjectPermissionApprovalActions.AllowChangeBypass } + { label: "Allow Change Bypass", value: ProjectPermissionApprovalActions.AllowChangeBypass }, + { label: "Allow Access Bypass", value: ProjectPermissionApprovalActions.AllowAccessBypass } ] }, [ProjectPermissionSub.SecretRotation]: { diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/AccessApprovalRequest.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/AccessApprovalRequest.tsx index 9f56b92ef..fd754b14c 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/AccessApprovalRequest.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/AccessApprovalRequest.tsx @@ -25,6 +25,7 @@ import { } from "@app/components/v2"; import { Badge } from "@app/components/v2/Badge"; import { + ProjectPermissionApprovalActions, ProjectPermissionMemberActions, ProjectPermissionSub, useProjectPermission, @@ -100,6 +101,11 @@ export const AccessApprovalRequest = ({ const { subscription } = useSubscription(); const { currentWorkspace } = useWorkspace(); + const canBypassApprovalPermission = permission.can( + ProjectPermissionApprovalActions.AllowAccessBypass, + ProjectPermissionSub.SecretApproval + ); + const { data: members } = useGetWorkspaceUsers(projectId, true); const membersGroupById = members?.reduce>( (prev, curr) => ({ ...prev, [curr.user.id]: curr }), @@ -350,7 +356,11 @@ export const AccessApprovalRequest = ({ details.isReviewedByUser || details.isRejectedByAnyone || (!details.isApprover && - !(details.isSoftEnforcement && details.isRequestedByCurrentUser)) + !( + details.isSoftEnforcement && + details.isRequestedByCurrentUser && + canBypassApprovalPermission + )) ) return; @@ -360,9 +370,11 @@ export const AccessApprovalRequest = ({ ) { setSelectedRequest({ ...request, - user: details.isRequestedByCurrentUser - ? user - : membersGroupById?.[request.requestedByUserId].user!, + user: + details.isRequestedByCurrentUser || + !membersGroupById?.[request.requestedByUserId].user + ? user + : membersGroupById?.[request.requestedByUserId].user, isRequestedByCurrentUser: details.isRequestedByCurrentUser, isApprover: details.isApprover }); @@ -376,7 +388,11 @@ export const AccessApprovalRequest = ({ details.isReviewedByUser || details.isRejectedByAnyone || (!details.isApprover && - !(details.isSoftEnforcement && details.isRequestedByCurrentUser)) + !( + details.isSoftEnforcement && + details.isRequestedByCurrentUser && + canBypassApprovalPermission + )) ) return; @@ -387,9 +403,11 @@ export const AccessApprovalRequest = ({ ) { setSelectedRequest({ ...request, - user: details.isRequestedByCurrentUser - ? user - : membersGroupById?.[request.requestedByUserId].user!, + user: + details.isRequestedByCurrentUser || + !membersGroupById?.[request.requestedByUserId].user + ? user + : membersGroupById?.[request.requestedByUserId].user, isRequestedByCurrentUser: details.isRequestedByCurrentUser, isApprover: details.isApprover }); @@ -463,6 +481,7 @@ export const AccessApprovalRequest = ({ setSelectedRequest(null); refetchRequests(); }} + canBypassApprovalPermission={canBypassApprovalPermission} /> )} diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx index d739d8bb6..5aaa0ea56 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx @@ -1,5 +1,8 @@ import { useCallback, useMemo, useState } from "react"; +import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import ms from "ms"; +import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { Button, Checkbox, FormControl, Input, Modal, ModalContent } from "@app/components/v2"; @@ -8,9 +11,6 @@ import { ProjectPermissionActions } from "@app/context"; import { useReviewAccessRequest } from "@app/hooks/api"; import { TAccessApprovalRequest } from "@app/hooks/api/accessApproval/types"; import { EnforcementLevel } from "@app/hooks/api/policies/enums"; -import { twMerge } from "tailwind-merge"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; export const ReviewAccessRequestModal = ({ isOpen, @@ -18,7 +18,8 @@ export const ReviewAccessRequestModal = ({ request, projectSlug, selectedRequester, - selectedEnvSlug + selectedEnvSlug, + canBypassApprovalPermission }: { isOpen: boolean; onOpenChange: (isOpen: boolean) => void; @@ -30,6 +31,7 @@ export const ReviewAccessRequestModal = ({ projectSlug: string; selectedRequester: string | undefined; selectedEnvSlug: string | undefined; + canBypassApprovalPermission: boolean; }) => { const [isLoading, setIsLoading] = useState<"approved" | "rejected" | null>(null); const [bypassApproval, setBypassApproval] = useState(false); @@ -182,7 +184,11 @@ export const ReviewAccessRequestModal = ({ - {isSoftEnforcement && request.isRequestedByCurrentUser && !request.isApprover && ( -
- setBypassApproval(checked === true)} - isChecked={bypassApproval} - id="byPassApproval" - checkIndicatorBg="text-white" - className={twMerge( - "mr-2", - bypassApproval ? "border-red bg-red hover:bg-red-600" : "" - )} - > - - Approve without waiting for requirements to be met (bypass policy protection) - - - {bypassApproval && ( - + setBypassApproval(checked === true)} + isChecked={bypassApproval} + id="byPassApproval" + checkIndicatorBg="text-white" + className={twMerge( + "mr-2", + bypassApproval ? "border-red bg-red hover:bg-red-600" : "" + )} > - setBypassReason(e.currentTarget.value)} - placeholder="Enter reason for bypass (min 10 chars)" - leftIcon={} - /> - - )} -
- )} + + Approve without waiting for requirements to be met (bypass policy protection) + + + {bypassApproval && ( + + setBypassReason(e.currentTarget.value)} + placeholder="Enter reason for bypass (min 10 chars)" + leftIcon={} + /> + + )} + + )}