From 2d74853196aaf866e68fe2dbcc473e53ef04ce4d Mon Sep 17 00:00:00 2001 From: lakshit verma Date: Sat, 8 Aug 2026 00:57:26 +0530 Subject: [PATCH] metaserver: retry TLS with --ca-file alone, not only with certs The TLS retry was gated on hasCerts (cert-file and key-file both set), so a TLS-only MetaServer with a CA-signed server certificate was never tried when the caller passed only --ca-file. The tool then reported AuthRequiredError and recommended the cert flags that would not help. Run the TLS retry whenever a CA file or client certificates are provided, and report a distinct reason when the handshake fails with a CA-only setup. --- pkg/metaserver/client.go | 16 ++++++++++------ pkg/metaserver/client_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/pkg/metaserver/client.go b/pkg/metaserver/client.go index 59e5302..608fe4c 100644 --- a/pkg/metaserver/client.go +++ b/pkg/metaserver/client.go @@ -47,9 +47,9 @@ type Client struct { // ListRuntimeClasses fetches the RuntimeClass objects served by the // MetaServer. Auth is detected and handled in three modes: plain HTTP when -// no auth is configured, mTLS retry when client certificates are provided, -// and an AuthRequiredError when the server demands certificates that were -// not provided. +// no auth is configured, a TLS retry when a CA file or client certificates +// are provided, and an AuthRequiredError when the server demands +// certificates that were not provided. func (c *Client) ListRuntimeClasses(ctx context.Context) ([]nodev1.RuntimeClass, error) { endpoint := c.endpoint() useTLS := strings.HasPrefix(c.BaseURL, "https://") @@ -59,7 +59,7 @@ func (c *Client) ListRuntimeClasses(ctx context.Context) ([]nodev1.RuntimeClass, return c.handleStatus(body, code) } - if c.hasCerts() { + if c.hasCerts() || c.CAFile != "" { body, code, retryErr := c.get(ctx, c.httpClient(true), endpoint) if retryErr == nil { return c.handleStatus(body, code) @@ -68,10 +68,14 @@ func (c *Client) ListRuntimeClasses(ctx context.Context) ([]nodev1.RuntimeClass, } if c.speaksTLS(endpoint) { - if c.hasCerts() { + switch { + case c.hasCerts(): return nil, &AuthRequiredError{Reason: fmt.Sprintf("MetaServer rejected client certificates: %v", err)} + case c.CAFile != "": + return nil, &AuthRequiredError{Reason: fmt.Sprintf("MetaServer TLS handshake failed: %v; pass --cert-file/--key-file if the server requires client certificates", err)} + default: + return nil, &AuthRequiredError{Reason: "MetaServer requires TLS with client certificates; pass --cert-file/--key-file/--ca-file"} } - return nil, &AuthRequiredError{Reason: "MetaServer requires TLS with client certificates; pass --cert-file/--key-file/--ca-file"} } return nil, &UnreachableError{Reason: fmt.Sprintf("MetaServer not reachable at %s: %v", c.BaseURL, err)} } diff --git a/pkg/metaserver/client_test.go b/pkg/metaserver/client_test.go index 21b6c0b..31fe640 100644 --- a/pkg/metaserver/client_test.go +++ b/pkg/metaserver/client_test.go @@ -276,3 +276,32 @@ func TestAuthTLSWithCertsSucceeds(t *testing.T) { t.Fatalf("expected [kata], got %+v", classes) } } + +// TestTLSWithCAOnlySucceeds covers a TLS-only MetaServer whose certificate +// is signed by a CA the caller trusts: no client certificate is needed, and +// passing --ca-file alone must trigger the TLS retry. +func TestTLSWithCAOnlySucceeds(t *testing.T) { + pki := generateTestPKI(t) + server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(runtimeClassList("kata")) + })) + server.TLS = &tls.Config{ + Certificates: []tls.Certificate{pki.serverCert}, + MinVersion: tls.VersionTLS12, + } + server.StartTLS() + defer server.Close() + + client := &Client{ + BaseURL: server.URL, + Timeout: 5 * time.Second, + CAFile: pki.caPath, + } + classes, err := client.ListRuntimeClasses(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(classes) != 1 || classes[0].Name != "kata" { + t.Fatalf("expected [kata], got %+v", classes) + } +}