feat(embeddednats): nkey CustomClientAuthentication against the allowlist
busauth.NewNkeyAuthenticator verifies a client's nkey signature over the server nonce (decoding like nats-server: raw-url then std base64), maps the nkey to its Ed25519 hex, and consults an injected IsAuthorized predicate. Checking on every connection (rather than a static Options.Nkeys map) means revoking a user denies its next connection with no restart. embeddednats gains StartHostAuth(auth) and sets AlwaysEnableNonce so the server advertises the nonce nkey clients need; Start/StartHost stay open (auth=nil) for dev. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
package busauth
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
server "github.com/nats-io/nats-server/v2/server"
|
||||
"github.com/nats-io/nkeys"
|
||||
)
|
||||
|
||||
// nkeyAuthenticator is a NATS server.Authentication that authorizes a client by
|
||||
// verifying the nkey signature over the server-presented nonce and then
|
||||
// consulting the bus user allowlist. Authorization is checked on every new
|
||||
// connection via the injected predicate (not a static Options.Nkeys map), so
|
||||
// revoking a user denies its next connection without restarting the server.
|
||||
type nkeyAuthenticator struct {
|
||||
// isAuthorized reports whether the lowercase-hex Ed25519 public key behind an
|
||||
// nkey belongs to an active bus user. Injected (membership.Store.IsAuthorized)
|
||||
// so this package stays free of the store dependency.
|
||||
isAuthorized func(signPubHex string) bool
|
||||
}
|
||||
|
||||
// NewNkeyAuthenticator builds a NATS custom authenticator backed by isAuthorized.
|
||||
// Pass it to embeddednats so the data plane only accepts registered identities.
|
||||
func NewNkeyAuthenticator(isAuthorized func(signPubHex string) bool) server.Authentication {
|
||||
return &nkeyAuthenticator{isAuthorized: isAuthorized}
|
||||
}
|
||||
|
||||
// Check verifies the client's nkey signature against the nonce the server
|
||||
// presented, then maps the nkey to its allowlist key and checks authorization.
|
||||
// Any malformed input or failed verification yields false (fail closed). The
|
||||
// signature decoding mirrors nats-server's own (raw-url base64, then std base64
|
||||
// fallback) so genuine clients using nats.Nkey are accepted unchanged.
|
||||
func (a *nkeyAuthenticator) Check(c server.ClientAuthentication) bool {
|
||||
opts := c.GetOpts()
|
||||
if opts.Nkey == "" {
|
||||
return false
|
||||
}
|
||||
sig, err := base64.RawURLEncoding.DecodeString(opts.Sig)
|
||||
if err != nil {
|
||||
sig, err = base64.StdEncoding.DecodeString(opts.Sig)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
pub, err := nkeys.FromPublicKey(opts.Nkey)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if err := pub.Verify(c.GetNonce(), sig); err != nil {
|
||||
return false
|
||||
}
|
||||
signPubHex, err := SignPubHexFromNkey(opts.Nkey)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return a.isAuthorized(signPubHex)
|
||||
}
|
||||
Reference in New Issue
Block a user