From 179380f82b3e735bfa97d7d37ad61105033fe72b Mon Sep 17 00:00:00 2001 From: x032205 Date: Mon, 20 Oct 2025 19:13:14 -0400 Subject: [PATCH] review fixes --- .../ee/services/audit-log/audit-log-types.ts | 12 ++ .../services/pam-account/pam-account-dal.ts | 24 ++-- .../pam-account/pam-account-service.ts | 24 +++- .../pam-resource/pam-resource-service.ts | 25 +++- docs/documentation/platform/pam/overview.mdx | 8 -- frontend/src/hooks/api/pam/queries.tsx | 22 ++++ .../src/hooks/api/pam/types/base-account.ts | 1 + .../PamAccountForm/PamAccountForm.tsx | 2 +- .../PamAccountForm/PostgresAccountForm.tsx | 20 +++- .../PamAccountForm/RotateAccountFields.tsx | 111 ++++++++++-------- .../shared/SqlAccountFields.tsx | 3 +- .../shared/SqlRotateAccountFields.tsx | 7 +- 12 files changed, 176 insertions(+), 83 deletions(-) diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index ff2dafa9f..be5906af5 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -516,6 +516,7 @@ export enum EventType { PAM_ACCOUNT_UPDATE = "pam-account-update", PAM_ACCOUNT_DELETE = "pam-account-delete", PAM_ACCOUNT_CREDENTIAL_ROTATION = "pam-account-credential-rotation", + PAM_ACCOUNT_CREDENTIAL_ROTATION_FAILED = "pam-account-credential-rotation-failed", PAM_RESOURCE_LIST = "pam-resource-list", PAM_RESOURCE_GET = "pam-resource-get", PAM_RESOURCE_CREATE = "pam-resource-create", @@ -3834,6 +3835,16 @@ interface PamAccountCredentialRotationEvent { }; } +interface PamAccountCredentialRotationFailedEvent { + type: EventType.PAM_ACCOUNT_CREDENTIAL_ROTATION_FAILED; + metadata: { + accountName: string; + accountId: string; + resourceId: string; + errorMessage: string; + }; +} + interface PamResourceListEvent { type: EventType.PAM_RESOURCE_LIST; metadata: { @@ -4225,6 +4236,7 @@ export type Event = | PamAccountUpdateEvent | PamAccountDeleteEvent | PamAccountCredentialRotationEvent + | PamAccountCredentialRotationFailedEvent | PamResourceListEvent | PamResourceGetEvent | PamResourceCreateEvent diff --git a/backend/src/ee/services/pam-account/pam-account-dal.ts b/backend/src/ee/services/pam-account/pam-account-dal.ts index c422c2b1d..e0377871c 100644 --- a/backend/src/ee/services/pam-account/pam-account-dal.ts +++ b/backend/src/ee/services/pam-account/pam-account-dal.ts @@ -18,7 +18,8 @@ export const pamAccountDALFactory = (db: TDbClient) => { .select( // resource db.ref("name").withSchema(TableName.PamResource).as("resourceName"), - db.ref("resourceType").withSchema(TableName.PamResource) + db.ref("resourceType").withSchema(TableName.PamResource), + db.ref("encryptedRotationAccountCredentials").withSchema(TableName.PamResource) ); if (filter) { @@ -28,15 +29,18 @@ export const pamAccountDALFactory = (db: TDbClient) => { const accounts = await query; - return accounts.map(({ resourceId, resourceName, resourceType, ...account }) => ({ - ...account, - resourceId, - resource: { - id: resourceId, - name: resourceName, - resourceType - } - })); + return accounts.map( + ({ resourceId, resourceName, resourceType, encryptedRotationAccountCredentials, ...account }) => ({ + ...account, + resourceId, + resource: { + id: resourceId, + name: resourceName, + resourceType, + encryptedRotationAccountCredentials + } + }) + ); }; const findAccountsDueForRotation = async (tx?: Knex) => { 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 6146c1a13..209eb25b9 100644 --- a/backend/src/ee/services/pam-account/pam-account-service.ts +++ b/backend/src/ee/services/pam-account/pam-account-service.ts @@ -330,7 +330,7 @@ export const pamAccountServiceFactory = ({ const decryptedAndPermittedAccounts: Array< TPamAccounts & { - resource: Pick; + resource: Pick & { rotationCredentialsConfigured: boolean }; credentials: TPamAccountCredentials; } > = []; @@ -360,7 +360,8 @@ export const pamAccountServiceFactory = ({ resource: { id: account.resource.id, name: account.resource.name, - resourceType: account.resource.resourceType + resourceType: account.resource.resourceType, + rotationCredentialsConfigured: !!account.resource.encryptedRotationAccountCredentials } }); } @@ -608,6 +609,25 @@ export const pamAccountServiceFactory = ({ } }); } catch (error) { + const errorMessage = error instanceof Error ? error.message : "An unknown error occurred"; + + await auditLogService.createAuditLog({ + projectId: account.projectId, + actor: { + type: ActorType.PLATFORM, + metadata: {} + }, + event: { + type: EventType.PAM_ACCOUNT_CREDENTIAL_ROTATION_FAILED, + metadata: { + accountId: account.id, + accountName: account.name, + resourceId: account.resourceId, + errorMessage + } + } + }); + logger.error(error, `Failed to rotate credentials for account ${account.id}`); } }); diff --git a/backend/src/ee/services/pam-resource/pam-resource-service.ts b/backend/src/ee/services/pam-resource/pam-resource-service.ts index 076ec6855..683e0bca4 100644 --- a/backend/src/ee/services/pam-resource/pam-resource-service.ts +++ b/backend/src/ee/services/pam-resource/pam-resource-service.ts @@ -204,13 +204,26 @@ export const pamResourceServiceFactory = ({ finalCredentials.password = decryptedCredentials.password; } - const validatedRotationAccountCredentials = await factory.validateAccountCredentials(finalCredentials); + try { + const validatedRotationAccountCredentials = await factory.validateAccountCredentials(finalCredentials); - updateDoc.encryptedRotationAccountCredentials = await encryptAccountCredentials({ - credentials: validatedRotationAccountCredentials, - projectId: resource.projectId, - kmsService - }); + updateDoc.encryptedRotationAccountCredentials = await encryptAccountCredentials({ + credentials: validatedRotationAccountCredentials, + projectId: resource.projectId, + kmsService + }); + } catch (err) { + if ( + err instanceof BadRequestError && + err.message === "Account credentials invalid: Username or password incorrect" + ) { + throw new BadRequestError({ + message: "Rotation Account credentials invalid: Username or password incorrect" + }); + } + + throw err; + } } } diff --git a/docs/documentation/platform/pam/overview.mdx b/docs/documentation/platform/pam/overview.mdx index 06b9915d4..a6e0094f5 100644 --- a/docs/documentation/platform/pam/overview.mdx +++ b/docs/documentation/platform/pam/overview.mdx @@ -43,11 +43,3 @@ Here’s how it works: ![Rotate Credentials Account](/images/pam/overview/rotate-credentials-account.png) Infisical will then use the rotation account on the resource to automatically update the credentials of the target account at the specified interval, eliminating credential staleness. - -## FAQ - - - - Infisical PAM currently supports PostgreSQL, with support for more databases, RDP, Kubernetes, social media accounts, and more coming soon. - - diff --git a/frontend/src/hooks/api/pam/queries.tsx b/frontend/src/hooks/api/pam/queries.tsx index 288d65ab9..c6df40e37 100644 --- a/frontend/src/hooks/api/pam/queries.tsx +++ b/frontend/src/hooks/api/pam/queries.tsx @@ -12,6 +12,7 @@ export const pamKeys = { session: () => [...pamKeys.all, "session"] as const, listResourceOptions: () => [...pamKeys.resource(), "options"] as const, listResources: (projectId: string) => [...pamKeys.resource(), "list", projectId], + getResource: (resourceId?: string) => [...pamKeys.resource(), "get", resourceId], listAccounts: (projectId: string) => [...pamKeys.account(), "list", projectId], getSession: (sessionId: string) => [...pamKeys.session(), "get", sessionId], listSessions: (projectId: string) => [...pamKeys.session(), "list", projectId] @@ -68,6 +69,27 @@ export const useListPamResources = ( }); }; +export const useGetPamResourceById = ( + resourceId?: string, + options?: Omit< + UseQueryOptions>, + "queryKey" | "queryFn" | "enabled" + > +) => { + return useQuery({ + queryKey: pamKeys.getResource(resourceId), + queryFn: async () => { + const { data } = await apiRequest.get<{ resource: TPamResource }>( + `/api/v1/pam/resources/${resourceId}` + ); + + return data.resource; + }, + enabled: !!resourceId, + ...options + }); +}; + // Accounts export const useListPamAccounts = ( projectId: string, diff --git a/frontend/src/hooks/api/pam/types/base-account.ts b/frontend/src/hooks/api/pam/types/base-account.ts index ec94d51b2..09ad6550d 100644 --- a/frontend/src/hooks/api/pam/types/base-account.ts +++ b/frontend/src/hooks/api/pam/types/base-account.ts @@ -9,6 +9,7 @@ export interface TBasePamAccount { id: string; name: string; resourceType: PamResourceType; + rotationCredentialsConfigured: boolean; }; name: string; description?: string | null; diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx index de23d243f..eee101a54 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx @@ -65,7 +65,7 @@ const CreateForm = ({ switch (resourceType) { case PamResourceType.Postgres: - return ; + return ; default: throw new Error(`Unhandled resource: ${resourceType}`); } diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx index 0033c48f3..bbf7982fb 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx @@ -1,9 +1,10 @@ +import { useEffect, useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Button, ModalClose } from "@app/components/v2"; -import { TPostgresAccount } from "@app/hooks/api/pam"; +import { TPostgresAccount, useGetPamResourceById } from "@app/hooks/api/pam"; import { BaseSqlAccountSchema } from "./shared/sql-account-schemas"; import { SqlAccountFields } from "./shared/SqlAccountFields"; @@ -12,6 +13,7 @@ import { RotateAccountFields, rotateAccountFieldsSchema } from "./RotateAccountF type Props = { account?: TPostgresAccount; + resourceId?: string; onSubmit: (formData: FormData) => Promise; }; @@ -21,7 +23,7 @@ const formSchema = genericAccountFieldsSchema.extend(rotateAccountFieldsSchema.s type FormData = z.infer; -export const PostgresAccountForm = ({ account, onSubmit }: Props) => { +export const PostgresAccountForm = ({ account, resourceId, onSubmit }: Props) => { const isUpdate = Boolean(account); const form = useForm({ @@ -42,6 +44,18 @@ export const PostgresAccountForm = ({ account, onSubmit }: Props) => { formState: { isSubmitting, isDirty } } = form; + const [rotationCredentialsConfigured, setRotationCredentialsConfigured] = useState(false); + + const { data: resource } = useGetPamResourceById(resourceId); + + useEffect(() => { + if (account) { + setRotationCredentialsConfigured(account.resource.rotationCredentialsConfigured); + } else { + setRotationCredentialsConfigured(!!resource?.rotationAccountCredentials); + } + }, [account]); + return (
{ > - +