feat(ca-bundle): inject org CA into Ray head + worker pods via ConfigMap (#15)#16
Conversation
Add a `singleuser.gallery`-style opt-in for organisation-supplied CA bundles. When `orgCABundle.secretName` is set, the chart renders: - An initContainer (default image `alpine:3.20`) that concatenates the base image's system trust store with the deployer-supplied CA into a shared emptyDir at /shared/combined-ca.crt - Two volumes on each pod spec: the deployer Secret (read-only) and the shared emptyDir - SSL_CERT_FILE / REQUESTS_CA_BUNDLE / CURL_CA_BUNDLE env vars on both head and worker pointing at the combined bundle, alongside any existing head.containerEnv / worker.containerEnv entries - A read-only volumeMount of the combined bundle on each main container When `secretName` is empty (default), zero CA-related output is rendered — no initContainer, no volumes, no env vars. Backward- compatible. Implemented as five named helpers in _helpers.tpl (.enabled, .initContainers, .volumes, .volumeMounts, .env) so head and worker share one source of truth, and the rayservice template gates each block on the same `if .secretName` predicate. Covers everything that honours SSL_CERT_FILE: requests, urllib3, curl, git, pip, torch.hub, stdlib urllib, properly-constructed httpx clients. Does NOT cover httpx.Client(verify=True) defaults, which hardcode cafile=certifi.where() and ignore env vars — caller code needs to pass verify=ssl.create_default_context() explicitly. README calls this out as a known coverage caveat. Closes #15
s/organisation/organization/, s/honour/honor/ across values.yaml comment block, _helpers.tpl docstrings, and README section. No functional change — rendered RayService is byte-identical to the prior commit on this branch. Keeps the chart's user-facing copy consistent with the rest of the project.
CA certificates are public material by design — the PKI trust model relies on root CAs being widely distributed. Kubernetes itself uses a ConfigMap for the cluster's own CA distribution (kube-root-ca.crt, auto-projected into every namespace), and cert-manager's trust-manager distributes Bundle CRs as ConfigMaps. Following that precedent. Breaking change against the prior commit on this branch (PR not yet merged, so no consumer impact): - values.yaml: orgCABundle.secretName -> orgCABundle.configMapName - _helpers.tpl: enabled predicate gates on configMapName; volumes helper renders as configMap.name instead of secret.secretName - README + values.yaml comment block: rewrite example to ConfigMap; add 'Why ConfigMap rather than Secret' explainer citing kube-root-ca.crt and trust-manager precedent helm template verified in three modes: configMapName set -> initContainer + ConfigMap volume + env vars no value (default) -> zero CA-related output old secretName set -> no injection (regression guard)
|
Heads-up on what we learned integrating this PR into a Netskope-fronted production cluster deployed via ArgoCD gitops. Net: the chart works as designed end-to-end. A few findings might be worth folding back before merge. End-to-end verification ✓Deployed PR HEAD (
Re: "Known coverage gap — httpx default
|
git's libcurl ignores SSL_CERT_FILE/REQUESTS_CA_BUNDLE/CURL_CA_BUNDLE and reads only GIT_SSL_CAINFO, so `pip install git+https://...` and other git-over-HTTPS calls failed certificate verification in worker pods even with the CA bundle mounted. Set GIT_SSL_CAINFO to the combined bundle too. Also document the ArgoCD footgun: the example sync policy's broad /spec/rayClusterConfig ignore rule combined with RespectIgnoreDifferences makes ArgoCD silently skip the CA injection (which lives under that path) while still reporting a healthy sync. See #17.
|
I took the initiative to address a couple of the items from the comment above: added |
…ection # Conflicts: # chart/templates/_helpers.tpl # chart/templates/rayservice.yaml
What
Adds opt-in injection of an organization-supplied CA bundle into the Ray head and worker pods, so outbound HTTPS calls succeed in environments running a TLS-inspecting proxy (Netskope, Zscaler, BlueCoat, internal corporate CAs, etc.). Resolves the symptom that motivated #15:
Implemented as a new
orgCABundlevalues block driving an initContainer that builds a combined CA bundle (system trust + org CA) and a set of env vars / volume mounts on the head and worker pods.Behavior
Opt-in via Secret. Deployer creates a Secret with key
ca.crt:…then points the chart at it:
When enabled, both head and worker pods get:
initContainer(defaultalpine:3.20) that concatenates/etc/ssl/certs/ca-certificates.crt+/var/local/org-ca/ca.crt→/shared/combined-ca.crtorg-ca(the Secret, read-only) +combined-ca(emptyDir)SSL_CERT_FILE,REQUESTS_CA_BUNDLE,CURL_CA_BUNDLE→/shared/combined-ca.crtWhen
configMapNameis empty (default), zero CA-related output is rendered — no initContainer, no volumes, no env vars. Backward-compatible for existing deploys.Implementation
Five named helpers in
_helpers.tpl(single source of truth for head + worker):nebari-rayserve.orgCABundle.enabled— predicatenebari-rayserve.orgCABundle.initContainers— the build stepnebari-rayserve.orgCABundle.volumes— Secret + emptyDirnebari-rayserve.orgCABundle.volumeMounts— read-only mount of combined bundlenebari-rayserve.orgCABundle.env— SSL_CERT_FILE/REQUESTS_CA_BUNDLE/CURL_CA_BUNDLEEach helper renders empty when the predicate is false, so the
rayservice.yamltemplate can wrap them in{{- with (include ...) }}and the no-op case produces byte-identical output to today.head.containerEnvandworker.containerEnvare preserved — the CA env vars are concat'd, not overwritten:{{- $headEnv := concat (.Values.head.containerEnv | default list) (fromYamlArray (include "nebari-rayserve.orgCABundle.env" .)) }}Coverage
This covers anything that honors
SSL_CERT_FILE:requests,urllib3,curl,git,pip,torch.hub, stdliburllib, and properly-constructed httpx clients (i.e. ones usingverify=ssl.create_default_context()).Known coverage gap — httpx default
verify=True: httpx's default verify path hardcodescafile=certifi.where(), which ignores env vars. Application code making httpx calls must construct an SSL context explicitly:README documents this caveat. (One specific upstream case — checkmaite's
httpx.streaminVisdroneODModel— is being fixed in a separate app-side change by the same team, since this chart can mount the bundle but can't fix the consumer's SSL context construction.)Verification
helm templaterendering, both modes (the chart'skuberay-operatordep was pulled withhelm dependency build chart/first):Enabled (
--set orgCABundle.configMapName=org-ca-bundle) — both head and worker get:Disabled (default): grepping
helm template foo ./chartforinitContainers|build-ca|SSL_CERT_FILE|combined-ca|org-careturns zero hits.Files
chart/values.yaml— neworgCABundleblock (42 lines, all defaults that keep behaviour unchanged)chart/templates/_helpers.tpl— five new helpers (78 lines, all gated by the same predicate so no-op when disabled)chart/templates/rayservice.yaml— wraps the five helpers into head + worker specs (30 line diff, additive, preserveshead.runtimeClassName/readinessProbe/livenessProbeandworker.containerEnvunchanged)README.md— new### Organization CA Bundle Injectionsubsection under## Chart ConfigurationTest plan
helm templatewithconfigMapName=org-ca-bundlerenders all five components on head + workerhelm templatewithoutconfigMapNameproduces byte-identical RayService to today (verified by absence of CA-related strings)helm templatewith bothhead.containerEnvandorgCABundle.configMapNameset produces the union of both env lists (deployer env vars are preserved, not overwritten by CA env vars)requestssucceeds (covered by SSL_CERT_FILE)verify=ssl.create_default_context()— confirms the coverage caveat pathCloses #15.