source: monitoring/plugins/ids/check_shib_sp @ 1847

Last change on this file since 1847 was 1847, checked in by oschonef, 12 years ago
  • add IDP monitroing script
  • cosmetic changes to SP script
  • add section about IDP script to README
  • Property svn:executable set to *
File size: 6.5 KB
Line 
1#!/usr/bin/perl
2#
3# check_shib_sp, version 2012-04-03
4#
5
6use strict;
7use warnings;
8use Getopt::Long;
9use LWP;
10use XML::Parser;
11use Crypt::OpenSSL::X509;
12use Date::Parse;
13use POSIX qw(mktime);
14use constant {
15    OK        => 0,
16    WARNING   => 1,
17    CRITICAL  => 2,
18    UNKNOWN   => 3,
19    DEPENDENT => 4,
20};
21use constant {
22    SHIBBOLETH   => 'shibboleth',
23    XERCES       => 'xerces',
24    OPENSAML     => 'opensaml',
25    XML_TOOLING  => 'xml-tooling',
26    XML_SECURITY => 'xml-security',
27};
28my $CODE2STRING = {
29    0 => 'OK',
30    1 => 'WARNING',
31    2 => 'CRITICAL',
32    3 => 'UNKNOWN',
33    4 => 'DEPENDENT',
34};
35
36
37sub get_recursive {
38    my $ctx_ref      = shift;
39    my $path_ref     = shift;
40    my $results_ref  = shift;
41    my $no_fail_bail = shift || 0;
42
43    # fixup root array
44    if (ref($ctx_ref) eq 'ARRAY') {
45        if (scalar(@{$ctx_ref}) != 1) {
46            die('malformed data, root array must contain one element');
47        }
48        $ctx_ref = {
49            'Kids' => $ctx_ref,
50        };
51    }
52
53    my $current = shift(@{$path_ref});
54    if ($current =~ m/^@/) {
55        my $target = substr($current, 1);
56        my $value = $ctx_ref->{$target};
57        if (defined($value)) {
58            push(@{$results_ref}, $value);
59            return 1;
60        }
61        else {
62            return 0;
63        }
64    }
65    else {
66        my $target = "main::" . $current;
67        my $results = 0;
68        foreach my $i (@{$ctx_ref->{'Kids'}}) {
69            if (ref($i) eq $target) {
70                if (scalar(@{$path_ref}) == 0) {
71                    push(@{$results_ref}, $i);
72                    $results += 1;
73                }
74                else {
75                    $results += get_recursive($i, $path_ref,
76                                              $results_ref, $no_fail_bail);
77                }
78            }
79        }
80        return $results;
81    }
82}
83
84
85sub get {
86    my $ctx_ref      = shift;
87    my $path         = shift;
88    my $no_fail_bail = shift || 0;
89
90    my @p = split('/', $path);
91    my @results = ();
92
93    if (get_recursive($ctx_ref, \@p, \@results, $no_fail_bail) > 0) {
94        return $results[0];
95    }
96    else {
97        if ($no_fail_bail) {
98            return undef;
99        }
100        else {
101            die("$path: not found");
102        }
103    }
104}
105
106
107sub get_all {
108    my $ctx_ref      = shift;
109    my $path         = shift;
110    my $no_fail_bail = shift || 0;
111
112    my @p = split('/', $path);
113    my @results = ();
114
115    if (get_recursive($ctx_ref, \@p, \@results, $no_fail_bail) <= 0) {
116        if (!$no_fail_bail) {
117            die("$path: not found");
118        }
119    }
120    return @results;
121}
122
123
124sub get_diff {
125    my $ts = shift;
126
127    my ($sec1, $min1, $hour1, $day1, $mon1, $year1) =
128        strptime($ts);
129    my ($sec2, $min2, $hour2, $day2, $mon2, $year2) =
130        gmtime(time());
131    my $t1 = mktime($sec1, $min1, $hour1, $day1, $mon1, $year1);
132    my $t2 = mktime($sec2, $min2, $hour2, $day2, $mon2, $year2);
133    return $t1 - $t2;
134}
135
136
137sub status {
138    my $code    = shift;
139    my $message = shift;
140
141    $message =~ s/\n*$//;
142    printf("%s: %s\n", $CODE2STRING->{$code}, $message);
143    exit $code;
144}
145
146
147my $host           = undef;
148my $url            = 'Shibboleth.sso/Status';
149my $use_ssl        = 0;
150my $max_clock_diff = 120;
151my $cert_warn_days = 31;
152my $timeout        = 10;
153
154
155my $result = GetOptions("H=s" => \$host,
156                        "u=s" => \$url,
157                        "t=i" => \$timeout,
158                        "S"   => \$use_ssl,
159                        "D=i" => \$max_clock_diff,
160                        "C=i" => \$cert_warn_days);
161if (!($result)) {
162    status(UNKNOWN, 'USAGE: -H <host> [-u <url>] [-t <timeout>] [-S] [-D <clock skew> -C <age>');
163}
164
165
166if (!defined($host)) {
167    status(UNKNOWN, 'missing manadorty parameter -H <host>');
168}
169
170#print "Host: $host\nURL: $url\nUSE_SSL: $use_ssl\nCLOCK: $max_clock_diff\nCERT WARN: $cert_warn_days\n";
171
172local $SIG{ALRM} = sub {
173    status(UNKNOWN, 'check timed out');
174};
175alarm $timeout;
176
177
178$url =~ s|^/+||g;
179$url =~ s|/+$||g;
180my $sp_status_url = sprintf('%s://%s/%s', ($use_ssl ? 'https' : 'http'),
181                                          $host,
182                                          $url);
183my $browser = LWP::UserAgent->new;
184my $response = $browser->get($sp_status_url);
185if ($response->is_success) {
186    my $result = undef;
187    eval {
188        my $parser = XML::Parser->new(Style => 'Objects');
189        $result = $parser->parse($response->content);
190    };
191    if ($@) {
192        status(CRITICAL, "error parsing XML response: " . $@);
193    }
194
195    my $time = get($result, 'StatusHandler/@time');
196    my $diff = get_diff($time);
197    if (abs($diff) > $max_clock_diff) {
198        status(CRITICAL,
199               sprintf('clock skew too large (IDP time: %s, skew %d)',
200                       $time, $diff));
201    }
202
203    my $versions = {
204        SHIBBOLETH   =>
205            get($result, 'StatusHandler/Version/@Shibboleth', 0),
206        XERCES       =>
207            get($result, 'StatusHandler/Version/@Xerces-C', 0),
208        OPENSAML     =>
209            get($result, 'StatusHandler/Version/@OpenSAML-C'),
210        XML_TOOLING  =>
211            get($result, 'StatusHandler/Version/@XML-Tooling-C'),
212        XML_SECURITY =>
213            get($result, 'StatusHandler/Version/@XML-Security-C'),
214    };
215
216    # check certificates
217    my $code    = OK;
218    my $message = undef;
219    foreach my $key (get_all($result, 'StatusHandler/md:KeyDescriptor', 1)) {
220        my $cert_data = get($key, 'ds:KeyInfo/ds:X509Data/ds:X509Certificate/Characters')->{'Text'};
221        my $cert = undef;
222        eval {
223            $cert_data =~ s/\n*//gs;
224            $cert_data =~ s/(.{64})/$1\n/gs;
225            $cert = Crypt::OpenSSL::X509->new_from_string(
226                sprintf("-----BEGIN CERTIFICATE-----\n%s\n" .
227                        "-----END CERTIFICATE-----\n", $cert_data));
228        };
229        if ($@) {
230            print $@;
231            status(UNKNOWN, "cannot parse certificate data");
232
233        }
234
235        if ($cert->checkend(0)) {
236            status(CRITICAL,
237                   sprintf('%s certificate is expired since %s',
238                           $key->{'use'}, $cert->notAfter()));
239        }
240        elsif ($cert->checkend($cert_warn_days * 86400)) {
241            my $expire_days = int(get_diff($cert->notAfter()) / 86400);
242            $code           = WARNING;
243            $message        = sprintf('%s certificate expires in %d day(s)',
244                                      $key->{'use'}, $expire_days);
245        }
246    }
247    if ($code != OK) {
248        status($code, $message);
249    }
250    else {
251        my $status = get($result, 'StatusHandler/Status');
252        if (defined(get($status, 'OK', 1))) {
253            status(OK, sprintf('Shibboleth SP version %s',
254                               $versions->{SHIBBOLETH}));
255        }
256        else {
257            status(CRITICAL, 'status is not OK; no further information');
258        }
259    }
260}
261else {
262    my $status = $response->status_line || 'unknown';
263    status(CRITICAL, sprintf('error fetching status: %s', $status));
264}
265exit OK;
Note: See TracBrowser for help on using the repository browser.