mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat(policies): Specific permission for bypassing policy
This commit is contained in:
@@ -2,7 +2,7 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionApprovalActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||||
@@ -98,7 +98,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionApprovalActions.Create,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
|
||||||
@@ -256,7 +256,10 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionApprovalActions.Edit,
|
||||||
|
ProjectPermissionSub.SecretApproval
|
||||||
|
);
|
||||||
|
|
||||||
const updatedPolicy = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
const updatedPolicy = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await accessApprovalPolicyDAL.updateById(
|
const doc = await accessApprovalPolicyDAL.updateById(
|
||||||
@@ -341,7 +344,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionApprovalActions.Delete,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -432,7 +435,10 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionApprovalActions.Read,
|
||||||
|
ProjectPermissionSub.SecretApproval
|
||||||
|
);
|
||||||
|
|
||||||
return policy;
|
return policy;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { AbilityBuilder, createMongoAbility, MongoAbility } from "@casl/ability"
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionApprovalActions,
|
||||||
ProjectPermissionCertificateActions,
|
ProjectPermissionCertificateActions,
|
||||||
ProjectPermissionCmekActions,
|
ProjectPermissionCmekActions,
|
||||||
ProjectPermissionDynamicSecretActions,
|
ProjectPermissionDynamicSecretActions,
|
||||||
@@ -25,7 +26,6 @@ const buildAdminPermissionRules = () => {
|
|||||||
[
|
[
|
||||||
ProjectPermissionSub.SecretFolders,
|
ProjectPermissionSub.SecretFolders,
|
||||||
ProjectPermissionSub.SecretImports,
|
ProjectPermissionSub.SecretImports,
|
||||||
ProjectPermissionSub.SecretApproval,
|
|
||||||
ProjectPermissionSub.Role,
|
ProjectPermissionSub.Role,
|
||||||
ProjectPermissionSub.Integrations,
|
ProjectPermissionSub.Integrations,
|
||||||
ProjectPermissionSub.Webhooks,
|
ProjectPermissionSub.Webhooks,
|
||||||
@@ -55,6 +55,17 @@ const buildAdminPermissionRules = () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionApprovalActions.Read,
|
||||||
|
ProjectPermissionApprovalActions.Edit,
|
||||||
|
ProjectPermissionApprovalActions.Create,
|
||||||
|
ProjectPermissionApprovalActions.Delete,
|
||||||
|
ProjectPermissionApprovalActions.AllowChangeBypass
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.SecretApproval
|
||||||
|
);
|
||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
ProjectPermissionCertificateActions.Read,
|
ProjectPermissionCertificateActions.Read,
|
||||||
@@ -243,7 +254,7 @@ const buildMemberPermissionRules = () => {
|
|||||||
ProjectPermissionSub.SecretImports
|
ProjectPermissionSub.SecretImports
|
||||||
);
|
);
|
||||||
|
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretApproval);
|
can([ProjectPermissionApprovalActions.Read], ProjectPermissionSub.SecretApproval);
|
||||||
can([ProjectPermissionSecretRotationActions.Read], ProjectPermissionSub.SecretRotation);
|
can([ProjectPermissionSecretRotationActions.Read], ProjectPermissionSub.SecretRotation);
|
||||||
|
|
||||||
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
|
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
|
||||||
@@ -391,7 +402,7 @@ const buildViewerPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
||||||
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
|
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
can(ProjectPermissionApprovalActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
can(ProjectPermissionSecretRotationActions.Read, ProjectPermissionSub.SecretRotation);
|
can(ProjectPermissionSecretRotationActions.Read, ProjectPermissionSub.SecretRotation);
|
||||||
can(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
can(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
||||||
|
|||||||
@@ -34,6 +34,14 @@ export enum ProjectPermissionSecretActions {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionApprovalActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete",
|
||||||
|
AllowChangeBypass = "allow-change-bypass"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionCmekActions {
|
export enum ProjectPermissionCmekActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
Create = "create",
|
Create = "create",
|
||||||
@@ -242,7 +250,7 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions, ProjectPermissionSub.IpAllowList]
|
| [ProjectPermissionActions, ProjectPermissionSub.IpAllowList]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Settings]
|
| [ProjectPermissionActions, ProjectPermissionSub.Settings]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
|
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.SecretApproval]
|
| [ProjectPermissionApprovalActions, ProjectPermissionSub.SecretApproval]
|
||||||
| [
|
| [
|
||||||
ProjectPermissionSecretRotationActions,
|
ProjectPermissionSecretRotationActions,
|
||||||
(
|
(
|
||||||
@@ -439,7 +447,7 @@ const PkiSubscriberConditionSchema = z
|
|||||||
const GeneralPermissionSchema = [
|
const GeneralPermissionSchema = [
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionApprovalActions).describe(
|
||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
@@ -605,7 +613,7 @@ const GeneralPermissionSchema = [
|
|||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
// Do not update this schema anymore, as it's kept purely for backwards compatability. Update V2 schema only.
|
// Do not update this schema anymore, as it's kept purely for backwards compatibility. Update V2 schema only.
|
||||||
export const ProjectPermissionV1Schema = z.discriminatedUnion("subject", [
|
export const ProjectPermissionV1Schema = z.discriminatedUnion("subject", [
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ import picomatch from "picomatch";
|
|||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionApprovalActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { containsGlobPatterns } from "@app/lib/picomatch";
|
import { containsGlobPatterns } from "@app/lib/picomatch";
|
||||||
@@ -89,7 +93,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionApprovalActions.Create,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -204,7 +208,10 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionApprovalActions.Edit,
|
||||||
|
ProjectPermissionSub.SecretApproval
|
||||||
|
);
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
if (!plan.secretApproval) {
|
if (!plan.secretApproval) {
|
||||||
@@ -301,7 +308,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionApprovalActions.Delete,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -340,7 +347,10 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionApprovalActions.Read,
|
||||||
|
ProjectPermissionSub.SecretApproval
|
||||||
|
);
|
||||||
|
|
||||||
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId, deletedAt: null });
|
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId, deletedAt: null });
|
||||||
return sapPolicies;
|
return sapPolicies;
|
||||||
@@ -413,7 +423,10 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionApprovalActions.Read,
|
||||||
|
ProjectPermissionSub.SecretApproval
|
||||||
|
);
|
||||||
|
|
||||||
return sapPolicy;
|
return sapPolicy;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -62,7 +62,11 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
|
|||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { throwIfMissingSecretReadValueOrDescribePermission } from "../permission/permission-fns";
|
import { throwIfMissingSecretReadValueOrDescribePermission } from "../permission/permission-fns";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { ProjectPermissionSecretActions, ProjectPermissionSub } from "../permission/project-permission";
|
import {
|
||||||
|
ProjectPermissionApprovalActions,
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "../permission/project-permission";
|
||||||
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
|
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
|
||||||
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
||||||
import { TSecretApprovalRequestDALFactory } from "./secret-approval-request-dal";
|
import { TSecretApprovalRequestDALFactory } from "./secret-approval-request-dal";
|
||||||
@@ -504,7 +508,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hasRole } = await permissionService.getProjectPermission({
|
const { hasRole, permission } = await permissionService.getProjectPermission({
|
||||||
actor: ActorType.USER,
|
actor: ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -531,7 +535,13 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
).length;
|
).length;
|
||||||
const isSoftEnforcement = secretApprovalRequest.policy.enforcementLevel === EnforcementLevel.Soft;
|
const isSoftEnforcement = secretApprovalRequest.policy.enforcementLevel === EnforcementLevel.Soft;
|
||||||
|
|
||||||
if (!hasMinApproval && !isSoftEnforcement)
|
if (
|
||||||
|
!hasMinApproval &&
|
||||||
|
!(
|
||||||
|
isSoftEnforcement &&
|
||||||
|
permission.can(ProjectPermissionApprovalActions.AllowChangeBypass, ProjectPermissionSub.SecretApproval)
|
||||||
|
)
|
||||||
|
)
|
||||||
throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
||||||
|
|
||||||
const { botKey, shouldUseSecretV2Bridge, project } = await projectBotService.getBotKey(projectId);
|
const { botKey, shouldUseSecretV2Bridge, project } = await projectBotService.getBotKey(projectId);
|
||||||
|
|||||||
@@ -178,12 +178,13 @@ Supports conditions and permission inversion
|
|||||||
|
|
||||||
#### Subject: `secret-approval`
|
#### Subject: `secret-approval`
|
||||||
|
|
||||||
| Action | Description |
|
| Action | Description |
|
||||||
| -------- | ----------------------------------- |
|
| --------------------- | ---------------------------------------------------------------------------- |
|
||||||
| `read` | View approval policies and requests |
|
| `read` | View approval policies and requests |
|
||||||
| `create` | Create new approval policies |
|
| `create` | Create new approval policies |
|
||||||
| `edit` | Modify approval policies |
|
| `edit` | Modify approval policies |
|
||||||
| `delete` | Remove approval policies |
|
| `delete` | Remove approval policies |
|
||||||
|
| `allow-change-bypass` | Allow request creators to bypass policy in break-glass situations |
|
||||||
|
|
||||||
#### Subject: `secret-rotation`
|
#### Subject: `secret-rotation`
|
||||||
|
|
||||||
|
|||||||
@@ -11,5 +11,6 @@ export {
|
|||||||
ProjectPermissionMemberActions,
|
ProjectPermissionMemberActions,
|
||||||
ProjectPermissionPkiSubscriberActions,
|
ProjectPermissionPkiSubscriberActions,
|
||||||
ProjectPermissionSshHostActions,
|
ProjectPermissionSshHostActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub,
|
||||||
|
ProjectPermissionApprovalActions
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ export enum ProjectPermissionSecretActions {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionApprovalActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete",
|
||||||
|
AllowChangeBypass = "allow-change-bypass"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionDynamicSecretActions {
|
export enum ProjectPermissionDynamicSecretActions {
|
||||||
ReadRootCredential = "read-root-credential",
|
ReadRootCredential = "read-root-credential",
|
||||||
CreateRootCredential = "create-root-credential",
|
CreateRootCredential = "create-root-credential",
|
||||||
@@ -285,7 +293,7 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions, ProjectPermissionSub.IpAllowList]
|
| [ProjectPermissionActions, ProjectPermissionSub.IpAllowList]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Settings]
|
| [ProjectPermissionActions, ProjectPermissionSub.Settings]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
|
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.SecretApproval]
|
| [ProjectPermissionApprovalActions, ProjectPermissionSub.SecretApproval]
|
||||||
| [
|
| [
|
||||||
ProjectPermissionIdentityActions,
|
ProjectPermissionIdentityActions,
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ export {
|
|||||||
ProjectPermissionPkiSubscriberActions,
|
ProjectPermissionPkiSubscriberActions,
|
||||||
ProjectPermissionSshHostActions,
|
ProjectPermissionSshHostActions,
|
||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
useProjectPermission
|
useProjectPermission,
|
||||||
|
ProjectPermissionApprovalActions
|
||||||
} from "./ProjectPermissionContext";
|
} from "./ProjectPermissionContext";
|
||||||
export { useServerConfig } from "./ServerConfigContext";
|
export { useServerConfig } from "./ServerConfigContext";
|
||||||
export { useSubscription } from "./SubscriptionContext";
|
export { useSubscription } from "./SubscriptionContext";
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import {
|
import {
|
||||||
PermissionConditionOperators,
|
PermissionConditionOperators,
|
||||||
|
ProjectPermissionApprovalActions,
|
||||||
ProjectPermissionDynamicSecretActions,
|
ProjectPermissionDynamicSecretActions,
|
||||||
ProjectPermissionGroupActions,
|
ProjectPermissionGroupActions,
|
||||||
ProjectPermissionIdentityActions,
|
ProjectPermissionIdentityActions,
|
||||||
@@ -52,6 +53,14 @@ const SecretPolicyActionSchema = z.object({
|
|||||||
[ProjectPermissionSecretActions.Create]: z.boolean().optional()
|
[ProjectPermissionSecretActions.Create]: z.boolean().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ApprovalPolicyActionSchema = z.object({
|
||||||
|
[ProjectPermissionApprovalActions.Read]: z.boolean().optional(),
|
||||||
|
[ProjectPermissionApprovalActions.Edit]: z.boolean().optional(),
|
||||||
|
[ProjectPermissionApprovalActions.Delete]: z.boolean().optional(),
|
||||||
|
[ProjectPermissionApprovalActions.Create]: z.boolean().optional(),
|
||||||
|
[ProjectPermissionApprovalActions.AllowChangeBypass]: z.boolean().optional()
|
||||||
|
});
|
||||||
|
|
||||||
const CmekPolicyActionSchema = z.object({
|
const CmekPolicyActionSchema = z.object({
|
||||||
read: z.boolean().optional(),
|
read: z.boolean().optional(),
|
||||||
edit: z.boolean().optional(),
|
edit: z.boolean().optional(),
|
||||||
@@ -261,7 +270,7 @@ export const projectRoleFormSchema = z.object({
|
|||||||
.array()
|
.array()
|
||||||
.default([]),
|
.default([]),
|
||||||
[ProjectPermissionSub.SshHostGroups]: GeneralPolicyActionSchema.array().default([]),
|
[ProjectPermissionSub.SshHostGroups]: GeneralPolicyActionSchema.array().default([]),
|
||||||
[ProjectPermissionSub.SecretApproval]: GeneralPolicyActionSchema.array().default([]),
|
[ProjectPermissionSub.SecretApproval]: ApprovalPolicyActionSchema.array().default([]),
|
||||||
[ProjectPermissionSub.SecretRollback]: SecretRollbackPolicyActionSchema.array().default([]),
|
[ProjectPermissionSub.SecretRollback]: SecretRollbackPolicyActionSchema.array().default([]),
|
||||||
[ProjectPermissionSub.Project]: WorkspacePolicyActionSchema.array().default([]),
|
[ProjectPermissionSub.Project]: WorkspacePolicyActionSchema.array().default([]),
|
||||||
[ProjectPermissionSub.Tags]: GeneralPolicyActionSchema.array().default([]),
|
[ProjectPermissionSub.Tags]: GeneralPolicyActionSchema.array().default([]),
|
||||||
@@ -402,7 +411,6 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
ProjectPermissionSub.PkiAlerts,
|
ProjectPermissionSub.PkiAlerts,
|
||||||
ProjectPermissionSub.PkiCollections,
|
ProjectPermissionSub.PkiCollections,
|
||||||
ProjectPermissionSub.CertificateTemplates,
|
ProjectPermissionSub.CertificateTemplates,
|
||||||
ProjectPermissionSub.SecretApproval,
|
|
||||||
ProjectPermissionSub.Tags,
|
ProjectPermissionSub.Tags,
|
||||||
ProjectPermissionSub.SecretRotation,
|
ProjectPermissionSub.SecretRotation,
|
||||||
ProjectPermissionSub.Kms,
|
ProjectPermissionSub.Kms,
|
||||||
@@ -564,6 +572,25 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (subject === ProjectPermissionSub.SecretApproval) {
|
||||||
|
const canCreate = action.includes(ProjectPermissionApprovalActions.Create);
|
||||||
|
const canDelete = action.includes(ProjectPermissionApprovalActions.Delete);
|
||||||
|
const canEdit = action.includes(ProjectPermissionApprovalActions.Edit);
|
||||||
|
const canRead = action.includes(ProjectPermissionApprovalActions.Read);
|
||||||
|
const canChangeBypass = action.includes(ProjectPermissionApprovalActions.AllowChangeBypass);
|
||||||
|
|
||||||
|
if (!formVal[subject]) formVal[subject] = [{}];
|
||||||
|
|
||||||
|
// Map actions to the keys defined in ApprovalPolicyActionSchema
|
||||||
|
if (canCreate) formVal[subject]![0][ProjectPermissionApprovalActions.Create] = true;
|
||||||
|
if (canDelete) formVal[subject]![0][ProjectPermissionApprovalActions.Delete] = true;
|
||||||
|
if (canEdit) formVal[subject]![0][ProjectPermissionApprovalActions.Edit] = true;
|
||||||
|
if (canRead) formVal[subject]![0][ProjectPermissionApprovalActions.Read] = true;
|
||||||
|
if (canChangeBypass)
|
||||||
|
formVal[subject]![0][ProjectPermissionApprovalActions.AllowChangeBypass] = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (subject === ProjectPermissionSub.SecretRollback) {
|
if (subject === ProjectPermissionSub.SecretRollback) {
|
||||||
const canRead = action.includes(ProjectPermissionActions.Read);
|
const canRead = action.includes(ProjectPermissionActions.Read);
|
||||||
const canCreate = action.includes(ProjectPermissionActions.Create);
|
const canCreate = action.includes(ProjectPermissionActions.Create);
|
||||||
@@ -1181,10 +1208,11 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
|||||||
[ProjectPermissionSub.SecretApproval]: {
|
[ProjectPermissionSub.SecretApproval]: {
|
||||||
title: "Secret Approval Policies",
|
title: "Secret Approval Policies",
|
||||||
actions: [
|
actions: [
|
||||||
{ label: "Read", value: "read" },
|
{ label: "Read", value: ProjectPermissionApprovalActions.Read },
|
||||||
{ label: "Create", value: "create" },
|
{ label: "Create", value: ProjectPermissionApprovalActions.Create },
|
||||||
{ label: "Modify", value: "edit" },
|
{ label: "Modify", value: ProjectPermissionApprovalActions.Edit },
|
||||||
{ label: "Remove", value: "delete" }
|
{ label: "Remove", value: ProjectPermissionApprovalActions.Delete },
|
||||||
|
{ label: "Allow Change Bypass", value: ProjectPermissionApprovalActions.AllowChangeBypass }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
[ProjectPermissionSub.SecretRotation]: {
|
[ProjectPermissionSub.SecretRotation]: {
|
||||||
@@ -1661,7 +1689,7 @@ export const RoleTemplates: Record<ProjectType, RoleTemplate[]> = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
subject: ProjectPermissionSub.SecretApproval,
|
subject: ProjectPermissionSub.SecretApproval,
|
||||||
actions: Object.values(ProjectPermissionActions)
|
actions: Object.values(ProjectPermissionApprovalActions)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
subject: ProjectPermissionSub.ServiceTokens,
|
subject: ProjectPermissionSub.ServiceTokens,
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import { TAccessApprovalPolicy, Workspace } from "@app/hooks/api/types";
|
|||||||
|
|
||||||
import { AccessPolicyForm } from "./components/AccessPolicyModal";
|
import { AccessPolicyForm } from "./components/AccessPolicyModal";
|
||||||
import { ApprovalPolicyRow } from "./components/ApprovalPolicyRow";
|
import { ApprovalPolicyRow } from "./components/ApprovalPolicyRow";
|
||||||
|
import { ProjectPermissionApprovalActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
@@ -61,8 +62,10 @@ const useApprovalPolicies = (permission: TProjectPermission, currentWorkspace?:
|
|||||||
projectSlug: currentWorkspace?.slug as string,
|
projectSlug: currentWorkspace?.slug as string,
|
||||||
options: {
|
options: {
|
||||||
enabled:
|
enabled:
|
||||||
permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval) &&
|
permission.can(
|
||||||
!!currentWorkspace?.slug
|
ProjectPermissionApprovalActions.Read,
|
||||||
|
ProjectPermissionSub.SecretApproval
|
||||||
|
) && !!currentWorkspace?.slug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -71,8 +74,10 @@ const useApprovalPolicies = (permission: TProjectPermission, currentWorkspace?:
|
|||||||
workspaceId: currentWorkspace?.id as string,
|
workspaceId: currentWorkspace?.id as string,
|
||||||
options: {
|
options: {
|
||||||
enabled:
|
enabled:
|
||||||
permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval) &&
|
permission.can(
|
||||||
!!currentWorkspace?.id
|
ProjectPermissionApprovalActions.Read,
|
||||||
|
ProjectPermissionSub.SecretApproval
|
||||||
|
) && !!currentWorkspace?.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -160,7 +165,7 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Create}
|
I={ProjectPermissionApprovalActions.Create}
|
||||||
a={ProjectPermissionSub.SecretApproval}
|
a={ProjectPermissionSub.SecretApproval}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
|
|||||||
@@ -303,73 +303,6 @@ export const AccessPolicyForm = ({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="enforcementLevel"
|
|
||||||
defaultValue={EnforcementLevel.Hard}
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Enforcement Level"
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText={error?.message}
|
|
||||||
tooltipText={
|
|
||||||
<>
|
|
||||||
<p>
|
|
||||||
Determines the level of enforcement for required approvers of a request:
|
|
||||||
</p>
|
|
||||||
<p className="mt-2">
|
|
||||||
<span className="font-bold">Hard</span> enforcement requires at least{" "}
|
|
||||||
<span className="font-bold"> {approversRequired}</span> approver(s) to
|
|
||||||
approve the request.`
|
|
||||||
</p>
|
|
||||||
<p className="mt-2">
|
|
||||||
<span className="font-bold">Soft</span> enforcement At least{" "}
|
|
||||||
<span className="font-bold">{approversRequired}</span> approver(s) must
|
|
||||||
approve the request; however, the requester can bypass approval
|
|
||||||
requirements in emergencies.
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
value={field.value}
|
|
||||||
onValueChange={(val) => field.onChange(val as EnforcementLevel)}
|
|
||||||
className="w-full border border-mineshaft-500"
|
|
||||||
>
|
|
||||||
{Object.values(EnforcementLevel).map((level) => {
|
|
||||||
return (
|
|
||||||
<SelectItem value={level} key={`enforcement-level-${level}`}>
|
|
||||||
<span className="capitalize">{level}</span>
|
|
||||||
</SelectItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="environment"
|
|
||||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Environment"
|
|
||||||
isRequired
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText={error?.message}
|
|
||||||
>
|
|
||||||
<FilterableSelect
|
|
||||||
isDisabled={isEditMode}
|
|
||||||
value={value}
|
|
||||||
onChange={onChange}
|
|
||||||
placeholder="Select environment..."
|
|
||||||
options={environments}
|
|
||||||
getOptionValue={(option) => option.slug}
|
|
||||||
getOptionLabel={(option) => option.name}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="secretPath"
|
name="secretPath"
|
||||||
@@ -386,6 +319,28 @@ export const AccessPolicyForm = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="environment"
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Environment"
|
||||||
|
isRequired
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<FilterableSelect
|
||||||
|
isDisabled={isEditMode}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
placeholder="Select environment..."
|
||||||
|
options={environments}
|
||||||
|
getOptionValue={(option) => option.slug}
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<p>Approvers</p>
|
<p>Approvers</p>
|
||||||
<p className="font-inter text-xs text-mineshaft-300 opacity-90">
|
<p className="font-inter text-xs text-mineshaft-300 opacity-90">
|
||||||
@@ -465,6 +420,29 @@ export const AccessPolicyForm = ({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="enforcementLevel"
|
||||||
|
defaultValue={EnforcementLevel.Hard}
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Bypass Approvals"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
id="self-approvals"
|
||||||
|
thumbClassName="bg-mineshaft-800"
|
||||||
|
isChecked={value === EnforcementLevel.Soft}
|
||||||
|
onCheckedChange={(v) =>
|
||||||
|
onChange(v ? EnforcementLevel.Soft : EnforcementLevel.Hard)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Allow request creators to bypass policy in break-glass situations
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<div className="mt-8 flex items-center space-x-4">
|
<div className="mt-8 flex items-center space-x-4">
|
||||||
<Button type="submit" isLoading={isSubmitting} isDisabled={isSubmitting}>
|
<Button type="submit" isLoading={isSubmitting} isDisabled={isSubmitting}>
|
||||||
Save
|
Save
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { EnforcementLevel, PolicyType } from "@app/hooks/api/policies/enums";
|
|||||||
import { ApproverType } from "@app/hooks/api/secretApproval/types";
|
import { ApproverType } from "@app/hooks/api/secretApproval/types";
|
||||||
import { WorkspaceEnv } from "@app/hooks/api/types";
|
import { WorkspaceEnv } from "@app/hooks/api/types";
|
||||||
import { TWorkspaceUser } from "@app/hooks/api/users/types";
|
import { TWorkspaceUser } from "@app/hooks/api/users/types";
|
||||||
|
import { ProjectPermissionApprovalActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
|
|
||||||
interface IPolicy {
|
interface IPolicy {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -117,7 +118,7 @@ export const ApprovalPolicyRow = ({
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="center" className="min-w-[100%] p-1">
|
<DropdownMenuContent align="center" className="min-w-[100%] p-1">
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Edit}
|
I={ProjectPermissionApprovalActions.Edit}
|
||||||
a={ProjectPermissionSub.SecretApproval}
|
a={ProjectPermissionSub.SecretApproval}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
@@ -136,7 +137,7 @@ export const ApprovalPolicyRow = ({
|
|||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Delete}
|
I={ProjectPermissionApprovalActions.Delete}
|
||||||
a={ProjectPermissionSub.SecretApproval}
|
a={ProjectPermissionSub.SecretApproval}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ import {
|
|||||||
usePerformSecretApprovalRequestMerge,
|
usePerformSecretApprovalRequestMerge,
|
||||||
useUpdateSecretApprovalRequestStatus
|
useUpdateSecretApprovalRequestStatus
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
|
import {
|
||||||
|
ProjectPermissionApprovalActions,
|
||||||
|
ProjectPermissionSub,
|
||||||
|
useProjectPermission
|
||||||
|
} from "@app/context";
|
||||||
import { EnforcementLevel } from "@app/hooks/api/policies/enums";
|
import { EnforcementLevel } from "@app/hooks/api/policies/enums";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -48,6 +53,12 @@ export const SecretApprovalRequestAction = ({
|
|||||||
|
|
||||||
const { mutateAsync: updateSecretStatusChange, isPending: isStatusChanging } =
|
const { mutateAsync: updateSecretStatusChange, isPending: isStatusChanging } =
|
||||||
useUpdateSecretApprovalRequestStatus();
|
useUpdateSecretApprovalRequestStatus();
|
||||||
|
|
||||||
|
const { permission } = useProjectPermission();
|
||||||
|
const canBypassApprovalPermission = permission.can(
|
||||||
|
ProjectPermissionApprovalActions.AllowChangeBypass,
|
||||||
|
ProjectPermissionSub.SecretApproval
|
||||||
|
);
|
||||||
|
|
||||||
const [byPassApproval, setByPassApproval] = useState(false);
|
const [byPassApproval, setByPassApproval] = useState(false);
|
||||||
const [bypassReason, setBypassReason] = useState("");
|
const [bypassReason, setBypassReason] = useState("");
|
||||||
@@ -113,7 +124,7 @@ export const SecretApprovalRequestAction = ({
|
|||||||
At least {approvals} approving review required
|
At least {approvals} approving review required
|
||||||
{Boolean(statusChangeByEmail) && `. Reopened by ${statusChangeByEmail}`}
|
{Boolean(statusChangeByEmail) && `. Reopened by ${statusChangeByEmail}`}
|
||||||
</span>
|
</span>
|
||||||
{isSoftEnforcement && !isMergable && (
|
{isSoftEnforcement && !isMergable && canBypassApprovalPermission && (
|
||||||
<div className="mt-2 flex flex-col space-y-2">
|
<div className="mt-2 flex flex-col space-y-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
onCheckedChange={(checked) => setByPassApproval(checked === true)}
|
onCheckedChange={(checked) => setByPassApproval(checked === true)}
|
||||||
|
|||||||
Reference in New Issue
Block a user