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:
Ivan Kozlovic
2020-11-20 15:10:01 -07:00
committed by GitHub
5 changed files with 417 additions and 65 deletions

View File

@@ -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 {

View File

@@ -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)
}
}
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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()