mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Update authz logic for MI
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
IMachineIdentity,
|
||||
Key,
|
||||
MachineIdentity,
|
||||
MachineMembership,
|
||||
@@ -8,7 +9,7 @@ import {
|
||||
ServiceTokenData,
|
||||
Workspace
|
||||
} from "../../models";
|
||||
import { Role } from "../../ee/models";
|
||||
import { IRole, Role } from "../../ee/models";
|
||||
import {
|
||||
pullSecrets as pull,
|
||||
v2PushSecrets as push,
|
||||
@@ -534,24 +535,26 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
|
||||
});
|
||||
|
||||
if (machineMembership) throw BadRequestError({
|
||||
message: `Machine identity with id ${machineId} already exists in workspace with id ${workspaceId}`
|
||||
message: `Machine identity with id ${machineId} already exists in project with id ${workspaceId}`
|
||||
});
|
||||
|
||||
const machineIdentity = await MachineIdentity.findById(machineId);
|
||||
if (!machineIdentity) throw ResourceNotFoundError();
|
||||
if (!machineIdentity) throw ResourceNotFoundError({
|
||||
message: `Failed to find machine identity with id ${machineId}`
|
||||
});
|
||||
|
||||
const workspace = await Workspace.findById(workspaceId);
|
||||
if (!workspace) throw ResourceNotFoundError();
|
||||
|
||||
if (!machineIdentity.organization.equals(workspace.organization)) throw BadRequestError({
|
||||
message: "Failed to add machine identity to workspace in another organization"
|
||||
message: "Failed to add machine identity to project in another organization"
|
||||
});
|
||||
|
||||
const rolePermission = await getRolePermissions(role, workspaceId);
|
||||
const hasRequiredPrivileges = isAtLeastAsPrivilegedWorkspace(permission, rolePermission);
|
||||
const isAsPrivilegedAsIntendedRole = isAtLeastAsPrivilegedWorkspace(permission, rolePermission);
|
||||
|
||||
if (!hasRequiredPrivileges) throw ForbiddenRequestError({
|
||||
message: "Failed to add a more privileged MI to project"
|
||||
if (!isAsPrivilegedAsIntendedRole) throw ForbiddenRequestError({
|
||||
message: "Failed to add MI to project with more privileged role"
|
||||
});
|
||||
|
||||
let customRole;
|
||||
@@ -604,29 +607,33 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
|
||||
ProjectPermissionSub.MachineIdentity
|
||||
);
|
||||
|
||||
let machineMembership = await MachineMembership.findOne({
|
||||
machineIdentity: new Types.ObjectId(machineId),
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
let machineMembership = await MachineMembership
|
||||
.findOne({
|
||||
machineIdentity: new Types.ObjectId(machineId),
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
})
|
||||
.populate<{
|
||||
machineIdentity: IMachineIdentity,
|
||||
customRole: IRole
|
||||
}>("machineIdentity customRole");
|
||||
|
||||
if (!machineMembership) throw BadRequestError({
|
||||
message: `Machine identity with id ${machineId} does not exist in workspace with id ${workspaceId}`
|
||||
message: `Machine identity with id ${machineId} does not exist in project with id ${workspaceId}`
|
||||
});
|
||||
|
||||
const machineIdentity = await MachineIdentity.findById(machineId);
|
||||
if (!machineIdentity) throw ResourceNotFoundError();
|
||||
|
||||
const workspace = await Workspace.findById(workspaceId);
|
||||
if (!workspace) throw ResourceNotFoundError();
|
||||
|
||||
if (!machineIdentity.organization.equals(workspace.organization)) throw BadRequestError({
|
||||
message: "Failed to update machine identity in workspace in another organization"
|
||||
const machineIdentityRolePermission = await getRolePermissions(
|
||||
machineMembership?.customRole?.slug ?? machineMembership.role,
|
||||
machineMembership.workspace.toString()
|
||||
);
|
||||
const isAsPrivilegedAsMachine = isAtLeastAsPrivilegedWorkspace(permission, machineIdentityRolePermission);
|
||||
if (!isAsPrivilegedAsMachine) throw ForbiddenRequestError({
|
||||
message: "Failed to update role of more privileged MI"
|
||||
});
|
||||
|
||||
const rolePermission = await getRolePermissions(role, workspaceId);
|
||||
const hasRequiredPrivileges = isAtLeastAsPrivilegedWorkspace(permission, rolePermission);
|
||||
const isAsPrivilegedAsIntendedRole = isAtLeastAsPrivilegedWorkspace(permission, rolePermission);
|
||||
|
||||
if (!hasRequiredPrivileges) throw ForbiddenRequestError({
|
||||
if (!isAsPrivilegedAsIntendedRole) throw ForbiddenRequestError({
|
||||
message: "Failed to update MI to a more privileged role"
|
||||
});
|
||||
|
||||
@@ -646,7 +653,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
|
||||
|
||||
machineMembership = await MachineMembership.findOneAndUpdate(
|
||||
{
|
||||
machineIdentity: machineIdentity._id,
|
||||
machineIdentity: machineMembership.machineIdentity,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
},
|
||||
{
|
||||
@@ -684,12 +691,30 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
|
||||
ProjectPermissionSub.MachineIdentity
|
||||
);
|
||||
|
||||
const machineMembership = await MachineMembership.findOneAndDelete({
|
||||
machineIdentity: new Types.ObjectId(machineId),
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
const machineMembership = await MachineMembership
|
||||
.findOne({
|
||||
machineIdentity: new Types.ObjectId(machineId),
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
})
|
||||
.populate<{
|
||||
machineIdentity: IMachineIdentity,
|
||||
customRole: IRole
|
||||
}>("machineIdentity customRole");
|
||||
|
||||
if (!machineMembership) throw ResourceNotFoundError({
|
||||
message: `Machine with id ${machineId} does not exist in project with id ${workspaceId}`
|
||||
});
|
||||
|
||||
if (!machineMembership) throw ResourceNotFoundError();
|
||||
const machineIdentityRolePermission = await getRolePermissions(
|
||||
machineMembership?.customRole?.slug ?? machineMembership.role,
|
||||
machineMembership.workspace.toString()
|
||||
);
|
||||
const isAsPrivilegedAsMachine = isAtLeastAsPrivilegedWorkspace(permission, machineIdentityRolePermission);
|
||||
if (!isAsPrivilegedAsMachine) throw ForbiddenRequestError({
|
||||
message: "Failed to remove more privileged MI from project"
|
||||
});
|
||||
|
||||
await MachineMembership.findByIdAndDelete(machineMembership._id);
|
||||
|
||||
return res.status(200).send({
|
||||
machineMembership
|
||||
|
||||
@@ -4,10 +4,10 @@ import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
IMachineIdentity,
|
||||
IMachineIdentityClientSecretData,
|
||||
IMachineIdentityClientSecret,
|
||||
IMachineIdentityTrustedIp,
|
||||
MachineIdentity,
|
||||
MachineIdentityClientSecretData,
|
||||
MachineIdentityClientSecret,
|
||||
MachineMembership,
|
||||
MachineMembershipOrg,
|
||||
Organization,
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import {
|
||||
ActorType,
|
||||
EventType,
|
||||
IRole,
|
||||
Role
|
||||
} from "../../models";
|
||||
import { validateRequest } from "../../../helpers/validation";
|
||||
@@ -25,7 +26,12 @@ import {
|
||||
getUserOrgPermissions,
|
||||
isAtLeastAsPrivilegedOrg
|
||||
} from "../../services/RoleService";
|
||||
import { BadRequestError, ForbiddenRequestError, ResourceNotFoundError, UnauthorizedRequestError } from "../../../utils/errors";
|
||||
import {
|
||||
BadRequestError,
|
||||
ForbiddenRequestError,
|
||||
ResourceNotFoundError,
|
||||
UnauthorizedRequestError
|
||||
} from "../../../utils/errors";
|
||||
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
|
||||
import { EEAuditLogService, EELicenseService } from "../../services";
|
||||
import { getAuthSecret, getSaltRounds } from "../../../config";
|
||||
@@ -38,15 +44,15 @@ import { ForbiddenError } from "@casl/ability";
|
||||
import { checkIPAgainstBlocklist } from "../../../utils/ip";
|
||||
import { getUserAgentType } from "../../../utils/posthog";
|
||||
|
||||
const packageClientSecretData = (clientSecretData: IMachineIdentityClientSecretData) => ({
|
||||
_id: clientSecretData._id,
|
||||
machineIdentity: clientSecretData.machineIdentity,
|
||||
isActive: clientSecretData.isActive,
|
||||
description: clientSecretData.description,
|
||||
clientSecretPrefix: clientSecretData.clientSecretPrefix,
|
||||
clientSecretNumUses: clientSecretData.clientSecretNumUses,
|
||||
clientSecretNumUsesLimit: clientSecretData.clientSecretNumUsesLimit,
|
||||
clientSecretTTL: clientSecretData.clientSecretTTL
|
||||
const packageClientSecretData = (machineIdentityClientSecret: IMachineIdentityClientSecret) => ({
|
||||
_id: machineIdentityClientSecret._id,
|
||||
machineIdentity: machineIdentityClientSecret.machineIdentity,
|
||||
isActive: machineIdentityClientSecret.isActive,
|
||||
description: machineIdentityClientSecret.description,
|
||||
clientSecretPrefix: machineIdentityClientSecret.clientSecretPrefix,
|
||||
clientSecretNumUses: machineIdentityClientSecret.clientSecretNumUses,
|
||||
clientSecretNumUsesLimit: machineIdentityClientSecret.clientSecretNumUsesLimit,
|
||||
clientSecretTTL: machineIdentityClientSecret.clientSecretTTL
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -63,7 +69,10 @@ export const getMIClientSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
const machineMembershipOrg = await MachineMembershipOrg.findOne({
|
||||
machineIdentity: new Types.ObjectId(machineId)
|
||||
}).populate<{ machineIdentity: IMachineIdentity }>("machineIdentity");
|
||||
}).populate<{
|
||||
machineIdentity: IMachineIdentity,
|
||||
customRole: IRole
|
||||
}>("machineIdentity customRole");
|
||||
|
||||
if (!machineMembershipOrg) throw ResourceNotFoundError();
|
||||
|
||||
@@ -74,14 +83,17 @@ export const getMIClientSecrets = async (req: Request, res: Response) => {
|
||||
OrgPermissionSubjects.MachineIdentity
|
||||
);
|
||||
|
||||
const rolePermission = await getOrgRolePermissions(machineMembershipOrg.role, machineMembershipOrg.organization.toString());
|
||||
const rolePermission = await getOrgRolePermissions(
|
||||
machineMembershipOrg?.customRole?.slug ?? machineMembershipOrg.role,
|
||||
machineMembershipOrg.organization.toString()
|
||||
);
|
||||
const hasRequiredPrivileges = isAtLeastAsPrivilegedOrg(permission, rolePermission);
|
||||
|
||||
if (!hasRequiredPrivileges) throw ForbiddenRequestError({
|
||||
message: "Failed to get client secrets for more privileged MI"
|
||||
});
|
||||
|
||||
const clientSecretData = await MachineIdentityClientSecretData
|
||||
const clientSecretData = await MachineIdentityClientSecret
|
||||
.find({
|
||||
machineIdentity: machineMembershipOrg.machineIdentity,
|
||||
isActive: true
|
||||
@@ -127,7 +139,10 @@ export const createMIClientSecret = async (req: Request, res: Response) => {
|
||||
|
||||
const machineMembershipOrg = await MachineMembershipOrg.findOne({
|
||||
machineIdentity: new Types.ObjectId(machineId)
|
||||
}).populate<{ machineIdentity: IMachineIdentity }>("machineIdentity");
|
||||
}).populate<{
|
||||
machineIdentity: IMachineIdentity,
|
||||
customRole: IRole
|
||||
}>("machineIdentity customRole");
|
||||
|
||||
if (!machineMembershipOrg) throw ResourceNotFoundError();
|
||||
|
||||
@@ -138,7 +153,10 @@ export const createMIClientSecret = async (req: Request, res: Response) => {
|
||||
OrgPermissionSubjects.MachineIdentity
|
||||
);
|
||||
|
||||
const rolePermission = await getOrgRolePermissions(machineMembershipOrg.role, machineMembershipOrg.organization.toString());
|
||||
const rolePermission = await getOrgRolePermissions(
|
||||
machineMembershipOrg?.customRole?.slug ?? machineMembershipOrg.role,
|
||||
machineMembershipOrg.organization.toString()
|
||||
);
|
||||
const hasRequiredPrivileges = isAtLeastAsPrivilegedOrg(permission, rolePermission);
|
||||
|
||||
if (!hasRequiredPrivileges) throw ForbiddenRequestError({
|
||||
@@ -148,7 +166,7 @@ export const createMIClientSecret = async (req: Request, res: Response) => {
|
||||
const clientSecret = crypto.randomBytes(32).toString("hex");
|
||||
const clientSecretHash = await bcrypt.hash(clientSecret, await getSaltRounds());
|
||||
|
||||
const machineIdentityClientSecretData = await new MachineIdentityClientSecretData({
|
||||
const machineIdentityClientSecret = await new MachineIdentityClientSecret({
|
||||
machineIdentity: machineMembershipOrg.machineIdentity,
|
||||
isActive: true,
|
||||
description,
|
||||
@@ -167,7 +185,7 @@ export const createMIClientSecret = async (req: Request, res: Response) => {
|
||||
metadata: {
|
||||
machineId: machineMembershipOrg.machineIdentity._id.toString(),
|
||||
clientId: machineMembershipOrg.machineIdentity.clientId,
|
||||
clientSecretId: machineIdentityClientSecretData._id.toString()
|
||||
clientSecretId: machineIdentityClientSecret._id.toString()
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -177,7 +195,7 @@ export const createMIClientSecret = async (req: Request, res: Response) => {
|
||||
|
||||
return res.status(200).send({
|
||||
clientSecret,
|
||||
clientSecretData: packageClientSecretData(machineIdentityClientSecretData)
|
||||
clientSecretData: packageClientSecretData(machineIdentityClientSecret)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -194,11 +212,18 @@ export const deleteMIClientSecret = async (req: Request, res: Response) => {
|
||||
}
|
||||
} = await validateRequest(reqValidator.DeleteClientSecretV3, req);
|
||||
|
||||
const machineMembershipOrg = await MachineMembershipOrg.findOne({
|
||||
machineIdentity: new Types.ObjectId(machineId)
|
||||
}).populate<{ machineIdentity: IMachineIdentity }>("machineIdentity");
|
||||
const machineMembershipOrg = await MachineMembershipOrg
|
||||
.findOne({
|
||||
machineIdentity: new Types.ObjectId(machineId)
|
||||
})
|
||||
.populate<{
|
||||
machineIdentity: IMachineIdentity,
|
||||
customRole: IRole
|
||||
}>("machineIdentity customRole");
|
||||
|
||||
if (!machineMembershipOrg) throw ResourceNotFoundError();
|
||||
if (!machineMembershipOrg) throw ResourceNotFoundError({
|
||||
message: `Failed to find machine identity with id ${machineId}`
|
||||
});
|
||||
|
||||
const { permission } = await getUserOrgPermissions(req.user._id, machineMembershipOrg.organization.toString());
|
||||
|
||||
@@ -207,19 +232,22 @@ export const deleteMIClientSecret = async (req: Request, res: Response) => {
|
||||
OrgPermissionSubjects.MachineIdentity
|
||||
);
|
||||
|
||||
const rolePermission = await getOrgRolePermissions(machineMembershipOrg.role, machineMembershipOrg.organization.toString());
|
||||
const rolePermission = await getOrgRolePermissions(
|
||||
machineMembershipOrg?.customRole?.slug ?? machineMembershipOrg.role,
|
||||
machineMembershipOrg.organization.toString()
|
||||
);
|
||||
const hasRequiredPrivileges = isAtLeastAsPrivilegedOrg(permission, rolePermission);
|
||||
|
||||
if (!hasRequiredPrivileges) throw ForbiddenRequestError({
|
||||
message: "Failed to delete client secrets for more privileged MI"
|
||||
});
|
||||
|
||||
const clientSecretData = await MachineIdentityClientSecretData.findOneAndDelete({
|
||||
const machineIdentityClientSecret = await MachineIdentityClientSecret.findOneAndDelete({
|
||||
_id: clientSecretId,
|
||||
machineIdentity: machineId
|
||||
});
|
||||
|
||||
if (!clientSecretData) throw ResourceNotFoundError();
|
||||
if (!machineIdentityClientSecret) throw ResourceNotFoundError();
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
@@ -237,7 +265,7 @@ export const deleteMIClientSecret = async (req: Request, res: Response) => {
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
clientSecretData: packageClientSecretData(clientSecretData)
|
||||
clientSecretData: packageClientSecretData(machineIdentityClientSecret)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -267,12 +295,12 @@ export const loginMI = async (req: Request, res: Response) => {
|
||||
trustedIps: machineIdentity.clientSecretTrustedIps
|
||||
});
|
||||
|
||||
const clientSecretData = await MachineIdentityClientSecretData.find({
|
||||
const clientSecretData = await MachineIdentityClientSecret.find({
|
||||
machineIdentity: machineIdentity._id,
|
||||
isActive: true
|
||||
});
|
||||
|
||||
let validatedClientSecretDatum: IMachineIdentityClientSecretData | undefined;
|
||||
let validatedClientSecretDatum: IMachineIdentityClientSecret | undefined;
|
||||
|
||||
for (const clientSecretDatum of clientSecretData) {
|
||||
const isSecretValid = await bcrypt.compare(
|
||||
@@ -298,13 +326,10 @@ export const loginMI = async (req: Request, res: Response) => {
|
||||
const expiresAt = new Date(new Date().getTime() + clientSecretTTL * 1000);
|
||||
|
||||
if (expiresAt < new Date()) {
|
||||
await MachineIdentityClientSecretData.findByIdAndUpdate(
|
||||
await MachineIdentityClientSecret.findByIdAndUpdate(
|
||||
validatedClientSecretDatum._id,
|
||||
{
|
||||
isActive: false
|
||||
},
|
||||
{
|
||||
new: true
|
||||
}
|
||||
);
|
||||
|
||||
@@ -315,7 +340,7 @@ export const loginMI = async (req: Request, res: Response) => {
|
||||
if (clientSecretNumUses > 0 && clientSecretNumUses === clientSecretNumUsesLimit) {
|
||||
// number of times client secret can be used for
|
||||
// a login operation reached
|
||||
await MachineIdentityClientSecretData.findByIdAndUpdate(
|
||||
await MachineIdentityClientSecret.findByIdAndUpdate(
|
||||
validatedClientSecretDatum._id,
|
||||
{
|
||||
isActive: false
|
||||
@@ -329,7 +354,7 @@ export const loginMI = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
// increment usage count by 1
|
||||
await MachineIdentityClientSecretData.findByIdAndUpdate(
|
||||
await MachineIdentityClientSecret.findByIdAndUpdate(
|
||||
validatedClientSecretDatum._id,
|
||||
{
|
||||
$inc: { clientSecretNumUses: 1 }
|
||||
@@ -342,7 +367,7 @@ export const loginMI = async (req: Request, res: Response) => {
|
||||
// token version
|
||||
const accessToken = createToken({
|
||||
payload: {
|
||||
machineId: machineIdentity._id.toString(), // consider changing to clientId and making it more extensible
|
||||
machineId: machineIdentity._id.toString(),
|
||||
clientSecretDataId: validatedClientSecretDatum._id.toString(),
|
||||
authTokenType: AuthTokenType.MACHINE_ACCESS_TOKEN,
|
||||
tokenVersion: validatedClientSecretDatum.accessTokenVersion
|
||||
@@ -525,22 +550,36 @@ export const updateMachineIdentity = async (req: Request, res: Response) => {
|
||||
}
|
||||
} = await validateRequest(reqValidator.UpdateMachineIdentityV3, req);
|
||||
|
||||
let machineIdentity = await MachineIdentity.findById(machineId);
|
||||
if (!machineIdentity) throw ResourceNotFoundError({
|
||||
message: `Machine identity with id ${machineId} not found`
|
||||
const machineMembershipOrg = await MachineMembershipOrg
|
||||
.findOne({
|
||||
machineIdentity: new Types.ObjectId(machineId)
|
||||
})
|
||||
.populate<{
|
||||
machineIdentity: IMachineIdentity,
|
||||
customRole: IRole
|
||||
}>("machineIdentity customRole");
|
||||
|
||||
if (!machineMembershipOrg) throw ResourceNotFoundError({
|
||||
message: `Failed to find machine identity with id ${machineId}`
|
||||
});
|
||||
|
||||
// TODO: validate existing role (if it is currently admin then cant demote it)
|
||||
|
||||
const { permission } = await getUserOrgPermissions(req.user._id, machineIdentity.organization.toString());
|
||||
|
||||
const { permission } = await getUserOrgPermissions(req.user._id, machineMembershipOrg.organization.toString());
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Edit,
|
||||
OrgPermissionSubjects.MachineIdentity
|
||||
);
|
||||
|
||||
const machineIdentityRolePermission = await getOrgRolePermissions(
|
||||
machineMembershipOrg?.customRole?.slug ?? machineMembershipOrg.role,
|
||||
machineMembershipOrg.organization.toString()
|
||||
);
|
||||
const hasRequiredPrivileges = isAtLeastAsPrivilegedOrg(permission, machineIdentityRolePermission);
|
||||
if (!hasRequiredPrivileges) throw ForbiddenRequestError({
|
||||
message: "Failed to update more privileged MI"
|
||||
});
|
||||
|
||||
if (role) {
|
||||
const rolePermission = await getOrgRolePermissions(role, machineIdentity.organization.toString());
|
||||
const rolePermission = await getOrgRolePermissions(role, machineMembershipOrg.organization.toString());
|
||||
const hasRequiredPrivileges = isAtLeastAsPrivilegedOrg(permission, rolePermission);
|
||||
|
||||
if (!hasRequiredPrivileges) throw ForbiddenRequestError({
|
||||
@@ -555,14 +594,14 @@ export const updateMachineIdentity = async (req: Request, res: Response) => {
|
||||
customRole = await Role.findOne({
|
||||
slug: role,
|
||||
isOrgRole: true,
|
||||
organization: machineIdentity.organization
|
||||
organization: machineMembershipOrg.organization
|
||||
});
|
||||
|
||||
if (!customRole) throw BadRequestError({ message: "Role not found" });
|
||||
}
|
||||
}
|
||||
|
||||
const plan = await EELicenseService.getPlan(machineIdentity.organization);
|
||||
const plan = await EELicenseService.getPlan(machineMembershipOrg.organization);
|
||||
|
||||
// validate client secret trusted ips
|
||||
let reformattedClientSecretTrustedIps;
|
||||
@@ -600,7 +639,7 @@ export const updateMachineIdentity = async (req: Request, res: Response) => {
|
||||
});
|
||||
}
|
||||
|
||||
machineIdentity = await MachineIdentity.findByIdAndUpdate(
|
||||
const machineIdentity = await MachineIdentity.findByIdAndUpdate(
|
||||
machineId,
|
||||
{
|
||||
name,
|
||||
@@ -669,38 +708,51 @@ export const deleteMachineIdentity = async (req: Request, res: Response) => {
|
||||
params: { machineId }
|
||||
} = await validateRequest(reqValidator.DeleteMachineIdentityV3, req);
|
||||
|
||||
let machineIdentity = await MachineIdentity.findById(machineId);
|
||||
if (!machineIdentity) throw ResourceNotFoundError({
|
||||
message: `Machine identity with id ${machineId} not found`
|
||||
});
|
||||
|
||||
const { permission } = await getUserOrgPermissions(req.user._id, machineIdentity.organization.toString());
|
||||
const machineMembershipOrg = await MachineMembershipOrg
|
||||
.findOne({
|
||||
machineIdentity: new Types.ObjectId(machineId)
|
||||
})
|
||||
.populate<{
|
||||
machineIdentity: IMachineIdentity,
|
||||
customRole: IRole
|
||||
}>("machineIdentity customRole");
|
||||
|
||||
if (!machineMembershipOrg) throw ResourceNotFoundError({
|
||||
message: `Failed to find machine identity with id ${machineId}`
|
||||
});
|
||||
|
||||
const { permission } = await getUserOrgPermissions(req.user._id, machineMembershipOrg.organization.toString());
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionActions.Delete,
|
||||
OrgPermissionSubjects.MachineIdentity
|
||||
);
|
||||
|
||||
const machineIdentityRolePermission = await getOrgRolePermissions(
|
||||
machineMembershipOrg?.customRole?.slug ?? machineMembershipOrg.role,
|
||||
machineMembershipOrg.organization.toString()
|
||||
);
|
||||
const hasRequiredPrivileges = isAtLeastAsPrivilegedOrg(permission, machineIdentityRolePermission);
|
||||
if (!hasRequiredPrivileges) throw ForbiddenRequestError({
|
||||
message: "Failed to delete more privileged MI"
|
||||
});
|
||||
|
||||
machineIdentity = await MachineIdentity.findByIdAndDelete(machineId);
|
||||
|
||||
if (!machineIdentity) throw BadRequestError({
|
||||
message: "Failed to delete service token"
|
||||
const machineIdentity = await MachineIdentity.findByIdAndDelete(machineMembershipOrg.machineIdentity);
|
||||
if (!machineIdentity) throw ResourceNotFoundError({
|
||||
message: `Machine identity with id ${machineId} not found`
|
||||
});
|
||||
|
||||
const machineMembershipOrg = await MachineMembershipOrg.findOneAndDelete({
|
||||
machineIdentity: machineIdentity._id,
|
||||
});
|
||||
await MachineMembershipOrg.findByIdAndDelete(machineMembershipOrg._id);
|
||||
|
||||
if (!machineMembershipOrg) throw BadRequestError({
|
||||
message: "Failed to delete service token"
|
||||
message: `Failed to delete machine identity with id ${machineId}`
|
||||
});
|
||||
|
||||
await MachineMembership.deleteMany({
|
||||
machineIdentity: machineIdentity._id,
|
||||
machineIdentity: machineMembershipOrg.machineIdentity
|
||||
});
|
||||
|
||||
await MachineIdentityClientSecretData.deleteMany({
|
||||
machineIdentity: machineIdentity._id
|
||||
await MachineIdentityClientSecret.deleteMany({
|
||||
machineIdentity: machineMembershipOrg.machineIdentity
|
||||
});
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
|
||||
@@ -28,7 +28,7 @@ router.delete(
|
||||
machineIdentityController.deleteMIClientSecret
|
||||
);
|
||||
|
||||
// consider moving to /auth/app/login
|
||||
// consider moving to /auth/machine/login
|
||||
router.post(
|
||||
"/login",
|
||||
machineIdentityController.loginMI
|
||||
|
||||
@@ -66,7 +66,7 @@ class EELicenseService {
|
||||
secretVersioning: true,
|
||||
pitRecovery: false,
|
||||
ipAllowlisting: false,
|
||||
rbac: false,
|
||||
rbac: true,
|
||||
customRateLimits: false,
|
||||
customAlerts: false,
|
||||
auditLogs: false,
|
||||
|
||||
@@ -206,7 +206,7 @@ const main = async () => {
|
||||
app.use("/api/v1/sso", eeSSORouter);
|
||||
app.use("/api/v1/cloud-products", eeCloudProductsRouter);
|
||||
app.use("/api/v3/api-key", v3apiKeyDataRouter);
|
||||
app.use("/api/v3/machines", v3MachineIdentityRouter);
|
||||
app.use("/api/v3/machines", v3MachineIdentityRouter); // TODO: consider moving to v1
|
||||
app.use("/api/v1/secret-rotation-providers", v1SecretRotationProviderRouter);
|
||||
app.use("/api/v1/secret-rotations", v1SecretRotation);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export * from "./userAction";
|
||||
export * from "./workspace";
|
||||
export * from "./serviceTokenData"; // TODO: deprecate
|
||||
export * from "./machineIdentity";
|
||||
export * from "./machineIdentityClientSecretData";
|
||||
export * from "./machineIdentityClientSecret";
|
||||
export * from "./machineMembershipOrg";
|
||||
export * from "./machineMembership";
|
||||
export * from "./apiKeyData"; // TODO: deprecate
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Document, Schema, Types, model } from "mongoose";
|
||||
|
||||
export interface IMachineIdentityClientSecretData extends Document {
|
||||
export interface IMachineIdentityClientSecret extends Document {
|
||||
_id: Types.ObjectId;
|
||||
machineIdentity: Types.ObjectId;
|
||||
isActive: boolean;
|
||||
@@ -16,7 +16,7 @@ export interface IMachineIdentityClientSecretData extends Document {
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
const machineIdentityClientSecretDataSchema = new Schema(
|
||||
const machineIdentityClientSecretSchema = new Schema(
|
||||
{
|
||||
machineIdentity: {
|
||||
type: Schema.Types.ObjectId,
|
||||
@@ -74,8 +74,8 @@ const machineIdentityClientSecretDataSchema = new Schema(
|
||||
}
|
||||
);
|
||||
|
||||
machineIdentityClientSecretDataSchema.index(
|
||||
machineIdentityClientSecretSchema.index(
|
||||
{ machineIdentity: 1, isActive: 1 }
|
||||
)
|
||||
|
||||
export const MachineIdentityClientSecretData = model<IMachineIdentityClientSecretData>("MachineIdentityClientSecretData", machineIdentityClientSecretDataSchema);
|
||||
export const MachineIdentityClientSecret = model<IMachineIdentityClientSecret>("MachineIdentityClientSecret", machineIdentityClientSecretSchema);
|
||||
@@ -1,6 +1,6 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
import { Types } from "mongoose";
|
||||
import { MachineIdentity, MachineIdentityClientSecretData } from "../../../models";
|
||||
import { MachineIdentity, MachineIdentityClientSecret } from "../../../models";
|
||||
import { getAuthSecret } from "../../../config";
|
||||
import { AuthTokenType } from "../../../variables";
|
||||
import { UnauthorizedRequestError } from "../../errors";
|
||||
@@ -18,14 +18,14 @@ export const validateMachineIdentity = async ({
|
||||
|
||||
if (decodedToken.authTokenType !== AuthTokenType.MACHINE_ACCESS_TOKEN) throw UnauthorizedRequestError();
|
||||
|
||||
const machineIdentityClientSecretData = await MachineIdentityClientSecretData.findOne({
|
||||
const machineIdentityClientSecret = await MachineIdentityClientSecret.findOne({
|
||||
_id: new Types.ObjectId(decodedToken.clientSecretDataId),
|
||||
isActive: true
|
||||
});
|
||||
|
||||
if (!machineIdentityClientSecretData) throw UnauthorizedRequestError();
|
||||
if (!machineIdentityClientSecret) throw UnauthorizedRequestError();
|
||||
|
||||
if (decodedToken.tokenVersion !== machineIdentityClientSecretData.accessTokenVersion) {
|
||||
if (decodedToken.tokenVersion !== machineIdentityClientSecret.accessTokenVersion) {
|
||||
// TODO: raise alarm
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed to authenticate",
|
||||
@@ -33,7 +33,7 @@ export const validateMachineIdentity = async ({
|
||||
}
|
||||
|
||||
const machineIdentity = await MachineIdentity.findByIdAndUpdate(
|
||||
machineIdentityClientSecretData.machineIdentity,
|
||||
machineIdentityClientSecret.machineIdentity,
|
||||
{
|
||||
accessTokenLastUsed: new Date(),
|
||||
$inc: { accessTokenUsageCount: 1 }
|
||||
|
||||
@@ -132,6 +132,13 @@ In the following steps, we explore how to create and use MIs for your applicatio
|
||||
- You are attempting to access a `/raw` secrets endpoint that requires your project to disable E2EE.
|
||||
- The client secret/access token is being used from an untrusted IP.
|
||||
</Accordion>
|
||||
<Accordion title="Why can I not create, read, update, or delete a machine identity?">
|
||||
There are a few reasons for why this might happen:
|
||||
|
||||
- You have insufficient organization permissions to create, read, update, delete machine identities.
|
||||
- The MI you are trying to read, update, or delete is more privileged than yourself.
|
||||
- The role you are trying to create a MI for or update a MI to is more privileged than yours.
|
||||
</Accordion>
|
||||
<Accordion title="Can you provide examples for using glob patterns?">
|
||||
1. `/**`: This pattern matches all folders at any depth in the directory structure. For example, it would match folders like `/folder1/`, `/folder1/subfolder/`, and so on.
|
||||
|
||||
|
||||
@@ -244,6 +244,7 @@ export const AddMachineIdentityModal = ({
|
||||
|
||||
reset();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const error = err as any;
|
||||
const text = error?.response?.data?.message
|
||||
?? `Failed to ${popUp?.machineIdentity?.data ? "updated" : "created"} machine identity`;
|
||||
|
||||
@@ -81,8 +81,11 @@ export const MachineIdentityTable = ({
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const error = err as any;
|
||||
const text = error?.response?.data?.message ?? "Failed to update machine identity role"
|
||||
|
||||
createNotification({
|
||||
text: "Failed to update machine identity role",
|
||||
text,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export const MachineIdentitySection = withProjectPermission(
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
const onRemoveServiceTokenDataSubmit = async (machineId: string) => {
|
||||
const onRemoveMachineIdentitySubmit = async (machineId: string) => {
|
||||
try {
|
||||
|
||||
await deleteMutateAsync({
|
||||
@@ -44,17 +44,20 @@ export const MachineIdentitySection = withProjectPermission(
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully removed service account from project",
|
||||
text: "Successfully removed machine identity from project",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
handlePopUpClose("deleteMachineIdentity");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to delete service account from project",
|
||||
type: "error"
|
||||
});
|
||||
console.error(err);
|
||||
const error = err as any;
|
||||
const text = error?.response?.data?.message ?? "Failed to remove machine identity from project"
|
||||
|
||||
createNotification({
|
||||
text,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +99,7 @@ export const MachineIdentitySection = withProjectPermission(
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteMachineIdentity", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={() =>
|
||||
onRemoveServiceTokenDataSubmit(
|
||||
onRemoveMachineIdentitySubmit(
|
||||
(popUp?.deleteMachineIdentity?.data as { machineId: string })?.machineId
|
||||
)
|
||||
}
|
||||
|
||||
@@ -89,8 +89,11 @@ export const MachineIdentityTable = ({
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const error = err as any;
|
||||
const text = error?.response?.data?.message ?? "Failed to update machine identity role"
|
||||
|
||||
createNotification({
|
||||
text: "Failed to update machine identity role",
|
||||
text,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user