feat: Add lifespan OpenSearch bootstrap via service JWT#1626
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a lifespan-startup OpenSearch security bootstrap gated by OPENRAG_BOOTSTRAP_OS_SECURITY_ON_STARTUP and PLATFORM_SERVICE_JWT: the app derives an admin username from a service JWT, creates a temporary JWT-authenticated OpenSearch client, waits for readiness, runs setup_opensearch_security, and the orchestrator skips duplicate setup. ChangesOpenSearch Bootstrap Startup Flow
Sequence DiagramsequenceDiagram
participant Config
participant KubeSA
participant AuthServer
participant Lifespan
participant IBMAuth
participant OpenSearch
Config->>KubeSA: read K8S_SA_TOKEN (K8S_SA_TOKEN_PATH)
KubeSA-->>Config: K8S_SA_TOKEN
Config->>AuthServer: POST /internal/token/opensearch {tenant_id} with Bearer K8S_SA_TOKEN
AuthServer-->>Config: { token: OPENRAG_SERVICE_TOKEN }
Lifespan->>IBMAuth: admin_username_from_service_jwt(PLATFORM_SERVICE_JWT)
Lifespan->>OpenSearch: create temporary client from PLATFORM_SERVICE_JWT
Lifespan->>OpenSearch: wait_for_opensearch()
Lifespan->>OpenSearch: setup_opensearch_security(admin_username)
OpenSearch-->>Lifespan: bootstrap complete
Lifespan->>OpenSearch: close temporary client
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
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 `@src/auth/ibm_auth.py`:
- Around line 35-49: admin_username_from_service_jwt currently returns any
truthy username or sub claim which may be non-string; update the function to
validate that the chosen claim is a string before returning it (if not a string,
return None). Locate admin_username_from_service_jwt, after decoding and
selecting claims.get("username") or claims.get("sub"), check isinstance(value,
str) and only return it when true; optionally log a warning when a non-string
claim is encountered to aid debugging.
🪄 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: Pro
Run ID: a9186d8d-645a-464d-9299-7a929ec75224
📒 Files selected for processing (4)
src/app/lifespan.pysrc/auth/ibm_auth.pysrc/config/settings.pysrc/services/startup_orchestrator.py
68e1d6f to
156b5fd
Compare
156b5fd to
de7dd9e
Compare
2ea49fa to
acf937f
Compare
Introduce a one-shot OpenSearch security bootstrap during FastAPI lifespan that derives the admin username from a platform-issued service JWT. Adds PLATFORM_SERVICE_JWT and OPENRAG_BOOTSTRAP_OS_SECURITY_ON_STARTUP settings, validates the presence of the token, decodes the admin username (new admin_username_from_service_jwt helper), waits for OpenSearch, and runs setup_opensearch_security before other startup tasks. Also update startup_tasks to skip OpenSearch setup when the lifespan bootstrap flag is enabled, and add logging and error handling for missing/invalid tokens.
Use the Kubernetes pod service account token as a fallback for PLATFORM_SERVICE_JWT when no explicit env var is provided. Adds K8S_SA_TOKEN_PATH (with a default of /var/run/secrets/kubernetes.io/serviceaccount/token) and a helper _read_k8s_sa_token() that safely reads the token file (handling missing/permission errors). PLATFORM_SERVICE_JWT is now populated from the env var or the token file, allowing in-cluster authentication without injecting a JWT.
Add config/utils.py to centralize reading the K8s service account token and to fetch an OpenSearch service token from an internal auth server (get_opensearch_service_token). Introduce AUTH_SERVER_URL, OPENRAG_TENANT_ID and K8S_SA_TOKEN handling in settings.py and prefer the fetched OPENRAG_SERVICE_TOKEN as PLATFORM_SERVICE_JWT when available. Refactor AppClients to expose create_opensearch_client_from_jwt (and keep create_user_opensearch_client as an alias) and update lifespan.py to create an OpenSearch client from the JWT, pass it into wait/setup calls, and ensure the client is closed in a finally block. This enables cluster-local token exchange and ensures the bootstrap client is properly cleaned up.
Ensure admin_username_from_service_jwt only returns string values from the JWT. Previously the function returned whatever was in the "username" or "sub" claim; now it checks the claim type, logs a warning (including claim_type) if the value is present but not a string, and returns None in that case to avoid downstream errors.
Implement fetching and caching of JWT verification public keys from issuer URLs and add JWT verification helpers. New utilities include bearer stripping, issuer allowlist checks, PEM/JWK(S) payload parsing, a TTLCache-backed issuer key cache, and get_public_key_from_issuer/verify_jwt_from_issuer functions. get_opensearch_service_token now accepts verify_token (default true) and will verify the returned service JWT against the auth server's issuer/pinned key. Add comprehensive unit tests for issuer verification (PEM, JWKS, raw PEM, prefix allowlist, and token verification behavior) and a small import cleanup in an existing test.
acf937f to
021f006
Compare
Switch startup token handling to read OPENRAG_SERVICE_TOKEN from the environment instead of deriving/fetching a PLATFORM_SERVICE_JWT via Kubernetes service account logic. settings: remove k8s SA token reading and the get_opensearch_service_token plumbing; add get_openrag_service_token() that returns OPENRAG_SERVICE_TOKEN. lifespan: call get_openrag_service_token() and update error messages and uses accordingly. utils: remove K8s token helper and the internal get_opensearch_service_token flow, simplify issuer-key discovery/verification (no issuer allowlist), and improve docstrings. startup_orchestrator and tests: update comments and unit tests to reflect removal of issuer-allowlist and service-token fetching behavior (remove related tests). This simplifies startup configuration by relying on an explicit env var for the platform service token and reduces cluster-local token-fetching complexity.
This pull request introduces a new mechanism for securely bootstrapping OpenSearch security using a platform-issued JWT at application startup, and adds a utility for verifying JWTs using issuer-hosted public keys. It also includes supporting refactors and comprehensive unit tests for the new JWT verification logic.
OpenSearch Security Bootstrap Enhancements:
Added a new startup flow in
lifespan.pyto perform a one-shot OpenSearch security bootstrap using an admin username derived from a platform-issued service JWT (OPENRAG_SERVICE_TOKEN). This is controlled by the newOPENRAG_BOOTSTRAP_OS_SECURITY_ON_STARTUPsetting, ensuring the admin role mapping is in place before other startup tasks. The corresponding call instartup_tasksis suppressed when this flag is enabled. [1] [2] [3] [4] [5]Added
get_openrag_service_tokenaccessor and related environment variable handling insettings.pyto support dynamic retrieval of the service token.Refactored OpenSearch client creation: renamed and generalized the method to
create_opensearch_client_from_jwt, and madecreate_user_opensearch_clienta wrapper. [1] [2]JWT Verification Utility:
Added
config/utils.pywith robust utilities for fetching and caching public keys from JWT issuers (supporting PEM, JWKS, and JWK formats), and for verifying JWTs by dynamically discovering the signing key from the token'sissclaim. This allows for flexible, issuer-driven JWT verification suitable for platform-managed deployments.Added
admin_username_from_service_jwtinauth/ibm_auth.pyto extract the admin username from a platform-issued service JWT, matching the precedence logic used elsewhere.Testing Improvements:
Added comprehensive unit tests for the new JWT issuer verification logic, covering PEM, JWKS, and raw PEM key responses.
Minor test setup improvements for isolation in
test_jwt_claims_cache.py. [1] [2]