Issue
Code backup
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
another-http-check
|
||||
rpmbuild/*
|
||||
*.rpm
|
||||
@@ -0,0 +1,2 @@
|
||||
%_topdir /app/rpmbuild
|
||||
%_tmppath /app/rpmbuild/tmp
|
||||
@@ -0,0 +1,31 @@
|
||||
FROM golang:1.13-alpine
|
||||
|
||||
ARG APP_GID
|
||||
ARG APP_USER
|
||||
|
||||
RUN apk add gcc g++ ca-certificates git curl vim rpm
|
||||
|
||||
RUN adduser -D -u ${APP_GID} -g ${APP_USER} ${APP_USER}
|
||||
|
||||
ADD .rpmmacros /home/${APP_USER}
|
||||
|
||||
USER ${APP_USER}
|
||||
|
||||
RUN curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
|
||||
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
|
||||
|
||||
RUN echo "call plug#begin('~/.vim/plugged')" > ~/.vimrc
|
||||
RUN echo "Plug 'fatih/vim-go', { 'do': ':GoUpdateBinaries' }" >> ~/.vimrc
|
||||
RUN echo "call plug#end()" >> ~/.vimrc
|
||||
RUN echo "set tabstop=4" >> ~/.vimrc
|
||||
RUN echo "set expandtab" >> ~/.vimrc
|
||||
RUN echo "set shiftwidth=4" >> ~/.vimrc
|
||||
RUN echo "set t_Co=256" >> ~/.vimrc
|
||||
RUN echo "set number" >> ~/.vimrc
|
||||
|
||||
RUN vim -E -s -u ~/.vimrc +PlugInstall +qall
|
||||
|
||||
ENV GO111MODULE=on
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2018 wf tech, s.r.o.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,39 @@
|
||||
CWD=$(shell pwd)
|
||||
APP_GID=$(shell id --group)
|
||||
APP_USER=${USER}
|
||||
CONTAINER_NAME=another-http-check
|
||||
BIN_NAME=another-http-check
|
||||
RPM_SPEC_NAME=another-http-check.spec
|
||||
GO_VERSION=$(shell grep FROM Dockerfile | awk '{ print $$2 }' | sed 's/[a-z:-]//g' | xargs echo -n)
|
||||
APP_VERSION=$(shell date +"%Y%m%d")
|
||||
LDFLAGS=-ldflags "-X main.goVersion=$(GO_VERSION) -X main.appVersion=$(APP_VERSION)"
|
||||
|
||||
default: binary
|
||||
|
||||
test: build
|
||||
docker run -v $(CWD):/app -it --rm $(CONTAINER_NAME) \
|
||||
go test -v $(LDFLAGS)
|
||||
|
||||
binary: build clean
|
||||
docker run -v $(CWD):/app -it --rm $(CONTAINER_NAME) \
|
||||
go build $(LDFLAGS) \
|
||||
-tags netgo -installsuffix netgo -o $(BIN_NAME)
|
||||
|
||||
rpm: binary
|
||||
rm -rf rpmbuild
|
||||
docker run -v $(CWD):/app -it --rm $(CONTAINER_NAME) \
|
||||
rpmbuild -ba $(RPM_SPEC_NAME)
|
||||
cp rpmbuild/RPMS/x86_64/*.rpm .
|
||||
|
||||
runshell: build
|
||||
docker run -v $(CWD):/app -it --rm $(CONTAINER_NAME) sh
|
||||
|
||||
clean:
|
||||
rm -f $(BIN_NAME)
|
||||
|
||||
upgrade-dependencies: build
|
||||
docker run -v $(CWD):/app -it --rm $(CONTAINER_NAME) go get -u
|
||||
|
||||
build:
|
||||
docker build --build-arg APP_GID=$(APP_GID) --build-arg=APP_USER=$(APP_USER) \
|
||||
-t $(CONTAINER_NAME) .
|
||||
@@ -0,0 +1,48 @@
|
||||
# another-http-check
|
||||
|
||||
This is replacement for original Nagios `check_http` check plugin. The original plugin contains some bugs and
|
||||
provides sometimes misleading error messages.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
another-http-check [OPTIONS]
|
||||
|
||||
|
||||
| Application Options: | |
|
||||
|----------------------|---------------------------------------------------------------------------------|
|
||||
| `-H=` | Host ex. google.com |
|
||||
| `-I=` | IPv4 address ex. 8.8.4.4 |
|
||||
| `-u`, `--uri=` | URI to check (default: /) |
|
||||
| `-p=` | Port ex. 80 for HTTP 443 for HTTPS (default: 80) |
|
||||
| `-S`, `--tls` | Use HTTPS |
|
||||
| `-t`, `--timeout=` | Timeout (default: 30) |
|
||||
| `--auth-basic` | Use HTTP basis |
|
||||
| `--auth-ntlm` | Use NTLM auth |
|
||||
| `-a`, `--auth=` | provide password to authenticate. example `user:password` |
|
||||
| `-e`, `--expect=` | Expected HTTP code (default: `200)` |
|
||||
| `-s`, `--string=` | Search for given string in response body |
|
||||
| `-C=` | Check SSL cert expiration |
|
||||
| `-k`, `--insecure` | Controls whether a client verifies the server's certificate chain and host name |
|
||||
| | |
|
||||
| `-v`, `--verbose` | Verbose mode |
|
||||
| `--guess-auth` | Guess auth type (none, basic, NTLM). Generates two requests instead of one |
|
||||
| `-h`, `--help` | Show this help message |
|
||||
|
||||
|
||||
## Build requirements
|
||||
|
||||
- Docker
|
||||
- make
|
||||
|
||||
## How to compile
|
||||
|
||||
- `make` creates statically linked binary
|
||||
- `make test` runs tests
|
||||
- `make runshell` opens shell inside Docker container (`vim` setup for hacking included)
|
||||
- `make rpm` - creates RPM package
|
||||
|
||||
|
||||
## Licence
|
||||
|
||||
Apache 2
|
||||
@@ -0,0 +1,24 @@
|
||||
Name: another-http-check
|
||||
Version: %(date +%%Y%%m%%d)
|
||||
Release: 1
|
||||
Summary: HTTP check for Nagios/Icinga
|
||||
License: APACHE 2
|
||||
|
||||
%description
|
||||
HTTP check for Nagios/Icinga
|
||||
|
||||
%prep
|
||||
# pass
|
||||
|
||||
%build
|
||||
# pass
|
||||
|
||||
%install
|
||||
mkdir -p %{buildroot}/usr/lib64/nagios/plugins/
|
||||
install -m 755 /app/another-http-check %{buildroot}/usr/lib64/nagios/plugins/another-http-check
|
||||
|
||||
%files
|
||||
/usr/lib64/nagios/plugins/another-http-check
|
||||
|
||||
%changelog
|
||||
# pass
|
||||
@@ -0,0 +1,366 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,495 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHTTPCodes(t *testing.T) {
|
||||
statusCodes := [4]int{200, 302, 404, 500}
|
||||
for _, statusCode := range statusCodes {
|
||||
r := &Request{
|
||||
Scheme: "https",
|
||||
Host: "httpbin.org",
|
||||
Port: 443,
|
||||
URI: fmt.Sprintf("/status/%s", strconv.Itoa(statusCode)),
|
||||
Timeout: 30,
|
||||
Verbose: false,
|
||||
}
|
||||
|
||||
var currrentStatusCodes []int
|
||||
currrentStatusCodes = append(currrentStatusCodes, statusCode)
|
||||
e := &Expected{
|
||||
StatusCodes: currrentStatusCodes,
|
||||
}
|
||||
|
||||
msg, code, err := Check(r, e)
|
||||
|
||||
if !strings.HasPrefix(msg, "OK") {
|
||||
t.Errorf("Wrong message [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if code != EXIT_OK {
|
||||
t.Errorf("Wrong exit code [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Returned error is not nil [URI: %s]", r.URI)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPWrongCodes(t *testing.T) {
|
||||
statusCodes := [4]int{200, 302, 404, 500}
|
||||
for _, statusCode := range statusCodes {
|
||||
r := &Request{
|
||||
Scheme: "https",
|
||||
Host: "httpbin.org",
|
||||
Port: 443,
|
||||
URI: fmt.Sprintf("/status/%s", strconv.Itoa(statusCode)),
|
||||
Timeout: 30,
|
||||
Verbose: false,
|
||||
}
|
||||
|
||||
var currrentStatusCodes []int
|
||||
currrentStatusCodes = append(currrentStatusCodes, statusCode+1)
|
||||
e := &Expected{
|
||||
StatusCodes: currrentStatusCodes,
|
||||
}
|
||||
|
||||
msg, code, err := Check(r, e)
|
||||
|
||||
if !strings.HasPrefix(msg, "CRITICAL") {
|
||||
t.Errorf("Wrong message [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if code != EXIT_CRITICAL {
|
||||
t.Errorf("Wrong exit code [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Returned error is not nil [URI: %s]", r.URI)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeout(t *testing.T) {
|
||||
r := &Request{
|
||||
Scheme: "https",
|
||||
Host: "httpbin.org",
|
||||
Port: 443,
|
||||
URI: "/delay/10",
|
||||
Timeout: 5,
|
||||
Verbose: false,
|
||||
}
|
||||
|
||||
var currrentStatusCodes []int
|
||||
currrentStatusCodes = append(currrentStatusCodes, 200)
|
||||
e := &Expected{
|
||||
StatusCodes: currrentStatusCodes,
|
||||
}
|
||||
|
||||
msg, code, err := Check(r, e)
|
||||
|
||||
if !strings.HasPrefix(msg, "CRITICAL") {
|
||||
t.Errorf("Wrong message [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if code != EXIT_CRITICAL {
|
||||
t.Errorf("Wrong exit code [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Returned error is not nil [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if !strings.Contains(msg, "Timeout - No response recieved in") {
|
||||
t.Errorf("Non-timeout message returned [URI: %s]", r.URI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeoutWarning(t *testing.T) {
|
||||
r := &Request{
|
||||
Scheme: "https",
|
||||
Host: "httpbin.org",
|
||||
Port: 443,
|
||||
URI: "/delay/10",
|
||||
WarningTimeout: 5,
|
||||
CriticalTimeout: 15,
|
||||
Verbose: false,
|
||||
}
|
||||
|
||||
var currrentStatusCodes []int
|
||||
currrentStatusCodes = append(currrentStatusCodes, 200)
|
||||
e := &Expected{
|
||||
StatusCodes: currrentStatusCodes,
|
||||
}
|
||||
|
||||
msg, code, err := Check(r, e)
|
||||
|
||||
if !strings.HasPrefix(msg, "WARNING") {
|
||||
t.Errorf("Wrong message [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if code != EXIT_WARNING {
|
||||
t.Errorf("Wrong exit code [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Returned error is not nil [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if !strings.Contains(msg, "Timeout - No response recieved in") {
|
||||
t.Errorf("Non-timeout message returned [URI: %s]", r.URI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeoutCritical(t *testing.T) {
|
||||
r := &Request{
|
||||
Scheme: "https",
|
||||
Host: "httpbin.org",
|
||||
Port: 443,
|
||||
URI: "/delay/10",
|
||||
WarningTimeout: 4,
|
||||
CriticalTimeout: 8,
|
||||
Verbose: false,
|
||||
}
|
||||
|
||||
var currrentStatusCodes []int
|
||||
currrentStatusCodes = append(currrentStatusCodes, 200)
|
||||
e := &Expected{
|
||||
StatusCodes: currrentStatusCodes,
|
||||
}
|
||||
|
||||
msg, code, err := Check(r, e)
|
||||
|
||||
if !strings.HasPrefix(msg, "CRITICAL") {
|
||||
t.Errorf("Wrong message [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if code != EXIT_CRITICAL {
|
||||
t.Errorf("Wrong exit code [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Returned error is not nil [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if !strings.Contains(msg, "Timeout - No response recieved in") {
|
||||
t.Errorf("Non-timeout message returned [URI: %s]", r.URI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAuthSuccess(t *testing.T) {
|
||||
r := &Request{
|
||||
Scheme: "https",
|
||||
Host: "httpbin.org",
|
||||
Port: 443,
|
||||
URI: "/basic-auth/user/password",
|
||||
Timeout: 30,
|
||||
Verbose: false,
|
||||
Authentication: Authentication{
|
||||
Type: AUTH_BASIC,
|
||||
User: "user",
|
||||
Password: "password",
|
||||
},
|
||||
}
|
||||
|
||||
var currrentStatusCodes []int
|
||||
currrentStatusCodes = append(currrentStatusCodes, 200)
|
||||
e := &Expected{
|
||||
StatusCodes: currrentStatusCodes,
|
||||
}
|
||||
|
||||
msg, code, err := Check(r, e)
|
||||
|
||||
if !strings.HasPrefix(msg, "OK") {
|
||||
t.Errorf("Wrong message [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if code != EXIT_OK {
|
||||
t.Errorf("Wrong exit code [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Returned error is not nil [URI: %s]", r.URI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAuthFail(t *testing.T) {
|
||||
r := &Request{
|
||||
Scheme: "https",
|
||||
Host: "httpbin.org",
|
||||
Port: 443,
|
||||
URI: "/basic-auth/user/password",
|
||||
Timeout: 30,
|
||||
Verbose: false,
|
||||
Authentication: Authentication{
|
||||
Type: AUTH_BASIC,
|
||||
User: "user",
|
||||
Password: "password_",
|
||||
},
|
||||
}
|
||||
|
||||
var currrentStatusCodes []int
|
||||
currrentStatusCodes = append(currrentStatusCodes, 200)
|
||||
e := &Expected{
|
||||
StatusCodes: currrentStatusCodes,
|
||||
}
|
||||
|
||||
msg, code, err := Check(r, e)
|
||||
|
||||
if !strings.HasPrefix(msg, "CRITICAL") {
|
||||
t.Errorf("Wrong message [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if code != EXIT_CRITICAL {
|
||||
t.Errorf("Wrong exit code [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Returned error is not nil [URI: %s]", r.URI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsTextOK(t *testing.T) {
|
||||
r := &Request{
|
||||
Scheme: "https",
|
||||
Host: "httpbin.org",
|
||||
Port: 443,
|
||||
URI: "/anything?foobar=baz",
|
||||
Timeout: 30,
|
||||
Verbose: false,
|
||||
}
|
||||
|
||||
var currrentStatusCodes []int
|
||||
currrentStatusCodes = append(currrentStatusCodes, 200)
|
||||
e := &Expected{
|
||||
StatusCodes: currrentStatusCodes,
|
||||
BodyText: "foobar",
|
||||
}
|
||||
|
||||
msg, code, err := Check(r, e)
|
||||
|
||||
if !strings.HasPrefix(msg, "OK") {
|
||||
t.Errorf("Wrong message [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if code != EXIT_OK {
|
||||
t.Errorf("Wrong exit code [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Returned error is not nil [URI: %s]", r.URI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsTextFail(t *testing.T) {
|
||||
r := &Request{
|
||||
Scheme: "https",
|
||||
Host: "httpbin.org",
|
||||
Port: 443,
|
||||
URI: "/anything?foobar=baz",
|
||||
Timeout: 30,
|
||||
Verbose: false,
|
||||
}
|
||||
|
||||
var currrentStatusCodes []int
|
||||
currrentStatusCodes = append(currrentStatusCodes, 200)
|
||||
e := &Expected{
|
||||
StatusCodes: currrentStatusCodes,
|
||||
BodyText: "loremipsum",
|
||||
}
|
||||
|
||||
msg, code, err := Check(r, e)
|
||||
|
||||
if !strings.HasPrefix(msg, "CRITICAL") {
|
||||
t.Errorf("Wrong message [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if code != EXIT_CRITICAL {
|
||||
t.Errorf("Wrong exit code [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Returned error is not nil [URI: %s]", r.URI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLOK(t *testing.T) {
|
||||
r := &Request{
|
||||
Scheme: "https",
|
||||
Host: "httpbin.org",
|
||||
Port: 443,
|
||||
URI: "/anything",
|
||||
Timeout: 30,
|
||||
Verbose: false,
|
||||
}
|
||||
|
||||
var currrentStatusCodes []int
|
||||
currrentStatusCodes = append(currrentStatusCodes, 200)
|
||||
e := &Expected{
|
||||
StatusCodes: currrentStatusCodes,
|
||||
SSLCheck: SSLCheck{
|
||||
Run: true,
|
||||
DaysWarning: 20,
|
||||
DaysCritical: 5,
|
||||
},
|
||||
}
|
||||
|
||||
msg, code, err := Check(r, e)
|
||||
|
||||
if !strings.HasPrefix(msg, "OK") {
|
||||
t.Errorf("Wrong message [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if code != EXIT_OK {
|
||||
t.Errorf("Wrong exit code [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Returned error is not nil [URI: %s]", r.URI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSLWarning(t *testing.T) {
|
||||
r := &Request{
|
||||
Scheme: "https",
|
||||
Host: "httpbin.org",
|
||||
Port: 443,
|
||||
URI: "/anything",
|
||||
Timeout: 30,
|
||||
Verbose: false,
|
||||
}
|
||||
|
||||
var currrentStatusCodes []int
|
||||
currrentStatusCodes = append(currrentStatusCodes, 200)
|
||||
e := &Expected{
|
||||
StatusCodes: currrentStatusCodes,
|
||||
SSLCheck: SSLCheck{
|
||||
Run: true,
|
||||
DaysWarning: 10000,
|
||||
DaysCritical: 5,
|
||||
},
|
||||
}
|
||||
|
||||
msg, code, err := Check(r, e)
|
||||
|
||||
if !strings.HasPrefix(msg, "WARNING") {
|
||||
t.Errorf("Wrong message [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if code != EXIT_WARNING {
|
||||
t.Errorf("Wrong exit code [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Returned error is not nil [URI: %s]", r.URI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAuthDetect(t *testing.T) {
|
||||
r := &Request{
|
||||
Scheme: "https",
|
||||
Host: "httpbin.org",
|
||||
Port: 443,
|
||||
URI: "/basic-auth/user/password",
|
||||
Timeout: 30,
|
||||
Verbose: false,
|
||||
Authentication: Authentication{
|
||||
Type: AUTH_BASIC,
|
||||
User: "user",
|
||||
Password: "password",
|
||||
},
|
||||
}
|
||||
|
||||
authCode := DetectAuthType(r)
|
||||
|
||||
if authCode != AUTH_BASIC {
|
||||
t.Errorf("Basic auth - wrong auth type detected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoneAuthDetect(t *testing.T) {
|
||||
r := &Request{
|
||||
Scheme: "https",
|
||||
Host: "httpbin.org",
|
||||
Port: 443,
|
||||
URI: "/status/200",
|
||||
Timeout: 30,
|
||||
Verbose: false,
|
||||
}
|
||||
|
||||
authCode := DetectAuthType(r)
|
||||
|
||||
if authCode != AUTH_NONE {
|
||||
t.Errorf("None auth - wrong auth type detected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserAgent(t *testing.T) {
|
||||
r := &Request{
|
||||
Scheme: "https",
|
||||
Host: "httpbin.org",
|
||||
Port: 443,
|
||||
URI: "/headers",
|
||||
Timeout: 30,
|
||||
Verbose: false,
|
||||
}
|
||||
|
||||
var currrentStatusCodes []int
|
||||
currrentStatusCodes = append(currrentStatusCodes, 200)
|
||||
e := &Expected{
|
||||
StatusCodes: currrentStatusCodes,
|
||||
BodyText: fmt.Sprintf("icinga-http-check/%s Go-http-client/%s", appVersion, goVersion),
|
||||
}
|
||||
|
||||
msg, code, err := Check(r, e)
|
||||
|
||||
if !strings.HasPrefix(msg, "OK") {
|
||||
t.Errorf("Wrong message [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if code != EXIT_OK {
|
||||
t.Errorf("Wrong exit code [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Returned error is not nil [URI: %s]", r.URI)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFollowRedirects(t *testing.T) {
|
||||
r := &Request{
|
||||
Scheme: "https",
|
||||
Host: "httpbin.org",
|
||||
Port: 443,
|
||||
URI: "/absolute-redirect/5",
|
||||
FollowRedirects: true,
|
||||
Timeout: 30,
|
||||
Verbose: false,
|
||||
}
|
||||
|
||||
var currrentStatusCodes []int
|
||||
currrentStatusCodes = append(currrentStatusCodes, 200)
|
||||
e := &Expected{
|
||||
StatusCodes: currrentStatusCodes,
|
||||
BodyText: fmt.Sprintf("icinga-http-check/%s Go-http-client/%s", appVersion, goVersion),
|
||||
}
|
||||
|
||||
msg, code, err := Check(r, e)
|
||||
|
||||
if !strings.HasPrefix(msg, "OK") {
|
||||
t.Errorf("Wrong message [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if code != EXIT_OK {
|
||||
t.Errorf("Wrong exit code [URI: %s]", r.URI)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Returned error is not nil [URI: %s]", r.URI)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
module another-http-check
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4
|
||||
github.com/jessevdk/go-flags v1.4.0
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f // indirect
|
||||
golang.org/x/net v0.0.0-20190520210107-018c4d40a106 // indirect
|
||||
golang.org/x/sys v0.0.0-20190520201301-c432e742b0af // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
golang.org/x/tools v0.0.0-20190520220859-26647e34d3c0 // indirect
|
||||
)
|
||||
|
||||
go 1.13
|
||||
@@ -0,0 +1,20 @@
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4 h1:pSm8mp0T2OH2CPmPDPtwHPr3VAQaOwVF/JbllOPP4xA=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
golang.org/x/crypto v0.0.0-20190227175134-215aa809caaf h1:CGelmUfSfeZpx2Pu+OznGcS0ff71WZ/ZOEkhMAB4hVQ=
|
||||
golang.org/x/crypto v0.0.0-20190227175134-215aa809caaf/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190520210107-018c4d40a106/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190520201301-c432e742b0af/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190520220859-26647e34d3c0/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
@@ -0,0 +1,171 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Host string `short:"H" description:"Host ex. google.com" default:""`
|
||||
IPAddress string `short:"I" description:"IPv4 address ex. 8.8.4.4" default:""`
|
||||
URI string `short:"u" long:"uri" description:"URI to check" default:"/"`
|
||||
Port int `short:"p" description:"Port ex. 80 for HTTP 443 for HTTPS" default:"80"`
|
||||
SSL bool `short:"S" long:"tls" description:"Use HTTPS"`
|
||||
Timeout int `short:"t" long:"timeout" description:"Timeout" default:"30"`
|
||||
AuthBasic bool `long:"auth-basic" description:"Use bacis auth"`
|
||||
AuthNtlm bool `long:"auth-ntlm" description:"Use NTLM auth"`
|
||||
Auth string `short:"a" long:"auth" description:"ex. user:password" default:""`
|
||||
ExpectedCode string `short:"e" long:"expect" description:"Expected HTTP code" default:"200"`
|
||||
BodyText string `short:"s" long:"string" description:"Search for given string in response body" default:""`
|
||||
SSLExpiration string `short:"C" description:"Check SSL cert expiration" default:""`
|
||||
SSLNoVerify bool `short:"k" long:"insecure" description:"Controls whether a client verifies the server's certificate chain and host name"`
|
||||
Verbose bool `short:"v" long:"verbose" description:"Verbose mode"`
|
||||
GuessAuth bool `long:"guess-auth" description:"Guess auth type"`
|
||||
FollowRedirects bool `long:"follow-redirects" description:"Follow redirects"`
|
||||
WarningTimeout int `short:"w" description:"Warning timeout" default:"0"`
|
||||
CriticalTimeout int `short:"c" description:"Critical timeout" default:"0"`
|
||||
NoSNI bool `long:"no-sni" description:"Do not use SNI"`
|
||||
ClientCertFile string `short:"J" long:"client-cert" description:"Name of file containing the client certificate (PEM format) to be used in establishing the SSL session"`
|
||||
PrivateKeyFile string `short:"K" long:"private-key" description:"Name of file containing the private key (PEM format) matching the client certificate"`
|
||||
DisableTLSRenegotiation bool `long:"disable-tls-renegotiation" description:"Disable TLS Renegotiation"`
|
||||
}
|
||||
|
||||
var options Options
|
||||
var parser = flags.NewParser(&options, flags.Default)
|
||||
var appVersion string
|
||||
var goVersion string
|
||||
|
||||
func main() {
|
||||
if _, err := parser.Parse(); err != nil {
|
||||
if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp {
|
||||
os.Exit(0)
|
||||
} else {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
var scheme string
|
||||
if options.Port == 443 || options.SSL {
|
||||
scheme = "https"
|
||||
} else {
|
||||
scheme = "http"
|
||||
}
|
||||
|
||||
port := options.Port
|
||||
if scheme == "https" && port == 80 {
|
||||
port = 443
|
||||
}
|
||||
|
||||
authType := AUTH_NONE
|
||||
if options.AuthBasic {
|
||||
authType = AUTH_BASIC
|
||||
}
|
||||
if options.AuthNtlm {
|
||||
authType = AUTH_NTLM
|
||||
}
|
||||
|
||||
var authUser string
|
||||
var authPassword string
|
||||
|
||||
if strings.Contains(options.Auth, ":") {
|
||||
authParts := strings.Split(options.Auth, ":")
|
||||
if len(authParts) != 2 {
|
||||
fmt.Println("UNKNOWN - Username and password not given: provide -a|--auth username:password")
|
||||
os.Exit(EXIT_UNKNOWN)
|
||||
}
|
||||
authUser = authParts[0]
|
||||
authPassword = authParts[1]
|
||||
}
|
||||
|
||||
if authType == AUTH_NONE && len(authUser) > 0 && len(authPassword) > 0 {
|
||||
authType = AUTH_BASIC
|
||||
}
|
||||
|
||||
if len(options.Auth) > 0 && len(authUser) == 0 && len(authPassword) == 0 {
|
||||
fmt.Println("UNKNOWN - Username and password not given: provide -a|--auth username:password")
|
||||
os.Exit(EXIT_UNKNOWN)
|
||||
}
|
||||
|
||||
r := &Request{
|
||||
Host: options.Host,
|
||||
IPAddress: options.IPAddress,
|
||||
URI: options.URI,
|
||||
Port: port,
|
||||
Scheme: scheme,
|
||||
Timeout: options.Timeout,
|
||||
Authentication: Authentication{
|
||||
Type: authType,
|
||||
User: authUser,
|
||||
Password: authPassword,
|
||||
},
|
||||
SSLNoVerify: options.SSLNoVerify,
|
||||
Verbose: options.Verbose,
|
||||
FollowRedirects: options.FollowRedirects,
|
||||
WarningTimeout: options.WarningTimeout,
|
||||
CriticalTimeout: options.CriticalTimeout,
|
||||
NoSNI: options.NoSNI,
|
||||
ClientCert: ClientCert{
|
||||
ClientCertFile: options.ClientCertFile,
|
||||
PrivateKeyFile: options.PrivateKeyFile,
|
||||
},
|
||||
TLSRenegotiation: !options.DisableTLSRenegotiation,
|
||||
}
|
||||
|
||||
if options.GuessAuth {
|
||||
authType = DetectAuthType(r)
|
||||
if r.Verbose {
|
||||
fmt.Println(fmt.Sprintf(">> Detected auth: %s", authLookup[authType]))
|
||||
}
|
||||
r.Authentication.Type = authType
|
||||
}
|
||||
|
||||
var statusCodes []int
|
||||
if strings.Contains(options.ExpectedCode, ",") {
|
||||
for _, code := range strings.Split(options.ExpectedCode, ",") {
|
||||
codeInt, _ := strconv.Atoi(code)
|
||||
statusCodes = append(statusCodes, codeInt)
|
||||
}
|
||||
} else {
|
||||
codeInt, _ := strconv.Atoi(options.ExpectedCode)
|
||||
statusCodes = append(statusCodes, codeInt)
|
||||
}
|
||||
|
||||
var SSLWarning int
|
||||
var SSLCritical int
|
||||
if strings.Contains(options.SSLExpiration, ",") {
|
||||
SSLParts := strings.Split(options.SSLExpiration, ",")
|
||||
if len(SSLParts) != 2 {
|
||||
fmt.Println("UNKNOWN - SSL check has invalid parameters: provide e.g. -C 14,7")
|
||||
os.Exit(EXIT_UNKNOWN)
|
||||
}
|
||||
SSLWarning, _ = strconv.Atoi(SSLParts[0])
|
||||
SSLCritical, _ = strconv.Atoi(SSLParts[1])
|
||||
} else {
|
||||
SSLWarning, _ = strconv.Atoi(options.SSLExpiration)
|
||||
SSLCritical = 0
|
||||
}
|
||||
|
||||
e := &Expected{
|
||||
StatusCodes: statusCodes,
|
||||
BodyText: options.BodyText,
|
||||
SSLCheck: SSLCheck{
|
||||
Run: options.SSL,
|
||||
DaysWarning: SSLWarning,
|
||||
DaysCritical: SSLCritical,
|
||||
},
|
||||
}
|
||||
|
||||
msg, code, err := Check(r, e)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(fmt.Sprintf("UNKNOWN, %s", err.Error()))
|
||||
os.Exit(EXIT_UNKNOWN)
|
||||
}
|
||||
|
||||
fmt.Println(msg)
|
||||
os.Exit(code)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/bin/sh
|
||||
|
||||
test -f another-http-check || exit 1
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -u /status/200 -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -u /status/201 -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -u '/anything?foobar=baz' -s 'foobar' -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -u '/anything?foobar=baz' -s 'fuubar' -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -u /headers -s 'icinga-http-check' -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -u /basic-auth/user/password \
|
||||
--auth-basic -a user:password -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -u /basic-auth/user/password \
|
||||
--auth-basic -a user:password_ -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -u /basic-auth/user/password \
|
||||
-a user:password -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -u /basic-auth/user/password \
|
||||
-a user -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -u /basic-auth/user/password \
|
||||
-a user:password --guess-auth -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -u /delay/10 -t 5 -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -C 15 -S -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -C 999999,999999 -S -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H self-signed.badssl.com -p 443 -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H self-signed.badssl.com -p 443 -k -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -u /absolute-redirect/3 \
|
||||
-v --follow-redirects
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -u /absolute-redirect/3 -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -u /delay/5 -w 4 -c 8 -v
|
||||
echo "Status code: $?"
|
||||
echo
|
||||
|
||||
./another-http-check -H httpbin.org -p 443 -u /delay/10 -w 4 -c 8 -v
|
||||
echo "Status code: $?"
|
||||
Reference in New Issue
Block a user