root / plugins / ssl / ssl-certificate-expiry @ 09b88141
Historique | Voir | Annoter | Télécharger (6,11 ko)
| 1 | 59f057f8 | Olivier Mehani | #!/bin/sh -u |
|---|---|---|---|
| 2 | a4c30808 | Olivier Mehani | # -*- sh -*- |
| 3 | 793b75b9 | Olivier Mehani | # shellcheck disable=SC2039 |
| 4 | a4c30808 | Olivier Mehani | |
| 5 | : << =cut |
||
| 6 | |||
| 7 | =head1 NAME |
||
| 8 | |||
| 9 | 59f057f8 | Olivier Mehani | ssl-certificate-expiry - Plugin to monitor Certificate expiration on multiple services and ports |
| 10 | a4c30808 | Olivier Mehani | |
| 11 | =head1 CONFIGURATION |
||
| 12 | |||
| 13 | 81e19668 | Olivier Mehani | [ssl-certificate-expiry] |
| 14 | 3aa213d4 | ruliane | env.services www.service.tld blah.example.net_PORT foo.example.net_PORT_STARTTLS |
| 15 | |||
| 16 | PORT is the TCP port number |
||
| 17 | STARTTLS is passed to openssl as "-starttls" argument. Useful for services like SMTP or IMAP implementing StartTLS. |
||
| 18 | Current known values are ftp, imap, pop3 and smtp |
||
| 19 | PORT is mandatory if STARTTLS is used. |
||
| 20 | a4c30808 | Olivier Mehani | |
| 21 | To set warning and critical levels do like this: |
||
| 22 | |||
| 23 | 81e19668 | Olivier Mehani | [ssl-certificate-expiry] |
| 24 | a4c30808 | Olivier Mehani | env.services ... |
| 25 | env.warning 30: |
||
| 26 | |||
| 27 | 47ef2182 | Olivier Mehani | Alternatively, if you want to monitor hosts separately, you can create multiple symlinks named as follows. |
| 28 | |||
| 29 | 81e19668 | Olivier Mehani | ssl-certificate-expiry_HOST_PORT |
| 30 | 47ef2182 | Olivier Mehani | |
| 31 | For example: |
||
| 32 | |||
| 33 | 81e19668 | Olivier Mehani | ssl-certificate-expiry_www.example.net |
| 34 | ssl-certificate-expiry_www.example.org_443 |
||
| 35 | ssl-certificate-expiry_192.0.2.42_636 |
||
| 36 | ssl-certificate-expiry_2001:0DB8::badc:0fee_485 |
||
| 37 | 3aa213d4 | ruliane | ssl-certificate-expiry_mail.example.net_25_smtp |
| 38 | 47ef2182 | Olivier Mehani | |
| 39 | 59f057f8 | Olivier Mehani | =head2 Cron setup |
| 40 | a4c30808 | Olivier Mehani | |
| 41 | 59f057f8 | Olivier Mehani | To avoid having to run the SSL checks during the munin-update, it is possible |
| 42 | to run it from cron, and save a cachefile to be read during the update, This is |
||
| 43 | particularly useful when checking a large number of certificates, or when some |
||
| 44 | of the hosts are slow. |
||
| 45 | a4c30808 | Olivier Mehani | |
| 46 | 59f057f8 | Olivier Mehani | To do so, add a cron job running the plugin with cron as the argument: |
| 47 | |||
| 48 | 7e995a02 | Olivier Mehani | <minute> * * * <user> /usr/sbin/munin-run/ssl-certificate-expiry cron |
| 49 | 59f057f8 | Olivier Mehani | |
| 50 | <user> should be the user that has write permission to the MUNIN_PLUGSTATE. |
||
| 51 | 7e995a02 | Olivier Mehani | <minute> should be a number between 0 and 59 when the check should run every hour. |
| 52 | |||
| 53 | If, for any reason, the cron script stops running, the script will revert to |
||
| 54 | uncached updates after the cache file is older than an hour. |
||
| 55 | 59f057f8 | Olivier Mehani | |
| 56 | =head1 AUTHORS |
||
| 57 | |||
| 58 | * Pactrick Domack (ssl_) |
||
| 59 | * Olivier Mehani (ssl-certificate-expiry) |
||
| 60 | bba98f95 | Martin Schobert | * Martin Schobert (check for intermediate certs) |
| 61 | |||
| 62 | 59f057f8 | Olivier Mehani | * Copyright (C) 2013 Patrick Domack <patrickdk@patrickdk.com> |
| 63 | * Copyright (C) 2017, 2019 Olivier Mehani <shtrom+munin@ssji.net> |
||
| 64 | bba98f95 | Martin Schobert | * Copyright (C) 2020 Martin Schobert <martin@schobert.cc> |
| 65 | a4c30808 | Olivier Mehani | |
| 66 | =head1 LICENSE |
||
| 67 | |||
| 68 | =cut |
||
| 69 | |||
| 70 | e7eb2886 | Lars Kruse | # shellcheck disable=SC1090 |
| 71 | a4c30808 | Olivier Mehani | . "${MUNIN_LIBDIR}/plugins/plugin.sh"
|
| 72 | |||
| 73 | 59f057f8 | Olivier Mehani | if [ "${MUNIN_DEBUG:-0}" = 1 ]; then
|
| 74 | a4c30808 | Olivier Mehani | set -x |
| 75 | fi |
||
| 76 | |||
| 77 | 81e19668 | Olivier Mehani | HOSTPORT=${0##*ssl-certificate-expiry_}
|
| 78 | 59f057f8 | Olivier Mehani | CACHEFILE="${MUNIN_PLUGSTATE}/$(basename "${0}").cache"
|
| 79 | 47ef2182 | Olivier Mehani | |
| 80 | if [ "${HOSTPORT}" != "${0}" ] \
|
||
| 81 | 7fed3b97 | Lars Kruse | && [ -n "${HOSTPORT}" ]; then
|
| 82 | 47ef2182 | Olivier Mehani | services="${HOSTPORT}"
|
| 83 | fi |
||
| 84 | |||
| 85 | e7eb2886 | Lars Kruse | |
| 86 | # Read data including a certificate from stdin and output the (fractional) number of days left |
||
| 87 | # until the expiry of this certificate. The output is empty if parsing failed. |
||
| 88 | parse_valid_days_from_certificate() {
|
||
| 89 | 793b75b9 | Olivier Mehani | local input_data |
| 90 | local valid_until_string |
||
| 91 | local valid_until_epoch |
||
| 92 | local now_epoch |
||
| 93 | local input_data |
||
| 94 | e7eb2886 | Lars Kruse | input_data=$(cat) |
| 95 | bba98f95 | Martin Schobert | |
| 96 | e7eb2886 | Lars Kruse | if echo "$input_data" | grep -q -- "-----BEGIN CERTIFICATE-----"; then |
| 97 | valid_until_string=$(echo "$input_data" | openssl x509 -noout -enddate \ |
||
| 98 | | grep "^notAfter=" | cut -f 2 -d "=") |
||
| 99 | if [ -n "$valid_until_string" ]; then |
||
| 100 | 292cfb95 | Lars Kruse | # FreeBSD requires special arguments for "date" |
| 101 | if uname | grep -q ^FreeBSD; then |
||
| 102 | valid_until_epoch=$(date -j -f '%b %e %T %Y %Z' "$valid_until_string" +%s) |
||
| 103 | now_epoch=$(date -j +%s) |
||
| 104 | else |
||
| 105 | valid_until_epoch=$(date --date="$valid_until_string" +%s) |
||
| 106 | e7eb2886 | Lars Kruse | now_epoch=$(date +%s) |
| 107 | 292cfb95 | Lars Kruse | fi |
| 108 | if [ -n "$valid_until_epoch" ]; then |
||
| 109 | e7eb2886 | Lars Kruse | # calculate the number of days left |
| 110 | echo "$valid_until_epoch" "$now_epoch" | awk '{ print(($1 - $2) / (24 * 3600)); }'
|
||
| 111 | fi |
||
| 112 | fi |
||
| 113 | fi |
||
| 114 | } |
||
| 115 | |||
| 116 | |||
| 117 | print_expire_days() {
|
||
| 118 | 793b75b9 | Olivier Mehani | local host="$1" |
| 119 | local port="$2" |
||
| 120 | 3aa213d4 | ruliane | local starttls="$3" |
| 121 | e7eb2886 | Lars Kruse | |
| 122 | # Wrap IPv6 addresses in square brackets |
||
| 123 | echo "$host" | grep -q ':' && host="[$host]" |
||
| 124 | |||
| 125 | 3aa213d4 | ruliane | local s_client_args= |
| 126 | [ -n "$starttls" ] && s_client_args="-starttls $starttls" |
||
| 127 | |||
| 128 | bba98f95 | Martin Schobert | # We extract and check the server certificate, |
| 129 | # but the end date also depends on intermediate certs. Therefore |
||
| 130 | # we want to check intermediate certs as well. |
||
| 131 | # |
||
| 132 | # The following cryptic lines do: |
||
| 133 | # - invoke openssl and connect to a port |
||
| 134 | # - print certs, not only the server cert |
||
| 135 | # - extract each certificate as a single line |
||
| 136 | # - pipe each cert to the parse_valid_days_from_certificate |
||
| 137 | # function, which basically is 'openssl x509 -enddate' |
||
| 138 | # - get a list of the parse_valid_days_from_certificate |
||
| 139 | # results and sort them |
||
| 140 | |||
| 141 | 3aa213d4 | ruliane | # shellcheck disable=SC2086 |
| 142 | 52144bc2 | Lars Kruse | echo "" | openssl s_client \ |
| 143 | bba98f95 | Martin Schobert | -servername "$host" -connect "${host}:${port}" \
|
| 144 | -showcerts \ |
||
| 145 | $s_client_args 2>/dev/null | \ |
||
| 146 | awk '{
|
||
| 147 | if ($0 == "-----BEGIN CERTIFICATE-----") cert="" |
||
| 148 | else if ($0 == "-----END CERTIFICATE-----") print cert |
||
| 149 | else cert=cert$0 |
||
| 150 | }' | \ |
||
| 151 | while read -r CERT; do |
||
| 152 | (printf '\n-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n' "$CERT") | \ |
||
| 153 | parse_valid_days_from_certificate |
||
| 154 | done | sort -n | head -n 1 |
||
| 155 | |||
| 156 | e7eb2886 | Lars Kruse | } |
| 157 | |||
| 158 | 59f057f8 | Olivier Mehani | main() {
|
| 159 | for service in $services; do |
||
| 160 | if echo "$service" | grep -q "_"; then |
||
| 161 | host=$(echo "$service" | cut -f 1 -d "_") |
||
| 162 | port=$(echo "$service" | cut -f 2 -d "_") |
||
| 163 | 3aa213d4 | ruliane | starttls=$(echo "$service" | cut -f 3 -d "_") |
| 164 | 59f057f8 | Olivier Mehani | else |
| 165 | host=$service |
||
| 166 | port=443 |
||
| 167 | 0b4725d6 | Olivier Mehani | starttls="" |
| 168 | 59f057f8 | Olivier Mehani | fi |
| 169 | fieldname="$(clean_fieldname "$service")" |
||
| 170 | 3aa213d4 | ruliane | valid_days=$(print_expire_days "$host" "$port" "$starttls") |
| 171 | 59f057f8 | Olivier Mehani | [ -z "$valid_days" ] && valid_days="U" |
| 172 | printf "%s.value %s\\n" "$fieldname" "$valid_days" |
||
| 173 | 7e995a02 | Olivier Mehani | echo "${fieldname}.extinfo Last checked: $(date)"
|
| 174 | 59f057f8 | Olivier Mehani | done |
| 175 | } |
||
| 176 | e7eb2886 | Lars Kruse | |
| 177 | 59f057f8 | Olivier Mehani | case ${1:-} in
|
| 178 | a4c30808 | Olivier Mehani | config) |
| 179 | echo "graph_title SSL Certificates Expiration" |
||
| 180 | echo 'graph_args --base 1000' |
||
| 181 | echo 'graph_vlabel days left' |
||
| 182 | echo 'graph_category security' |
||
| 183 | 59f057f8 | Olivier Mehani | echo "graph_info This graph shows the numbers of days before certificate expiry" |
| 184 | a4c30808 | Olivier Mehani | for service in $services; do |
| 185 | fieldname=$(clean_fieldname "$service") |
||
| 186 | 47ef2182 | Olivier Mehani | echo "${fieldname}.label $(echo "${service}" | sed 's/_/:/')"
|
| 187 | 59f057f8 | Olivier Mehani | print_thresholds "${fieldname}" warning critical
|
| 188 | a4c30808 | Olivier Mehani | done |
| 189 | |||
| 190 | exit 0 |
||
| 191 | ;; |
||
| 192 | 59f057f8 | Olivier Mehani | cron) |
| 193 | 7e995a02 | Olivier Mehani | UPDATE="$(main)" |
| 194 | echo "${UPDATE}" > "${CACHEFILE}"
|
||
| 195 | chmod 0644 "${CACHEFILE}"
|
||
| 196 | 59f057f8 | Olivier Mehani | |
| 197 | exit 0 |
||
| 198 | ;; |
||
| 199 | a4c30808 | Olivier Mehani | esac |
| 200 | |||
| 201 | 7e995a02 | Olivier Mehani | if [ -n "$(find "${CACHEFILE}" -mmin -60 2>/dev/null)" ]; then
|
| 202 | 59f057f8 | Olivier Mehani | cat "${CACHEFILE}"
|
| 203 | exit 0 |
||
| 204 | fi |
||
| 205 | a4c30808 | Olivier Mehani | |
| 206 | 59f057f8 | Olivier Mehani | main |
