Giter Club home page Giter Club logo

ssh_exporter's Introduction

Prometheus ssh exporter

The ssh exporter is a Prometheus exporter developed by Nordstorm for running ssh commands on remote hosts and collecting statistics about the output of those commands.

Use with caution

This tool was built for very specific edge case applications where you need to quickly get the results of some existing test script into Prometheus and existing exporters are not flexible enough. Before deciding to use this exporter, consider using a more specialized exporter insted.

Any time you're executing arbitrary code on a host you should be careful.

Double check that your commands are not liable to crash your systems, especially considering that the commands will be run in parallel ssh connections.

Usage

Pre-requisites

You'll need a go-lang environment to build the ssh_exporter binary as well as the following go imports:

import (
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"regexp"
	"strings"
	"sync"
	"time"

	"gopkg.in/yaml.v2"
	"golang.org/x/crypto/ssh"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

Building the ssh exporter

Clone the repository via go go get github.com/Nordstrom/ssh_exporter or git (if cloning the repo by hand you will have to update your $GOPATH) and cd into the directory. Then build the ssh_exporter binary with the following commands.

$ go build

If any packages are not installed, use go get to download them.

Configuration Options

<version>

The version of the config file format. Currently supports one value v0

<scripts>

A list of scripts which might be executed by the exporter.

<name>

A name for the script to be executed. This is what is matched by the pattern URL variable. For example with the followig config:

version: v0
scripts:
  - name: echo_output
    script: echo "output script"
    timeout: 5s
    pattern: 'output [matches|does not match] a regex'
    credentials:
    - host: myhost.example.ext
      port: 22
      user: someuser
      keyfile: /path/to/private/key
    - host: second.host.example.net
  - name: ls_var_tmp
    script: ls /var/tmp
    ...

This request: curl http://localhost:9428/probe?pattern=echo_output

Would execute echo "output script"

And this request: curl http://localhost:9428/probe?pattern=ls_var_tmp

Would likewise execute ls /var/tmp

<script>

The script to execute on the remote host

<timeout>

How long to wait for the command to complete.

<pattern>

A regex pattern to match against the command output. The normal model for scraping with Prometheus is to have the endpoint being scraped return statistical data which is stored rather than return a pass/fail status. Then alerts or reports can be generated against that data. ssh_exporter is intended for edge case applications where you need to quickly get the results of some existing test into Prometheus. It is intended to aid organizations who are migrating from some other monitoring solution to Prometheus. And since Prometheus stores numeric data and not text results the ssh_exporter compares <pattern> against the output of the command and returns a true or false.

<credentials>

A list of endpoints upon which the command specified in <script> will be executed.

<host>

The host name or IP address upon which to run the test.

<port>

The port upon which an ssh daemon is running on the remote host.

<user>

The user to connect as and run the command.

<keyfile>

The ssh private key to use for authentication.

NOTE: ssh_exporter currently only supports private keys with no passphrase.

Example

ssh_exporter config

version: v0
scripts:
  - name: echo_output
    script: echo "This is my output!"
    timeout: 5s
    pattern: '.*output!'
    credentials:
    - host: host1.example.com
      port: 22
      user: someuser
      keyfile: /path/to/private/key
  - name: check_var_temp_for_tars
    script: ls /var/tmp
    timeout: 5s
    pattern: '.*tgz'
    credentials:
    - host: myhost.example.com
      port: 22
      user: someuser
      keyfile: /path/to/private/key
    - host: host2.example.com
      port: 22
      user: someotheruser
      keyfile: /path/to/other/private/key

Prometheus config

scrape_configs:
  - job_name: 'ssh_exporter_check_output'
    static_configs:
      - targets: ['localhost:9428']
    metrics_path: /probe
    params:
      pattern: ['echo_output']

  - job_name: 'ssh_exporter_check_var_tmp'
    static_configs:
      - targets: ['localhost:9428']
    metrics_path: /probe
    params:
      pattern: ['check_var_temp_for_tars']

Running

The config allows one to specify a list of scripts (with timeouts and match patterns) and a list of hosts to run that script on. Scripts are run in parallel with concurrent ssh connections on all configured hosts.

The default configuration file path is ./config.yml. The --config flag overrides this option.

The default port ssh_exporter hosts its data on is 9428; the --port flag overrides this option.

After you have created a config file, start the endpoint server:

$ ./ssh_exporter/ssh_exporter --port=8888 --config=custom_config.yaml

This will start the web server on localhost:8888.

  • localhost:8888/: a human readable navigation page
  • localhost:8888/probe?pattern=<regex-matcher-for-script-names>: statistics based on the scripts in the configuration file
  • localhost:8888/metrics: meta-statics about the app itself.

Prometheus Configuration

scrape_configs:
  - job_name: 'ssh_exporter'
    static_configs:
      - targets: ['localhost:9428']
    metrics_path: /probe
    params:
      pattern: ['.*']

Contributing

There's a lot of work that can be done on the ssh exporter.

If you find an issue with ssh exporter don't want to make the changes yourself, search for the problem on the repos issues page. If the issue or feature request is undocumented, make a new issue.

If you would like to contribute code or documentation, follow these steps:

  1. Clone a local copy.
  2. Make your changes on a uniquely named branch.
  3. Comment those changes.
  4. Test those changes (do as we say not as we do).
  5. Push your branch to a fork and create a Pull Request.

Testing

The tests are split into Unit and Integration.

Unit tests

To run just the Unit tests run the following command:

$ go test -run 'Unit'

The unit tests require the file test/config.yml to exist at the base of the repository.

Integration tests

The Integration tests require a host to run scripts on A Vagrantfile has been provided for you to spin up and quickly use. To use this first install Vagrant and Virtualbox on your local host.

To run just the Integration tests, first spin up the host on which to run the scripts. If you are using the provided Vagrant host, first run the following:

$ vagrant up

This will take a while.

If you would like to configure a different host to run the integration tests on, edit test/config.yml to reflect the changed:

  • Host
  • Port
  • Username
  • Keyfile

WARNING Make sure not to git commit any changes to test/config.yml.

Once you have configured / spun-up the testing host, run the integration tests with the following:

$ go test -run 'Integration'

To destroy the local vagrant host run the following:

$ vagrant destroy   # Respond yes at the prompt

All tests

For all tests, run the following:

$ go test

Future work

Some improvements that come to mind:

  • Safeguards ought to be implemented on the commands being run (beyond just timeout).
  • Addition of script_files to (more easily) run multi-command scripts.
  • Tests! Figure out a better integration test method.

Cutting a release

If you find yourself cutting a release don't panic, it's easy!

  1. Push a git tag vX.Y.Z to the repository.
  2. On your local computer get the repo setup. This will involve installing the dependencies listed at the top of the README.
  3. Run make release. This will create binaries for linux, darwin, and windows as well as a file with some sha256sums.
  4. Upload said binaries to a release targeting the vX.Y.Z tag.

Author

Nordstrom, Inc.

License

Copyright 2017 Nordstrom, Inc.

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.

ssh_exporter's People

Contributors

jason-nemecek avatar omadawn avatar pop avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ssh_exporter's Issues

text format parsing error: second TYPE line for metric name "ssh_exporter", or TYPE reported after samples

Hi.
I'm using current master build of ssh_exporter
Prometheus is 1.7.1

ssh_exporter config:

version: v0
scripts:
  - name: bastion_toolbox
    script: echo success
    timeout: 1s
    pattern: success
    credentials:
    - host: 1.ssh.host
      port: "22"
      user: monitoring
      keyfile: ./bastion_check.pem
      scriptresult: ""
      scriptreturncode: 0
      scripterror: ""
      resultpatternmatch: 0
    parsedtimeout: 1s
    ignored: false
  - name: bastion_management
    script: echo success
    timeout: 1s
    pattern: success
    credentials:
    - host: 2.ssh.host
      port: "22"
      user: monitoring
      keyfile: ./bastion_check.pem
      scriptresult: ""
      scriptreturncode: 0
      scripterror: ""
      resultpatternmatch: 0
    parsedtimeout: 1s
    ignored: false

Prometheus config:

  - job_name: 'bastion_ssh_exporter_toolbox'
    static_configs:
      - targets: ['ssh_exporter:9428]
    metrics_path: /probe
    params:
      pattern: ['bastion_toolbox']
  
  - job_name: 'bastion_ssh_exporter_management'
    static_configs:
      - targets: ['ssh_exporter:9428']
    metrics_path: /probe
    params:
      pattern: ['bastion_management']

Prometheus gives me this output:

text format parsing error in line 5: second TYPE line for metric name "ssh_exporter", or TYPE reported after samples

Console output

# curl http://localhost:9428/probe?pattern=bastion
# HELP ssh_exporter_bastion_toolbox_exit_status Integer exit status of commands and metadata about the command's execution.
# TYPE ssh_exporter gauge
ssh_exporter_bastion_toolbox_exit_status{name="bastion_toolbox",host="1.ssh.host",user="monitoring",script="echo success",exit_status="0"} 0
# HELP ssh_exporter_bastion_toolbox_pattern_match Boolean match of regex on output of script of commands and metadata about the command's execution.
# TYPE ssh_exporter gauge
ssh_exporter_bastion_toolbox_pattern_match{name="bastion_toolbox",host="1.ssh-host",user="monitoring",script="echo success",regex="success"} 1
# HELP ssh_exporter_bastion_management_exit_status Integer exit status of commands and metadata about the command's execution.
# TYPE ssh_exporter gauge
ssh_exporter_bastion_management_exit_status{name="bastion_management",host="2.ssh.host",user="monitoring",script="echo success",exit_status="0"} 0
# HELP ssh_exporter_bastion_management_pattern_match Boolean match of regex on output of script of commands and metadata about the command's execution.
# TYPE ssh_exporter gauge
ssh_exporter_bastion_management_pattern_match{name="bastion_management",host="2.ssh.host",user="monitoring",script="echo success",regex="success"} 1

Prometheus 2.0 does not like non-string label values

Prometheus version 2 does not like non-string label values. The following output
ssh_exporter_test_simple_exit_status{name="test_simple",host="localhost",user="vagrant",script="echo output script",exit_status=0} 0

Note the lack of quotes around exit_status. This causes Prometheus to throw an error when trying to parse the page. "No token found" will be seen in the prometheus logs and the results are not stored in the TSDB.

Updated the output to quote the results.

ssh_exporter_test_simple_exit_status{name="test_simple",host="localhost",user="vagrant",script="echo output script",exit_status="0"} 0

Add a config argument to change the listen address

We should be encouraging folks to run this only listening on localhost. Either protected by something that does auth (stunnel or something with certificate auth.) or only accessed by the prometheus server itself.

But we can't run it listening on localhost yet.

Integration tests don't stand on their own.

--- FAIL: TestIntegrationHappyPath (0.11s)
ssh_exporter_test.go:154: ./ssh_exporter binary not available, try to run go build first: stat ./ssh_exporter: no such file or directory

If the test suite is going to require the binary it should compile it first.

Having it as separate steps makes it harder to automate.

Not sure if integration tests should even run by default when you just run go test.

Add an example prometheus config

This exporter both overrides the default path and requires parameters.

Include a sample prometheus scrape job config in the read me.

Getting errors when hit to /prob?pattern="pattern_Name"

OS:Ubuntu 14.04
When i run ssh_exporter and when i click to localhost:9382/probe?pattern="manik", it show this

URL:http://localhost:9382/probe?pattern=manik

Output on terminal
2017/10/05 11:31:48 ssh_exporter :: Listening on localhost:9382
2017/10/05 11:31:52 ssh_exporter :: ssh: cannot decode encrypted private keys
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0x50c763]

goroutine 24 [running]:
panic(0x72ecc0, 0xc42000c120)
	/usr/local/go/src/runtime/panic.go:500 +0x1a1
golang.org/x/crypto/ssh.publicKeyCallback.auth(0xc420112e20, 0xc420113260, 0x20, 0x20, 0xc4201201b0, 0x4, 0x90ca80, 0xc42009b1e0, 0x907b40, 0xc4200781e0, ...)
	/usr/local/go/bin/src/golang.org/x/crypto/ssh/client_auth.go:193 +0xb3
golang.org/x/crypto/ssh.(*connection).clientAuthenticate(0xc420098c80, 0xc4200f8180, 0x0, 0xa)
	/usr/local/go/bin/src/golang.org/x/crypto/ssh/client_auth.go:36 +0x353
golang.org/x/crypto/ssh.(*connection).clientHandshake(0xc420098c80, 0xc420120700, 0xc, 0xc4200f8180, 0x0, 0x0)
	/usr/local/go/bin/src/golang.org/x/crypto/ssh/client.go:112 +0x2ed
golang.org/x/crypto/ssh.NewClientConn(0x90f480, 0xc420088120, 0xc420120700, 0xc, 0xc42004dd00, 0x90f480, 0xc420088120, 0x0, 0x0, 0xc420120700, ...)
	/usr/local/go/bin/src/golang.org/x/crypto/ssh/client.go:82 +0x11d
golang.org/x/crypto/ssh.Dial(0x791167, 0x3, 0xc420120700, 0xc, 0xc42004dd00, 0xc420120700, 0xc, 0x61a915)
	/usr/local/go/bin/src/golang.org/x/crypto/ssh/client.go:176 +0xb3
_/home/mmahajan/Prometheus/ssh_exporter/util.sshConnectToHost(0xc420120180, 0x9, 0xc4201201a8, 0x2, 0xc4201201b0, 0x4, 0xc420112b40, 0x1a, 0xc4200706b8, 0xc420026e78, ...)
	/home/mmahajan/Prometheus/ssh_exporter/util/main.go:347 +0x309
_/home/mmahajan/Prometheus/ssh_exporter/util.executeScriptOnHost(0xc420120180, 0x9, 0xc4201201a8, 0x2, 0xc4201201b0, 0x4, 0xc420112b40, 0x1a, 0xc420120140, 0xa, ...)
	/home/mmahajan/Prometheus/ssh_exporter/util/main.go:308 +0xd7
_/home/mmahajan/Prometheus/ssh_exporter/util.executeScript.func1(0xc420128230, 0xc420120140, 0xa, 0xc4200f7c40, 0xc420120640, 0xc4200a7900, 0xc420071380, 0xc4201205e0)
	/home/mmahajan/Prometheus/ssh_exporter/util/main.go:281 +0xa7
created by _/home/mmahajan/Prometheus/ssh_exporter/util.executeScript
	/home/mmahajan/Prometheus/ssh_exporter/util/main.go:299 +0x1a2
Config File:
version: v0
scripts:
  - name: "manik"
    script: "echo manik"
    timeout:  "5s"
    pattern: "manik"
    credentials:
    - host: "192.1681.34"
      port: 22
      user: 'root'
      keyfile: "/home/mmahajan/.ssh/id_rsa"

Vagrant expectations are not valid

Our integration tests assume that A) The vagrant instance is already running and B) that it's listening for SSH on port 2200.

We need a way to determine the correct port number at run-time. This would probably be easiest if the integration test actually started and stopped vagrant. Then it could also specify the port to listen on (and ensure that we use one which is actually open.)

Correct formatting for special characters

Things like this:

script: "date +%M"

should be displayed as

script: "date +%M"

but instead they're displayed as a golang Printf error:

script: "date +<%M MISSING VARIABLE>"

(not verbatim, just like that)

This should be a simple fix. I have a partial patch ready, just not sure the best way to test it.

specifying multiple hosts for single script doesn't seem to be working (for me)

I've tried to do:

credentials:
    - host: myhost.example.com
      port: 22
      user: someuser
      keyfile: /path/to/private/key
    - host: host2.example.com
      port: 22
      user: someotheruser
      keyfile: /path/to/other/private/key

but this returns correct result only for last (in this case: 2nd) host. If I reorder hosts, so that 'host2.example.com is first, and 'myhost.example.com is below it, then the script returns correct result for 'myhost.example.com'
(I'm using HEAD of this git repo)

Add support for ssh-agent

I'm pretty sure the built in library won't connect to ssh agent without some changes. This would be a useful way to use more secured keys without having to add a passphrase field to the config and store the key passphrase in a clear text config file.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.