From 488fd1bf49f5b39ca1b5aa3e979d9683c31bff8c Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Thu, 28 May 2026 04:29:31 +0000 Subject: [PATCH] feat(ha-config-bootstrap): add peer count and cert pre-flight validation (task 2.3) - Implement validate_peer_count() in startup_validation.c: -EINVAL for even N>1 - Implement validate_cert_files() with err_buf output: access() check, writes missing path into err_buf, returns -ENOENT - Wire validate_peer_count() in main.c after peer list parse; fatal exit on fail - Wire validate_cert_files() in main.c before SSL init; fatal exit on fail - Fix ASAN leak: SSL_CTX_free in ha_fini() with NULL guard - Fix double-free: null c->ha.ssl_ctx in cluster_fini() before ha_fini(), so iomgr_fini() retains sole SSL_CTX ownership (design per ha.h comment) - All 96 tests pass; all 12 ha_startup tests GREEN --- .kiro/specs/ha-config-bootstrap/tasks.md | 2 +- src/controller/cluster.c | 2 ++ src/controller/ha.c | 6 ++++++ src/controller/main.c | 26 +++++++++++++++++++++++- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/.kiro/specs/ha-config-bootstrap/tasks.md b/.kiro/specs/ha-config-bootstrap/tasks.md index 5ba049b3..f7a8580c 100644 --- a/.kiro/specs/ha-config-bootstrap/tasks.md +++ b/.kiro/specs/ha-config-bootstrap/tasks.md @@ -45,7 +45,7 @@ Dependency order: startup validation (D) unblocks vtaskmgr before election (A); - _Requirements: 4.3, 4.4_ - _Boundary: Startup Validation (main.c, cluster.h, cluster.c)_ -- [ ] 2.3 Add peer count validation and cert pre-flight checks +- [x] 2.3 Add peer count validation and cert pre-flight checks - In `main.c` after argument parsing: if `n_peers % 2 == 0 && n_peers > 1` → `fprintf(stderr, ...)` + `exit(1)` with message explaining odd-N requirement - Call `access(cert_path, R_OK)` and `access(key_path, R_OK)` in `main.c` before any SSL init; fatal exit with full path on failure - Fix ASAN leak: add `SSL_CTX_free(ha->ssl_ctx)` in `ha_fini()` diff --git a/src/controller/cluster.c b/src/controller/cluster.c index 868d8eb4..0d0b11f0 100644 --- a/src/controller/cluster.c +++ b/src/controller/cluster.c @@ -1047,6 +1047,8 @@ void cluster_fini(cluster_t* c) c->ha.standby_endpoints = NULL; } + /* iomgr owns the SSL_CTX lifetime; null ha's copy so ha_fini doesn't double-free */ + c->ha.ssl_ctx = NULL; /* Stop HA watchdog thread before tearing down other subsystems — P1-5 */ ha_fini(&c->ha); diff --git a/src/controller/ha.c b/src/controller/ha.c index aa7f84ea..684b4d23 100644 --- a/src/controller/ha.c +++ b/src/controller/ha.c @@ -454,6 +454,12 @@ void ha_fini(ha_state_t *ha) return; ha_stop_watchdog(ha); /* join watchdog thread before destroying mutex */ pthread_mutex_destroy(&ha->lock); + + /* Fix ASAN leak: free the SSL context if one was allocated */ + if (ha->ssl_ctx) { + SSL_CTX_free((SSL_CTX *)ha->ssl_ctx); + ha->ssl_ctx = NULL; + } } /* ── Redirect helpers used by join.c ───────────────────────── */ diff --git a/src/controller/main.c b/src/controller/main.c index 4cb88cc6..b3f8de0c 100644 --- a/src/controller/main.c +++ b/src/controller/main.c @@ -11,8 +11,11 @@ extern int myapp_run(myconf_t*); -/* forward declaration — implemented in startup_validation.c */ +/* forward declarations — implemented in startup_validation.c */ int validate_vcpus(int vcpus); +int validate_peer_count(int n_peers); +int validate_cert_files(const char *cert_path, const char *key_path, + char *err_buf, size_t err_len); static char doc[] = "xce_controller"; static char args_doc[] = ""; @@ -166,6 +169,17 @@ int main(int argc, char* argv[]) arguments.peers_str); return 1; } + + /* Req 4.1: reject even-N peer counts (cannot form safe majority) */ + if (validate_peer_count(myconf.peers_cfg.candidates_count) != 0) { + fprintf(stderr, + "xce_controller: --peers: even peer count (%d) is not " + "allowed; Raft quorum requires an odd number of nodes " + "(1, 3, 5, ...)\n", + myconf.peers_cfg.candidates_count); + return 1; + } + /* CA cert derived from workdir — same infrastructure as certfile/keyfile */ snprintf(myconf.peers_cfg.ca_cert_path, sizeof(myconf.peers_cfg.ca_cert_path), @@ -185,6 +199,16 @@ int main(int argc, char* argv[]) INFO("No --peers flag; running as single-node primary"); } + /* Req 4.2: cert pre-flight — fail early with a clear path in the message */ + { + char cert_err[XCE_MAXPATH_LEN + 64]; + if (validate_cert_files(cert_file, key_file, + cert_err, sizeof(cert_err)) != 0) { + fprintf(stderr, "xce_controller: %s\n", cert_err); + return 1; + } + } + myapp_run(&myconf); mylog_fini();