mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-17 03:24:40 -07:00
Merge pull request #865 from nats-io/tls_user
Support for mapping user from TLS client certificate
This commit is contained in:
@@ -261,6 +261,7 @@ func (s *Server) isClientAuthorized(c *client) bool {
|
||||
authorization := s.opts.Authorization
|
||||
username := s.opts.Username
|
||||
password := s.opts.Password
|
||||
tlsMap := s.opts.TLSMap
|
||||
s.optsMu.RUnlock()
|
||||
|
||||
// Check custom auth first, then jwts, then nkeys, then multiple users, then token, then single user/pass.
|
||||
@@ -318,11 +319,48 @@ func (s *Server) isClientAuthorized(c *client) bool {
|
||||
s.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
} else if hasUsers && c.opts.Username != "" {
|
||||
user, ok = s.users[c.opts.Username]
|
||||
if !ok {
|
||||
s.mu.Unlock()
|
||||
return false
|
||||
} else if hasUsers {
|
||||
// Check if we are tls verify and are mapping users from the client_certificate
|
||||
if tlsMap {
|
||||
tlsState := c.GetTLSConnectionState()
|
||||
if tlsState == nil {
|
||||
c.Debugf("User required in cert, no TLS connection state")
|
||||
s.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
if len(tlsState.PeerCertificates) == 0 {
|
||||
c.Debugf("User required in cert, no peer certificates found")
|
||||
s.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
cert := tlsState.PeerCertificates[0]
|
||||
if len(tlsState.PeerCertificates) > 1 {
|
||||
c.Debugf("Multiple peer certificates found, selecting first")
|
||||
}
|
||||
if len(cert.EmailAddresses) == 0 {
|
||||
c.Debugf("User required in cert, none found")
|
||||
s.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
euser := cert.EmailAddresses[0]
|
||||
user, ok = s.users[euser]
|
||||
if !ok {
|
||||
c.Debugf("User in cert [%q], not found", euser)
|
||||
s.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
if len(cert.EmailAddresses) > 1 {
|
||||
c.Debugf("Multiple users found in cert, selecting first [%q]", euser)
|
||||
}
|
||||
if c.opts.Username != "" {
|
||||
s.Warnf("User found in connect proto, but user required from cert - %v", c)
|
||||
}
|
||||
} else if c.opts.Username != "" {
|
||||
user, ok = s.users[c.opts.Username]
|
||||
if !ok {
|
||||
s.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
@@ -612,33 +612,6 @@ func TestConfigCheck(t *testing.T) {
|
||||
errorLine: 14,
|
||||
errorPos: 11,
|
||||
},
|
||||
{
|
||||
name: "when account user within accounts block has no user, pass",
|
||||
config: `
|
||||
accounts {
|
||||
|
||||
#
|
||||
# synadia > nats.io, cncf
|
||||
#
|
||||
synadia {
|
||||
# SAADJL5XAEM6BDYSWDTGVILJVY54CQXZM5ZLG4FRUAKB62HWRTPNSGXOHA
|
||||
nkey = "AC5GRL36RQV7MJ2GT6WQSCKDKJKYTK4T2LGLWJ2SEJKRDHFOQQWGGFQL"
|
||||
|
||||
users [
|
||||
{
|
||||
}
|
||||
]
|
||||
|
||||
exports = [
|
||||
{ service: "synadia.requests", accounts: [nats, cncf] }
|
||||
]
|
||||
}
|
||||
}
|
||||
`,
|
||||
err: errors.New(`User entry requires a user and a password`),
|
||||
errorLine: 13,
|
||||
errorPos: 10,
|
||||
},
|
||||
{
|
||||
name: "when accounts block has unknown fields",
|
||||
config: `
|
||||
|
||||
@@ -123,6 +123,7 @@ type Options struct {
|
||||
TLSTimeout float64 `json:"tls_timeout"`
|
||||
TLS bool `json:"-"`
|
||||
TLSVerify bool `json:"-"`
|
||||
TLSMap bool `json:"-"`
|
||||
TLSCert string `json:"-"`
|
||||
TLSKey string `json:"-"`
|
||||
TLSCaCert string `json:"-"`
|
||||
@@ -227,6 +228,7 @@ type TLSConfigOpts struct {
|
||||
KeyFile string
|
||||
CaFile string
|
||||
Verify bool
|
||||
Map bool
|
||||
Timeout float64
|
||||
Ciphers []uint16
|
||||
CurvePreferences []tls.CurveID
|
||||
@@ -238,10 +240,11 @@ TLS configuration is specified in the tls section of a configuration file:
|
||||
e.g.
|
||||
|
||||
tls {
|
||||
cert_file: "./certs/server-cert.pem"
|
||||
key_file: "./certs/server-key.pem"
|
||||
ca_file: "./certs/ca.pem"
|
||||
verify: true
|
||||
cert_file: "./certs/server-cert.pem"
|
||||
key_file: "./certs/server-key.pem"
|
||||
ca_file: "./certs/ca.pem"
|
||||
verify: true
|
||||
verify_and_map: true
|
||||
|
||||
cipher_suites: [
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
@@ -457,6 +460,7 @@ func (o *Options) ProcessConfigFile(configFile string) error {
|
||||
continue
|
||||
}
|
||||
o.TLSTimeout = tc.Timeout
|
||||
o.TLSMap = tc.Map
|
||||
case "write_deadline":
|
||||
wd, ok := v.(string)
|
||||
if ok {
|
||||
@@ -1610,9 +1614,9 @@ func parseUsers(mv interface{}, opts *Options, errors *[]error, warnings *[]erro
|
||||
}
|
||||
}
|
||||
|
||||
// Check to make sure we have at least username and password if defined.
|
||||
if nkey.Nkey == "" && (user.Username == "" || user.Password == "") {
|
||||
return nil, nil, &configErr{tk, fmt.Sprintf("User entry requires a user and a password")}
|
||||
// Check to make sure we have at least an nkey or username <password> defined.
|
||||
if nkey.Nkey == "" && user.Username == "" {
|
||||
return nil, nil, &configErr{tk, fmt.Sprintf("User entry requires a user")}
|
||||
} else if nkey.Nkey != "" {
|
||||
// Make sure the nkey a proper public nkey for a user..
|
||||
if !nkeys.IsValidPublicUserKey(nkey.Nkey) {
|
||||
@@ -1871,6 +1875,13 @@ func parseTLS(v interface{}) (*TLSConfigOpts, error) {
|
||||
return nil, &configErr{tk, fmt.Sprintf("error parsing tls config, expected 'verify' to be a boolean")}
|
||||
}
|
||||
tc.Verify = verify
|
||||
case "verify_and_map":
|
||||
verify, ok := mv.(bool)
|
||||
if !ok {
|
||||
return nil, &configErr{tk, fmt.Sprintf("error parsing tls config, expected 'verify_and_map' to be a boolean")}
|
||||
}
|
||||
tc.Verify = verify
|
||||
tc.Map = verify
|
||||
case "cipher_suites":
|
||||
ra := mv.([]interface{})
|
||||
if len(ra) == 0 {
|
||||
|
||||
29
test/configs/certs/client-id-auth-cert.pem
Normal file
29
test/configs/certs/client-id-auth-cert.pem
Normal file
@@ -0,0 +1,29 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIE7DCCAtSgAwIBAgIJAO+k4G7bNTy5MA0GCSqGSIb3DQEBBQUAMIGLMQswCQYD
|
||||
VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzAR
|
||||
BgNVBAoTCkFwY2VyYSBJbmMxEDAOBgNVBAsTB25hdHMuaW8xEjAQBgNVBAMTCWxv
|
||||
Y2FsaG9zdDEcMBoGCSqGSIb3DQEJARYNZGVyZWtAbmF0cy5pbzAeFw0xODEyMjAw
|
||||
MDQwMjZaFw0xOTAxMTkwMDQwMjZaMIGaMQswCQYDVQQGEwJVUzELMAkGA1UECAwC
|
||||
Q0ExFDASBgNVBAcMC0xvcyBBbmdlbGVzMSQwIgYDVQQKDBtTeW5hZGlhIENvbW11
|
||||
bmljYXRpb25zIEluYy4xEDAOBgNVBAsMB05BVFMuaW8xEjAQBgNVBAMMCWxvY2Fs
|
||||
aG9zdDEcMBoGCSqGSIb3DQEJARYNZGVyZWtAbmF0cy5pbzCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBAJzsochRXgyF2qQsVNfL/H84pvhDYTqMarYSijUe
|
||||
CHUxVi4nNxQsr9Z0SrtzjaeiJ9uOH1vDpzTevsI1Akv6tUiZxgFhxUv79cbO1WNF
|
||||
fzwXANzDHwOxyEnSSl3X5HN+L67cgc+chof7tc+c1KqVYXPpJy0hSvCY6o1W0IbE
|
||||
4KeR7m6kNh6jPvQAPz00jZeUVIenE3zuMrLeLnTqdhWrZwjme8FybH1sxMQu1T3v
|
||||
SI0mhl7Xmkct0UTH11dJ4azrgmddxI0ZBIXAlQA3PTY4QyKiqR4V95wkEwn5hq3a
|
||||
XCB5YT9XHYGI/An8Dkotqn/7fjQ2h7BqW6qevQOu8ce52B0CAwEAAaNCMEAwKQYD
|
||||
VR0RBCIwIIIJbG9jYWxob3N0hwR/AAABgQ1kZXJla0BuYXRzLmlvMBMGA1UdJQQM
|
||||
MAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4ICAQBbBvhUFoVOgP+1qTUMIYCQ
|
||||
hjtLOogW/tOXO6FIxFr3WEtf5WOc+Ko/FiwfsLyS0HTXvz7C7S4KRtgXLBVRjSy+
|
||||
FJdUKiCVeuZ3ZjUl2b7rLj/36N+cClz0Ipy7FKyN0ww1sscWDL1F1E65tv+LnnQ1
|
||||
p0dDIsHEbz968t0hVoUzPs1DJNKdU1K482mTPjjTbwEvt59keITRy2rV+vKxt1n2
|
||||
szfovqllHDGrvlZnT7kGYTvWHa3NkL5AdLmVb51PFcN+LpWykyQsZN1Zr5x5GbHj
|
||||
UQVhrnJUJOWtI5HmXfhlwUZZtUezgAvpTEkjqT+yrL1PbN61uvBXHI8piZG3Vnu1
|
||||
QS/KT2aptmb/W5KH2EkrldvVKYOSyo0lDxBu8ZGYQCJnaI+vtpCIVZSv9LkMJNDf
|
||||
NQSCjwoVfqSlaOg/b+HMaDmOaGtpYyHEJ45WgtZxU+GYdJX9XLgef/1WyG9wP9fg
|
||||
/Uki2D5QqRhWZ1SA3C8zItcfLhfVbOj3RX69FRO8RcxEjLOJzQztbOdpesbEMFmV
|
||||
rBYnOP4N4lBtou0f8m5TZu8tbTk1UypdS/TqZNPk/XC/xU/oj+XRcOxg7fK5MJLw
|
||||
xKF/EjGP5K81KnE140uvaeoVkD5MbxFygUuvArXzuiLEf5Kydf9zT/AymNc6ySEi
|
||||
kt4g8+vpo1BX4/lTyI6dKQ==
|
||||
-----END CERTIFICATE-----
|
||||
28
test/configs/certs/client-id-auth-key.pem
Normal file
28
test/configs/certs/client-id-auth-key.pem
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCc7KHIUV4Mhdqk
|
||||
LFTXy/x/OKb4Q2E6jGq2Eoo1Hgh1MVYuJzcULK/WdEq7c42noifbjh9bw6c03r7C
|
||||
NQJL+rVImcYBYcVL+/XGztVjRX88FwDcwx8DschJ0kpd1+Rzfi+u3IHPnIaH+7XP
|
||||
nNSqlWFz6SctIUrwmOqNVtCGxOCnke5upDYeoz70AD89NI2XlFSHpxN87jKy3i50
|
||||
6nYVq2cI5nvBcmx9bMTELtU970iNJoZe15pHLdFEx9dXSeGs64JnXcSNGQSFwJUA
|
||||
Nz02OEMioqkeFfecJBMJ+Yat2lwgeWE/Vx2BiPwJ/A5KLap/+340Noewaluqnr0D
|
||||
rvHHudgdAgMBAAECggEAU7Rtf7rXXfqwa84x24I1x27isZ7PaDmqWkB9dGP2wvx0
|
||||
Kd5FJP/JM62Ih4DV2MtIU3b2By7QLAV8338DIKA0vus3kVkjsqpNqaR+cpuJiPYE
|
||||
Qb3a90+HtMj1XiVg+LIgoTeCDspBgJulmX7gebWA7CE8Ani1zqziwE3EoX63DuqU
|
||||
uViC4bXKNHV3f7+rtImyHR6i7LVwS6waaOXP9I79vBncNnUGe0+JDtnFDu8wNO0k
|
||||
domaHrf2lRgSHgsIppeNZ5zOEJ3YB2BORxbI2+kDZtYB8cavH5YLJw/mdha1okuI
|
||||
J4JLd3F7WQz1hX6/noMZxRquWKL7NlTcz8mCyL31yQKBgQDP/NlMPVd+s9Vxrf44
|
||||
6KBV2xYRPY3O2eBgVE6EbKbYFhg3cBKb96MB98AGQ41gYmbgvI7dv2jT3PT7EJM2
|
||||
xJpP4WuJYaQ8fCL1onyk4WgGbOLdGnVHPmFi2Swjt9UJay64yDknVPpqSnBvGLmw
|
||||
tl3XsfWrTpInjtXeDVwk9h0JUwKBgQDBJipgIaVqUeQ1dwXbJe2atUpAv1oVt4ll
|
||||
omqUiY0LLwidZLZ4CvNlsK/eCzWFTf30yLcTYTDg5Xuav5+rBwOWYwN8lWv1rkAn
|
||||
Qew3cSPXpxP97gjgAhirnOmVecTaTzqPBG82F30idoC4jpX9lhmdBD/PdGQ4ySLK
|
||||
N++ReIO6zwKBgHavnPiKkKE20fhbB5VF+ijEKqWP8Jo3bnjJ4zxiHBt3ED6ib5wd
|
||||
BiIbVLK+XbDAtmBMeWJE1fcAQbP7U2aPbldjFVCCLYxuciylmmckUY5JGHR/oqkT
|
||||
CdO0hiGjx6fmR/UeHK87KOL6s4pSG7ShfI+Xd89XuMNmGNjr2sckwpENAoGBAJy5
|
||||
lUTvyENfM6fWbmAGhKg2Vov3OOfKR6i6g3UHr/TVM05TfGQnrpxjJDEuMz15rYnE
|
||||
nBkTkg/K5eMJfkvOozCSIzAiJrnxrIiuSzgpjAXewrAXSAhMayxFZJwvdHYYN9H4
|
||||
rSzdHmKqeYRH3pkoBJyN6CEztmcFfj9L6A7IFUutAoGAJwR9nQAbRnxVKYZgXef+
|
||||
QD7/1QIhB97CWag4Gc2VyPdEoqM6z2FzOFuSjMUYRbR4W/ksNdns5Mb1MIQYLmqv
|
||||
dzAjfZeaGril0QQeaYcWct41MKjKzyroOSRKNOs1Ae/Dd0KsUbWjjMoQHmBffcjS
|
||||
40p5iL1eAK5f4haKBVo+uKw=
|
||||
-----END PRIVATE KEY-----
|
||||
24
test/configs/tls_cert_id.conf
Normal file
24
test/configs/tls_cert_id.conf
Normal file
@@ -0,0 +1,24 @@
|
||||
# TLS config file
|
||||
# We require client certs and pull the user from the cert itself.
|
||||
|
||||
listen: 127.0.0.1:9333
|
||||
|
||||
tls {
|
||||
# Server cert
|
||||
cert_file: "./configs/certs/server-cert.pem"
|
||||
# Server private key
|
||||
key_file: "./configs/certs/server-key.pem"
|
||||
# Specified time for handshake to complete
|
||||
timeout: 2
|
||||
# Optional certificate authority for clients
|
||||
ca_file: "./configs/certs/ca.pem"
|
||||
# Require a client certificate and map user id from certificate
|
||||
verify_and_map: true
|
||||
}
|
||||
|
||||
# User authenticated from above in certificate.
|
||||
authorization {
|
||||
users = [
|
||||
{user: derek@nats.io, permissions: { publish:"foo" }}
|
||||
]
|
||||
}
|
||||
@@ -117,6 +117,19 @@ func TestTLSClientCertificate(t *testing.T) {
|
||||
defer nc.Close()
|
||||
}
|
||||
|
||||
func TestTLSClientCertificateHasUserID(t *testing.T) {
|
||||
srv, opts := RunServerWithConfig("./configs/tls_cert_id.conf")
|
||||
defer srv.Shutdown()
|
||||
nurl := fmt.Sprintf("tls://%s:%d", opts.Host, opts.Port)
|
||||
nc, err := nats.Connect(nurl,
|
||||
nats.ClientCert("./configs/certs/client-id-auth-cert.pem", "./configs/certs/client-id-auth-key.pem"),
|
||||
nats.RootCAs("./configs/certs/ca.pem"))
|
||||
if err != nil {
|
||||
t.Fatalf("Expected to connect, got %v", err)
|
||||
}
|
||||
defer nc.Close()
|
||||
}
|
||||
|
||||
func TestTLSVerifyClientCertificate(t *testing.T) {
|
||||
srv, opts := RunServerWithConfig("./configs/tlsverify_noca.conf")
|
||||
defer srv.Shutdown()
|
||||
|
||||
Reference in New Issue
Block a user