diff --git a/Changes b/Changes index bcec956c..ae1bb9d3 100644 --- a/Changes +++ b/Changes @@ -6,6 +6,7 @@ next: - check_eventlog: translate event type and source filter conditions into the WMI query - add ppc64le rpm/deb builds - update windows exporter to 0.32.0 + - check_http: add -P/--post to send request data 1.00 Fri Oct 16 16:47:03 CEST 2026 - add check_multi: execute multiple subchecks and evaluate the overall result diff --git a/docs/checks/plugins/check_http.md b/docs/checks/plugins/check_http.md index fb358c8a..de4e86fa 100644 --- a/docs/checks/plugins/check_http.md +++ b/docs/checks/plugins/check_http.md @@ -21,94 +21,87 @@ It basically wraps the plugin from https://github.com/sni/check_http_go check_http [OPTIONS] Application Options: - -H, --hostname= Host name using Host headers - -I, --IP-address= IP address or Host name - -j, --method= Set HTTP Method (default: GET) - -u, --uri= URI to request (default: /) - -e, --expect= Comma-delimited list of expected HTTP response - status. By default, 1XX, 2XX are OK, 3XX depends on - --onredirect option, 4XX are WARNING, 5XX are CRITICAL - -s, --string= String to expect in the content - --base64-string= Base64-encoded string to expect in the content - -A, --useragent= UserAgent to be sent (default: check_http) - -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 - mandatory days left to warn and optional days to crit - with a comma: warn_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 - the maximum TLS version as well to latest version: 1.3 - --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 - -f, --onredirect=[ok|warning|critical|follow|sticky|stickyport] What strategy to use when encountering a redirect. - ok/warning/critical returns immediately. follow uses - the new URL returned by golang HTTP client. Sticky - keeps the hostname the same after redirect, and - stickyport persists the port as well. - --max-buffer-size= Max buffer size to read response body (max.: 100MB) - (default: 1MB) - -t, --timeout= Timeout to wait for connection. If no time unit is - given at the end, default of seconds is assumed - (default: 10) - -w, --warning= If the request+response takes longer than the - 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. - (default: 30) - -c, --critical= If the request+response takes longer than the - 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. - (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 - consecutive mode (default: 1s) - --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 than - 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 - -v, --verbose Show verbose output - --show-body Print body content below status line - --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 - 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. - --check-san 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. - --ignore-not-after Certificates are invalid after the timestamp in their - NotAfter has passed. This field can be ignored with - this flag. - --ignore-not-before Certificates are invalid before the timestamp in - their NotBefore is reached. This field can be ignored - with this flag. - --ignore-signature-algorithm Some signature algorithms are deemed insecure, and - are deprecated. The algorithm used can be ignored - with this flag. + -H, --hostname= Host name using Host headers + -I, --IP-address= IP address or Host name + -j, --method= Set HTTP Method (default: GET) + -P, --post= Data to send in the request body. Defaults to POST unless --method + is set. Set the Content-Type header using --header (e.g. --header + 'Content-Type: application/json') + -u, --uri= URI to request (default: /) + -e, --expect= Comma-delimited list of expected HTTP response status. By default, + 1XX, 2XX are OK, 3XX depends on --onredirect option, 4XX are + WARNING, 5XX are CRITICAL + -s, --string= String to expect in the content + --base64-string= Base64-encoded string to expect in the content + -A, --useragent= UserAgent to be sent (default: check_http) + -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 mandatory days + left to warn and optional days to crit with a comma: + warn_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 the maximum TLS + version as well to latest version: 1.3 + --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 + -f, --onredirect= Redirect strategy: ok, warning, critical, follow, sticky, + stickyport. ok/warning/critical returns immediately. follow uses + the new URL returned by golang HTTP client. sticky keeps the + hostname the same after redirect, and stickyport persists the port + as well. + --max-buffer-size= Max buffer size to read response body (max.: 100MB) (default: 1MB) + -t, --timeout= Timeout to wait for connection. If no time unit is given at the + end, default of seconds is assumed (default: 10) + -w, --warning= If the request+response takes longer than the 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. + (default: 30) + -c, --critical= If the request+response takes longer than the 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. + (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 consecutive mode + (default: 1s) + --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 than 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 + -v, --verbose Show verbose output + --show-body Print body content below status line + --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 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. + --check-san 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. + --ignore-not-after Certificates are invalid after the timestamp in their NotAfter has + passed. This field can be ignored with this flag. + --ignore-not-before Certificates are invalid before the timestamp in their NotBefore is + reached. This field can be ignored with this flag. + --ignore-signature-algorithm Some signature algorithms are deemed insecure, and are deprecated. + The algorithm used can be ignored with this flag. Help Options: - -h, --help Show this help message + -h, --help Show this help message ``` ## Examples diff --git a/pkg/check_http/check_http.go b/pkg/check_http/check_http.go index 54914730..9ef3d84e 100644 --- a/pkg/check_http/check_http.go +++ b/pkg/check_http/check_http.go @@ -63,6 +63,7 @@ type commandOpts struct { Hostname string `short:"H" long:"hostname" description:"Host name using Host headers"` IPAddress string `short:"I" long:"IP-address" description:"IP address or Host name"` Method string `short:"j" long:"method" default:"GET" description:"Set HTTP Method"` + Post *string `short:"P" long:"post" description:"Data to send in the request body. Defaults to POST unless --method is set. Set the Content-Type header using --header (e.g. --header 'Content-Type: application/json')"` URI string `short:"u" long:"uri" default:"/" description:"URI to request"` ExpectStr string `short:"e" long:"expect" default:"" description:"Comma-delimited list of expected HTTP response status. By default, 1XX, 2XX are OK, 3XX depends on --onredirect option, 4XX are WARNING, 5XX are CRITICAL"` ExpectContent string `short:"s" long:"string" description:"String to expect in the content"` @@ -76,7 +77,7 @@ type commandOpts struct { 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"` - Onredirect string `short:"f" long:"onredirect" description:"What strategy to use when encountering a redirect. ok/warning/critical returns immediately. follow uses the new URL returned by golang HTTP client. Sticky keeps the hostname the same after redirect, and stickyport persists the port as well." choice:"ok" choice:"warning" choice:"critical" choice:"follow" choice:"sticky" choice:"stickyport"` + Onredirect string `short:"f" long:"onredirect" value-name:"" description:"Redirect strategy: ok, warning, critical, follow, sticky, stickyport. ok/warning/critical returns immediately. follow uses the new URL returned by golang HTTP client. sticky keeps the hostname the same after redirect, and stickyport persists the port as well."` MaxBufferSize string ` long:"max-buffer-size" default:"1MB" description:"Max buffer size to read response body (max.: 100MB)"` 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 than the 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."` @@ -293,13 +294,16 @@ func buildRequest(ctx context.Context, opts *commandOpts) (*http.Request, error) uri := fmt.Sprintf("%s://%s%s", schema, host, opts.flags.URI) - var buffer bytes.Buffer + var body io.Reader = http.NoBody + if opts.flags.Post != nil { + body = strings.NewReader(*opts.flags.Post) + } req, err := http.NewRequestWithContext( ctx, opts.flags.Method, uri, - &buffer, + body, ) if err != nil { //nolint:wrapcheck // 3rd party source code imported @@ -999,6 +1003,21 @@ func Check(ctx context.Context, output io.Writer, osArgs []string) int { return UNKNOWN } + if option := psr.FindOptionByLongName("onredirect"); option.IsSet() { + switch opts.flags.Onredirect { + case "ok", "warning", "critical", "follow", "sticky", "stickyport": + default: + fmt.Fprintf(output, "Invalid value `%s' for option `%s'. Allowed values are: ok, warning, critical, follow, sticky or stickyport\n", + opts.flags.Onredirect, option) + + return UNKNOWN + } + } + + if opts.flags.Post != nil && psr.FindOptionByLongName("method").IsSetDefault() { + opts.flags.Method = http.MethodPost + } + var loggerCastOk bool opts.log, loggerCastOk = ctx.Value(snclientLoggerContextKey).(*factorlog.FactorLog) diff --git a/pkg/check_http/check_http_test.go b/pkg/check_http/check_http_test.go index e8f30865..6b7325a5 100644 --- a/pkg/check_http/check_http_test.go +++ b/pkg/check_http/check_http_test.go @@ -25,6 +25,62 @@ const ( testURI = "/legal-notice/" ) +func TestHTTPPost(t *testing.T) { + jsonData := "{\n \"name\": \"example\",\n \"enabled\": true\n}" + for _, test := range []struct { + name string + args []string + method string + body string + contentType string + }{ + {name: "default GET", method: http.MethodGet}, + { + name: "short option", args: []string{"-P", "name=value&other=a+b"}, + method: http.MethodPost, body: "name=value&other=a+b", + }, + { + name: "JSON", args: []string{"--post=" + jsonData, "-k", "Content-Type: application/json"}, + method: http.MethodPost, body: jsonData, contentType: "application/json", + }, + { + name: "empty POST", args: []string{"--post="}, + method: http.MethodPost, + }, + { + name: "explicit PUT", args: []string{"-j", "PUT", "--post=payload"}, + method: http.MethodPut, body: "payload", + }, + { + name: "explicit GET", args: []string{"--post=payload", "--method=GET"}, + method: http.MethodGet, body: "payload", + }, + } { + t.Run(test.name, func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) { + body, err := io.ReadAll(req.Body) + assert.NoError(t, err) + assert.Equal(t, test.method, req.Method) + assert.Equal(t, test.body, string(body)) + assert.Equal(t, test.contentType, req.Header.Get("Content-Type")) + assert.Equal(t, "/submit", req.URL.Path) + fmt.Fprint(writer, "request received") + })) + defer srv.Close() + + target, err := url.Parse(srv.URL) + require.NoError(t, err) + args := make([]string, 0, 7+len(test.args)) + args = append(args, "check_http", "-H", target.Host, "-u", "/submit", "-s", "request received") + args = append(args, test.args...) + + var output strings.Builder + code := Check(t.Context(), &output, args) + assert.Equal(t, OK, code, output.String()) + }) + } +} + func TestHTTP(t *testing.T) { if testing.Short() { t.Skip("skipping network test in short mode")