Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for mapping user from TLS client certificate #865

Merged
merged 1 commit into from
Dec 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 43 additions & 5 deletions server/auth.go
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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