mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 18:27:36 +00:00
Merge pull request #4970 from Infisical/chore/pam-access-account-with-path
chore: updates pam access account endpoint to use account path instead of id [PAM-64]
This commit is contained in:
@@ -106,7 +106,8 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
description: "Access PAM account",
|
description: "Access PAM account",
|
||||||
body: z.object({
|
body: z.object({
|
||||||
accountId: z.string().uuid(),
|
accountPath: z.string().trim(),
|
||||||
|
projectId: z.string().uuid(),
|
||||||
duration: z
|
duration: z
|
||||||
.string()
|
.string()
|
||||||
.min(1)
|
.min(1)
|
||||||
@@ -151,7 +152,9 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorIp: req.realIp,
|
actorIp: req.realIp,
|
||||||
actorName: `${req.auth.user.firstName ?? ""} ${req.auth.user.lastName ?? ""}`.trim(),
|
actorName: `${req.auth.user.firstName ?? ""} ${req.auth.user.lastName ?? ""}`.trim(),
|
||||||
actorUserAgent: req.auditLogInfo.userAgent ?? "",
|
actorUserAgent: req.auditLogInfo.userAgent ?? "",
|
||||||
...req.body
|
accountPath: req.body.accountPath,
|
||||||
|
projectId: req.body.projectId,
|
||||||
|
duration: req.body.duration
|
||||||
},
|
},
|
||||||
req.permission
|
req.permission
|
||||||
);
|
);
|
||||||
@@ -163,7 +166,8 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
|
|||||||
event: {
|
event: {
|
||||||
type: EventType.PAM_ACCOUNT_ACCESS,
|
type: EventType.PAM_ACCOUNT_ACCESS,
|
||||||
metadata: {
|
metadata: {
|
||||||
accountId: req.body.accountId,
|
accountId: response.account.id,
|
||||||
|
accountPath: req.body.accountPath,
|
||||||
accountName: response.account.name,
|
accountName: response.account.name,
|
||||||
duration: req.body.duration ? new Date(req.body.duration).toISOString() : undefined
|
duration: req.body.duration ? new Date(req.body.duration).toISOString() : undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4076,6 +4076,7 @@ interface PamAccountAccessEvent {
|
|||||||
type: EventType.PAM_ACCOUNT_ACCESS;
|
type: EventType.PAM_ACCOUNT_ACCESS;
|
||||||
metadata: {
|
metadata: {
|
||||||
accountId: string;
|
accountId: string;
|
||||||
|
accountPath: string;
|
||||||
accountName: string;
|
accountName: string;
|
||||||
duration?: string;
|
duration?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -487,7 +487,7 @@ export const pamAccountServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const access = async (
|
const access = async (
|
||||||
{ accountId, actorEmail, actorIp, actorName, actorUserAgent, duration }: TAccessAccountDTO,
|
{ accountPath, projectId, actorEmail, actorIp, actorName, actorUserAgent, duration }: TAccessAccountDTO,
|
||||||
actor: OrgServiceActor
|
actor: OrgServiceActor
|
||||||
) => {
|
) => {
|
||||||
const orgLicensePlan = await licenseService.getPlan(actor.orgId);
|
const orgLicensePlan = await licenseService.getPlan(actor.orgId);
|
||||||
@@ -497,8 +497,36 @@ export const pamAccountServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const account = await pamAccountDAL.findById(accountId);
|
const pathSegments: string[] = accountPath.split("/").filter(Boolean);
|
||||||
if (!account) throw new NotFoundError({ message: `Account with ID '${accountId}' not found` });
|
if (pathSegments.length === 0) {
|
||||||
|
throw new BadRequestError({ message: "Invalid accountPath. Path must contain at least the account name." });
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountName: string = pathSegments[pathSegments.length - 1] ?? "";
|
||||||
|
const folderPathSegments: string[] = pathSegments.slice(0, -1);
|
||||||
|
|
||||||
|
const folderPath: string = folderPathSegments.length > 0 ? `/${folderPathSegments.join("/")}` : "/";
|
||||||
|
|
||||||
|
let folderId: string | null = null;
|
||||||
|
if (folderPath !== "/") {
|
||||||
|
const folder = await pamFolderDAL.findByPath(projectId, folderPath);
|
||||||
|
if (!folder) {
|
||||||
|
throw new NotFoundError({ message: `Folder at path '${folderPath}' not found` });
|
||||||
|
}
|
||||||
|
folderId = folder.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = await pamAccountDAL.findOne({
|
||||||
|
projectId,
|
||||||
|
folderId,
|
||||||
|
name: accountName
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Account with name '${accountName}' not found at path '${accountPath}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const resource = await pamResourceDAL.findById(account.resourceId);
|
const resource = await pamResourceDAL.findById(account.resourceId);
|
||||||
if (!resource) throw new NotFoundError({ message: `Resource with ID '${account.resourceId}' not found` });
|
if (!resource) throw new NotFoundError({ message: `Resource with ID '${account.resourceId}' not found` });
|
||||||
@@ -508,22 +536,16 @@ export const pamAccountServiceFactory = ({
|
|||||||
actorAuthMethod: actor.authMethod,
|
actorAuthMethod: actor.authMethod,
|
||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
actorOrgId: actor.orgId,
|
actorOrgId: actor.orgId,
|
||||||
projectId: account.projectId,
|
projectId,
|
||||||
actionProjectType: ActionProjectType.PAM
|
actionProjectType: ActionProjectType.PAM
|
||||||
});
|
});
|
||||||
|
|
||||||
const accountPath = await getFullPamFolderPath({
|
|
||||||
pamFolderDAL,
|
|
||||||
folderId: account.folderId,
|
|
||||||
projectId: account.projectId
|
|
||||||
});
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionPamAccountActions.Access,
|
ProjectPermissionPamAccountActions.Access,
|
||||||
subject(ProjectPermissionSub.PamAccounts, {
|
subject(ProjectPermissionSub.PamAccounts, {
|
||||||
resourceName: resource.name,
|
resourceName: resource.name,
|
||||||
accountName: account.name,
|
accountName: account.name,
|
||||||
accountPath
|
accountPath: folderPath
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -533,7 +555,7 @@ export const pamAccountServiceFactory = ({
|
|||||||
actorIp,
|
actorIp,
|
||||||
actorName,
|
actorName,
|
||||||
actorUserAgent,
|
actorUserAgent,
|
||||||
projectId: account.projectId,
|
projectId,
|
||||||
resourceName: resource.name,
|
resourceName: resource.name,
|
||||||
resourceType: resource.resourceType,
|
resourceType: resource.resourceType,
|
||||||
status: PamSessionStatus.Starting,
|
status: PamSessionStatus.Starting,
|
||||||
@@ -542,11 +564,7 @@ export const pamAccountServiceFactory = ({
|
|||||||
expiresAt: new Date(Date.now() + duration)
|
expiresAt: new Date(Date.now() + duration)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { connectionDetails, gatewayId, resourceType } = await decryptResource(
|
const { connectionDetails, gatewayId, resourceType } = await decryptResource(resource, projectId, kmsService);
|
||||||
resource,
|
|
||||||
account.projectId,
|
|
||||||
kmsService
|
|
||||||
);
|
|
||||||
|
|
||||||
const user = await userDAL.findById(actor.id);
|
const user = await userDAL.findById(actor.id);
|
||||||
if (!user) throw new NotFoundError({ message: `User with ID '${actor.id}' not found` });
|
if (!user) throw new NotFoundError({ message: `User with ID '${actor.id}' not found` });
|
||||||
@@ -578,20 +596,20 @@ export const pamAccountServiceFactory = ({
|
|||||||
const connectionCredentials = (await decryptResourceConnectionDetails({
|
const connectionCredentials = (await decryptResourceConnectionDetails({
|
||||||
encryptedConnectionDetails: resource.encryptedConnectionDetails,
|
encryptedConnectionDetails: resource.encryptedConnectionDetails,
|
||||||
kmsService,
|
kmsService,
|
||||||
projectId: account.projectId
|
projectId
|
||||||
})) as TSqlResourceConnectionDetails;
|
})) as TSqlResourceConnectionDetails;
|
||||||
|
|
||||||
const credentials = await decryptAccountCredentials({
|
const credentials = await decryptAccountCredentials({
|
||||||
encryptedCredentials: account.encryptedCredentials,
|
encryptedCredentials: account.encryptedCredentials,
|
||||||
kmsService,
|
kmsService,
|
||||||
projectId: account.projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
metadata = {
|
metadata = {
|
||||||
username: credentials.username,
|
username: credentials.username,
|
||||||
database: connectionCredentials.database,
|
database: connectionCredentials.database,
|
||||||
accountName: account.name,
|
accountName: account.name,
|
||||||
accountPath
|
accountPath: folderPath
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -600,7 +618,7 @@ export const pamAccountServiceFactory = ({
|
|||||||
const credentials = await decryptAccountCredentials({
|
const credentials = await decryptAccountCredentials({
|
||||||
encryptedCredentials: account.encryptedCredentials,
|
encryptedCredentials: account.encryptedCredentials,
|
||||||
kmsService,
|
kmsService,
|
||||||
projectId: account.projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
metadata = {
|
metadata = {
|
||||||
@@ -622,7 +640,7 @@ export const pamAccountServiceFactory = ({
|
|||||||
gatewayClientPrivateKey: gatewayConnectionDetails.gateway.clientPrivateKey,
|
gatewayClientPrivateKey: gatewayConnectionDetails.gateway.clientPrivateKey,
|
||||||
gatewayServerCertificateChain: gatewayConnectionDetails.gateway.serverCertificateChain,
|
gatewayServerCertificateChain: gatewayConnectionDetails.gateway.serverCertificateChain,
|
||||||
relayHost: gatewayConnectionDetails.relayHost,
|
relayHost: gatewayConnectionDetails.relayHost,
|
||||||
projectId: account.projectId,
|
projectId,
|
||||||
account,
|
account,
|
||||||
metadata
|
metadata
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ export type TUpdateAccountDTO = Partial<Omit<TCreateAccountDTO, "folderId" | "re
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TAccessAccountDTO = {
|
export type TAccessAccountDTO = {
|
||||||
accountId: string;
|
accountPath: string;
|
||||||
|
projectId: string;
|
||||||
actorEmail: string;
|
actorEmail: string;
|
||||||
actorIp: string;
|
actorIp: string;
|
||||||
actorName: string;
|
actorName: string;
|
||||||
|
|||||||
@@ -10,11 +10,26 @@ import { PamResourceType, TPamAccount } from "@app/hooks/api/pam";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
account?: TPamAccount;
|
account?: TPamAccount;
|
||||||
|
accountPath?: string;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onOpenChange: (isOpen: boolean) => void;
|
onOpenChange: (isOpen: boolean) => void;
|
||||||
|
projectId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PamAccessAccountModal = ({ isOpen, onOpenChange, account }: Props) => {
|
export const PamAccessAccountModal = ({
|
||||||
|
isOpen,
|
||||||
|
onOpenChange,
|
||||||
|
account,
|
||||||
|
projectId,
|
||||||
|
accountPath
|
||||||
|
}: Props) => {
|
||||||
|
let fullAccountPath = account?.name;
|
||||||
|
if (accountPath) {
|
||||||
|
let path = accountPath;
|
||||||
|
if (path.startsWith("/")) path = path.slice(1);
|
||||||
|
fullAccountPath = `${path}/${account?.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
const { protocol, hostname, port } = window.location;
|
const { protocol, hostname, port } = window.location;
|
||||||
const portSuffix = port && port !== "80" && port !== "443" ? `:${port}` : "";
|
const portSuffix = port && port !== "80" && port !== "443" ? `:${port}` : "";
|
||||||
const siteURL = `${protocol}//${hostname}${portSuffix}`;
|
const siteURL = `${protocol}//${hostname}${portSuffix}`;
|
||||||
@@ -68,9 +83,9 @@ export const PamAccessAccountModal = ({ isOpen, onOpenChange, account }: Props)
|
|||||||
switch (account.resource.resourceType) {
|
switch (account.resource.resourceType) {
|
||||||
case PamResourceType.Postgres:
|
case PamResourceType.Postgres:
|
||||||
case PamResourceType.MySQL:
|
case PamResourceType.MySQL:
|
||||||
return `infisical pam db access-account ${account.id} --duration ${cliDuration} --domain ${siteURL}`;
|
return `infisical pam db access-account ${fullAccountPath} --project-id ${projectId} --duration ${cliDuration} --domain ${siteURL}`;
|
||||||
case PamResourceType.SSH:
|
case PamResourceType.SSH:
|
||||||
return `infisical pam ssh access-account ${account.id} --duration ${cliDuration} --domain ${siteURL}`;
|
return `infisical pam ssh access-account ${fullAccountPath} --project-id ${projectId} --duration ${cliDuration} --domain ${siteURL}`;
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -466,6 +466,12 @@ export const PamAccountsTable = ({ projectId }: Props) => {
|
|||||||
isOpen={popUp.accessAccount.isOpen}
|
isOpen={popUp.accessAccount.isOpen}
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle("accessAccount", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("accessAccount", isOpen)}
|
||||||
account={popUp.accessAccount.data}
|
account={popUp.accessAccount.data}
|
||||||
|
accountPath={
|
||||||
|
popUp.accessAccount.data?.folderId
|
||||||
|
? folderPaths[popUp.accessAccount.data.folderId]
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
projectId={projectId}
|
||||||
/>
|
/>
|
||||||
<PamDeleteAccountModal
|
<PamDeleteAccountModal
|
||||||
isOpen={popUp.deleteAccount.isOpen}
|
isOpen={popUp.deleteAccount.isOpen}
|
||||||
|
|||||||
Reference in New Issue
Block a user