Projet

Général

Profil

Paste
Télécharger au format
Statistiques
| Branche: | Révision:

root / plugins / ssl / ssl-certificate-expiry @ 09b88141

Historique | Voir | Annoter | Télécharger (6,11 ko)

1
#!/bin/sh -u
2
# -*- sh -*-
3
# shellcheck disable=SC2039
4

    
5
: << =cut
6

    
7
=head1 NAME
8

    
9
ssl-certificate-expiry - Plugin to monitor Certificate expiration on multiple services and ports
10

    
11
=head1 CONFIGURATION
12

    
13
  [ssl-certificate-expiry]
14
    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

    
21
To set warning and critical levels do like this:
22

    
23
  [ssl-certificate-expiry]
24
    env.services ...
25
    env.warning 30:
26

    
27
Alternatively, if you want to monitor hosts separately, you can create multiple symlinks named as follows.
28

    
29
  ssl-certificate-expiry_HOST_PORT
30

    
31
For example:
32

    
33
  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
  ssl-certificate-expiry_mail.example.net_25_smtp
38

    
39
=head2 Cron setup
40

    
41
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

    
46
To do so, add a cron job running the plugin with cron as the argument:
47

    
48
  <minute> * * * <user> /usr/sbin/munin-run/ssl-certificate-expiry cron
49

    
50
<user> should be the user that has write permission to the MUNIN_PLUGSTATE.
51
<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

    
56
=head1 AUTHORS
57

    
58
 * Pactrick Domack (ssl_)
59
 * Olivier Mehani (ssl-certificate-expiry)
60
 * Martin Schobert (check for intermediate certs)
61
 
62
 * Copyright (C) 2013 Patrick Domack <patrickdk@patrickdk.com>
63
 * Copyright (C) 2017, 2019 Olivier Mehani <shtrom+munin@ssji.net>
64
 * Copyright (C) 2020 Martin Schobert <martin@schobert.cc> 
65

    
66
=head1 LICENSE
67

    
68
=cut
69

    
70
# shellcheck disable=SC1090
71
. "${MUNIN_LIBDIR}/plugins/plugin.sh"
72

    
73
if [ "${MUNIN_DEBUG:-0}" = 1 ]; then
74
    set -x
75
fi
76

    
77
HOSTPORT=${0##*ssl-certificate-expiry_}
78
CACHEFILE="${MUNIN_PLUGSTATE}/$(basename "${0}").cache"
79

    
80
if [ "${HOSTPORT}" != "${0}" ] \
81
	&& [ -n "${HOSTPORT}" ]; then
82
	services="${HOSTPORT}"
83
fi
84

    
85

    
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
    local input_data
90
    local valid_until_string
91
    local valid_until_epoch
92
    local now_epoch
93
    local input_data
94
    input_data=$(cat)
95

    
96
    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
            # 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
                now_epoch=$(date +%s)
107
            fi
108
            if [ -n "$valid_until_epoch" ]; then
109
                # 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
    local host="$1"
119
    local port="$2"
120
    local starttls="$3"
121

    
122
    # Wrap IPv6 addresses in square brackets
123
    echo "$host" | grep -q ':' && host="[$host]"
124

    
125
    local s_client_args=
126
    [ -n "$starttls" ] && s_client_args="-starttls $starttls"
127

    
128
    # 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
    # shellcheck disable=SC2086
142
    echo "" | openssl s_client \
143
	-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
}
157

    
158
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
	    starttls=$(echo "$service" | cut -f 3 -d "_")
164
	else
165
	    host=$service
166
	    port=443
167
	    starttls=""
168
	fi
169
	fieldname="$(clean_fieldname "$service")"
170
	valid_days=$(print_expire_days "$host" "$port" "$starttls")
171
	[ -z "$valid_days" ] && valid_days="U"
172
	printf "%s.value %s\\n" "$fieldname" "$valid_days"
173
        echo "${fieldname}.extinfo Last checked: $(date)"
174
    done
175
}
176

    
177
case ${1:-} in
178
    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
	echo "graph_info This graph shows the numbers of days before certificate expiry"
184
	for service in $services; do
185
	    fieldname=$(clean_fieldname "$service")
186
	    echo "${fieldname}.label $(echo "${service}" | sed 's/_/:/')"
187
	    print_thresholds "${fieldname}" warning critical
188
	done
189

    
190
	exit 0
191
	;;
192
    cron)
193
	UPDATE="$(main)"
194
	echo "${UPDATE}" > "${CACHEFILE}"
195
	chmod 0644 "${CACHEFILE}"
196

    
197
	exit 0
198
	;;
199
esac
200

    
201
if [ -n "$(find "${CACHEFILE}" -mmin -60 2>/dev/null)" ]; then
202
	cat "${CACHEFILE}"
203
	exit 0
204
fi
205

    
206
main