Skip to content

HYPERFLEET-1288 - feat: support multiple JWT issuers with per-issuer config (DRAFT)#285

Open
pnguyen44 wants to merge 1 commit into
openshift-hyperfleet:mainfrom
pnguyen44:HYPERFLEET-1288
Open

HYPERFLEET-1288 - feat: support multiple JWT issuers with per-issuer config (DRAFT)#285
pnguyen44 wants to merge 1 commit into
openshift-hyperfleet:mainfrom
pnguyen44:HYPERFLEET-1288

Conversation

@pnguyen44

@pnguyen44 pnguyen44 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

HYPERFLEET-1288

The API can now authenticate requests from multiple identity providers. Each issuer is configured independently with its own JWK source, token header, audience, and identity resolution settings. This enables scenarios like accepting tokens from both an API gateway (e.g. Keycloak) and GCP service accounts without proxying through a single IdP.

Breaking change: the flat server.jwt.* / server.jwk.* / server.identity_header fields are replaced by a server.jwt.configs[] list. Existing deployments must migrate to the new format.

Changes

  • Replaced single-issuer JWT config with a per-issuer server.jwt.configs[] list
  • JWT handler validates incoming tokens against each configured issuer and propagates the matched config into request context
  • Identity resolution (for audit fields like created_by) is now per-issuer, not global
  • Added config validation: header collision checks, forbidden JWT source headers (Cookie/Set-Cookie), identity pattern validation
  • Updated Helm chart (values, schema, configmap template, NOTES.txt, README) and all docs

Test Plan

  • Unit tests added/updated
  • make test-all passes
  • make lint passes
  • Helm chart changes validated with make test-helm (if applicable)
  • Deployed to a development cluster and verified (if Helm/config changes)
  • E2E tests passed (if cross-component or major changes)

@openshift-ci openshift-ci Bot requested review from Mischulee and jsell-rh July 7, 2026 18:46
@openshift-ci

openshift-ci Bot commented Jul 7, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign ldornele for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

