Guest User

Untitled

a guest
Dec 12th, 2025
32
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Go 4.19 KB | None | 0 0
  1. package main
  2.  
  3. import (
  4.   "fmt"
  5.   "log"
  6.   "os"
  7.   "strings"
  8.   "crypto/tls"
  9.   "time"
  10.   "math"
  11.   "net/http"
  12.  
  13.   "github.com/prometheus/client_golang/prometheus"
  14.   "github.com/prometheus/client_golang/prometheus/promhttp"
  15. )
  16.  
  17. var (
  18.     connErrs = prometheus.NewGaugeVec(
  19.     prometheus.GaugeOpts{
  20.           Name: "host_connection_error",
  21.           Help: "Boolean connection error indicator",
  22.       },
  23.     []string{"host"},
  24.   )
  25.     certExpiry = prometheus.NewGaugeVec(
  26.         prometheus.GaugeOpts{
  27.             Name: "cert_expiry_in_seconds",
  28.             Help: "Seconds left to certificate expiry in chain (can be CA)",
  29.         },
  30.         []string{"host", "cn"},
  31.     )
  32. )
  33.  
  34. type HostnameChan chan string
  35.  
  36. func hostChecker(hostChan HostnameChan) {
  37.   defer func() {
  38.     if hostChan != nil {
  39.       close(hostChan)
  40.     }
  41.   }()
  42.  
  43.   for {
  44.     if hostChan == nil { return } // quit if the channel is dereferenced
  45.  
  46.     select {
  47.     case host := <- hostChan:
  48.       next_expiry, selected_cert := getCertExpiry(host)
  49.       log.Printf("%s (%s): %f hours\n", host, selected_cert, next_expiry / 60 / 60)
  50.     }
  51.   }
  52. }
  53.  
  54. func getCertExpiry(host string) (float64, string) {
  55.   now := time.Now()
  56.  
  57.   conf := &tls.Config{
  58.        InsecureSkipVerify: true,
  59.   }
  60.  
  61.   next_expiry := math.MaxFloat64
  62.   selected_cert := "none"
  63.  
  64.   conn, err := tls.Dial("tcp", host, conf)
  65.   if err != nil {
  66.       log.Println(err)
  67.       connErrs.With(prometheus.Labels{"host": host}).Set(1)
  68.       return next_expiry, selected_cert
  69.   }
  70.   defer conn.Close()
  71.  
  72.  
  73.   for _, cert := range conn.ConnectionState().PeerCertificates {
  74.     seconds_left := cert.NotAfter.Sub(now).Seconds()
  75.     if seconds_left < next_expiry {
  76.       selected_cert = cert.Subject.CommonName
  77.       next_expiry = seconds_left
  78.     }
  79.   }
  80.  
  81.   connErrs.With(prometheus.Labels{"host": host}).Set(0)
  82.   certExpiry.With(prometheus.Labels{"host": host, "cn": selected_cert}).Set(next_expiry)
  83.   return next_expiry, selected_cert
  84. }
  85.  
  86. func makeRegistry() *prometheus.Registry {
  87.   r := prometheus.NewRegistry()
  88.   r.MustRegister(connErrs)
  89.   r.MustRegister(certExpiry)
  90.   return r
  91. }
  92.  
  93. func checkAll(hostChan HostnameChan, hosts []string) {
  94.   for _, host := range hosts {
  95.     hostChan <- host
  96.   }
  97. }
  98.  
  99. func parseTimerConfig(defaultDuration time.Duration) time.Duration {
  100.   fromConfig := os.Getenv("CHK_INTERVAL")
  101.  
  102.   if fromConfig == "" {
  103.     log.Printf("No CHK_INTERVAL given. Using default.\n", defaultDuration.Minutes())
  104.     return defaultDuration
  105.   }
  106.  
  107.   duration, err := time.ParseDuration(fromConfig)
  108.  
  109.   if err != nil {
  110.     log.Printf("Error parsing CHK_INTERVAL value: '%s'. Using default.\n", fromConfig)
  111.     log.Println(err)
  112.     return defaultDuration
  113.   }
  114.  
  115.   if duration.Minutes() < 1 {
  116.     log.Printf("Error: CHK_INTERVAL must be >= 1, given: %v. Using default", duration)
  117.     return defaultDuration
  118.   }
  119.  
  120.   return duration
  121.  
  122. }
  123.  
  124. func main() {
  125.  
  126.     fmt.Print(`
  127.                _       _               _
  128.               | |     | |             | |
  129.   ___ ___ _ __| |_ ___| |__   ___  ___| | __
  130.  / __/ _ \ '__| __/ __| '_ \ / _ \/ __| |/ /
  131. | (_|  __/ |  | || (__| | | |  __/ (__|   <
  132.  \___\___|_|   \__\___|_| |_|\___|\___|_|\_\
  133.  
  134. `)
  135.  
  136.     r := makeRegistry()
  137.  
  138.     envHosts := os.Getenv("HOSTS")
  139.  
  140.     if envHosts == "" {
  141.       log.Fatal("No hosts given")
  142.     }
  143.     hosts := strings.Split(envHosts, ",")
  144.  
  145.     log.Printf("Got hosts: %s\n", strings.Join(hosts, " "))
  146.     if len(hosts) < 1 {
  147.       log.Fatal("No hosts given")
  148.     }
  149.  
  150.     defaultTickerInterval, err := time.ParseDuration("15m")
  151.     if err != nil {
  152.       log.Fatal(err)
  153.     }
  154.  
  155.     tickerInterval := parseTimerConfig(defaultTickerInterval)
  156.     log.Printf("Ticker interval: %v", tickerInterval)
  157.  
  158.     // checker
  159.     hostChan := make(HostnameChan)
  160.     go hostChecker(hostChan)
  161.  
  162.     // initial run
  163.     checkAll(hostChan, hosts)
  164.  
  165.     // timer
  166.     ticker := time.NewTicker(tickerInterval)
  167.     go func() {
  168.         for {
  169.             select {
  170.             case t := <-ticker.C:
  171.                 log.Printf("TICK! running checks... %d\n", t)
  172.                 checkAll(hostChan, hosts)
  173.             }
  174.         }
  175.     }()
  176.  
  177.     http.Handle("/metrics", promhttp.HandlerFor(r, promhttp.HandlerOpts{}))
  178.     log.Fatal(http.ListenAndServe(":8080", nil))
  179. }
Advertisement
Add Comment
Please, Sign In to add comment