Embed cluster CA in kubeconfig in necessary#37
Conversation
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>
|
/cc @dsimansk |
| mux.HandleFunc("POST /api/function/create", handleFuncCreate) | ||
| mux.Handle("GET /api/cluster/ca", &clusterCAHandler{CAPath: *caPath}) | ||
| mux.Handle("/", http.FileServer(http.FS(static))) | ||
|
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Yeah, we probably move the backen to the functions operator eventually. It's not decided yet.
twoGiants
left a comment
There was a problem hiding this comment.
Just a few comments so far. Review is in progress.
| if block.Type == "CERTIFICATE" { | ||
| cert, err := x509.ParseCertificate(block.Bytes) | ||
| if err != nil { | ||
| jsonError(w, "failed to parse CA certificate: "+err.Error(), http.StatusInternalServerError) | ||
| return | ||
| } | ||
| pool.AddCert(cert) | ||
| found = true | ||
| } |
There was a problem hiding this comment.
nit
Use guard pattern for less nested ifs.
| if block.Type == "CERTIFICATE" { | |
| cert, err := x509.ParseCertificate(block.Bytes) | |
| if err != nil { | |
| jsonError(w, "failed to parse CA certificate: "+err.Error(), http.StatusInternalServerError) | |
| return | |
| } | |
| pool.AddCert(cert) | |
| found = true | |
| } | |
| if block.Type != "CERTIFICATE" { | |
| continue | |
| } | |
| cert, err := x509.ParseCertificate(block.Bytes) | |
| if err != nil { | |
| jsonError(w, "failed to parse CA certificate: "+err.Error(), http.StatusInternalServerError) | |
| return | |
| } | |
| pool.AddCert(cert) | |
| found = true | |
| w.Header().Set("Content-Type", "application/json") | ||
| json.NewEncoder(w).Encode(map[string]interface{}{"ca": nil}) |
There was a problem hiding this comment.
Duplicated 3 times. You can extract it in a func.
There was a problem hiding this comment.
I think we can start extracting the handlers in their own files.
The func create handler can be in a file and the ca handler in one.
You can put the common logic in common.go or leave in 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))) | ||
|
|
There was a problem hiding this comment.
Yeah, we probably move the backen to the functions operator eventually. It's not decided yet.
Summary
insecure-skip-tls-verify: truein generated kubeconfigs with proper CA certificate handlingGET /api/cluster/causes a two-probe approach: first tries system roots (public CA), then the service account CA bundle (private CA)ubi9-microruntime image (it ships with no CA infrastructure at all)Changes
Backend (
backend/main.go,backend/main_test.go)clusterCAHandlerstruct with injectableSystemTLSfor testability--kube-root-ca-pathCLI flag to override the default SA CA path for local devFrontend (
OcpClusterService.ts,OcpClusterService.test.ts)#fetchClusterCAcalls the backend via the console proxy#buildKubeconfigconditionally setscertificate-authority-datainstead ofinsecure-skip-tls-verifyDev environment (
init.sh)extract_cluster_caextracts the CA fromkube-root-ca.crtConfigMap viaoc--kube-root-ca-pathto the backend on startup and on file-watch restartContainer image (
Dockerfile)/etc/pki/tls/certs/ca-bundle.crtfromgo-toolsetbuild stage intoubi9-microTest plan
go test -v ./...(8 tests pass)yarn ci(lint + 14 suites / 127 tests + build){ca: null}{ca: "<base64>"}certificate-authority-data🤖 Generated with Claude Code