feat(busauth,client): pin the bus CA over TLS
busauth.LoadCATLSConfig turns a ca.crt path into a *tls.Config trusting only that private CA (clients must pin it; the system roots would reject a self-signed server cert). busauth.ServerTLSConfig loads the server keypair. client.Options gains TLS; NewWithOptions calls nats.Secure when set, so the data-plane connection is encrypted and the server pinned. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
package busauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoadCATLSConfig builds a *tls.Config that trusts ONLY the given CA certificate
|
||||||
|
// (PEM file), for a bus client pinning the project's self-signed CA. Because the
|
||||||
|
// bus uses a private CA rather than a public one, clients must pin it explicitly;
|
||||||
|
// trusting the system roots would reject the server cert. This is the single
|
||||||
|
// helper every client (Go peers, the mobile binding, the gateway) uses to turn a
|
||||||
|
// ca.crt path into a connection config.
|
||||||
|
func LoadCATLSConfig(caPEMPath string) (*tls.Config, error) {
|
||||||
|
pem, err := os.ReadFile(caPEMPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("busauth: read CA %q: %w", caPEMPath, err)
|
||||||
|
}
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
if !pool.AppendCertsFromPEM(pem) {
|
||||||
|
return nil, fmt.Errorf("busauth: CA %q contains no valid PEM certificate", caPEMPath)
|
||||||
|
}
|
||||||
|
return &tls.Config{RootCAs: pool, MinVersion: tls.VersionTLS12}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerTLSConfig loads the bus NATS server's certificate and private key (PEM
|
||||||
|
// files) into a *tls.Config to present to clients. The private key never leaves
|
||||||
|
// the host; only the CA cert travels to clients.
|
||||||
|
func ServerTLSConfig(certPEMPath, keyPEMPath string) (*tls.Config, error) {
|
||||||
|
cert, err := tls.LoadX509KeyPair(certPEMPath, keyPEMPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("busauth: load server keypair: %w", err)
|
||||||
|
}
|
||||||
|
return &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12}, nil
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -66,6 +67,10 @@ type Options struct {
|
|||||||
// with an nkey to a server that does not advertise nkey auth ("nkeys not
|
// with an nkey to a server that does not advertise nkey auth ("nkeys not
|
||||||
// supported by the server"), so this is opt-in rather than always-on.
|
// supported by the server"), so this is opt-in rather than always-on.
|
||||||
UseNkey bool
|
UseNkey bool
|
||||||
|
// TLS, when non-nil, secures the NATS connection and pins the server to this
|
||||||
|
// config's RootCAs (the bus's self-signed CA). Build it with
|
||||||
|
// busauth.LoadCATLSConfig(caPath). Nil keeps the connection plaintext.
|
||||||
|
TLS *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// New connects to NATS and records the control-plane URL with default Options
|
// New connects to NATS and records the control-plane URL with default Options
|
||||||
@@ -87,6 +92,9 @@ func NewWithOptions(natsURL, ctrlURL string, id cs.Identity, opts Options) (*Cli
|
|||||||
}
|
}
|
||||||
natsOpts = append(natsOpts, nats.Nkey(nkeyPub, nkeySign))
|
natsOpts = append(natsOpts, nats.Nkey(nkeyPub, nkeySign))
|
||||||
}
|
}
|
||||||
|
if opts.TLS != nil {
|
||||||
|
natsOpts = append(natsOpts, nats.Secure(opts.TLS))
|
||||||
|
}
|
||||||
nc, err := nats.Connect(natsURL, natsOpts...)
|
nc, err := nats.Connect(natsURL, natsOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("client: connect nats %q: %w", natsURL, err)
|
return nil, fmt.Errorf("client: connect nats %q: %w", natsURL, err)
|
||||||
|
|||||||
Reference in New Issue
Block a user