Files
VSC/Go/another-http-check-master/check.go
T
claudio 368d6fafea Issue
Code backup
2026-05-10 16:59:01 +02:00

367 lines
8.3 KiB
Go

package main
import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"net/http"
"strconv"
"strings"
"time"
"github.com/Azure/go-ntlmssp"
)
const (
// Authentication types
AUTH_NONE = 0
AUTH_BASIC = 1
AUTH_NTLM = 2
// Exit codes
EXIT_OK = 0
EXIT_WARNING = 1
EXIT_CRITICAL = 2
EXIT_UNKNOWN = 3
)
// Authentication
type Authentication struct {
Type int
User string
Password string
}
type SSLCheck struct {
Run bool
DaysWarning int
DaysCritical int
}
type ClientCert struct {
ClientCertFile string
PrivateKeyFile string
}
// Request
type Request struct {
Scheme string
Host string
IPAddress string
TLS bool
Port int
URI string
Timeout int
Verbose bool
SSLNoVerify bool
Authentication Authentication
FollowRedirects bool
WarningTimeout int
CriticalTimeout int
NoSNI bool
ClientCert ClientCert
TLSRenegotiation bool
}
// Check params
type Expected struct {
StatusCodes []int
BodyText string
SSLCheck SSLCheck
}
// Lookup map for auth type names
var authLookup = map[int]string{
AUTH_NONE: "none",
AUTH_BASIC: "basic auth",
AUTH_NTLM: "NTLM auth",
}
// URL getter
func (r Request) GetURL() string {
var host string
if len(r.IPAddress) > 0 {
host = r.IPAddress
} else {
host = r.Host
}
return fmt.Sprintf("%s://%s:%s%s", r.Scheme, host, strconv.Itoa(r.Port), r.URI)
}
// Use timeout interval
func (r Request) UseTimoutInterval() bool {
return r.WarningTimeout > 0 && r.CriticalTimeout > 0 && r.WarningTimeout < r.CriticalTimeout
}
// Status code check helper
func checkStatusCode(code int, e *Expected) bool {
for _, expectedCode := range e.StatusCodes {
if expectedCode == code {
return true
}
}
return false
}
// Certificate check helper
func checkCerts(certs [][]*x509.Certificate, e *Expected) (string, int) {
timeNow := time.Now()
checkedCerts := make(map[string]bool)
for _, chain := range certs {
for _, cert := range chain {
if _, checked := checkedCerts[string(cert.Signature)]; checked {
continue
}
checkedCerts[string(cert.Signature)] = true
expiresIn := int(cert.NotAfter.Sub(timeNow).Hours())
if e.SSLCheck.DaysCritical > 0 && e.SSLCheck.DaysCritical*24 >= expiresIn {
return fmt.Sprintf("CRITICAL - SSL cert expires in %f days", float32(expiresIn)/24), EXIT_CRITICAL
}
if e.SSLCheck.DaysWarning > 0 && e.SSLCheck.DaysWarning*24 >= expiresIn {
return fmt.Sprintf("WARNING - SSL cert expires in %f days", float32(expiresIn)/24), EXIT_WARNING
}
}
}
return "", EXIT_OK
}
// TLS config factory
func getTLSConfig(r *Request) (*tls.Config, error) {
TLSConfig := &tls.Config{}
// InsecureSkipVerify
if r.SSLNoVerify {
TLSConfig.InsecureSkipVerify = true
}
// SNI
if !r.NoSNI && len(r.Host) > 0 {
TLSConfig.ServerName = r.Host
}
// Client cert
if r.ClientCert.ClientCertFile != "" && r.ClientCert.PrivateKeyFile != "" {
cert, err := tls.LoadX509KeyPair(r.ClientCert.ClientCertFile, r.ClientCert.PrivateKeyFile)
if err != nil {
return nil, err
}
TLSConfig.Certificates = []tls.Certificate{cert}
}
// Renegotiation
if r.TLSRenegotiation {
TLSConfig.Renegotiation = tls.RenegotiateOnceAsClient
}
return TLSConfig, nil
}
// HTTP client factory
func initHTTPClient(r *Request) (*http.Client, error) {
// Get TLS config
TLSConfig, err := getTLSConfig(r)
if err != nil {
return nil, err
}
http.DefaultTransport.(*http.Transport).TLSClientConfig = TLSConfig
// Setup timeout
var timeout time.Duration
if r.UseTimoutInterval() {
timeout = time.Duration(time.Duration(r.CriticalTimeout) * time.Second)
} else {
timeout = time.Duration(time.Duration(r.Timeout) * time.Second)
}
// Init client
client := &http.Client{
Timeout: timeout,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if r.FollowRedirects {
return nil
} else {
return http.ErrUseLastResponse
}
},
}
return client, nil
}
// Adds custom User-Agent header
func setUserAgent(request *http.Request) {
request.Header.Set("User-Agent", fmt.Sprintf("icinga-http-check/%s Go-http-client/%s", appVersion, goVersion))
}
// Main check function
func Check(r *Request, e *Expected) (string, int, error) {
if len(r.Host) == 0 && len(r.IPAddress) == 0 {
return "UNKNOWN - No host or IP address given", EXIT_UNKNOWN, nil
}
client, err := initHTTPClient(r)
if err != nil {
return "CRITICAL", EXIT_CRITICAL, err
}
url := r.GetURL()
if r.Verbose {
fmt.Println(">> URL: " + url)
}
// Prepare request
request, err := http.NewRequest("GET", url, nil)
if err != nil {
if r.Verbose {
fmt.Println(fmt.Sprintf(">> http.NewRequest error: %v", err))
}
return "UNKNOWN", EXIT_UNKNOWN, err
}
// User agent
setUserAgent(request)
// Authentication
if r.Authentication.Type == AUTH_BASIC {
request.SetBasicAuth(r.Authentication.User, r.Authentication.Password)
}
// TODO - test
if r.Authentication.Type == AUTH_NTLM {
// Get TLS config
TLSConfig, err := getTLSConfig(r)
if err != nil {
return "CRITICAL", EXIT_CRITICAL, err
}
transport := ntlmssp.Negotiator{
RoundTripper: &http.Transport{
TLSClientConfig: TLSConfig,
},
}
client.Transport = transport
request.SetBasicAuth(r.Authentication.User, r.Authentication.Password)
}
// SNI
if !r.NoSNI && len(r.Host) > 0 {
request.Host = r.Host
}
start := time.Now()
timeInfo := func() string {
return fmt.Sprintf("time=%fs", float32(time.Now().UnixNano()-start.UnixNano())/float32(1000000000))
}
res, err := client.Do(request)
if err != nil {
if r.Verbose {
fmt.Println(fmt.Sprintf(">> client.GET error: %v", err))
}
if err, ok := err.(net.Error); ok && err.Timeout() {
var timeout int
if r.UseTimoutInterval() {
timeout = r.CriticalTimeout
} else {
timeout = r.Timeout
}
return fmt.Sprintf("CRITICAL - Timeout - No response recieved in %d seconds|%s", timeout, timeInfo()), EXIT_CRITICAL, nil
}
return fmt.Sprintf("CRITICAL - %s|%s", err.Error(), timeInfo()), EXIT_CRITICAL, nil
}
defer res.Body.Close()
// Timeout interval
if r.UseTimoutInterval() {
delta := float32(time.Now().UnixNano()-start.UnixNano()) / float32(1000000000)
if delta >= float32(r.WarningTimeout) {
return fmt.Sprintf("WARNING - Timeout - No response recieved in %d seconds|%s", r.WarningTimeout, timeInfo()), EXIT_WARNING, nil
}
}
if r.Verbose {
fmt.Println(fmt.Sprintf(">> Response status: %s", res.Status))
}
// Check status code
if !checkStatusCode(res.StatusCode, e) {
var expectedStatusCodes []string
for _, code := range e.StatusCodes {
expectedStatusCodes = append(expectedStatusCodes, strconv.Itoa(code))
}
return fmt.Sprintf("CRITICAL - Got response HTTP/1.1 %s, expected %s|%s", strconv.Itoa(res.StatusCode), strings.Join(expectedStatusCodes, ", "), timeInfo()), EXIT_CRITICAL, nil
}
// Check body text
if len(e.BodyText) > 0 {
expectedText := []byte(e.BodyText)
bodyBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
return "UNKNOWN", EXIT_UNKNOWN, err
}
if !bytes.Contains(bodyBytes, expectedText) {
return fmt.Sprintf("CRITICAL - String '%s' not found in body|%s", e.BodyText, timeInfo()), EXIT_CRITICAL, nil
}
}
// Check SSL cert
if e.SSLCheck.Run {
SSLMsg, SSLExit := checkCerts(res.TLS.VerifiedChains, e)
if SSLExit != EXIT_OK {
return fmt.Sprintf("%s|%s", SSLMsg, timeInfo()), SSLExit, nil
}
}
return fmt.Sprintf("OK - Got response HTTP/1.1 %s|%s", strconv.Itoa(res.StatusCode), timeInfo()), EXIT_OK, nil
}
// Detects auth type
func DetectAuthType(r *Request) int {
client, err := initHTTPClient(r)
if err != nil {
// `Check` should handle all errors
return AUTH_NONE
}
url := r.GetURL()
request, err := http.NewRequest("GET", url, nil)
if err != nil {
// `Check` should handle all errors
return AUTH_NONE
}
// User agent
setUserAgent(request)
// SNI
if !r.NoSNI && len(r.Host) > 0 {
request.Host = r.Host
}
res, err := client.Do(request)
if err != nil {
// `Check` should handle all errors
return AUTH_NONE
}
defer res.Body.Close()
authHeaders, ok := res.Header["Www-Authenticate"]
if ok {
authHeader := strings.ToLower(authHeaders[0])
if strings.HasPrefix(authHeader, "ntlm") || strings.HasPrefix(authHeader, "negotiate") {
return AUTH_NTLM
}
if strings.HasPrefix(authHeader, "basic") {
return AUTH_BASIC
}
}
return AUTH_NONE
}