mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
[FIXED] Better support for distinguishedNameMatch in TLS Auth
Signed-off-by: Waldemar Quevedo <wally@synadia.com>
This commit is contained in:
committed by
Ivan Kozlovic
parent
12ae32a477
commit
7a88eee090
234
internal/ldap/dn.go
Normal file
234
internal/ldap/dn.go
Normal file
@@ -0,0 +1,234 @@
|
||||
// Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com)
|
||||
// Portions copyright (c) 2015-2016 go-ldap Authors
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509/pkix"
|
||||
enchex "encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var attributeTypeNames = map[string]string{
|
||||
"2.5.4.6": "C",
|
||||
"2.5.4.10": "O",
|
||||
"2.5.4.11": "OU",
|
||||
"2.5.4.3": "CN",
|
||||
"2.5.4.5": "SERIALNUMBER",
|
||||
"2.5.4.7": "L",
|
||||
"2.5.4.8": "ST",
|
||||
"2.5.4.9": "STREET",
|
||||
"2.5.4.17": "POSTALCODE",
|
||||
// FIXME: Add others.
|
||||
"0.9.2342.19200300.100.1.25": "DC",
|
||||
}
|
||||
|
||||
// AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514
|
||||
type AttributeTypeAndValue struct {
|
||||
// Type is the attribute type
|
||||
Type string
|
||||
// Value is the attribute value
|
||||
Value string
|
||||
}
|
||||
|
||||
// RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514
|
||||
type RelativeDN struct {
|
||||
Attributes []*AttributeTypeAndValue
|
||||
}
|
||||
|
||||
// DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514
|
||||
type DN struct {
|
||||
RDNs []*RelativeDN
|
||||
}
|
||||
|
||||
// FromCertSubject takes a pkix.Name from a cert and returns a DN
|
||||
// that uses the same set.
|
||||
func FromCertSubject(subject pkix.Name) (*DN, error) {
|
||||
dn := &DN{
|
||||
RDNs: make([]*RelativeDN, 0),
|
||||
}
|
||||
for i := len(subject.Names) - 1; i >= 0; i-- {
|
||||
name := subject.Names[i]
|
||||
oidString := name.Type.String()
|
||||
typeName, ok := attributeTypeNames[oidString]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid type name: %+v", name)
|
||||
}
|
||||
v, ok := name.Value.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid type value: %+v", v)
|
||||
}
|
||||
rdn := &RelativeDN{
|
||||
Attributes: []*AttributeTypeAndValue{
|
||||
{
|
||||
Type: typeName,
|
||||
Value: v,
|
||||
},
|
||||
},
|
||||
}
|
||||
dn.RDNs = append(dn.RDNs, rdn)
|
||||
}
|
||||
return dn, nil
|
||||
}
|
||||
|
||||
// ParseDN returns a distinguishedName or an error.
|
||||
// The function respects https://tools.ietf.org/html/rfc4514
|
||||
func ParseDN(str string) (*DN, error) {
|
||||
dn := new(DN)
|
||||
dn.RDNs = make([]*RelativeDN, 0)
|
||||
rdn := new(RelativeDN)
|
||||
rdn.Attributes = make([]*AttributeTypeAndValue, 0)
|
||||
buffer := bytes.Buffer{}
|
||||
attribute := new(AttributeTypeAndValue)
|
||||
escaping := false
|
||||
|
||||
unescapedTrailingSpaces := 0
|
||||
stringFromBuffer := func() string {
|
||||
s := buffer.String()
|
||||
s = s[0 : len(s)-unescapedTrailingSpaces]
|
||||
buffer.Reset()
|
||||
unescapedTrailingSpaces = 0
|
||||
return s
|
||||
}
|
||||
|
||||
for i := 0; i < len(str); i++ {
|
||||
char := str[i]
|
||||
switch {
|
||||
case escaping:
|
||||
unescapedTrailingSpaces = 0
|
||||
escaping = false
|
||||
switch char {
|
||||
case ' ', '"', '#', '+', ',', ';', '<', '=', '>', '\\':
|
||||
buffer.WriteByte(char)
|
||||
continue
|
||||
}
|
||||
// Not a special character, assume hex encoded octet
|
||||
if len(str) == i+1 {
|
||||
return nil, errors.New("got corrupted escaped character")
|
||||
}
|
||||
|
||||
dst := []byte{0}
|
||||
n, err := enchex.Decode([]byte(dst), []byte(str[i:i+2]))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode escaped character: %s", err)
|
||||
} else if n != 1 {
|
||||
return nil, fmt.Errorf("expected 1 byte when un-escaping, got %d", n)
|
||||
}
|
||||
buffer.WriteByte(dst[0])
|
||||
i++
|
||||
case char == '\\':
|
||||
unescapedTrailingSpaces = 0
|
||||
escaping = true
|
||||
case char == '=':
|
||||
attribute.Type = stringFromBuffer()
|
||||
// Special case: If the first character in the value is # the following data
|
||||
// is BER encoded. Throw an error since not supported right now.
|
||||
if len(str) > i+1 && str[i+1] == '#' {
|
||||
return nil, errors.New("unsupported BER encoding")
|
||||
}
|
||||
case char == ',' || char == '+':
|
||||
// We're done with this RDN or value, push it
|
||||
if len(attribute.Type) == 0 {
|
||||
return nil, errors.New("incomplete type, value pair")
|
||||
}
|
||||
attribute.Value = stringFromBuffer()
|
||||
rdn.Attributes = append(rdn.Attributes, attribute)
|
||||
attribute = new(AttributeTypeAndValue)
|
||||
if char == ',' {
|
||||
dn.RDNs = append(dn.RDNs, rdn)
|
||||
rdn = new(RelativeDN)
|
||||
rdn.Attributes = make([]*AttributeTypeAndValue, 0)
|
||||
}
|
||||
case char == ' ' && buffer.Len() == 0:
|
||||
// ignore unescaped leading spaces
|
||||
continue
|
||||
default:
|
||||
if char == ' ' {
|
||||
// Track unescaped spaces in case they are trailing and we need to remove them
|
||||
unescapedTrailingSpaces++
|
||||
} else {
|
||||
// Reset if we see a non-space char
|
||||
unescapedTrailingSpaces = 0
|
||||
}
|
||||
buffer.WriteByte(char)
|
||||
}
|
||||
}
|
||||
if buffer.Len() > 0 {
|
||||
if len(attribute.Type) == 0 {
|
||||
return nil, errors.New("DN ended with incomplete type, value pair")
|
||||
}
|
||||
attribute.Value = stringFromBuffer()
|
||||
rdn.Attributes = append(rdn.Attributes, attribute)
|
||||
dn.RDNs = append(dn.RDNs, rdn)
|
||||
}
|
||||
return dn, nil
|
||||
}
|
||||
|
||||
// Equal returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
|
||||
// Returns true if they have the same number of relative distinguished names
|
||||
// and corresponding relative distinguished names (by position) are the same.
|
||||
func (d *DN) Equal(other *DN) bool {
|
||||
if len(d.RDNs) != len(other.RDNs) {
|
||||
return false
|
||||
}
|
||||
for i := range d.RDNs {
|
||||
if !d.RDNs[i].Equal(other.RDNs[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// AncestorOf returns true if the other DN consists of at least one RDN followed by all the RDNs of the current DN.
|
||||
// "ou=widgets,o=acme.com" is an ancestor of "ou=sprockets,ou=widgets,o=acme.com"
|
||||
// "ou=widgets,o=acme.com" is not an ancestor of "ou=sprockets,ou=widgets,o=foo.com"
|
||||
// "ou=widgets,o=acme.com" is not an ancestor of "ou=widgets,o=acme.com"
|
||||
func (d *DN) AncestorOf(other *DN) bool {
|
||||
if len(d.RDNs) >= len(other.RDNs) {
|
||||
return false
|
||||
}
|
||||
// Take the last `len(d.RDNs)` RDNs from the other DN to compare against
|
||||
otherRDNs := other.RDNs[len(other.RDNs)-len(d.RDNs):]
|
||||
for i := range d.RDNs {
|
||||
if !d.RDNs[i].Equal(otherRDNs[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch).
|
||||
// Relative distinguished names are the same if and only if they have the same number of AttributeTypeAndValues
|
||||
// and each attribute of the first RDN is the same as the attribute of the second RDN with the same attribute type.
|
||||
// The order of attributes is not significant.
|
||||
// Case of attribute types is not significant.
|
||||
func (r *RelativeDN) Equal(other *RelativeDN) bool {
|
||||
if len(r.Attributes) != len(other.Attributes) {
|
||||
return false
|
||||
}
|
||||
return r.hasAllAttributes(other.Attributes) && other.hasAllAttributes(r.Attributes)
|
||||
}
|
||||
|
||||
func (r *RelativeDN) hasAllAttributes(attrs []*AttributeTypeAndValue) bool {
|
||||
for _, attr := range attrs {
|
||||
found := false
|
||||
for _, myattr := range r.Attributes {
|
||||
if myattr.Equal(attr) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Equal returns true if the AttributeTypeAndValue is equivalent to the specified AttributeTypeAndValue
|
||||
// Case of the attribute type is not significant
|
||||
func (a *AttributeTypeAndValue) Equal(other *AttributeTypeAndValue) bool {
|
||||
return strings.EqualFold(a.Type, other.Type) && a.Value == other.Value
|
||||
}
|
||||
212
internal/ldap/dn_test.go
Normal file
212
internal/ldap/dn_test.go
Normal file
@@ -0,0 +1,212 @@
|
||||
// Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com)
|
||||
// Portions copyright (c) 2015-2016 go-ldap Authors
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSuccessfulDNParsing(t *testing.T) {
|
||||
testcases := map[string]DN{
|
||||
"": {[]*RelativeDN{}},
|
||||
"cn=Jim\\2C \\22Hasse Hö\\22 Hansson!,dc=dummy,dc=com": {[]*RelativeDN{
|
||||
{[]*AttributeTypeAndValue{{"cn", "Jim, \"Hasse Hö\" Hansson!"}}},
|
||||
{[]*AttributeTypeAndValue{{"dc", "dummy"}}},
|
||||
{[]*AttributeTypeAndValue{{"dc", "com"}}}}},
|
||||
"UID=jsmith,DC=example,DC=net": {[]*RelativeDN{
|
||||
{[]*AttributeTypeAndValue{{"UID", "jsmith"}}},
|
||||
{[]*AttributeTypeAndValue{{"DC", "example"}}},
|
||||
{[]*AttributeTypeAndValue{{"DC", "net"}}}}},
|
||||
"OU=Sales+CN=J. Smith,DC=example,DC=net": {[]*RelativeDN{
|
||||
{[]*AttributeTypeAndValue{
|
||||
{"OU", "Sales"},
|
||||
{"CN", "J. Smith"}}},
|
||||
{[]*AttributeTypeAndValue{{"DC", "example"}}},
|
||||
{[]*AttributeTypeAndValue{{"DC", "net"}}}}},
|
||||
//
|
||||
// "1.3.6.1.4.1.1466.0=#04024869": {[]*RelativeDN{
|
||||
// {[]*AttributeTypeAndValue{{"1.3.6.1.4.1.1466.0", "Hi"}}}}},
|
||||
// "1.3.6.1.4.1.1466.0=#04024869,DC=net": {[]*RelativeDN{
|
||||
// {[]*AttributeTypeAndValue{{"1.3.6.1.4.1.1466.0", "Hi"}}},
|
||||
// {[]*AttributeTypeAndValue{{"DC", "net"}}}}},
|
||||
"CN=Lu\\C4\\8Di\\C4\\87": {[]*RelativeDN{
|
||||
{[]*AttributeTypeAndValue{{"CN", "Lučić"}}}}},
|
||||
" CN = Lu\\C4\\8Di\\C4\\87 ": {[]*RelativeDN{
|
||||
{[]*AttributeTypeAndValue{{"CN", "Lučić"}}}}},
|
||||
` A = 1 , B = 2 `: {[]*RelativeDN{
|
||||
{[]*AttributeTypeAndValue{{"A", "1"}}},
|
||||
{[]*AttributeTypeAndValue{{"B", "2"}}}}},
|
||||
` A = 1 + B = 2 `: {[]*RelativeDN{
|
||||
{[]*AttributeTypeAndValue{
|
||||
{"A", "1"},
|
||||
{"B", "2"}}}}},
|
||||
` \ \ A\ \ = \ \ 1\ \ , \ \ B\ \ = \ \ 2\ \ `: {[]*RelativeDN{
|
||||
{[]*AttributeTypeAndValue{{" A ", " 1 "}}},
|
||||
{[]*AttributeTypeAndValue{{" B ", " 2 "}}}}},
|
||||
` \ \ A\ \ = \ \ 1\ \ + \ \ B\ \ = \ \ 2\ \ `: {[]*RelativeDN{
|
||||
{[]*AttributeTypeAndValue{
|
||||
{" A ", " 1 "},
|
||||
{" B ", " 2 "}}}}},
|
||||
}
|
||||
|
||||
for test, answer := range testcases {
|
||||
dn, err := ParseDN(test)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(dn, &answer) {
|
||||
t.Errorf("Parsed DN %s is not equal to the expected structure", test)
|
||||
t.Logf("Expected:")
|
||||
for _, rdn := range answer.RDNs {
|
||||
for _, attribs := range rdn.Attributes {
|
||||
t.Logf("#%v\n", attribs)
|
||||
}
|
||||
}
|
||||
t.Logf("Actual:")
|
||||
for _, rdn := range dn.RDNs {
|
||||
for _, attribs := range rdn.Attributes {
|
||||
t.Logf("#%v\n", attribs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorDNParsing(t *testing.T) {
|
||||
testcases := map[string]string{
|
||||
"*": "DN ended with incomplete type, value pair",
|
||||
"cn=Jim\\0Test": "failed to decode escaped character: encoding/hex: invalid byte: U+0054 'T'",
|
||||
"cn=Jim\\0": "got corrupted escaped character",
|
||||
"DC=example,=net": "DN ended with incomplete type, value pair",
|
||||
// "1=#0402486": "failed to decode BER encoding: encoding/hex: odd length hex string",
|
||||
"test,DC=example,DC=com": "incomplete type, value pair",
|
||||
"=test,DC=example,DC=com": "incomplete type, value pair",
|
||||
}
|
||||
|
||||
for test, answer := range testcases {
|
||||
_, err := ParseDN(test)
|
||||
if err == nil {
|
||||
t.Errorf("Expected %s to fail parsing but succeeded\n", test)
|
||||
} else if err.Error() != answer {
|
||||
t.Errorf("Unexpected error on %s:\n%s\nvs.\n%s\n", test, answer, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNEqual(t *testing.T) {
|
||||
testcases := []struct {
|
||||
A string
|
||||
B string
|
||||
Equal bool
|
||||
}{
|
||||
// Exact match
|
||||
{"", "", true},
|
||||
{"o=A", "o=A", true},
|
||||
{"o=A", "o=B", false},
|
||||
|
||||
{"o=A,o=B", "o=A,o=B", true},
|
||||
{"o=A,o=B", "o=A,o=C", false},
|
||||
|
||||
{"o=A+o=B", "o=A+o=B", true},
|
||||
{"o=A+o=B", "o=A+o=C", false},
|
||||
|
||||
// Case mismatch in type is ignored
|
||||
{"o=A", "O=A", true},
|
||||
{"o=A,o=B", "o=A,O=B", true},
|
||||
{"o=A+o=B", "o=A+O=B", true},
|
||||
|
||||
// Case mismatch in value is significant
|
||||
{"o=a", "O=A", false},
|
||||
{"o=a,o=B", "o=A,O=B", false},
|
||||
{"o=a+o=B", "o=A+O=B", false},
|
||||
|
||||
// Multi-valued RDN order mismatch is ignored
|
||||
{"o=A+o=B", "O=B+o=A", true},
|
||||
// Number of RDN attributes is significant
|
||||
{"o=A+o=B", "O=B+o=A+O=B", false},
|
||||
|
||||
// Missing values are significant
|
||||
{"o=A+o=B", "O=B+o=A+O=C", false}, // missing values matter
|
||||
{"o=A+o=B+o=C", "O=B+o=A", false}, // missing values matter
|
||||
|
||||
// Whitespace tests
|
||||
// Matching
|
||||
{
|
||||
"cn=John Doe, ou=People, dc=sun.com",
|
||||
"cn=John Doe, ou=People, dc=sun.com",
|
||||
true,
|
||||
},
|
||||
// Difference in leading/trailing chars is ignored
|
||||
{
|
||||
"cn=John Doe, ou=People, dc=sun.com",
|
||||
"cn=John Doe,ou=People,dc=sun.com",
|
||||
true,
|
||||
},
|
||||
// Difference in values is significant
|
||||
{
|
||||
"cn=John Doe, ou=People, dc=sun.com",
|
||||
"cn=John Doe, ou=People, dc=sun.com",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testcases {
|
||||
a, err := ParseDN(tc.A)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
continue
|
||||
}
|
||||
b, err := ParseDN(tc.B)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if expected, actual := tc.Equal, a.Equal(b); expected != actual {
|
||||
t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
|
||||
continue
|
||||
}
|
||||
if expected, actual := tc.Equal, b.Equal(a); expected != actual {
|
||||
t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNAncestor(t *testing.T) {
|
||||
testcases := []struct {
|
||||
A string
|
||||
B string
|
||||
Ancestor bool
|
||||
}{
|
||||
// Exact match returns false
|
||||
{"", "", false},
|
||||
{"o=A", "o=A", false},
|
||||
{"o=A,o=B", "o=A,o=B", false},
|
||||
{"o=A+o=B", "o=A+o=B", false},
|
||||
|
||||
// Mismatch
|
||||
{"ou=C,ou=B,o=A", "ou=E,ou=D,ou=B,o=A", false},
|
||||
|
||||
// Descendant
|
||||
{"ou=C,ou=B,o=A", "ou=E,ou=C,ou=B,o=A", true},
|
||||
}
|
||||
|
||||
for i, tc := range testcases {
|
||||
a, err := ParseDN(tc.A)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
continue
|
||||
}
|
||||
b, err := ParseDN(tc.B)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if expected, actual := tc.Ancestor, a.AncestorOf(b); expected != actual {
|
||||
t.Errorf("%d: when comparing '%s' and '%s' expected %v, got %v", i, tc.A, tc.B, expected, actual)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
133
server/auth.go
133
server/auth.go
@@ -24,6 +24,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/jwt"
|
||||
"github.com/nats-io/nats-server/v2/internal/ldap"
|
||||
"github.com/nats-io/nkeys"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
@@ -323,6 +324,8 @@ func (s *Server) processClientOrLeafAuthentication(c *client) bool {
|
||||
)
|
||||
|
||||
s.mu.Lock()
|
||||
users := s.users
|
||||
tlsMap := opts.TLSMap
|
||||
authRequired := s.info.AuthRequired
|
||||
if !authRequired {
|
||||
// TODO(dlc) - If they send us credentials should we fail?
|
||||
@@ -363,18 +366,39 @@ func (s *Server) processClientOrLeafAuthentication(c *client) bool {
|
||||
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
|
||||
// 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) {
|
||||
// First do literal lookup using the resulting string representation
|
||||
// of RDNSequence as implemented by the pkix package from Go.
|
||||
if u != "" {
|
||||
usr, ok := users[u]
|
||||
if !ok {
|
||||
return "", ok
|
||||
}
|
||||
user = usr
|
||||
return usr.Username, ok
|
||||
}
|
||||
euser = u
|
||||
return true
|
||||
|
||||
if certRDN == nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Look through the accounts for an RDN that is equal to the one
|
||||
// presented by the certificate.
|
||||
for _, usr := range users {
|
||||
// TODO: Use this utility to make a full validation pass
|
||||
// on start in case tlsmap feature is being used.
|
||||
inputRDN, err := ldap.ParseDN(usr.Username)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if inputRDN.Equal(certRDN) {
|
||||
user = usr
|
||||
return usr.Username, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
})
|
||||
if !authorized {
|
||||
s.mu.Unlock()
|
||||
@@ -385,7 +409,7 @@ func (s *Server) processClientOrLeafAuthentication(c *client) bool {
|
||||
}
|
||||
// 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
|
||||
c.opts.Username = user.Username
|
||||
} else {
|
||||
if c.kind == CLIENT && c.opts.Username == "" && s.opts.NoAuthUser != "" {
|
||||
if u, exists := s.users[s.opts.NoAuthUser]; exists {
|
||||
@@ -496,7 +520,6 @@ func (s *Server) processClientOrLeafAuthentication(c *client) bool {
|
||||
}
|
||||
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
|
||||
@@ -525,7 +548,6 @@ func (s *Server) processClientOrLeafAuthentication(c *client) bool {
|
||||
// or the one specified in config (if provided).
|
||||
return s.registerLeafWithAccount(c, opts.LeafNode.Account)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -549,7 +571,9 @@ func getTLSAuthDCs(rdns *pkix.RDNSequence) string {
|
||||
return strings.Join(dcs, ",")
|
||||
}
|
||||
|
||||
func checkClientTLSCertSubject(c *client, fn func(string) bool) bool {
|
||||
type tlsMapAuthFn func(string, *ldap.DN) (string, bool)
|
||||
|
||||
func checkClientTLSCertSubject(c *client, fn tlsMapAuthFn) bool {
|
||||
tlsState := c.GetTLSConnectionState()
|
||||
if tlsState == nil {
|
||||
c.Debugf("User required in cert, no TLS connection state")
|
||||
@@ -575,41 +599,64 @@ func checkClientTLSCertSubject(c *client, fn func(string) bool) bool {
|
||||
switch {
|
||||
case hasEmailAddresses:
|
||||
for _, u := range cert.EmailAddresses {
|
||||
if fn(u) {
|
||||
c.Debugf("Using email found in cert for auth [%q]", u)
|
||||
if match, ok := fn(u, nil); ok {
|
||||
c.Debugf("Using email found in cert for auth [%q]", match)
|
||||
return true
|
||||
}
|
||||
}
|
||||
fallthrough
|
||||
case hasSANs:
|
||||
for _, u := range cert.DNSNames {
|
||||
if fn(u) {
|
||||
c.Debugf("Using SAN found in cert for auth [%q]", u)
|
||||
if match, ok := fn(u, nil); ok {
|
||||
c.Debugf("Using SAN found in cert for auth [%q]", match)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get the full RDN Sequence that includes the domain components.
|
||||
// Use the string representation of the full RDN Sequence including
|
||||
// the domain components in case there are any.
|
||||
rdn := cert.Subject.ToRDNSequence().String()
|
||||
|
||||
// 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 {
|
||||
c.Debugf("Using DistinguishedNameMatch for auth [%q]", match)
|
||||
return true
|
||||
}
|
||||
c.Debugf("DistinguishedNameMatch could not be used for auth [%q]", rdn)
|
||||
}
|
||||
|
||||
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()
|
||||
//
|
||||
// NOTE: The original sequence from string representation by ToRDNSequence does not follow
|
||||
// the correct ordering, so this addition ofdomainComponents would likely be deprecated in
|
||||
// another release in favor of using the correct ordered as parsed by the go-ldap library.
|
||||
//
|
||||
dcs := getTLSAuthDCs(&rdns)
|
||||
if len(dcs) > 0 {
|
||||
u := strings.Join([]string{rdn, dcs}, ",")
|
||||
if fn(u) {
|
||||
c.Debugf("Using RDNSequence for auth [%q]", u)
|
||||
if match, ok := fn(u, nil); ok {
|
||||
c.Debugf("Using RDNSequence for auth [%q]", match)
|
||||
return true
|
||||
}
|
||||
c.Debugf("RDNSequence could not be used for auth [%q]", u)
|
||||
}
|
||||
}
|
||||
|
||||
// Use the subject of the certificate.
|
||||
u := cert.Subject.String()
|
||||
c.Debugf("Using certificate subject for auth [%q]", u)
|
||||
return fn(u)
|
||||
// If no match, then use the string representation of the RDNSequence
|
||||
// from the subject without the domainComponents.
|
||||
if match, ok := fn(rdn, nil); ok {
|
||||
c.Debugf("Using certificate subject for auth [%q]", match)
|
||||
return true
|
||||
}
|
||||
|
||||
c.Debugf("User in cert [%q], not found", rdn)
|
||||
return false
|
||||
}
|
||||
|
||||
// checkRouterAuth checks optional router authorization which can be nil or username/password.
|
||||
@@ -628,8 +675,8 @@ func (s *Server) isRouterAuthorized(c *client) bool {
|
||||
}
|
||||
|
||||
if opts.Cluster.TLSMap {
|
||||
return checkClientTLSCertSubject(c, func(user string) bool {
|
||||
return opts.Cluster.Username == user
|
||||
return checkClientTLSCertSubject(c, func(user string, _ *ldap.DN) (string, bool) {
|
||||
return "", opts.Cluster.Username == user
|
||||
})
|
||||
}
|
||||
|
||||
@@ -652,8 +699,8 @@ func (s *Server) isGatewayAuthorized(c *client) bool {
|
||||
|
||||
// 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
|
||||
return checkClientTLSCertSubject(c, func(user string, _ *ldap.DN) (string, bool) {
|
||||
return "", opts.Gateway.Username == user
|
||||
})
|
||||
}
|
||||
|
||||
@@ -700,6 +747,30 @@ func (s *Server) isLeafNodeAuthorized(c *client) bool {
|
||||
if opts.LeafNode.Username != _EMPTY_ {
|
||||
return isAuthorized(opts.LeafNode.Username, opts.LeafNode.Password, opts.LeafNode.Account)
|
||||
} else if len(opts.LeafNode.Users) > 0 {
|
||||
if opts.LeafNode.TLSMap {
|
||||
var user *User
|
||||
found := checkClientTLSCertSubject(c, func(u string, _ *ldap.DN) (string, bool) {
|
||||
// This is expected to be a very small array.
|
||||
for _, usr := range opts.LeafNode.Users {
|
||||
if u == usr.Username {
|
||||
user = usr
|
||||
return u, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
})
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
if c.opts.Username != "" {
|
||||
s.Warnf("User %q found in connect proto, but user required from cert", c.opts.Username)
|
||||
}
|
||||
c.opts.Username = user.Username
|
||||
// This will authorize since are using an existing user,
|
||||
// but it will also register with proper account.
|
||||
return isAuthorized(user.Username, user.Password, user.Account.Name)
|
||||
}
|
||||
|
||||
// This is expected to be a very small array.
|
||||
for _, u := range opts.LeafNode.Users {
|
||||
if u.Username == c.opts.Username {
|
||||
|
||||
28
test/configs/certs/rdns/client-d.key
Normal file
28
test/configs/certs/rdns/client-d.key
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDay//0l8jlnU/v
|
||||
DtoyNKmxIC+goq/ooiBnOCkuRyGus1yA70V4MWYEzi/N76sVw3MHXMpWxcYvxNO7
|
||||
+8szsgXVQF6G1pxnm2e3/Xp+lZWVHkjR/6h6dQpAwgyIkXCsIN/4+TYIn1T3/hdx
|
||||
x4rYgeK1YO47lUSLskGNcU76nmqhooboWmWW8NYFJszvS0VUMXvMpaOmrFaIwx4e
|
||||
9nDt8n2H7lyAq7cqo7sHv9ytT6xSPV2ok8swrbD9Ez/lyT+F/WbHkmP1HCK5365d
|
||||
FmH3STH1E2l2lasTC4p92RK7NiWzzyIgOelcT6TblZxxJSyH8KfjaHcxFcJhkHjc
|
||||
zY3h/K0fAgMBAAECggEBAIcSx6othlXSn0VbKvMxtczmrOCDbwu0A0MV1b5/JVkf
|
||||
26yxinagMHYpADQnkLw31CyoaTXWlPpqjbiQwqrgbV9whKrDlP0VYJuivdul5xmO
|
||||
/6+9IDqxRKoj4e7xsthg10RyPZxnGOKcl8ajRKFS1i3ZcFmSViXT30o9uF9aK0Qq
|
||||
2G3EjPQCA7Ivwz2abBWSV0absnNvx4JBaquKC/8+0241wy2md39rJAOVYzdFc6I8
|
||||
330CQvaBrlpQaNVJhZSsUa2XJb4E2kLPM9pDY4SlOewRf/hz+S/cageimpEIQJDe
|
||||
u8C9xqgVrS2xT0ny7iFPTpHwyFdxywOQ4GIka7/q26ECgYEA+eqnQAs5nw/s+qEd
|
||||
nVarDsGcfFwUp7SDnwWvlBvtlt9OTXcqGe929WlKVDaiumOyUKacL8PPa3ru6ZDu
|
||||
+LXA9QikQldjVWCQHtKzDYmDiju5XuxJj+4rOKG2ctmpbjM2SlOoyibqRlU/k9GI
|
||||
htOAqCBWE8Ul6ovsUXUZC8ByFhECgYEA4B9syA+y8HG1Rccqo9iOyq65wCqrWuo8
|
||||
d1Q34Dw+mkFUTgLVSWTX0u7926bnsgvMPmGOhZ9ekq1ihZyVhIX0AY/MbfR98wSa
|
||||
YMC9ISn+otZhDtl3SvLUP28pL0zQEw8Wifz2Nk+OwUsDf9E5PlHuDD6Ps1qpiVqG
|
||||
BFHQRgtAoC8CgYAd/fwuYmp63VVqSpWcQT9sGO4nuoE8ExzMo8kLdEKSHaBvCYMC
|
||||
88sJ7qXd72SeC8LljOknjk9BLdKoMx2KuX07qtrTn1srbtg86rpUQJGJsFsxuhel
|
||||
70Y+mKGlrNt5fynfx6R1BjCNWkO0AKxqyc0h4CeUXc+ME1i7+dqUn3bRkQKBgQDZ
|
||||
LjqIp4XbsCRb6MMuIMVGLQi86dxA7mkHrWmz9k0nx5S9P8uVIo5tzb/b4SH2i64w
|
||||
6PJmE+heNHwbQ4Az+mZYORN9nYWLP/OlPEBJ6drhyuIktKD/1M3OZpa/Siz7uww5
|
||||
TRL90BxivKE4c/OHq3cFEH7J61oMStdBSlKL/Y1zawKBgAUOe/bE28jglBMGlhUt
|
||||
J3YTiwSMgu2JaOaa4IZwu5MKjI7sUob98ztdnYK7o5E1ingk6Bb+Kct0P3dU29mC
|
||||
MqtrTbrU2RwyCC8p2tj6PatL6okhCleqRqTHFqtX8SfgqKA5sOozBQd9hDVqoDBS
|
||||
ogq0KJfKew73/8Ms0Ji8M2xK
|
||||
-----END PRIVATE KEY-----
|
||||
23
test/configs/certs/rdns/client-d.pem
Normal file
23
test/configs/certs/rdns/client-d.pem
Normal file
@@ -0,0 +1,23 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDwzCCAqugAwIBAgIUbcKrNnZy7OU0rvNXp+A4KhDg3bIwDQYJKoZIhvcNAQEL
|
||||
BQAwajELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFDASBgNVBAcM
|
||||
C0xvcyBBbmdlbGVzMQ0wCwYDVQQKDAROQVRTMQ0wCwYDVQQLDAROQVRTMRIwEAYD
|
||||
VQQDDAlsb2NhbGhvc3QwHhcNMjAwOTAxMTkwMTQ5WhcNMjUwODMxMTkwMTQ5WjCB
|
||||
lDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRQwEgYDVQQHDAtMb3MgQW5nZWxl
|
||||
czENMAsGA1UECwwETkFUUzENMAsGA1UECgwETkFUUzEWMBQGA1UEAwwNKi5leGFt
|
||||
cGxlLmNvbTEXMBUGCgmSJomT8ixkARkWB2V4YW1wbGUxEzARBgoJkiaJk/IsZAEZ
|
||||
FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDay//0l8jlnU/v
|
||||
DtoyNKmxIC+goq/ooiBnOCkuRyGus1yA70V4MWYEzi/N76sVw3MHXMpWxcYvxNO7
|
||||
+8szsgXVQF6G1pxnm2e3/Xp+lZWVHkjR/6h6dQpAwgyIkXCsIN/4+TYIn1T3/hdx
|
||||
x4rYgeK1YO47lUSLskGNcU76nmqhooboWmWW8NYFJszvS0VUMXvMpaOmrFaIwx4e
|
||||
9nDt8n2H7lyAq7cqo7sHv9ytT6xSPV2ok8swrbD9Ez/lyT+F/WbHkmP1HCK5365d
|
||||
FmH3STH1E2l2lasTC4p92RK7NiWzzyIgOelcT6TblZxxJSyH8KfjaHcxFcJhkHjc
|
||||
zY3h/K0fAgMBAAGjNjA0MDIGA1UdEQQrMCmCCWxvY2FsaG9zdIILZXhhbXBsZS5j
|
||||
b22CD3d3dy5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAhXTt3ByBsVZz
|
||||
zwTm7rJEl4oOXuIjzX4aeCBboN3FzSFYY9MYD9NNIKZdhSxPpSIV7p9DHfUX2Wlp
|
||||
o1kKntjHSdDSOpIihGds1Oi5kYbrKpB7uj81KFjmn52as7yf5prX2CCN9RImrNqP
|
||||
NkJiCR+B6KELeqvRMoHWg11kGZnhJ1/oAKholHgaILRNTHd3QXGvFBdm6JEYIQkt
|
||||
3Jk8SZIhDM/jxEEOeEK5Mz4BjAaNIbiNVY1yUDF/+OymXfgILq1+dtmaoaxVMMRX
|
||||
68E538NyCS7Bg56AhxwlhhFNVk4r96JRLgZTxb+CbkkZ32DNkkp0k5h/JIpkKQRH
|
||||
OwJkyC9OZQ==
|
||||
-----END CERTIFICATE-----
|
||||
129
test/tls_test.go
129
test/tls_test.go
@@ -1138,19 +1138,49 @@ func TestTLSClientAuthWithRDNSequence(t *testing.T) {
|
||||
err error
|
||||
rerr error
|
||||
}{
|
||||
// To generate certs for these tests:
|
||||
//
|
||||
// ```
|
||||
// openssl req -newkey rsa:2048 -nodes -keyout client-$CLIENT_ID.key -subj "/C=US/ST=CA/L=Los Angeles/OU=NATS/O=NATS/CN=*.example.com/DC=example/DC=com" -addext extendedKeyUsage=clientAuth -out client-$CLIENT_ID.csr
|
||||
// openssl x509 -req -extfile <(printf "subjectAltName=DNS:localhost,DNS:example.com,DNS:www.example.com") -days 1825 -in client-$CLIENT_ID.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out client-$CLIENT_ID.pem
|
||||
// ```
|
||||
//
|
||||
// To confirm subject from cert:
|
||||
//
|
||||
// ```
|
||||
// openssl x509 -in client-$CLIENT_ID.pem -text | grep Subject:
|
||||
// ```
|
||||
//
|
||||
{
|
||||
"connect with tls using full RDN sequence",
|
||||
`
|
||||
port: -1
|
||||
%s
|
||||
|
||||
authorization {
|
||||
users = [
|
||||
{ user = "CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=California,C=US,DC=foo1,DC=foo2" }
|
||||
]
|
||||
}
|
||||
`,
|
||||
// C = US, ST = California, L = Los Angeles, O = NATS, OU = NATS, CN = localhost, DC = foo1, DC = foo2
|
||||
nats.ClientCert("./configs/certs/rdns/client-a.pem", "./configs/certs/rdns/client-a.key"),
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"connect with tls using full RDN sequence in original order",
|
||||
`
|
||||
port: -1
|
||||
%s
|
||||
|
||||
authorization {
|
||||
users = [
|
||||
{ user = "CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=California,C=US,DC=foo1,DC=foo2" }
|
||||
{ user = "DC=foo2,DC=foo1,CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=California,C=US" }
|
||||
]
|
||||
}
|
||||
`,
|
||||
// C=US/ST=California/L=Los Angeles/O=NATS/OU=NATS/CN=localhost/DC=foo1/DC=foo2
|
||||
// C = US, ST = California, L = Los Angeles, O = NATS, OU = NATS, CN = localhost, DC = foo1, DC = foo2
|
||||
nats.ClientCert("./configs/certs/rdns/client-a.pem", "./configs/certs/rdns/client-a.key"),
|
||||
nil,
|
||||
nil,
|
||||
@@ -1163,13 +1193,14 @@ func TestTLSClientAuthWithRDNSequence(t *testing.T) {
|
||||
|
||||
authorization {
|
||||
users = [
|
||||
{ user = "DC=foo2,DC=foo1,CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=California,C=US" },
|
||||
{ user = "CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=California,C=US,DC=foo1,DC=foo2" },
|
||||
{ user = "CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=California,C=US",
|
||||
permissions = { subscribe = { deny = ">" }} }
|
||||
]
|
||||
}
|
||||
`,
|
||||
// C=US/ST=California/L=Los Angeles/O=NATS/OU=NATS/CN=localhost
|
||||
// C = US, ST = California, L = Los Angeles, O = NATS, OU = NATS, CN = localhost
|
||||
nats.ClientCert("./configs/certs/rdns/client-b.pem", "./configs/certs/rdns/client-b.key"),
|
||||
nil,
|
||||
errors.New("nats: timeout"),
|
||||
@@ -1182,17 +1213,19 @@ func TestTLSClientAuthWithRDNSequence(t *testing.T) {
|
||||
|
||||
authorization {
|
||||
users = [
|
||||
{ user = "DC=foo2,DC=foo1,CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=California,C=US" }
|
||||
{ user = "CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=California,C=US,DC=foo1,DC=foo2" }
|
||||
{ user = "CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=California,C=US"},
|
||||
]
|
||||
}
|
||||
`,
|
||||
// Cert is:
|
||||
//
|
||||
// C=US/ST=California/L=Los Angeles/O=NATS/OU=NATS/CN=localhost/DC=foo3/DC=foo4
|
||||
// C = US, ST = California, L = Los Angeles, O = NATS, OU = NATS, CN = localhost, DC = foo3, DC = foo4
|
||||
//
|
||||
// but it will actually match the 2nd user so will not get an error (backwards compatible behavior)
|
||||
// but it will actually match the user without DCs so will not get an error:
|
||||
//
|
||||
// CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=California,C=US
|
||||
// C = US, ST = California, L = Los Angeles, O = NATS, OU = NATS, CN = localhost
|
||||
//
|
||||
nats.ClientCert("./configs/certs/rdns/client-c.pem", "./configs/certs/rdns/client-c.key"),
|
||||
nil,
|
||||
@@ -1206,16 +1239,96 @@ func TestTLSClientAuthWithRDNSequence(t *testing.T) {
|
||||
|
||||
authorization {
|
||||
users = [
|
||||
{ user = "CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=California,C=US,DC=foo1,DC=foo2" }
|
||||
{ user = "CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=California,C=US,DC=foo1,DC=foo2" },
|
||||
{ user = "DC=foo2,DC=foo1,CN=localhost,OU=NATS,O=NATS,L=Los Angeles,ST=California,C=US" }
|
||||
]
|
||||
}
|
||||
`,
|
||||
// C=US/ST=California/L=Los Angeles/O=NATS/OU=NATS/CN=localhost/DC=foo3/DC=foo4
|
||||
// C = US, ST = California, L = Los Angeles, O = NATS, OU = NATS, CN = localhost, DC = foo3, DC = foo4
|
||||
//
|
||||
nats.ClientCert("./configs/certs/rdns/client-c.pem", "./configs/certs/rdns/client-c.key"),
|
||||
errors.New("nats: Authorization Violation"),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"connect with tls and RDN sequence with space after comma not should matter",
|
||||
`
|
||||
port: -1
|
||||
%s
|
||||
|
||||
authorization {
|
||||
users = [
|
||||
{ user = "DC=foo2, DC=foo1, CN=localhost, OU=NATS, O=NATS, L=Los Angeles, ST=California, C=US" }
|
||||
]
|
||||
}
|
||||
`,
|
||||
// C=US/ST=California/L=Los Angeles/O=NATS/OU=NATS/CN=localhost/DC=foo1/DC=foo2
|
||||
//
|
||||
nats.ClientCert("./configs/certs/rdns/client-a.pem", "./configs/certs/rdns/client-a.key"),
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"connect with tls and full RDN sequence respects order",
|
||||
`
|
||||
port: -1
|
||||
%s
|
||||
|
||||
authorization {
|
||||
users = [
|
||||
{ user = "DC=com, DC=example, CN=*.example.com, O=NATS, OU=NATS, L=Los Angeles, ST=CA, C=US" }
|
||||
]
|
||||
}
|
||||
`,
|
||||
//
|
||||
// C = US, ST = CA, L = Los Angeles, OU = NATS, O = NATS, CN = *.example.com, DC = example, DC = com
|
||||
//
|
||||
nats.ClientCert("./configs/certs/rdns/client-d.pem", "./configs/certs/rdns/client-d.key"),
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"connect with tls and full RDN sequence with added domainComponents and spaces also matches",
|
||||
`
|
||||
port: -1
|
||||
%s
|
||||
|
||||
authorization {
|
||||
users = [
|
||||
{ user = "CN=*.example.com,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US,DC=example,DC=com" }
|
||||
]
|
||||
}
|
||||
`,
|
||||
//
|
||||
// C = US, ST = CA, L = Los Angeles, OU = NATS, O = NATS, CN = *.example.com, DC = example, DC = com
|
||||
//
|
||||
nats.ClientCert("./configs/certs/rdns/client-d.pem", "./configs/certs/rdns/client-d.key"),
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"connect with tls and full RDN sequence with correct order takes precedence over others matches",
|
||||
`
|
||||
port: -1
|
||||
%s
|
||||
|
||||
authorization {
|
||||
users = [
|
||||
{ user = "CN=*.example.com,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US,DC=example,DC=com",
|
||||
permissions = { subscribe = { deny = ">" }} }
|
||||
{ user = "DC=com,DC=example,CN=*.example.com,O=NATS,OU=NATS,L=Los Angeles,ST=CA,C=US" }
|
||||
{ user = "CN=*.example.com,OU=NATS,O=NATS,L=Los Angeles,ST=CA,C=US",
|
||||
permissions = { subscribe = { deny = ">" }} }
|
||||
]
|
||||
}
|
||||
`,
|
||||
//
|
||||
// C = US, ST = CA, L = Los Angeles, OU = NATS, O = NATS, CN = *.example.com, DC = example, DC = com
|
||||
//
|
||||
nats.ClientCert("./configs/certs/rdns/client-d.pem", "./configs/certs/rdns/client-d.key"),
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
content := fmt.Sprintf(test.config, `
|
||||
|
||||
Reference in New Issue
Block a user