aboutsummaryrefslogtreecommitdiff
path: root/flakes/private/monitoring/plugins/check_dnssec
blob: a6e408d5a3bd1070e9e925d033a9eaef0bc59a22 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#!/usr/bin/env bash

# check_dnssec_expiry.sh
#
# Copyright 2017 by Mario Rimann <mario@rimann.org>
# Licensed under the permissive MIT license, see LICENSE.md
#
# Development of this script was partially sponsored by my
# employer internezzo, see http://www.internezzo.ch
#
# If this script helps you to make your work easier, please consider
# to give feedback or do something good, see https://rimann.org/support

usage() {
	cat - >&2 << _EOT_
usage $0 -z <zone> [-w <warning %>] [-c <critical %>] [-r <resolver>] [-f <always failing domain>]

	-z <zone>
		specify zone to check
	-w <critical %>
		warning time left percentage
	-c <critical %>
		critical time left percentage
	-r <resolver>
		specify which resolver to use.
	-f <always failing domain>
		specify a domain that will always fail DNSSEC.
		used to test if DNSSEC is supported in used tools.
	-t <DNS record type to check>
		specify a DNS record type for calculating the remaining lifetime.
		For example SOA, A, etc.
_EOT_
	exit 255
}

# Parse the input options
while getopts ":z:w:c:r:f:h:t:" opt; do
  case $opt in
    z)
      zone=$OPTARG
      ;;
    w)
      warning=$OPTARG
      ;;
    c)
      critical=$OPTARG
      ;;
    r)
      resolver=$OPTARG
      ;;
    f)
      alwaysFailingDomain=$OPTARG
      ;;
    t)
      recordType=$OPTARG
      ;;
    h)
      usage ;;
  esac
done


# Check if dig is available at all - fail hard if not
pathToDig=$( which dig )
if [[ ! -e $pathToDig ]]; then
	echo "No executable of dig found, cannot proceed without dig. Sorry!"
	exit 1
fi

# Check if we got a zone to validate - fail hard if not
if [[ -z $zone ]]; then
	echo "Missing zone to test - please provide a zone via the -z parameter."
	usage
	exit 3
fi

# Check if we got warning/critical percentage values, use defaults if not
if [[ -z $warning ]]; then
	warning=20
fi
if [[ -z $critical ]]; then
	critical=10
fi


# Use Google's 8.8.8.8 resolver as fallback if none is provided
if [[ -z $resolver ]]; then
	resolver="8.8.8.8"
fi

if [[ -z $alwaysFailingDomain ]]; then
	alwaysFailingDomain="dnssec-failed.org"
fi

# Use SOA record type as fallback
if [[ -z $recordType ]]; then
        recordType="SOA"
fi

# Check the resolver to properly validate DNSSEC at all (if he doesn't, every further test is futile and a waste of bandwith)
checkResolverDoesDnssecValidation=$(dig +nocmd +nostats +noquestion $alwaysFailingDomain @${resolver} | grep "opcode: QUERY" | grep "status: SERVFAIL")
if [[ -z $checkResolverDoesDnssecValidation ]]; then
	echo "WARNING: Resolver seems to not validate DNSSEC signatures - going further seems hopeless right now."
	exit 1
fi

# Check if the resolver delivers an answer for the domain to test
checkDomainResolvableWithDnssecEnabledResolver=$(dig +short @${resolver} SOA $zone)
if [[ -z $checkDomainResolvableWithDnssecEnabledResolver ]]; then

	checkDomainResolvableWithDnssecValidationExplicitelyDisabled=$(dig +short @${resolver} SOA $zone +cd)

	if [[ ! -z $checkDomainResolvableWithDnssecValidationExplicitelyDisabled ]]; then
		echo "CRITICAL: The domain $zone can be validated without DNSSEC validation - but will fail on resolvers that do validate DNSSEC."
		exit 2
	else
		echo "CRITICAL: The domain $zone cannot be resolved via $resolver as resolver while DNSSEC validation is active."
		exit 2
	fi
fi

# Check if the domain is DNSSEC signed at all
# (and emerge a WARNING in that case, since this check is about testing DNSSEC being "present" and valid which is not the case for an unsigned zone)
checkZoneItselfIsSignedAtAll=$( dig $zone @$resolver DS +short )
if [[ -z $checkZoneItselfIsSignedAtAll ]]; then
	echo "WARNING: Zone $zone seems to be unsigned itself (= resolvable, but no DNSSEC involved at all)"
	exit 1
fi


