Merge pull request #3387 from nats-io/fix_3317

[ADDED] Monitoring: TLS Peer Certificates in Connz when auth is on
This commit is contained in:
Ivan Kozlovic
2022-08-24 14:28:01 -06:00
committed by GitHub
3 changed files with 119 additions and 37 deletions

View File

@@ -14,7 +14,10 @@
package server
import (
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"encoding/json"
"fmt"
"net"
@@ -108,38 +111,46 @@ const (
// ConnInfo has detailed information on a per connection basis.
type ConnInfo struct {
Cid uint64 `json:"cid"`
Kind string `json:"kind,omitempty"`
Type string `json:"type,omitempty"`
IP string `json:"ip"`
Port int `json:"port"`
Start time.Time `json:"start"`
LastActivity time.Time `json:"last_activity"`
Stop *time.Time `json:"stop,omitempty"`
Reason string `json:"reason,omitempty"`
RTT string `json:"rtt,omitempty"`
Uptime string `json:"uptime"`
Idle string `json:"idle"`
Pending int `json:"pending_bytes"`
InMsgs int64 `json:"in_msgs"`
OutMsgs int64 `json:"out_msgs"`
InBytes int64 `json:"in_bytes"`
OutBytes int64 `json:"out_bytes"`
NumSubs uint32 `json:"subscriptions"`
Name string `json:"name,omitempty"`
Lang string `json:"lang,omitempty"`
Version string `json:"version,omitempty"`
TLSVersion string `json:"tls_version,omitempty"`
TLSCipher string `json:"tls_cipher_suite,omitempty"`
AuthorizedUser string `json:"authorized_user,omitempty"`
Account string `json:"account,omitempty"`
Subs []string `json:"subscriptions_list,omitempty"`
SubsDetail []SubDetail `json:"subscriptions_list_detail,omitempty"`
JWT string `json:"jwt,omitempty"`
IssuerKey string `json:"issuer_key,omitempty"`
NameTag string `json:"name_tag,omitempty"`
Tags jwt.TagList `json:"tags,omitempty"`
MQTTClient string `json:"mqtt_client,omitempty"` // This is the MQTT client id
Cid uint64 `json:"cid"`
Kind string `json:"kind,omitempty"`
Type string `json:"type,omitempty"`
IP string `json:"ip"`
Port int `json:"port"`
Start time.Time `json:"start"`
LastActivity time.Time `json:"last_activity"`
Stop *time.Time `json:"stop,omitempty"`
Reason string `json:"reason,omitempty"`
RTT string `json:"rtt,omitempty"`
Uptime string `json:"uptime"`
Idle string `json:"idle"`
Pending int `json:"pending_bytes"`
InMsgs int64 `json:"in_msgs"`
OutMsgs int64 `json:"out_msgs"`
InBytes int64 `json:"in_bytes"`
OutBytes int64 `json:"out_bytes"`
NumSubs uint32 `json:"subscriptions"`
Name string `json:"name,omitempty"`
Lang string `json:"lang,omitempty"`
Version string `json:"version,omitempty"`
TLSVersion string `json:"tls_version,omitempty"`
TLSCipher string `json:"tls_cipher_suite,omitempty"`
TLSPeerCerts []*TLSPeerCert `json:"tls_peer_certs,omitempty"`
AuthorizedUser string `json:"authorized_user,omitempty"`
Account string `json:"account,omitempty"`
Subs []string `json:"subscriptions_list,omitempty"`
SubsDetail []SubDetail `json:"subscriptions_list_detail,omitempty"`
JWT string `json:"jwt,omitempty"`
IssuerKey string `json:"issuer_key,omitempty"`
NameTag string `json:"name_tag,omitempty"`
Tags jwt.TagList `json:"tags,omitempty"`
MQTTClient string `json:"mqtt_client,omitempty"` // This is the MQTT client id
}
// TLSPeerCert contains basic information about a TLS peer certificate
type TLSPeerCert struct {
Subject string `json:"subject,omitempty"`
SubjectPKISha256 string `json:"spki_sha256,omitempty"`
CertSha256 string `json:"cert_sha256,omitempty"`
}
// DefaultConnListSize is the default size of the connection list.
@@ -394,7 +405,7 @@ func (s *Server) Connz(opts *ConnzOptions) (*Connz, error) {
for _, client := range openClients {
client.mu.Lock()
ci := &conns[i]
ci.fill(client, client.nc, c.Now)
ci.fill(client, client.nc, c.Now, auth)
// Fill in subscription data if requested.
if len(client.subs) > 0 {
if subsDet {
@@ -524,7 +535,7 @@ func (s *Server) Connz(opts *ConnzOptions) (*Connz, error) {
// Fills in the ConnInfo from the client.
// client should be locked.
func (ci *ConnInfo) fill(client *client, nc net.Conn, now time.Time) {
func (ci *ConnInfo) fill(client *client, nc net.Conn, now time.Time, auth bool) {
ci.Cid = client.cid
ci.MQTTClient = client.getMQTTClientID()
ci.Kind = client.kindString()
@@ -554,6 +565,9 @@ func (ci *ConnInfo) fill(client *client, nc net.Conn, now time.Time) {
cs := conn.ConnectionState()
ci.TLSVersion = tlsVersion(cs.Version)
ci.TLSCipher = tlsCipher(cs.CipherSuite)
if auth && len(cs.PeerCertificates) > 0 {
ci.TLSPeerCerts = makePeerCerts(cs.PeerCertificates)
}
}
if client.port != 0 {
@@ -562,6 +576,18 @@ func (ci *ConnInfo) fill(client *client, nc net.Conn, now time.Time) {
}
}
func makePeerCerts(pc []*x509.Certificate) []*TLSPeerCert {
res := make([]*TLSPeerCert, len(pc))
for i, c := range pc {
tmp := sha256.Sum256(c.RawSubjectPublicKeyInfo)
ssha := hex.EncodeToString(tmp[:])
tmp = sha256.Sum256(c.Raw)
csha := hex.EncodeToString(tmp[:])
res[i] = &TLSPeerCert{Subject: c.Subject.String(), SubjectPKISha256: ssha, CertSha256: csha}
}
return res
}
// Assume lock is held
func (c *client) getRTT() time.Duration {
if c.rtt == 0 {
@@ -1869,7 +1895,7 @@ func createOutboundRemoteGatewayz(c *client, opts *GatewayzOptions, now time.Tim
rgw.IsConfigured = !c.gw.cfg.isImplicit()
}
rgw.Connection = &ConnInfo{}
rgw.Connection.fill(c, c.nc, now)
rgw.Connection.fill(c, c.nc, now, false)
name = c.gw.name
}
c.mu.Unlock()
@@ -1953,7 +1979,7 @@ func (s *Server) createInboundsRemoteGatewayz(opts *GatewayzOptions, now time.Ti
rgw.Accounts = createInboundAccountsGatewayz(opts, c.gw)
}
rgw.Connection = &ConnInfo{}
rgw.Connection.fill(c, c.nc, now)
rgw.Connection.fill(c, c.nc, now, false)
igws = append(igws, rgw)
m[c.gw.name] = igws
}

View File

@@ -2186,6 +2186,62 @@ func TestConnzTLSCfg(t *testing.T) {
}
}
func TestConnzTLSPeerCerts(t *testing.T) {
resetPreviousHTTPConnections()
tc := &TLSConfigOpts{}
tc.CertFile = "../test/configs/certs/server-cert.pem"
tc.KeyFile = "../test/configs/certs/server-key.pem"
tc.CaFile = "../test/configs/certs/ca.pem"
tc.Verify = true
tc.Timeout = 2.0
var err error
opts := DefaultMonitorOptions()
opts.TLSConfig, err = GenTLSConfig(tc)
require_NoError(t, err)
s := RunServer(opts)
defer s.Shutdown()
nc := natsConnect(t, s.ClientURL(),
nats.ClientCert("../test/configs/certs/client-cert.pem", "../test/configs/certs/client-key.pem"),
nats.RootCAs("../test/configs/certs/ca.pem"))
defer nc.Close()
endpoint := fmt.Sprintf("http://%s:%d/connz", opts.HTTPHost, s.MonitorAddr().Port)
for mode := 0; mode < 2; mode++ {
// Without "auth" option, we should not get the details
connz := pollConz(t, s, mode, endpoint, nil)
require_True(t, len(connz.Conns) == 1)
c := connz.Conns[0]
if c.TLSPeerCerts != nil {
t.Fatalf("Did not expect TLSPeerCerts when auth is not specified: %+v", c.TLSPeerCerts)
}
// Now specify "auth" option
connz = pollConz(t, s, mode, endpoint+"?auth=1", &ConnzOptions{Username: true})
require_True(t, len(connz.Conns) == 1)
c = connz.Conns[0]
if c.TLSPeerCerts == nil {
t.Fatal("Expected TLSPeerCerts to be set, was not")
} else if len(c.TLSPeerCerts) != 1 {
t.Fatalf("Unexpected peer certificates: %+v", c.TLSPeerCerts)
} else {
for _, d := range c.TLSPeerCerts {
if d.Subject != "CN=localhost,OU=nats.io,O=Synadia,ST=California,C=US" {
t.Fatalf("Unexpected subject: %s", d.Subject)
}
if len(d.SubjectPKISha256) != 64 {
t.Fatalf("Unexpected spki_sha256: %s", d.SubjectPKISha256)
}
if len(d.CertSha256) != 64 {
t.Fatalf("Unexpected cert_sha256: %s", d.CertSha256)
}
}
}
}
}
func TestServerIDs(t *testing.T) {
s := runMonitorServer()
defer s.Shutdown()

View File

@@ -2662,7 +2662,7 @@ func (s *Server) saveClosedClient(c *client, nc net.Conn, reason ClosedState) {
c.mu.Lock()
cc := &closedClient{}
cc.fill(c, nc, now)
cc.fill(c, nc, now, false)
cc.Stop = &now
cc.Reason = reason.String()