Skip to content

Embed cluster CA in kubeconfig in necessary#37

Open
matejvasek wants to merge 8 commits into
functions-dev:masterfrom
matejvasek:kubeconfig-ca
Open

Embed cluster CA in kubeconfig in necessary#37
matejvasek wants to merge 8 commits into
functions-dev:masterfrom
matejvasek:kubeconfig-ca

Conversation

@matejvasek

@matejvasek matejvasek commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Replace insecure-skip-tls-verify: true in generated kubeconfigs with proper CA certificate handling
  • New backend endpoint GET /api/cluster/ca uses a two-probe approach: first tries system roots (public CA), then the service account CA bundle (private CA)
  • Only embeds the CA when the cluster uses a private CA; publicly trusted certs rely on the runner's system trust store
  • Copy RHEL CA trust bundle into ubi9-micro runtime image (it ships with no CA infrastructure at all)

Changes

Backend (backend/main.go, backend/main_test.go)

  • clusterCAHandler struct with injectable SystemTLS for testability
  • --kube-root-ca-path CLI flag to override the default SA CA path for local dev
  • 8 tests covering all handler paths: missing param, non-HTTPS, missing file, public CA, private CA, bundle mismatch, empty file, malformed file

Frontend (OcpClusterService.ts, OcpClusterService.test.ts)

  • #fetchClusterCA calls the backend via the console proxy
  • #buildKubeconfig conditionally sets certificate-authority-data instead of insecure-skip-tls-verify
  • Tests for both CA-present (private) and CA-null (public) cases

Dev environment (init.sh)

  • extract_cluster_ca extracts the CA from kube-root-ca.crt ConfigMap via oc
  • Passes --kube-root-ca-path to the backend on startup and on file-watch restart

Container image (Dockerfile)

  • Copy /etc/pki/tls/certs/ca-bundle.crt from go-toolset build stage into ubi9-micro

Test plan

  • go test -v ./... (8 tests pass)
  • yarn ci (lint + 14 suites / 127 tests + build)
  • In-cluster: publicly trusted API server (Let's Encrypt) returns {ca: null}
  • In-cluster: private CA cluster returns {ca: "<base64>"}
  • Create a function on a private CA cluster, verify KUBECONFIG GitHub secret contains certificate-authority-data

🤖 Generated with Claude Code

matejvasek and others added 7 commits June 10, 2026 22:00
The kubeconfig for GitHub Actions previously hardcoded
insecure-skip-tls-verify, skipping TLS verification entirely.

A new backend endpoint (GET /api/cluster/ca) reads the service
account CA bundle, probes the API server's TLS certificate, and
returns the CA only when the bundle actually verifies the
handshake. If the bundle does not work (e.g. a mismatched
intermediate after a Let's Encrypt rotation), it is omitted and
the runner's system trust store handles verification instead.

Signed-off-by: Matej Vašek <matejvasek@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Add a system roots probe before the SA bundle probe. If the
API server's cert is already publicly trusted, the CA is not
embedded in the kubeconfig. This avoids shipping public CAs
that may break after intermediate rotation (e.g. Let's
Encrypt) while still embedding private ones that a GitHub
Actions runner would not otherwise trust.

Signed-off-by: Matej Vašek <matejvasek@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>

fixup: correct JSDoc on generateKubeconfig function

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Matej Vašek <matejvasek@gmail.com>

fix: copy CA trust bundle into ubi9-micro runtime image

ubi9-micro ships with no CA certificates, so the system roots
TLS probe always failed and every cluster appeared to use a
private CA. Copy the RHEL CA bundle from the go-toolset build
stage so probe 1 can correctly identify publicly trusted certs.

Signed-off-by: Matej Vašek <matejvasek@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Replace the global saCAPath variable and systemTLSConfig
function with a clusterCAHandler struct that holds CAPath
and SystemTLS as fields. Tests construct their own handler
instances instead of mutating and restoring globals.

Signed-off-by: Matej Vašek <matejvasek@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>
A missing service account CA file now returns an error instead
of silently returning null. The MissingCAFile test is fixed to
use a local TLS server so probe 1 always fails, ensuring the
test actually exercises the file-read path.

Signed-off-by: Matej Vašek <matejvasek@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Add a CLI flag to override the default CA path so init.sh can
extract the cluster CA via oc and pass it to the backend. This
lets the /api/cluster/ca endpoint work outside a pod where the
service account mount is not available.

Signed-off-by: Matej Vašek <matejvasek@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Matej Vašek <matejvasek@gmail.com>
Cover the two previously untested error paths in
clusterCAHandler: an empty CA file with no PEM blocks, and a
CA file with a valid PEM envelope but corrupt DER content.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Matej Vašek <matejvasek@gmail.com>
Signed-off-by: Matej Vašek <matejvasek@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@matejvasek matejvasek requested a review from twoGiants June 10, 2026 21:17
@matejvasek

Copy link
Copy Markdown
Collaborator Author

/cc @dsimansk

@matejvasek matejvasek requested a review from gauron99 June 10, 2026 21:34
Comment thread backend/main.go
mux.HandleFunc("POST /api/function/create", handleFuncCreate)
mux.Handle("GET /api/cluster/ca", &clusterCAHandler{CAPath: *caPath})
mux.Handle("/", http.FileServer(http.FS(static)))

@dsimansk dsimansk Jun 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm thinking if this couldn't be controller-runtime based controller with addition of static HTTP serve on top of it. But that's broader decision. Currently it make sense as it is.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yeah, we probably move the backen to the functions operator eventually. It's not decided yet.

@twoGiants twoGiants left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Just a few comments so far. Review is in progress.

Comment thread backend/main.go Outdated
Comment thread backend/main.go Outdated
Comment thread backend/main.go
Comment thread backend/main.go
mux.HandleFunc("POST /api/function/create", handleFuncCreate)
mux.Handle("GET /api/cluster/ca", &clusterCAHandler{CAPath: *caPath})
mux.Handle("/", http.FileServer(http.FS(static)))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yeah, we probably move the backen to the functions operator eventually. It's not decided yet.

Move func create handler to func_create.go and cluster CA
handler to cluster_ca.go. Extract jsonOK helper to deduplicate
the JSON response pattern. Use guard clause in the PEM parsing
loop to reduce nesting.

Signed-off-by: Matej Vašek <matejvasek@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@matejvasek

Copy link
Copy Markdown
Collaborator Author

@twoGiants I did suggested changes.

@matejvasek matejvasek requested a review from twoGiants June 12, 2026 14:12
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.

3 participants