From fb2b64cb19bed0ad69e4e066293fc564af40443e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 12 May 2025 15:19:42 +0400 Subject: [PATCH] feat(identities/k8s): gateway support --- ...103022_identity-kubernetes-auth-gateway.ts | 25 ++++ .../db/schemas/identity-kubernetes-auths.ts | 3 +- .../ee/services/gateway/gateway-service.ts | 20 ++- backend/src/lib/api-docs/constants.ts | 2 + backend/src/lib/gateway/index.ts | 60 ++++++-- backend/src/server/routes/index.ts | 22 +-- .../v1/identity-kubernetes-auth-router.ts | 5 +- .../identity-kubernetes-auth-service.ts | 140 +++++++++++++----- .../identity-kubernetes-auth-types.ts | 2 + .../src/hooks/api/identities/mutations.tsx | 12 +- frontend/src/hooks/api/identities/types.ts | 3 + .../IdentityKubernetesAuthForm.tsx | 47 ++++++ 12 files changed, 267 insertions(+), 74 deletions(-) create mode 100644 backend/src/db/migrations/20250512103022_identity-kubernetes-auth-gateway.ts diff --git a/backend/src/db/migrations/20250512103022_identity-kubernetes-auth-gateway.ts b/backend/src/db/migrations/20250512103022_identity-kubernetes-auth-gateway.ts new file mode 100644 index 000000000..fcd9bfc3e --- /dev/null +++ b/backend/src/db/migrations/20250512103022_identity-kubernetes-auth-gateway.ts @@ -0,0 +1,25 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const hasGatewayIdColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "gatewayId"); + + if (!hasGatewayIdColumn) { + await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (table) => { + table.uuid("gatewayId").nullable(); + table.foreign("gatewayId").references("id").inTable(TableName.Gateway).onDelete("SET NULL"); + }); + } +} + +export async function down(knex: Knex): Promise { + const hasGatewayIdColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "gatewayId"); + + if (hasGatewayIdColumn) { + await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (table) => { + table.dropForeign("gatewayId"); + table.dropColumn("gatewayId"); + }); + } +} diff --git a/backend/src/db/schemas/identity-kubernetes-auths.ts b/backend/src/db/schemas/identity-kubernetes-auths.ts index 448cec386..3c9dd400c 100644 --- a/backend/src/db/schemas/identity-kubernetes-auths.ts +++ b/backend/src/db/schemas/identity-kubernetes-auths.ts @@ -29,7 +29,8 @@ export const IdentityKubernetesAuthsSchema = z.object({ allowedNames: z.string(), allowedAudience: z.string(), encryptedKubernetesTokenReviewerJwt: zodBuffer.nullable().optional(), - encryptedKubernetesCaCertificate: zodBuffer.nullable().optional() + encryptedKubernetesCaCertificate: zodBuffer.nullable().optional(), + gatewayId: z.string().uuid().nullable().optional() }); export type TIdentityKubernetesAuths = z.infer; diff --git a/backend/src/ee/services/gateway/gateway-service.ts b/backend/src/ee/services/gateway/gateway-service.ts index 5a17bc028..141797c98 100644 --- a/backend/src/ee/services/gateway/gateway-service.ts +++ b/backend/src/ee/services/gateway/gateway-service.ts @@ -590,13 +590,7 @@ export const gatewayServiceFactory = ({ return gateways; }; - // this has no permission check and used for dynamic secrets directly - // assumes permission check is already done - const fnGetGatewayClientTls = async (projectGatewayId: string) => { - const projectGateway = await projectGatewayDAL.findById(projectGatewayId); - if (!projectGateway) throw new NotFoundError({ message: `Project gateway with ID ${projectGatewayId} not found.` }); - - const { gatewayId } = projectGateway; + const fnGetGatewayClientTlsByGatewayId = async (gatewayId: string) => { const gateway = await gatewayDAL.findById(gatewayId); if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${gatewayId} not found.` }); @@ -638,6 +632,17 @@ export const gatewayServiceFactory = ({ }; }; + // this has no permission check and used for dynamic secrets directly + // assumes permission check is already done + const fnGetGatewayClientTls = async (projectGatewayId: string) => { + const projectGateway = await projectGatewayDAL.findById(projectGatewayId); + if (!projectGateway) throw new NotFoundError({ message: `Project gateway with ID ${projectGatewayId} not found.` }); + + const gatewayDetails = await fnGetGatewayClientTlsByGatewayId(projectGateway.gatewayId); + + return gatewayDetails; + }; + return { getGatewayRelayDetails, exchangeAllocatedRelayAddress, @@ -647,6 +652,7 @@ export const gatewayServiceFactory = ({ deleteGatewayById, getProjectGateways, fnGetGatewayClientTls, + fnGetGatewayClientTlsByGatewayId, heartbeat }; }; diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 6805575f2..d09760d8c 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -357,6 +357,7 @@ export const KUBERNETES_AUTH = { allowedNames: "The comma-separated list of trusted service account names that can authenticate with Infisical.", allowedAudience: "The optional audience claim that the service account JWT token must have to authenticate with Infisical.", + gatewayId: "The ID of the gateway to use when performing kubernetes API requests.", accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.", accessTokenTTL: "The lifetime for an access token in seconds.", accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.", @@ -373,6 +374,7 @@ export const KUBERNETES_AUTH = { allowedNames: "The new comma-separated list of trusted service account names that can authenticate with Infisical.", allowedAudience: "The new optional audience claim that the service account JWT token must have to authenticate with Infisical.", + gatewayId: "The ID of the gateway to use when performing kubernetes API requests.", accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.", accessTokenTTL: "The new lifetime for an acccess token in seconds.", accessTokenMaxTTL: "The new maximum lifetime for an acccess token in seconds.", diff --git a/backend/src/lib/gateway/index.ts b/backend/src/lib/gateway/index.ts index 84d801dda..7a94c6384 100644 --- a/backend/src/lib/gateway/index.ts +++ b/backend/src/lib/gateway/index.ts @@ -174,6 +174,8 @@ const setupProxyServer = async ({ return new Promise((resolve, reject) => { const server = net.createServer(); + let streamClosed = false; + // eslint-disable-next-line @typescript-eslint/no-misused-promises server.on("connection", async (clientConn) => { try { @@ -202,9 +204,15 @@ const setupProxyServer = async ({ // Handle client connection close clientConn.on("end", () => { - writer.close().catch((err) => { - logger.error(err); - }); + if (!streamClosed) { + try { + writer.close().catch((err) => { + logger.debug(err, "Error closing writer (already closed)"); + }); + } catch (error) { + logger.debug(error, "Error in writer close"); + } + } }); clientConn.on("error", (clientConnErr) => { @@ -249,14 +257,29 @@ const setupProxyServer = async ({ setupCopy(); // Handle connection closure clientConn.on("close", () => { - stream.destroy().catch((err) => { - proxyErrorMsg.push((err as Error)?.message); - }); + if (!streamClosed) { + streamClosed = true; + stream.destroy().catch((err) => { + logger.debug(err, "Stream already destroyed during close event"); + }); + } }); const cleanup = async () => { - clientConn?.destroy(); - await stream.destroy(); + try { + clientConn?.destroy(); + } catch (err) { + logger.debug(err, "Error destroying client connection"); + } + + if (!streamClosed) { + streamClosed = true; + try { + await stream.destroy(); + } catch (err) { + logger.debug(err, "Error destroying stream (might be already closed)"); + } + } }; clientConn.on("error", (clientConnErr) => { @@ -301,8 +324,17 @@ const setupProxyServer = async ({ server, port: address.port, cleanup: async () => { - server.close(); - await quicClient?.destroy(); + try { + server.close(); + } catch (err) { + logger.debug(err, "Error closing server"); + } + + try { + await quicClient?.destroy(); + } catch (err) { + logger.debug(err, "Error destroying QUIC client"); + } }, getProxyError: () => proxyErrorMsg.join(",") }); @@ -320,10 +352,10 @@ interface ProxyOptions { orgId: string; } -export const withGatewayProxy = async ( - callback: (port: number) => Promise, +export const withGatewayProxy = async ( + callback: (port: number) => Promise, options: ProxyOptions -): Promise => { +): Promise => { const { relayHost, relayPort, targetHost, targetPort, tlsOptions, identityId, orgId } = options; // Setup the proxy server @@ -339,7 +371,7 @@ export const withGatewayProxy = async ( try { // Execute the callback with the allocated port - await callback(port); + return await callback(port); } catch (err) { const proxyErrorMessage = getProxyError(); if (proxyErrorMessage) { diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index cb9931ec2..6367496f1 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1401,12 +1401,24 @@ export const registerRoutes = async ( identityUaDAL, licenseService }); + + const gatewayService = gatewayServiceFactory({ + permissionService, + gatewayDAL, + kmsService, + licenseService, + orgGatewayConfigDAL, + keyStore, + projectGatewayDAL + }); + const identityKubernetesAuthService = identityKubernetesAuthServiceFactory({ identityKubernetesAuthDAL, identityOrgMembershipDAL, identityAccessTokenDAL, permissionService, licenseService, + gatewayService, kmsService }); const identityGcpAuthService = identityGcpAuthServiceFactory({ @@ -1461,16 +1473,6 @@ export const registerRoutes = async ( identityDAL }); - const gatewayService = gatewayServiceFactory({ - permissionService, - gatewayDAL, - kmsService, - licenseService, - orgGatewayConfigDAL, - keyStore, - projectGatewayDAL - }); - const dynamicSecretProviders = buildDynamicSecretProviders({ gatewayService }); diff --git a/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts b/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts index 21759e0cd..fd01b9957 100644 --- a/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts +++ b/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts @@ -21,7 +21,8 @@ const IdentityKubernetesAuthResponseSchema = IdentityKubernetesAuthsSchema.pick( kubernetesHost: true, allowedNamespaces: true, allowedNames: true, - allowedAudience: true + allowedAudience: true, + gatewayId: true }).extend({ caCert: z.string(), tokenReviewerJwt: z.string().optional().nullable() @@ -106,6 +107,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide allowedNamespaces: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNamespaces), // TODO: validation allowedNames: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNames), allowedAudience: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedAudience), + gatewayId: z.string().uuid().optional().nullable().describe(KUBERNETES_AUTH.ATTACH.gatewayId), accessTokenTrustedIps: z .object({ ipAddress: z.string().trim() @@ -205,6 +207,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide allowedNamespaces: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNamespaces), // TODO: validation allowedNames: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNames), allowedAudience: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedAudience), + gatewayId: z.string().uuid().optional().nullable().describe(KUBERNETES_AUTH.UPDATE.gatewayId), accessTokenTrustedIps: z .object({ ipAddress: z.string().trim() diff --git a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts index 9c0e8d2dd..2e7950f80 100644 --- a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts +++ b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts @@ -4,6 +4,7 @@ import https from "https"; import jwt from "jsonwebtoken"; import { IdentityAuthMethod, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas"; +import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { @@ -13,6 +14,7 @@ import { import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; +import { withGatewayProxy } from "@app/lib/gateway"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { ActorType, AuthTokenType } from "../auth/auth-type"; @@ -43,6 +45,7 @@ type TIdentityKubernetesAuthServiceFactoryDep = { permissionService: Pick; licenseService: Pick; kmsService: Pick; + gatewayService: TGatewayServiceFactory; }; export type TIdentityKubernetesAuthServiceFactory = ReturnType; @@ -53,8 +56,43 @@ export const identityKubernetesAuthServiceFactory = ({ identityAccessTokenDAL, permissionService, licenseService, + gatewayService, kmsService }: TIdentityKubernetesAuthServiceFactoryDep) => { + const $gatewayProxyWrapper = async ( + inputs: { + gatewayId: string; + targetHost: string; + targetPort: number; + }, + gatewayCallback: (host: string, port: number) => Promise + ): Promise => { + const relayDetails = await gatewayService.fnGetGatewayClientTlsByGatewayId(inputs.gatewayId); + const [relayHost, relayPort] = relayDetails.relayAddress.split(":"); + + const callbackResult = await withGatewayProxy( + async (port) => { + const res = await gatewayCallback("localhost", port); + return res; + }, + { + targetHost: inputs.targetHost, + targetPort: inputs.targetPort, + relayHost, + relayPort: Number(relayPort), + identityId: relayDetails.identityId, + orgId: relayDetails.orgId, + tlsOptions: { + ca: relayDetails.certChain, + cert: relayDetails.certificate, + key: relayDetails.privateKey.toString() + } + } + ); + + return callbackResult; + }; + const login = async ({ identityId, jwt: serviceAccountJwt }: TLoginKubernetesAuthDTO) => { const identityKubernetesAuth = await identityKubernetesAuthDAL.findOne({ identityId }); if (!identityKubernetesAuth) { @@ -92,46 +130,70 @@ export const identityKubernetesAuthServiceFactory = ({ tokenReviewerJwt = serviceAccountJwt; } - const { data } = await axios - .post( - `${identityKubernetesAuth.kubernetesHost}/apis/authentication.k8s.io/v1/tokenreviews`, - { - apiVersion: "authentication.k8s.io/v1", - kind: "TokenReview", - spec: { - token: serviceAccountJwt, - ...(identityKubernetesAuth.allowedAudience ? { audiences: [identityKubernetesAuth.allowedAudience] } : {}) - } - }, - { - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${tokenReviewerJwt}` - }, - signal: AbortSignal.timeout(10000), - timeout: 10000, - // if ca cert, rejectUnauthorized: true - httpsAgent: new https.Agent({ - ca: caCert, - rejectUnauthorized: !!caCert - }) - } - ) - .catch((err) => { - if (err instanceof AxiosError) { - if (err.response) { - const { message } = err?.response?.data as unknown as { message?: string }; + const tokenReviewCallback = async (host: string = identityKubernetesAuth.kubernetesHost, port?: number) => { + let baseUrl = `https://${host}`; - if (message) { - throw new UnauthorizedError({ - message, - name: "KubernetesTokenReviewRequestError" - }); + if (port) { + baseUrl += `:${port}`; + } + + const res = await axios + .post( + `${baseUrl}/apis/authentication.k8s.io/v1/tokenreviews`, + { + apiVersion: "authentication.k8s.io/v1", + kind: "TokenReview", + spec: { + token: serviceAccountJwt, + ...(identityKubernetesAuth.allowedAudience ? { audiences: [identityKubernetesAuth.allowedAudience] } : {}) + } + }, + { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${tokenReviewerJwt}` + }, + signal: AbortSignal.timeout(10000), + timeout: 10000, + // if ca cert, rejectUnauthorized: true + httpsAgent: new https.Agent({ + ca: caCert, + // rejectUnauthorized: !!caCert, + rejectUnauthorized: false + }) + } + ) + .catch((err) => { + if (err instanceof AxiosError) { + if (err.response) { + const { message } = err?.response?.data as unknown as { message?: string }; + + if (message) { + throw new UnauthorizedError({ + message, + name: "KubernetesTokenReviewRequestError" + }); + } } } - } - throw err; - }); + throw err; + }); + + return res.data; + }; + + const [k8sHost, k8sPort] = identityKubernetesAuth.kubernetesHost.split(":"); + + const data = identityKubernetesAuth.gatewayId + ? await $gatewayProxyWrapper( + { + gatewayId: identityKubernetesAuth.gatewayId, + targetHost: k8sHost, + targetPort: k8sPort ? Number(k8sPort) : 443 + }, + tokenReviewCallback + ) + : await tokenReviewCallback(); if ("error" in data.status) throw new UnauthorizedError({ message: data.status.error, name: "KubernetesTokenReviewError" }); @@ -222,6 +284,7 @@ export const identityKubernetesAuthServiceFactory = ({ const attachKubernetesAuth = async ({ identityId, + gatewayId, kubernetesHost, caCert, tokenReviewerJwt, @@ -296,6 +359,7 @@ export const identityKubernetesAuthServiceFactory = ({ accessTokenMaxTTL, accessTokenTTL, accessTokenNumUsesLimit, + gatewayId, accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps), encryptedKubernetesTokenReviewerJwt: tokenReviewerJwt ? encryptor({ plainText: Buffer.from(tokenReviewerJwt) }).cipherTextBlob @@ -318,6 +382,7 @@ export const identityKubernetesAuthServiceFactory = ({ allowedNamespaces, allowedNames, allowedAudience, + gatewayId, accessTokenTTL, accessTokenMaxTTL, accessTokenNumUsesLimit, @@ -378,6 +443,7 @@ export const identityKubernetesAuthServiceFactory = ({ allowedNamespaces, allowedNames, allowedAudience, + gatewayId, accessTokenMaxTTL, accessTokenTTL, accessTokenNumUsesLimit, diff --git a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-types.ts b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-types.ts index b3bbcb49e..7a9cb88b5 100644 --- a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-types.ts +++ b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-types.ts @@ -13,6 +13,7 @@ export type TAttachKubernetesAuthDTO = { allowedNamespaces: string; allowedNames: string; allowedAudience: string; + gatewayId?: string | null; accessTokenTTL: number; accessTokenMaxTTL: number; accessTokenNumUsesLimit: number; @@ -28,6 +29,7 @@ export type TUpdateKubernetesAuthDTO = { allowedNamespaces?: string; allowedNames?: string; allowedAudience?: string; + gatewayId?: string | null; accessTokenTTL?: number; accessTokenMaxTTL?: number; accessTokenNumUsesLimit?: number; diff --git a/frontend/src/hooks/api/identities/mutations.tsx b/frontend/src/hooks/api/identities/mutations.tsx index e0077527f..9209a601c 100644 --- a/frontend/src/hooks/api/identities/mutations.tsx +++ b/frontend/src/hooks/api/identities/mutations.tsx @@ -741,7 +741,8 @@ export const useAddIdentityKubernetesAuth = () => { accessTokenTTL, accessTokenMaxTTL, accessTokenNumUsesLimit, - accessTokenTrustedIps + accessTokenTrustedIps, + gatewayId }) => { const { data: { identityKubernetesAuth } @@ -757,7 +758,8 @@ export const useAddIdentityKubernetesAuth = () => { accessTokenTTL, accessTokenMaxTTL, accessTokenNumUsesLimit, - accessTokenTrustedIps + accessTokenTrustedIps, + gatewayId } ); @@ -846,7 +848,8 @@ export const useUpdateIdentityKubernetesAuth = () => { accessTokenTTL, accessTokenMaxTTL, accessTokenNumUsesLimit, - accessTokenTrustedIps + accessTokenTrustedIps, + gatewayId }) => { const { data: { identityKubernetesAuth } @@ -862,7 +865,8 @@ export const useUpdateIdentityKubernetesAuth = () => { accessTokenTTL, accessTokenMaxTTL, accessTokenNumUsesLimit, - accessTokenTrustedIps + accessTokenTrustedIps, + gatewayId } ); diff --git a/frontend/src/hooks/api/identities/types.ts b/frontend/src/hooks/api/identities/types.ts index c5f8cbc4a..e5b9609a1 100644 --- a/frontend/src/hooks/api/identities/types.ts +++ b/frontend/src/hooks/api/identities/types.ts @@ -346,6 +346,7 @@ export type IdentityKubernetesAuth = { accessTokenMaxTTL: number; accessTokenNumUsesLimit: number; accessTokenTrustedIps: IdentityTrustedIp[]; + gatewayId?: string | null; }; export type AddIdentityKubernetesAuthDTO = { @@ -356,6 +357,7 @@ export type AddIdentityKubernetesAuthDTO = { allowedNamespaces: string; allowedNames: string; allowedAudience: string; + gatewayId?: string | null; caCert: string; accessTokenTTL: number; accessTokenMaxTTL: number; @@ -373,6 +375,7 @@ export type UpdateIdentityKubernetesAuthDTO = { allowedNamespaces?: string; allowedNames?: string; allowedAudience?: string; + gatewayId?: string | null; caCert?: string; accessTokenTTL?: number; accessTokenMaxTTL?: number; diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx index 87dc7dbfd..d7832fff8 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx @@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form"; import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; +import { useQuery } from "@tanstack/react-query"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; @@ -11,6 +12,8 @@ import { FormControl, IconButton, Input, + Select, + SelectItem, Tab, TabList, TabPanel, @@ -19,6 +22,7 @@ import { } from "@app/components/v2"; import { useOrganization, useSubscription } from "@app/context"; import { + gatewaysQueryKeys, useAddIdentityKubernetesAuth, useGetIdentityKubernetesAuth, useUpdateIdentityKubernetesAuth @@ -32,6 +36,7 @@ const schema = z .object({ kubernetesHost: z.string().min(1), tokenReviewerJwt: z.string().optional(), + gatewayId: z.string().optional().nullable(), allowedNames: z.string(), allowedNamespaces: z.string(), allowedAudience: z.string(), @@ -79,6 +84,8 @@ export const IdentityKubernetesAuthForm = ({ const { mutateAsync: updateMutateAsync } = useUpdateIdentityKubernetesAuth(); const [tabValue, setTabValue] = useState(IdentityFormTab.Configuration); + const { data: gateways, isPending: isGatewayLoading } = useQuery(gatewaysQueryKeys.list()); + const { data } = useGetIdentityKubernetesAuth(identityId ?? "", { enabled: isUpdate }); @@ -96,6 +103,7 @@ export const IdentityKubernetesAuthForm = ({ tokenReviewerJwt: "", allowedNames: "", allowedNamespaces: "", + gatewayId: null, allowedAudience: "", caCert: "", accessTokenTTL: "2592000", @@ -120,6 +128,7 @@ export const IdentityKubernetesAuthForm = ({ allowedNamespaces: data.allowedNamespaces, allowedAudience: data.allowedAudience, caCert: data.caCert, + gatewayId: data.gatewayId || null, accessTokenTTL: String(data.accessTokenTTL), accessTokenMaxTTL: String(data.accessTokenMaxTTL), accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit), @@ -157,6 +166,7 @@ export const IdentityKubernetesAuthForm = ({ accessTokenTTL, accessTokenMaxTTL, accessTokenNumUsesLimit, + gatewayId, accessTokenTrustedIps }: FormData) => { try { @@ -172,6 +182,7 @@ export const IdentityKubernetesAuthForm = ({ allowedAudience, caCert, identityId, + gatewayId: gatewayId || null, accessTokenTTL: Number(accessTokenTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), @@ -186,6 +197,7 @@ export const IdentityKubernetesAuthForm = ({ allowedNames: allowedNames || "", allowedNamespaces: allowedNamespaces || "", allowedAudience: allowedAudience || "", + gatewayId: gatewayId || null, caCert: caCert || "", accessTokenTTL: Number(accessTokenTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL), @@ -217,6 +229,7 @@ export const IdentityKubernetesAuthForm = ({ [ "kubernetesHost", "tokenReviewerJwt", + "gatewayId", "accessTokenTTL", "accessTokenMaxTTL", "accessTokenNumUsesLimit", @@ -280,6 +293,40 @@ export const IdentityKubernetesAuthForm = ({ )} /> + + ( + + + + )} + /> +