feat(secret-approval): implemented backend api for secret policies

This commit is contained in:
Akhil Mohan
2023-09-27 23:10:28 +05:30
parent 9cda85f03e
commit edeb6bbc66
8 changed files with 202 additions and 3 deletions

View File

@@ -16,6 +16,7 @@ import * as workspaceController from "./workspaceController";
import * as secretScanningController from "./secretScanningController";
import * as webhookController from "./webhookController";
import * as secretImpsController from "./secretImpsController";
import * as secretApprovalController from "./secretApprovalController";
export {
authController,
@@ -35,5 +36,6 @@ export {
workspaceController,
secretScanningController,
webhookController,
secretImpsController
secretImpsController,
secretApprovalController
};

View File

@@ -0,0 +1,109 @@
import { ForbiddenError } from "@casl/ability";
import { Request, Response } from "express";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { validateRequest } from "../../helpers/validation";
import { SecretApproval } from "../../models/secretApproval";
import { BadRequestError } from "../../utils/errors";
import * as reqValidator from "../../validation/secretApproval";
const ERR_SECRET_APPROVAL_NOT_FOUND = BadRequestError({ message: "secret approval not found" });
export const createSecretApprovalRule = async (req: Request, res: Response) => {
const {
body: { approvals, secretPath, approvers, environment, workspaceId }
} = await validateRequest(reqValidator.CreateSecretApprovalRule, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretApproval
);
const secretApproval = new SecretApproval({
workspace: workspaceId,
secretPath,
environment,
approvals,
approvers
});
await secretApproval.save();
return res.send({
approval: secretApproval
});
};
export const updateSecretApprovalRule = async (req: Request, res: Response) => {
const {
body: { approvals, approvers, secretPath },
params: { id }
} = await validateRequest(reqValidator.UpdateSecretApprovalRule, req);
const secretApproval = await SecretApproval.findById(id);
if (!secretApproval) throw ERR_SECRET_APPROVAL_NOT_FOUND;
const { permission } = await getUserProjectPermissions(
req.user._id,
secretApproval.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.SecretApproval
);
const updatedDoc = await SecretApproval.findByIdAndUpdate(id, {
approvals,
approvers,
$set: secretPath === "-" ? undefined : { secretPath }
});
return res.send({
approval: updatedDoc
});
};
export const deleteSecretApprovalRule = async (req: Request, res: Response) => {
const {
params: { id }
} = await validateRequest(reqValidator.DeleteSecretApprovalRule, req);
const secretApproval = await SecretApproval.findById(id);
if (!secretApproval) throw ERR_SECRET_APPROVAL_NOT_FOUND;
const { permission } = await getUserProjectPermissions(
req.user._id,
secretApproval.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretApproval
);
const deletedDoc = await SecretApproval.findByIdAndDelete(id);
return res.send({
approval: deletedDoc
});
};
export const getSecretApprovalRules = async (req: Request, res: Response) => {
const {
query: { workspaceId }
} = await validateRequest(reqValidator.GetSecretApprovalRuleList, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretApproval
);
const doc = await SecretApproval.find({ workspace: workspaceId });
return res.send({
approvals: doc
});
};

View File

@@ -49,7 +49,8 @@ export enum ProjectPermissionSub {
IpAllowList = "ip-allowlist",
Workspace = "workspace",
Secrets = "secrets",
SecretRollback = "secret-rollback"
SecretRollback = "secret-rollback",
SecretApproval = "secret-approval"
}
type SubjectFields = {
@@ -72,6 +73,7 @@ export type ProjectPermissionSet =
| [ProjectPermissionActions, ProjectPermissionSub.IpAllowList]
| [ProjectPermissionActions, ProjectPermissionSub.Settings]
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
| [ProjectPermissionActions, ProjectPermissionSub.SecretApproval]
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Workspace]
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Workspace]
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
@@ -85,6 +87,11 @@ const buildAdminPermission = () => {
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback);
@@ -154,6 +161,8 @@ const buildMemberPermission = () => {
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback);
@@ -203,6 +212,7 @@ const buildViewerPermission = () => {
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);

View File

@@ -38,6 +38,7 @@ import {
membership as v1MembershipRouter,
organization as v1OrganizationRouter,
password as v1PasswordRouter,
secretApproval as v1SecretApproval,
secretImps as v1SecretImpsRouter,
secret as v1SecretRouter,
secretsFolder as v1SecretsFolder,
@@ -176,6 +177,7 @@ const main = async () => {
app.use("/api/v1/webhooks", v1WebhooksRouter);
app.use("/api/v1/secret-imports", v1SecretImpsRouter);
app.use("/api/v1/roles", v1RoleRouter);
app.use("/api/v1/secret-approvals", v1SecretApproval);
// v2 routes (improvements)
app.use("/api/v2/signup", v2SignupRouter);

View File

@@ -17,6 +17,7 @@ import integrationAuth from "./integrationAuth";
import secretsFolder from "./secretsFolder";
import webhooks from "./webhook";
import secretImps from "./secretImps";
import secretApproval from "./secretApproval";
export {
signup,
@@ -37,5 +38,6 @@ export {
integrationAuth,
secretsFolder,
webhooks,
secretImps
secretImps,
secretApproval
};

View File

@@ -0,0 +1,39 @@
import express from "express";
const router = express.Router();
import { requireAuth } from "../../middleware";
import { secretApprovalController } from "../../controllers/v1";
import { AuthMode } from "../../variables";
router.get(
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
secretApprovalController.getSecretApprovalRules
);
router.post(
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
secretApprovalController.createSecretApprovalRule
);
router.patch(
"/:id",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
secretApprovalController.updateSecretApprovalRule
);
router.delete(
"/:id",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
secretApprovalController.deleteSecretApprovalRule
);
export default router;

View File

@@ -1,3 +1,4 @@
export * from "./secretApproval";
export * from "./user";
export * from "./workspace";
export * from "./bot";

View File

@@ -0,0 +1,34 @@
import { z } from "zod";
export const GetSecretApprovalRuleList = z.object({
query: z.object({
workspaceId: z.string()
})
});
export const CreateSecretApprovalRule = z.object({
body: z.object({
workspaceId: z.string(),
environment: z.string(),
secretPath: z.string().optional(),
approvers: z.string().array().optional(),
approvals: z.number().min(1).default(1)
})
});
export const UpdateSecretApprovalRule = z.object({
params: z.object({
id: z.string()
}),
body: z.object({
approvers: z.string().array().optional(),
approvals: z.number().min(1).optional(),
secretPath: z.string().optional()
})
});
export const DeleteSecretApprovalRule = z.object({
params: z.object({
id: z.string()
})
});