Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIXED] LeafNode TLSMap and websocket auth override #1470

Merged
merged 1 commit into from Jun 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
157 changes: 84 additions & 73 deletions server/auth.go
Expand Up @@ -261,7 +261,7 @@ func (s *Server) buildNkeysAndUsersFromOptions(nko []*NkeyUser, uo []*User) (map
var nkeys map[string]*NkeyUser
var users map[string]*User

if len(nko) > 0 {
if nko != nil {
nkeys = make(map[string]*NkeyUser, len(nko))
for _, u := range nko {
copy := u.clone()
Expand All @@ -276,7 +276,7 @@ func (s *Server) buildNkeysAndUsersFromOptions(nko []*NkeyUser, uo []*User) (map
nkeys[u.Nkey] = copy
}
}
if len(uo) > 0 {
if uo != nil {
users = make(map[string]*User, len(uo))
for _, u := range uo {
copy := u.clone()
Expand Down Expand Up @@ -327,60 +327,6 @@ func (s *Server) isClientAuthorized(c *client) bool {
return s.processClientOrLeafAuthentication(c, opts)
}

type authOpts struct {
username string
password string
token string
noAuthUser string
tlsMap bool
users map[string]*User
nkeys map[string]*NkeyUser
}

func (s *Server) getAuthOpts(c *client, o *Options, auth *authOpts) bool {
// c.ws is immutable, but may need lock if we get race reports.
wsClient := c.ws != nil

authRequired := s.info.AuthRequired
// For websocket clients, if there is no top-level auth, then we
// check for websocket specifically.
if !authRequired && wsClient {
authRequired = s.websocket.authRequired
}
if !authRequired {
return false
}
auth.noAuthUser = o.NoAuthUser
auth.tlsMap = o.TLSMap
if wsClient {
wo := &o.Websocket
// If those are specified, override, regardless if there is
// auth configuration (like user/pwd, etc..) in websocket section.
if wo.NoAuthUser != _EMPTY_ {
auth.noAuthUser = wo.NoAuthUser
}
if wo.TLSMap {
auth.tlsMap = true
}
// Now check for websocket auth specific override
if s.websocket.authRequired {
auth.username = wo.Username
auth.password = wo.Password
auth.token = wo.Token
auth.users = s.websocket.users
auth.nkeys = s.websocket.nkeys
return true
}
// else fallback to regular auth config
}
auth.username = o.Username
auth.password = o.Password
auth.token = o.Authorization
auth.users = s.users
auth.nkeys = s.nkeys
return true
}

func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) bool {
var (
nkey *NkeyUser
Expand All @@ -389,16 +335,56 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) boo
user *User
ok bool
err error
auth authOpts
ao bool // auth override
)

s.mu.Lock()
authRequired := s.getAuthOpts(c, opts, &auth)
authRequired := s.info.AuthRequired
// c.ws is immutable, but may need lock if we get race reports.
if !authRequired && c.ws != nil {
// If no auth required for regular clients, then check if
// we have an override for websocket clients.
authRequired = s.websocket.authOverride
}
if !authRequired {
// TODO(dlc) - If they send us credentials should we fail?
s.mu.Unlock()
return true
}
var (
username string
password string
token string
noAuthUser string
users map[string]*User
nkusers map[string]*NkeyUser
)
tlsMap := opts.TLSMap
if c.ws != nil {
wo := &opts.Websocket
// Always override TLSMap.
tlsMap = wo.TLSMap
// The rest depends on if there was any auth override in
// the websocket's config.
if s.websocket.authOverride {
noAuthUser = wo.NoAuthUser
username = wo.Username
password = wo.Password
token = wo.Token
users = s.websocket.users
nkusers = s.websocket.nkeys
ao = true
}
} else if c.kind == LEAF {
tlsMap = opts.LeafNode.TLSMap
}
if !ao {
noAuthUser = opts.NoAuthUser
username = opts.Username
password = opts.Password
token = opts.Authorization
users = s.users
nkusers = s.nkeys
}

// Check if we have trustedKeys defined in the server. If so we require a user jwt.
if s.trustedKeys != nil {
Expand All @@ -424,21 +410,21 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) boo
}

// Check if we have nkeys or users for client.
hasNkeys := auth.nkeys != nil
hasUsers := auth.users != nil
hasNkeys := len(nkusers) > 0
hasUsers := len(users) > 0
if hasNkeys && c.opts.Nkey != "" {
nkey, ok = auth.nkeys[c.opts.Nkey]
nkey, ok = nkusers[c.opts.Nkey]
if !ok {
s.mu.Unlock()
return false
}
} else if hasUsers {
// Check if we are tls verify and are mapping users from the client_certificate
if auth.tlsMap {
if tlsMap {
var euser string
authorized := checkClientTLSCertSubject(c, func(u string) bool {
var ok bool
user, ok = auth.users[u]
user, ok = users[u]
if !ok {
c.Debugf("User in cert [%q], not found", u)
return false
Expand All @@ -451,20 +437,20 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) boo
return false
}
if c.opts.Username != "" {
s.Warnf("User found in connect proto, but user required from cert - %v", c)
s.Warnf("User %q found in connect proto, but user required from cert", c.opts.Username)
}
// Already checked that the client didn't send a user in connect
// but we set it here to be able to identify it in the logs.
c.opts.Username = euser
} else {
if c.kind == CLIENT && c.opts.Username == "" && auth.noAuthUser != "" {
if u, exists := auth.users[auth.noAuthUser]; exists {
if c.kind == CLIENT && c.opts.Username == "" && noAuthUser != "" {
if u, exists := users[noAuthUser]; exists {
c.opts.Username = u.Username
c.opts.Password = u.Password
}
}
if c.opts.Username != "" {
user, ok = auth.users[c.opts.Username]
user, ok = users[c.opts.Username]
if !ok {
s.mu.Unlock()
return false
Expand Down Expand Up @@ -586,13 +572,13 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) boo
}

if c.kind == CLIENT {
if auth.token != "" {
return comparePasswords(auth.token, c.opts.Token)
} else if auth.username != "" {
if auth.username != c.opts.Username {
if token != "" {
return comparePasswords(token, c.opts.Token)
} else if username != "" {
if username != c.opts.Username {
return false
}
return comparePasswords(auth.password, c.opts.Password)
return comparePasswords(password, c.opts.Password)
}
} else if c.kind == LEAF {
// There is no required username/password to connect and
Expand Down Expand Up @@ -784,6 +770,31 @@ func (s *Server) isLeafNodeAuthorized(c *client) bool {
if opts.LeafNode.Username != _EMPTY_ {
return isAuthorized(opts.LeafNode.Username, opts.LeafNode.Password, opts.LeafNode.Account)
} else if len(opts.LeafNode.Users) > 0 {
if opts.LeafNode.TLSMap {
var user *User
found := checkClientTLSCertSubject(c, func(u string) bool {
// This is expected to be a very small array.
for _, usr := range opts.LeafNode.Users {
if u == usr.Username {
user = usr
return true
}
}
c.Debugf("User in cert [%q], not found", u)
return false
})
if !found {
return false
}
if c.opts.Username != "" {
s.Warnf("User %q found in connect proto, but user required from cert", c.opts.Username)
}
c.opts.Username = user.Username
// This will authorize since are using an existing user,
// but it will also register with proper account.
return isAuthorized(user.Username, user.Password, user.Account.GetName())
}

// This is expected to be a very small array.
for _, u := range opts.LeafNode.Users {
if u.Username == c.opts.Username {
Expand Down
96 changes: 96 additions & 0 deletions server/leafnode_test.go
Expand Up @@ -1496,3 +1496,99 @@ func TestLeafNodeTmpClients(t *testing.T) {
checkLeafNodeConnected(t, b)
checkTmp(0)
}

func TestLeafNodeTLSVerifyAndMap(t *testing.T) {

accName := "MyAccount"
acc := NewAccount(accName)
certUserName := "CN=example.com,OU=NATS.io"
users := []*User{&User{Username: certUserName, Account: acc}}

for _, test := range []struct {
name string
leafUsers bool
provideCert bool
}{
{"no users override, provides cert", false, true},
{"no users override, does not provide cert", false, false},
{"users override, provides cert", true, true},
{"users override, does not provide cert", true, false},
} {
t.Run(test.name, func(t *testing.T) {
o := DefaultOptions()
o.Accounts = []*Account{acc}
o.LeafNode.Host = "127.0.0.1"
o.LeafNode.Port = -1
if test.leafUsers {
o.LeafNode.Users = users
} else {
o.Users = users
}
tc := &TLSConfigOpts{
CertFile: "../test/configs/certs/tlsauth/server.pem",
KeyFile: "../test/configs/certs/tlsauth/server-key.pem",
CaFile: "../test/configs/certs/tlsauth/ca.pem",
Verify: true,
}
tlsc, err := GenTLSConfig(tc)
if err != nil {
t.Fatalf("Error creating tls config: %v", err)
}
o.LeafNode.TLSConfig = tlsc
o.LeafNode.TLSMap = true
s := RunServer(o)
defer s.Shutdown()

slo := DefaultOptions()
sltlsc := &tls.Config{}
if test.provideCert {
tc := &TLSConfigOpts{
CertFile: "../test/configs/certs/tlsauth/client.pem",
KeyFile: "../test/configs/certs/tlsauth/client-key.pem",
}
var err error
sltlsc, err = GenTLSConfig(tc)
if err != nil {
t.Fatalf("Error generating tls config: %v", err)
}
}
sltlsc.InsecureSkipVerify = true
u, _ := url.Parse(fmt.Sprintf("nats://%s:%d", o.LeafNode.Host, o.LeafNode.Port))
slo.LeafNode.Remotes = []*RemoteLeafOpts{
{
TLSConfig: sltlsc,
URLs: []*url.URL{u},
},
}
sl := RunServer(slo)
defer sl.Shutdown()

if !test.provideCert {
// Wait a bit and make sure we are not connecting
time.Sleep(100 * time.Millisecond)
checkLeafNodeConnectedCount(t, sl, 0)
return
}
checkLeafNodeConnected(t, sl)

var uname string
var accname string
s.mu.Lock()
for _, c := range s.leafs {
c.mu.Lock()
uname = c.opts.Username
if c.acc != nil {
accname = c.acc.GetName()
}
c.mu.Unlock()
}
s.mu.Unlock()
if uname != certUserName {
t.Fatalf("Expected username %q, got %q", certUserName, uname)
}
if accname != accName {
t.Fatalf("Expected account %q, got %v", accName, accname)
}
})
}
}
2 changes: 1 addition & 1 deletion server/server.go
Expand Up @@ -1901,7 +1901,7 @@ func (s *Server) createClient(conn net.Conn, ws *websocket) *client {
// then we use the websocket's specific boolean that will be set to true
// if there is any auth{} configured in websocket{}.
if ws != nil && !info.AuthRequired {
info.AuthRequired = s.websocket.authRequired
info.AuthRequired = s.websocket.authOverride
}
if s.nonceRequired() {
// Nonce handling
Expand Down