# HG changeset patch # User Heiko Schlittermann # Date 1466518718 -7200 # Node ID 765b680bcc0d51f5cf0a6e064a1f614dab31463e # Parent 32c8d32920790ceae64b032307f469d6e0b92940# Parent 744983a363380f883d26486cf6c3da6198e0336d [merge] test diff -r 32c8d3292079 -r 765b680bcc0d lib/Nagios/Check/DNS/check_tlsa_record.pm --- a/lib/Nagios/Check/DNS/check_tlsa_record.pm Mon Jun 20 12:26:04 2016 +0200 +++ b/lib/Nagios/Check/DNS/check_tlsa_record.pm Tue Jun 21 16:18:38 2016 +0200 @@ -3,72 +3,63 @@ use strict; use warnings; use feature qw(say switch); -use if $ENV{DEBUG} => 'Smart::Comments'; use Carp; use Data::Dumper; +use if $ENV{DEBUG} => 'Smart::Comments'; #use if $^V >= v5.0.20 => (experimental => gw(smartmatch)); use experimental qw(smartmatch); -use File::Temp qw(tempfile); - -#use File::Temp; -use FileHandle; +use File::Temp; use Fcntl qw(F_GETFD F_SETFD FD_CLOEXEC); our $VERSION = '0.1'; -my $dane_pattern = -'^(?(?\d+)\s+(?\d+)\s+(?\d+)\s+(?[0-9a-f ]+))$'; -my $with_cname = '^(?[_a-z]+.*\n).*'; - -#@TODO use only fd of tempfile instead of filename -#my $tempfile = File::Temp->new( +my $dane_pattern = qr'^(?(?\d+)\s+(?\d+)\s+(?\d+)\s+(?[0-9a-f ]+))$'; +my $with_cname = qr'^(?[_a-z]+.*\n).*'; -my $tempfile; -my $handle; - -($handle, $tempfile) = tempfile( - TEMPLATE => 'XXXXXXXXXXXXXXXX', - DIR => '/tmp/', - SUFFIX => '.tmp', -); +# keep in in 'our' to avoid garbage collection and destruction +# of the File::Temp object +our $tmpfile = File::Temp->new(); +my $fdname = '/dev/fd/' . fileno $tmpfile; +fcntl($tmpfile, F_SETFD, fcntl($tmpfile, F_GETFD, 0) & ~FD_CLOEXEC) + or die "clear FD_CLOEXEC on $tmpfile: $!\n"; sub main { my $domain = shift; my $port = shift // 443; my $protocol = shift // 'tcp'; my @validate = validate_tlsa($domain, $port, $protocol); - my $length = @validate; - my $return = ''; + my $length = @validate; + my $return = ''; - if ($length > 1) { - for (my $i = 0; $i < $length; $i++) { - $return .= "$validate[$i]\n"; - } + if ( $length > 1 ) { + for ( my $i = 0; $i < $length; $i++) { + $return .= "$validate[$i]\n"; + } } else { - $return = $validate[0]; + $return = $validate[0]; } - return $return; + return $return; } sub get_tlsa_from_dns { - my $domain = shift; - my $port = shift // croak 'Need a port number'; - my $protocol = shift // 'tcp'; - my $query = "dig tlsa _$port._$protocol.$domain +short"; - my @dns_return = qx($query); + my $domain = shift; + my $port = shift // croak 'Need a port number'; + my $protocol = shift // 'tcp'; + my $query = "dig tlsa _$port._$protocol.$domain +short"; + my @dns_return = qx($query); my $return_length = @dns_return; my $cname; - for (my $i = 0; $i < $return_length; $i++) { - - if ($dns_return[$i] =~ /^[_a-z]+[a-z0-9]+/i) { + for ( my $i = 0; $i < $return_length; $i++) + { - #$dns_return[$i] = "CNAME: $dns_return[$i]"; - #$dns_return[$i-1] = $dns_return[$i]; - $dns_return[$i] = $dns_return[$i + 1]; - } + if ($dns_return[$i] =~ /^[_a-z]+[a-z0-9]+/i) { + #$dns_return[$i] = "CNAME: $dns_return[$i]"; + #$dns_return[$i-1] = $dns_return[$i]; + $dns_return[$i] = $dns_return[$i+1]; + } } # FIXME: what's about the \n? We should cut it! @@ -78,24 +69,23 @@ sub get_cert { my $domain = shift; my $port = shift; - my $query; + my $cmd; my $cert; if ($port == 25) { - $query = "openssl s_client -starttls smtp -connect $domain:$port"; + $cmd = "openssl s_client -starttls smtp -connect $domain:$port"; } else { - $query = "openssl s_client -connect $domain:$port"; + $cmd = "openssl s_client -connect $domain:$port"; } - my $same = "< /dev/null 2>/dev/null | openssl x509 -out $tempfile 2>&1"; - $query = "$query $same"; + my $same = "< /dev/null 2>/dev/null | openssl x509 -out $fdname 2>&1"; + $cmd .= $same; - $cert = qx($query); + die sprintf "[%s] returned exit:%d signal:%d\n", + $cmd, $? >> 8, $? & 0xff + if $?; - if ($cert =~ /.*unable.*/gi) { - $cert = 'unable NO'; ## @TODO google.de returns unable to write.. - } - return $cert; + return qx($cmd); } sub get_tlsa_from_cert { @@ -105,13 +95,14 @@ my $gentlsa; $gentlsa = <<_; -openssl x509 -in $tempfile -pubkey | -openssl rsa -pubin -inform PEM -outform DER 2>/dev/null | +openssl x509 -in $fdname -pubkey | +openssl rsa -pubin -inform PEM -outform DER 2>/dev/null | openssl $hashit _ + if ($tlsa_selector == 0) { - $gentlsa = "openssl x509 -in $tempfile -outform DER | openssl $hashit"; + $gentlsa = "openssl x509 -in $fdname -outform DER | openssl $hashit"; } my $tlsa_record = qx($gentlsa) or die "nothing found!\n"; @@ -136,11 +127,11 @@ my $tlsa_match_type; if ($dig_return =~ /$dane_pattern/i) { - $tlsa_match_type = $+{tlsa_match_type}; + $tlsa_match_type = $+{tlsa_match_type}; } if ($tlsa_match_type >= 3) { - return "Not valid: $tlsa_match_type"; + return "Not valid: $tlsa_match_type"; } for ($tlsa_match_type) { @@ -157,14 +148,13 @@ my $dns_tlsa; if ($dns_return =~ /$dane_pattern/i) { - $dns_tlsa = $+{tlsa_hash}; - $dns_tlsa =~ s/(\S*)\s+(\S*)$/$1$2/; + $dns_tlsa = $+{tlsa_hash}; + $dns_tlsa =~ s/(\S*)\s+(\S*)$/$1$2/; } return $dns_tlsa; } sub get_tlsa_usage { - # # @TODO: check certificate trust chain # get_ca_cert() @@ -192,38 +182,40 @@ my $tlsa_usage; if ($dns_return =~ /$dane_pattern/i) { - $tlsa_usage = $+{tlsa_usage}; + $tlsa_usage = $+{tlsa_usage}; } return $tlsa_usage; } sub get_tlsa_selector { - # - # 0: Full certificate: the Certificate binary structure as defined - # in [RFC5280] - # 1: SubjectPublicKeyInfo: DER-encoded binary structure as defined - # in [RFC5280] - # Vorteil: Wenn immer derselbe Private Key für die Generierung von - # Zertifikaten genutzt wird, muss der TLSA-Record nicht mit jedem - # Zertifikatswechsel erneuert werden. - # + # + # 0: Full certificate: the Certificate binary structure as defined + # in [RFC5280] + # 1: SubjectPublicKeyInfo: DER-encoded binary structure as defined + # in [RFC5280] + # Vorteil: Wenn immer derselbe Private Key für die Generierung von + # Zertifikaten genutzt wird, muss der TLSA-Record nicht mit jedem + # Zertifikatswechsel erneuert werden. + # my $dns_return = shift; my $tlsa_selector; + if ($dns_return =~ /$dane_pattern/i) { - $tlsa_selector = $+{tlsa_selector}; + $tlsa_selector = $+{tlsa_selector}; } return $tlsa_selector; } + sub validate_tlsa { - my $domain = shift; - my $port = shift; - my $protocol = shift; - my @dns_return = get_tlsa_from_dns($domain, $port, $protocol); - my $length = @dns_return; + my $domain = shift; + my $port = shift; + my $protocol = shift; + my @dns_return = get_tlsa_from_dns($domain, $port, $protocol); + my $length = @dns_return; my $fail_selector = 0; my $fail_usage = 0; my $fail_match_type = 0; @@ -235,82 +227,74 @@ my @cname; if ($length == 0) { - return 'WARNING: No TLSA to check'; + return 'WARNING: No TLSA to check'; } - my $cert = get_cert($domain, $port); + my $cert = get_cert($domain, $port); if ($cert =~ /.*unable to load certificate.*/) { return "WARNING: No SSL-Certificate for $domain:$port"; } - my $cert_tlsa = get_tlsa_from_cert($cert); + my $cert_tlsa = get_tlsa_from_cert($cert); chomp $cert_tlsa; for (my $i = 0; $i < $length; $i++) { - if ($dns_return[$i] =~ /no tlsa.*$/gi) { - return "WARNING: $dns_return[$i]"; + if ($dns_return[$i] =~ /no tlsa.*$/gi) { + return "WARNING: $dns_return[$i]"; + } + #if ($dns_return[$i] =~ /CNAME: .*$/gi) { + # #$dns_return[$i] = $dns_retrun[$i+1]; + # $i++; + #} + if ($dns_return[$i] !~ /CNAME: .*$/gi) { + $dns_tlsa[$i] = get_tlsa_dns_record($dns_return[$i]); + $tlsa_selector[$i] = get_tlsa_selector($dns_return[$i]); + $tlsa_usage[$i] = get_tlsa_usage($dns_return[$i]); + $tlsa_match_type[$i] = get_tlsa_match_type($dns_return[$i]); + + if ($tlsa_selector[$i] < 0 or $tlsa_selector[$i] > 1) { + $return[$i] = "CRITICAL: TLSA Selector \'$tlsa_selector[$i]\' for $domain:$port is not valid"; + $fail_selector = 1; + } + + if ($tlsa_usage[$i] < 0 or $tlsa_usage[$i] > 3) { + $return[$i] = "CRITICAL: TLSA Usage \'$tlsa_usage[$i]\' for $domain:$port is not valid"; + $fail_usage = 1; } - #if ($dns_return[$i] =~ /CNAME: .*$/gi) { - # #$dns_return[$i] = $dns_retrun[$i+1]; - # $i++; - #} - if ($dns_return[$i] !~ /CNAME: .*$/gi) { - $dns_tlsa[$i] = get_tlsa_dns_record($dns_return[$i]); - $tlsa_selector[$i] = get_tlsa_selector($dns_return[$i]); - $tlsa_usage[$i] = get_tlsa_usage($dns_return[$i]); - $tlsa_match_type[$i] = get_tlsa_match_type($dns_return[$i]); - - if ($tlsa_selector[$i] < 0 or $tlsa_selector[$i] > 1) { - $return[$i] = -"CRITICAL: TLSA Selector \'$tlsa_selector[$i]\' for $domain:$port is not valid"; - $fail_selector = 1; - } - - if ($tlsa_usage[$i] < 0 or $tlsa_usage[$i] > 3) { - $return[$i] = -"CRITICAL: TLSA Usage \'$tlsa_usage[$i]\' for $domain:$port is not valid"; - $fail_usage = 1; - } - - #if ($tlsa_match_type[$i] !~ /not.*(?\d+)/i) - if ($tlsa_match_type[$i] =~ /not.*(?\d+)/i) { - $return[$i] = -"CRITICAL: TLSA Match Type \'$+{mt}\' for $domain:$port is not valid"; - $fail_match_type = 1; - } - - if ($fail_match_type != 1) { - $cert_tlsa = get_tlsa_from_cert($cert, $tlsa_match_type[$i]); - - if ($fail_selector != 1) { - $cert_tlsa = get_tlsa_from_cert($cert, $tlsa_match_type[$i], - $tlsa_selector[$i]); - } - - chomp $cert_tlsa; - - } - - if ( $fail_usage != 1 - and $fail_selector != 1 - and $fail_match_type != 1) - { - if ("$dns_tlsa[$i]" ne "$cert_tlsa") { - $return[$i] = - "CRITICAL: TLSA Record for $domain:$port is not valid"; - } - else { - $return[$i] = "OK: TLSA Record for $domain:$port is valid"; - } - } + #if ($tlsa_match_type[$i] !~ /not.*(?\d+)/i) + if ($tlsa_match_type[$i] =~ /not.*(?\d+)/i) + { + $return[$i] = "CRITICAL: TLSA Match Type \'$+{mt}\' for $domain:$port is not valid"; + $fail_match_type = 1; } - else { - chomp $dns_return[$i]; - $return[$i] = "$dns_return[$i] for $domain:$port"; + if ($fail_match_type != 1) { + $cert_tlsa = get_tlsa_from_cert($cert,$tlsa_match_type[$i]); + + if ($fail_selector != 1) { + $cert_tlsa = get_tlsa_from_cert($cert,$tlsa_match_type[$i],$tlsa_selector[$i]); + } + + chomp $cert_tlsa; + } + + if ($fail_usage != 1 and $fail_selector != 1 and $fail_match_type != 1 ) { + if ("$dns_tlsa[$i]" ne "$cert_tlsa") { + $return[$i] = "CRITICAL: TLSA Record for $domain:$port is not valid"; + } + else { + $return[$i] = "OK: TLSA Record for $domain:$port is valid"; + } + } + } + + else { + chomp $dns_return[$i]; + $return[$i] = "$dns_return[$i] for $domain:$port"; + } } return @return;