feat(gateway): add Kubernetes auth for gateway enrollment - #7525
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
💬 Discussion in Slack: #pr-review-infisical-7525-feat-gateway-add-kubernetes-auth-for-gateway-enrollment Posted by Review Police — reviews, comments, new commits, and CI failures will stream into this channel. |
|
| Filename | Overview |
|---|---|
| backend/src/ee/services/resource-auth-method/kubernetes-auth-fns.ts | Implements host checks, TokenReview calls, and allowlists, but the request remains vulnerable to DNS rebinding because it does not pin the validated address. |
| backend/src/ee/services/resource-auth-method/resource-auth-method-service.ts | Adds the Kubernetes auth lifecycle and encrypted secrets, but update-time validation can use a different credential set from the effective persisted configuration. |
| backend/src/ee/routes/v3/gateway-router.ts | Adds validated Kubernetes configuration and login API variants with audit and telemetry handling. |
| backend/src/ee/services/gateway-v2/gateway-v2-service.ts | Integrates Kubernetes configuration validation and encryption into gateway creation before opening the database transaction. |
| backend/src/db/migrations/20260804134122_resource-kubernetes-auth.ts | Adds the Kubernetes auth configuration table with encrypted credential columns and cascading ownership. |
| helm-charts/infisical-gateway/templates/deployment.yaml | Adds Kubernetes enrollment arguments and validates service-account token mounting requirements. |
| frontend/src/pages/organization/NetworkingPage/components/NetworkingAuthMethodForm.tsx | Adds the gateway-only Kubernetes auth form, allowlists, optional reviewer credentials, and explicit TLS behavior controls. |
Reviews (1): Last reviewed commit: "feat(gateway): add Kubernetes auth for g..." | Re-trigger Greptile
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 11 · PR risk: 0/10 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 77647efe76
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Gateways are deployed in-cluster, so they can authenticate with the projected service account token of their own pod instead of being handed an enrollment token. This removes the credential-distribution step that made automated gateway creation awkward. Backend: - New resource_kubernetes_auths table holding the cluster host, namespace and service account allowlists, an optional audience, and KMS-encrypted CA certificate and token reviewer JWT. - kubernetes added as a third ResourceAuthMethodType alongside aws and token, wired through create, update, read, revoke, and a new loginWithKubernetes. - Login submits a TokenReview to the cluster's API server, then checks the reviewed namespace, service account, and audience against the allowlists before minting a GATEWAY_ACCESS_TOKEN. - At least one allowlist is required, enforced in the route schema and again in the service, so an unrestricted config cannot authenticate any pod. - Host is checked against private address ranges and for reachability at config time, reusing the identity Kubernetes auth validators so a misconfiguration surfaces on save rather than at gateway start. - Audit log and telemetry carry the new method, plus the reviewed namespace and service account on success and a reason code on failure. - Extracted the auth-method view schema that was triplicated across the gateway, relay, and KMIP routers into one shared module. Frontend: - Kubernetes option in the gateway auth method form, with placeholders and tooltips matching the machine identity Kubernetes auth page. - Deployment card gains Helm and container-command tabs for this method. - Removed the read-view components orphaned by the v3 UI migration; the always-editable form is the display now. Docs and chart: - Kubernetes auth documented in gateway deployment, the CLI command reference, and the Helm chart reference. - Chart 1.3.0 adds gateway.enrollment.method=kubernetes with gatewayId and an optional service account token path. The CA certificate round-trips through the API since it is public key material already mounted into every pod. The reviewer JWT is a live cluster credential and is never returned, only reported as configured, following the alert channel convention for secret fields.
77647ef to
5009b21
Compare
Addresses review findings on the Kubernetes auth path. - TokenReview now goes through safeRequest, which validates and pins the connection to the addresses it resolved. The config-time host check could not stop a hostname that re-resolved to a private address before login, which let an unauthenticated login POST the reviewer JWT and service account token to an internal endpoint, and trust a forged TokenReview response back from it. - verifyTlsCertificate is honoured as configured instead of being silently dropped when no CA is set. A cluster CA is not in the system trust store, so that combination now fails the config-time check with a TLS error rather than quietly authenticating over an unverified connection. Turning verification off is still an explicit choice. - setMethod validates the host against the credentials that will actually be stored. Omitting a secret keeps the stored one, so validating with only what the request carried checked a different config than the one a gateway would later log in with. - Chart records that Kubernetes enrollment needs a CLI image containing --enroll-method=kubernetes, since the default tag predates it.
The Kubernetes host is operator-supplied and the login route is unauthenticated, so cap how much of the response Axios will buffer.
Bumps image.tag from 0.43.84 to 0.43.118, and the two places the helm docs mirror it. 0.43.118 does not exist yet: it is the next release after v0.43.117 and needs to be the one that carries Infisical/cli#353, so this has to be confirmed when that release is cut.
The preflight reused the identity Kubernetes auth validators, which run a DNS safety check and then issue the request on a separate lookup. A rebinding hostname could pass the check on a public address and resolve to a private one for the request itself, so a gateway editor could make the backend probe internal hosts and could leak a supplied reviewer JWT to them. Both preflight calls now go through safeRequest, which pins the connection to the addresses it validated, and the response size is bounded like the login path. Error classification is preserved, so the messages for an unresolvable host, a failed certificate check, and a reviewer without system:auth-delegator are unchanged.
Revoking a Kubernetes-auth gateway returns "kubernetes", which the frontend type did not allow. Nothing branches on it today, so this was a silent type lie rather than a visible bug.
Drops the three-line explanation above the kubernetes block in values.yaml and the copy of it in the chart docs. The per-key comments below it already say what each value is for.
Preserving the stored reviewer JWT for validation, added so the preflight checks the config that will actually be stored, meant the credential travelled to whatever host the request carried. Someone with gateway edit access could point the host at a server they control, omit the write-only field, and read the token off the Authorization header. The stored token is now only reused when the host is unchanged. Changing the host requires re-entering it, or sending an empty value to remove it. Both earlier properties still hold: the preflight validates the credentials that will be stored, and a host and reviewer pair is never stored unvalidated.
- kubernetesHost is normalised and validated in one place. The scheme is matched case-insensitively, so "HTTPS://host" no longer becomes "https://HTTPS://host" and "HTTP://host" no longer slips past the https-only check. A path, query or credentials are rejected, which is what let a typo'd host save and then kill the gateway on startup. The stored value is canonical, so a trailing slash no longer reads as a host change. - The preflight also rejects a 404 from /version, since that means the address does not serve the Kubernetes API. 401 and 403 are still allowed, because /version can require authentication. - The form can remove a stored token reviewer JWT again. Without it, changing the host was a dead end: the guard requires the token re-entered or cleared, and a blank field means keep. - Corrected the Verify TLS tooltip, which still claimed the toggle only takes effect with a CA certificate. It is honoured either way, and without a CA the system trust store is used, which a cluster CA will not be in. - Installing with serviceAccount.createAsAuthDelegator disabled now fails unless gateway.enrollment.kubernetes.externalTokenReview is set. Previously the install succeeded, the pod ran, and the gateway silently never connected. - Login failure reason codes, the TokenReview apiVersion/kind/path and the probe token are consts rather than repeated literals.
…n guard Selecting a gateway or pool to review through now requires AttachGateways, and a pool additionally requires the gateway pool entitlement. Machine identity Kubernetes auth gates its gateway selection the same way; this path only checked that the gateway existed in the org. The preservation guard also compared the host alone, so a preserved reviewer token could be redirected by routing it through a different gateway, trusting a different CA, or turning TLS verification off, none of which changed the host. It now covers every input that decides where the token travels and who can read it in transit. The stored CA is decrypted before the comparison rather than after, so an unchanged config still saves without re-entering the token.
…stor In gateway review mode the selected gateway supplies the TokenReview verdict, so it decides every login for the gateway that points at it. A pool is the wrong shape for that: its membership can change after the config is saved, under a permission unrelated to editing the gateway being protected, so a pool editor could add a gateway they control and have it authenticate as the protected one. Pools remain available for the token reviewer JWT mode, where the gateway only carries traffic and the API server still decides.
Attaching a gateway pool was gated on AttachGateways, so a role granted that but denied attach-gateway-pools could still route TokenReview traffic through a pool. Each proxy type now requires its own attach permission.
…bernetes-auth-support-for-gateway-enrollment # Conflicts: # helm-charts/infisical-gateway/CHANGELOG.md
…ation The committed schema listed the new columns after the timestamps, which is the order my dev database happened to have them in after the columns were added by hand. Running the migration from scratch produces the declared order, so the generated file never matched and the schema check failed.
A rejected review comes back as a Status object, where `status` is the string "Failure" rather than a review result. The truthiness check passed it through and the `in` operator then threw on a string, turning a denied login into a 500 with no explanation. Reject anything that is not an object and surface the API server's own message, so an expired reviewer token reads as "did not return a token review: Unauthorized" instead.
Selecting a pool that contains the gateway being configured was accepted, and the request then hung: pool selection can pick the very gateway trying to authenticate, which cannot vouch for itself because the proxy runs over a tunnel that only exists after it has authenticated. A gateway that is down has no heartbeat and would not be picked, so this only bites while a stale heartbeat is still inside its TTL, which is exactly the restart case the proxy exists to serve. The check moves into the service, where the pool membership DAL is reachable, and now covers both a directly selected gateway and pool membership.
The API field carried the database column name, gatewayV2Id. That "V2" only exists to separate the column from a legacy v1 gateway reference, a distinction gateways v3 does not have, and machine identity Kubernetes auth already exposes plain gatewayId while mapping to the same column. It also sat next to gatewayPoolId, which carries no V2, so the pair read as though they pointed at different generations of the same thing. The column keeps its name to match the identity table and the gateways_v2 foreign key; only the request and response fields change.
The dialog printed a helm repo URL for a host that does not exist, so copying the command failed at the first line. Use the URL the docs use, and note that the CLI form runs inside the cluster, since it reads the pod's service account token and so cannot reach a loopback address. Reviewer cycles are also now refused. Pointing a gateway at a reviewer that eventually reviews back through it left both unable to authenticate, each waiting on a tunnel the other had not built. The walk is depth bounded, which also keeps a cycle already in the data from spinning.
…view mode Gateway review mode is exempt from the reviewer preservation guard, because the token is not sent anywhere in that mode. That exemption let a host be parked: switch to gateway mode with a new host, which stores it without requiring the write-only token, then switch back to API mode where the host already matches and so does not count as a destination change. The preserved token was then sent to it. Two layers. The review mode now counts as part of the destination, so any transition requires re-entering the token. And a host is refused outright in gateway review mode, where it is unused, which removes the parking step.
…reviewer Editing the host with a reviewer token configured was rejected outright, which is a confusing wall for an ordinary edit. The concern was real, the stored token must not reach an address it was never validated against, but refusing the save is the wrong way to hold that line. Follow what PAM account updates already do and test only with credentials supplied in the request: the stored token is kept, and simply is not used against a destination it has not been validated against. The reviewer probe is skipped until the token is supplied again, so a bad token surfaces at the next login rather than at save. The config-time preflight also stops attaching the reviewer in gateway review mode, matching the login path, where the gateway reviews with its own service account and ours would only be exposed to it.
Deleting an organisation cascades away both a reviewer gateway and the auth config pointing at it, but the inbound constraint was checked at statement time, so it fired before the cascade had removed the referencer and the delete failed as a 500. Defer the check to COMMIT, by which point both rows are gone. RESTRICT is not deferrable in Postgres even when the constraint is declared so, hence NO ACTION, which gives the same protection for a direct gateway delete: that is still refused while another gateway reviews through it, and the delete path names the dependents before it gets that far.
Preserving it only guarded the config-time probe. The next gateway login decrypts the same token and sends it as a bearer to whatever destination is now stored, so pointing the host at a server you control still captured it. Clear it instead. The save succeeds, which is what made refusing it the wrong answer, and the credential does not follow the config somewhere it was never authorised for. The response reports it as no longer configured, so the operator sees they need to supply one for the new destination. An edit that changes nothing still keeps the stored token.
…bernetes-auth-support-for-gateway-enrollment # Conflicts: # frontend/src/pages/organization/NetworkingPage/GatewayDetailsByIDPage/components/GatewayDeploySection/GatewayDeploySection.tsx
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 35997175 | Triggered | Username Password | 4663b19 | backend/src/ee/services/dynamic-secret/providers/models.test.ts | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
0.43.118 predated the CLI change, so the chart defaulted to an image whose gateway command rejects --enroll-method=kubernetes. 0.43.123 is the first release cut after it landed.
Context
Adds Kubernetes auth as a third gateway auth method alongside
awsandtoken. The gateway authenticates with its own pod's service account token, which Infisical verifies against the cluster's TokenReview API, so no enrollment token needs distributing to the cluster.Requires Infisical/cli#353. PAM-341.
Screenshots
New "Kubernetes Auth" option under Authentication on the gateway detail page.
Steps to verify the change
gateway.enrollment.method=kubernetes.Type
Checklist
type(scope): short description(scope is optional, e.g.,fix: prevent crash on syncorfix(api): handle null response).