Repository: nats-io/nats-server
Pull request #1727 (2020-11-19 15:03:17)

[ADDED] verify_cert_and_check_known_urls to tie subject alt name to url in cfg
Only works for gateways and routes. When true the subject alt DNS name
must match one url in the corresponding configuration

As discussed. At the very least have a look at the unit test cases in TestDNSAltNameMatching. Looking at certs to write my other tests I started to wonder if we should extend this to IPs too?

```
        X509v3 extensions:
            X509v3 Subject Alternative Name:
                DNS:localhost, IP Address:127.0.0.1
```

Also look for 127.0.0.1 and it's IPv6 variant. Opinions?

If yes, I'd be open to suggestions on which urls to provide in test/tls_test.go line 823, 891, 892

What I intend to include in the doc
verify_and_implicit_allow| Only settable in a non client context where verify=true is the default (gateways/cluster). The incoming connections certificate's `X509v3 Subject Alternative Name` DNS entries will be matched against all urls in the configuration context that contains this tls map. If a match is found the connection is accepted, otherwise rejected. Meaning for gateways we will match all DNS entries in the certificate against all gateway urls. For cluster we will match against all route urls. As a consequence of this, dynamic cluster growth may require config changes in other cluster where this flag is true. DNS name checking is performed according to https://tools.ietf.org/html/rfc6125#section-6.4.1 Only the full wildcard `*` is supported for the left most label.  This would be a way to keep cluster growth flexible.


Signed-off-by: Matthias Hanel <mh@synadia.com>

matthiashanel: @kozlovic @philpennock @davidkemper 

Talked to @philpennock , let's hold off on IPs for now.
As for the name, here are more suggestions, any (partly/fully) sound particular appealing or descriptive?:
verify_and_inherit_allowed_names
verify_and_map_listed_names
verify_and_map_existing_hostnames
verify_and_map_names
kozlovic: Maybe `verify_and_map_listed_names` or `verify_and_map_existing_hostnames`? But I am very bad with names, so I would let maybe @davidkemper weigh in.

