mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 11:48:43 -07:00
Merge pull request #1727 from nats-io/tls-verify-and-impliict-allow
[ADDED] verify_cert_and_check_known_urls to tie subject alt name to url in cfg
This commit is contained in:
101
server/auth.go
101
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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
107
server/opts.go
107
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
|
||||
|
||||
169
test/tls_test.go
169
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()
|
||||
|
||||
Reference in New Issue
Block a user