# Check if there are multiple RRSIG responses and check them one after the other
now=$(date +"%s")
rrsigEntries=$( dig @$resolver $recordType $zone +dnssec | grep RRSIG )
if [[ -z $rrsigEntries ]]; then
        echo "CRITICAL: There is no RRSIG for the SOA of your zone."
        exit 2
else
	while read -r rrsig; do
		# Get the RRSIG entry and extract the date out of it
		expiryDateOfSignature=$( echo $rrsig | awk '{print $9}')
		checkValidityOfExpirationTimestamp=$( echo $expiryDateOfSignature | egrep '[0-9]{14}')
		if [[ -z $checkValidityOfExpirationTimestamp ]]; then
			echo "UNKNOWN: Something went wrong while checking the expiration of the RRSIG entry - investigate please".
			exit 3
		fi

		inceptionDateOfSignature=$( echo $rrsig | awk '{print $10}')
		checkValidityOfInceptionTimestamp=$( echo $inceptionDateOfSignature | egrep '[0-9]{14}')
		if [[ -z $checkValidityOfInceptionTimestamp ]]; then
			echo "UNKNOWN: Something went wrong while checking the inception date of the RRSIG entry - investigate please".
			exit 3
		fi

		# Fiddle out the expiry and inceptiondate of the signature to have a base to do some calculations afterwards
		expiryDateAsString="${expiryDateOfSignature:0:4}-${expiryDateOfSignature:4:2}-${expiryDateOfSignature:6:2} ${expiryDateOfSignature:8:2}:${expiryDateOfSignature:10:2}:00"
		expiryDateOfSignatureAsUnixTime=$( date -u -d "$expiryDateAsString" +"%s" 2>/dev/null )
		if [[ $? -ne 0 ]]; then
			# if we come to this place, something must have gone wrong converting the date-string. This can happen as e.g. MacOS X and Linux don't behave the same way in this topic...
			expiryDateOfSignatureAsUnixTime=$( date -j -u -f "%Y-%m-%d %T" "$expiryDateAsString" +"%s" )
		fi
		inceptionDateAsString="${inceptionDateOfSignature:0:4}-${inceptionDateOfSignature:4:2}-${inceptionDateOfSignature:6:2} ${inceptionDateOfSignature:8:2}:${inceptionDateOfSignature:10:2}:00"
		inceptionDateOfSignatureAsUnixTime=$( date -u -d "$inceptionDateAsString" +"%s" 2>/dev/null )
		if [[ $? -ne 0 ]]; then
			# if we come to this place, something must have gone wrong converting the date-string. This can happen as e.g. MacOS X and Linux don't behave the same way in this topic...
			inceptionDateOfSignatureAsUnixTime=$( date -j -u -f "%Y-%m-%d %T" "$inceptionDateAsString" +"%s" )
		fi


		# calculate the remaining lifetime of the signature
		totalLifetime=$( expr $expiryDateOfSignatureAsUnixTime - $inceptionDateOfSignatureAsUnixTime)
		remainingLifetimeOfSignature=$( expr $expiryDateOfSignatureAsUnixTime - $now)
		remainingPercentage=$( expr "100" \* $remainingLifetimeOfSignature / $totalLifetime)

		# store the result of this single RRSIG's check
		if [[ -z $maxRemainingLifetime || $remainingLifetimeOfSignature -gt $maxRemainingLifetime ]]; then
			maxRemainingLifetime=$remainingLifetimeOfSignature
			maxRemainingPercentage=$remainingPercentage
		fi
	done <<< "$rrsigEntries"
fi




# determine if we need to alert, and if so, how loud to cry, depending on warning/critial threshholds provided
if [[ $maxRemainingPercentage -lt $critical ]]; then
	echo "CRITICAL: DNSSEC signature for $zone is very short before expiration! ($maxRemainingPercentage% remaining) | sig_lifetime=$maxRemainingLifetime  sig_lifetime_percentage=$remainingPercentage%;$warning;$critical"
	exit 2
elif [[ $remainingPercentage -lt $warning ]]; then
	echo "WARNING: DNSSEC signature for $zone is short before expiration! ($maxRemainingPercentage% remaining) | sig_lifetime=$maxRemainingLifetime  sig_lifetime_percentage=$remainingPercentage%;$warning;$critical"
	exit 1
else
	echo "OK: DNSSEC signatures for $zone seem to be valid and not expired ($maxRemainingPercentage% remaining) | sig_lifetime=$maxRemainingLifetime  sig_lifetime_percentage=$remainingPercentage%;$warning;$critical"
	exit 0
fi