feat(client,cmd,mobile): connect securely via client.Connect(caPath)
client.Connect is the single migration seam: a non-empty caPath connects with TLS pinned to the bus CA plus nkey auth (matching enforce + bus-tls), an empty caPath keeps the legacy plaintext dev connection; control-plane requests are signed either way. worker and chat gain a --ca flag; the gomobile NewSession gains a caPath parameter so the Android app bundles ca.crt and connects securely. Every peer now flows through one code path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+13
-12
@@ -27,11 +27,12 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var (
|
var (
|
||||||
natsURL = flag.String("nats-url", "nats://127.0.0.1:4250", "NATS url")
|
natsURL = flag.String("nats-url", "nats://127.0.0.1:4250", "NATS url")
|
||||||
ctrlURL = flag.String("ctrl-url", "http://127.0.0.1:8470", "membershipd control-plane url")
|
ctrlURL = flag.String("ctrl-url", "http://127.0.0.1:8470", "membershipd control-plane url")
|
||||||
roomSub = flag.String("room", "proc.test.ticks", "room subject to subscribe to")
|
roomSub = flag.String("room", "proc.test.ticks", "room subject to subscribe to")
|
||||||
idFile = flag.String("id-file", "./local_files/chat.id", "identity file path")
|
idFile = flag.String("id-file", "./local_files/chat.id", "identity file path")
|
||||||
demoEnc = flag.Bool("demo-encrypted", false, "run the encrypted forward-secrecy demo")
|
demoEnc = flag.Bool("demo-encrypted", false, "run the encrypted forward-secrecy demo")
|
||||||
|
caFile = flag.String("ca", "", "path to the bus CA cert (ca.crt); set to connect with TLS + nkey to a secured bus")
|
||||||
)
|
)
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@@ -39,19 +40,19 @@ func main() {
|
|||||||
log.SetPrefix("[chat] ")
|
log.SetPrefix("[chat] ")
|
||||||
|
|
||||||
if *demoEnc {
|
if *demoEnc {
|
||||||
runEncryptedDemo(*natsURL, *ctrlURL)
|
runEncryptedDemo(*natsURL, *ctrlURL, *caFile)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
runSimple(*natsURL, *ctrlURL, *roomSub, *idFile)
|
runSimple(*natsURL, *ctrlURL, *roomSub, *idFile, *caFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// runSimple subscribes to a cleartext subject and prints messages live.
|
// runSimple subscribes to a cleartext subject and prints messages live.
|
||||||
func runSimple(natsURL, ctrlURL, roomSub, idFile string) {
|
func runSimple(natsURL, ctrlURL, roomSub, idFile, caFile string) {
|
||||||
id, err := client.LoadOrCreateIdentity(idFile)
|
id, err := client.LoadOrCreateIdentity(idFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("identity: %v", err)
|
log.Fatalf("identity: %v", err)
|
||||||
}
|
}
|
||||||
c, err := client.New(natsURL, ctrlURL, id)
|
c, err := client.Connect(natsURL, ctrlURL, id, caFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("connect: %v", err)
|
log.Fatalf("connect: %v", err)
|
||||||
}
|
}
|
||||||
@@ -91,7 +92,7 @@ func shortID(id string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// runEncryptedDemo proves E2E encryption + forward secrecy end-to-end.
|
// runEncryptedDemo proves E2E encryption + forward secrecy end-to-end.
|
||||||
func runEncryptedDemo(natsURL, ctrlURL string) {
|
func runEncryptedDemo(natsURL, ctrlURL, caFile string) {
|
||||||
log.Printf("=== encrypted forward-secrecy demo ===")
|
log.Printf("=== encrypted forward-secrecy demo ===")
|
||||||
pass := true
|
pass := true
|
||||||
check := func(name string, ok bool) {
|
check := func(name string, ok bool) {
|
||||||
@@ -109,10 +110,10 @@ func runEncryptedDemo(natsURL, ctrlURL string) {
|
|||||||
idB, err := newEphemeralIdentity()
|
idB, err := newEphemeralIdentity()
|
||||||
must(err, "generate B identity")
|
must(err, "generate B identity")
|
||||||
|
|
||||||
a, err := client.New(natsURL, ctrlURL, idA)
|
a, err := client.Connect(natsURL, ctrlURL, idA, caFile)
|
||||||
must(err, "connect A")
|
must(err, "connect A")
|
||||||
defer a.Close()
|
defer a.Close()
|
||||||
b, err := client.New(natsURL, ctrlURL, idB)
|
b, err := client.Connect(natsURL, ctrlURL, idB, caFile)
|
||||||
must(err, "connect B")
|
must(err, "connect B")
|
||||||
defer b.Close()
|
defer b.Close()
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -23,6 +23,7 @@ func main() {
|
|||||||
ctrlURL = flag.String("ctrl-url", "http://127.0.0.1:8470", "membershipd control-plane url")
|
ctrlURL = flag.String("ctrl-url", "http://127.0.0.1:8470", "membershipd control-plane url")
|
||||||
roomSub = flag.String("room", "proc.test.ticks", "room subject to publish to")
|
roomSub = flag.String("room", "proc.test.ticks", "room subject to publish to")
|
||||||
idFile = flag.String("id-file", "./local_files/worker.id", "identity file path")
|
idFile = flag.String("id-file", "./local_files/worker.id", "identity file path")
|
||||||
|
caFile = flag.String("ca", "", "path to the bus CA cert (ca.crt); set to connect with TLS + nkey to a secured bus")
|
||||||
)
|
)
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("identity: %v", err)
|
log.Fatalf("identity: %v", err)
|
||||||
}
|
}
|
||||||
c, err := client.New(*natsURL, *ctrlURL, id)
|
c, err := client.Connect(*natsURL, *ctrlURL, id, *caFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("connect: %v", err)
|
log.Fatalf("connect: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-4
@@ -44,14 +44,18 @@ func GenerateIdentity(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewSession loads the identity at idPath and connects to the bus. natsURL is
|
// NewSession loads the identity at idPath and connects to the bus. natsURL is
|
||||||
// the data plane (for example nats://host:4250) and ctrlURL is the control
|
// the data plane (for example tls://host:4250) and ctrlURL is the control plane
|
||||||
// plane HTTP endpoint (for example http://host:8470).
|
// HTTP endpoint (for example http://host:8470). caPath is the path to the bus
|
||||||
func NewSession(idPath, natsURL, ctrlURL string) (*Session, error) {
|
// CA certificate (ca.crt) bundled with the app: when set, the session connects
|
||||||
|
// securely (TLS pinned to that CA + nkey authentication on the data plane),
|
||||||
|
// matching a bus running with auth + TLS. Pass an empty caPath to connect in
|
||||||
|
// plaintext to an unsecured (dev) bus.
|
||||||
|
func NewSession(idPath, natsURL, ctrlURL, caPath string) (*Session, error) {
|
||||||
id, err := client.LoadOrCreateIdentity(idPath)
|
id, err := client.LoadOrCreateIdentity(idPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c, err := client.New(natsURL, ctrlURL, id)
|
c, err := client.Connect(natsURL, ctrlURL, id, caPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,24 @@ func New(natsURL, ctrlURL string, id cs.Identity) (*Client, error) {
|
|||||||
return NewWithOptions(natsURL, ctrlURL, id, Options{})
|
return NewWithOptions(natsURL, ctrlURL, id, Options{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Connect is the single migration seam every peer (worker, chat, mobile,
|
||||||
|
// gateway) uses to pick its security posture from one input: the CA path. With
|
||||||
|
// a non-empty caPath it connects securely — TLS pinned to that CA plus nkey
|
||||||
|
// authentication on the data plane — matching a bus running with bus-auth
|
||||||
|
// enforce + bus-tls. With an empty caPath it falls back to the legacy plaintext,
|
||||||
|
// no-nkey connection for local dev against an unsecured bus. The control-plane
|
||||||
|
// HTTP requests are signed in both cases (that signing is unconditional).
|
||||||
|
func Connect(natsURL, ctrlURL string, id cs.Identity, caPath string) (*Client, error) {
|
||||||
|
if caPath == "" {
|
||||||
|
return New(natsURL, ctrlURL, id)
|
||||||
|
}
|
||||||
|
tlsCfg, err := busauth.LoadCATLSConfig(caPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("client: load CA %q: %w", caPath, err)
|
||||||
|
}
|
||||||
|
return NewWithOptions(natsURL, ctrlURL, id, Options{UseNkey: true, TLS: tlsCfg})
|
||||||
|
}
|
||||||
|
|
||||||
// NewWithOptions is New with explicit connection options (nkey auth, and, from
|
// NewWithOptions is New with explicit connection options (nkey auth, and, from
|
||||||
// phase 0001d, TLS). It is the single place the data-plane connection is built,
|
// phase 0001d, TLS). It is the single place the data-plane connection is built,
|
||||||
// so every peer (worker, chat, mobile, gateway) gets identical behavior by
|
// so every peer (worker, chat, mobile, gateway) gets identical behavior by
|
||||||
|
|||||||
Reference in New Issue
Block a user