Skip to content

Commit

Permalink
Cherry-pick PRs into v2.10.8 release branch (#4941)
Browse files Browse the repository at this point in the history
Cherry-pick the following PRs into the 2.10.8 release branch:

* #4937
* #4935 
* #4938 
* #4940

Signed-off-by: Neil Twigg <neil@nats.io>
  • Loading branch information
neilalexander committed Jan 10, 2024
2 parents d67d53c + 32cb040 commit ab71f02
Show file tree
Hide file tree
Showing 10 changed files with 382 additions and 53 deletions.
12 changes: 12 additions & 0 deletions server/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,18 @@ func (a *Account) RemoveMapping(src string) bool {
a.mappings[len(a.mappings)-1] = nil // gc
a.mappings = a.mappings[:len(a.mappings)-1]
a.hasMapped.Store(len(a.mappings) > 0)
// If we have connected leafnodes make sure to update.
if a.nleafs > 0 {
// Need to release because lock ordering is client -> account
a.mu.Unlock()
// Now grab the leaf list lock. We can hold client lock under this one.
a.lmu.RLock()
for _, lc := range a.lleafs {
lc.forceRemoveFromSmap(src)
}
a.lmu.RUnlock()
a.mu.Lock()
}
return true
}
}
Expand Down
103 changes: 75 additions & 28 deletions server/auth.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2012-2023 The NATS Authors
// Copyright 2012-2024 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand Down Expand Up @@ -743,13 +743,24 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) (au
// Check if we have nkeys or users for client.
hasNkeys := len(s.nkeys) > 0
hasUsers := len(s.users) > 0
if hasNkeys && c.opts.Nkey != _EMPTY_ {
nkey, ok = s.nkeys[c.opts.Nkey]
if !ok || !c.connectionTypeAllowed(nkey.AllowedConnectionTypes) {
s.mu.Unlock()
return false
if hasNkeys {
if (c.kind == CLIENT || c.kind == LEAF) && noAuthUser != _EMPTY_ &&
c.opts.Username == _EMPTY_ && c.opts.Password == _EMPTY_ && c.opts.Token == _EMPTY_ && c.opts.Nkey == _EMPTY_ {
if _, exists := s.nkeys[noAuthUser]; exists {
c.mu.Lock()
c.opts.Nkey = noAuthUser
c.mu.Unlock()
}
}
if c.opts.Nkey != _EMPTY_ {
nkey, ok = s.nkeys[c.opts.Nkey]
if !ok || !c.connectionTypeAllowed(nkey.AllowedConnectionTypes) {
s.mu.Unlock()
return false
}
}
} else if hasUsers {
}
if hasUsers && nkey == nil {
// Check if we are tls verify and are mapping users from the client_certificate.
if tlsMap {
authorized := checkClientTLSCertSubject(c, func(u string, certDN *ldap.DN, _ bool) (string, bool) {
Expand Down Expand Up @@ -989,27 +1000,30 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) (au
}

if nkey != nil {
if c.opts.Sig == _EMPTY_ {
c.Debugf("Signature missing")
return false
}
sig, err := base64.RawURLEncoding.DecodeString(c.opts.Sig)
if err != nil {
// Allow fallback to normal base64.
sig, err = base64.StdEncoding.DecodeString(c.opts.Sig)
// If we did not match noAuthUser check signature which is required.
if nkey.Nkey != noAuthUser {
if c.opts.Sig == _EMPTY_ {
c.Debugf("Signature missing")
return false
}
sig, err := base64.RawURLEncoding.DecodeString(c.opts.Sig)
if err != nil {
c.Debugf("Signature not valid base64")
// Allow fallback to normal base64.
sig, err = base64.StdEncoding.DecodeString(c.opts.Sig)
if err != nil {
c.Debugf("Signature not valid base64")
return false
}
}
pub, err := nkeys.FromPublicKey(c.opts.Nkey)
if err != nil {
c.Debugf("User nkey not valid: %v", err)
return false
}
if err := pub.Verify(c.nonce, sig); err != nil {
c.Debugf("Signature not verified")
return false
}
}
pub, err := nkeys.FromPublicKey(c.opts.Nkey)
if err != nil {
c.Debugf("User nkey not valid: %v", err)
return false
}
if err := pub.Verify(c.nonce, sig); err != nil {
c.Debugf("Signature not verified")
return false
}
if err := c.RegisterNkeyUser(nkey); err != nil {
return false
Expand Down Expand Up @@ -1308,6 +1322,33 @@ func (s *Server) isLeafNodeAuthorized(c *client) bool {
// with that user (from the leafnode's authorization{} config).
if opts.LeafNode.Username != _EMPTY_ {
return isAuthorized(opts.LeafNode.Username, opts.LeafNode.Password, opts.LeafNode.Account)
} else if opts.LeafNode.Nkey != _EMPTY_ {
if c.opts.Nkey != opts.LeafNode.Nkey {
return false
}
if c.opts.Sig == _EMPTY_ {
c.Debugf("Signature missing")
return false
}
sig, err := base64.RawURLEncoding.DecodeString(c.opts.Sig)
if err != nil {
// Allow fallback to normal base64.
sig, err = base64.StdEncoding.DecodeString(c.opts.Sig)
if err != nil {
c.Debugf("Signature not valid base64")
return false
}
}
pub, err := nkeys.FromPublicKey(c.opts.Nkey)
if err != nil {
c.Debugf("User nkey not valid: %v", err)
return false
}
if err := pub.Verify(c.nonce, sig); err != nil {
c.Debugf("Signature not verified")
return false
}
return s.registerLeafWithAccount(c, opts.LeafNode.Account)
} else if len(opts.LeafNode.Users) > 0 {
if opts.LeafNode.TLSMap {
var user *User
Expand Down Expand Up @@ -1425,15 +1466,21 @@ func validateNoAuthUser(o *Options, noAuthUser string) error {
if len(o.TrustedOperators) > 0 {
return fmt.Errorf("no_auth_user not compatible with Trusted Operator")
}
if o.Users == nil {
return fmt.Errorf(`no_auth_user: "%s" present, but users are not defined`, noAuthUser)

if o.Nkeys == nil && o.Users == nil {
return fmt.Errorf(`no_auth_user: "%s" present, but users/nkeys are not defined`, noAuthUser)
}
for _, u := range o.Users {
if u.Username == noAuthUser {
return nil
}
}
for _, u := range o.Nkeys {
if u.Nkey == noAuthUser {
return nil
}
}
return fmt.Errorf(
`no_auth_user: "%s" not present as user in authorization block or account configuration`,
`no_auth_user: "%s" not present as user or nkey in authorization block or account configuration`,
noAuthUser)
}
27 changes: 26 additions & 1 deletion server/auth_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2012-2022 The NATS Authors
// Copyright 2012-2024 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand All @@ -15,6 +15,7 @@ package server

import (
"context"
"encoding/json"
"fmt"
"net"
"net/url"
Expand Down Expand Up @@ -277,6 +278,30 @@ func TestNoAuthUser(t *testing.T) {
}
}

func TestNoAuthUserNkey(t *testing.T) {
conf := createConfFile(t, []byte(`
listen: "127.0.0.1:-1"
accounts {
FOO { users [{user: "foo", password: "pwd1"}] }
BAR { users [{nkey: "UBO2MQV67TQTVIRV3XFTEZOACM4WLOCMCDMAWN5QVN5PI2N6JHTVDRON"}] }
}
no_auth_user: "UBO2MQV67TQTVIRV3XFTEZOACM4WLOCMCDMAWN5QVN5PI2N6JHTVDRON"
`))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()

// Make sure we connect ok and to the correct account.
nc := natsConnect(t, s.ClientURL())
resp, err := nc.Request(userDirectInfoSubj, nil, time.Second)
require_NoError(t, err)
response := ServerAPIResponse{Data: &UserInfo{}}
err = json.Unmarshal(resp.Data, &response)
require_NoError(t, err)
userInfo := response.Data.(*UserInfo)
require_Equal(t, userInfo.UserID, "UBO2MQV67TQTVIRV3XFTEZOACM4WLOCMCDMAWN5QVN5PI2N6JHTVDRON")
require_Equal(t, userInfo.Account, "BAR")
}

func TestUserConnectionDeadline(t *testing.T) {
clientAuth := &DummyAuth{
t: t,
Expand Down
49 changes: 44 additions & 5 deletions server/leafnode.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2019-2023 The NATS Authors
// Copyright 2019-2024 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand Down Expand Up @@ -239,7 +239,7 @@ func validateLeafNode(o *Options) error {
}
} else {
if len(o.LeafNode.Users) != 0 {
return fmt.Errorf("operator mode does not allow specifying user in leafnode config")
return fmt.Errorf("operator mode does not allow specifying users in leafnode config")
}
for _, r := range o.LeafNode.Remotes {
if !nkeys.IsValidPublicAccountKey(r.LocalAccount) {
Expand Down Expand Up @@ -299,12 +299,12 @@ func validateLeafNode(o *Options) error {
// with gateways. So if an option validation needs to be done regardless,
// it MUST be done before this point!

if o.Gateway.Name == "" && o.Gateway.Port == 0 {
if o.Gateway.Name == _EMPTY_ && o.Gateway.Port == 0 {
return nil
}
// If we are here we have both leaf nodes and gateways defined, make sure there
// is a system account defined.
if o.SystemAccount == "" {
if o.SystemAccount == _EMPTY_ {
return fmt.Errorf("leaf nodes and gateways (both being defined) require a system account to also be configured")
}
if err := validatePinnedCerts(o.LeafNode.TLSPinnedCerts); err != nil {
Expand Down Expand Up @@ -334,6 +334,9 @@ func validateLeafNodeAuthOptions(o *Options) error {
if o.LeafNode.Username != _EMPTY_ {
return fmt.Errorf("can not have a single user/pass and a users array")
}
if o.LeafNode.Nkey != _EMPTY_ {
return fmt.Errorf("can not have a single nkey and a users array")
}
users := map[string]struct{}{}
for _, u := range o.LeafNode.Users {
if _, exists := users[u.Username]; exists {
Expand Down Expand Up @@ -830,6 +833,19 @@ func (c *client) sendLeafConnect(clusterName string, headers bool) error {
sig := base64.RawURLEncoding.EncodeToString(sigraw)
cinfo.JWT = bytesToString(tmp)
cinfo.Sig = sig
} else if nkey := c.leaf.remote.Nkey; nkey != _EMPTY_ {
kp, err := nkeys.FromSeed([]byte(nkey))
if err != nil {
c.Errorf("Remote nkey has malformed seed")
return err
}
// Wipe our key on exit.
defer kp.Wipe()
sigraw, _ := kp.Sign(c.nonce)
sig := base64.RawURLEncoding.EncodeToString(sigraw)
pkey, _ := kp.PublicKey()
cinfo.Nkey = pkey
cinfo.Sig = sig
} else if userInfo := c.leaf.remote.curURL.User; userInfo != nil {
cinfo.User = userInfo.Username()
cinfo.Pass, _ = userInfo.Password()
Expand All @@ -839,7 +855,7 @@ func (c *client) sendLeafConnect(clusterName string, headers bool) error {
}
b, err := json.Marshal(cinfo)
if err != nil {
c.Errorf("Error marshaling CONNECT to route: %v\n", err)
c.Errorf("Error marshaling CONNECT to remote leafnode: %v\n", err)
return err
}
// Although this call is made before the writeLoop is created,
Expand Down Expand Up @@ -1688,6 +1704,7 @@ func (s *Server) removeLeafNodeConnection(c *client) {
// Connect information for solicited leafnodes.
type leafConnectInfo struct {
Version string `json:"version,omitempty"`
Nkey string `json:"nkey,omitempty"`
JWT string `json:"jwt,omitempty"`
Sig string `json:"sig,omitempty"`
User string `json:"user,omitempty"`
Expand Down Expand Up @@ -2169,6 +2186,28 @@ func (c *client) forceAddToSmap(subj string) {
c.sendLeafNodeSubUpdate(subj, 1)
}

// Used to force remove a subject from the subject map.
func (c *client) forceRemoveFromSmap(subj string) {
c.mu.Lock()
defer c.mu.Unlock()

if c.leaf.smap == nil {
return
}
n := c.leaf.smap[subj]
if n == 0 {
return
}
n--
if n == 0 {
// Remove is now zero
delete(c.leaf.smap, subj)
c.sendLeafNodeSubUpdate(subj, 0)
} else {
c.leaf.smap[subj] = n
}
}

// Send the subscription interest change to the other side.
// Lock should be held.
func (c *client) sendLeafNodeSubUpdate(key string, n int32) {
Expand Down

0 comments on commit ab71f02

Please sign in to comment.