Cleaner SSL check program
* Split off the progam into various functions * Pass the program's data (plugin, certificate) through a single structure
This commit is contained in:
parent
cdcb18821f
commit
2c5d27684a
1 changed files with 101 additions and 64 deletions
|
@ -12,16 +12,21 @@ import (
|
||||||
"nocternity.net/monitoring/plugin"
|
"nocternity.net/monitoring/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type cliFlags struct {
|
type checkProgram struct {
|
||||||
hostname string
|
hostname string
|
||||||
port int
|
port int
|
||||||
warn int
|
warn int
|
||||||
crit int
|
crit int
|
||||||
ignoreCnOnly bool
|
ignoreCnOnly bool
|
||||||
|
|
||||||
|
certificate *x509.Certificate
|
||||||
|
plugin *plugin.Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCli(p *plugin.Plugin) (flags *cliFlags) {
|
func newProgram() (flags *checkProgram) {
|
||||||
flags = &cliFlags{}
|
flags = &checkProgram{
|
||||||
|
plugin: plugin.New("Certificate check"),
|
||||||
|
}
|
||||||
flag.StringVar(&flags.hostname, "hostname", "", "Host name to connect to.")
|
flag.StringVar(&flags.hostname, "hostname", "", "Host name to connect to.")
|
||||||
flag.StringVar(&flags.hostname, "H", "", "Host name to connect to (shorthand).")
|
flag.StringVar(&flags.hostname, "H", "", "Host name to connect to (shorthand).")
|
||||||
flag.IntVar(&flags.port, "port", -1, "Port to connect to.")
|
flag.IntVar(&flags.port, "port", -1, "Port to connect to.")
|
||||||
|
@ -36,23 +41,45 @@ func handleCli(p *plugin.Plugin) (flags *cliFlags) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkFlags(p *plugin.Plugin, flags *cliFlags) bool {
|
func (program *checkProgram) close() {
|
||||||
if flags.hostname == "" {
|
program.plugin.Done()
|
||||||
p.SetState(plugin.UNKNOWN, "no hostname specified")
|
}
|
||||||
|
|
||||||
|
func (program *checkProgram) checkFlags() bool {
|
||||||
|
if program.hostname == "" {
|
||||||
|
program.plugin.SetState(plugin.UNKNOWN, "no hostname specified")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if flags.port < 1 || flags.port > 65535 {
|
if program.port < 1 || program.port > 65535 {
|
||||||
p.SetState(plugin.UNKNOWN, "invalid or missing port number")
|
program.plugin.SetState(plugin.UNKNOWN, "invalid or missing port number")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if flags.warn != -1 && flags.crit != -1 && flags.warn <= flags.crit {
|
if program.warn != -1 && program.crit != -1 && program.warn <= program.crit {
|
||||||
p.SetState(plugin.UNKNOWN, "nonsensical thresholds")
|
program.plugin.SetState(plugin.UNKNOWN, "nonsensical thresholds")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
flags.hostname = strings.ToLower(flags.hostname)
|
program.hostname = strings.ToLower(program.hostname)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (program *checkProgram) getCertificate() error {
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
MinVersion: tls.VersionTLS10,
|
||||||
|
}
|
||||||
|
connString := fmt.Sprintf("%s:%d", program.hostname, program.port)
|
||||||
|
conn, err := tls.Dial("tcp", connString, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connection failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
if err := conn.Handshake(); err != nil {
|
||||||
|
return fmt.Errorf("handshake failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
program.certificate = conn.ConnectionState().PeerCertificates[0]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func findHostname(cert *x509.Certificate, hostname string) bool {
|
func findHostname(cert *x509.Certificate, hostname string) bool {
|
||||||
for _, name := range cert.DNSNames {
|
for _, name := range cert.DNSNames {
|
||||||
if strings.ToLower(name) == hostname {
|
if strings.ToLower(name) == hostname {
|
||||||
|
@ -62,60 +89,70 @@ func findHostname(cert *x509.Certificate, hostname string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func (program *checkProgram) checkCertificateName() bool {
|
||||||
p := plugin.New("Certificate check")
|
if len(program.certificate.DNSNames) == 0 {
|
||||||
defer p.Done()
|
if !program.ignoreCnOnly {
|
||||||
flags := handleCli(p)
|
program.plugin.SetState(plugin.WARNING,
|
||||||
if !checkFlags(p, flags) {
|
"certificate doesn't have SAN domain names")
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
dn := strings.ToLower(program.certificate.Subject.String())
|
||||||
tls_cfg := &tls.Config{
|
if !strings.HasPrefix(dn, fmt.Sprintf("cn=%s,", program.hostname)) {
|
||||||
InsecureSkipVerify: true,
|
program.plugin.SetState(plugin.CRITICAL, "incorrect certificate CN")
|
||||||
MinVersion: tls.VersionTLS10,
|
return false
|
||||||
}
|
}
|
||||||
tls_conn, tls_err := tls.Dial("tcp", fmt.Sprintf("%s:%d", flags.hostname, flags.port), tls_cfg)
|
} else if !findHostname(program.certificate, program.hostname) {
|
||||||
if tls_err != nil {
|
program.plugin.SetState(plugin.CRITICAL, "host name not found in SAN domain names")
|
||||||
p.SetState(plugin.UNKNOWN, fmt.Sprintf("connection failed: %s", tls_err))
|
return false
|
||||||
return
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (program *checkProgram) checkCertificateExpiry(tlDays int) (plugin.Status, string) {
|
||||||
|
var limitStr string
|
||||||
|
var state plugin.Status
|
||||||
|
if program.crit > 0 && tlDays <= program.crit {
|
||||||
|
limitStr = fmt.Sprintf(" (<= %d)", program.crit)
|
||||||
|
state = plugin.CRITICAL
|
||||||
|
} else if program.warn > 0 && tlDays <= program.warn {
|
||||||
|
limitStr = fmt.Sprintf(" (<= %d)", program.warn)
|
||||||
|
state = plugin.WARNING
|
||||||
|
} else {
|
||||||
|
limitStr = ""
|
||||||
|
state = plugin.OK
|
||||||
|
}
|
||||||
|
statusString := fmt.Sprintf("certificate will expire in %d days%s",
|
||||||
|
tlDays, limitStr)
|
||||||
|
return state, statusString
|
||||||
|
}
|
||||||
|
|
||||||
|
func (program *checkProgram) setPerfData(tlDays int) {
|
||||||
|
pdat := perfdata.New("validity", perfdata.UOM_NONE, fmt.Sprintf("%d", tlDays))
|
||||||
|
if program.crit > 0 {
|
||||||
|
pdat.SetCrit(perfdata.PDRMax(fmt.Sprint(program.crit)))
|
||||||
|
}
|
||||||
|
if program.warn > 0 {
|
||||||
|
pdat.SetWarn(perfdata.PDRMax(fmt.Sprint(program.warn)))
|
||||||
|
}
|
||||||
|
program.plugin.AddPerfData(pdat)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (program *checkProgram) runCheck() {
|
||||||
|
err := program.getCertificate()
|
||||||
|
if err != nil {
|
||||||
|
program.plugin.SetState(plugin.UNKNOWN, err.Error())
|
||||||
|
} else if program.checkCertificateName() {
|
||||||
|
timeLeft := program.certificate.NotAfter.Sub(time.Now())
|
||||||
|
tlDays := int((timeLeft + 86399*time.Second) / (24 * time.Hour))
|
||||||
|
program.plugin.SetState(program.checkCertificateExpiry(tlDays))
|
||||||
|
program.setPerfData(tlDays)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
program := newProgram()
|
||||||
|
defer program.close()
|
||||||
|
if program.checkFlags() {
|
||||||
|
program.runCheck()
|
||||||
}
|
}
|
||||||
defer tls_conn.Close()
|
|
||||||
if hsk_err := tls_conn.Handshake(); hsk_err != nil {
|
|
||||||
p.SetState(plugin.UNKNOWN, fmt.Sprintf("handshake failed: %s", hsk_err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
certificate := tls_conn.ConnectionState().PeerCertificates[0]
|
|
||||||
|
|
||||||
if len(certificate.DNSNames) == 0 {
|
|
||||||
if !flags.ignoreCnOnly {
|
|
||||||
p.SetState(plugin.WARNING, "certificate doesn't have SAN domain names")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dn := strings.ToLower(certificate.Subject.String())
|
|
||||||
if !strings.HasPrefix(dn, fmt.Sprintf("cn=%s,", flags.hostname)) {
|
|
||||||
p.SetState(plugin.CRITICAL, "incorrect certificate CN")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if !findHostname(certificate, flags.hostname) {
|
|
||||||
p.SetState(plugin.CRITICAL, "host name not found in SAN domain names")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
timeLeft := certificate.NotAfter.Sub(time.Now())
|
|
||||||
tlDays := int((timeLeft + 86399*time.Second) / (24 * time.Hour))
|
|
||||||
if flags.crit > 0 && tlDays <= flags.crit {
|
|
||||||
p.SetState(plugin.CRITICAL, fmt.Sprintf("certificate will expire in %d days (<= %d)", tlDays, flags.crit))
|
|
||||||
} else if flags.warn > 0 && tlDays <= flags.warn {
|
|
||||||
p.SetState(plugin.WARNING, fmt.Sprintf("certificate will expire in %d days (<= %d)", tlDays, flags.warn))
|
|
||||||
} else {
|
|
||||||
p.SetState(plugin.OK, fmt.Sprintf("certificate will expire in %d days", tlDays))
|
|
||||||
}
|
|
||||||
|
|
||||||
pdat := perfdata.New("validity", perfdata.UOM_NONE, fmt.Sprintf("%d", tlDays))
|
|
||||||
if flags.crit > 0 {
|
|
||||||
pdat.SetCrit(perfdata.PDRMax(fmt.Sprint(flags.crit)))
|
|
||||||
}
|
|
||||||
if flags.warn > 0 {
|
|
||||||
pdat.SetWarn(perfdata.PDRMax(fmt.Sprint(flags.warn)))
|
|
||||||
}
|
|
||||||
p.AddPerfData(pdat)
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue