Skip to content

feat(openbao): import the JWT secrets plugin under infra/openbao - #647

Open
balajinvda wants to merge 8 commits into
mainfrom
feat/openbao-jwt-plugin
Open

feat(openbao): import the JWT secrets plugin under infra/openbao#647
balajinvda wants to merge 8 commits into
mainfrom
feat/openbao-jwt-plugin

Conversation

@balajinvda

@balajinvda balajinvda commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Why

OpenBao is approved for GitHub. Its image bakes in a JWT secrets plugin that is currently built by cloning a GitLab-only fork at a pinned revision, so a reproducible public build needs that source here.

What this is, and what it is not

The plugin is third-party: outfoxx/vault-plugin-secrets-jwt, Apache-2.0, Copyright 2021 Outfox, Inc. Authorship across its 74 commits is Kevin Wooten 64, Ian Fox 9, Brad Vernon 1. Exactly one commit is NVIDIA's.

So the usual "apply NVIDIA headers, copyright, NOTICE" pass would have been wrong here. Attribution is preserved instead of overwritten:

  • Apache-2.0 LICENSE (11,359 bytes) and upstream HEADER retained
  • all 17 upstream Go files keep their Outfox header
  • only the two files NVIDIA authored carry an NVIDIA header
  • NOTICE enumerates every modification, as Apache-2.0 section 4(b) requires
  • AGENTS.md tells the repository copyright stamper to stay out

NOTICE was written against the actual diff, not from the commit message. The commit says "allow setting aud"; the code disables the guard rejecting sub in a role's claims and registers a ReadOperation on the signing path.

friendlyid-go is removed, not imported

The image's plugin depended on github.com/mariuszs/friendlyid-go. Upstream carries no license at all (GitHub API reports NONE; LICENSE 404s on both branches), so it is not redistributable. The openbao repo's own openbao-osrb-report.txt already required its removal "with no source copied from friendlyid-go".

plugin/friendlyid.go is an independent base62 implementation written from the definition. The plugin used exactly one function, Encode; Decode was never called. Tests cover fixed width (including zero and max UUID), round-trip against a test-side decoder, collisions over 500 ids, alphabet containment, and the generator wiring.

Verified: friendlyid-go is absent from go.mod, go.sum, all sources, and go list -m all.

Placement

infra/openbao/plugins/vault-plugin-secrets-jwt.

src/ is first-party NVCF code and this is not, so placing it there would assert authorship we do not have. Nesting under infra/openbao keeps the source with the Dockerfile and build script that pin it, matching the infra/cassandra shape. There is no third_party/ tree today and inventing one for a single consumer separates the fork from the script that pins it.

The module path is renamed to the in-repo path. We do not track upstream, so resolving inside the monorepo is worth more than keeping diffs readable against Outfox. Apache-2.0 requires retaining the license, notices and a statement of changes; a rename does none of those harms.

Build strategy

GitHub. The module is deliberately not in go.work.bazel and has no BUILD.bazel, so the Bazel matrix never sees it. Its graph is 278 modules including hashicorp/vault/api and hashicorp/vault/sdk, and the root module uses no hashicorp/vault at all. Joining the root graph would put all of that into minimal version selection for every service in the repository, to build one plugin binary that ships inside one image.

The cost of that isolation is that nothing would otherwise compile or test this code, which matters now that NVIDIA owns modifications to it. .github/workflows/openbao-jwt-plugin.yml closes that: plain go, scoped to the one directory, path-filtered. It also asserts friendlyid-go stays out of the module graph, since its return would be a licensing regression rather than a build failure and nothing else would catch it.

GitLab. Unchanged by this PR. nvcf-internal continues to build the openbao image through the Dockerfile backend, and the openbao CI is preserved as-is. The follow-up that adds infra/openbao repoints build-jwt-plugin.sh at this in-repo source instead of cloning the GitLab fork.

Not imported on purpose

Upstream project machinery: .github/workflows/ above all, which would otherwise become live workflows in this repository, plus goreleaser config, the upstream Dockerfile, install script, Makefile and linter config. Source, tests and license material were retained in full.

Testing

go build ./... and go test ./... pass under the new module path, including the plugin's pre-existing suite.

Follow-ups

  • infra/openbao itself: public Dockerfile and scripts, with build-jwt-plugin.sh repointed at this source
  • nvcf-internal service config, openbao CI preserved as-is
  • overlays/openbao for the plugin binaries, which stay out of the OSS snapshot
  • regenerate the OSRB dependency counts, which the report notes must be redone once friendlyid-go is gone

Summary by CodeRabbit

  • New Features
    • Added an OpenBao secrets engine for signing and issuing JWTs.
    • Supports RSA and ECDSA algorithms, configurable claims, headers, audiences, subjects, and token lifetimes.
    • Provides role management, public JWKS publishing, automatic key rotation, and expired-key cleanup.
    • Builds and packages verified JWT plugin binaries for supported architectures.
  • Documentation
    • Added installation, configuration, usage, container, licensing, and provenance documentation.
  • Tests
    • Added end-to-end, runtime, and stress coverage for signing, verification, rotation, configuration, and role management.

balaji-g and others added 2 commits August 3, 2026 16:48
OpenBao is approved for GitHub, and its image bakes in a JWT secrets plugin
built from a fork that lives only on GitLab. A reproducible public build needs
that source here.

The plugin is third-party: outfoxx/vault-plugin-secrets-jwt, Apache-2.0,
Copyright 2021 Outfox, Inc. 73 of its 74 commits are upstream work. It is
placed under infra/ rather than src/ because src/ is first-party NVCF code and
this is not; nesting it under infra/openbao keeps it with the Dockerfile and
build script that pin it, the same shape as infra/cassandra.

Attribution is preserved rather than overwritten: the Apache-2.0 LICENSE and
the upstream HEADER stay, all 17 upstream files keep their Outfox header, and
NOTICE enumerates every NVIDIA modification as section 4(b) requires. Only the
two files NVIDIA authored carry an NVIDIA header. AGENTS.md tells the
copyright stamper to stay out.

The friendlyid-go dependency is gone. Upstream mariuszs/friendlyid-go carries
no license at all, so it is not redistributable, and the repository's own OSRB
report already required its removal with no source copied. plugin/friendlyid.go
is an independent base62 implementation written from the definition, with
tests covering fixed width, round-trip, collisions and alphabet containment.

The module path is renamed to the in-repo path. We do not track upstream, so
resolving inside the monorepo is worth more than keeping diffs readable
against Outfox.

