fix(scheduler,cli): bug-1453/1454/1455/1456 remove dead CA-cert plumbing, fix scheduler HA config bugs

bug-1453: remove unused client-side CA-certificate plumbing. C-side:
drop ca_cert_path from xce_cluster_config_t, do_connect()'s ca_cert
param, --ca-cert flags on worker/scheduler, and controller's cert
population (TLS is server-side only, SSL_VERIFY_NONE everywhere on
the client side already, verified zero SSL_CTX_load_verify_locations
calls in the C tree). Go-side: delete xcecli's self-signed cert
TOFU-pinning flow (cmd/auth/cert/*), CACert field, SetRootCAs/
loadRootCAs, and every call site.

bug-1454: fix xcecli_ctx_t leak in scheduler.c sched_init() — the
unconditional initial ctx_init was never freed before the HA
candidate loop re-initialized it on the first iteration.

bug-1455: sched_init()'s memset(sched, 0, ...) was silently wiping
sched->cluster_cfg that main.c had populated from --peers just before
calling sched_init(), so --peers was always ignored and the scheduler
fell back to the hardcoded localhost:9990 single-host default.
Restructured sched_init() to take cluster_cfg as an explicit parameter
(assigned after the memset) instead of relying on caller-side
ordering with a separate sched_set_cluster_cfg() setter, which is now
removed.

bug-1456: revert an unsafe stack-local xce_client_config_t introduced
while fixing bug-1455 — xcecli_ctx_init() stores that pointer as
ctx->conf and client_failover.c dereferences it for the ctx's entire
life (runtime reconnect), so a per-loop-iteration stack copy would
dangle the moment sched_init() returned. Back to mutating the
caller-owned, process-lifetime config in place.

Verified: full ASAN build (all components), meson test suite 9/9 OK,
manual scheduler testsuite 15/15, Go build/vet/test all pass.
This commit is contained in:
Rongsong Shen 2026-07-20 12:01:10 +00:00
parent 619f41f70e
commit 94f50ce6e1
34 changed files with 1999 additions and 2274 deletions

View file

@ -1,8 +1,6 @@
package request
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
@ -45,34 +43,6 @@ func NewAPIClient(baseURL string) *APIClient {
return c
}
func (c *APIClient) SetRootCAs(certPath string) {
if !strings.HasPrefix(c.BaseURL, "https://") {
return
}
if certPath == "" {
c.Client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) //nolint:gosec
return
}
pool := loadRootCAs(certPath)
c.Client.SetTLSClientConfig(&tls.Config{RootCAs: pool})
}
// loadRootCAs load the root certificate
func loadRootCAs(certPath string) *x509.CertPool {
certPool := x509.NewCertPool()
cert, err := os.ReadFile(certPath)
if err != nil {
logger.Error("failed to read the cert file")
return nil
}
if ok := certPool.AppendCertsFromPEM(cert); !ok {
logger.Error("failed to append the cert")
return nil
}
return certPool
}
// Logon perform the login operation
func (c *APIClient) Logon(username, password string) (*models.LogonResponse, error) {
var loginResp models.LogonResponse
@ -100,13 +70,8 @@ func (c *APIClient) Logon(username, password string) (*models.LogonResponse, err
return &loginResp, nil
}
func (c *APIClient) GetRequestFromAPIServer(token, pageToken, certPath, dst string, resp any, params map[string]string) error {
func (c *APIClient) GetRequestFromAPIServer(token, pageToken, dst string, resp any, params map[string]string) error {
baseURL := c.BaseURL
if strings.HasPrefix(baseURL, "https://") {
c.Client.SetTLSClientConfig(&tls.Config{
RootCAs: loadRootCAs(certPath),
})
}
if idx := strings.Index(baseURL, dst); idx != -1 {
baseURL = baseURL[:idx]
}
@ -271,13 +236,8 @@ func (c *APIClient) PostFile(token string, files []string) (*models.UploadRespon
}
// Post sends a POST request to the given URL with the given data
func (c *APIClient) PostRequestToAPIServer(token, certPath, dst string, data []byte, resp interface{}) error {
func (c *APIClient) PostRequestToAPIServer(token, dst string, data []byte, resp interface{}) error {
baseURL := c.BaseURL
if strings.HasPrefix(baseURL, "https://") && certPath != "" {
c.Client.SetTLSClientConfig(&tls.Config{
RootCAs: loadRootCAs(certPath),
})
}
if idx := strings.Index(baseURL, dst); idx != -1 {
baseURL = baseURL[:idx]
}
@ -306,13 +266,8 @@ func (c *APIClient) PostRequestToAPIServer(token, certPath, dst string, data []b
return nil
}
func (c *APIClient) DeleteRequestFromAPIServer(token, certPath, dst string, data []byte, resp interface{}) error {
func (c *APIClient) DeleteRequestFromAPIServer(token, dst string, data []byte, resp interface{}) error {
baseURL := c.BaseURL
if strings.HasPrefix(baseURL, "https://") && certPath != "" {
c.Client.SetTLSClientConfig(&tls.Config{
RootCAs: loadRootCAs(certPath),
})
}
if idx := strings.Index(baseURL, dst); idx != -1 {
baseURL = baseURL[:idx]
}

View file

@ -2,36 +2,6 @@ package request
import "testing"
// TestSetRootCAs_HTTPNoOp verifies HTTP clients are not affected.
func TestSetRootCAs_HTTPNoOp(t *testing.T) {
c := NewAPIClient("http://localhost:8080")
c.SetRootCAs("/some/cert.pem") // must not panic
}
// TestSetRootCAs_HTTPSEmptyCert verifies InsecureSkipVerify is set when no cert provided.
func TestSetRootCAs_HTTPSEmptyCert(t *testing.T) {
c := NewAPIClient("https://localhost:8443")
c.SetRootCAs("") // no cert → insecure
// If we reach here without panic, the call succeeded.
// Behavioral correctness is verified by TestBuildTLSLogic below.
}
// TestBuildTLSLogic tests the cert-path branching logic directly.
func TestBuildTLSLogic(t *testing.T) {
// empty cert → insecure
if certPath := ""; certPath != "" {
t.Error("logic error in test")
}
// non-empty cert → not insecure (even if file missing, InsecureSkipVerify=false)
insecure := func(certPath string) bool { return certPath == "" }
if !insecure("") {
t.Error("empty certPath should be insecure")
}
if insecure("/some/cert.pem") {
t.Error("non-empty certPath should not be insecure")
}
}
func TestAuthHeaderConsistency(t *testing.T) {
token := "test-token-abc"
headers := map[string]string{