|
|
|
@@ -1,54 +1,23 @@
|
|
|
|
|
import { ForbiddenError, subject } from "@casl/ability";
|
|
|
|
|
import { ForbiddenError } from "@casl/ability";
|
|
|
|
|
|
|
|
|
|
import { ActionProjectType, TPamAccounts, TPamResources } from "@app/db/schemas";
|
|
|
|
|
import { ActionProjectType, TPamResources } from "@app/db/schemas";
|
|
|
|
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
|
|
|
|
import {
|
|
|
|
|
ProjectPermissionActions,
|
|
|
|
|
ProjectPermissionPamAccountActions,
|
|
|
|
|
ProjectPermissionSub
|
|
|
|
|
} from "@app/ee/services/permission/project-permission";
|
|
|
|
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
|
|
|
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
|
|
|
|
import { DatabaseErrorCode } from "@app/lib/error-codes";
|
|
|
|
|
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
|
|
|
|
import { OrgServiceActor } from "@app/lib/types";
|
|
|
|
|
import { ActorType } from "@app/services/auth/auth-type";
|
|
|
|
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
|
|
|
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
|
|
|
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
|
|
|
|
|
|
|
|
|
import { TGatewayV2ServiceFactory } from "../gateway-v2/gateway-v2-service";
|
|
|
|
|
import { TLicenseServiceFactory } from "../license/license-service";
|
|
|
|
|
import { TPamFolderDALFactory } from "../pam-folder/pam-folder-dal";
|
|
|
|
|
import { getFullPamFolderPath } from "../pam-folder/pam-folder-fns";
|
|
|
|
|
import { TPamSessionDALFactory } from "../pam-session/pam-session-dal";
|
|
|
|
|
import { PamSessionStatus } from "../pam-session/pam-session-enums";
|
|
|
|
|
import { OrgPermissionGatewayActions, OrgPermissionSubjects } from "../permission/org-permission";
|
|
|
|
|
import { TPamAccountDALFactory } from "./pam-account-dal";
|
|
|
|
|
import { TPamResourceDALFactory } from "./pam-resource-dal";
|
|
|
|
|
import { PamResource } from "./pam-resource-enums";
|
|
|
|
|
import { PAM_RESOURCE_FACTORY_MAP } from "./pam-resource-factory";
|
|
|
|
|
import {
|
|
|
|
|
decryptAccount,
|
|
|
|
|
decryptAccountCredentials,
|
|
|
|
|
decryptResource,
|
|
|
|
|
decryptResourceConnectionDetails,
|
|
|
|
|
encryptAccountCredentials,
|
|
|
|
|
encryptResourceConnectionDetails,
|
|
|
|
|
listResourceOptions
|
|
|
|
|
} from "./pam-resource-fns";
|
|
|
|
|
import {
|
|
|
|
|
TAccessAccountDTO,
|
|
|
|
|
TCreateAccountDTO,
|
|
|
|
|
TCreateResourceDTO,
|
|
|
|
|
TPamAccountCredentials,
|
|
|
|
|
TUpdateAccountDTO,
|
|
|
|
|
TUpdateResourceDTO
|
|
|
|
|
} from "./pam-resource-types";
|
|
|
|
|
import { decryptResource, encryptResourceConnectionDetails, listResourceOptions } from "./pam-resource-fns";
|
|
|
|
|
import { TCreateResourceDTO, TUpdateResourceDTO } from "./pam-resource-types";
|
|
|
|
|
|
|
|
|
|
type TPamResourceServiceFactoryDep = {
|
|
|
|
|
pamResourceDAL: TPamResourceDALFactory;
|
|
|
|
|
pamSessionDAL: TPamSessionDALFactory;
|
|
|
|
|
pamAccountDAL: TPamAccountDALFactory;
|
|
|
|
|
pamFolderDAL: TPamFolderDALFactory;
|
|
|
|
|
projectDAL: TProjectDALFactory;
|
|
|
|
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
|
|
|
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
|
|
|
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
|
|
|
@@ -56,18 +25,12 @@ type TPamResourceServiceFactoryDep = {
|
|
|
|
|
TGatewayV2ServiceFactory,
|
|
|
|
|
"getPAMConnectionDetails" | "getPlatformConnectionDetailsByGatewayId"
|
|
|
|
|
>;
|
|
|
|
|
userDAL: TUserDALFactory;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type TPamResourceServiceFactory = ReturnType<typeof pamResourceServiceFactory>;
|
|
|
|
|
|
|
|
|
|
export const pamResourceServiceFactory = ({
|
|
|
|
|
pamResourceDAL,
|
|
|
|
|
pamSessionDAL,
|
|
|
|
|
pamAccountDAL,
|
|
|
|
|
pamFolderDAL,
|
|
|
|
|
projectDAL,
|
|
|
|
|
userDAL,
|
|
|
|
|
permissionService,
|
|
|
|
|
licenseService,
|
|
|
|
|
kmsService,
|
|
|
|
@@ -94,7 +57,7 @@ export const pamResourceServiceFactory = ({
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return decryptResource(resource, actor.orgId, kmsService);
|
|
|
|
|
return decryptResource(resource, resource.projectId, kmsService);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const create = async (
|
|
|
|
@@ -129,7 +92,7 @@ export const pamResourceServiceFactory = ({
|
|
|
|
|
|
|
|
|
|
const encryptedConnectionDetails = await encryptResourceConnectionDetails({
|
|
|
|
|
connectionDetails: validatedConnectionDetails,
|
|
|
|
|
orgId: actor.orgId,
|
|
|
|
|
projectId,
|
|
|
|
|
kmsService
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
@@ -141,7 +104,7 @@ export const pamResourceServiceFactory = ({
|
|
|
|
|
projectId
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return decryptResource(resource, actor.orgId, kmsService);
|
|
|
|
|
return decryptResource(resource, projectId, kmsService);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateById = async ({ connectionDetails, resourceId, name }: TUpdateResourceDTO, actor: OrgServiceActor) => {
|
|
|
|
@@ -182,7 +145,7 @@ export const pamResourceServiceFactory = ({
|
|
|
|
|
const validatedConnectionDetails = await factory.validateConnection();
|
|
|
|
|
const encryptedConnectionDetails = await encryptResourceConnectionDetails({
|
|
|
|
|
connectionDetails: validatedConnectionDetails,
|
|
|
|
|
orgId: actor.orgId,
|
|
|
|
|
projectId: resource.projectId,
|
|
|
|
|
kmsService
|
|
|
|
|
});
|
|
|
|
|
updateDoc.encryptedConnectionDetails = encryptedConnectionDetails;
|
|
|
|
@@ -190,12 +153,12 @@ export const pamResourceServiceFactory = ({
|
|
|
|
|
|
|
|
|
|
// If nothing was updated, return the fetched resource
|
|
|
|
|
if (Object.keys(updateDoc).length === 0) {
|
|
|
|
|
return decryptResource(resource, actor.orgId, kmsService);
|
|
|
|
|
return decryptResource(resource, resource.projectId, kmsService);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updatedResource = await pamResourceDAL.updateById(resourceId, updateDoc);
|
|
|
|
|
|
|
|
|
|
return decryptResource(updatedResource, actor.orgId, kmsService);
|
|
|
|
|
return decryptResource(updatedResource, resource.projectId, kmsService);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const deleteById = async (id: string, actor: OrgServiceActor) => {
|
|
|
|
@@ -213,9 +176,20 @@ export const pamResourceServiceFactory = ({
|
|
|
|
|
|
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.PamResources);
|
|
|
|
|
|
|
|
|
|
const deletedResource = await pamResourceDAL.deleteById(id);
|
|
|
|
|
|
|
|
|
|
return decryptResource(deletedResource, actor.orgId, kmsService);
|
|
|
|
|
try {
|
|
|
|
|
const deletedResource = await pamResourceDAL.deleteById(id);
|
|
|
|
|
return await decryptResource(deletedResource, resource.projectId, kmsService);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
if (
|
|
|
|
|
err instanceof DatabaseError &&
|
|
|
|
|
(err.error as { code: string })?.code === DatabaseErrorCode.ForeignKeyViolation
|
|
|
|
|
) {
|
|
|
|
|
throw new BadRequestError({
|
|
|
|
|
message: "Failed to delete resource because it is attached to active PAM accounts"
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
throw err;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const list = async (projectId: string, actor: OrgServiceActor) => {
|
|
|
|
@@ -233,440 +207,7 @@ export const pamResourceServiceFactory = ({
|
|
|
|
|
const resources = await pamResourceDAL.find({ projectId });
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
resources: await Promise.all(resources.map((resource) => decryptResource(resource, actor.orgId, kmsService)))
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Accounts
|
|
|
|
|
const createAccount = async (
|
|
|
|
|
{ credentials, resourceId, name, description, folderId }: TCreateAccountDTO,
|
|
|
|
|
actor: OrgServiceActor
|
|
|
|
|
) => {
|
|
|
|
|
const orgLicensePlan = await licenseService.getPlan(actor.orgId);
|
|
|
|
|
if (!orgLicensePlan.pam) {
|
|
|
|
|
throw new BadRequestError({
|
|
|
|
|
message: "PAM operation failed due to organization plan restrictions."
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const resource = await pamResourceDAL.findById(resourceId);
|
|
|
|
|
if (!resource) throw new NotFoundError({ message: `Resource with ID '${resourceId}' not found` });
|
|
|
|
|
|
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
|
|
|
actor: actor.type,
|
|
|
|
|
actorAuthMethod: actor.authMethod,
|
|
|
|
|
actorId: actor.id,
|
|
|
|
|
actorOrgId: actor.orgId,
|
|
|
|
|
projectId: resource.projectId,
|
|
|
|
|
actionProjectType: ActionProjectType.PAM
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const accountPath = await getFullPamFolderPath({
|
|
|
|
|
pamFolderDAL,
|
|
|
|
|
folderId,
|
|
|
|
|
projectId: resource.projectId
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
|
|
|
ProjectPermissionPamAccountActions.Create,
|
|
|
|
|
subject(ProjectPermissionSub.PamAccounts, {
|
|
|
|
|
resourceName: resource.name,
|
|
|
|
|
accountName: name,
|
|
|
|
|
accountPath
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const connectionDetails = await decryptResourceConnectionDetails({
|
|
|
|
|
orgId: actor.orgId,
|
|
|
|
|
encryptedConnectionDetails: resource.encryptedConnectionDetails,
|
|
|
|
|
kmsService
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const factory = PAM_RESOURCE_FACTORY_MAP[resource.resourceType as PamResource](
|
|
|
|
|
resource.resourceType as PamResource,
|
|
|
|
|
connectionDetails,
|
|
|
|
|
resource.gatewayId,
|
|
|
|
|
gatewayV2Service
|
|
|
|
|
);
|
|
|
|
|
const validatedCredentials = await factory.validateAccountCredentials(credentials);
|
|
|
|
|
|
|
|
|
|
const encryptedCredentials = await encryptAccountCredentials({
|
|
|
|
|
credentials: validatedCredentials,
|
|
|
|
|
orgId: actor.orgId,
|
|
|
|
|
kmsService
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const account = await pamAccountDAL.create({
|
|
|
|
|
projectId: resource.projectId,
|
|
|
|
|
resourceId: resource.id,
|
|
|
|
|
encryptedCredentials,
|
|
|
|
|
name,
|
|
|
|
|
description,
|
|
|
|
|
folderId
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...(await decryptAccount(account, actor.orgId, kmsService)),
|
|
|
|
|
resource: { id: resource.id, name: resource.name, resourceType: resource.resourceType }
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateAccountById = async (
|
|
|
|
|
{ accountId, credentials, description, name }: TUpdateAccountDTO,
|
|
|
|
|
actor: OrgServiceActor
|
|
|
|
|
) => {
|
|
|
|
|
const orgLicensePlan = await licenseService.getPlan(actor.orgId);
|
|
|
|
|
if (!orgLicensePlan.pam) {
|
|
|
|
|
throw new BadRequestError({
|
|
|
|
|
message: "PAM operation failed due to organization plan restrictions."
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const account = await pamAccountDAL.findById(accountId);
|
|
|
|
|
if (!account) throw new NotFoundError({ message: `Account with ID '${accountId}' not found` });
|
|
|
|
|
|
|
|
|
|
const resource = await pamResourceDAL.findById(account.resourceId);
|
|
|
|
|
if (!resource) throw new NotFoundError({ message: `Resource with ID '${account.resourceId}' not found` });
|
|
|
|
|
|
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
|
|
|
actor: actor.type,
|
|
|
|
|
actorAuthMethod: actor.authMethod,
|
|
|
|
|
actorId: actor.id,
|
|
|
|
|
actorOrgId: actor.orgId,
|
|
|
|
|
projectId: account.projectId,
|
|
|
|
|
actionProjectType: ActionProjectType.PAM
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const accountPath = await getFullPamFolderPath({
|
|
|
|
|
pamFolderDAL,
|
|
|
|
|
folderId: account.folderId,
|
|
|
|
|
projectId: account.projectId
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
|
|
|
ProjectPermissionPamAccountActions.Edit,
|
|
|
|
|
subject(ProjectPermissionSub.PamAccounts, {
|
|
|
|
|
resourceName: resource.name,
|
|
|
|
|
accountName: account.name,
|
|
|
|
|
accountPath
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const updateDoc: Partial<TPamAccounts> = {};
|
|
|
|
|
|
|
|
|
|
if (name !== undefined) {
|
|
|
|
|
updateDoc.name = name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (description !== undefined) {
|
|
|
|
|
updateDoc.description = description;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (credentials !== undefined) {
|
|
|
|
|
const connectionDetails = await decryptResourceConnectionDetails({
|
|
|
|
|
orgId: actor.orgId,
|
|
|
|
|
encryptedConnectionDetails: resource.encryptedConnectionDetails,
|
|
|
|
|
kmsService
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const factory = PAM_RESOURCE_FACTORY_MAP[resource.resourceType as PamResource](
|
|
|
|
|
resource.resourceType as PamResource,
|
|
|
|
|
connectionDetails,
|
|
|
|
|
resource.gatewayId,
|
|
|
|
|
gatewayV2Service
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Logic to prevent overwriting unedited censored values
|
|
|
|
|
const finalCredentials = { ...credentials };
|
|
|
|
|
if (credentials.password === "******") {
|
|
|
|
|
const decryptedCredentials = await decryptAccountCredentials({
|
|
|
|
|
encryptedCredentials: account.encryptedCredentials,
|
|
|
|
|
orgId: actor.orgId,
|
|
|
|
|
kmsService
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
finalCredentials.password = decryptedCredentials.password;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const validatedCredentials = await factory.validateAccountCredentials(finalCredentials);
|
|
|
|
|
const encryptedCredentials = await encryptAccountCredentials({
|
|
|
|
|
credentials: validatedCredentials,
|
|
|
|
|
orgId: actor.orgId,
|
|
|
|
|
kmsService
|
|
|
|
|
});
|
|
|
|
|
updateDoc.encryptedCredentials = encryptedCredentials;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If nothing was updated, return the fetched account
|
|
|
|
|
if (Object.keys(updateDoc).length === 0) {
|
|
|
|
|
return decryptAccount(account, actor.orgId, kmsService);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updatedAccount = await pamAccountDAL.updateById(accountId, updateDoc);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...(await decryptAccount(updatedAccount, actor.orgId, kmsService)),
|
|
|
|
|
resource: { id: resource.id, name: resource.name, resourceType: resource.resourceType }
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const deleteAccountById = async (id: string, actor: OrgServiceActor) => {
|
|
|
|
|
const account = await pamAccountDAL.findById(id);
|
|
|
|
|
if (!account) throw new NotFoundError({ message: `Account with ID '${id}' not found` });
|
|
|
|
|
|
|
|
|
|
const resource = await pamResourceDAL.findById(account.resourceId);
|
|
|
|
|
if (!resource) throw new NotFoundError({ message: `Resource with ID '${account.resourceId}' not found` });
|
|
|
|
|
|
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
|
|
|
actor: actor.type,
|
|
|
|
|
actorAuthMethod: actor.authMethod,
|
|
|
|
|
actorId: actor.id,
|
|
|
|
|
actorOrgId: actor.orgId,
|
|
|
|
|
projectId: account.projectId,
|
|
|
|
|
actionProjectType: ActionProjectType.PAM
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const accountPath = await getFullPamFolderPath({
|
|
|
|
|
pamFolderDAL,
|
|
|
|
|
folderId: account.folderId,
|
|
|
|
|
projectId: account.projectId
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
|
|
|
ProjectPermissionPamAccountActions.Delete,
|
|
|
|
|
subject(ProjectPermissionSub.PamAccounts, {
|
|
|
|
|
resourceName: resource.name,
|
|
|
|
|
accountName: account.name,
|
|
|
|
|
accountPath
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const deletedAccount = await pamAccountDAL.deleteById(id);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...(await decryptAccount(deletedAccount, actor.orgId, kmsService)),
|
|
|
|
|
resource: { id: resource.id, name: resource.name, resourceType: resource.resourceType }
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const listAccounts = async (projectId: string, actor: OrgServiceActor) => {
|
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
|
|
|
actor: actor.type,
|
|
|
|
|
actorAuthMethod: actor.authMethod,
|
|
|
|
|
actorId: actor.id,
|
|
|
|
|
actorOrgId: actor.orgId,
|
|
|
|
|
projectId,
|
|
|
|
|
actionProjectType: ActionProjectType.PAM
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const accountsWithResourceDetails = await pamAccountDAL.findWithResourceDetails({ projectId });
|
|
|
|
|
|
|
|
|
|
const canReadFolders = permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.PamFolders);
|
|
|
|
|
|
|
|
|
|
const folders = canReadFolders ? await pamFolderDAL.find({ projectId }) : [];
|
|
|
|
|
|
|
|
|
|
const decryptedAndPermittedAccounts: Array<
|
|
|
|
|
TPamAccounts & {
|
|
|
|
|
resource: Pick<TPamResources, "id" | "name" | "resourceType">;
|
|
|
|
|
credentials: TPamAccountCredentials;
|
|
|
|
|
}
|
|
|
|
|
> = [];
|
|
|
|
|
|
|
|
|
|
for await (const account of accountsWithResourceDetails) {
|
|
|
|
|
const accountPath = await getFullPamFolderPath({
|
|
|
|
|
pamFolderDAL,
|
|
|
|
|
folderId: account.folderId,
|
|
|
|
|
projectId: account.projectId
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Check permission for each individual account
|
|
|
|
|
if (
|
|
|
|
|
permission.can(
|
|
|
|
|
ProjectPermissionPamAccountActions.Read,
|
|
|
|
|
subject(ProjectPermissionSub.PamAccounts, {
|
|
|
|
|
resourceName: account.resource.name,
|
|
|
|
|
accountName: account.name,
|
|
|
|
|
accountPath
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
// Decrypt the account only if the user has permission to read it
|
|
|
|
|
const decryptedAccount = await decryptAccount(account, actor.orgId, kmsService);
|
|
|
|
|
decryptedAndPermittedAccounts.push({
|
|
|
|
|
...decryptedAccount,
|
|
|
|
|
resource: {
|
|
|
|
|
id: account.resource.id,
|
|
|
|
|
name: account.resource.name,
|
|
|
|
|
resourceType: account.resource.resourceType
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
accounts: decryptedAndPermittedAccounts,
|
|
|
|
|
folders
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const accessAccount = async (
|
|
|
|
|
{ accountId, actorEmail, actorIp, actorName, actorUserAgent, duration }: TAccessAccountDTO,
|
|
|
|
|
actor: OrgServiceActor
|
|
|
|
|
) => {
|
|
|
|
|
const orgLicensePlan = await licenseService.getPlan(actor.orgId);
|
|
|
|
|
if (!orgLicensePlan.pam) {
|
|
|
|
|
throw new BadRequestError({
|
|
|
|
|
message: "PAM operation failed due to organization plan restrictions."
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const account = await pamAccountDAL.findById(accountId);
|
|
|
|
|
if (!account) throw new NotFoundError({ message: `Account with ID '${accountId}' not found` });
|
|
|
|
|
|
|
|
|
|
const resource = await pamResourceDAL.findById(account.resourceId);
|
|
|
|
|
if (!resource) throw new NotFoundError({ message: `Resource with ID '${account.resourceId}' not found` });
|
|
|
|
|
|
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
|
|
|
actor: actor.type,
|
|
|
|
|
actorAuthMethod: actor.authMethod,
|
|
|
|
|
actorId: actor.id,
|
|
|
|
|
actorOrgId: actor.orgId,
|
|
|
|
|
projectId: account.projectId,
|
|
|
|
|
actionProjectType: ActionProjectType.PAM
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const accountPath = await getFullPamFolderPath({
|
|
|
|
|
pamFolderDAL,
|
|
|
|
|
folderId: account.folderId,
|
|
|
|
|
projectId: account.projectId
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
|
|
|
ProjectPermissionPamAccountActions.Access,
|
|
|
|
|
subject(ProjectPermissionSub.PamAccounts, {
|
|
|
|
|
resourceName: resource.name,
|
|
|
|
|
accountName: account.name,
|
|
|
|
|
accountPath
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const session = await pamSessionDAL.create({
|
|
|
|
|
accountName: account.name,
|
|
|
|
|
actorEmail,
|
|
|
|
|
actorIp,
|
|
|
|
|
actorName,
|
|
|
|
|
actorUserAgent,
|
|
|
|
|
projectId: account.projectId,
|
|
|
|
|
resourceName: resource.name,
|
|
|
|
|
resourceType: resource.resourceType,
|
|
|
|
|
status: PamSessionStatus.Starting,
|
|
|
|
|
accountId: account.id,
|
|
|
|
|
userId: actor.id,
|
|
|
|
|
expiresAt: duration ? new Date(Date.now() + duration) : null
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const { connectionDetails, gatewayId, resourceType } = await decryptResource(resource, actor.orgId, kmsService);
|
|
|
|
|
|
|
|
|
|
const user = await userDAL.findById(actor.id);
|
|
|
|
|
|
|
|
|
|
const gatewayConnectionDetails = await gatewayV2Service.getPAMConnectionDetails({
|
|
|
|
|
gatewayId,
|
|
|
|
|
duration,
|
|
|
|
|
sessionId: session.id,
|
|
|
|
|
resourceType: resource.resourceType as PamResource,
|
|
|
|
|
host: connectionDetails.host,
|
|
|
|
|
port: connectionDetails.port,
|
|
|
|
|
actorMetadata: {
|
|
|
|
|
id: actor.id,
|
|
|
|
|
type: actor.type,
|
|
|
|
|
name: user.email ?? ""
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!gatewayConnectionDetails) {
|
|
|
|
|
throw new NotFoundError({ message: `Gateway connection details for gateway '${gatewayId}' not found.` });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
sessionId: session.id,
|
|
|
|
|
resourceType,
|
|
|
|
|
relayClientCertificate: gatewayConnectionDetails.relay.clientCertificate,
|
|
|
|
|
relayClientPrivateKey: gatewayConnectionDetails.relay.clientPrivateKey,
|
|
|
|
|
relayServerCertificateChain: gatewayConnectionDetails.relay.serverCertificateChain,
|
|
|
|
|
gatewayClientCertificate: gatewayConnectionDetails.gateway.clientCertificate,
|
|
|
|
|
gatewayClientPrivateKey: gatewayConnectionDetails.gateway.clientPrivateKey,
|
|
|
|
|
gatewayServerCertificateChain: gatewayConnectionDetails.gateway.serverCertificateChain,
|
|
|
|
|
relayHost: gatewayConnectionDetails.relayHost,
|
|
|
|
|
projectId: account.projectId
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getSessionCredentials = async (sessionId: string, actor: OrgServiceActor) => {
|
|
|
|
|
const orgLicensePlan = await licenseService.getPlan(actor.orgId);
|
|
|
|
|
if (!orgLicensePlan.pam) {
|
|
|
|
|
throw new BadRequestError({
|
|
|
|
|
message: "PAM operation failed due to organization plan restrictions."
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// To be hit by gateways only
|
|
|
|
|
if (actor.type !== ActorType.IDENTITY) {
|
|
|
|
|
throw new ForbiddenRequestError({ message: "Only gateways can perform this action" });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const session = await pamSessionDAL.findById(sessionId);
|
|
|
|
|
if (!session) throw new NotFoundError({ message: `Session with ID '${sessionId}' not found` });
|
|
|
|
|
|
|
|
|
|
const project = await projectDAL.findById(session.projectId);
|
|
|
|
|
if (!project) throw new NotFoundError({ message: `Project with ID '${session.projectId}' not found` });
|
|
|
|
|
|
|
|
|
|
const { permission } = await permissionService.getOrgPermission(
|
|
|
|
|
actor.type,
|
|
|
|
|
actor.id,
|
|
|
|
|
project.orgId,
|
|
|
|
|
actor.authMethod,
|
|
|
|
|
actor.orgId
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
|
|
|
OrgPermissionGatewayActions.CreateGateways,
|
|
|
|
|
OrgPermissionSubjects.Gateway
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!session.accountId) throw new NotFoundError({ message: "Session is missing accountId column" });
|
|
|
|
|
|
|
|
|
|
// Verify that the session has not ended
|
|
|
|
|
if (session.endedAt || (session.expiresAt && session.expiresAt < new Date())) {
|
|
|
|
|
throw new BadRequestError({ message: "Session has ended or expired" });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify that the session has not already had credentials fetched
|
|
|
|
|
if (session.status !== PamSessionStatus.Starting) {
|
|
|
|
|
throw new BadRequestError({ message: "Session has already been started" });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const account = await pamAccountDAL.findById(session.accountId);
|
|
|
|
|
if (!account) throw new NotFoundError({ message: `Account with ID '${session.accountId}' not found` });
|
|
|
|
|
|
|
|
|
|
const resource = await pamResourceDAL.findById(account.resourceId);
|
|
|
|
|
if (!resource) throw new NotFoundError({ message: `Resource with ID '${account.resourceId}' not found` });
|
|
|
|
|
|
|
|
|
|
const decryptedAccount = await decryptAccount(account, actor.orgId, kmsService);
|
|
|
|
|
|
|
|
|
|
const decryptedResource = await decryptResource(resource, actor.orgId, kmsService);
|
|
|
|
|
|
|
|
|
|
// Mark session as started
|
|
|
|
|
await pamSessionDAL.updateById(sessionId, {
|
|
|
|
|
status: PamSessionStatus.Active,
|
|
|
|
|
startedAt: new Date()
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
credentials: {
|
|
|
|
|
...decryptedResource.connectionDetails,
|
|
|
|
|
...decryptedAccount.credentials
|
|
|
|
|
},
|
|
|
|
|
projectId: project.id
|
|
|
|
|
resources: await Promise.all(resources.map((resource) => decryptResource(resource, projectId, kmsService)))
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@@ -676,12 +217,6 @@ export const pamResourceServiceFactory = ({
|
|
|
|
|
updateById,
|
|
|
|
|
deleteById,
|
|
|
|
|
list,
|
|
|
|
|
listResourceOptions,
|
|
|
|
|
createAccount,
|
|
|
|
|
updateAccountById,
|
|
|
|
|
deleteAccountById,
|
|
|
|
|
listAccounts,
|
|
|
|
|
accessAccount,
|
|
|
|
|
getSessionCredentials
|
|
|
|
|
listResourceOptions
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|