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.
This commit is contained in:
lakshit verma 2026-08-08 00:57:26 +05:30
parent 92af3a99e3
commit 2d74853196
No known key found for this signature in database
2 changed files with 39 additions and 6 deletions

View file

@ -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)}
}

View file

@ -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)
}
}