mirror of
https://github.com/henrygd/beszel.git
synced 2026-09-24 18:37:49 +02:00
feat: add TLS certificate expiry check to HTTPS network monitors (#2401)
Co-authored-by: henrygd <hank@henrygd.me>
This commit is contained in:
74
agent/network_monitor_cert.go
Normal file
74
agent/network_monitor_cert.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/henrygd/beszel/internal/entities/monitor"
|
||||
)
|
||||
|
||||
const (
|
||||
certCheckInterval = 24 * time.Hour
|
||||
certCheckRetryInterval = time.Hour
|
||||
)
|
||||
|
||||
// certChecker fetches the leaf certificate for an HTTPS target.
|
||||
type certChecker func(context.Context, string) (monitor.CertInfo, error)
|
||||
|
||||
// certCheckEnabled reports whether a monitor's certificate is checked, which is
|
||||
// the case for every HTTP monitor with an https target.
|
||||
func certCheckEnabled(config monitor.Config) bool {
|
||||
return config.Protocol == "http" && len(config.Target) > 8 && strings.EqualFold(config.Target[:8], "https://")
|
||||
}
|
||||
|
||||
// checkCert reads the leaf certificate presented by an HTTPS target. The chain is
|
||||
// not verified, so expired or self-signed certificates are still reported.
|
||||
func checkCert(ctx context.Context, target string) (monitor.CertInfo, error) {
|
||||
address, host, err := certAddress(target)
|
||||
if err != nil {
|
||||
return monitor.CertInfo{}, err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, monitor.MaxProbeTimeout)
|
||||
defer cancel()
|
||||
dialer := tls.Dialer{Config: &tls.Config{ServerName: host, InsecureSkipVerify: true}}
|
||||
conn, err := dialer.DialContext(ctx, "tcp", address)
|
||||
if err != nil {
|
||||
return monitor.CertInfo{}, err
|
||||
}
|
||||
defer conn.Close()
|
||||
certs := conn.(*tls.Conn).ConnectionState().PeerCertificates
|
||||
if len(certs) == 0 {
|
||||
return monitor.CertInfo{}, errors.New("no peer certificates")
|
||||
}
|
||||
leaf := certs[0]
|
||||
return monitor.CertInfo{
|
||||
Expires: leaf.NotAfter.UnixMilli(),
|
||||
Issuer: leaf.Issuer.CommonName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// certAddress returns the dial address and server name for an HTTPS URL.
|
||||
func certAddress(target string) (address, host string, err error) {
|
||||
u, err := url.Parse(target)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if !strings.EqualFold(u.Scheme, "https") {
|
||||
return "", "", fmt.Errorf("certificate check requires an https target: %s", target)
|
||||
}
|
||||
host = u.Hostname()
|
||||
if host == "" {
|
||||
return "", "", fmt.Errorf("missing host in target: %s", target)
|
||||
}
|
||||
port := u.Port()
|
||||
if port == "" {
|
||||
port = "443"
|
||||
}
|
||||
return net.JoinHostPort(host, port), host, nil
|
||||
}
|
||||
Reference in New Issue
Block a user