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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,7 @@ DOC_PLUGINS=\
check_http \
check_nsc_web \
check_tcp \
check_ssh \

# skip markdown help files for these commands
DOC_EXCLUDES=\
Expand Down
85 changes: 85 additions & 0 deletions docs/checks/plugins/check_ssh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
title: check_ssh
---

## check_ssh

Runs check_tcp with an SSH configururation to check for a running SSH server.
It basically wraps the plugin from https://github.com/taku-k/go-check-plugins/tree/master/check-tcp

- [Examples](#examples)
- [Usage](#usage)

## Implementation

| Windows | Linux | FreeBSD | MacOSX |
|:------------------:|:------------------:|:------------------:|:------------------:|
| :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |

## Examples

### Default Check

check_ssh github.com
SSH OK - 0.234 seconds response time on github.com port 22 [SSH-2.0-8ad108e] | time=0.234029s;;;0.000000;10.000000

check_ssh --hostname github.com --warning 1
SSH OK - 0.262 seconds response time on github.com port 22 [SSH-2.0-8ad108e] | time=0.262048s;;;1.000000;10.000000

### Example using NRPE and Naemon

Naemon Config

define command{
command_name check_nrpe
command_line $USER1$/check_nrpe -H $HOSTADDRESS$ -n -c $ARG1$ -a $ARG2$
}

define service {
host_name testhost
service_description check_ssh
use generic-service
check_command check_nrpe!check_ssh!'-H' '192.168.178.100' '-p' '2323'
}

## Usage

```Usage:
check_tcp [OPTIONS]

Application Options:
--service= Service name. e.g. ftp, smtp, pop, imap and so on
-H, --hostname= Host name or IP Address
-p, --port= Port number
-s, --send= String to send to the server
-e, --expect-pattern= Regexp pattern to expect in server response
-q, --quit= String to send server to initiate a clean close
of the connection
-S, --ssl Use SSL for the connection.
-U, --unix-sock= Unix Domain Socket
--no-check-certificate Do not check certificate
-t, --timeout= Seconds before connection times out (default: 10)
-m, --maxbytes= Close connection once more than this number of
bytes are received
-d, --delay= Seconds to wait between sending string and
polling for response
-w, --warning= Response time to result in warning status
(seconds)
-c, --critical= Response time to result in critical status
(seconds) (default: 10)
-E, --escape Can use \n, \r, \t or \ in send or quit string.
Must come before send or quit option. By default,
nothing added to send, \r\n added to end of quit
-W, --error-warning Set the error level to warning when exiting with
unexpected error (default: critical). In the case
of request succeeded, evaluation result of -c
option eval takes priority.
-C, --expect-closed Verify that the port/unixsock is closed. If the
port/unixsock is closed, OK; if open, follow the
ErrWarning flag. This option only verifies the
connection.
-v, --verbose Enables verbose logging of the actions taken.

Help Options:
-h, --help Show this help message
```
1 change: 1 addition & 0 deletions docs/checks/plugins/check_tcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Application Options:
port/unixsock is closed, OK; if open, follow the
ErrWarning flag. This option only verifies the
connection.
-v, --verbose Enables verbose logging of the actions taken.

Help Options:
-h, --help Show this help message
Expand Down
55 changes: 55 additions & 0 deletions pkg/check_tcp/check_ssh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package check_tcp

import (
"context"
"fmt"
"io"
"strings"
)

func CheckSSH(_ context.Context, output io.Writer, args []string, sendString string) int {
opts, err := parseArgs(args)
if err != nil {
fmt.Fprintf(output, "%s", err.Error())

return 2
}

if opts.Port == 0 {
opts.Port = 22
}

if opts.Timeout > opts.Critical && opts.Critical != 0 && opts.Verbose {
fmt.Fprintf(output, "Timeout to establish connection: %f is higher than critical threshold: %f\n", opts.Timeout, opts.Critical)
}

if opts.Timeout > opts.Warning && opts.Warning != 0 && opts.Verbose {
fmt.Fprintf(output, "Timeout to establish connection: %f is higher than warning threshold: %f\n", opts.Timeout, opts.Warning)
}

opts.Send = sendString
// SSH Tcp connections print out a string:
// nc -v github.com 22
// Connection to github.com (140.82.121.4) 22 port [tcp/ssh] succeeded!
// SSH-2.0-8ad108e

// RFC 4253:4.2
// https://datatracker.ietf.org/doc/html/rfc4253 , Page 5
// The third field consists of any printable ASCII characters
// EXCEPT whitespaces and minus sign
// The group at the end consists of two parts
// !- includes ASCII characters incl. '!' until '-' but not '-'
// .~ includes ASCII characters incl. '.' until '-' but not '~'
// this skips over the minus sign
opts.ExpectPattern = `^SSH-\d+\.\d+-[!-,.-~]+`

ckr := opts.run(output)
ckr.Name = "SSH"
if opts.Service != "" {
ckr.Name = opts.Service
}

fmt.Fprintf(output, "%s %s - %s", ckr.Name, ckr.Status, strings.TrimSpace(ckr.Message))

return int(ckr.Status)
}
40 changes: 38 additions & 2 deletions pkg/check_tcp/check_tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"net"
"os"
"regexp"
"slices"
"strings"
"time"

Expand All @@ -17,13 +18,29 @@ import (
)

func Check(ctx context.Context, output io.Writer, args []string) int {
// snclient supports short arguments with multiple chars like -v and not -vv or -vvv
// if snclient agent has these verbose arguments, they are passed as is to internal checks
// if that is the case, delete them and add -v instead.
hadVerbose := false
args = slices.DeleteFunc(args, func(s string) bool {
isVerbose := s == "-v" || s == "-vv" || s == "-vvv"
if isVerbose {
hadVerbose = true
}

return isVerbose
})
if hadVerbose {
args = append(args, "-v")
}

opts, err := parseArgs(args)
if err != nil {
fmt.Fprintf(output, "%s", err.Error())
return 2
}

ckr := opts.run()
ckr := opts.run(output)
ckr.Name = "TCP"
if opts.Service != "" {
ckr.Name = opts.Service
Expand All @@ -48,6 +65,7 @@ type tcpOpts struct {
Escape bool `short:"E" long:"escape" description:"Can use \\n, \\r, \\t or \\ in send or quit string. Must come before send or quit option. By default, nothing added to send, \\r\\n added to end of quit"`
ErrWarning bool `short:"W" long:"error-warning" description:"Set the error level to warning when exiting with unexpected error (default: critical). In the case of request succeeded, evaluation result of -c option eval takes priority."`
ExpectClosed bool `short:"C" long:"expect-closed" description:"Verify that the port/unixsock is closed. If the port/unixsock is closed, OK; if open, follow the ErrWarning flag. This option only verifies the connection."`
Verbose bool `short:"v" long:"verbose" description:"Enables verbose logging of the actions taken."`
}

type exchange struct {
Expand Down Expand Up @@ -120,6 +138,11 @@ var defaultExchangeMap = map[string]exchange{
Send: "version\n",
ExpectPattern: `\A[0-9]+\.[0-9]+\n\z`,
},
"SSH": {
Port: 22,
Send: "SSH-1.0-check_tcp",
ExpectPattern: `^SSH-\d+\.\d+-[!-,.-~]+`,
},
}

func (opts *tcpOpts) prepare() error {
Expand Down Expand Up @@ -174,7 +197,7 @@ func dial(network, address string, ssl bool, noCheckCertificate bool, timeout ti
return d.Dial(network, address)
}

func (opts *tcpOpts) run() *checkers.Checker {
func (opts *tcpOpts) run(output io.Writer) *checkers.Checker {
err := opts.prepare()
if err != nil {
return checkers.Unknown(err.Error())
Expand Down Expand Up @@ -202,6 +225,9 @@ func (opts *tcpOpts) run() *checkers.Checker {
time.Sleep(time.Duration(opts.Delay) * time.Second)
}

if opts.Verbose {
fmt.Fprintf(output, "Establishing a connection to addr: %s protocol: %s ssl: %t noCheckCertificate: %t timeout: %f\n", addr, proto, opts.SSL, opts.NoCheckCertificate, timeout.Seconds())
}
conn, err := dial(proto, addr, opts.SSL, opts.NoCheckCertificate, timeout)
if err != nil {
if opts.ExpectClosed {
Expand Down Expand Up @@ -234,6 +260,9 @@ func (opts *tcpOpts) run() *checkers.Checker {
}

if opts.Send != "" {
if opts.Verbose {
fmt.Fprintf(output, "Writing to the socket: %s\n", opts.Send)
}
err := write(conn, []byte(opts.Send), timeout)
if err != nil {
if opts.ErrWarning {
Expand All @@ -258,10 +287,17 @@ func (opts *tcpOpts) run() *checkers.Checker {
return checkers.Warning("Unexpected response from host/socket: " + res)
}
return checkers.Critical("Unexpected response from host/socket: " + res)
} else {
if opts.Verbose {
fmt.Fprintf(output, "Result matched expected regex: '%s'\n", opts.ExpectPattern)
}
}
}

if opts.Quit != "" {
if opts.Verbose {
fmt.Fprintf(output, "Writing to the socket for quitting: %s\n", opts.Quit)
}
err := write(conn, []byte(opts.Quit), timeout)
if err != nil {
if opts.ErrWarning {
Expand Down
1 change: 1 addition & 0 deletions pkg/snclient/builtin_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func (l *CheckBuiltin) Check(ctx context.Context, snc *Agent, check *CheckData,

args := []string{}
args = append(args, check.rawArgs...)
// if snclient is started with verbose arguments, pass them to internal check as well
switch {
case snc.flags.Verbose >= 3:
args = append(args, "-vvv")
Expand Down
57 changes: 57 additions & 0 deletions pkg/snclient/check_ssh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package snclient

import (
"context"
"fmt"
"io"
"runtime"
"slices"

"github.com/consol-monitoring/snclient/pkg/check_tcp"
)

func init() {
AvailableChecks["check_ssh"] = CheckEntry{"check_ssh", NewCheckSSH}
}

func NewCheckSSH() CheckHandler {
return &CheckBuiltin{
name: "check_ssh",
description: `Runs check_tcp with an SSH configururation to check for a running SSH server.
It basically wraps the plugin from https://github.com/taku-k/go-check-plugins/tree/master/check-tcp`,
check: checkSSH,
docTitle: `check_ssh`,
usage: `check_ssh [<options>]`,
exampleDefault: `
check_ssh github.com
SSH OK - 0.234 seconds response time on github.com port 22 [SSH-2.0-8ad108e] | time=0.234029s;;;0.000000;10.000000

check_ssh --hostname github.com --warning 1
SSH OK - 0.262 seconds response time on github.com port 22 [SSH-2.0-8ad108e] | time=0.262048s;;;1.000000;10.000000
`,
exampleArgs: `'-H' '192.168.178.100' '-p' '2323'`,
}
}

func checkSSH(ctx context.Context, output io.Writer, args []string) int {
// snclient supports short arguments with multiple chars like -v and not -vv or -vvv
// if snclient agent has these verbose arguments, they are passed as is to internal checks
// if that is the case, delete them and add -v instead.
hadVerbose := false
args = slices.DeleteFunc(args, func(s string) bool {
isVerbose := s == "-v" || s == "-vv" || s == "-vvv"
if isVerbose {
hadVerbose = true
}

return isVerbose
})
if hadVerbose {
args = append(args, "-v")
}

// the string to be sent on the connection
sendStr := fmt.Sprintf("SSH-1.0-snclient_build_%s_runtime_%s", Build, runtime.Version())

return check_tcp.CheckSSH(ctx, output, args, sendStr)
}
35 changes: 35 additions & 0 deletions pkg/snclient/check_ssh_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package snclient

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestCheckSSH(t *testing.T) {
config := `
[/modules]
CheckBuiltinPlugins = enabled
`
snc := StartTestAgent(t, config)

res := snc.RunCheck("check_ssh", []string{"-H", "github.com", "-p", "22"})
assert.Equalf(t, CheckExitOK, res.State, "state ok")
assert.Regexpf(
t,
`^SSH OK - [\d.]+ seconds response time on github.com port 22`,
string(res.BuildPluginOutput()),
"output matches",
)

res = snc.RunCheck("check_ssh", []string{"-H", "bitbucket.org"})
assert.Equalf(t, CheckExitOK, res.State, "state ok")
assert.Regexpf(
t,
`^SSH OK - [\d.]+ seconds response time on bitbucket.org port 22`,
string(res.BuildPluginOutput()),
"output matches",
)

StopTestAgent(t, snc)
}
3 changes: 2 additions & 1 deletion pkg/snclient/check_tcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ use ssl = false

res := snc.RunCheck("check_tcp", []string{"-H", "localhost", "-p", "45666"})
assert.Equalf(t, CheckExitOK, res.State, "state ok")
assert.Regexpf(t,
assert.Regexpf(
t,
`^TCP OK - [\d.]+ seconds response time on localhost port 45666`,
string(res.BuildPluginOutput()),
"output matches",
Expand Down
Loading