From ac5c185f767968df7ab506fb47ef13ed4380af1a Mon Sep 17 00:00:00 2001 From: Victor Santos Date: Fri, 5 Dec 2025 00:56:33 -0300 Subject: [PATCH] feat: enhance PAM account handling with type safety and improved response structure - Introduced type inference for sanitized accounts to ensure consistent data handling. - Updated account response structure to explicitly cast accounts to the sanitized type. - Refined the decryption function to omit sensitive fields from the returned account object. - Improved error handling in SQL resource factory by enforcing required gateway ID validation. --- .../pam-account-routers/pam-account-router.ts | 4 +++- .../ee/services/pam-account/pam-account-fns.ts | 17 ++++++++++++----- .../services/pam-account/pam-account-service.ts | 2 +- .../pam-resource/aws-iam/aws-iam-federation.ts | 8 +------- .../aws-iam/aws-iam-resource-fns.ts | 4 +++- .../aws-iam/aws-iam-resource-schemas.ts | 4 +--- .../shared/sql/sql-resource-factory.ts | 12 ++++++++++++ .../PamAccountForm/PamAccountForm.tsx | 6 +++++- .../components/PamAccountRow.tsx | 2 +- 9 files changed, 39 insertions(+), 20 deletions(-) diff --git a/backend/src/ee/routes/v1/pam-account-routers/pam-account-router.ts b/backend/src/ee/routes/v1/pam-account-routers/pam-account-router.ts index df46a6753..1dd08b7df 100644 --- a/backend/src/ee/routes/v1/pam-account-routers/pam-account-router.ts +++ b/backend/src/ee/routes/v1/pam-account-routers/pam-account-router.ts @@ -23,6 +23,8 @@ const SanitizedAccountSchema = z.union([ SanitizedAwsIamAccountWithResourceSchema ]); +type TSanitizedAccount = z.infer; + export const registerPamAccountRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", @@ -95,7 +97,7 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => { } }); - return { accounts, folders, totalCount, folderId, folderPaths }; + return { accounts: accounts as TSanitizedAccount[], folders, totalCount, folderId, folderPaths }; } }); diff --git a/backend/src/ee/services/pam-account/pam-account-fns.ts b/backend/src/ee/services/pam-account/pam-account-fns.ts index aae703eeb..71ef0fd7b 100644 --- a/backend/src/ee/services/pam-account/pam-account-fns.ts +++ b/backend/src/ee/services/pam-account/pam-account-fns.ts @@ -72,17 +72,24 @@ export const decryptAccount = async < account: T, projectId: string, kmsService: Pick -): Promise => { +): Promise< + Omit & { + credentials: TPamAccountCredentials; + lastRotationMessage: string | null; + } +> => { + const { encryptedCredentials, encryptedLastRotationMessage, ...rest } = account; + return { - ...account, + ...rest, credentials: await decryptAccountCredentials({ - encryptedCredentials: account.encryptedCredentials, + encryptedCredentials, projectId, kmsService }), - lastRotationMessage: account.encryptedLastRotationMessage + lastRotationMessage: encryptedLastRotationMessage ? await decryptAccountMessage({ - encryptedMessage: account.encryptedLastRotationMessage, + encryptedMessage: encryptedLastRotationMessage, projectId, kmsService }) diff --git a/backend/src/ee/services/pam-account/pam-account-service.ts b/backend/src/ee/services/pam-account/pam-account-service.ts index fe2f558aa..ceced9ca2 100644 --- a/backend/src/ee/services/pam-account/pam-account-service.ts +++ b/backend/src/ee/services/pam-account/pam-account-service.ts @@ -439,7 +439,7 @@ export const pamAccountServiceFactory = ({ const totalCount = totalFolderCount + totalAccountCount; const decryptedAndPermittedAccounts: Array< - TPamAccounts & { + Omit & { resource: Pick & { rotationCredentialsConfigured: boolean }; credentials: TPamAccountCredentials; lastRotationMessage: string | null; diff --git a/backend/src/ee/services/pam-resource/aws-iam/aws-iam-federation.ts b/backend/src/ee/services/pam-resource/aws-iam/aws-iam-federation.ts index eb28e008b..2595627a1 100644 --- a/backend/src/ee/services/pam-resource/aws-iam/aws-iam-federation.ts +++ b/backend/src/ee/services/pam-resource/aws-iam/aws-iam-federation.ts @@ -170,12 +170,6 @@ export const generateConsoleFederationUrl = async ({ const federationEndpoint = "https://signin.aws.amazon.com/federation"; - // Console destination can be regional - const getConsoleHost = () => - connectionDetails.region === "us-east-1" - ? "console.aws.amazon.com" - : `${connectionDetails.region}.console.aws.amazon.com`; - const signinTokenUrl = `${federationEndpoint}?Action=getSigninToken&Session=${encodeURIComponent(sessionJson)}`; const tokenResponse = await fetch(signinTokenUrl); @@ -199,7 +193,7 @@ export const generateConsoleFederationUrl = async ({ throw new Error(`AWS federation endpoint did not return a SigninToken: ${responseText.substring(0, 200)}`); } - const consoleDestination = `https://${getConsoleHost()}/`; + const consoleDestination = `https://console.aws.amazon.com/`; const consoleUrl = `${federationEndpoint}?Action=login&SigninToken=${encodeURIComponent(tokenData.SigninToken)}&Destination=${encodeURIComponent(consoleDestination)}`; return { diff --git a/backend/src/ee/services/pam-resource/aws-iam/aws-iam-resource-fns.ts b/backend/src/ee/services/pam-resource/aws-iam/aws-iam-resource-fns.ts index c421e29e6..d04018d49 100644 --- a/backend/src/ee/services/pam-resource/aws-iam/aws-iam-resource-fns.ts +++ b/backend/src/ee/services/pam-resource/aws-iam/aws-iam-resource-fns.ts @@ -1,3 +1,5 @@ +import RE2 from "re2"; + import { BadRequestError } from "@app/lib/errors"; import { AwsIamResourceListItemSchema } from "./aws-iam-resource-schemas"; @@ -14,7 +16,7 @@ export const getAwsIamResourceListItem = () => { * ARN format: arn:aws:iam::123456789012:role/RoleName */ export const extractAwsAccountIdFromArn = (roleArn: string): string => { - const match = roleArn.match(/^arn:aws:iam::(\d{12}):role\//); + const match = roleArn.match(new RE2("^arn:aws:iam::(\\d{12}):role/")); if (!match) { throw new BadRequestError({ message: "Invalid IAM Role ARN format" }); } diff --git a/backend/src/ee/services/pam-resource/aws-iam/aws-iam-resource-schemas.ts b/backend/src/ee/services/pam-resource/aws-iam/aws-iam-resource-schemas.ts index e45807ae4..9167b4e5d 100644 --- a/backend/src/ee/services/pam-resource/aws-iam/aws-iam-resource-schemas.ts +++ b/backend/src/ee/services/pam-resource/aws-iam/aws-iam-resource-schemas.ts @@ -67,9 +67,7 @@ export const AwsIamAccountSchema = BasePamAccountSchema.extend({ }); export const CreateAwsIamAccountSchema = BaseCreatePamAccountSchema.extend({ - credentials: AwsIamAccountCredentialsSchema, - // AWS IAM doesn't support credential rotation - credentials are generated on-the-fly via STS - rotationEnabled: z.boolean().optional().default(false) + credentials: AwsIamAccountCredentialsSchema }); export const UpdateAwsIamAccountSchema = BaseUpdatePamAccountSchema.extend({ diff --git a/backend/src/ee/services/pam-resource/shared/sql/sql-resource-factory.ts b/backend/src/ee/services/pam-resource/shared/sql/sql-resource-factory.ts index b3128c422..26fa7ff39 100644 --- a/backend/src/ee/services/pam-resource/shared/sql/sql-resource-factory.ts +++ b/backend/src/ee/services/pam-resource/shared/sql/sql-resource-factory.ts @@ -233,6 +233,10 @@ export const sqlResourceFactory: TPamResourceFactory { const validateConnection = async () => { + if (!gatewayId) { + throw new BadRequestError({ message: "Gateway ID is required" }); + } + try { await executeWithGateway({ connectionDetails, gatewayId, resourceType }, gatewayV2Service, async (client) => { await client.validate(true); @@ -255,6 +259,10 @@ export const sqlResourceFactory: TPamResourceFactory { try { + if (!gatewayId) { + throw new BadRequestError({ message: "Gateway ID is required" }); + } + await executeWithGateway( { connectionDetails, @@ -296,6 +304,10 @@ export const sqlResourceFactory: TPamResourceFactory { const newPassword = alphaNumericNanoId(32); + if (!gatewayId) { + throw new BadRequestError({ message: "Gateway ID is required" }); + } + try { return await executeWithGateway( { diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx index b5d2d4c4c..d14642483 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx @@ -73,7 +73,11 @@ const CreateForm = ({ ); case PamResourceType.AwsIam: return ( - + ); default: throw new Error(`Unhandled resource: ${resourceType}`); diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountRow.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountRow.tsx index 9ea842bc3..a2c668b27 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountRow.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountRow.tsx @@ -103,7 +103,7 @@ export const PamAccountRow = ({ )} - {account.lastRotatedAt && ( + {"lastRotatedAt" in account && account.lastRotatedAt && (