merge + final tweaks

This commit is contained in:
x032205
2025-05-21 15:46:36 -04:00
parent dc6a94ccda
commit e3725dd3ab
12 changed files with 134 additions and 92 deletions

View File

@@ -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

View File

@@ -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) => {

View File

@@ -61,7 +61,8 @@ const buildAdminPermissionRules = () => {
ProjectPermissionApprovalActions.Edit,
ProjectPermissionApprovalActions.Create,
ProjectPermissionApprovalActions.Delete,
ProjectPermissionApprovalActions.AllowChangeBypass
ProjectPermissionApprovalActions.AllowChangeBypass,
ProjectPermissionApprovalActions.AllowAccessBypass
],
ProjectPermissionSub.SecretApproval
);

View File

@@ -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 {

View File

@@ -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)
<Info>
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.
</Info>
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)

View File

@@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 129 KiB

View File

@@ -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`

View File

@@ -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 {

View File

@@ -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]: {

View File

@@ -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<Record<string, TWorkspaceUser>>(
(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}
/>
)}

View File

@@ -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 = ({
<Button
isLoading={isLoading === "approved"}
isDisabled={
!!isLoading || (!request.isApprover && !bypassApproval && isSoftEnforcement)
!!isLoading ||
(!request.isApprover &&
!bypassApproval &&
isSoftEnforcement &&
canBypassApprovalPermission)
}
onClick={() => handleReview("approved")}
className="mt-4"
@@ -202,39 +208,42 @@ export const ReviewAccessRequestModal = ({
</Button>
</div>
{isSoftEnforcement && request.isRequestedByCurrentUser && !request.isApprover && (
<div className="mt-2 flex flex-col space-y-2">
<Checkbox
onCheckedChange={(checked) => setBypassApproval(checked === true)}
isChecked={bypassApproval}
id="byPassApproval"
checkIndicatorBg="text-white"
className={twMerge(
"mr-2",
bypassApproval ? "border-red bg-red hover:bg-red-600" : ""
)}
>
<span className="text-xs text-red">
Approve without waiting for requirements to be met (bypass policy protection)
</span>
</Checkbox>
{bypassApproval && (
<FormControl
label="Reason for bypass"
className="mt-2"
isRequired
tooltipText="Enter a reason for bypassing the secret change policy"
{isSoftEnforcement &&
request.isRequestedByCurrentUser &&
!request.isApprover &&
canBypassApprovalPermission && (
<div className="mt-2 flex flex-col space-y-2">
<Checkbox
onCheckedChange={(checked) => setBypassApproval(checked === true)}
isChecked={bypassApproval}
id="byPassApproval"
checkIndicatorBg="text-white"
className={twMerge(
"mr-2",
bypassApproval ? "border-red bg-red hover:bg-red-600" : ""
)}
>
<Input
value={bypassReason}
onChange={(e) => setBypassReason(e.currentTarget.value)}
placeholder="Enter reason for bypass (min 10 chars)"
leftIcon={<FontAwesomeIcon icon={faTriangleExclamation} />}
/>
</FormControl>
)}
</div>
)}
<span className="text-xs text-red">
Approve without waiting for requirements to be met (bypass policy protection)
</span>
</Checkbox>
{bypassApproval && (
<FormControl
label="Reason for bypass"
className="mt-2"
isRequired
tooltipText="Enter a reason for bypassing the secret change policy"
>
<Input
value={bypassReason}
onChange={(e) => setBypassReason(e.currentTarget.value)}
placeholder="Enter reason for bypass (min 10 chars)"
leftIcon={<FontAwesomeIcon icon={faTriangleExclamation} />}
/>
</FormControl>
)}
</div>
)}
</div>
</ModalContent>
</Modal>