diff --git a/backend/src/ee/routes/v1/pam-session-router.ts b/backend/src/ee/routes/v1/pam-session-router.ts index 90fe4fa9b..cc8969c92 100644 --- a/backend/src/ee/routes/v1/pam-session-router.ts +++ b/backend/src/ee/routes/v1/pam-session-router.ts @@ -35,7 +35,7 @@ export const registerPamSessionRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - credentials: z.any() // UNION DOES NOT WORK WITH ZOD SCHEMA + credentials: SessionCredentialsSchema }) } }, 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 b6f2bbae9..6f1805300 100644 --- a/backend/src/ee/services/pam-account/pam-account-service.ts +++ b/backend/src/ee/services/pam-account/pam-account-service.ts @@ -24,11 +24,10 @@ 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 { TMySQLResourceConnectionDetails } from "../pam-resource/mysql/mysql-resource-types"; import { TPamResourceDALFactory } from "../pam-resource/pam-resource-dal"; import { PamResource } from "../pam-resource/pam-resource-enums"; import { TPamAccountCredentials } from "../pam-resource/pam-resource-types"; -import { TPostgresResourceConnectionDetails } from "../pam-resource/postgres/postgres-resource-types"; +import { TSqlResourceConnectionDetails } from "../pam-resource/shared/sql/sql-resource-types"; import { TPamSessionDALFactory } from "../pam-session/pam-session-dal"; import { PamSessionStatus } from "../pam-session/pam-session-enums"; import { OrgPermissionGatewayActions, OrgPermissionSubjects } from "../permission/org-permission"; @@ -492,7 +491,7 @@ export const pamAccountServiceFactory = ({ encryptedConnectionDetails: resource.encryptedConnectionDetails, kmsService, projectId: account.projectId - })) as TMySQLResourceConnectionDetails | TPostgresResourceConnectionDetails; + })) as TSqlResourceConnectionDetails; const credentials = await decryptAccountCredentials({ encryptedCredentials: account.encryptedCredentials, @@ -517,9 +516,7 @@ export const pamAccountServiceFactory = ({ }); metadata = { - username: credentials.username, - accountName: account.name, - accountPath + username: credentials.username }; } break; diff --git a/backend/src/ee/services/pam-resource/ssh/ssh-resource-schemas.ts b/backend/src/ee/services/pam-resource/ssh/ssh-resource-schemas.ts index 779574e01..97d462369 100644 --- a/backend/src/ee/services/pam-resource/ssh/ssh-resource-schemas.ts +++ b/backend/src/ee/services/pam-resource/ssh/ssh-resource-schemas.ts @@ -20,25 +20,25 @@ export const SSHResourceListItemSchema = z.object({ }); export const SSHResourceConnectionDetailsSchema = z.object({ - host: z.string().trim(), + host: z.string().trim().max(255), port: z.number() }); export const SSHPasswordCredentialsSchema = z.object({ authMethod: z.literal(SSHAuthMethod.Password), - username: z.string().trim(), - password: z.string().trim() + username: z.string().trim().max(255), + password: z.string().trim().max(255) }); export const SSHPublicKeyCredentialsSchema = z.object({ authMethod: z.literal(SSHAuthMethod.PublicKey), - username: z.string().trim(), - privateKey: z.string().trim() + username: z.string().trim().max(255), + privateKey: z.string().trim().max(5000) }); export const SSHCertificateCredentialsSchema = z.object({ authMethod: z.literal(SSHAuthMethod.Certificate), - username: z.string().trim() + username: z.string().trim().max(255) }); export const SSHAccountCredentialsSchema = z.discriminatedUnion("authMethod", [ diff --git a/backend/src/ee/services/pam-session/pam-session-fns.ts b/backend/src/ee/services/pam-session/pam-session-fns.ts index 842dfbb6d..38e9f6239 100644 --- a/backend/src/ee/services/pam-session/pam-session-fns.ts +++ b/backend/src/ee/services/pam-session/pam-session-fns.ts @@ -2,7 +2,7 @@ import { TPamSessions } from "@app/db/schemas"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { KmsDataKey } from "@app/services/kms/kms-types"; -import { TPamSanitizedSession, TPamSessionCommandLog, TTerminalEvent } from "./pam-session.types"; +import { TPamSanitizedSession, TPamSessionCommandLog, TTerminalEvent } from "./pam-session-types"; export const decryptSessionCommandLogs = async ({ projectId, diff --git a/backend/src/ee/services/pam-session/pam-session-service.ts b/backend/src/ee/services/pam-session/pam-session-service.ts index 26ff7daa6..18c185cac 100644 --- a/backend/src/ee/services/pam-session/pam-session-service.ts +++ b/backend/src/ee/services/pam-session/pam-session-service.ts @@ -12,10 +12,10 @@ import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TLicenseServiceFactory } from "../license/license-service"; import { OrgPermissionGatewayActions, OrgPermissionSubjects } from "../permission/org-permission"; import { ProjectPermissionPamSessionActions, ProjectPermissionSub } from "../permission/project-permission"; -import { TUpdateSessionLogsDTO } from "./pam-session.types"; import { TPamSessionDALFactory } from "./pam-session-dal"; import { PamSessionStatus } from "./pam-session-enums"; import { decryptSession } from "./pam-session-fns"; +import { TUpdateSessionLogsDTO } from "./pam-session-types"; type TPamSessionServiceFactoryDep = { pamSessionDAL: TPamSessionDALFactory; diff --git a/backend/src/ee/services/pam-session/pam-session.types.ts b/backend/src/ee/services/pam-session/pam-session-types.ts similarity index 100% rename from backend/src/ee/services/pam-session/pam-session.types.ts rename to backend/src/ee/services/pam-session/pam-session-types.ts diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccessAccountModal.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccessAccountModal.tsx index 97fcb26f6..704c13eba 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccessAccountModal.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccessAccountModal.tsx @@ -61,18 +61,15 @@ export const PamAccessAccountModal = ({ isOpen, onOpenChange, account }: Props) const command = useMemo(() => { if (!account) return ""; - if ( - account.resource.resourceType === PamResourceType.Postgres || - account.resource.resourceType === PamResourceType.MySQL - ) { - return `infisical pam db access-account ${account.id} --duration ${cliDuration}`; + switch (account.resource.resourceType) { + case PamResourceType.Postgres: + case PamResourceType.MySQL: + return `infisical pam db access-account ${account.id} --duration ${cliDuration}`; + case PamResourceType.SSH: + return `infisical pam ssh access-account ${account.id} --duration ${cliDuration}`; + default: + return ""; } - - if (account.resource.resourceType === PamResourceType.SSH) { - return `infisical pam ssh ${account.id} --duration ${cliDuration}`; - } - - return ""; }, [account, cliDuration]); if (!account) return null; diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx index 0288411b1..fc1662349 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx @@ -10,7 +10,7 @@ import { DiscriminativePick } from "@app/types"; import { PamAccountHeader } from "../PamAccountHeader"; import { MySQLAccountForm } from "./MySQLAccountForm"; import { PostgresAccountForm } from "./PostgresAccountForm"; -import { SSHAccountForm } from "./SSHAccountForm"; +import { SshAccountForm } from "./SshAccountForm"; type FormProps = { onComplete: (account: TPamAccount) => void; @@ -68,7 +68,7 @@ const CreateForm = ({ ); case PamResourceType.SSH: return ( - + ); default: throw new Error(`Unhandled resource: ${resourceType}`); @@ -99,7 +99,7 @@ const UpdateForm = ({ account, onComplete }: UpdateFormProps) => { case PamResourceType.MySQL: return ; case PamResourceType.SSH: - return ; + return ; default: throw new Error(`Unhandled resource: ${account.resource.resourceType}`); } diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/SSHAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/SSHAccountForm.tsx deleted file mode 100644 index 0302e4759..000000000 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/SSHAccountForm.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { zodResolver } from "@hookform/resolvers/zod"; -import { FormProvider, useForm } from "react-hook-form"; -import { z } from "zod"; - -import { Button, ModalClose } from "@app/components/v2"; -import { PamResourceType, TSSHAccount } from "@app/hooks/api/pam"; -import { UNCHANGED_PASSWORD_SENTINEL } from "@app/hooks/api/pam/constants"; -import { SSHAuthMethod } from "@app/hooks/api/pam/types/ssh-resource"; - -import { GenericAccountFields, genericAccountFieldsSchema } from "./GenericAccountFields"; -import { BaseSshAccountSchema } from "./shared/ssh-account-schemas"; -import { SshAccountFields } from "./shared/SshAccountFields"; - -type Props = { - account?: TSSHAccount; - resourceId?: string; - resourceType?: PamResourceType; - onSubmit: (formData: FormData) => Promise; -}; - -const formSchema = genericAccountFieldsSchema.extend({ - credentials: BaseSshAccountSchema, - // We don't support rotation for now, just feed a false value to - // make the schema happy - rotationEnabled: z.boolean().default(false) -}); - -type FormData = z.infer; - -export const SSHAccountForm = ({ account, onSubmit }: Props) => { - const isUpdate = Boolean(account); - - const getDefaultCredentials = () => { - if (!account) return undefined; - - if (account.credentials.authMethod === SSHAuthMethod.Password) { - return { - ...account.credentials, - password: UNCHANGED_PASSWORD_SENTINEL - }; - } - - if (account.credentials.authMethod === SSHAuthMethod.PublicKey) { - return { - ...account.credentials, - privateKey: UNCHANGED_PASSWORD_SENTINEL - }; - } - - return account.credentials; - }; - - const form = useForm({ - resolver: zodResolver(formSchema), - defaultValues: account - ? { - ...account, - credentials: getDefaultCredentials() - } - : { - name: "", - description: "", - credentials: { - authMethod: SSHAuthMethod.Password, - username: "", - password: "" - } - } - }); - - const { - handleSubmit, - formState: { isSubmitting, isDirty } - } = form; - - return ( - -
{ - handleSubmit(onSubmit)(e); - }} - > - - -
- - - - -
- -
- ); -}; diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/SshAccountFields.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/SshAccountForm.tsx similarity index 57% rename from frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/SshAccountFields.tsx rename to frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/SshAccountForm.tsx index 091120c4e..fc8ea7acb 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/SshAccountFields.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/SshAccountForm.tsx @@ -1,11 +1,63 @@ import { useEffect, useState } from "react"; -import { Controller, useFormContext, useWatch } from "react-hook-form"; +import { Controller, FormProvider, useForm, useFormContext, useWatch } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; -import { FormControl, Input, Select, SelectItem, TextArea } from "@app/components/v2"; +import { + Button, + FormControl, + Input, + ModalClose, + Select, + SelectItem, + TextArea +} from "@app/components/v2"; +import { PamResourceType, TSSHAccount } from "@app/hooks/api/pam"; import { UNCHANGED_PASSWORD_SENTINEL } from "@app/hooks/api/pam/constants"; import { SSHAuthMethod } from "@app/hooks/api/pam/types/ssh-resource"; -export const SshAccountFields = ({ isUpdate }: { isUpdate: boolean }) => { +import { GenericAccountFields, genericAccountFieldsSchema } from "./GenericAccountFields"; + +type Props = { + account?: TSSHAccount; + resourceId?: string; + resourceType?: PamResourceType; + onSubmit: (formData: FormData) => Promise; +}; + +export const SSHPasswordCredentialsSchema = z.object({ + authMethod: z.literal(SSHAuthMethod.Password), + username: z.string().trim().min(1, "Username is required"), + password: z.string().trim().min(1, "Password is required") +}); + +export const SSHPublicKeyCredentialsSchema = z.object({ + authMethod: z.literal(SSHAuthMethod.PublicKey), + username: z.string().trim().min(1, "Username is required"), + privateKey: z.string().trim().min(1, "Private key is required") +}); + +export const SSHCertificateCredentialsSchema = z.object({ + authMethod: z.literal(SSHAuthMethod.Certificate), + username: z.string().trim().min(1, "Username is required") +}); + +export const BaseSshAccountSchema = z.discriminatedUnion("authMethod", [ + SSHPasswordCredentialsSchema, + SSHPublicKeyCredentialsSchema, + SSHCertificateCredentialsSchema +]); + +const formSchema = genericAccountFieldsSchema.extend({ + credentials: BaseSshAccountSchema, + // We don't support rotation for now, just feed a false value to + // make the schema happy + rotationEnabled: z.boolean().default(false) +}); + +type FormData = z.infer; + +const SshAccountFields = ({ isUpdate }: { isUpdate: boolean }) => { const { control, setValue } = useFormContext(); const [showPassword, setShowPassword] = useState(false); @@ -141,3 +193,80 @@ export const SshAccountFields = ({ isUpdate }: { isUpdate: boolean }) => { ); }; + +export const SshAccountForm = ({ account, onSubmit }: Props) => { + const isUpdate = Boolean(account); + + const getDefaultCredentials = () => { + if (!account) return undefined; + + if (account.credentials.authMethod === SSHAuthMethod.Password) { + return { + ...account.credentials, + password: UNCHANGED_PASSWORD_SENTINEL + }; + } + + if (account.credentials.authMethod === SSHAuthMethod.PublicKey) { + return { + ...account.credentials, + privateKey: UNCHANGED_PASSWORD_SENTINEL + }; + } + + return account.credentials; + }; + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: account + ? { + ...account, + credentials: getDefaultCredentials() + } + : { + name: "", + description: "", + credentials: { + authMethod: SSHAuthMethod.Password, + username: "", + password: "" + } + } + }); + + const { + handleSubmit, + formState: { isSubmitting, isDirty } + } = form; + + return ( + +
{ + handleSubmit(onSubmit)(e); + }} + > + + +
+ + + + +
+ +
+ ); +}; diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/ssh-account-schemas.ts b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/ssh-account-schemas.ts deleted file mode 100644 index b34617c16..000000000 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/ssh-account-schemas.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { z } from "zod"; - -import { SSHAuthMethod } from "@app/hooks/api/pam/types/ssh-resource"; - -export const SSHPasswordCredentialsSchema = z.object({ - authMethod: z.literal(SSHAuthMethod.Password), - username: z.string().trim().min(1, "Username is required"), - password: z.string().trim().min(1, "Password is required") -}); - -export const SSHPublicKeyCredentialsSchema = z.object({ - authMethod: z.literal(SSHAuthMethod.PublicKey), - username: z.string().trim().min(1, "Username is required"), - privateKey: z.string().trim().min(1, "Private key is required") -}); - -export const SSHCertificateCredentialsSchema = z.object({ - authMethod: z.literal(SSHAuthMethod.Certificate), - username: z.string().trim().min(1, "Username is required") -}); - -export const BaseSshAccountSchema = z.discriminatedUnion("authMethod", [ - SSHPasswordCredentialsSchema, - SSHPublicKeyCredentialsSchema, - SSHCertificateCredentialsSchema -]); diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/SSHResourceForm.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/SSHResourceForm.tsx index 942c6bac5..c2e89ea5e 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/SSHResourceForm.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/SSHResourceForm.tsx @@ -5,15 +5,19 @@ import { z } from "zod"; import { Button, ModalClose } from "@app/components/v2"; import { PamResourceType, TSSHResource } from "@app/hooks/api/pam"; -import { GenericResourceFields, genericResourceFieldsSchema } from "./GenericResourceFields"; -import { BaseSshConnectionDetailsSchema } from "./shared/ssh-resource-schemas"; import { SshResourceFields } from "./shared/SshResourceFields"; +import { GenericResourceFields, genericResourceFieldsSchema } from "./GenericResourceFields"; type Props = { resource?: TSSHResource; onSubmit: (formData: FormData) => Promise; }; +const BaseSshConnectionDetailsSchema = z.object({ + host: z.string().trim().min(1, "Host is required"), + port: z.number().int().min(1).max(65535) +}); + const formSchema = genericResourceFieldsSchema.extend({ resourceType: z.literal(PamResourceType.SSH), connectionDetails: BaseSshConnectionDetailsSchema diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/ssh-resource-schemas.ts b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/ssh-resource-schemas.ts deleted file mode 100644 index 33ce650ec..000000000 --- a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/ssh-resource-schemas.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { z } from "zod"; - -import { SSHAuthMethod } from "@app/hooks/api/pam/types/ssh-resource"; - -export const BaseSshConnectionDetailsSchema = z.object({ - host: z.string().trim().min(1, "Host is required"), - port: z.number().int().min(1).max(65535) -}); - -export const SSHPasswordCredentialsSchema = z.object({ - authMethod: z.literal(SSHAuthMethod.Password), - username: z.string().trim().min(1, "Username is required"), - password: z.string().trim().min(1, "Password is required") -}); - -export const SSHPublicKeyCredentialsSchema = z.object({ - authMethod: z.literal(SSHAuthMethod.PublicKey), - username: z.string().trim().min(1, "Username is required"), - privateKey: z.string().trim().min(1, "Private key is required") -}); - -export const SSHCertificateCredentialsSchema = z.object({ - authMethod: z.literal(SSHAuthMethod.Certificate), - username: z.string().trim().min(1, "Username is required") -}); - -export const BaseSshAccountSchema = z.discriminatedUnion("authMethod", [ - SSHPasswordCredentialsSchema, - SSHPublicKeyCredentialsSchema, - SSHCertificateCredentialsSchema -]);