Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 18 additions & 17 deletions docs/checks/plugins/check_http.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,16 @@ Application Options:
-s, --string= String to expect in the content
--base64-string= Base64 Encoded string to expect the content
-A, --useragent= UserAgent to be sent (default: check_http)
-a, --authorization= username:password on sites with basic authentication
-a, --authorization= Pass '[username]:[password]' formatted string to be
used as basic authorization header
-k, --header= Any other tags to be sent in http header. Use
multiple times for additional headers
-C, --certificate= check certificates instead of content. Specified in
-C, --certificate= Check certificates instead of content. Specified in
mandatory days left to warn and optional days to crit
with a comma: warn_days[,<crit_days>]
--tls-min=[1.0|1.0+|1.1|1.1+|1.2|1.2+|1.3] minimum supported TLS version. Values with plus set
--tls-min=[1.0|1.0+|1.1|1.1+|1.2|1.2+|1.3] Minimum supported TLS version. Values with plus set
the max tls version as well to latest version: 1.3
--tls-max=[1.0|1.1|1.2|1.3] maximum supported TLS version
--tls-max=[1.0|1.1|1.2|1.3] Maximum supported TLS version
--proxy= Proxy that should be used
-r, --regex= Search page for case-sensitive regex string
-R, --regexi= Search page for case-insensitive regex string
Expand All @@ -98,26 +99,26 @@ Application Options:
unit is given at the end, default of seconds is
assumed. Value is truncated to milliseconds.
(default: 60)
--wait-for-interval= retry interval (default: 2s)
--wait-for-max= time to wait for success (max.: 180s)
--interim= interval time after successful request for
--wait-for-interval= Retry interval (default: 2s)
--wait-for-max= Time to wait for success (max.: 180s)
--interim= Interval time after successful request for
consecutive mode (default: 1s)
--consecutive= number of consecutive successful requests required
--consecutive= Number of consecutive successful requests required
(max.: 5) (default: 1)
-p, --port= Port number
--max-redirs= Maximum redirects before giving up on following
--no-discard raise error when the response body is larger then
--no-discard Raise error when the response body is larger then
max-buffer-size
--wait-for retry until successful when enabled
-S, --ssl use https
--sni enable SNI
-4 use tcp4 only
-6 use tcp6 only
--wait-for Retry until successful when enabled
-S, --ssl Use https
--sni Enable SNI
-4 Use tcp4 only
-6 Use tcp6 only
-v, --verbose Show verbose output
--show-body Print body content below status line
--ignore-certificate-chain by default all certificates are checked in many
aspects. Toggle this option to only check the leaf
(final) certificate.
--ignore-certificate-chain During certificate check, all certificates are
checked in many aspects. Toggle this option to only
check the leaf (final) certificate.
--check-cn Subject Common Name of leaf certificate can be
checked to match hostname exactly. Common Name field
is now largely unused in modern web, with Subject
Expand Down
110 changes: 89 additions & 21 deletions pkg/check_http/check_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ type commandOpts struct {
ExpectContent string `short:"s" long:"string" description:"String to expect in the content"`
Base64ExpectContent string ` long:"base64-string" description:"Base64 Encoded string to expect the content"`
UserAgent string `short:"A" long:"useragent" default:"check_http" description:"UserAgent to be sent"`
Authorization string `short:"a" long:"authorization" description:"username:password on sites with basic authentication"`
Authorization string `short:"a" long:"authorization" description:"Pass '[username]:[password]' formatted string to be used as basic authorization header"`
Header []string `short:"k" long:"header" description:"Any other tags to be sent in http header. Use multiple times for additional headers"`
Certificate string `short:"C" long:"certificate" description:"check certificates instead of content. Specified in mandatory days left to warn and optional days to crit with a comma: warn_days[,<crit_days>]" `
TLSMinVersion string ` long:"tls-min" description:"minimum supported TLS version. Values with plus set the max tls version as well to latest version: 1.3" choice:"1.0" choice:"1.0+" choice:"1.1" choice:"1.1+" choice:"1.2" choice:"1.2+" choice:"1.3"`
TLSMaxVersion string ` long:"tls-max" description:"maximum supported TLS version" choice:"1.0" choice:"1.1" choice:"1.2" choice:"1.3"`
Certificate string `short:"C" long:"certificate" description:"Check certificates instead of content. Specified in mandatory days left to warn and optional days to crit with a comma: warn_days[,<crit_days>]" `
TLSMinVersion string ` long:"tls-min" description:"Minimum supported TLS version. Values with plus set the max tls version as well to latest version: 1.3" choice:"1.0" choice:"1.0+" choice:"1.1" choice:"1.1+" choice:"1.2" choice:"1.2+" choice:"1.3"`
TLSMaxVersion string ` long:"tls-max" description:"Maximum supported TLS version" choice:"1.0" choice:"1.1" choice:"1.2" choice:"1.3"`
Proxy string ` long:"proxy" description:"Proxy that should be used"`
RegexStr string `short:"r" long:"regex" description:"Search page for case-sensitive regex string"`
RegexiStr string `short:"R" long:"regexi" description:"Search page for case-insensitive regex string"`
Expand All @@ -80,21 +80,21 @@ type commandOpts struct {
TimeoutStr string `short:"t" long:"timeout" default:"10" description:"Timeout to wait for connection. If no time unit is given at the end, default of seconds is assumed"`
WarningThresholdStr string `short:"w" long:"warning" default:"30" description:"If the request+response takes longer specified warning threshold, raises a warning. If no time unit is given at the end, default of seconds is assumed. Value is truncated to milliseconds."`
CriticalThresholdStr string `short:"c" long:"critical" default:"60" description:"If the request+response takes longer specified critical threshold, raises a critical. If no time unit is given at the end, default of seconds is assumed. Value is truncated to milliseconds."`
WaitForInterval time.Duration ` long:"wait-for-interval" default:"2s" description:"retry interval"`
WaitForMax time.Duration ` long:"wait-for-max" description:"time to wait for success (max.: 180s)"`
Interim time.Duration ` long:"interim" default:"1s" description:"interval time after successful request for consecutive mode"`
Consecutive int ` long:"consecutive" default:"1" description:"number of consecutive successful requests required (max.: 5)"`
WaitForInterval time.Duration ` long:"wait-for-interval" default:"2s" description:"Retry interval"`
WaitForMax time.Duration ` long:"wait-for-max" description:"Time to wait for success (max.: 180s)"`
Interim time.Duration ` long:"interim" default:"1s" description:"Interval time after successful request for consecutive mode"`
Consecutive int ` long:"consecutive" default:"1" description:"Number of consecutive successful requests required (max.: 5)"`
Port int `short:"p" long:"port" description:"Port number"`
MaxRedirects int ` long:"max-redirs" description:"Maximum redirects before giving up on following"`
NoDiscard bool ` long:"no-discard" description:"raise error when the response body is larger then max-buffer-size"`
WaitFor bool ` long:"wait-for" description:"retry until successful when enabled"`
SSL bool `short:"S" long:"ssl" description:"use https"`
SNI bool ` long:"sni" description:"enable SNI"`
TCP4 bool `short:"4" description:"use tcp4 only"`
TCP6 bool `short:"6" description:"use tcp6 only"`
NoDiscard bool ` long:"no-discard" description:"Raise error when the response body is larger then max-buffer-size"`
WaitFor bool ` long:"wait-for" description:"Retry until successful when enabled"`
SSL bool `short:"S" long:"ssl" description:"Use https"`
SNI bool ` long:"sni" description:"Enable SNI"`
TCP4 bool `short:"4" description:"Use tcp4 only"`
TCP6 bool `short:"6" description:"Use tcp6 only"`
Verbose bool `short:"v" long:"verbose" description:"Show verbose output"`
ShowBody bool ` long:"show-body" description:"Print body content below status line"`
IgnoreCertificateChain bool ` long:"ignore-certificate-chain" description:"by default all certificates are checked in many aspects. Toggle this option to only check the leaf (final) certificate."`
IgnoreCertificateChain bool ` long:"ignore-certificate-chain" description:"During certificate check, all certificates are checked in many aspects. Toggle this option to only check the leaf (final) certificate."`
CheckCN bool ` long:"check-cn" description:"Subject Common Name of leaf certificate can be checked to match hostname exactly. Common Name field is now largely unused in modern web, with Subject Alternative Name fields being more prevalent and used instead of Common Name when present. It is not checked by default, use this flag to enable it."`
CheckSAN bool ` long:"check-san" description:"Subject Alternative Names can be checked against the hostname. SANs contain the hostnames and IP addresses this certificate is valid for. They are ignored if the certificate is a Certificate Authority type, meaning they are used to sign other certificates and not for proving security for a hostname. It is not checked by default, use this flag to enable it."`
IgnoreNotAfter bool ` long:"ignore-not-after" description:"Certificates are invalid after the timestamp in their NotAfter has passed. This field can be ignored with this flag."`
Expand All @@ -115,6 +115,18 @@ func (opts *commandOpts) tracef(format string, args ...any) {
}
}

func (opts *commandOpts) debugf(format string, args ...any) {
if !opts.flags.Verbose {
return
}

if opts.log != nil {
opts.log.Debugf(format, args...)
} else {
log.Printf(format, args...)
}
}

func makeTLSConfig(opts *commandOpts) (conf *tls.Config) {
//nolint:gosec // TLS check is deliberately skipped, certificate checks are done in its separate function
conf = &tls.Config{
Expand Down Expand Up @@ -158,10 +170,17 @@ func makeDialer(opts *commandOpts) func(ctx context.Context, _ string, _ string)
tcpMode = "tcp6"
}

dialFunc := func(ctx context.Context, _, _ string) (net.Conn, error) {
addr := net.JoinHostPort(opts.flags.IPAddress, strconv.Itoa(opts.flags.Port))
dialFunc := func(ctx context.Context, _ string, addr string) (net.Conn, error) {
// when a proxy is configured, the http transport passes the proxy address as addr, need to dial the proxy instead of the target
if opts.flags.Proxy != "" && addr != "" {
return baseDialFunc(ctx, tcpMode, addr)
}

// otherwise it according to -I/-p
// also used by the -C certificate check which calls dialFunc with an empty addr
targetAddr := net.JoinHostPort(opts.flags.IPAddress, strconv.Itoa(opts.flags.Port))

return baseDialFunc(ctx, tcpMode, addr)
return baseDialFunc(ctx, tcpMode, targetAddr)
}

return dialFunc
Expand All @@ -171,16 +190,40 @@ func makeDialer(opts *commandOpts) func(ctx context.Context, _ string, _ string)
func makeTransport(opts *commandOpts, dialFunc func(ctx context.Context, _ string, _ string) (net.Conn, error), tlsConfig *tls.Config) (http.RoundTripper, error) {
proxy := http.ProxyFromEnvironment

var parsedURL *url.URL
proxyScheme := ""

if opts.flags.Proxy != "" {
parsedURL, err := url.Parse(opts.flags.Proxy)
var err error
parsedURL, err = url.Parse(opts.flags.Proxy)
if err != nil {
return nil, fmt.Errorf("Error while parsing Proxy URL. Error was: %s", err.Error())
}

opts.debugf("Proxy used: %q", parsedURL)

proxyScheme = parsedURL.Scheme
if proxyScheme == "" {
proxyScheme = "http"
}

opts.debugf("Proxy is using scheme: %q", proxyScheme)

switch proxyScheme {
case "https":
opts.debugf("This means a TLS connection will be established to the proxy")
case "socks5", "socks5h":
opts.debugf("This means the proxy will resolve the target hostname")
case "socks4a":
opts.debugf("socks4a is not supported by the go http client, only socks5/socks5h is supported")
default:
opts.debugf("Using proxy with unsupported scheme: %q", proxyScheme)
}

proxy = http.ProxyURL(parsedURL)
}

return &http.Transport{
transport := &http.Transport{
// inherited http.DefaultTransport
Proxy: proxy,
DialContext: dialFunc,
Expand All @@ -191,7 +234,32 @@ func makeTransport(opts *commandOpts, dialFunc func(ctx context.Context, _ strin
ResponseHeaderTimeout: opts.TimeoutParsed,
TLSClientConfig: tlsConfig,
ForceAttemptHTTP2: true,
}, nil
}

if proxyScheme == "https" {
opts.debugf("The proxy certificate will be verified")

proxyTLSConfig := makeProxyTLSConfig(opts, parsedURL)
transport.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := dialFunc(ctx, network, addr)
if err != nil {
return nil, err
}

return tls.Client(conn, proxyTLSConfig), nil
}
}

return transport, nil
}

// makeProxyTLSConfig returns a tls config that verifies the certificate of an https proxy.
func makeProxyTLSConfig(opts *commandOpts, proxyURL *url.URL) *tls.Config {
return &tls.Config{
ServerName: proxyURL.Hostname(),
MinVersion: opts.tlsMinVersion,
MaxVersion: opts.tlsMaxVersion,
}
}

func buildRequest(ctx context.Context, opts *commandOpts) (*http.Request, error) {
Expand Down
Loading