diff --git a/server/monitor.go b/server/monitor.go index 10e3af057d3..ea90c4876a3 100644 --- a/server/monitor.go +++ b/server/monitor.go @@ -1279,6 +1279,7 @@ type Varz struct { SlowConsumersStats *SlowConsumersStats `json:"slow_consumer_stats"` // SlowConsumersStats are statistics about all detected Slow Consumer StaleConnectionStats *StaleConnectionStats `json:"stale_connection_stats,omitempty"` // StaleConnectionStats are statistics about all detected Stale Connections Proxies *ProxiesOptsVarz `json:"proxies,omitempty"` // Proxies hold information about network proxy devices + TLSCertNotAfter time.Time `json:"tls_cert_not_after,omitzero"` // TLSCertNotAfter is the expiration date of the TLS certificate of this server } // JetStreamVarz contains basic runtime information about jetstream @@ -1291,34 +1292,36 @@ type JetStreamVarz struct { // ClusterOptsVarz contains monitoring cluster information type ClusterOptsVarz struct { - Name string `json:"name,omitempty"` // Name is the configured cluster name - Host string `json:"addr,omitempty"` // Host is the host the cluster listens on for connections - Port int `json:"cluster_port,omitempty"` // Port is the port the cluster listens on for connections - AuthTimeout float64 `json:"auth_timeout,omitempty"` // AuthTimeout is the time cluster connections have to complete authentication - URLs []string `json:"urls,omitempty"` // URLs is the list of cluster URLs - TLSTimeout float64 `json:"tls_timeout,omitempty"` // TLSTimeout is how long TLS operations have to complete - TLSRequired bool `json:"tls_required,omitempty"` // TLSRequired indicates if TLS is required for connections - TLSVerify bool `json:"tls_verify,omitempty"` // TLSVerify indicates if full verification of TLS connections is performed - PoolSize int `json:"pool_size,omitempty"` // PoolSize is the configured route connection pool size - WriteDeadline time.Duration `json:"write_deadline,omitempty"` // WriteDeadline is the maximum time writes to sockets have to complete - WriteTimeout string `json:"write_timeout,omitempty"` // WriteTimeout is the closure policy for write deadline errors + Name string `json:"name,omitempty"` // Name is the configured cluster name + Host string `json:"addr,omitempty"` // Host is the host the cluster listens on for connections + Port int `json:"cluster_port,omitempty"` // Port is the port the cluster listens on for connections + AuthTimeout float64 `json:"auth_timeout,omitempty"` // AuthTimeout is the time cluster connections have to complete authentication + URLs []string `json:"urls,omitempty"` // URLs is the list of cluster URLs + TLSTimeout float64 `json:"tls_timeout,omitempty"` // TLSTimeout is how long TLS operations have to complete + TLSRequired bool `json:"tls_required,omitempty"` // TLSRequired indicates if TLS is required for connections + TLSVerify bool `json:"tls_verify,omitempty"` // TLSVerify indicates if full verification of TLS connections is performed + PoolSize int `json:"pool_size,omitempty"` // PoolSize is the configured route connection pool size + WriteDeadline time.Duration `json:"write_deadline,omitempty"` // WriteDeadline is the maximum time writes to sockets have to complete + WriteTimeout string `json:"write_timeout,omitempty"` // WriteTimeout is the closure policy for write deadline errors + TLSCertNotAfter time.Time `json:"tls_cert_not_after,omitzero"` // TLSCertNotAfter is the expiration date of the TLS certificate } // GatewayOptsVarz contains monitoring gateway information type GatewayOptsVarz struct { - Name string `json:"name,omitempty"` // Name is the configured cluster name - Host string `json:"host,omitempty"` // Host is the host the gateway listens on for connections - Port int `json:"port,omitempty"` // Port is the post gateway connections listens on - AuthTimeout float64 `json:"auth_timeout,omitempty"` // AuthTimeout is the time cluster connections have to complete authentication - TLSTimeout float64 `json:"tls_timeout,omitempty"` // TLSTimeout is how long TLS operations have to complete - TLSRequired bool `json:"tls_required,omitempty"` // TLSRequired indicates if TLS is required for connections - TLSVerify bool `json:"tls_verify,omitempty"` // TLSVerify indicates if full verification of TLS connections is performed - Advertise string `json:"advertise,omitempty"` // Advertise is the URL advertised to remote gateway clients - ConnectRetries int `json:"connect_retries,omitempty"` // ConnectRetries is how many connection attempts the route will make - Gateways []RemoteGatewayOptsVarz `json:"gateways,omitempty"` // Gateways is state of configured gateway remotes - RejectUnknown bool `json:"reject_unknown,omitempty"` // RejectUnknown indicates if unknown cluster connections will be rejected - WriteDeadline time.Duration `json:"write_deadline,omitempty"` // WriteDeadline is the maximum time writes to sockets have to complete - WriteTimeout string `json:"write_timeout,omitempty"` // WriteTimeout is the closure policy for write deadline errors + Name string `json:"name,omitempty"` // Name is the configured cluster name + Host string `json:"host,omitempty"` // Host is the host the gateway listens on for connections + Port int `json:"port,omitempty"` // Port is the post gateway connections listens on + AuthTimeout float64 `json:"auth_timeout,omitempty"` // AuthTimeout is the time cluster connections have to complete authentication + TLSTimeout float64 `json:"tls_timeout,omitempty"` // TLSTimeout is how long TLS operations have to complete + TLSRequired bool `json:"tls_required,omitempty"` // TLSRequired indicates if TLS is required for connections + TLSVerify bool `json:"tls_verify,omitempty"` // TLSVerify indicates if full verification of TLS connections is performed + Advertise string `json:"advertise,omitempty"` // Advertise is the URL advertised to remote gateway clients + ConnectRetries int `json:"connect_retries,omitempty"` // ConnectRetries is how many connection attempts the route will make + Gateways []RemoteGatewayOptsVarz `json:"gateways,omitempty"` // Gateways is state of configured gateway remotes + RejectUnknown bool `json:"reject_unknown,omitempty"` // RejectUnknown indicates if unknown cluster connections will be rejected + WriteDeadline time.Duration `json:"write_deadline,omitempty"` // WriteDeadline is the maximum time writes to sockets have to complete + WriteTimeout string `json:"write_timeout,omitempty"` // WriteTimeout is the closure policy for write deadline errors + TLSCertNotAfter time.Time `json:"tls_cert_not_after,omitzero"` // TLSCertNotAfter is the expiration date of the TLS certificaet } // RemoteGatewayOptsVarz contains monitoring remote gateway information @@ -1340,6 +1343,7 @@ type LeafNodeOptsVarz struct { TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"` // TLSOCSPPeerVerify indicates if OCSP verification will be performed WriteDeadline time.Duration `json:"write_deadline,omitempty"` // WriteDeadline is the maximum time writes to sockets have to complete WriteTimeout string `json:"write_timeout,omitempty"` // WriteTimeout is the closure policy for write deadline errors + TLSCertNotAfter time.Time `json:"tls_cert_not_after,omitzero"` // TLSCertNotAfter is the expiration date of the TLS certificate } // DenyRules Contains lists of subjects not allowed to be imported/exported @@ -1370,6 +1374,7 @@ type MQTTOptsVarz struct { AckWait time.Duration `json:"ack_wait,omitempty"` // AckWait is how long the internal JetStream state store will allow acks to complete MaxAckPending uint16 `json:"max_ack_pending,omitempty"` // MaxAckPending is how many outstanding acks the internal JetStream state store will allow TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"` // TLSOCSPPeerVerify indicates if OCSP verification will be done + TLSCertNotAfter time.Time `json:"tls_cert_not_after,omitzero"` // TLSCertNotAfter is the expiration date of the TLS certificate } // WebsocketOptsVarz contains monitoring websocket information @@ -1388,6 +1393,7 @@ type WebsocketOptsVarz struct { AllowedOrigins []string `json:"allowed_origins,omitempty"` // AllowedOrigins list of configured trusted origins Compression bool `json:"compression,omitempty"` // Compression indicates if compression is supported TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"` // TLSOCSPPeerVerify indicates if OCSP verification will be done + TLSCertNotAfter time.Time `json:"tls_cert_not_after,omitzero"` // TLSCertNotAfter is the expiration date of the TLS certificate } // OCSPResponseCacheVarz contains OCSP response cache information @@ -1454,6 +1460,22 @@ func myUptime(d time.Duration) string { return fmt.Sprintf("%ds", tsecs) } +func tlsCertNotAfter(config *tls.Config) time.Time { + if config == nil || len(config.Certificates) == 0 { + return time.Time{} + } + cert := config.Certificates[0] + leaf := cert.Leaf + if leaf == nil { + var err error + leaf, err = x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + return time.Time{} + } + } + return leaf.NotAfter +} + // HandleRoot will show basic info and links to others handlers. func (s *Server) HandleRoot(w http.ResponseWriter, r *http.Request) { // This feels dumb to me, but is required: https://code.google.com/p/go/issues/detail?id=4799 @@ -1779,6 +1801,13 @@ func (s *Server) updateVarzConfigReloadableFields(v *Varz) { v.TLSOCSPPeerVerify = s.ocspPeerVerify && v.TLSRequired && s.opts.tlsConfigOpts != nil && s.opts.tlsConfigOpts.OCSPPeerConfig != nil && s.opts.tlsConfigOpts.OCSPPeerConfig.Verify + v.TLSCertNotAfter = tlsCertNotAfter(opts.TLSConfig) + v.Cluster.TLSCertNotAfter = tlsCertNotAfter(opts.Cluster.TLSConfig) + v.Gateway.TLSCertNotAfter = tlsCertNotAfter(opts.Gateway.TLSConfig) + v.LeafNode.TLSCertNotAfter = tlsCertNotAfter(opts.LeafNode.TLSConfig) + v.MQTT.TLSCertNotAfter = tlsCertNotAfter(opts.MQTT.TLSConfig) + v.Websocket.TLSCertNotAfter = tlsCertNotAfter(opts.Websocket.TLSConfig) + if opts.Proxies != nil { if v.Proxies == nil { v.Proxies = &ProxiesOptsVarz{} diff --git a/server/monitor_test.go b/server/monitor_test.go index ee02af00467..5ab14cdc244 100644 --- a/server/monitor_test.go +++ b/server/monitor_test.go @@ -2744,7 +2744,9 @@ func TestMonitorCluster(t *testing.T) { opts.Cluster.TLSConfig != nil, opts.Cluster.TLSConfig != nil, DEFAULT_ROUTE_POOL_SIZE, - 0, _EMPTY_, + 0, + _EMPTY_, + time.Time{}, } varzURL := fmt.Sprintf("http://127.0.0.1:%d/varz", s.MonitorAddr().Port) @@ -2760,7 +2762,7 @@ func TestMonitorCluster(t *testing.T) { // Having this here to make sure that if fields are added in ClusterOptsVarz, // we make sure to update this test (compiler will report an error if we don't) - _ = ClusterOptsVarz{"", "", 0, 0, nil, 2, false, false, 0, 0, _EMPTY_} + _ = ClusterOptsVarz{"", "", 0, 0, nil, 2, false, false, 0, 0, _EMPTY_, time.Time{}} // Alter the fields to make sure that we have a proper deep copy // of what may be stored in the server. Anything we change here @@ -2915,7 +2917,9 @@ func TestMonitorGateway(t *testing.T) { opts.Gateway.ConnectRetries, []RemoteGatewayOptsVarz{{"B", 1, nil}}, opts.Gateway.RejectUnknown, - 0, _EMPTY_, + 0, + _EMPTY_, + time.Time{}, } // Since URLs array is not guaranteed to be always the same order, // we don't add it in the expected GatewayOptsVarz, instead we @@ -2953,7 +2957,7 @@ func TestMonitorGateway(t *testing.T) { // Having this here to make sure that if fields are added in GatewayOptsVarz, // we make sure to update this test (compiler will report an error if we don't) - _ = GatewayOptsVarz{"", "", 0, 0, 0, false, false, "", 0, []RemoteGatewayOptsVarz{{"", 0, nil}}, false, 0, "default"} + _ = GatewayOptsVarz{"", "", 0, 0, 0, false, false, "", 0, []RemoteGatewayOptsVarz{{"", 0, nil}}, false, 0, "default", time.Time{}} // Alter the fields to make sure that we have a proper deep copy // of what may be stored in the server. Anything we change here @@ -3139,7 +3143,9 @@ func TestMonitorLeafNode(t *testing.T) { }, }, false, - 0, _EMPTY_, + 0, + _EMPTY_, + time.Time{}, } varzURL := fmt.Sprintf("http://127.0.0.1:%d/varz", s.MonitorAddr().Port) @@ -3164,7 +3170,7 @@ func TestMonitorLeafNode(t *testing.T) { // Having this here to make sure that if fields are added in ClusterOptsVarz, // we make sure to update this test (compiler will report an error if we don't) - _ = LeafNodeOptsVarz{"", 0, 0, 0, false, false, []RemoteLeafOptsVarz{{"", 0, nil, nil, false}}, false, 0, _EMPTY_} + _ = LeafNodeOptsVarz{"", 0, 0, 0, false, false, []RemoteLeafOptsVarz{{"", 0, nil, nil, false}}, false, 0, _EMPTY_, time.Time{}} // Alter the fields to make sure that we have a proper deep copy // of what may be stored in the server. Anything we change here @@ -6406,3 +6412,46 @@ func TestMonitorVarzMetadata(t *testing.T) { t.Fatalf("expected: %v, got: %v", expected, v.Metadata) } } + +func TestMonitorVarzTLSCertEndDate(t *testing.T) { + resetPreviousHTTPConnections() + opts := DefaultMonitorOptions() + tlsConfig, err := GenTLSConfig( + &TLSConfigOpts{ + CertFile: "../test/configs/certs/server-cert.pem", + KeyFile: "../test/configs/certs/server-key.pem", + CaFile: "../test/configs/certs/ca.pem", + }) + if err != nil { + t.Fatalf("Error generating TLS config: %v", err) + } + + opts.TLSConfig = tlsConfig + opts.Cluster.TLSConfig = tlsConfig + opts.Gateway.TLSConfig = tlsConfig + opts.LeafNode.TLSConfig = tlsConfig + opts.MQTT.TLSConfig = tlsConfig + opts.Websocket.TLSConfig = tlsConfig + + s := RunServer(opts) + defer s.Shutdown() + + url := fmt.Sprintf("http://127.0.0.1:%d/varz", s.MonitorAddr().Port) + v := pollVarz(t, s, 0, url, nil) + + expected := time.Date(2032, 8, 24, 20, 23, 02, 0, time.UTC) + + check := func(t *testing.T, notAfter time.Time) { + t.Helper() + if notAfter != expected { + t.Fatalf("Expected expiration date '%v', got '%v'", expected, notAfter) + } + } + + check(t, v.TLSCertNotAfter) + check(t, v.Cluster.TLSCertNotAfter) + check(t, v.Gateway.TLSCertNotAfter) + check(t, v.LeafNode.TLSCertNotAfter) + check(t, v.MQTT.TLSCertNotAfter) + check(t, v.Websocket.TLSCertNotAfter) +}