Upstream project machinery is deliberately not imported: GitHub Actions
workflows above all, which would otherwise become live workflows in this
repository, plus goreleaser, the upstream Dockerfile, install script, Makefile
and linter config.

Co-authored-by: Balaji Ganesan <bganesan@nvidia.com>
The imported plugin is a standalone Go module with no BUILD.bazel and no entry
in go.work.bazel, so the Bazel matrix never sees it and nothing would compile
or test it. NVIDIA owns modifications to this code now, so that gap matters.

It is kept out of the root graph on purpose. Its module graph is 278 modules
including hashicorp/vault/api and hashicorp/vault/sdk, and the root module uses
no hashicorp/vault at all today. Joining go.work.bazel would pull all of it
into minimal version selection for every service in the repository in order to
build one plugin binary that ships inside a single image.

This job is plain go scoped to that one directory, path-filtered so it only
runs when the plugin changes.

It also asserts friendlyid-go stays out of the module graph. That project has
no license and is not redistributable, so its return would be a licensing
regression rather than a build failure, and nothing else in CI would notice.

Co-authored-by: Balaji Ganesan <bganesan@nvidia.com>
@balajinvda
balajinvda requested review from a team as code owners August 4, 2026 00:17
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 38339144-70c4-437a-86c5-0600f25020ff

📥 Commits

Reviewing files that changed from the base of the PR and between be90a69 and 27687ba.

📒 Files selected for processing (1)
  • dependencies.md

📝 Walkthrough

Walkthrough

Added an isolated OpenBao JWT secrets plugin. The change includes configuration and role APIs, JWT signing, JWKS publication, key lifecycle management, licensing and documentation, CI checks, containerized tests, dependency inventory updates, and OpenBao packaging.

Changes

OpenBao JWT secrets plugin

