package main import ( "crypto/sha256" "crypto/tls" "crypto/x509" "encoding/hex" "encoding/json" "errors" "fmt" "io" "os" "strings" "time" "github.com/nats-io/nats.go" ) // Credential is what the mesh seals to a module so it can reach the bus (mesh-controller // cmd/mesh-controller issueWith): where, the server's certificate by fingerprint, who, and the // password. The address is not used here — this runs beside the server, and the server is reached on // loopback — but the fingerprint is: loopback or not, the certificate is the mesh's own. type Credential struct { URL string `json:"url"` Fingerprint string `json:"fingerprint,omitempty"` User string `json:"user"` Password string `json:"password"` } func readCredential(path string) (Credential, error) { var raw []byte var err error if path == "-" { raw, err = io.ReadAll(io.LimitReader(os.Stdin, 1<<20)) } else { raw, err = os.ReadFile(path) } if err != nil { return Credential{}, fmt.Errorf("cannot read the credential: %w", err) } var c Credential if err := json.Unmarshal(raw, &c); err != nil { return Credential{}, errors.New("the credential is not the JSON the mesh seals to a module (url, fingerprint, user, password)") } if c.User == "" || c.Password == "" { return Credential{}, errors.New("the credential names no user or password, so the bus would refuse it") } return c, nil } // connect dials the bus as the credential's user. A `tls://` server is held to the credential's // fingerprint and nothing else — the mesh's bus presents a certificate of its own, in no trust store, // so a name check could only fail or be skipped. A plain `nats://` server is accepted only on // loopback, and is what a restore's own private server is. func connect(server string, c Credential) (*nats.Conn, error) { opts := []nats.Option{ nats.UserInfo(c.User, c.Password), nats.Name("mesh-nats-snapshot"), // Answers come to the user's own inbox and no other: the bus grants every user its own // `_INBOX..>` and nothing wider (novox/hq design 25 §4). nats.CustomInboxPrefix("_INBOX." + c.User), nats.Timeout(10 * time.Second), // One attempt: a snapshot that cannot reach the bus fails the night, loudly, rather than // waiting for it. nats.NoReconnect(), } switch { case strings.HasPrefix(server, "tls://"): if c.Fingerprint == "" { return nil, errors.New("the credential carries no fingerprint, so the bus's certificate cannot be checked") } opts = append(opts, nats.Secure(pinnedTo(c.Fingerprint))) case strings.HasPrefix(server, "nats://127.0.0.1:"), strings.HasPrefix(server, "nats://localhost:"): if c.Fingerprint != "" { // The mesh's bus requires TLS; a plain connection to it would be refused anyway. opts = append(opts, nats.Secure(pinnedTo(c.Fingerprint))) } default: return nil, fmt.Errorf("%s: the bus is reached over tls:// (or plain nats:// on loopback, for a restore's own server)", server) } nc, err := nats.Connect(server, opts...) if err != nil { return nil, fmt.Errorf("cannot reach the bus at %s as %s: %w", server, c.User, err) } return nc, nil } // pinnedTo accepts exactly the certificate with this SHA-256 — the same pin every machine holds // (mesh-controller internal/broker PinnedToFingerprint). func pinnedTo(want string) *tls.Config { return &tls.Config{ InsecureSkipVerify: true, //nolint:gosec // replaced by the pin below, which is stricter MinVersion: tls.VersionTLS12, VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error { if len(rawCerts) == 0 { return errors.New("the bus presented no certificate") } sum := sha256.Sum256(rawCerts[0]) if got := "sha256:" + hex.EncodeToString(sum[:]); got != want { return fmt.Errorf("the bus presented a certificate this mesh does not know (%s)", got) } return nil }, } }