diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 108229942..3870a811f 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -2049,6 +2049,7 @@ "editInternalResourceDialogModeCidr": "CIDR", "editInternalResourceDialogModeHttp": "HTTP", "editInternalResourceDialogModeHttps": "HTTPS", + "editInternalResourceDialogModeSsh": "SSH", "editInternalResourceDialogScheme": "Метод", "editInternalResourceDialogEnableSsl": "Активирайте TLS", "editInternalResourceDialogEnableSslDescription": "Активирайте SSL/TLS криптиране за сигурни HTTPS връзки към целта.", @@ -2098,6 +2099,7 @@ "createInternalResourceDialogModeCidr": "CIDR", "createInternalResourceDialogModeHttp": "HTTP", "createInternalResourceDialogModeHttps": "HTTPS", + "createInternalResourceDialogModeSsh": "SSH", "scheme": "Метод", "createInternalResourceDialogScheme": "Метод", "createInternalResourceDialogEnableSsl": "Активирайте TLS", diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index 7c118ff29..4b1909063 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -2049,6 +2049,7 @@ "editInternalResourceDialogModeCidr": "CIDR", "editInternalResourceDialogModeHttp": "HTTP", "editInternalResourceDialogModeHttps": "HTTPS", + "editInternalResourceDialogModeSsh": "SSH", "editInternalResourceDialogScheme": "Schéma", "editInternalResourceDialogEnableSsl": "Povolit SSL", "editInternalResourceDialogEnableSslDescription": "Povolit šifrování SSL/TLS pro zabezpečené HTTPS připojení k cíli.", @@ -2098,6 +2099,7 @@ "createInternalResourceDialogModeCidr": "CIDR", "createInternalResourceDialogModeHttp": "HTTP", "createInternalResourceDialogModeHttps": "HTTPS", + "createInternalResourceDialogModeSsh": "SSH", "scheme": "Schéma", "createInternalResourceDialogScheme": "Schéma", "createInternalResourceDialogEnableSsl": "Povolit SSL", diff --git a/messages/de-DE.json b/messages/de-DE.json index 11d76dab5..663a0789b 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -2049,6 +2049,7 @@ "editInternalResourceDialogModeCidr": "CIDR", "editInternalResourceDialogModeHttp": "HTTP", "editInternalResourceDialogModeHttps": "HTTPS", + "editInternalResourceDialogModeSsh": "SSH", "editInternalResourceDialogScheme": "Schema", "editInternalResourceDialogEnableSsl": "TLS aktivieren", "editInternalResourceDialogEnableSslDescription": "SSL/TLS-Verschlüsselung für sichere HTTPS-Verbindungen zum Ziel aktivieren.", @@ -2098,6 +2099,7 @@ "createInternalResourceDialogModeCidr": "CIDR", "createInternalResourceDialogModeHttp": "HTTP", "createInternalResourceDialogModeHttps": "HTTPS", + "createInternalResourceDialogModeSsh": "SSH", "scheme": "Schema", "createInternalResourceDialogScheme": "Schema", "createInternalResourceDialogEnableSsl": "TLS aktivieren", diff --git a/messages/en-US.json b/messages/en-US.json index 141b686d6..0cf4492c0 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2110,6 +2110,7 @@ "editInternalResourceDialogModeCidr": "CIDR", "editInternalResourceDialogModeHttp": "HTTP", "editInternalResourceDialogModeHttps": "HTTPS", + "editInternalResourceDialogModeSsh": "SSH", "editInternalResourceDialogScheme": "Scheme", "editInternalResourceDialogEnableSsl": "Enable TLS", "editInternalResourceDialogEnableSslDescription": "Enable SSL/TLS encryption for secure HTTPS connections to the destination.", @@ -2159,6 +2160,7 @@ "createInternalResourceDialogModeCidr": "CIDR", "createInternalResourceDialogModeHttp": "HTTP", "createInternalResourceDialogModeHttps": "HTTPS", + "createInternalResourceDialogModeSsh": "SSH", "scheme": "Scheme", "createInternalResourceDialogScheme": "Scheme", "createInternalResourceDialogEnableSsl": "Enable TLS", diff --git a/messages/es-ES.json b/messages/es-ES.json index 9e5b6fc82..f42d0d495 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -2049,6 +2049,7 @@ "editInternalResourceDialogModeCidr": "CIDR", "editInternalResourceDialogModeHttp": "HTTP", "editInternalResourceDialogModeHttps": "HTTPS", + "editInternalResourceDialogModeSsh": "SSH", "editInternalResourceDialogScheme": "Esquema", "editInternalResourceDialogEnableSsl": "Activar TLS", "editInternalResourceDialogEnableSslDescription": "Habilitar cifrado SSL/TLS para conexiones HTTPS seguras al destino.", @@ -2098,6 +2099,7 @@ "createInternalResourceDialogModeCidr": "CIDR", "createInternalResourceDialogModeHttp": "HTTP", "createInternalResourceDialogModeHttps": "HTTPS", + "createInternalResourceDialogModeSsh": "SSH", "scheme": "Esquema", "createInternalResourceDialogScheme": "Esquema", "createInternalResourceDialogEnableSsl": "Activar TLS", diff --git a/messages/fr-FR.json b/messages/fr-FR.json index da3350e46..02f5e0d41 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -2049,6 +2049,7 @@ "editInternalResourceDialogModeCidr": "CIDR", "editInternalResourceDialogModeHttp": "HTTP", "editInternalResourceDialogModeHttps": "HTTPS", + "editInternalResourceDialogModeSsh": "SSH", "editInternalResourceDialogScheme": "Méthode HTTP", "editInternalResourceDialogEnableSsl": "Activer TLS", "editInternalResourceDialogEnableSslDescription": "Activer le cryptage SSL/TLS pour des connexions HTTPS sécurisées vers la destination.", @@ -2098,6 +2099,7 @@ "createInternalResourceDialogModeCidr": "CIDR", "createInternalResourceDialogModeHttp": "HTTP", "createInternalResourceDialogModeHttps": "HTTPS", + "createInternalResourceDialogModeSsh": "SSH", "scheme": "Méthode HTTP", "createInternalResourceDialogScheme": "Méthode HTTP", "createInternalResourceDialogEnableSsl": "Activer TLS", diff --git a/messages/it-IT.json b/messages/it-IT.json index 3ec9c0011..1892e4985 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -2049,6 +2049,7 @@ "editInternalResourceDialogModeCidr": "CIDR", "editInternalResourceDialogModeHttp": "HTTP", "editInternalResourceDialogModeHttps": "HTTPS", + "editInternalResourceDialogModeSsh": "SSH", "editInternalResourceDialogScheme": "Metodo HTTP", "editInternalResourceDialogEnableSsl": "Abilitare TLS", "editInternalResourceDialogEnableSslDescription": "Abilita la crittografia SSL/TLS per connessioni HTTPS sicure alla destinazione.", @@ -2098,6 +2099,7 @@ "createInternalResourceDialogModeCidr": "CIDR", "createInternalResourceDialogModeHttp": "HTTP", "createInternalResourceDialogModeHttps": "HTTPS", + "createInternalResourceDialogModeSsh": "SSH", "scheme": "Metodo HTTP", "createInternalResourceDialogScheme": "Metodo HTTP", "createInternalResourceDialogEnableSsl": "Abilitare TLS", diff --git a/messages/ko-KR.json b/messages/ko-KR.json index d1bd16382..3b78b86a0 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -2049,6 +2049,7 @@ "editInternalResourceDialogModeCidr": "CIDR", "editInternalResourceDialogModeHttp": "HTTP", "editInternalResourceDialogModeHttps": "HTTPS", + "editInternalResourceDialogModeSsh": "SSH", "editInternalResourceDialogScheme": "스킴", "editInternalResourceDialogEnableSsl": "TLS 활성화", "editInternalResourceDialogEnableSslDescription": "목적지로의 안전한 HTTPS 연결을 위한 SSL/TLS 암호화 활성화.", @@ -2098,6 +2099,7 @@ "createInternalResourceDialogModeCidr": "CIDR", "createInternalResourceDialogModeHttp": "HTTP", "createInternalResourceDialogModeHttps": "HTTPS", + "createInternalResourceDialogModeSsh": "SSH", "scheme": "스킴", "createInternalResourceDialogScheme": "스킴", "createInternalResourceDialogEnableSsl": "TLS 활성화", diff --git a/messages/nb-NO.json b/messages/nb-NO.json index d76013d16..d70df295b 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -2049,6 +2049,7 @@ "editInternalResourceDialogModeCidr": "CIDR", "editInternalResourceDialogModeHttp": "HTTP", "editInternalResourceDialogModeHttps": "HTTPS", + "editInternalResourceDialogModeSsh": "SSH", "editInternalResourceDialogScheme": "Skjema", "editInternalResourceDialogEnableSsl": "Aktiver TLS", "editInternalResourceDialogEnableSslDescription": "Aktiver SSL/TLS-kryptering for sikre HTTPS-tilkoblinger til destinasjonen.", @@ -2098,6 +2099,7 @@ "createInternalResourceDialogModeCidr": "CIDR", "createInternalResourceDialogModeHttp": "HTTP", "createInternalResourceDialogModeHttps": "HTTPS", + "createInternalResourceDialogModeSsh": "SSH", "scheme": "Skjema", "createInternalResourceDialogScheme": "Skjema", "createInternalResourceDialogEnableSsl": "Aktiver TLS", diff --git a/messages/nl-NL.json b/messages/nl-NL.json index f989db342..58c5587de 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -2049,6 +2049,7 @@ "editInternalResourceDialogModeCidr": "CIDR", "editInternalResourceDialogModeHttp": "HTTP", "editInternalResourceDialogModeHttps": "HTTPS", + "editInternalResourceDialogModeSsh": "SSH", "editInternalResourceDialogScheme": "Schema", "editInternalResourceDialogEnableSsl": "TLS inschakelen", "editInternalResourceDialogEnableSslDescription": "Schakel SSL/TLS-encryptie in voor beveiligde HTTPS-verbindingen met de bestemming.", @@ -2098,6 +2099,7 @@ "createInternalResourceDialogModeCidr": "CIDR", "createInternalResourceDialogModeHttp": "HTTP", "createInternalResourceDialogModeHttps": "HTTPS", + "createInternalResourceDialogModeSsh": "SSH", "scheme": "Schema", "createInternalResourceDialogScheme": "Schema", "createInternalResourceDialogEnableSsl": "TLS inschakelen", diff --git a/messages/pl-PL.json b/messages/pl-PL.json index 4d801023b..9675fe84a 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -2049,6 +2049,7 @@ "editInternalResourceDialogModeCidr": "CIDR", "editInternalResourceDialogModeHttp": "HTTP", "editInternalResourceDialogModeHttps": "HTTPS", + "editInternalResourceDialogModeSsh": "SSH", "editInternalResourceDialogScheme": "Schemat", "editInternalResourceDialogEnableSsl": "Włącz TLS", "editInternalResourceDialogEnableSslDescription": "Włącz szyfrowanie SSL/TLS dla bezpiecznych połączeń HTTPS z miejscem docelowym.", @@ -2098,6 +2099,7 @@ "createInternalResourceDialogModeCidr": "CIDR", "createInternalResourceDialogModeHttp": "HTTP", "createInternalResourceDialogModeHttps": "HTTPS", + "createInternalResourceDialogModeSsh": "SSH", "scheme": "Schemat", "createInternalResourceDialogScheme": "Schemat", "createInternalResourceDialogEnableSsl": "Włącz TLS", diff --git a/messages/pt-PT.json b/messages/pt-PT.json index 0604c1caf..075a9d8c5 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -2049,6 +2049,7 @@ "editInternalResourceDialogModeCidr": "CIDR", "editInternalResourceDialogModeHttp": "HTTP", "editInternalResourceDialogModeHttps": "HTTPS", + "editInternalResourceDialogModeSsh": "SSH", "editInternalResourceDialogScheme": "Esquema", "editInternalResourceDialogEnableSsl": "Ativar TLS", "editInternalResourceDialogEnableSslDescription": "Ativar criptografia SSL/TLS para conexões HTTPS seguras com o destino.", @@ -2098,6 +2099,7 @@ "createInternalResourceDialogModeCidr": "CIDR", "createInternalResourceDialogModeHttp": "HTTP", "createInternalResourceDialogModeHttps": "HTTPS", + "createInternalResourceDialogModeSsh": "SSH", "scheme": "Esquema", "createInternalResourceDialogScheme": "Esquema", "createInternalResourceDialogEnableSsl": "Ativar TLS", diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 0f3e48962..3e10d79c9 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -2049,6 +2049,7 @@ "editInternalResourceDialogModeCidr": "СИДР", "editInternalResourceDialogModeHttp": "HTTP", "editInternalResourceDialogModeHttps": "HTTPS", + "editInternalResourceDialogModeSsh": "SSH", "editInternalResourceDialogScheme": "Схема", "editInternalResourceDialogEnableSsl": "Включить TLS", "editInternalResourceDialogEnableSslDescription": "Включите шифрование SSL/TLS для защищенных HTTPS соединений с конечной точкой.", @@ -2098,6 +2099,7 @@ "createInternalResourceDialogModeCidr": "СИДР", "createInternalResourceDialogModeHttp": "HTTP", "createInternalResourceDialogModeHttps": "HTTPS", + "createInternalResourceDialogModeSsh": "SSH", "scheme": "Схема", "createInternalResourceDialogScheme": "Схема", "createInternalResourceDialogEnableSsl": "Включить TLS", diff --git a/messages/tr-TR.json b/messages/tr-TR.json index e1d965e8e..e83139470 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -2049,6 +2049,7 @@ "editInternalResourceDialogModeCidr": "CIDR", "editInternalResourceDialogModeHttp": "HTTP", "editInternalResourceDialogModeHttps": "HTTPS", + "editInternalResourceDialogModeSsh": "SSH", "editInternalResourceDialogScheme": "Şema", "editInternalResourceDialogEnableSsl": "TLS Etkinleştir", "editInternalResourceDialogEnableSslDescription": "Hedefe güvenli HTTPS bağlantıları için SSL/TLS şifrelemeyi etkinleştirin.", @@ -2098,6 +2099,7 @@ "createInternalResourceDialogModeCidr": "CIDR", "createInternalResourceDialogModeHttp": "HTTP", "createInternalResourceDialogModeHttps": "HTTPS", + "createInternalResourceDialogModeSsh": "SSH", "scheme": "Şema", "createInternalResourceDialogScheme": "Şema", "createInternalResourceDialogEnableSsl": "TLS'yi Etkinleştir", diff --git a/messages/zh-CN.json b/messages/zh-CN.json index a23647dba..4f27045ad 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -2049,6 +2049,7 @@ "editInternalResourceDialogModeCidr": "CIDR", "editInternalResourceDialogModeHttp": "HTTP", "editInternalResourceDialogModeHttps": "HTTPS", + "editInternalResourceDialogModeSsh": "SSH", "editInternalResourceDialogScheme": "方案", "editInternalResourceDialogEnableSsl": "启用 TLS", "editInternalResourceDialogEnableSslDescription": "为目标的安全 HTTPS 连接启用 SSL/TLS 加密。", @@ -2098,6 +2099,7 @@ "createInternalResourceDialogModeCidr": "CIDR", "createInternalResourceDialogModeHttp": "HTTP", "createInternalResourceDialogModeHttps": "HTTPS", + "createInternalResourceDialogModeSsh": "SSH", "scheme": "方案", "createInternalResourceDialogScheme": "方案", "createInternalResourceDialogEnableSsl": "启用 TLS", diff --git a/server/routers/siteResource/createSiteResource.ts b/server/routers/siteResource/createSiteResource.ts index 632c5e3fd..e56de628a 100644 --- a/server/routers/siteResource/createSiteResource.ts +++ b/server/routers/siteResource/createSiteResource.ts @@ -67,7 +67,7 @@ const createSiteResourceSchema = z udpPortRangeString: portRangeStringSchema, disableIcmp: z.boolean().optional(), authDaemonPort: z.int().positive().optional(), - authDaemonMode: z.enum(["site", "remote"]).optional(), + authDaemonMode: z.enum(["site", "remote", "native"]).optional(), pamMode: z.enum(["passthrough", "push"]).optional(), domainId: z.string().optional(), // only used for http mode, we need this to verify the alias is unique within the org subdomain: z.string().optional() // only used for http mode, we need this to verify the alias is unique within the org @@ -213,6 +213,7 @@ export async function createSiteResource( disableIcmp, authDaemonPort, authDaemonMode, + pamMode, domainId, subdomain } = parsedBody.data; @@ -417,6 +418,7 @@ export async function createSiteResource( insertValues.authDaemonPort = authDaemonPort; if (authDaemonMode !== undefined) insertValues.authDaemonMode = authDaemonMode; + if (pamMode !== undefined) insertValues.pamMode = pamMode; } [newSiteResource] = await trx .insert(siteResources) diff --git a/server/routers/siteResource/updateSiteResource.ts b/server/routers/siteResource/updateSiteResource.ts index 7f8ef3e25..332b395b0 100644 --- a/server/routers/siteResource/updateSiteResource.ts +++ b/server/routers/siteResource/updateSiteResource.ts @@ -77,7 +77,8 @@ const updateSiteResourceSchema = z udpPortRangeString: portRangeStringSchema, disableIcmp: z.boolean().optional(), authDaemonPort: z.int().positive().nullish(), - authDaemonMode: z.enum(["site", "remote"]).optional(), + authDaemonMode: z.enum(["site", "remote", "native"]).optional(), + pamMode: z.enum(["passthrough", "push"]).optional(), domainId: z.string().optional(), subdomain: z.string().optional() }) @@ -222,6 +223,7 @@ export async function updateSiteResource( disableIcmp, authDaemonPort, authDaemonMode, + pamMode, domainId, subdomain } = parsedBody.data; @@ -430,13 +432,17 @@ export async function updateSiteResource( const sshPamSet = isLicensedSshPam && (authDaemonPort !== undefined || - authDaemonMode !== undefined) + authDaemonMode !== undefined || + pamMode !== undefined) ? { ...(authDaemonPort !== undefined && { authDaemonPort }), ...(authDaemonMode !== undefined && { authDaemonMode + }), + ...(pamMode !== undefined && { + pamMode }) } : {}; @@ -554,13 +560,17 @@ export async function updateSiteResource( const sshPamSet = isLicensedSshPam && (authDaemonPort !== undefined || - authDaemonMode !== undefined) + authDaemonMode !== undefined || + pamMode !== undefined) ? { ...(authDaemonPort !== undefined && { authDaemonPort }), ...(authDaemonMode !== undefined && { authDaemonMode + }), + ...(pamMode !== undefined && { + pamMode }) } : {}; diff --git a/src/app/[orgId]/settings/resources/client/page.tsx b/src/app/[orgId]/settings/resources/client/page.tsx index ad661f55b..295541e8c 100644 --- a/src/app/[orgId]/settings/resources/client/page.tsx +++ b/src/app/[orgId]/settings/resources/client/page.tsx @@ -56,7 +56,9 @@ export default async function ClientResourcesPage( pagination = responseData.pagination; } catch (e) {} - const siteIdParam = parsePositiveInt(searchParams.get("siteId") ?? undefined); + const siteIdParam = parsePositiveInt( + searchParams.get("siteId") ?? undefined + ); let initialFilterSite: { siteId: number; @@ -106,7 +108,10 @@ export default async function ClientResourcesPage( siteNiceId: siteResource.siteNiceIds[idx], online: siteResource.siteOnlines[idx] })), - mode: siteResource.mode, + mode: + siteResource.pamMode && siteResource.mode === "host" + ? "ssh" + : siteResource.mode, scheme: siteResource.scheme, ssl: siteResource.ssl, siteNames: siteResource.siteNames, @@ -125,6 +130,7 @@ export default async function ClientResourcesPage( disableIcmp: siteResource.disableIcmp || false, authDaemonMode: siteResource.authDaemonMode ?? null, authDaemonPort: siteResource.authDaemonPort ?? null, + pamMode: siteResource.pamMode ?? null, subdomain: siteResource.subdomain ?? null, domainId: siteResource.domainId ?? null, fullDomain: siteResource.fullDomain ?? null, diff --git a/src/components/ClientResourcesTable.tsx b/src/components/ClientResourcesTable.tsx index 156cc7f41..32e00be50 100644 --- a/src/components/ClientResourcesTable.tsx +++ b/src/components/ClientResourcesTable.tsx @@ -77,7 +77,7 @@ export type InternalResourceRow = { siteIds: number[]; siteNiceIds: string[]; // mode: "host" | "cidr" | "port"; - mode: "host" | "cidr" | "http"; + mode: "host" | "cidr" | "http" | "ssh"; scheme: "http" | "https" | null; ssl: boolean; // protocol: string | null; @@ -90,8 +90,9 @@ export type InternalResourceRow = { tcpPortRangeString: string | null; udpPortRangeString: string | null; disableIcmp: boolean; - authDaemonMode?: "site" | "remote" | null; + authDaemonMode?: "site" | "remote" | "native" | null; authDaemonPort?: number | null; + pamMode?: "passthrough" | "push" | null; subdomain?: string | null; domainId?: string | null; fullDomain?: string | null; @@ -410,6 +411,10 @@ export default function ClientResourcesTable({ { value: "http", label: t("editInternalResourceDialogModeHttp") + }, + { + value: "ssh", + label: t("editInternalResourceDialogModeSsh") } ]} selectedValue={searchParams.get("mode") ?? undefined} @@ -425,13 +430,14 @@ export default function ClientResourcesTable({ cell: ({ row }) => { const resourceRow = row.original; const modeLabels: Record< - "host" | "cidr" | "port" | "http", + "host" | "cidr" | "port" | "http" | "ssh", string > = { host: t("editInternalResourceDialogModeHost"), cidr: t("editInternalResourceDialogModeCidr"), port: t("editInternalResourceDialogModePort"), - http: t("editInternalResourceDialogModeHttp") + http: t("editInternalResourceDialogModeHttp"), + ssh: t("editInternalResourceDialogModeSsh") }; return {modeLabels[resourceRow.mode]}; } diff --git a/src/components/CreateInternalResourceDialog.tsx b/src/components/CreateInternalResourceDialog.tsx index dc1dacd4b..58eac9b92 100644 --- a/src/components/CreateInternalResourceDialog.tsx +++ b/src/components/CreateInternalResourceDialog.tsx @@ -47,7 +47,9 @@ export default function CreateInternalResourceDialog({ try { let data = { ...values }; if ( - (data.mode === "host" || data.mode === "http") && + (data.mode === "host" || + data.mode === "http" || + data.mode === "ssh") && isHostname(data.destination) ) { const currentAlias = data.alias?.trim() || ""; @@ -60,12 +62,15 @@ export default function CreateInternalResourceDialog({ } } + // "ssh" mode maps to "host" in the backend with SSH settings + const backendMode = data.mode === "ssh" ? "host" : data.mode; + await api.put< AxiosResponse<{ data: { siteResourceId: number } }> >(`/org/${orgId}/site-resource`, { name: data.name, siteIds: data.siteIds, - mode: data.mode, + mode: backendMode, destination: data.destination, enabled: true, ...(data.mode === "http" && { @@ -94,7 +99,25 @@ export default function CreateInternalResourceDialog({ authDaemonPort: data.authDaemonPort }) }), - ...((data.mode === "host" || data.mode == "cidr") && { + ...(data.mode === "ssh" && { + alias: + data.alias && + typeof data.alias === "string" && + data.alias.trim() + ? data.alias + : undefined, + pamMode: data.pamMode ?? undefined, + ...(data.authDaemonMode != null && { + authDaemonMode: data.authDaemonMode + }), + ...(data.authDaemonMode === "remote" && + data.authDaemonPort != null && { + authDaemonPort: data.authDaemonPort + }) + }), + ...((data.mode === "host" || + data.mode === "ssh" || + data.mode === "cidr") && { tcpPortRangeString: data.tcpPortRangeString, udpPortRangeString: data.udpPortRangeString, disableIcmp: data.disableIcmp ?? false diff --git a/src/components/EditInternalResourceDialog.tsx b/src/components/EditInternalResourceDialog.tsx index 859981f7d..11d31ba13 100644 --- a/src/components/EditInternalResourceDialog.tsx +++ b/src/components/EditInternalResourceDialog.tsx @@ -51,7 +51,9 @@ export default function EditInternalResourceDialog({ try { let data = { ...values }; if ( - (data.mode === "host" || data.mode === "http") && + (data.mode === "host" || + data.mode === "http" || + data.mode === "ssh") && isHostname(data.destination) ) { const currentAlias = data.alias?.trim() || ""; @@ -64,10 +66,13 @@ export default function EditInternalResourceDialog({ } } + // "ssh" mode maps to "host" in the backend with SSH settings + const backendMode = data.mode === "ssh" ? "host" : data.mode; + await api.post(`/site-resource/${resource.id}`, { name: data.name, siteIds: data.siteIds, - mode: data.mode, + mode: backendMode, niceId: data.niceId, destination: data.destination, ...(data.mode === "http" && { @@ -95,7 +100,24 @@ export default function EditInternalResourceDialog({ authDaemonPort: data.authDaemonPort || null }) }), - ...((data.mode === "host" || data.mode === "cidr") && { + ...(data.mode === "ssh" && { + alias: + data.alias && + typeof data.alias === "string" && + data.alias.trim() + ? data.alias + : null, + pamMode: data.pamMode ?? undefined, + ...(data.authDaemonMode != null && { + authDaemonMode: data.authDaemonMode + }), + ...(data.authDaemonMode === "remote" && { + authDaemonPort: data.authDaemonPort || null + }) + }), + ...((data.mode === "host" || + data.mode === "ssh" || + data.mode === "cidr") && { tcpPortRangeString: data.tcpPortRangeString, udpPortRangeString: data.udpPortRangeString, disableIcmp: data.disableIcmp ?? false diff --git a/src/components/InternalResourceForm.tsx b/src/components/InternalResourceForm.tsx index 357353d8e..b7800b9ed 100644 --- a/src/components/InternalResourceForm.tsx +++ b/src/components/InternalResourceForm.tsx @@ -136,7 +136,7 @@ export const cleanForFQDN = (name: string): string => // --- Types --- -export type InternalResourceMode = "host" | "cidr" | "http"; +export type InternalResourceMode = "host" | "cidr" | "http" | "ssh"; export type InternalResourceData = { id: number; @@ -151,8 +151,9 @@ export type InternalResourceData = { tcpPortRangeString?: string | null; udpPortRangeString?: string | null; disableIcmp?: boolean; - authDaemonMode?: "site" | "remote" | null; + authDaemonMode?: "site" | "remote" | "native" | null; authDaemonPort?: number | null; + pamMode?: "passthrough" | "push" | null; httpHttpsPort?: number | null; scheme?: "http" | "https" | null; ssl?: boolean; @@ -183,8 +184,9 @@ export type InternalResourceFormValues = { tcpPortRangeString?: string | null; udpPortRangeString?: string | null; disableIcmp?: boolean; - authDaemonMode?: "site" | "remote" | null; + authDaemonMode?: "site" | "remote" | "native" | null; authDaemonPort?: number | null; + pamMode?: "passthrough" | "push" | null; httpHttpsPort?: number | null; scheme?: "http" | "https"; ssl?: boolean; @@ -256,6 +258,10 @@ export function InternalResourceForm({ variant === "create" ? "createInternalResourceDialogModeHttp" : "editInternalResourceDialogModeHttp"; + const modeSshKey = + variant === "create" + ? "createInternalResourceDialogModeSsh" + : "editInternalResourceDialogModeSsh"; const schemeLabelKey = variant === "create" ? "createInternalResourceDialogScheme" @@ -301,7 +307,7 @@ export function InternalResourceForm({ .object({ name: z.string().min(1, t(nameRequiredKey)).max(255, t(nameMaxKey)), siteIds: siteIdsSchema, - mode: z.enum(["host", "cidr", "http"]), + mode: z.enum(["host", "cidr", "http", "ssh"]), destination: z .string() .min( @@ -332,8 +338,12 @@ export function InternalResourceForm({ tcpPortRangeString: createPortRangeStringSchema(t), udpPortRangeString: createPortRangeStringSchema(t), disableIcmp: z.boolean().optional(), - authDaemonMode: z.enum(["site", "remote"]).optional().nullable(), + authDaemonMode: z + .enum(["site", "remote", "native"]) + .optional() + .nullable(), authDaemonPort: z.number().int().positive().optional().nullable(), + pamMode: z.enum(["passthrough", "push"]).optional().nullable(), roles: z.array(tagSchema).optional(), users: z.array(tagSchema).optional(), clients: z @@ -456,6 +466,19 @@ export function InternalResourceForm({ number | null >(null); + const [sshServerMode, setSshServerMode] = useState<"standard" | "native">( + () => { + if ( + variant === "edit" && + resource && + resource.authDaemonMode === "native" + ) { + return "native"; + } + return "standard"; + } + ); + const [tcpPortMode, setTcpPortMode] = useState(() => variant === "edit" && resource ? getPortModeFromString(resource.tcpPortRangeString) @@ -494,8 +517,12 @@ export function InternalResourceForm({ tcpPortRangeString: resource.tcpPortRangeString ?? "*", udpPortRangeString: resource.udpPortRangeString ?? "*", disableIcmp: resource.disableIcmp ?? false, - authDaemonMode: resource.authDaemonMode ?? "site", + authDaemonMode: + resource.authDaemonMode === "native" + ? "native" + : (resource.authDaemonMode ?? "site"), authDaemonPort: resource.authDaemonPort ?? null, + pamMode: resource.pamMode ?? "passthrough", httpHttpsPort: resource.httpHttpsPort ?? null, scheme: resource.scheme ?? "http", ssl: resource.ssl ?? false, @@ -524,6 +551,7 @@ export function InternalResourceForm({ disableIcmp: false, authDaemonMode: "site", authDaemonPort: null, + pamMode: "passthrough", roles: [], users: [], clients: [] @@ -546,6 +574,15 @@ export function InternalResourceForm({ const httpConfigFullDomain = form.watch("httpConfigFullDomain"); const isHttpMode = mode === "http"; const authDaemonMode = form.watch("authDaemonMode") ?? "site"; + const pamMode = form.watch("pamMode") ?? "passthrough"; + const isNative = sshServerMode === "native"; + const showDaemonLocation = + mode === "ssh" && !isNative && pamMode === "push"; + const showDaemonPort = + mode === "ssh" && + !isNative && + pamMode === "push" && + authDaemonMode === "remote"; const hasInitialized = useRef(false); const previousResourceId = useRef(null); @@ -579,11 +616,13 @@ export function InternalResourceForm({ disableIcmp: false, authDaemonMode: "site", authDaemonPort: null, + pamMode: "passthrough", roles: [], users: [], clients: [] }); setSelectedSites([]); + setSshServerMode("standard"); setTcpPortMode("all"); setUdpPortMode("all"); setTcpCustomPorts(""); @@ -611,12 +650,19 @@ export function InternalResourceForm({ tcpPortRangeString: resource.tcpPortRangeString ?? "*", udpPortRangeString: resource.udpPortRangeString ?? "*", disableIcmp: resource.disableIcmp ?? false, - authDaemonMode: resource.authDaemonMode ?? "site", + authDaemonMode: + resource.authDaemonMode === "native" + ? "native" + : (resource.authDaemonMode ?? "site"), authDaemonPort: resource.authDaemonPort ?? null, + pamMode: resource.pamMode ?? "passthrough", roles: [], users: [], clients: [] }); + setSshServerMode( + resource.authDaemonMode === "native" ? "native" : "standard" + ); setSelectedSites(buildSelectedSitesForResource(resource)); setTcpPortMode( getPortModeFromString(resource.tcpPortRangeString) @@ -736,7 +782,7 @@ export function InternalResourceForm({ title: t("editInternalResourceDialogAccessPolicy"), href: "#" }, - ...(disableEnterpriseFeatures || mode !== "host" + ...(disableEnterpriseFeatures || mode !== "ssh" ? [] : [{ title: t("sshAccess"), href: "#" }]) ]} @@ -847,6 +893,12 @@ export function InternalResourceForm({ label: t( modeHttpKey ) + }, + { + value: "ssh" as const, + label: t( + modeSshKey + ) } ] : []) @@ -1574,152 +1626,256 @@ export function InternalResourceForm({ )} - {/* SSH Access tab (host mode only) */} - {!disableEnterpriseFeatures && mode === "host" && ( + {/* SSH Access tab (ssh mode only) */} + {!disableEnterpriseFeatures && mode === "ssh" && (
-
- -
- {t.rich( - "internalResourceAuthDaemonDescription", + + {/* Mode */} +
+

+ {t("sshServerMode")} +

+ + value={sshServerMode} + options={[ { - docsLink: (chunks) => ( - - {chunks} - - - ) + id: "native", + title: t("sshServerModePangolin"), + description: t( + "sshServerModeNativeDescription" + ), + disabled: sshSectionDisabled + }, + { + id: "standard", + title: t("sshServerModeStandard"), + description: t( + "sshServerModeStandardDescription" + ), + disabled: sshSectionDisabled } - )} -
-
-
- ( - - - {t( - "internalResourceAuthDaemonStrategyLabel" - )} - - - - value={ - field.value ?? undefined - } - options={[ - { - id: "site", - title: t( - "internalResourceAuthDaemonSite" - ), - description: t( - "internalResourceAuthDaemonSiteDescription" - ), - disabled: - sshSectionDisabled - }, - { - id: "remote", - title: t( - "internalResourceAuthDaemonRemote" - ), - description: t( - "internalResourceAuthDaemonRemoteDescription" - ), - disabled: - sshSectionDisabled - } - ]} - onChange={(v) => { - if (sshSectionDisabled) - return; - field.onChange(v); - if (v === "site") { - form.setValue( - "authDaemonPort", - null - ); - } - }} - cols={2} - /> - - - - )} + ]} + onChange={(v) => { + if (sshSectionDisabled) return; + setSshServerMode(v); + if (v === "native") { + form.setValue( + "authDaemonMode", + "native" + ); + form.setValue( + "authDaemonPort", + null + ); + } else { + form.setValue( + "authDaemonMode", + "site" + ); + } + }} + cols={2} /> - {authDaemonMode === "remote" && ( +
+ + {/* Auth Method (standard only) */} + {!isNative && ( +
+

+ {t("sshAuthenticationMethod")} +

( - - {t( - "internalResourceAuthDaemonPort" - )} - - value={ - field.value ?? "" + field.value ?? + "passthrough" } - onChange={(e) => { + options={[ + { + id: "passthrough", + title: t( + "sshAuthMethodManual" + ), + description: t( + "sshAuthMethodManualDescription" + ), + disabled: + sshSectionDisabled + }, + { + id: "push", + title: t( + "sshAuthMethodAutomated" + ), + description: t( + "sshAuthMethodAutomatedDescription" + ), + disabled: + sshSectionDisabled + } + ]} + onChange={(v) => { if ( sshSectionDisabled ) return; - const v = - e.target.value; - if (v === "") { - field.onChange( + field.onChange(v); + if ( + v === + "passthrough" + ) { + form.setValue( + "authDaemonPort", null ); - return; } - const num = - parseInt(v, 10); - field.onChange( - Number.isNaN( - num - ) - ? null - : num - ); }} + cols={2} /> )} /> - )} -
+
+ )} + + {/* Daemon Location (standard + push) */} + {showDaemonLocation && ( +
+

+ {t("sshAuthDaemonLocation")} +

+ ( + + + + value={ + (field.value as + | "site" + | "remote") ?? + "site" + } + options={[ + { + id: "site", + title: t( + "internalResourceAuthDaemonSite" + ), + description: t( + "sshDaemonLocationSiteDescription" + ), + disabled: + sshSectionDisabled + }, + { + id: "remote", + title: t( + "sshDaemonLocationRemote" + ), + description: t( + "sshDaemonLocationRemoteDescription" + ), + disabled: + sshSectionDisabled + } + ]} + onChange={(v) => { + if ( + sshSectionDisabled + ) + return; + field.onChange(v); + if (v === "site") { + form.setValue( + "authDaemonPort", + null + ); + } + }} + cols={2} + /> + + + + )} + /> +

+ {t("sshDaemonDisclaimer")}{" "} + + {t("learnMore")} + + +

+
+ )} + + {/* Daemon Port (standard + push + remote) */} + {showDaemonPort && ( + ( + + + {t("sshDaemonPort")} + + + { + if (sshSectionDisabled) + return; + const v = + e.target.value; + if (v === "") { + field.onChange( + null + ); + return; + } + const num = parseInt( + v, + 10 + ); + field.onChange( + Number.isNaN(num) + ? null + : num + ); + }} + /> + + + + )} + /> + )}
)}