JWT auth now uses server.jwt.configs for per-issuer settings. The server and handler wiring pass issuer lists through context, caller identity resolution reads issuer config from request context, and validation now enforces per-issuer header, identity claim, and JWK source rules. Charts, docs, config loading, dump output, and tests were updated to match the new structure.
Estimated code review effort: 4 (Complex) | ~75 minutes


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Sec-02: Secrets In Log Output ❌ Error FAIL: logger.Info(ctx, config.DumpConfig(...)) logs a dump containing Password:; that's sensitive-log output (CWE-532). Remove Password/secret-bearing fields from DumpConfig or stop logging the full config dump.
✅ Passed checks (10 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
No Hardcoded Secrets ✅ Passed No hardcoded creds in changed production code; matches were only docs/test fixtures (placeholders, PEM blobs). CWE-798 not present.
No Weak Cryptography ✅ Passed PASS: no banned primitives, ECB, custom crypto, or secret compares found; diff uses jwt libs only, not CWE-327/CWE-208.
No Injection Vectors ✅ Passed No new CWE-89/CWE-78/CWE-79/CWE-502 sinks found in changed non-test files; targeted scans showed no yaml.Unmarshal, exec.Command, template.HTML, or SQL string-concat patterns.
No Privileged Containers ✅ Passed No CWE-250 issue: the PR patch adds no privileged/hostNetwork/hostPID/SYS_ADMIN/root settings in manifests/templates/Dockerfiles; chart changes only reshape JWT docs/config rendering.
No Pii Or Sensitive Data In Logs ✅ Passed No new or modified log statements expose PII, request/response bodies, or credentials; the only new warning logs a JWT validation error, not token contents. CWE-532 not observed.
Title check ✅ Passed Title matches the main change: multi-issuer JWT support with per-issuer config.
Description check ✅ Passed Description is aligned with the changeset and accurately describes the JWT/config migration.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Comment @coderabbitai help to get the list of available commands.

@hyperfleet-ci-bot

hyperfleet-ci-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

Risk Score: 5 — risk/high

Signal Detail Points
PR size 1711 lines (>500) +2
Sensitive paths cmd/ +2
Test coverage Missing tests for: cmd/hyperfleet-api/environments cmd/hyperfleet-api/server pkg/validation test +1

Computed by hyperfleet-risk-scorer

@pnguyen44

Copy link
Copy Markdown
Contributor Author

/retest

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
pkg/auth/auth_middleware.go (1)

35-53: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Silently discarded identity-resolution error on the non-mutating path.

When CallerIdentityFromRequest returns a non-empty err on a read request (e.g. malformed/oversized/pattern-rejected identity header or claim), execution falls through to Line 53 and serves the request anonymously with the error dropped — no log, no comment. That masks misconfiguration and rejected identity input at a security boundary. Make the degradation explicit (log at Warn + comment) rather than swallowing it.

As per path instructions: "Log-and-continue MUST be intentional degradation with a comment, not a missing return."

Proposed change
 		if isMutatingMethod(r.Method) {
 			msg := "Caller identity is required for mutating requests but could not be resolved"
 			if err != nil {
 				msg = fmt.Sprintf("Unable to resolve caller identity: %s", err)
 			}
 			handleError(ctx, w, r, errors.CodeAuthNoCredentials, msg)
 			return
 		}
 
+		// Non-mutating requests may proceed unauthenticated; surface (don't swallow)
+		// any identity-resolution error for observability.
+		if err != nil {
+			logger.WithError(ctx, err).Warn("caller identity resolution failed on non-mutating request; proceeding anonymously")
+		}
 		next.ServeHTTP(w, r)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/auth/auth_middleware.go` around lines 35 - 53, The non-mutating branch in
auth_middleware.go is silently swallowing a non-nil error from
CallerIdentityFromRequest when identity is empty, so make the fallback explicit
in that path. In authMiddleware, before calling next.ServeHTTP for read
requests, add a Warn-level log that records the CallerIdentityFromRequest
failure and add a brief comment explaining this intentional anonymous
degradation; keep mutating requests using handleError as they do now.

Source: Path instructions

pkg/auth/jwt_handler.go (1)

191-212: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject non-HTTPS server.jwt.configs[].jwk_cert_url in config validation
validate:"omitempty,url" accepts http://, and buildKeyfunc will fetch JWKS over that URL. That enables cleartext key retrieval and key substitution/token forgery (CWE-319, CWE-345). Enforce https:// before this reaches pkg/auth/jwt_handler.go.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/auth/jwt_handler.go` around lines 191 - 212, The JWT config validation
currently allows non-HTTPS jwk_cert_url values, which lets buildKeyfunc in
jwt_handler.go fetch JWKS over cleartext. Tighten validation for
server.jwt.configs[].jwk_cert_url so only https:// URLs are accepted, and reject
any http:// or non-HTTPS scheme before buildKeyfunc can call
keyfunc.NewDefaultCtx or jwkset.NewHTTPClient.
🧹 Nitpick comments (6)
charts/values.schema.json (1)

151-188: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Schema doesn't enforce a JWK source or exclusivity for it.

configs[] items only require issuer_url. An operator can --set-json an issuer with neither jwk_cert_url nor jwk_cert_file (or with both), and this passes helm template --validate/schema checks. Signature verification then depends entirely on Go-side startup validation catching it — if that check has a gap, JWTHandler may build a keyfunc with no key material (CWE-347: Improper Verification of Cryptographic Signature). Enforce it at the schema level too so bad configs fail before deployment, not at pod crash-loop.

🔒 Proposed fix: require exactly one JWK source
                   "items": {
                     "type": "object",
                     "properties": {
                       "issuer_url": { "type": "string", "description": "OIDC issuer URL for token validation (required)" },
                       "jwk_cert_url": { "type": "string", "description": "URL to fetch JWK certificates from" },
                       "jwk_cert_file": { "type": "string", "description": "Path to a local JWK certificate file" },
                       ...
                     },
-                    "required": ["issuer_url"]
+                    "required": ["issuer_url"],
+                    "oneOf": [
+                      { "required": ["jwk_cert_url"], "not": { "required": ["jwk_cert_file"] } },
+                      { "required": ["jwk_cert_file"], "not": { "required": ["jwk_cert_url"] } }
+                    ]
                   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@charts/values.schema.json` around lines 151 - 188, The configs[] item schema
only requires issuer_url and does not enforce a JWK source, so update
values.schema.json to make jwt/JWK configuration valid only when exactly one of
jwk_cert_url or jwk_cert_file is provided. Add schema-level validation for the
item object under configs to require one and only one JWK source, so invalid
combinations are rejected by Helm before deployment. Use the existing
issuer_url, jwk_cert_url, jwk_cert_file, and configs item definition to place
the constraint.
docs/authentication.md (1)

176-186: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Document forbidden header values for header/identity_header.

Per PR objectives, validation now rejects Cookie/Set-Cookie (and other collisions) for JWT source/identity headers — this prevents Bearer-style auth from being confused with cookie-based auth (CWE-352 relevant: cookies auto-attach cross-origin, unlike explicit headers). The field reference table and per-issuer examples don't mention this restriction, so operators will hit an undocumented startup validation error.

📝 Suggested table note
 | `header` | No | `Authorization` | HTTP header to read the JWT from |
 | `audience` | No | `""` (any) | Expected `aud` claim; skipped if empty |
 | `identity_claim` | No | `email` | JWT claim used as audit identity |
 | `identity_claim_pattern` | No | `""` (none) | Regex the identity value must match; non-matching requests get 401 |
 | `identity_header` | No | `""` (disabled) | HTTP header that overrides the JWT claim for audit identity (gateway-set only) |
+
+> `header` and `identity_header` must not be `Cookie` or `Set-Cookie` (and cannot collide with each other) — configuration validation rejects these at startup.

Also applies to: 234-249

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/authentication.md` around lines 176 - 186, Update the authentication
docs to mention that `header` and `identity_header` must not use forbidden
values like `Cookie` or `Set-Cookie`, and note that other header collisions are
rejected by startup validation. Add this restriction to the field reference
table for `header`/`identity_header` and to the per-issuer examples so readers
of `authentication.md` can see the constraint before configuring JWT source
headers.
docs/config.md (2)

292-294: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Heading level skips from h2 to h4.

#### Caller Identity follows ## Advanced Configuration with no h3 in between (the <summary> tag isn't a real heading), violating MD001.

📝 Proposed fix
-#### Caller Identity
+### Caller Identity
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/config.md` around lines 292 - 294, The heading hierarchy is skipping a
level in the configuration docs: the existing Caller Identity section is using a
fourth-level heading immediately after a second-level section. Update the
markdown heading in the Caller Identity section to the appropriate third-level
heading so it follows the surrounding structure and satisfies MD001, keeping the
section name and link target consistent.

Source: Linters/SAST tools


502-507: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Validation Rules section omits server.jwt.configs.

The rewritten Server validation bullets (port, timeouts) don't mention the new JWT issuer validation (required issuer_url, one of jwk_cert_url/jwk_cert_file, forbidden header values, identity pattern rules) even though this is the most significant breaking/validated surface in this release.

📝 Suggested addition
 **Server**:

 - `server.port`: 1-65535
 - `server.timeouts.read`: ≥ 1s
 - `server.timeouts.write`: ≥ 1s
+- `server.jwt.configs`: required non-empty when `server.jwt.enabled=true`; see [Issuer configuration reference](authentication.md#issuer-configuration-reference) for per-field validation rules
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/config.md` around lines 502 - 507, The Server validation rules section
is missing the new `server.jwt.configs` requirements, so update the Server
bullets to include the JWT validation surface alongside `server.port` and
timeouts. Add the key constraints from the `server.jwt.configs` schema: each
config must have `issuer_url`, must provide exactly one of `jwk_cert_url` or
`jwk_cert_file`, must reject forbidden header values, and must enforce the
identity pattern rules. Keep the wording concise but explicit so readers can see
the full set of Server validation requirements at a glance.
pkg/config/server_test.go (1)

106-139: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add negative coverage for the two untested security controls. Validate() enforces (a) forbidden JWT source header (cfg.Header = Cookie/Set-Cookie) and (b) header vs identity_header collision. Neither branch is exercised, so a regression that drops either check would pass CI. Error paths of security validators must be tested (TEST-02).

Suggested subtests
t.Run("forbidden JWT source header fails", func(t *testing.T) {
	RegisterTestingT(t)
	cfg := JWTConfig{Enabled: true, Configs: []JWTIssuerConfig{{
		IssuerURL:  "https://issuer.example.com",
		JWKCertURL: "https://keys.example.com",
		Header:     "Cookie",
	}}}
	err := cfg.Validate()
	Expect(err).To(HaveOccurred())
	Expect(err.Error()).To(ContainSubstring("not allowed as a JWT source"))
})

t.Run("header equal to identity_header fails", func(t *testing.T) {
	RegisterTestingT(t)
	cfg := JWTConfig{Enabled: true, Configs: []JWTIssuerConfig{{
		IssuerURL:      "https://issuer.example.com",
		JWKCertURL:     "https://keys.example.com",
		Header:         "X-Token",
		IdentityHeader: "x-token",
	}}}
	err := cfg.Validate()
	Expect(err).To(HaveOccurred())
	Expect(err.Error()).To(ContainSubstring("must differ from header"))
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/config/server_test.go` around lines 106 - 139, Add negative test coverage
in TestJWTConfig_ValidateIdentityHeader for the security checks in
JWTConfig.Validate(). Create subtests that exercise the forbidden JWT source
header path by setting JWTIssuerConfig.Header to Cookie or Set-Cookie and
asserting validation fails with the expected “not allowed as a JWT source”
message, and another subtest that sets Header and IdentityHeader to the same
value (case-insensitive) and asserts validation fails with the “must differ from
header” message. Keep using the existing base helper and validate through
cfg.Validate() so the branches in JWTConfig and JWTIssuerConfig are directly
covered.

Source: Path instructions

pkg/config/loader.go (1)

305-307: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Env/CLI override removal shifts JWT trust entirely to the YAML file (CWE-732). Since server.jwt.configs can no longer be set/overridden via env vars or flags, issuer URLs, JWK sources, and identity settings live only in the config file. Ensure deployments mount this file read-only with restrictive ownership/permissions and document the loss of env-var override for these security-sensitive fields (path instruction: "Environment variable overrides documented").

Also applies to: 375-376

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/config/loader.go` around lines 305 - 307, The JWT config override path
was removed, so the security-sensitive settings under server.jwt.configs now
come only from the YAML file. Update the config-loading/docs flow around
l.bindEnv and the server.jwt.enabled handling to clearly document that issuer
URLs, JWK sources, and identity settings are no longer env/CLI overridable, and
add deployment guidance that this file must be mounted read-only with
restrictive ownership and permissions. Also update the affected documentation
section for environment variable overrides so users know these fields must be
managed in the config file only.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/helper.go`:
- Around line 179-182: The default identity header is only being set in
startAPIServer, which can make IdentityHeaderName() return an empty value when
configs are read through other paths. Extract the "X-HyperFleet-Identity"
literal into a shared constant and apply it during config defaulting so both
startAPIServer and IdentityHeaderName() resolve the same value. Make sure the
default is set in the shared config setup used by
helper.Env()/environments.Environment() rather than only at server startup.

---

Outside diff comments:
In `@pkg/auth/auth_middleware.go`:
- Around line 35-53: The non-mutating branch in auth_middleware.go is silently
swallowing a non-nil error from CallerIdentityFromRequest when identity is
empty, so make the fallback explicit in that path. In authMiddleware, before
calling next.ServeHTTP for read requests, add a Warn-level log that records the
CallerIdentityFromRequest failure and add a brief comment explaining this
intentional anonymous degradation; keep mutating requests using handleError as
they do now.

In `@pkg/auth/jwt_handler.go`:
- Around line 191-212: The JWT config validation currently allows non-HTTPS
jwk_cert_url values, which lets buildKeyfunc in jwt_handler.go fetch JWKS over
cleartext. Tighten validation for server.jwt.configs[].jwk_cert_url so only
https:// URLs are accepted, and reject any http:// or non-HTTPS scheme before
buildKeyfunc can call keyfunc.NewDefaultCtx or jwkset.NewHTTPClient.

---

Nitpick comments:
In `@charts/values.schema.json`:
- Around line 151-188: The configs[] item schema only requires issuer_url and
does not enforce a JWK source, so update values.schema.json to make jwt/JWK
configuration valid only when exactly one of jwk_cert_url or jwk_cert_file is
provided. Add schema-level validation for the item object under configs to
require one and only one JWK source, so invalid combinations are rejected by
Helm before deployment. Use the existing issuer_url, jwk_cert_url,
jwk_cert_file, and configs item definition to place the constraint.

In `@docs/authentication.md`:
- Around line 176-186: Update the authentication docs to mention that `header`
and `identity_header` must not use forbidden values like `Cookie` or
`Set-Cookie`, and note that other header collisions are rejected by startup
validation. Add this restriction to the field reference table for
`header`/`identity_header` and to the per-issuer examples so readers of
`authentication.md` can see the constraint before configuring JWT source
headers.

In `@docs/config.md`:
- Around line 292-294: The heading hierarchy is skipping a level in the
configuration docs: the existing Caller Identity section is using a fourth-level
heading immediately after a second-level section. Update the markdown heading in
the Caller Identity section to the appropriate third-level heading so it follows
the surrounding structure and satisfies MD001, keeping the section name and link
target consistent.
- Around line 502-507: The Server validation rules section is missing the new
`server.jwt.configs` requirements, so update the Server bullets to include the
JWT validation surface alongside `server.port` and timeouts. Add the key
constraints from the `server.jwt.configs` schema: each config must have
`issuer_url`, must provide exactly one of `jwk_cert_url` or `jwk_cert_file`,
must reject forbidden header values, and must enforce the identity pattern
rules. Keep the wording concise but explicit so readers can see the full set of
Server validation requirements at a glance.

In `@pkg/config/loader.go`:
- Around line 305-307: The JWT config override path was removed, so the
security-sensitive settings under server.jwt.configs now come only from the YAML
file. Update the config-loading/docs flow around l.bindEnv and the
server.jwt.enabled handling to clearly document that issuer URLs, JWK sources,
and identity settings are no longer env/CLI overridable, and add deployment
guidance that this file must be mounted read-only with restrictive ownership and
permissions. Also update the affected documentation section for environment
variable overrides so users know these fields must be managed in the config file
only.

In `@pkg/config/server_test.go`:
- Around line 106-139: Add negative test coverage in
TestJWTConfig_ValidateIdentityHeader for the security checks in
JWTConfig.Validate(). Create subtests that exercise the forbidden JWT source
header path by setting JWTIssuerConfig.Header to Cookie or Set-Cookie and
asserting validation fails with the expected “not allowed as a JWT source”
message, and another subtest that sets Header and IdentityHeader to the same
value (case-insensitive) and asserts validation fails with the “must differ from
header” message. Keep using the existing base helper and validate through
cfg.Validate() so the branches in JWTConfig and JWTIssuerConfig are directly
covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 8d30c17b-6936-4d05-8b9f-61aae07b9860

📥 Commits

Reviewing files that changed from the base of the PR and between 4304dd2 and 5606b4c.

📒 Files selected for processing (29)
  • charts/README.md
  • charts/templates/NOTES.txt
  • charts/templates/configmap.yaml
  • charts/values.schema.json
  • charts/values.yaml
  • cmd/hyperfleet-api/server/api_server.go
  • cmd/hyperfleet-api/server/routes.go
  • configs/config.yaml.example
  • docs/api-operator-guide.md
  • docs/authentication.md
  • docs/config.md
  • docs/deployment.md
  • pkg/auth/auth_middleware.go
  • pkg/auth/context.go
  • pkg/auth/identity.go
  • pkg/auth/identity_test.go
  • pkg/auth/jwt_handler.go
  • pkg/auth/jwt_handler_test.go
  • pkg/config/dump.go
  • pkg/config/dump_test.go
  • pkg/config/flags.go
  • pkg/config/helpers_test.go
  • pkg/config/loader.go
  • pkg/config/loader_test.go
  • pkg/config/server.go
  • pkg/config/server_test.go
  • pkg/validation/identity_header.go
  • test/helper.go
  • test/integration/integration_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift-hyperfleet/architecture (manual)
  • openshift-hyperfleet/hyperfleet-api (manual)
  • openshift-hyperfleet/hyperfleet-sentinel (manual)
  • openshift-hyperfleet/hyperfleet-adapter (manual)
  • openshift-hyperfleet/hyperfleet-broker (manual)
💤 Files with no reviewable changes (2)
  • test/integration/integration_test.go
  • pkg/config/flags.go

Comment thread test/helper.go
@pnguyen44 pnguyen44 changed the title HYPERFLEET-1288 - feat: support multiple JWT issuers with per-issuer config HYPERFLEET-1288 - feat: support multiple JWT issuers with per-issuer config (DRAFT) Jul 7, 2026
@pnguyen44

Copy link
Copy Markdown
Contributor Author

/test presubmits-integration

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant