mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-13 17:58:00 -07:00
We now share more information about the responder and the requestor. The requestor information by default is not shared, but can be when declaring the import. Also fixed bug for error handling on old request style requests that would always result on a 408 response. Signed-off-by: Derek Collison <derek@nats.io>
765 lines
20 KiB
Go
765 lines
20 KiB
Go
// Copyright 2012-2019 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
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package server
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/nats-io/jwt"
|
|
"github.com/nats-io/nkeys"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
// Authentication is an interface for implementing authentication
|
|
type Authentication interface {
|
|
// Check if a client is authorized to connect
|
|
Check(c ClientAuthentication) bool
|
|
}
|
|
|
|
// ClientAuthentication is an interface for client authentication
|
|
type ClientAuthentication interface {
|
|
// Get options associated with a client
|
|
GetOpts() *clientOpts
|
|
// If TLS is enabled, TLS ConnectionState, nil otherwise
|
|
GetTLSConnectionState() *tls.ConnectionState
|
|
// Optionally map a user after auth.
|
|
RegisterUser(*User)
|
|
// RemoteAddress expose the connection information of the client
|
|
RemoteAddress() net.Addr
|
|
}
|
|
|
|
// NkeyUser is for multiple nkey based users
|
|
type NkeyUser struct {
|
|
Nkey string `json:"user"`
|
|
Permissions *Permissions `json:"permissions,omitempty"`
|
|
Account *Account `json:"account,omitempty"`
|
|
SigningKey string `json:"signing_key,omitempty"`
|
|
}
|
|
|
|
// User is for multiple accounts/users.
|
|
type User struct {
|
|
Username string `json:"user"`
|
|
Password string `json:"password"`
|
|
Permissions *Permissions `json:"permissions,omitempty"`
|
|
Account *Account `json:"account,omitempty"`
|
|
}
|
|
|
|
// clone performs a deep copy of the User struct, returning a new clone with
|
|
// all values copied.
|
|
func (u *User) clone() *User {
|
|
if u == nil {
|
|
return nil
|
|
}
|
|
clone := &User{}
|
|
*clone = *u
|
|
clone.Permissions = u.Permissions.clone()
|
|
return clone
|
|
}
|
|
|
|
// clone performs a deep copy of the NkeyUser struct, returning a new clone with
|
|
// all values copied.
|
|
func (n *NkeyUser) clone() *NkeyUser {
|
|
if n == nil {
|
|
return nil
|
|
}
|
|
clone := &NkeyUser{}
|
|
*clone = *n
|
|
clone.Permissions = n.Permissions.clone()
|
|
return clone
|
|
}
|
|
|
|
// SubjectPermission is an individual allow and deny struct for publish
|
|
// and subscribe authorizations.
|
|
type SubjectPermission struct {
|
|
Allow []string `json:"allow,omitempty"`
|
|
Deny []string `json:"deny,omitempty"`
|
|
}
|
|
|
|
// ResponsePermission can be used to allow responses to any reply subject
|
|
// that is received on a valid subscription.
|
|
type ResponsePermission struct {
|
|
MaxMsgs int `json:"max"`
|
|
Expires time.Duration `json:"ttl"`
|
|
}
|
|
|
|
// Permissions are the allowed subjects on a per
|
|
// publish or subscribe basis.
|
|
type Permissions struct {
|
|
Publish *SubjectPermission `json:"publish"`
|
|
Subscribe *SubjectPermission `json:"subscribe"`
|
|
Response *ResponsePermission `json:"responses,omitempty"`
|
|
}
|
|
|
|
// RoutePermissions are similar to user permissions
|
|
// but describe what a server can import/export from and to
|
|
// another server.
|
|
type RoutePermissions struct {
|
|
Import *SubjectPermission `json:"import"`
|
|
Export *SubjectPermission `json:"export"`
|
|
}
|
|
|
|
// clone will clone an individual subject permission.
|
|
func (p *SubjectPermission) clone() *SubjectPermission {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
clone := &SubjectPermission{}
|
|
if p.Allow != nil {
|
|
clone.Allow = make([]string, len(p.Allow))
|
|
copy(clone.Allow, p.Allow)
|
|
}
|
|
if p.Deny != nil {
|
|
clone.Deny = make([]string, len(p.Deny))
|
|
copy(clone.Deny, p.Deny)
|
|
}
|
|
return clone
|
|
}
|
|
|
|
// clone performs a deep copy of the Permissions struct, returning a new clone
|
|
// with all values copied.
|
|
func (p *Permissions) clone() *Permissions {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
clone := &Permissions{}
|
|
if p.Publish != nil {
|
|
clone.Publish = p.Publish.clone()
|
|
}
|
|
if p.Subscribe != nil {
|
|
clone.Subscribe = p.Subscribe.clone()
|
|
}
|
|
if p.Response != nil {
|
|
clone.Response = &ResponsePermission{
|
|
MaxMsgs: p.Response.MaxMsgs,
|
|
Expires: p.Response.Expires,
|
|
}
|
|
}
|
|
return clone
|
|
}
|
|
|
|
// checkAuthforWarnings will look for insecure settings and log concerns.
|
|
// Lock is assumed held.
|
|
func (s *Server) checkAuthforWarnings() {
|
|
warn := false
|
|
if s.opts.Password != "" && !isBcrypt(s.opts.Password) {
|
|
warn = true
|
|
}
|
|
for _, u := range s.users {
|
|
// Skip warn if using TLS certs based auth
|
|
// unless a password has been left in the config.
|
|
if u.Password == "" && s.opts.TLSMap {
|
|
continue
|
|
}
|
|
|
|
if !isBcrypt(u.Password) {
|
|
warn = true
|
|
break
|
|
}
|
|
}
|
|
if warn {
|
|
// Warning about using plaintext passwords.
|
|
s.Warnf("Plaintext passwords detected, use nkeys or bcrypt.")
|
|
}
|
|
}
|
|
|
|
// If opts.Users or opts.Nkeys have definitions without an account
|
|
// defined assign them to the default global account.
|
|
// Lock should be held.
|
|
func (s *Server) assignGlobalAccountToOrphanUsers() {
|
|
for _, u := range s.users {
|
|
if u.Account == nil {
|
|
u.Account = s.gacc
|
|
}
|
|
}
|
|
for _, u := range s.nkeys {
|
|
if u.Account == nil {
|
|
u.Account = s.gacc
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the given permissions has a ResponsePermission
|
|
// set, ensure that defaults are set (if values are 0)
|
|
// and that a Publish permission is set, and Allow
|
|
// is disabled if not explicitly set.
|
|
func validateResponsePermissions(p *Permissions) {
|
|
if p == nil || p.Response == nil {
|
|
return
|
|
}
|
|
if p.Publish == nil {
|
|
p.Publish = &SubjectPermission{}
|
|
}
|
|
if p.Publish.Allow == nil {
|
|
// We turn off the blanket allow statement.
|
|
p.Publish.Allow = []string{}
|
|
}
|
|
// If there is a response permission, ensure
|
|
// that if value is 0, we set the default value.
|
|
if p.Response.MaxMsgs == 0 {
|
|
p.Response.MaxMsgs = DEFAULT_ALLOW_RESPONSE_MAX_MSGS
|
|
}
|
|
if p.Response.Expires == 0 {
|
|
p.Response.Expires = DEFAULT_ALLOW_RESPONSE_EXPIRATION
|
|
}
|
|
}
|
|
|
|
// configureAuthorization will do any setup needed for authorization.
|
|
// Lock is assumed held.
|
|
func (s *Server) configureAuthorization() {
|
|
if s.opts == nil {
|
|
return
|
|
}
|
|
|
|
// Snapshot server options.
|
|
opts := s.getOpts()
|
|
|
|
// Check for multiple users first
|
|
// This just checks and sets up the user map if we have multiple users.
|
|
if opts.CustomClientAuthentication != nil {
|
|
s.info.AuthRequired = true
|
|
} else if len(s.trustedKeys) > 0 {
|
|
s.info.AuthRequired = true
|
|
} else if opts.Nkeys != nil || opts.Users != nil {
|
|
// Support both at the same time.
|
|
if opts.Nkeys != nil {
|
|
s.nkeys = make(map[string]*NkeyUser)
|
|
for _, u := range opts.Nkeys {
|
|
copy := u.clone()
|
|
if u.Account != nil {
|
|
if v, ok := s.accounts.Load(u.Account.Name); ok {
|
|
copy.Account = v.(*Account)
|
|
}
|
|
}
|
|
if copy.Permissions != nil {
|
|
validateResponsePermissions(copy.Permissions)
|
|
}
|
|
s.nkeys[u.Nkey] = copy
|
|
}
|
|
}
|
|
if opts.Users != nil {
|
|
s.users = make(map[string]*User)
|
|
for _, u := range opts.Users {
|
|
copy := u.clone()
|
|
if u.Account != nil {
|
|
if v, ok := s.accounts.Load(u.Account.Name); ok {
|
|
copy.Account = v.(*Account)
|
|
}
|
|
}
|
|
if copy.Permissions != nil {
|
|
validateResponsePermissions(copy.Permissions)
|
|
}
|
|
s.users[u.Username] = copy
|
|
}
|
|
}
|
|
s.assignGlobalAccountToOrphanUsers()
|
|
s.info.AuthRequired = true
|
|
} else if opts.Username != "" || opts.Authorization != "" {
|
|
s.info.AuthRequired = true
|
|
} else {
|
|
s.users = nil
|
|
s.info.AuthRequired = false
|
|
}
|
|
}
|
|
|
|
// checkAuthentication will check based on client type and
|
|
// return boolean indicating if client is authorized.
|
|
func (s *Server) checkAuthentication(c *client) bool {
|
|
switch c.kind {
|
|
case CLIENT:
|
|
return s.isClientAuthorized(c)
|
|
case ROUTER:
|
|
return s.isRouterAuthorized(c)
|
|
case GATEWAY:
|
|
return s.isGatewayAuthorized(c)
|
|
case LEAF:
|
|
return s.isLeafNodeAuthorized(c)
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// isClientAuthorized will check the client against the proper authorization method and data.
|
|
// This could be nkey, token, or username/password based.
|
|
func (s *Server) isClientAuthorized(c *client) bool {
|
|
opts := s.getOpts()
|
|
|
|
// Check custom auth first, then jwts, then nkeys, then
|
|
// multiple users with TLS map if enabled, then token,
|
|
// then single user/pass.
|
|
if opts.CustomClientAuthentication != nil {
|
|
return opts.CustomClientAuthentication.Check(c)
|
|
}
|
|
|
|
return s.processClientOrLeafAuthentication(c)
|
|
}
|
|
|
|
func (s *Server) processClientOrLeafAuthentication(c *client) bool {
|
|
var (
|
|
nkey *NkeyUser
|
|
juc *jwt.UserClaims
|
|
acc *Account
|
|
user *User
|
|
ok bool
|
|
err error
|
|
opts = s.getOpts()
|
|
)
|
|
|
|
s.mu.Lock()
|
|
authRequired := s.info.AuthRequired
|
|
if !authRequired {
|
|
// TODO(dlc) - If they send us credentials should we fail?
|
|
s.mu.Unlock()
|
|
return true
|
|
}
|
|
|
|
// Check if we have trustedKeys defined in the server. If so we require a user jwt.
|
|
if s.trustedKeys != nil {
|
|
if c.opts.JWT == "" {
|
|
s.mu.Unlock()
|
|
c.Debugf("Authentication requires a user JWT")
|
|
return false
|
|
}
|
|
// So we have a valid user jwt here.
|
|
juc, err = jwt.DecodeUserClaims(c.opts.JWT)
|
|
if err != nil {
|
|
s.mu.Unlock()
|
|
c.Debugf("User JWT not valid: %v", err)
|
|
return false
|
|
}
|
|
vr := jwt.CreateValidationResults()
|
|
juc.Validate(vr)
|
|
if vr.IsBlocking(true) {
|
|
s.mu.Unlock()
|
|
c.Debugf("User JWT no longer valid: %+v", vr)
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Check if we have nkeys or users for client.
|
|
hasNkeys := s.nkeys != nil
|
|
hasUsers := s.users != nil
|
|
if hasNkeys && c.opts.Nkey != "" {
|
|
nkey, ok = s.nkeys[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 opts.TLSMap {
|
|
var euser string
|
|
authorized := checkClientTLSCertSubject(c, func(u string) bool {
|
|
var ok bool
|
|
user, ok = s.users[u]
|
|
if !ok {
|
|
c.Debugf("User in cert [%q], not found", u)
|
|
return false
|
|
}
|
|
euser = u
|
|
return true
|
|
})
|
|
if !authorized {
|
|
s.mu.Unlock()
|
|
return false
|
|
}
|
|
if c.opts.Username != "" {
|
|
s.Warnf("User found in connect proto, but user required from cert - %v", c)
|
|
}
|
|
// 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 == "" && s.opts.NoAuthUser != "" {
|
|
if u, exists := s.users[s.opts.NoAuthUser]; exists {
|
|
c.opts.Username = u.Username
|
|
c.opts.Password = u.Password
|
|
}
|
|
}
|
|
if c.opts.Username != "" {
|
|
user, ok = s.users[c.opts.Username]
|
|
if !ok {
|
|
s.mu.Unlock()
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
s.mu.Unlock()
|
|
|
|
// If we have a jwt and a userClaim, make sure we have the Account, etc associated.
|
|
// We need to look up the account. This will use an account resolver if one is present.
|
|
if juc != nil {
|
|
issuer := juc.Issuer
|
|
if juc.IssuerAccount != "" {
|
|
issuer = juc.IssuerAccount
|
|
}
|
|
if acc, err = s.LookupAccount(issuer); acc == nil {
|
|
c.Debugf("Account JWT lookup error: %v", err)
|
|
return false
|
|
}
|
|
if !s.isTrustedIssuer(acc.Issuer) {
|
|
c.Debugf("Account JWT not signed by trusted operator")
|
|
return false
|
|
}
|
|
if juc.IssuerAccount != "" && !acc.hasIssuer(juc.Issuer) {
|
|
c.Debugf("User JWT issuer is not known")
|
|
return false
|
|
}
|
|
if acc.IsExpired() {
|
|
c.Debugf("Account JWT has expired")
|
|
return false
|
|
}
|
|
// Verify the signature against the nonce.
|
|
if c.opts.Sig == "" {
|
|
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(juc.Subject)
|
|
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 acc.checkUserRevoked(juc.Subject) {
|
|
c.Debugf("User authentication revoked")
|
|
return false
|
|
}
|
|
|
|
nkey = buildInternalNkeyUser(juc, acc)
|
|
if err := c.RegisterNkeyUser(nkey); err != nil {
|
|
return false
|
|
}
|
|
// Hold onto the user's public key.
|
|
c.pubKey = juc.Subject
|
|
|
|
// Generate an event if we have a system account.
|
|
s.accountConnectEvent(c)
|
|
|
|
// Check if we need to set an auth timer if the user jwt expires.
|
|
c.checkExpiration(juc.Claims())
|
|
return true
|
|
}
|
|
|
|
if nkey != nil {
|
|
if c.opts.Sig == "" {
|
|
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
|
|
}
|
|
if err := c.RegisterNkeyUser(nkey); err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
if user != nil {
|
|
ok = comparePasswords(user.Password, c.opts.Password)
|
|
// If we are authorized, register the user which will properly setup any permissions
|
|
// for pub/sub authorizations.
|
|
if ok {
|
|
c.RegisterUser(user)
|
|
// Generate an event if we have a system account and this is not the $G account.
|
|
s.accountConnectEvent(c)
|
|
}
|
|
return ok
|
|
}
|
|
|
|
if c.kind == CLIENT {
|
|
if opts.Authorization != "" {
|
|
return comparePasswords(opts.Authorization, c.opts.Token)
|
|
} else if opts.Username != "" {
|
|
if opts.Username != c.opts.Username {
|
|
return false
|
|
}
|
|
return comparePasswords(opts.Password, c.opts.Password)
|
|
}
|
|
} else if c.kind == LEAF {
|
|
// There is no required username/password to connect and
|
|
// there was no u/p in the CONNECT or none that matches the
|
|
// know users. Register the leaf connection with global account
|
|
// or the one specified in config (if provided).
|
|
return s.registerLeafWithAccount(c, opts.LeafNode.Account)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func getTLSAuthDCs(rdns *pkix.RDNSequence) string {
|
|
dcOID := asn1.ObjectIdentifier{0, 9, 2342, 19200300, 100, 1, 25}
|
|
dcs := []string{}
|
|
for _, rdn := range *rdns {
|
|
if len(rdn) == 0 {
|
|
continue
|
|
}
|
|
for _, atv := range rdn {
|
|
value, ok := atv.Value.(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if atv.Type.Equal(dcOID) {
|
|
dcs = append(dcs, "DC="+value)
|
|
}
|
|
}
|
|
}
|
|
return strings.Join(dcs, ",")
|
|
}
|
|
|
|
func checkClientTLSCertSubject(c *client, fn func(string) bool) bool {
|
|
tlsState := c.GetTLSConnectionState()
|
|
if tlsState == nil {
|
|
c.Debugf("User required in cert, no TLS connection state")
|
|
return false
|
|
}
|
|
if len(tlsState.PeerCertificates) == 0 {
|
|
c.Debugf("User required in cert, no peer certificates found")
|
|
return false
|
|
}
|
|
cert := tlsState.PeerCertificates[0]
|
|
if len(tlsState.PeerCertificates) > 1 {
|
|
c.Debugf("Multiple peer certificates found, selecting first")
|
|
}
|
|
|
|
hasSANs := len(cert.DNSNames) > 0
|
|
hasEmailAddresses := len(cert.EmailAddresses) > 0
|
|
hasSubject := len(cert.Subject.String()) > 0
|
|
if !hasEmailAddresses && !hasSubject {
|
|
c.Debugf("User required in cert, none found")
|
|
return false
|
|
}
|
|
|
|
switch {
|
|
case hasEmailAddresses:
|
|
for _, u := range cert.EmailAddresses {
|
|
if fn(u) {
|
|
c.Debugf("Using email found in cert for auth [%q]", u)
|
|
return true
|
|
}
|
|
}
|
|
fallthrough
|
|
case hasSANs:
|
|
for _, u := range cert.DNSNames {
|
|
if fn(u) {
|
|
c.Debugf("Using SAN found in cert for auth [%q]", u)
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try to get the full RDN Sequence that includes the domain components.
|
|
var rdns pkix.RDNSequence
|
|
if _, err := asn1.Unmarshal(cert.RawSubject, &rdns); err == nil {
|
|
// If found domain components then include roughly following
|
|
// the order from https://tools.ietf.org/html/rfc2253
|
|
rdn := cert.Subject.ToRDNSequence().String()
|
|
dcs := getTLSAuthDCs(&rdns)
|
|
if len(dcs) > 0 {
|
|
u := strings.Join([]string{rdn, dcs}, ",")
|
|
if fn(u) {
|
|
c.Debugf("Using RDNSequence for auth [%q]", u)
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Use the subject of the certificate.
|
|
u := cert.Subject.String()
|
|
c.Debugf("Using certificate subject for auth [%q]", u)
|
|
return fn(u)
|
|
}
|
|
|
|
// checkRouterAuth checks optional router authorization which can be nil or username/password.
|
|
func (s *Server) isRouterAuthorized(c *client) bool {
|
|
// Snapshot server options.
|
|
opts := s.getOpts()
|
|
|
|
// Check custom auth first, then TLS map if enabled
|
|
// then single user/pass.
|
|
if s.opts.CustomRouterAuthentication != nil {
|
|
return s.opts.CustomRouterAuthentication.Check(c)
|
|
}
|
|
|
|
if opts.Cluster.Username == "" {
|
|
return true
|
|
}
|
|
|
|
if opts.Cluster.TLSMap {
|
|
return checkClientTLSCertSubject(c, func(user string) bool {
|
|
return opts.Cluster.Username == user
|
|
})
|
|
}
|
|
|
|
if opts.Cluster.Username != c.opts.Username {
|
|
return false
|
|
}
|
|
if !comparePasswords(opts.Cluster.Password, c.opts.Password) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// isGatewayAuthorized checks optional gateway authorization which can be nil or username/password.
|
|
func (s *Server) isGatewayAuthorized(c *client) bool {
|
|
// Snapshot server options.
|
|
opts := s.getOpts()
|
|
if opts.Gateway.Username == "" {
|
|
return true
|
|
}
|
|
|
|
// Check whether TLS map is enabled, otherwise use single user/pass.
|
|
if opts.Gateway.TLSMap {
|
|
return checkClientTLSCertSubject(c, func(user string) bool {
|
|
return opts.Gateway.Username == user
|
|
})
|
|
}
|
|
|
|
if opts.Gateway.Username != c.opts.Username {
|
|
return false
|
|
}
|
|
return comparePasswords(opts.Gateway.Password, c.opts.Password)
|
|
}
|
|
|
|
func (s *Server) registerLeafWithAccount(c *client, account string) bool {
|
|
var err error
|
|
acc := s.globalAccount()
|
|
if account != _EMPTY_ {
|
|
acc, err = s.lookupAccount(account)
|
|
if err != nil {
|
|
s.Errorf("authentication of user %q failed, unable to lookup account %q: %v",
|
|
c.opts.Username, account, err)
|
|
return false
|
|
}
|
|
}
|
|
if err = c.registerWithAccount(acc); err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// isLeafNodeAuthorized will check for auth for an inbound leaf node connection.
|
|
func (s *Server) isLeafNodeAuthorized(c *client) bool {
|
|
opts := s.getOpts()
|
|
|
|
isAuthorized := func(username, password, account string) bool {
|
|
if username != c.opts.Username {
|
|
return false
|
|
}
|
|
if !comparePasswords(password, c.opts.Password) {
|
|
return false
|
|
}
|
|
return s.registerLeafWithAccount(c, account)
|
|
}
|
|
|
|
// If leafnodes config has an authorization{} stanza, this takes precedence.
|
|
// The user in CONNECT mutch match. We will bind to the account associated
|
|
// 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 len(opts.LeafNode.Users) > 0 {
|
|
// This is expected to be a very small array.
|
|
for _, u := range opts.LeafNode.Users {
|
|
if u.Username == c.opts.Username {
|
|
var accName string
|
|
if u.Account != nil {
|
|
accName = u.Account.Name
|
|
}
|
|
return isAuthorized(u.Username, u.Password, accName)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// We are here if we accept leafnode connections without any credential.
|
|
|
|
// Still, if the CONNECT has some user info, we will bind to the
|
|
// user's account or to the specified default account (if provided)
|
|
// or to the global account.
|
|
return s.processClientOrLeafAuthentication(c)
|
|
}
|
|
|
|
// Support for bcrypt stored passwords and tokens.
|
|
const bcryptPrefix = "$2a$"
|
|
|
|
// isBcrypt checks whether the given password or token is bcrypted.
|
|
func isBcrypt(password string) bool {
|
|
return strings.HasPrefix(password, bcryptPrefix)
|
|
}
|
|
|
|
func comparePasswords(serverPassword, clientPassword string) bool {
|
|
// Check to see if the server password is a bcrypt hash
|
|
if isBcrypt(serverPassword) {
|
|
if err := bcrypt.CompareHashAndPassword([]byte(serverPassword), []byte(clientPassword)); err != nil {
|
|
return false
|
|
}
|
|
} else if serverPassword != clientPassword {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func validateAuth(o *Options) error {
|
|
if o.NoAuthUser == "" {
|
|
return nil
|
|
}
|
|
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`, o.NoAuthUser)
|
|
}
|
|
for _, u := range o.Users {
|
|
if u.Username == o.NoAuthUser {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf(
|
|
`no_auth_user: "%s" not present as user in authorization block or account configuration`,
|
|
o.NoAuthUser)
|
|
}
|