feat(server): add mTLS certificate bootstrap via JWT for Home connections

- Introduced `-home-jwt` flag and `HOME_JWT` environment variable to provide JWT for mTLS certificate generation.
- Added new APIs to handle certificate requests, validate JWT claims, and manage local certificate files.
- Updated Home TLS configuration to support client certificates, keys, and dynamic server name resolution.
This commit is contained in:
Luis Pater
2026-05-19 00:53:40 +08:00
parent cc0cb057b3
commit 77ba15f71b
4 changed files with 414 additions and 8 deletions
+56 -1
View File
@@ -190,6 +190,7 @@ func main() {
var password string
var homeAddr string
var homePassword string
var homeJWT string
var homeDisableClusterDiscovery bool
var tuiMode bool
var standalone bool
@@ -212,6 +213,7 @@ func main() {
flag.StringVar(&password, "password", "", "")
flag.StringVar(&homeAddr, "home", "", "Home control plane address in host:port, redis://host:port, or rediss://host:port format (loads config from home and skips local config file)")
flag.StringVar(&homePassword, "home-password", "", "Home control plane password (Redis AUTH)")
flag.StringVar(&homeJWT, "home-jwt", "", "Home control plane JWT for mTLS certificate bootstrap and connection")
flag.BoolVar(&homeDisableClusterDiscovery, "home-disable-cluster-discovery", false, "Disable Home CLUSTER NODES discovery and keep using the configured -home address")
flag.BoolVar(&tuiMode, "tui", false, "Start with terminal management UI")
flag.BoolVar(&standalone, "standalone", false, "In TUI mode, start an embedded local server")
@@ -311,6 +313,11 @@ func main() {
homePassword = v
}
}
if strings.TrimSpace(homeJWT) == "" {
if v, ok := lookupEnv("HOME_JWT", "home_jwt"); ok {
homeJWT = v
}
}
if value, ok := lookupEnv("PGSTORE_DSN", "pgstore_dsn"); ok {
usePostgresStore = true
@@ -375,7 +382,55 @@ func main() {
// Determine and load the configuration file.
// Prefer the Postgres store when configured, otherwise fallback to git or local files.
var configFilePath string
if strings.TrimSpace(homeAddr) != "" {
if strings.TrimSpace(homeJWT) != "" {
configLoadedFromHome = true
ctxHome, cancelHome := context.WithTimeout(context.Background(), 30*time.Second)
homeCfg, errHomeCfg := home.ConfigFromJWT(ctxHome, homeJWT)
cancelHome()
if errHomeCfg != nil {
log.Errorf("invalid -home-jwt: %v", errHomeCfg)
return
}
if homeDisableClusterDiscovery {
homeCfg.DisableClusterDiscovery = true
}
homeClient := home.New(homeCfg)
defer homeClient.Close()
ctxHomeConfig, cancelHomeConfig := context.WithTimeout(context.Background(), 30*time.Second)
raw, errGetConfig := homeClient.GetConfig(ctxHomeConfig)
cancelHomeConfig()
if errGetConfig != nil {
log.Errorf("failed to fetch config from home: %v", errGetConfig)
return
}
parsed, errParseConfig := config.ParseConfigBytes(raw)
if errParseConfig != nil {
log.Errorf("failed to parse config payload from home: %v", errParseConfig)
return
}
if parsed == nil {
parsed = &config.Config{}
}
parsed.Home = homeCfg
parsed.Port = 8317 // Default to 8317 for home mode, can be overridden by home config
parsed.UsageStatisticsEnabled = true
cfg = parsed
// Keep a non-empty config path for downstream components (log paths, management assets, etc),
// but do not require the file to exist when loading config from home.
if strings.TrimSpace(configPath) != "" {
configFilePath = configPath
} else {
configFilePath = filepath.Join(wd, "config.yaml")
}
// Local stores are intentionally disabled when config is loaded from home.
usePostgresStore = false
useObjectStore = false
useGitStore = false
} else if strings.TrimSpace(homeAddr) != "" {
configLoadedFromHome = true
trimmedHomePassword := strings.TrimSpace(homePassword)
homeCfg, errHomeCfg := parseHomeFlagConfig(homeAddr, trimmedHomePassword)