From b64777952158e9fa1826cffd4ed272e62728ece6 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sun, 7 Jun 2026 12:49:19 +0200 Subject: [PATCH] feat(membershipd): enable NATS nkey auth (enforce) and TLS flags Opens the store before NATS so the authenticator can consult IsAuthorized. Under --bus-auth enforce the embedded NATS gets the nkey authenticator (only allowlisted identities connect); --tls-cert/--tls-key make it present the server certificate and require TLS. External NATS manages its own auth/TLS. Co-Authored-By: Claude Opus 4.8 (1M context) --- cmd/membershipd/main.go | 64 +++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/cmd/membershipd/main.go b/cmd/membershipd/main.go index a26428e..23a444b 100644 --- a/cmd/membershipd/main.go +++ b/cmd/membershipd/main.go @@ -17,6 +17,7 @@ import ( server "github.com/nats-io/nats-server/v2/server" "github.com/enmanuel/unibus/pkg/blobstore" + "github.com/enmanuel/unibus/pkg/busauth" "github.com/enmanuel/unibus/pkg/embeddednats" "github.com/enmanuel/unibus/pkg/membership" ) @@ -41,6 +42,8 @@ func main() { natsPort = flag.Int("nats-port", 4250, "embedded NATS listen port (when --nats-url empty)") natsStore = flag.String("nats-store", "./local_files/jetstream", "embedded JetStream store dir") busAuth = flag.String("bus-auth", "off", "control-plane auth rollout: off|soft|enforce (feature flag bus-auth)") + tlsCert = flag.String("tls-cert", "", "PATH to the NATS server certificate (deploy/tls/server.crt); enables TLS on the embedded data plane") + tlsKey = flag.String("tls-key", "", "path to the NATS server private key (deploy/tls/server.key); required with --tls-cert") ) flag.Parse() @@ -52,25 +55,8 @@ func main() { log.SetFlags(log.LstdFlags | log.Lmsgprefix) log.SetPrefix("[membershipd] ") - // Data plane: embedded or external NATS. - var ns *server.Server - natsClientURL := *natsURL - if natsClientURL == "" { - var err error - // Bind the embedded NATS to the same interface as the HTTP API so a single - // --bind flag governs reachability: 127.0.0.1 keeps the whole stack - // loopback-only; 0.0.0.0 exposes both planes to the LAN. - ns, err = embeddednats.StartHost(*natsStore, *bind, *natsPort) - if err != nil { - log.Fatalf("start embedded nats: %v", err) - } - natsClientURL = embeddednats.ClientURL(ns) - log.Printf("embedded NATS (JetStream) ready: %s", natsClientURL) - } else { - log.Printf("using external NATS: %s", natsClientURL) - } - - // Control plane: SQLite store + blob store + HTTP API. + // Control plane store first: the NATS authenticator consults IsAuthorized, so + // the store must exist before the embedded server starts. store, err := membership.Open(*dbPath) if err != nil { log.Fatalf("open membership store: %v", err) @@ -84,6 +70,46 @@ func main() { } log.Printf("blob store: %s", *storeDir) + // Data plane: embedded or external NATS. For the embedded server, enforce + // turns on the nkey authenticator (only allowlisted identities may connect) + // and --tls-cert/--tls-key turn on TLS. An external NATS manages its own + // auth/TLS, so those flags do not apply to it. + var ns *server.Server + natsClientURL := *natsURL + if natsClientURL == "" { + cfg := embeddednats.ServerConfig{ + // Bind the embedded NATS to the same interface as the HTTP API so a + // single --bind flag governs reachability: 127.0.0.1 keeps the whole + // stack loopback-only; 0.0.0.0 exposes both planes to the LAN. + StoreDir: *natsStore, + Host: *bind, + Port: *natsPort, + } + if authMode == membership.AuthEnforce { + cfg.Auth = busauth.NewNkeyAuthenticator(store.IsAuthorized) + log.Printf("NATS nkey authentication: ON (enforce)") + } + if *tlsCert != "" || *tlsKey != "" { + if *tlsCert == "" || *tlsKey == "" { + log.Fatalf("--tls-cert and --tls-key must be set together") + } + tlsCfg, err := busauth.ServerTLSConfig(*tlsCert, *tlsKey) + if err != nil { + log.Fatalf("load NATS TLS: %v", err) + } + cfg.TLS = tlsCfg + log.Printf("NATS TLS: ON (%s)", *tlsCert) + } + ns, err = embeddednats.StartServer(cfg) + if err != nil { + log.Fatalf("start embedded nats: %v", err) + } + natsClientURL = embeddednats.ClientURL(ns) + log.Printf("embedded NATS (JetStream) ready: %s", natsClientURL) + } else { + log.Printf("using external NATS: %s", natsClientURL) + } + srv := membership.NewServer(store, blobs, authMode) log.Printf("control-plane auth: %s", authMode) addr := *bind + ":" + *httpPort