Patch:
diff --git a/server/accounts.go b/server/accounts.go
index 82c35ed0..669067bd 100644
--- a/server/accounts.go
+++ b/server/accounts.go
@@ -1631,7 +1631,6 @@ func (a *Account) addServiceImport(dest *Account, from, to string, claim *jwt.Im
 		if to == from {
 			usePub = true
 		} else {
-			from, _ = transformUntokenize(from)
 			// Create a transform
 			if tr, err = newTransform(from, transformTokenize(to)); err != nil {
 				a.mu.Unlock()
diff --git a/server/auth.go b/server/auth.go
index 601e28a4..7043b2cc 100644
--- a/server/auth.go
+++ b/server/auth.go
@@ -20,6 +20,7 @@ import (
 	"encoding/base64"
 	"fmt"
 	"net"
+	"net/url"
 	"regexp"
 	"strings"
 	"time"
@@ -418,7 +419,7 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) boo
 	} else if hasUsers {
 		// Check if we are tls verify and are mapping users from the client_certificate.
 		if tlsMap {
-			authorized := checkClientTLSCertSubject(c, func(u string, certRDN *ldap.DN) (string, bool) {
+			authorized := checkClientTLSCertSubject(c, func(u string, certRDN *ldap.DN, _ bool) (string, bool) {
 				// First do literal lookup using the resulting string representation
 				// of RDNSequence as implemented by the pkix package from Go.
 				if u != "" {
@@ -667,7 +668,7 @@ func getTLSAuthDCs(rdns *pkix.RDNSequence) string {
 	return strings.Join(dcs, ",")
 }
 
-type tlsMapAuthFn func(string, *ldap.DN) (string, bool)
+type tlsMapAuthFn func(string, *ldap.DN, bool) (string, bool)
 
 func checkClientTLSCertSubject(c *client, fn tlsMapAuthFn) bool {
 	tlsState := c.GetTLSConnectionState()
@@ -696,7 +697,7 @@ func checkClientTLSCertSubject(c *client, fn tlsMapAuthFn) bool {
 	switch {
 	case hasEmailAddresses:
 		for _, u := range cert.EmailAddresses {
-			if match, ok := fn(u, nil); ok {
+			if match, ok := fn(u, nil, false); ok {
 				c.Debugf("Using email found in cert for auth [%q]", match)
 				return true
 			}
@@ -704,7 +705,7 @@ func checkClientTLSCertSubject(c *client, fn tlsMapAuthFn) bool {
 		fallthrough
 	case hasSANs:
 		for _, u := range cert.DNSNames {
-			if match, ok := fn(u, nil); ok {
+			if match, ok := fn(u, nil, true); ok {
 				c.Debugf("Using SAN found in cert for auth [%q]", match)
 				return true
 			}
@@ -712,7 +713,7 @@ func checkClientTLSCertSubject(c *client, fn tlsMapAuthFn) bool {
 		fallthrough
 	case hasURIs:
 		for _, u := range cert.URIs {
-			if match, ok := fn(u.String(), nil); ok {
+			if match, ok := fn(u.String(), nil, false); ok {
 				c.Debugf("Using URI found in cert for auth [%q]", match)
 				return true
 			}
@@ -726,7 +727,7 @@ func checkClientTLSCertSubject(c *client, fn tlsMapAuthFn) bool {
 	// Match that follows original order from the subject takes precedence.
 	dn, err := ldap.FromCertSubject(cert.Subject)
 	if err == nil {
-		if match, ok := fn("", dn); ok {
+		if match, ok := fn("", dn, false); ok {
 			c.Debugf("Using DistinguishedNameMatch for auth [%q]", match)
 			return true
 		}
@@ -745,7 +746,7 @@ func checkClientTLSCertSubject(c *client, fn tlsMapAuthFn) bool {
 		dcs := getTLSAuthDCs(&rdns)
 		if len(dcs) > 0 {
 			u := strings.Join([]string{rdn, dcs}, ",")
-			if match, ok := fn(u, nil); ok {
+			if match, ok := fn(u, nil, false); ok {
 				c.Debugf("Using RDNSequence for auth [%q]", match)
 				return true
 			}
@@ -755,7 +756,7 @@ func checkClientTLSCertSubject(c *client, fn tlsMapAuthFn) bool {
 
 	// If no match, then use the string representation of the RDNSequence
 	// from the subject without the domainComponents.
-	if match, ok := fn(rdn, nil); ok {
+	if match, ok := fn(rdn, nil, false); ok {
 		c.Debugf("Using certificate subject for auth [%q]", match)
 		return true
 	}
@@ -764,6 +765,38 @@ func checkClientTLSCertSubject(c *client, fn tlsMapAuthFn) bool {
 	return false
 }
 
+func dnsAltNameLabels(dnsAltName string) []string {
+	return strings.Split(strings.ToLower(dnsAltName), ".")
+}
+
+// Check DNS name according to https://tools.ietf.org/html/rfc6125#section-6.4.1
+func dnsAltNameMatches(dnsAltNameLabels []string, urls []*url.URL) bool {
+URLS:
+	for _, url := range urls {
+		if url == nil {
+			continue URLS
+		}
+		hostLabels := strings.Split(strings.ToLower(url.Hostname()), ".")
+		// Following https://tools.ietf.org/html/rfc6125#section-6.4.3, should not => will not, may => will not
+		// The wilcard * never matches multiple label and only matches the left most label.
+		if len(hostLabels) != len(dnsAltNameLabels) {
+			continue URLS
+		}
+		i := 0
+		// only match wildcard on left most label
+		if dnsAltNameLabels[0] == "*" {
+			i++
+		}
+		for ; i < len(dnsAltNameLabels); i++ {
+			if dnsAltNameLabels[i] != hostLabels[i] {
+				continue URLS
+			}
+		}
+		return true
+	}
+	return false
+}
+
 // checkRouterAuth checks optional router authorization which can be nil or username/password.
 func (s *Server) isRouterAuthorized(c *client) bool {
 	// Snapshot server options.
@@ -775,14 +808,25 @@ func (s *Server) isRouterAuthorized(c *client) bool {
 		return s.opts.CustomRouterAuthentication.Check(c)
 	}
 
-	if opts.Cluster.Username == "" {
-		return true
+	if opts.Cluster.TLSMap || opts.Cluster.TLSCheckKnwonURLs {
+		return checkClientTLSCertSubject(c, func(user string, _ *ldap.DN, isDNSAltName bool) (string, bool) {
+			if user == "" {
+				return "", false
+			}
+			if opts.Cluster.TLSCheckKnwonURLs && isDNSAltName {
+				if dnsAltNameMatches(dnsAltNameLabels(user), opts.Routes) {
+					return "", true
+				}
+			}
+			if opts.Cluster.TLSMap && opts.Cluster.Username == user {
+				return "", true
+			}
+			return "", false
+		})
 	}
 
-	if opts.Cluster.TLSMap {
-		return checkClientTLSCertSubject(c, func(user string, _ *ldap.DN) (string, bool) {
-			return "", opts.Cluster.Username == user
-		})
+	if opts.Cluster.Username == "" {
+		return true
 	}
 
 	if opts.Cluster.Username != c.opts.Username {
@@ -798,17 +842,32 @@ func (s *Server) isRouterAuthorized(c *client) bool {
 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, _ *ldap.DN) (string, bool) {
-			return "", opts.Gateway.Username == user
+	if opts.Gateway.TLSMap || opts.Gateway.TLSCheckKnownURLs {
+		return checkClientTLSCertSubject(c, func(user string, _ *ldap.DN, isDNSAltName bool) (string, bool) {
+			if user == "" {
+				return "", false
+			}
+			if opts.Gateway.TLSCheckKnownURLs && isDNSAltName {
+				labels := dnsAltNameLabels(user)
+				for _, gw := range opts.Gateway.Gateways {
+					if gw != nil && dnsAltNameMatches(labels, gw.URLs) {
+						return "", true
+					}
+				}
+			}
+			if opts.Gateway.TLSMap && opts.Gateway.Username == user {
+				return "", true
+			}
+			return "", false
 		})
 	}
 
+	if opts.Gateway.Username == "" {
+		return true
+	}
+
 	if opts.Gateway.Username != c.opts.Username {
 		return false
 	}
@@ -854,7 +913,7 @@ func (s *Server) isLeafNodeAuthorized(c *client) bool {
 	} else if len(opts.LeafNode.Users) > 0 {
 		if opts.LeafNode.TLSMap {
 			var user *User
-			found := checkClientTLSCertSubject(c, func(u string, _ *ldap.DN) (string, bool) {
+			found := checkClientTLSCertSubject(c, func(u string, _ *ldap.DN, _ bool) (string, bool) {
 				// This is expected to be a very small array.
 				for _, usr := range opts.LeafNode.Users {
 					if u == usr.Username {
diff --git a/server/client.go b/server/client.go
index 048e4f1e..71b57841 100644
--- a/server/client.go
+++ b/server/client.go
@@ -2949,13 +2949,14 @@ func (c *client) deliverMsg(sub *subscription, subject, reply, mh, msg []byte, g
 	client.outBytes += msgSize
 
 	// Check for internal subscriptions.
-	if sub.icb != nil {
+	if sub.icb != nil || client.kind == SYSTEM || client.kind == JETSTREAM || client.kind == ACCOUNT {
 		client.mu.Unlock()
-		// Internal account clients are for service imports and need the '\r\n'.
+		// Internal account clients are for service imports and need the
+		// complete raw msg with '\r\n'.
 		if client.kind == ACCOUNT {
-			sub.icb(sub, c, string(subject), string(reply), msg)
+			sub.icb(sub, c, string(subject), string(c.pa.reply), msg)
 		} else {
-			sub.icb(sub, c, string(subject), string(reply), msg[:msgSize])
+			sub.icb(sub, c, string(subject), string(c.pa.reply), msg[:msgSize])
 		}
 		return true
 	}
@@ -3377,7 +3378,6 @@ func (c *client) processInboundClientMsg(msg []byte) bool {
 			atomic.LoadInt64(&c.srv.gateway.totalQSubs) > 0 {
 			flag |= pmrCollectQueueNames
 		}
-
 		didDeliver, qnames = c.processMsgResults(c.acc, r, msg, c.pa.deliver, c.pa.subject, c.pa.reply, flag)
 	}
 
@@ -3501,7 +3501,8 @@ func (c *client) processServiceImport(si *serviceImport, acc *Account, msg []byt
 	// TODO(dlc) - restrict to configured service imports and not responses?
 	tracking, headers := shouldSample(si.latency, c)
 	if len(c.pa.reply) > 0 {
-		if rsi = c.setupResponseServiceImport(acc, si, tracking, headers); rsi != nil {
+		rsi = c.setupResponseServiceImport(acc, si, tracking, headers)
+		if rsi != nil {
 			nrr = []byte(rsi.from)
 		}
 	} else {
@@ -3530,7 +3531,7 @@ func (c *client) processServiceImport(si *serviceImport, acc *Account, msg []byt
 	}
 
 	// Now check to see if this account has mappings that could affect the service import.
-	// Can't use non-locked trick like in processInboundClientMsg, so just call into selectMappedSubject
+	// Can't use non locked trick like in processInboundClientMsg, so just call into selectMappedSubject
 	// so we only lock once.
 	to, _ = si.acc.selectMappedSubject(to)
 
@@ -3705,9 +3706,8 @@ func (c *client) processMsgResults(acc *Account, r *SublistResult, msg, deliver,
 			}
 			continue
 		}
-		// Assume delivery subject is the normal subject to this point.
+		// Assume delivery subject is normal subject to this point.
 		dsubj = subj
-
 		// Check for stream import mapped subs (shadow subs). These apply to local subs only.
 		if sub.im != nil {
 			// If this message was a service import do not re-export to an exported stream.
@@ -3715,26 +3715,14 @@ func (c *client) processMsgResults(acc *Account, r *SublistResult, msg, deliver,
 				continue
 			}
 			if sub.im.tr != nil {
-				to, _ := sub.im.tr.transformSubject(string(dsubj))
+				to, _ := sub.im.tr.transformSubject(string(subj))
 				dsubj = append(_dsubj[:0], to...)
 			} else if sub.im.usePub {
 				dsubj = append(_dsubj[:0], subj...)
 			} else {
 				dsubj = append(_dsubj[:0], sub.im.to...)
 			}
-			// If we are mapping for a deliver subject we will reverse roles.
-			// The original subj we set from above is correct for the msg header,
-			// but we need to transform the deliver subject to properly route.
-			if len(deliver) > 0 {
-				dsubj, subj = subj, dsubj
-			}
 		}
-
-		// Remap to original if internal.
-		if sub.icb != nil {
-			subj = subject
-		}
-
 		// Normal delivery
 		mh := c.msgHeader(dsubj, creply, sub)
 		didDeliver = c.deliverMsg(sub, subj, creply, mh, msg, rplyHasGWPrefix) || didDeliver
diff --git a/server/events.go b/server/events.go
index 449198cd..4d5b7330 100644
--- a/server/events.go
+++ b/server/events.go
@@ -305,14 +305,12 @@ RESET:
 				}
 			}
 			c.mu.Lock()
-
 			// We can have an override for account here.
 			if pm.acc != nil {
 				c.acc = pm.acc
 			} else {
 				c.acc = sysacc
 			}
-
 			// Prep internal structures needed to send message.
 			c.pa.subject = []byte(pm.sub)
 			c.pa.size = len(b)
@@ -1620,7 +1618,7 @@ func (s *Server) debugSubscribers(sub *subscription, c *client, subject, reply s
 
 	// If we are only local, go ahead and return.
 	if expected == 0 {
-		s.sendInternalAccountMsg(nil, reply, nsubs)
+		s.sendInternalAccountMsg(c.acc, reply, nsubs)
 		return
 	}
 
@@ -1664,7 +1662,7 @@ func (s *Server) debugSubscribers(sub *subscription, c *client, subject, reply s
 		delete(s.sys.replies, replySubj)
 		s.mu.Unlock()
 		// Send the response.
-		s.sendInternalAccountMsg(nil, reply, atomic.LoadInt32(&nsubs))
+		s.sendInternalAccountMsg(c.acc, reply, atomic.LoadInt32(&nsubs))
 	}()
 }
 
diff --git a/server/jetstream_api.go b/server/jetstream_api.go
index 8b34cb30..ba12c443 100644
--- a/server/jetstream_api.go
+++ b/server/jetstream_api.go
@@ -552,7 +552,7 @@ func (s *Server) setJetStreamExportSubs() error {
 }
 
 func (s *Server) sendAPIResponse(c *client, subject, reply, request, response string) {
-	s.sendInternalAccountMsg(nil, reply, response)
+	s.sendInternalAccountMsg(c.acc, reply, response)
 	s.sendJetStreamAPIAuditAdvisory(c, subject, request, response)
 }
 
diff --git a/server/opts.go b/server/opts.go
index 817f198d..32802133 100644
--- a/server/opts.go
+++ b/server/opts.go
@@ -57,20 +57,21 @@ func NoErrOnUnknownFields(noError bool) {
 // NOTE: This structure is no longer used for monitoring endpoints
 // and json tags are deprecated and may be removed in the future.
 type ClusterOpts struct {
-	Name           string            `json:"-"`
-	Host           string            `json:"addr,omitempty"`
-	Port           int               `json:"cluster_port,omitempty"`
-	Username       string            `json:"-"`
-	Password       string            `json:"-"`
-	AuthTimeout    float64           `json:"auth_timeout,omitempty"`
-	Permissions    *RoutePermissions `json:"-"`
-	TLSTimeout     float64           `json:"-"`
-	TLSConfig      *tls.Config       `json:"-"`
-	TLSMap         bool              `json:"-"`
-	ListenStr      string            `json:"-"`
-	Advertise      string            `json:"-"`
-	NoAdvertise    bool              `json:"-"`
-	ConnectRetries int               `json:"-"`
+	Name              string            `json:"-"`
+	Host              string            `json:"addr,omitempty"`
+	Port              int               `json:"cluster_port,omitempty"`
+	Username          string            `json:"-"`
+	Password          string            `json:"-"`
+	AuthTimeout       float64           `json:"auth_timeout,omitempty"`
+	Permissions       *RoutePermissions `json:"-"`
+	TLSTimeout        float64           `json:"-"`
+	TLSConfig         *tls.Config       `json:"-"`
+	TLSMap            bool              `json:"-"`
+	TLSCheckKnwonURLs bool              `json:"-"`
+	ListenStr         string            `json:"-"`
+	Advertise         string            `json:"-"`
+	NoAdvertise       bool              `json:"-"`
+	ConnectRetries    int               `json:"-"`
 
 	// Not exported (used in tests)
 	resolver netResolver
@@ -80,19 +81,20 @@ type ClusterOpts struct {
 // NOTE: This structure is no longer used for monitoring endpoints
 // and json tags are deprecated and may be removed in the future.
 type GatewayOpts struct {
-	Name           string               `json:"name"`
-	Host           string               `json:"addr,omitempty"`
-	Port           int                  `json:"port,omitempty"`
-	Username       string               `json:"-"`
-	Password       string               `json:"-"`
-	AuthTimeout    float64              `json:"auth_timeout,omitempty"`
-	TLSConfig      *tls.Config          `json:"-"`
-	TLSTimeout     float64              `json:"tls_timeout,omitempty"`
-	TLSMap         bool                 `json:"-"`
-	Advertise      string               `json:"advertise,omitempty"`
-	ConnectRetries int                  `json:"connect_retries,omitempty"`
-	Gateways       []*RemoteGatewayOpts `json:"gateways,omitempty"`
-	RejectUnknown  bool                 `json:"reject_unknown,omitempty"` // config got renamed to reject_unknown_cluster
+	Name              string               `json:"name"`
+	Host              string               `json:"addr,omitempty"`
+	Port              int                  `json:"port,omitempty"`
+	Username          string               `json:"-"`
+	Password          string               `json:"-"`
+	AuthTimeout       float64              `json:"auth_timeout,omitempty"`
+	TLSConfig         *tls.Config          `json:"-"`
+	TLSTimeout        float64              `json:"tls_timeout,omitempty"`
+	TLSMap            bool                 `json:"-"`
+	TLSCheckKnownURLs bool                 `json:"-"`
+	Advertise         string               `json:"advertise,omitempty"`
+	ConnectRetries    int                  `json:"connect_retries,omitempty"`
+	Gateways          []*RemoteGatewayOpts `json:"gateways,omitempty"`
+	RejectUnknown     bool                 `json:"reject_unknown,omitempty"` // config got renamed to reject_unknown_cluster
 
 	// Not exported, for tests.
 	resolver         netResolver
@@ -391,15 +393,16 @@ type authorization struct {
 // TLSConfigOpts holds the parsed tls config information,
 // used with flag parsing
 type TLSConfigOpts struct {
-	CertFile         string
-	KeyFile          string
-	CaFile           string
-	Verify           bool
-	Insecure         bool
-	Map              bool
-	Timeout          float64
-	Ciphers          []uint16
-	CurvePreferences []tls.CurveID
+	CertFile          string
+	KeyFile           string
+	CaFile            string
+	Verify            bool
+	Insecure          bool
+	Map               bool
+	TLSCheckKnownURLs bool
+	Timeout           float64
+	Ciphers           []uint16
+	CurvePreferences  []tls.CurveID
 }
 
 var tlsUsage = `
@@ -745,7 +748,7 @@ func (o *Options) processConfigFileLine(k string, v interface{}, errors *[]error
 	case "ping_max":
 		o.MaxPingsOut = int(v.(int64))
 	case "tls":
-		tc, err := parseTLS(tk)
+		tc, err := parseTLS(tk, true)
 		if err != nil {
 			*errors = append(*errors, err)
 			return
@@ -924,7 +927,7 @@ func (o *Options) processConfigFileLine(k string, v interface{}, errors *[]error
 			*errors = append(*errors, err)
 		}
 	case "resolver_tls":
-		tc, err := parseTLS(tk)
+		tc, err := parseTLS(tk, true)
 		if err != nil {
 			*errors = append(*errors, err)
 			return
@@ -1160,6 +1163,7 @@ func parseCluster(v interface{}, opts *Options, errors *[]error, warnings *[]err
 			opts.Cluster.TLSConfig = config
 			opts.Cluster.TLSTimeout = tlsopts.Timeout
 			opts.Cluster.TLSMap = tlsopts.Map
+			opts.Cluster.TLSCheckKnwonURLs = tlsopts.TLSCheckKnownURLs
 		case "cluster_advertise", "advertise":
 			opts.Cluster.Advertise = mv.(string)
 		case "no_advertise":
@@ -1275,6 +1279,7 @@ func parseGateway(v interface{}, o *Options, errors *[]error, warnings *[]error)
 			o.Gateway.TLSConfig = config
 			o.Gateway.TLSTimeout = tlsopts.Timeout
 			o.Gateway.TLSMap = tlsopts.Map
+			o.Gateway.TLSCheckKnownURLs = tlsopts.TLSCheckKnownURLs
 		case "advertise":
 			o.Gateway.Advertise = mv.(string)
 		case "connect_retries":
@@ -1481,7 +1486,7 @@ func parseLeafNodes(v interface{}, opts *Options, errors *[]error, warnings *[]e
 		case "reconnect", "reconnect_delay", "reconnect_interval":
 			opts.LeafNode.ReconnectInterval = time.Duration(int(mv.(int64))) * time.Second
 		case "tls":
-			tc, err := parseTLS(tk)
+			tc, err := parseTLS(tk, true)
 			if err != nil {
 				*errors = append(*errors, err)
 				continue
@@ -1678,7 +1683,7 @@ func parseRemoteLeafNodes(v interface{}, errors *[]error, warnings *[]error) ([]
 				}
 				remote.Credentials = p
 			case "tls":
-				tc, err := parseTLS(tk)
+				tc, err := parseTLS(tk, true)
 				if err != nil {
 					*errors = append(*errors, err)
 					continue
@@ -1733,7 +1738,7 @@ func parseRemoteLeafNodes(v interface{}, errors *[]error, warnings *[]error) ([]
 // Parse TLS and returns a TLSConfig and TLSTimeout.
 // Used by cluster and gateway parsing.
 func getTLSConfig(tk token) (*tls.Config, *TLSConfigOpts, error) {
-	tc, err := parseTLS(tk)
+	tc, err := parseTLS(tk, false)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -3203,7 +3208,7 @@ func parseCurvePreferences(curveName string) (tls.CurveID, error) {
 }
 
 // Helper function to parse TLS configs.
-func parseTLS(v interface{}) (t *TLSConfigOpts, retErr error) {
+func parseTLS(v interface{}, isClientCtx bool) (t *TLSConfigOpts, retErr error) {
 	var (
 		tlsm map[string]interface{}
 		tc   = TLSConfigOpts{}
@@ -3251,8 +3256,22 @@ func parseTLS(v interface{}) (t *TLSConfigOpts, retErr error) {
 			if !ok {
 				return nil, &configErr{tk, "error parsing tls config, expected 'verify_and_map' to be a boolean"}
 			}
-			tc.Verify = verify
+			if verify {
+				tc.Verify = verify
+			}
 			tc.Map = verify
+		case "verify_cert_and_check_known_urls":
+			verify, ok := mv.(bool)
+			if !ok {
+				return nil, &configErr{tk, "error parsing tls config, expected 'verify_cert_and_check_known_urls' to be a boolean"}
+			}
+			if verify && isClientCtx {
+				return nil, &configErr{tk, "verify_cert_and_check_known_urls not supported in this context"}
+			}
+			if verify {
+				tc.Verify = verify
+			}
+			tc.TLSCheckKnownURLs = verify
 		case "cipher_suites":
 			ra := mv.([]interface{})
 			if len(ra) == 0 {
@@ -3408,7 +3427,7 @@ func parseWebsocket(v interface{}, o *Options, errors *[]error, warnings *[]erro
 		case "no_tls":
 			o.Websocket.NoTLS = mv.(bool)
 		case "tls":
-			tc, err := parseTLS(tk)
+			tc, err := parseTLS(tk, true)
 			if err != nil {
 				*errors = append(*errors, err)
 				continue
diff --git a/server/stream.go b/server/stream.go
index 2698080a..770fb012 100644
--- a/server/stream.go
+++ b/server/stream.go
@@ -873,7 +873,6 @@ func (mset *Stream) processInboundJetStreamMsg(_ *subscription, pc *client, subj
 	if c != nil && c.acc != nil {
 		accName = c.acc.Name
 	}
-
 	doAck := !mset.config.NoAck
 	pubAck := mset.pubAck
 	jsa := mset.jsa
@@ -1100,7 +1099,6 @@ func (mset *Stream) internalSendLoop() {
 			didDeliver := c.processInboundClientMsg(msg)
 			c.pa.szb = nil
 			c.flushClients(0)
-
 			// Check to see if this is a delivery for an observable and
 			// we failed to deliver the message. If so alert the observable.
 			if pm.o != nil && pm.seq > 0 && !didDeliver {


Test patch:
diff --git a/server/accounts_test.go b/server/accounts_test.go
index ee98ab95..35d74277 100644
--- a/server/accounts_test.go
+++ b/server/accounts_test.go
@@ -661,7 +661,7 @@ func TestSimpleMapping(t *testing.T) {
 		}
 		matches := mraw[0]
 		if matches[SUB_INDEX] != "import.foo" {
-			t.Fatalf("Did not get correct subject: wanted %q, got %q", "import.foo", matches[SUB_INDEX])
+			t.Fatalf("Did not get correct subject: '%s'", matches[SUB_INDEX])
 		}
 		if matches[SID_INDEX] != sid {
 			t.Fatalf("Did not get correct sid: '%s'", matches[SID_INDEX])
diff --git a/server/auth_test.go b/server/auth_test.go
index 031c70ec..1fb3acef 100644
--- a/server/auth_test.go
+++ b/server/auth_test.go
@@ -14,6 +14,7 @@
 package server
 
 import (
+	"net/url"
 	"reflect"
 	"strings"
 	"testing"
@@ -154,3 +155,60 @@ func TestUserUnknownAllowedConnectionType(t *testing.T) {
 		}
 	}
 }
+
+func TestDNSAltNameMatching(t *testing.T) {
+	for idx, test := range []struct {
+		altName string
+		urls    []string
+		match   bool
+	}{
+		{"foo", []string{"FOO"}, true},
+		{"foo", []string{".."}, false},
+		{"foo", []string{"."}, false},
+		{"Foo", []string{"foO"}, true},
+		{"FOO", []string{"foo"}, true},
+		{"foo1", []string{"bar"}, false},
+		{"multi", []string{"m", "mu", "mul", "multi"}, true},
+		{"multi", []string{"multi", "m", "mu", "mul"}, true},
+		{"foo.bar", []string{"foo", "foo.bar.bar", "foo.baz"}, false},
+		{"foo.Bar", []string{"foo", "bar.foo", "Foo.Bar"}, true},
+		{"foo.*", []string{"foo", "bar.foo", "Foo.Bar"}, false}, // only match left most
+		{"f*.bar", []string{"foo", "bar.foo", "Foo.Bar"}, false},
+		{"*.bar", []string{"foo.bar"}, true},
+		{"*", []string{"baz.bar", "bar", "z.y"}, true},
+		{"*", []string{"bar"}, true},
+		{"*", []string{"."}, false},
+		{"*", []string{""}, true},
+		{"*", []string{"*"}, true},
+		{"bar.*", []string{"bar.*"}, true},
+		{"*.Y-X-red-mgmt.default.svc", []string{"A.Y-X-red-mgmt.default.svc"}, true},
+		{"*.Y-X-green-mgmt.default.svc", []string{"A.Y-X-green-mgmt.default.svc"}, true},
+		{"*.Y-X-blue-mgmt.default.svc", []string{"A.Y-X-blue-mgmt.default.svc"}, true},
+		{"Y-X-red-mgmt", []string{"Y-X-red-mgmt"}, true},
+		{"Y-X-red-mgmt", []string{"X-X-red-mgmt"}, false},
+		{"Y-X-red-mgmt", []string{"Y-X-green-mgmt"}, false},
+		{"Y-X-red-mgmt", []string{"Y"}, false},
+		{"Y-X-red-mgmt", []string{"Y-X"}, false},
+		{"Y-X-red-mgmt", []string{"Y-X-red"}, false},
+		{"Y-X-red-mgmt", []string{"X-red-mgmt"}, false},
+		{"Y-X-green-mgmt", []string{"Y-X-green-mgmt"}, true},
+		{"Y-X-blue-mgmt", []string{"Y-X-blue-mgmt"}, true},
+		{"connect.Y.local", []string{"connect.Y.local"}, true},
+		{"connect.Y.local", []string{".Y.local"}, false},
+		{"connect.Y.local", []string{"..local"}, false},
+		{"gcp.Y.local", []string{"gcp.Y.local"}, true},
+		{"uswest1.gcp.Y.local", []string{"uswest1.gcp.Y.local"}, true},
+	} {
+		urlSet := make([]*url.URL, len(test.urls))
+		for i, u := range test.urls {
+			var err error
+			urlSet[i], err = url.Parse("nats://" + u)
+			if err != nil {
+				t.Fatal(err)
+			}
+		}
+		if dnsAltNameMatches(dnsAltNameLabels(test.altName), urlSet) != test.match {
+			t.Fatal("Test", idx, "Match miss match, expected:", test.match)
+		}
+	}
+}
diff --git a/server/config_check_test.go b/server/config_check_test.go
index c6e9ce30..166aa761 100644
--- a/server/config_check_test.go
+++ b/server/config_check_test.go
@@ -360,6 +360,19 @@ func TestConfigCheck(t *testing.T) {
 			errorLine: 4,
 			errorPos:  5,
 		},
+		{
+			name: "verify_cert_and_check_known_urls not support for clients",
+			config: `
+		tls = {
+						cert_file: "configs/certs/server.pem"
+						key_file: "configs/certs/key.pem"
+					    verify_cert_and_check_known_urls: true
+		}
+		`,
+			err:       errors.New("verify_cert_and_check_known_urls not supported in this context"),
+			errorLine: 5,
+			errorPos:  10,
+		},
 		{
 			name: "when unknown option is in cluster config with defined routes",
 			config: `
@@ -1140,6 +1153,25 @@ func TestConfigCheck(t *testing.T) {
 			errorLine: 0,
 			errorPos:  0,
 		},
+		{
+			name: "verify_cert_and_check_known_urls do not work for leaf nodes",
+			config: `
+		leafnodes {
+		  remotes = [
+		    {
+		      url: "tls://nats:7422"
+		      tls {
+		        timeout: 0.01
+				verify_cert_and_check_known_urls: true
+		      }
+		    }
+		  ]
+		}`,
+			//Unexpected error after processing config: /var/folders/9h/6g_c9l6n6bb8gp331d_9y0_w0000gn/T/057996446:8:5:
+			err:       errors.New("verify_cert_and_check_known_urls not supported in this context"),
+			errorLine: 8,
+			errorPos:  5,
+		},
 		{
 			name: "when leafnode remotes use wrong type",
 			config: `
@@ -1360,6 +1392,21 @@ func TestConfigCheck(t *testing.T) {
 			errorLine: 3,
 			errorPos:  21,
 		},
+		{
+			name: "verify_cert_and_check_known_urls not support for websockets",
+			config: `
+				websocket {
+                    tls {
+						cert_file: "configs/certs/server.pem"
+						key_file: "configs/certs/key.pem"
+					    verify_cert_and_check_known_urls: true
+					}
+				}
+			`,
+			err:       fmt.Errorf("verify_cert_and_check_known_urls not supported in this context"),
+			errorLine: 6,
+			errorPos:  10,
+		},
 	}
 
 	checkConfig := func(config string) error {
diff --git a/test/jetstream_test.go b/test/jetstream_test.go
index 06ade7fc..cea5fc77 100644
--- a/test/jetstream_test.go
+++ b/test/jetstream_test.go
@@ -1099,7 +1099,7 @@ func TestJetStreamAddStreamSameConfigOK(t *testing.T) {
 	}
 }
 
-func sendStreamMsg(t *testing.T, nc *nats.Conn, subject, msg string) *server.PubAck {
+func sendStreamMsg(t *testing.T, nc *nats.Conn, subject, msg string) uint64 {
 	t.Helper()
 	resp, _ := nc.Request(subject, []byte(msg), 500*time.Millisecond)
 	if resp == nil {
@@ -1112,7 +1112,7 @@ func sendStreamMsg(t *testing.T, nc *nats.Conn, subject, msg string) *server.Pub
 	if err := json.Unmarshal(resp.Data[3:], &pubAck); err != nil {
 		t.Fatalf("Unexpected error: %v", err)
 	}
-	return &pubAck
+	return pubAck.Seq
 }
 
 func TestJetStreamBasicAckPublish(t *testing.T) {
@@ -9800,147 +9800,3 @@ func TestJetStreamDeliveryAfterServerRestart(t *testing.T) {
 		t.Fatalf("Did not get message: %v", err)
 	}
 }
-
-// This is for the basics of importing the ability to send to a stream and consume
-// from a consumer that is pull based on push based on a well known delivery subject.
-func TestJetStreamAccountImportBasics(t *testing.T) {
-	conf := createConfFile(t, []byte(`
-		listen: 127.0.0.1:-1
-		no_auth_user: rip
-		jetstream: {max_mem_store: 64GB, max_file_store: 10TB}
-		accounts: {
-			JS: {
-				jetstream: enabled
-				users: [ {user: dlc, password: foo} ]
-				exports [
-					# This is for sending into a stream from other accounts.
-					{ service: "ORDERS.*" }
-					# This is for accessing a pull based consumer.
-					{ service: "$JS.API.CONSUMER.MSG.NEXT.*.*" }
-					# This is streaming to a delivery subject for a push based consumer.
-					{ stream: "deliver.ORDERS" }
-					# This is to ack received messages. This is a service to ack acks..
-					{ service: "$JS.ACK.ORDERS.*.>" }
-				]
-			},
-			IU: {
-				users: [ {user: rip, password: bar} ]
-				imports [
-					{ service: { subject: "ORDERS.*", account: JS }, to: "my.orders.$1" }
-					{ service: { subject: "$JS.API.CONSUMER.MSG.NEXT.ORDERS.d", account: JS }, to: "nxt.msg" }
-					{ stream:  { subject: "deliver.ORDERS", account: JS }, to: "d" }
-					{ service: { subject: "$JS.ACK.ORDERS.*.>", account: JS } }
-				]
-			},
-		}
-	`))
-	defer os.Remove(conf)
-
-	s, _ := RunServerWithConfig(conf)
-	defer s.Shutdown()
-
-	if config := s.JetStreamConfig(); config != nil {
-		defer os.RemoveAll(config.StoreDir)
-	}
-
-	acc, err := s.LookupAccount("JS")
-	if err != nil {
-		t.Fatalf("Unexpected error looking up account: %v", err)
-	}
-
-	mset, err := acc.AddStream(&server.StreamConfig{Name: "ORDERS", Subjects: []string{"ORDERS.*"}})
-	if err != nil {
-		t.Fatalf("Unexpected error adding stream: %v", err)
-	}
-	defer mset.Delete()
-
-	// This should be the rip user, the one that imports some JS.
-	nc := clientConnectToServer(t, s)
-	defer nc.Close()
-
-	// Simple publish to a stream.
-	pubAck := sendStreamMsg(t, nc, "my.orders.foo", "ORDERS-1")
-	if pubAck.Stream != "ORDERS" || pubAck.Seq != 1 {
-		t.Fatalf("Bad pubAck received: %+v", pubAck)
-	}
-	if msgs := mset.State().Msgs; msgs != 1 {
-		t.Fatalf("Expected 1 message, got %d", msgs)
-	}
-
-	total := 2
-	for i := 2; i <= total; i++ {
-		sendStreamMsg(t, nc, "my.orders.bar", fmt.Sprintf("ORDERS-%d", i))
-	}
-	if msgs := mset.State().Msgs; msgs != uint64(total) {
-		t.Fatalf("Expected %d messages, got %d", total, msgs)
-	}
-
-	// Now test access to a pull based consumer, e.g. workqueue.
-	o, err := mset.AddConsumer(&server.ConsumerConfig{
-		Durable:   "d",
-		AckPolicy: server.AckExplicit,
-	})
-	if err != nil {
-		t.Fatalf("Expected no error, got %v", err)
-	}
-	defer o.Delete()
-
-	// We mapped the next message request, "$JS.API.CONSUMER.MSG.NEXT.ORDERS.d" -> "nxt.msg"
-	m, err := nc.Request("nxt.msg", nil, time.Second)
-	if err != nil {
-		t.Fatalf("Unexpected error: %v", err)
-	}
-	if string(m.Data) != "ORDERS-1" {
-		t.Fatalf("Expected to receive %q, got %q", "ORDERS-1", m.Data)
-	}
-
-	// Now test access to a push based consumer
-	o, err = mset.AddConsumer(&server.ConsumerConfig{
-		Durable:        "p",
-		DeliverSubject: "deliver.ORDERS",
-		AckPolicy:      server.AckExplicit,
-	})
-	if err != nil {
-		t.Fatalf("Expected no error, got %v", err)
-	}
-	defer o.Delete()
-
-	// We remapped from above, deliver.ORDERS -> d
-	sub, _ := nc.SubscribeSync("d")
-	defer sub.Unsubscribe()
-
-	checkFor(t, 250*time.Millisecond, 10*time.Millisecond, func() error {
-		if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != total {
-			return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, total)
-		}
-		return nil
-	})
-
-	m, _ = sub.NextMsg(time.Second)
-	// Make sure we remapped subject correctly across the account boundary.
-	if m.Subject != "ORDERS.foo" {
-		t.Fatalf("Expected subject of %q, got %q", "ORDERS.foo", m.Subject)
-	}
-	// Now make sure we can ack messages correctly.
-	m.Ack()
-	nc.Flush()
-
-	if info := o.Info(); info.AckFloor.Consumer != 1 {
-		t.Fatalf("Did not receive the ack properly")
-	}
-
-	// Grab second one now.
-	m, _ = sub.NextMsg(time.Second)
-	// Make sure we remapped subject correctly across the account boundary.
-	if m.Subject != "ORDERS.bar" {
-		t.Fatalf("Expected subject of %q, got %q", "ORDERS.bar", m.Subject)
-	}
-	// Now make sure we can ack messages and get back an ack as well.
-	resp, _ := nc.Request(m.Reply, nil, 100*time.Millisecond)
-	if resp == nil {
-		t.Fatalf("No response, possible timeout?")
-	}
-	if info := o.Info(); info.AckFloor.Consumer != 2 {
-		t.Fatalf("Did not receive the ack properly")
-	}
-}
diff --git a/test/tls_test.go b/test/tls_test.go
index 633d88f5..17ceac39 100644
--- a/test/tls_test.go
+++ b/test/tls_test.go
@@ -786,6 +786,175 @@ func TestTLSGatewaysCertificateCNBasedAuth(t *testing.T) {
 	}
 }
 
+func TestTLSRoutesCertificateImplicitAllowPass(t *testing.T) {
+	testTLSRoutesCertificateImplicitAllow(t, true)
+}
+
+func TestTLSRoutesCertificateImplicitAllowFail(t *testing.T) {
+	testTLSRoutesCertificateImplicitAllow(t, false)
+}
+
+func testTLSRoutesCertificateImplicitAllow(t *testing.T, pass bool) {
+	t.Helper()
+	// Base config for the servers
+	cfg, err := ioutil.TempFile("", "cfg")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(cfg.Name())
+	cfg.WriteString(fmt.Sprintf(`
+		cluster {
+		  tls {
+			cert_file = "./configs/certs/tlsauth/server.pem"
+			key_file = "./configs/certs/tlsauth/server-key.pem"
+			ca_file = "./configs/certs/tlsauth/ca.pem"
+			verify_cert_and_check_known_urls = true
+			insecure = %t
+			timeout = 1
+		  }
+		}
+	`, !pass)) // set insecure to skip verification on the outgoing end
+	if err = cfg.Sync(); err != nil {
+		t.Fatal(err)
+	}
+
+	optsA := LoadConfig(cfg.Name())
+	optsB := LoadConfig(cfg.Name())
+
+	routeURLs := "nats://localhost:9935, nats://localhost:9936"
+	if !pass {
+		routeURLs = "nats://127.0.0.1:9935, nats://127.0.0.1:9936"
+	}
+	optsA.Host = "127.0.0.1"
+	optsA.Port = 9335
+	optsA.Cluster.Name = "xyz"
+	optsA.Cluster.Host = optsA.Host
+	optsA.Cluster.Port = 9935
+	optsA.Routes = server.RoutesFromStr(routeURLs)
+	optsA.NoSystemAccount = true
+	srvA := RunServer(optsA)
+	defer srvA.Shutdown()
+
+	optsB.Host = "127.0.0.1"
+	optsB.Port = 9336
+	optsB.Cluster.Name = "xyz"
+	optsB.Cluster.Host = optsB.Host
+	optsB.Cluster.Port = 9936
+	optsB.Routes = server.RoutesFromStr(routeURLs)
+	optsB.NoSystemAccount = true
+	srvB := RunServer(optsB)
+	defer srvB.Shutdown()
+
+	// srvC is not connected to srvA and srvB due to wrong cert
+	if pass {
+		checkNumRoutes(t, srvA, 1)
+		checkNumRoutes(t, srvB, 1)
+	} else {
+		time.Sleep(1 * time.Second) // the fail case uses the IP, so a short wait is sufficient
+		if srvA.NumRoutes() != 0 || srvB.NumRoutes() != 0 {
+			t.Fatal("No route connection expected")
+		}
+	}
+}
+
+func TestTLSGatewaysCertificateImplicitAllowPass(t *testing.T) {
+	testTLSGatewaysCertificateImplicitAllow(t, true)
+}
+
+func TestTLSGatewaysCertificateImplicitAllowFail(t *testing.T) {
+	testTLSGatewaysCertificateImplicitAllow(t, false)
+}
+
+func testTLSGatewaysCertificateImplicitAllow(t *testing.T, pass bool) {
+	t.Helper()
+	// Base config for the servers
+	cfg, err := ioutil.TempFile("", "cfg")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(cfg.Name())
+	cfg.WriteString(fmt.Sprintf(`
+		gateway {
+		  tls {
+			cert_file = "./configs/certs/tlsauth/server.pem"
+			key_file = "./configs/certs/tlsauth/server-key.pem"
+			ca_file = "./configs/certs/tlsauth/ca.pem"
+			verify_cert_and_check_known_urls = true
+			insecure = %t
+			timeout = 1
+		  }
+		}
+	`, !pass)) // set insecure to skip verification on the outgoing end
+	if err = cfg.Sync(); err != nil {
+		t.Fatal(err)
+	}
+
+	optsA := LoadConfig(cfg.Name())
+	optsB := LoadConfig(cfg.Name())
+
+	urlA := "nats://localhost:9995"
+	urlB := "nats://localhost:9996"
+	if !pass {
+		urlA = "nats://127.0.0.1:9995"
+		urlB = "nats://127.0.0.1:9996"
+	}
+
+	gwA, err := url.Parse(urlA)
+	if err != nil {
+		t.Fatal(err)
+	}
+	gwB, err := url.Parse(urlB)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	optsA.Host = "127.0.0.1"
+	optsA.Port = -1
+	optsA.Gateway.Name = "A"
+	optsA.Gateway.Port = 9995
+
+	optsB.Host = "127.0.0.1"
+	optsB.Port = -1
+	optsB.Gateway.Name = "B"
+	optsB.Gateway.Port = 9996
+
+	gateways := make([]*server.RemoteGatewayOpts, 2)
+	gateways[0] = &server.RemoteGatewayOpts{
+		Name: optsA.Gateway.Name,
+		URLs: []*url.URL{gwA},
+	}
+	gateways[1] = &server.RemoteGatewayOpts{
+		Name: optsB.Gateway.Name,
+		URLs: []*url.URL{gwB},
+	}
+
+	optsA.Gateway.Gateways = gateways
+	optsB.Gateway.Gateways = gateways
+
+	server.SetGatewaysSolicitDelay(100 * time.Millisecond)
+	defer server.ResetGatewaysSolicitDelay()
+
+	srvA := RunServer(optsA)
+	defer srvA.Shutdown()
+
+	srvB := RunServer(optsB)
+	defer srvB.Shutdown()
+
+	// Because we need to use "localhost" in the gw URLs (to match
+	// hostname in the user/CN), the server may try to connect to
+	// a [::1], etc.. that may or may not work, so give a lot of
+	// time for that to complete ok.
+	if pass {
+		waitForOutboundGateways(t, srvA, 1, 5*time.Second)
+		waitForOutboundGateways(t, srvB, 1, 5*time.Second)
+	} else {
+		time.Sleep(1 * time.Second) // the fail case uses the IP, so a short wait is sufficient
+		if srvA.NumOutboundGateways() != 0 || srvB.NumOutboundGateways() != 0 {
+			t.Fatal("No outbound gateway connection expected")
+		}
+	}
+}
+
 func TestTLSVerifyClientCertificate(t *testing.T) {
 	srv, opts := RunServerWithConfig("./configs/tlsverify_noca.conf")
 	defer srv.Shutdown()