Skip to content

Commit

Permalink
Merge pull request #865 from nats-io/tls_user
Browse files Browse the repository at this point in the history
Support for mapping user from TLS client certificate
  • Loading branch information
derekcollison committed Dec 20, 2018
2 parents 91fa422 + 7978d8e commit f10020e
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 39 deletions.
48 changes: 43 additions & 5 deletions server/auth.go
Expand Up @@ -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.
Expand Down Expand Up @@ -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()
Expand Down
27 changes: 0 additions & 27 deletions server/config_check_test.go
Expand Up @@ -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: `
Expand Down
25 changes: 18 additions & 7 deletions server/opts.go
Expand Up @@ -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:"-"`
Expand Down Expand Up @@ -227,6 +228,7 @@ type TLSConfigOpts struct {
KeyFile string
CaFile string
Verify bool
Map bool
Timeout float64
Ciphers []uint16
CurvePreferences []tls.CurveID
Expand All @@ -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",
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down
29 changes: 29 additions & 0 deletions test/configs/certs/client-id-auth-cert.pem
@@ -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 changes: 28 additions & 0 deletions test/configs/certs/client-id-auth-key.pem
@@ -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 changes: 24 additions & 0 deletions test/configs/tls_cert_id.conf
@@ -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" }}
]
}
13 changes: 13 additions & 0 deletions test/tls_test.go
Expand Up @@ -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()
Expand Down

0 comments on commit f10020e

Please sign in to comment.