Layer / File(s) Summary
Project setup and repository metadata
.github/workflows/*, infra/openbao/plugins/vault-plugin-secrets-jwt/{AGENTS.md,CLAUDE.md,HEADER,LICENSE,NOTICE,README.md,go.mod}
Added the isolated Go module, repository guidance, licensing files, attribution notices, documentation, and CI checks.
Backend, configuration, and signing foundation
infra/openbao/plugins/vault-plugin-secrets-jwt/cmd/*, plugin/{backend.go,config.go,friendlyid.go,policy_signer.go,token.go,util.go}, plugin/*_test.go
Added plugin startup, backend lifecycle hooks, cached configuration, key rotation and pruning, base62 identifiers, token schemas, and Vault policy signing.
Configuration and role endpoints
infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/{path_config.go,path_config_test.go,path_roles.go,path_roles_test.go}
Added configuration and role CRUD operations with validation for algorithms, durations, claims, headers, subjects, and audiences.
JWT signing and JWKS endpoints
infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/{path_sign.go,path_sign_test.go,path_jwks.go,path_jwks_test.go}
Added role-based JWT signing, claim and header handling, temporal claims, compact token responses, public JWK conversion, and endpoint tests.
Container and end-to-end validation
infra/openbao/plugins/vault-plugin-secrets-jwt/test/*
Added Docker-based Vault environments, a JWT verification CLI, functional tests, and concurrent stress tests.
OpenBao packaging and delivery
infra/openbao/{Dockerfile,README.md}, infra/openbao/scripts/*, infra/openbao/files/plugins/*, infra/openbao/upgrade/*
Added local plugin builds, cross-architecture verification, runtime smoke tests, provenance data, license-header utilities, dependency inventory entries, and upgrade-test packaging.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested reviewers: kristinapathak

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title follows Conventional Commits and accurately describes the primary feature: importing the JWT secrets plugin under OpenBao.
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.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/openbao-jwt-plugin

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

@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: 10

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (16)
infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/backend_test.go-92-92 (1)

92-92: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

+ 1 adds one nanosecond, not one second.

config.KeyRotationPeriod is a time.Duration, so the untyped constant 1 is one nanosecond. The tests wait for the rotation period plus a one-nanosecond margin. The margin depends on the small delay between key creation and the sleep call, so these tests can fail intermittently under load. Use an explicit margin such as time.Second. This is upstream test code; the CI guideline requires go test ./... to pass, so a stable margin is worthwhile.

💚 Proposed change (apply to each sleep)
-	time.Sleep(config.KeyRotationPeriod + 1)
+	time.Sleep(config.KeyRotationPeriod + time.Second)
As per coding guidelines: "The plugin's tests must pass with `go test ./...`".

Also applies to: 138-138, 192-192, 202-202, 212-212

🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/backend_test.go` at
line 92, Update every time.Sleep call in the affected backend tests to add an
explicit one-second margin to config.KeyRotationPeriod instead of the implicit
one-nanosecond + 1. Apply this consistently to the sleeps near the existing
rotation-period checks, preserving the test flow while making key-rotation
timing reliable.

Source: Coding guidelines

infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/policy_signer.go-50-52 (1)

50-52: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject or intentionally convert non-string role headers.

Role.Headers accepts arbitrary values. Numeric and boolean values reach fmt.Sprintf("%s", v) and become strings such as %!s(float64=1.5). Reject non-string values or use an intentional conversion such as %v.

🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/policy_signer.go`
around lines 50 - 52, Update the ExtraHeaders processing in the policy signer to
handle non-string Role.Headers values intentionally: either validate and reject
values that are not strings, or convert them using a general value format such
as %v. Ensure numeric and boolean headers never produce malformed %!s(...)
output while preserving existing string header behavior.
infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/util.go-70-78 (1)

70-78: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Import the hash implementations explicitly. The plugin currently gets crypto/sha1, crypto/sha256, and crypto/sha512 through crypto/x509, but this creates unrelated transitive coupling. Add blank imports for those packages so createKeyId and PolicySigner.sign cannot panic if that dependency changes. Keep SHA-1 unless consumers can migrate because changing it changes existing kid values.

🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/util.go` around lines
70 - 78, Update the imports in util.go to explicitly blank-import crypto/sha1,
crypto/sha256, and crypto/sha512, ensuring the hash implementations used by
createKeyId and PolicySigner.sign are registered independently of crypto/x509.
Preserve SHA-1 and the existing createKeyId output so current kid values remain
unchanged.
infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_sign.go-153-157 (1)

153-157: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The %T verb reports the wrong type for a non-string audience entry. Both files use the same copied audience loop. Each passes audEntry to %T. After the type assertion fails, audEntry holds the zero-value string, so the message always reports string and never names the actual type. Pass rawAudEntry instead.

  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_sign.go#L153-L157: change the operand on Line 156 from audEntry to rawAudEntry.
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_roles.go#L264-L268: change the operand on Line 267 from audEntry to rawAudEntry.

The audience validation block is duplicated between the two files. Extract it into a shared helper so a single fix covers both paths.

🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_sign.go` around
lines 153 - 157, The audience validation duplicated in path_sign.go lines
153-157 and path_roles.go lines 264-268 reports the asserted zero-value string
type; extract the shared validation into a common helper and pass rawAudEntry to
the %T diagnostic so both paths report the actual non-string type. Update both
call sites to use the helper while preserving existing invalid-request behavior.
infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_roles_test.go-138-150 (1)

138-150: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert the result of the sub claim role write.

Line 139 assigns err and never checks it. Line 147 overwrites err. The NVIDIA change allows sub in the role claims field, but no assertion covers that behavior. Add the success check.

💚 Proposed fix
-	// added for nv
-	err := writeRole(b, storage, role, role+".example.com", map[string]interface{}{"sub": "allowed"}, map[string]interface{}{})
-
-	// added for nv
-	// sub claim is allowed in role's claims field
-	// if err == nil {
-	// t.Fatalf("Create role should have failed")
-	// }
+	// The 'sub' claim is permitted in the role 'claims' field.
+	err := writeRole(b, storage, role, role+".example.com", map[string]interface{}{"sub": "allowed"}, map[string]interface{}{})
+	if err != nil {
+		t.Fatalf("create role with 'sub' claim should have succeeded: %s\n", err)
+	}
🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_roles_test.go`
around lines 138 - 150, Update the first writeRole call in the role test to
assert that creating a role with the sub claim succeeds, before reusing err for
the foo claim case. Keep the existing failure assertion for the subsequent
writeRole call unchanged.
infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_config.go-155-157 (1)

155-157: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use %v for the []int slice in the error message.

%s applied to []int renders as [%!s(int=2048) %!s(int=3072) ...]. The operator sees a malformed list of supported values.

🐛 Proposed fix
-			return logical.ErrorResponse("unsupported rsa_key_bits, must be one of %s", AllowedRSAKeyBits), logical.ErrInvalidRequest
+			return logical.ErrorResponse("unsupported rsa_key_bits, must be one of %v", AllowedRSAKeyBits), logical.ErrInvalidRequest
🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_config.go` around
lines 155 - 157, Update the error response in the rsa_key_bits validation within
the configuration handler to format AllowedRSAKeyBits with a verb suitable for
[]int, so the supported values render correctly while preserving the existing
invalid-request behavior.
infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_sign.go-127-138 (1)

127-138: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Line 136 omits the operand for %T.

The message renders as 'sub' claim was %!T(MISSING), not string. Pass rawSub.

The related %T defect at Line 156 shares a root cause with path_roles.go Line 267. See the consolidated comment.

🐛 Proposed fix
-			return logical.ErrorResponse("'sub' claim was %T, not string"), logical.ErrInvalidRequest
+			return logical.ErrorResponse("'sub' claim was %T, not string", rawSub), logical.ErrInvalidRequest
🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_sign.go` around
lines 127 - 138, Update the type-mismatch error response in the claims
validation logic to pass rawSub as the operand for the %T formatter. Preserve
the existing error message and invalid-request return behavior.
infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_sign.go-38-49 (1)

38-49: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Remove unsupported headers from the sign request test.

pathSign does not declare or read keyHeaders. The SDK ignores the key and adds a warning. TestPrivateHeader passes because the role already defines tid=12345. If per-request headers are intended, add the schema, read the field, and validate it against config.allowedHeadersMap.

🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_sign.go` around
lines 38 - 49, Remove the unsupported headers field from the sign request test
associated with pathSign, since pathSign does not declare or read keyHeaders.
Keep the test focused on the role-defined tid=12345 claim; only add per-request
header support if implementing the full schema, field-reading, and
config.allowedHeadersMap validation path.
infra/openbao/plugins/vault-plugin-secrets-jwt/README.md-125-137 (1)

125-137: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use headers terminology in the Allowed Headers section.

The section documents allowed_headers, but the example text calls iss and path “claims.” Replace “claims” with “headers” to match the API field and command.

🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/README.md` around lines 125 -
137, Update the Allowed Headers section in the README to refer to iss and path
as headers rather than claims, while leaving the allowed_headers command and
surrounding documentation unchanged.
infra/openbao/plugins/vault-plugin-secrets-jwt/README.md-181-187 (1)

181-187: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Quote wildcard patterns in shell examples.

An unquoted * is expanded by the shell when matching files exist. Quote both subject_pattern and audience_pattern in the configuration and role commands.

Proposed fix
- vault write jwt/config subject_pattern=*.example.com
+ vault write jwt/config subject_pattern='*.example.com'
- vault write jwt/config audience_pattern=*.example.com
+ vault write jwt/config audience_pattern='*.example.com'
- vault write jwt/roles/test-role subject_pattern=*.example.com
+ vault write jwt/roles/test-role subject_pattern='*.example.com'
- vault write jwt/roles/test-role audience_pattern=*.example.com
+ vault write jwt/roles/test-role audience_pattern='*.example.com'

Also applies to: 268-274

🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/README.md` around lines 181 -
187, Quote the wildcard values in the README command examples for both
subject_pattern and audience_pattern, including the corresponding configuration
and role commands referenced by the comment, so the shell passes the literal *.
Preserve the existing commands and patterns otherwise.
infra/openbao/plugins/vault-plugin-secrets-jwt/README.md-142-149 (1)

142-149: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the remaining user-facing spelling and grammar errors.

Fix ES256algorithm, 2048 bit, stings, and rewrote. These errors reduce clarity in the usage documentation.

Also applies to: 189-190, 309-310

🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/README.md` around lines 142 -
149, Correct the remaining spelling and grammar errors in the README: add the
missing space in “ES256 algorithm,” hyphenate “2048-bit,” and fix the
misspellings “stings” and “rewrote.” Apply these edits at the referenced
documentation occurrences while preserving the surrounding usage instructions.

Source: Linters/SAST tools

infra/openbao/plugins/vault-plugin-secrets-jwt/AGENTS.md-3-7 (1)

3-7: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Separate repository policy from Apache-2.0 requirements.

Apache-2.0 section 4(b) requires prominent notices in modified files. It does not require a complete modification inventory in NOTICE. Keep the inventory because repository policy requires it, but describe the legal obligation accurately.

  • infra/openbao/plugins/vault-plugin-secrets-jwt/AGENTS.md#L3-L7: replace the section 4(b) attribution with repository-policy wording.
  • infra/openbao/plugins/vault-plugin-secrets-jwt/NOTICE#L19-L20: replace “As required by section 4(b)” with wording that identifies the list as the repository's compliance record.

As per coding guidelines, preserve upstream headers and record NVIDIA-authored changes in NOTICE.

🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/AGENTS.md` around lines 3 - 7,
Update the section 4(b) attribution in
infra/openbao/plugins/vault-plugin-secrets-jwt/AGENTS.md lines 3-7 to describe
the modification inventory as repository policy, not an Apache-2.0 requirement.
Update infra/openbao/plugins/vault-plugin-secrets-jwt/NOTICE lines 19-20 to
identify the list as the repository’s compliance record instead of saying it is
required by section 4(b); preserve upstream headers and the NVIDIA change
inventory.

Source: Coding guidelines

infra/openbao/plugins/vault-plugin-secrets-jwt/README.md-5-5 (1)

5-5: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use signing terminology for the supported algorithms.

This plugin signs JWTs. ES256, ES384, ES512, and the RS* algorithms are signature algorithms, not encryption algorithms. Rename this section and use “asymmetric signing” so users do not infer token confidentiality.

Proposed wording
-# Encryption and Key Management
+# Signing and Key Management
...
-The plugin supports a subset of the asymmetric encryption algorithms outlined in the JWT
+The plugin supports a subset of the asymmetric signing algorithms outlined in the JWT
 specification.
...
-Note: Due to its reliance on asymmetric encryption, the plugin will not support symmetric algorithms.
+Note: The plugin uses asymmetric signing algorithms and does not support symmetric algorithms.

Also applies to: 33-54

🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/README.md` at line 5, Update
the README section heading and related wording covering the supported ES256,
ES384, ES512, and RS* algorithms to use “signing” terminology, specifically
describing them as asymmetric signing rather than encryption, while preserving
the existing algorithm documentation.
infra/openbao/plugins/vault-plugin-secrets-jwt/README.md-2-2 (1)

2-2: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use a valid heading hierarchy.

The document jumps from an H1 directly to H3 for the introductory subtitle and Early Access. Change both headings to H2.

Also applies to: 29-29

🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/README.md` at line 2, Update
the introductory subtitle and the “Early Access” heading in the README to use H2
markers instead of H3, preserving the existing heading text and valid hierarchy
beneath the document’s H1.

Source: Linters/SAST tools

infra/openbao/plugins/vault-plugin-secrets-jwt/README.md-312-315 (1)

312-315: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the Quick Links list render as Markdown.

The four-space indentation makes the list a code block. Remove the indentation so the links render as a Markdown list.

Proposed fix
-    - Vault Website: https://www.vaultproject.io
-    - Main Project Github: https://www.github.com/hashicorp/vault
-    - JWT docs: https://jwt.io
+- Vault Website: https://www.vaultproject.io
+- Main Project Github: https://www.github.com/hashicorp/vault
+- JWT docs: https://jwt.io
🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/README.md` around lines 312 -
315, Remove the leading indentation from each item under the “Quick Links”
heading so the Vault Website, Main Project Github, and JWT docs entries render
as a Markdown list rather than a code block.

Source: Linters/SAST tools

infra/openbao/plugins/vault-plugin-secrets-jwt/README.md-96-104 (1)

96-104: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Use a repository-owned image or remove the container claim.

outfoxx/vault is the upstream image. This repository contains no image definition, and NOTICE states that the upstream Dockerfile was omitted. Document the repository build path and image name, or remove these instructions.

🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/README.md` around lines 96 -
104, Update the Container section in the README to remove the unsupported
outfoxx/vault testing instructions, or replace them with the repository-owned
image name and documented build path. Do not retain a claim that this repository
provides a pre-packaged container without identifying its local build definition
and image.
🧹 Nitpick comments (4)
infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/config.go (1)

252-252: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

DefaultConfig shares the package-level DefaultAllowedClaims slice.

Every default Config points at the same backing array. A later in-place modification of AllowedClaims on one config changes the default for all other backends in the process. Copy the slice.

♻️ Proposed change
-	c.AllowedClaims = DefaultAllowedClaims
+	c.AllowedClaims = append([]string(nil), DefaultAllowedClaims...)
🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/config.go` at line 252,
Update DefaultConfig where AllowedClaims is initialized so each Config receives
an independent copy of DefaultAllowedClaims rather than sharing its backing
array; preserve the existing default values while preventing later in-place
mutations from affecting other configurations.
infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_sign_test.go (1)

30-151: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Merge the two near-identical sign helpers.

getSignedTokenWithClaims and getSignedTokenWithoutClaims differ only in the operation and in whether Data is set. Everything after the HandleRequest call is duplicated. Extract one helper that takes the operation and an optional data map.

The merge also fixes a duplicated defect. Line 71 and Line 130 format err into the message, but err is nil at that point because the preceding FetchJWKS call succeeded. The message renders error locating unique public keys: %!s(<nil>). Report the key ID and the match count instead.

♻️ Proposed message fix
 	matchingPublicKeys := publicKeys.Key(token.Headers[0].KeyID)
 	if len(matchingPublicKeys) != 1 {
-		return fmt.Errorf("error locating unique public keys: %s", err)
+		return fmt.Errorf("expected 1 public key for kid %q, got %d", token.Headers[0].KeyID, len(matchingPublicKeys))
 	}
🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_sign_test.go`
around lines 30 - 151, Merge getSignedTokenWithClaims and
getSignedTokenWithoutClaims into one shared helper that accepts the request
operation and optional request data, preserving the existing claims and headers
destination behavior. Update both callers to supply their respective operation
and data configuration, then use the shared post-HandleRequest token-validation
flow. In the unique public-key failure path, report token.Headers[0].KeyID and
the matching key count instead of formatting the stale nil err value.
infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_roles.go (1)

246-251: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Delete the commented-out sub check and record the deviation.

The NVIDIA modification disables the reserved-sub restriction. The disabled code stays in the file as a comment block. Remove the block. Keep one short comment that states the behavior change, and record the modification in NOTICE in the same commit, as the plugin path instructions require. If the restriction must return later, open an issue instead of keeping the code in a comment.

The same pattern appears in path_roles_test.go Lines 138-145.

As per coding guidelines: "record each such change in NOTICE in the same commit" and "create a ticket for follow-up work instead of leaving a TODO in code".

🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_roles.go` around
lines 246 - 251, Remove the commented-out reserved-sub validation block near the
role claims handling in path_roles.go and retain only a brief comment
documenting that NVIDIA disables this restriction. Apply the same cleanup to the
corresponding block in path_roles_test.go, and record both modifications in
NOTICE as required; do not leave TODOs or disabled code comments.

Source: Coding guidelines

infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_jwks_test.go (1)

74-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The test compares the endpoint output with the same function it calls.

FetchJWKS reaches getPublicKeys through b.pathJwksRead. Line 74 calls getPublicKeys directly. Both sides of the Line 92 comparison come from the same producer, so the test verifies the JSON round trip only. It cannot detect a defect inside getPublicKeys, including the zero-value key entries described in path_jwks.go Lines 81-113.

Add an assertion on the key contents. Check that each returned key has a non-nil Key, the expected Use value, and a KeyID that matches the active policy versions.

🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_jwks_test.go`
around lines 74 - 94, Update the JWKS test around b.pathJwksRead and the
expectedKeySet comparison so expected data is not validated solely against
getPublicKeys. Add assertions for every returned key verifying Key is non-nil,
Use has the expected value, and KeyID matches the active policy versions, while
preserving the existing endpoint comparison.
🤖 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 @.github/workflows/openbao-jwt-plugin.yml:
- Around line 71-76: Update the “Assert no unlicensed dependency” workflow step
to capture the successful output of `go list -m all` before searching it, and
make any failure of that command fail the step rather than treating it as
absence. Search the captured module list for `mariuszs/friendlyid-go` without
piping `go list` directly into `grep`, while preserving the existing error and
exit behavior when the dependency is found.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/NOTICE`:
- Around line 11-14: Update the attribution statements in NOTICE to apply only
to upstream-originated files, and add AGENTS.md and CLAUDE.md to the
NVIDIA-authored modification inventory with their correct header treatment.
Preserve upstream copyright headers only on files that retain upstream origins,
and ensure both guidance files are explicitly recorded.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/config.go`:
- Around line 186-201: Update saveConfig so its err != nil path returns the
assigned internal error instead of nil. Preserve the existing successful return
behavior when the RSA or signature algorithm is supported, ensuring unsupported
cases propagate the error and do not report a successful save.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_jwks.go`:
- Around line 81-113: Update the JWKS construction in the policy key loop to
initialize an empty Keys slice and append only fully usable public keys. Skip
missing versions, PEM decode failures, parse failures, and entries without
either a valid FormattedPublicKey or RSAKey; assign metadata only after a key is
confirmed, then append the completed jose.JSONWebKey instead of indexing a
fixed-length slice.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_roles_test.go`:
- Around line 204-215: Update the audience assertion in the test to compare the
response’s claims["aud"] value against the expected audience array defined by
the test, rather than the locally extracted audience variable. Keep the existing
response parsing and missing-claim checks intact.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_roles.go`:
- Around line 96-105: The role schema exposes keyMaxAllowedAudiences and
keyAllowedClaims without storing or applying them. Either implement both fields
end to end—add corresponding Role fields, populate them in pathRolesWrite via
d.GetOk, validate the audience limit against config.MaxAudiences, and use the
role-specific values in the audience-count and signing logic—or remove both
schema entries and their descriptions so unsupported settings are not accepted.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/util.go`:
- Around line 37-44: Update friendlyIdGenerator.id to use uuid.NewRandom instead
of uuid.NewUUID, preserving the existing error handling and encodeBase62UUID
conversion. Confirm that no consumers require the current version-1 jti format
before making this change.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/test/jwtverify/jwtverify.go`:
- Around line 48-60: Update validateToken’s JWKS retrieval to use an http.Client
configured with a bounded Timeout instead of http.Get/default client, while
preserving the existing response-body close and read/error handling.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/test/stress-test.sh`:
- Around line 11-13: Update the stress script’s worker lifecycle around fail and
the stress job launch sites: track every background worker PID, wait for each
worker before the parent exits, and propagate a nonzero status when any worker
fails. Ensure fail still terminates the worker process group while the script
ultimately returns failure instead of exiting successfully.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/test/test.sh`:
- Around line 4-7: Both test.sh and stress-test.sh must wait for the background
Vault server to become ready before the first CLI request. Add a health or
status polling step after exporting VAULT_ADDR and before vault login in each
script, preserving the existing server startup and process handling.

---

Minor comments:
In `@infra/openbao/plugins/vault-plugin-secrets-jwt/AGENTS.md`:
- Around line 3-7: Update the section 4(b) attribution in
infra/openbao/plugins/vault-plugin-secrets-jwt/AGENTS.md lines 3-7 to describe
the modification inventory as repository policy, not an Apache-2.0 requirement.
Update infra/openbao/plugins/vault-plugin-secrets-jwt/NOTICE lines 19-20 to
identify the list as the repository’s compliance record instead of saying it is
required by section 4(b); preserve upstream headers and the NVIDIA change
inventory.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/backend_test.go`:
- Line 92: Update every time.Sleep call in the affected backend tests to add an
explicit one-second margin to config.KeyRotationPeriod instead of the implicit
one-nanosecond + 1. Apply this consistently to the sleeps near the existing
rotation-period checks, preserving the test flow while making key-rotation
timing reliable.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_config.go`:
- Around line 155-157: Update the error response in the rsa_key_bits validation
within the configuration handler to format AllowedRSAKeyBits with a verb
suitable for []int, so the supported values render correctly while preserving
the existing invalid-request behavior.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_roles_test.go`:
- Around line 138-150: Update the first writeRole call in the role test to
assert that creating a role with the sub claim succeeds, before reusing err for
the foo claim case. Keep the existing failure assertion for the subsequent
writeRole call unchanged.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_sign.go`:
- Around line 153-157: The audience validation duplicated in path_sign.go lines
153-157 and path_roles.go lines 264-268 reports the asserted zero-value string
type; extract the shared validation into a common helper and pass rawAudEntry to
the %T diagnostic so both paths report the actual non-string type. Update both
call sites to use the helper while preserving existing invalid-request behavior.
- Around line 127-138: Update the type-mismatch error response in the claims
validation logic to pass rawSub as the operand for the %T formatter. Preserve
the existing error message and invalid-request return behavior.
- Around line 38-49: Remove the unsupported headers field from the sign request
test associated with pathSign, since pathSign does not declare or read
keyHeaders. Keep the test focused on the role-defined tid=12345 claim; only add
per-request header support if implementing the full schema, field-reading, and
config.allowedHeadersMap validation path.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/policy_signer.go`:
- Around line 50-52: Update the ExtraHeaders processing in the policy signer to
handle non-string Role.Headers values intentionally: either validate and reject
values that are not strings, or convert them using a general value format such
as %v. Ensure numeric and boolean headers never produce malformed %!s(...)
output while preserving existing string header behavior.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/util.go`:
- Around line 70-78: Update the imports in util.go to explicitly blank-import
crypto/sha1, crypto/sha256, and crypto/sha512, ensuring the hash implementations
used by createKeyId and PolicySigner.sign are registered independently of
crypto/x509. Preserve SHA-1 and the existing createKeyId output so current kid
values remain unchanged.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/README.md`:
- Around line 125-137: Update the Allowed Headers section in the README to refer
to iss and path as headers rather than claims, while leaving the allowed_headers
command and surrounding documentation unchanged.
- Around line 181-187: Quote the wildcard values in the README command examples
for both subject_pattern and audience_pattern, including the corresponding
configuration and role commands referenced by the comment, so the shell passes
the literal *. Preserve the existing commands and patterns otherwise.
- Around line 142-149: Correct the remaining spelling and grammar errors in the
README: add the missing space in “ES256 algorithm,” hyphenate “2048-bit,” and
fix the misspellings “stings” and “rewrote.” Apply these edits at the referenced
documentation occurrences while preserving the surrounding usage instructions.
- Line 5: Update the README section heading and related wording covering the
supported ES256, ES384, ES512, and RS* algorithms to use “signing” terminology,
specifically describing them as asymmetric signing rather than encryption, while
preserving the existing algorithm documentation.
- Line 2: Update the introductory subtitle and the “Early Access” heading in the
README to use H2 markers instead of H3, preserving the existing heading text and
valid hierarchy beneath the document’s H1.
- Around line 312-315: Remove the leading indentation from each item under the
“Quick Links” heading so the Vault Website, Main Project Github, and JWT docs
entries render as a Markdown list rather than a code block.
- Around line 96-104: Update the Container section in the README to remove the
unsupported outfoxx/vault testing instructions, or replace them with the
repository-owned image name and documented build path. Do not retain a claim
that this repository provides a pre-packaged container without identifying its
local build definition and image.

---

Nitpick comments:
In `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/config.go`:
- Line 252: Update DefaultConfig where AllowedClaims is initialized so each
Config receives an independent copy of DefaultAllowedClaims rather than sharing
its backing array; preserve the existing default values while preventing later
in-place mutations from affecting other configurations.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_jwks_test.go`:
- Around line 74-94: Update the JWKS test around b.pathJwksRead and the
expectedKeySet comparison so expected data is not validated solely against
getPublicKeys. Add assertions for every returned key verifying Key is non-nil,
Use has the expected value, and KeyID matches the active policy versions, while
preserving the existing endpoint comparison.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_roles.go`:
- Around line 246-251: Remove the commented-out reserved-sub validation block
near the role claims handling in path_roles.go and retain only a brief comment
documenting that NVIDIA disables this restriction. Apply the same cleanup to the
corresponding block in path_roles_test.go, and record both modifications in
NOTICE as required; do not leave TODOs or disabled code comments.

In `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_sign_test.go`:
- Around line 30-151: Merge getSignedTokenWithClaims and
getSignedTokenWithoutClaims into one shared helper that accepts the request
operation and optional request data, preserving the existing claims and headers
destination behavior. Update both callers to supply their respective operation
and data configuration, then use the shared post-HandleRequest token-validation
flow. In the unique public-key failure path, report token.Headers[0].KeyID and
the matching key count instead of formatting the stale nil err value.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a44dc0a9-00a3-4dc0-ba61-be2e80aaafa5

📥 Commits

Reviewing files that changed from the base of the PR and between 892b704 and b6aad63.

⛔ Files ignored due to path filters (15)
  • infra/openbao/plugins/vault-plugin-secrets-jwt/go.sum is excluded by !**/*.sum
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/testdata/allowed_claims.json is excluded by !**/testdata/**
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/testdata/claims.json is excluded by !**/testdata/**
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/testdata/claims1.json is excluded by !**/testdata/**
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/testdata/claims10.json is excluded by !**/testdata/**
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/testdata/claims2.json is excluded by !**/testdata/**
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/testdata/claims3.json is excluded by !**/testdata/**
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/testdata/claims4.json is excluded by !**/testdata/**
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/testdata/claims5.json is excluded by !**/testdata/**
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/testdata/claims6.json is excluded by !**/testdata/**
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/testdata/claims7.json is excluded by !**/testdata/**
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/testdata/claims8.json is excluded by !**/testdata/**
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/testdata/claims9.json is excluded by !**/testdata/**
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/testdata/claims_foo.json is excluded by !**/testdata/**
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/testdata/invalid_claims.json is excluded by !**/testdata/**
📒 Files selected for processing (32)
  • .github/workflows/openbao-jwt-plugin.yml
  • infra/openbao/plugins/vault-plugin-secrets-jwt/AGENTS.md
  • infra/openbao/plugins/vault-plugin-secrets-jwt/CLAUDE.md
  • infra/openbao/plugins/vault-plugin-secrets-jwt/HEADER
  • infra/openbao/plugins/vault-plugin-secrets-jwt/LICENSE
  • infra/openbao/plugins/vault-plugin-secrets-jwt/NOTICE
  • infra/openbao/plugins/vault-plugin-secrets-jwt/README.md
  • infra/openbao/plugins/vault-plugin-secrets-jwt/cmd/vault-plugin-secrets-jwt/main.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/go.mod
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/backend.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/backend_test.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/config.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/friendlyid.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/friendlyid_test.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_config.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_config_test.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_jwks.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_jwks_test.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_roles.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_roles_test.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_sign.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_sign_test.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/policy_signer.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/token.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/util.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/Dockerfile
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/Stress-Dockerfile
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/config.hcl
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/godoc.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/jwtverify/jwtverify.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/stress-test.sh
  • infra/openbao/plugins/vault-plugin-secrets-jwt/test/test.sh

Comment thread .github/workflows/openbao-jwt-plugin.yml
Comment thread infra/openbao/plugins/vault-plugin-secrets-jwt/NOTICE Outdated
Comment thread infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/config.go
Comment thread infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_jwks.go
Comment thread infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/path_roles.go
Comment thread infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/util.go
Comment thread infra/openbao/plugins/vault-plugin-secrets-jwt/test/stress-test.sh
Comment thread infra/openbao/plugins/vault-plugin-secrets-jwt/test/test.sh
balaji-g and others added 2 commits August 3, 2026 19:16
Brings across the rest of nvcf-openbao, mirroring infra/cassandra: Dockerfile,
scripts, files/plugins, README, license-header tooling, upgrade/.

Not imported: Dockerfile.internal, which bases on an internal mirror;
.gitlab-ci.yml, which stays as-is in nvcf-internal; renovate, releaserc,
CODEOWNERS and the OSRB report.

The plugin is now compiled in a Dockerfile build stage rather than copied in
prebuilt. Previously build-jwt-plugin.sh cloned a fork hosted outside this
repository at a pinned revision, so the image could not be built from public
source and the binaries had to arrive some other way: committed to git, or
injected as a private overlay the way cassandra takes its exporter jar.

Neither is needed. buildah is invoked with infra/openbao as the build context
and the plugin source now sits at plugins/ inside it, so the image compiles
the plugin from the same commit that produces it. That is a stronger position
than cassandra, whose exporter jar is a third-party release artifact we cannot
build and must therefore vendor.

files/plugins/.gitignore keeps built binaries out of git; the directory still
ships only .gitkeep.

build-jwt-plugin.sh is kept and repointed at the in-repo source, since it is
also the local developer path and what verify/smoke run against.

Co-authored-by: Balaji Ganesan <bganesan@nvidia.com>
check-license enforces that NOTICE lists every per-directory notice file, and
the import added one it did not know about:

  ERROR: NOTICE is out of sync with repo notice / third-party license paths.
  +    infra/openbao/plugins/vault-plugin-secrets-jwt/NOTICE

Regenerated with ./tools/scripts/update-license. check-license now passes:
291 files with valid headers, MPL audit in sync.

Co-authored-by: Balaji Ganesan <bganesan@nvidia.com>
@balajinvda

Copy link
Copy Markdown
Contributor Author

Thanks — worked through all ten. Two were mine and are fixed; eight are pre-existing upstream code and are deliberately out of scope for an import.

Fixed

  • .github/workflows/openbao-jwt-plugin.yml — correct, the gate failed open. Inside if, a failing go list read as "dependency absent", and under pipefail grep -q can close the pipe and leave go list killed by SIGPIPE. Now captures the module list first and fails the step if enumeration fails.
  • NOTICE — also correct, and it mattered because the inaccuracy was in a legal notice. AGENTS.md and CLAUDE.md are NVIDIA-added, so "files not listed below are unmodified" and "all other files retain the upstream header" were both false. The scope statements are now limited to upstream-originated files and the guidance files are inventoried as item 7, noting they carry no header because they are not distributed source.

Not fixed: upstream Outfox code

finding origin
plugin/config.go:201 saveConfig discards error upstream
plugin/path_jwks.go:113 upstream
plugin/path_roles.go:105 max_audiences / allowed_claims ignored upstream — git blame: Kevin Wooten
plugin/path_roles_test.go:215 audience assertion compares a value with itself upstream — git blame: Kevin Wooten
plugin/util.go:44 uuid.NewUUID is v1 (MAC + timestamp) upstream call
test/jwtverify/jwtverify.go:60 upstream
test/stress-test.sh:13 upstream
test/test.sh:7 upstream

I checked the two on NVIDIA-modified files rather than assuming: path_roles.go and path_roles_test.go were both touched by NVIDIA's single commit, but git blame puts these specific lines on upstream commits ("Add support for roles, multiple algorithms & raw JWKS endpoint", "Fix audience array handling in roles config").

This PR imports third-party Apache-2.0 code with attribution intact. Fixing upstream defects inside the import would blur which lines are Outfox's and which are ours, and every such fix has to be added to the NOTICE inventory. They are better as a follow-up that can be reviewed as NVIDIA changes on their own merits.

Two are worth that follow-up on substance: saveConfig swallowing an error, and uuid.NewUUID embedding a host MAC address in identifiers issued by a signing plugin.

balaji-g and others added 2 commits August 3, 2026 19:29
CodeRabbit, both valid:

The workflow's licensing gate failed open. Inside `if`, a failing `go list`
read as "dependency absent", and under pipefail `grep -q` can close the pipe
and leave `go list` killed by SIGPIPE. It now captures the module list and
fails the step if enumeration fails.

NOTICE claimed unlisted files were unmodified upstream and that all other files
retain the Outfox header. AGENTS.md and CLAUDE.md are NVIDIA-added, so both
statements were false. The scope is now limited to upstream-originated files
and the guidance files are inventoried.

The remaining eight findings are pre-existing upstream code, including the two
on NVIDIA-modified files: git blame puts path_roles.go:105 and
path_roles_test.go:215 on upstream commits. Fixing upstream defects inside an
import would blur which lines are Outfox's and which are ours.

verify-jwt-plugin.sh asserted three things that only held when the plugin came
from a frozen external revision:

- the module path, updated for the rename
- a required vcs.revision, dropped. Neither build path has git metadata: this
  script builds from a copy in a temp dir and the image build COPYs source into
  a layer. Requiring the stamp fails both; requiring a value pins a revision
  that no longer exists.
- pinned sha256s, now recorded rather than asserted. Any source edit in this
  repository legitimately changes them, so equality would fail on every real
  change and teach people to update the constant without reading it.

What still carries provenance is asserted: module path, target triple,
toolchain floor, and the x/net, vault/api and vault/sdk versions.

Verified end to end: build-jwt-plugin.sh produces both arches from the in-repo
source and verify-jwt-plugin.sh passes on both.

Co-authored-by: Balaji Ganesan <bganesan@nvidia.com>
… the host MAC

Two upstream defects CodeRabbit raised. I had declined all eight upstream
findings as out of scope for an import, which was wrong for these two: we do
not track upstream, so this code is ours to maintain, and both are security
relevant in a plugin that signs tokens.

plugin/config.go discarded the "unknown/unsupported signature algorithm" error
and returned nil, so an unsupported algorithm reported success while the key
was never rotated and the caller had no way to detect it.

plugin/util.go generated token ids from uuid.NewUUID, which is a version 1
UUID: it encodes the host MAC address and the creation timestamp. That id is
published as the token's jti, so every token holder received the signer's
hardware address and issue time. Now uuid.NewRandom (v4), with a test that
asserts the version and that consecutive ids do not share a trailing segment,
which is what a node-derived id would show.

Both recorded in NOTICE as required by Apache-2.0 section 4(b).

The remaining six findings are upstream style and test-harness issues with no
security or correctness impact, and are left to a follow-up so they can be
reviewed as NVIDIA changes rather than buried in an import.

Co-authored-by: Balaji Ganesan <bganesan@nvidia.com>

@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: 5

🤖 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 `@infra/openbao/Dockerfile`:
- Line 35: Change plugin executable permissions from 775 to 755 consistently:
update the COPY directive in infra/openbao/Dockerfile at lines 35-35, the chmod
and install modes in infra/openbao/scripts/build-jwt-plugin.sh at lines 61-72,
and the COPY directive in infra/openbao/upgrade/Dockerfile.upgrade at lines
13-13.

In `@infra/openbao/files/plugins/PROVENANCE.md`:
- Around line 3-12: Remove the private NVIDIA GitLab source URL and internal
NVCF-10946 work-item reference from the provenance documentation. Retain only
publicly verifiable in-repository provenance, such as the build and verification
script references and publicly available commit or dependency details.

In `@infra/openbao/README.md`:
- Around line 7-39: The documentation describes an obsolete prebuilt-plugin
workflow. In infra/openbao/README.md lines 7-39, remove external cloning, binary
placement, architecture-specific build commands, and related prerequisites, and
document that the image builds the in-tree plugin module. In
infra/openbao/files/plugins/PROVENANCE.md lines 3-29, remove claims about
committed binaries and fixed hashes, and describe the repository source and
current verification process.

In `@infra/openbao/scripts/build-jwt-plugin.sh`:
- Around line 31-42: Update the work-directory setup and cleanup logic around
work_dir and cleanup so the exit trap removes only directories created by
mktemp. Track whether WORK_DIR was caller-provided, preserve it by default
regardless of KEEP_WORK_DIR, and retain the existing cleanup behavior for
script-created temporary directories unless explicit deletion is requested.

In `@infra/openbao/scripts/smoke-jwt-plugin-runtime.sh`:
- Around line 46-113: Update the smoke-test script to create a single private
temporary directory with mktemp -d before generating artifacts, and replace
every predictable /tmp/openbao-dev.hcl, /tmp/openbao.log, /tmp/bao-status.txt,
JWT, claims, and decoded-output path with files under that directory. Extend the
existing EXIT trap to remove the temporary directory while still terminating
server_pid, and preserve all current command behavior and checks.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 39dec38d-76c9-45d8-81a3-229d948aaf38

📥 Commits

Reviewing files that changed from the base of the PR and between b6aad63 and 4525c79.

📒 Files selected for processing (15)
  • .github/workflows/openbao-jwt-plugin.yml
  • NOTICE
  • infra/openbao/.license-header.txt
  • infra/openbao/Dockerfile
  • infra/openbao/README.md
  • infra/openbao/files/plugins/.gitignore
  • infra/openbao/files/plugins/.gitkeep
  • infra/openbao/files/plugins/PROVENANCE.md
  • infra/openbao/plugins/vault-plugin-secrets-jwt/NOTICE
  • infra/openbao/scripts/apply-license-header.sh
  • infra/openbao/scripts/build-jwt-plugin.sh
  • infra/openbao/scripts/check-license-header.sh
  • infra/openbao/scripts/smoke-jwt-plugin-runtime.sh
  • infra/openbao/scripts/verify-jwt-plugin.sh
  • infra/openbao/upgrade/Dockerfile.upgrade
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/openbao-jwt-plugin.yml

Comment thread infra/openbao/Dockerfile Outdated
Comment thread infra/openbao/files/plugins/PROVENANCE.md Outdated
Comment thread infra/openbao/README.md
Comment thread infra/openbao/scripts/build-jwt-plugin.sh Outdated
Comment thread infra/openbao/scripts/smoke-jwt-plugin-runtime.sh Outdated

@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

🤖 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 `@infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/friendlyid_test.go`:
- Around line 136-157: Update the UUID version test to exercise
friendlyIdGenerator.id() rather than calling uuid.NewRandom() directly: create a
generator, generate each identifier, decode it with the test decoder defined
earlier, and assert the decoded UUID is version 4. Retain the existing
consecutive-ID suffix comparison as an additional check.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 7d9913ed-bb04-4608-ab55-5551c6d2baa5

📥 Commits

Reviewing files that changed from the base of the PR and between 4525c79 and ed923b5.

📒 Files selected for processing (4)
  • infra/openbao/plugins/vault-plugin-secrets-jwt/NOTICE
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/config.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/friendlyid_test.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/util.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • infra/openbao/plugins/vault-plugin-secrets-jwt/NOTICE
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/util.go
  • infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/config.go

Comment thread infra/openbao/plugins/vault-plugin-secrets-jwt/plugin/friendlyid_test.go Outdated
balaji-g and others added 2 commits August 3, 2026 20:18
All six findings were on files this PR adds, and all were valid.

files/plugins/PROVENANCE.md carried an internal GitLab fork URL and a private
tracker id. I imported it wholesale without scanning it, which is exactly what
the OSS hygiene rule exists to prevent. It was also stale: it described
committed binaries and pinned hashes that no longer apply now that the plugin
is compiled during the image build. Rewritten around the in-tree source.

Dockerfile: the plugin is installed 0555 rather than 775. Group write on an
executable the server exec's is not needed by anything.

README.md still told readers to clone the upstream project and place binaries
by hand. It now documents the in-tree build.

build-jwt-plugin.sh deleted a caller-supplied WORK_DIR on exit. It now removes
only a directory it created itself.

smoke-jwt-plugin-runtime.sh wrote a dev root token, server logs and status
output to fixed /tmp paths. It now uses a private 0700 directory removed on
exit; predictable names in a shared /tmp are both a disclosure risk and a
collision between concurrent runs.

The UUID version test asserted on uuid.NewRandom() directly, which tested the
uuid package rather than this code. It now decodes what friendlyIdGenerator.id()
actually returns and asserts version 4 and the RFC4122 variant. Confirmed the
test fails when the v1 call is reintroduced and passes when it is restored.

Co-authored-by: Balaji Ganesan <bganesan@nvidia.com>
check-dependency-docs regenerates dependencies.md and fails if the committed
copy differs. The imported JWT plugin adds eleven Go modules to the
repository-wide inventory, mostly hashicorp/go-secure-stdlib and crypto
transitives pulled in by the vault SDK.

Regenerated with `GOWORK=off go run -C ./tools/collect-dependencies .`, which
is what CI runs.

Also satisfies the OSRB report's requirement that dependency counts be
regenerated once friendlyid-go is removed, which this branch does.

Co-authored-by: Balaji Ganesan <bganesan@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants