Compare commits

..

10 Commits

Author SHA1 Message Date
Stephane Bortzmeyer
ff901cfc06 Fix a bug when no RDAP server and verbose 2024-07-13 18:00:37 +02:00
Stephane Bortzmeyer
014fe9fec5 Merge branch 'Seb35-issue-4' 2024-06-16 20:43:20 +02:00
Stephane Bortzmeyer
60a6410941 Merge branch 'issue-4' of https://forge.chapril.org/Seb35/check_expire into Seb35-issue-4 2024-06-16 20:40:29 +02:00
Stephane Bortzmeyer
4e17d48adb Merge branch 'Seb35-pull-request-6' 2024-06-16 20:37:52 +02:00
Stephane Bortzmeyer
f7e355702f Post-merge cleaning 2024-06-16 20:37:42 +02:00
Stephane Bortzmeyer
6d6f054a26 Iran RDAP server down 2024-06-16 20:36:55 +02:00
Stephane Bortzmeyer
30d4678e03 Merge branch 'pull-request-6' of https://forge.chapril.org/Seb35/check_expire into Seb35-pull-request-6 2024-06-16 20:36:43 +02:00
Stephane Bortzmeyer
9dc05e9c7b Error code outside of its visibility 2023-03-15 19:53:12 +00:00
3af8b31034 Fix two failing tests
By the way, converted the timeout parameter to a float.
2022-12-18 19:56:04 +01:00
0b10e8d563 Allow a specific cache directory or no cache
If the env var XDG_CACHE_HOME is set, the cache directory is
"$XDG_CACHE_HOME/ianardap", else if HOME is set, it is
"$HOME/.ianardapcaches", else no cache is used but a loud
warning is displayed at the end of the result to encourage
users to set a cache directory.

Adapted pytest tests, no change needed on test_exe_matrix. They are
obviously slower with no cache. NB: two tests now fail independently
of this change.

Issue: #4
2022-12-18 19:09:49 +01:00
6 changed files with 66 additions and 39 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
__pycache__/

View File

@ -19,6 +19,8 @@ You need Python 3 and [Requests](http://python-requests.org/). You can install R
Then, copy the script `check_expire` and the file `ianardap.py`to the directory of local plugins. Then, copy the script `check_expire` and the file `ianardap.py`to the directory of local plugins.
The use of a cache directory is not mandatory, but highly recommended. It can be either `$XDG_CACHE_HOME/ianardap` or `$HOME/.ianardapcaches` depending on the environment variables set.
## Icinga configuration ## Icinga configuration
If you use Icinga, here is a possible definition of the command: If you use Icinga, here is a possible definition of the command:
@ -35,6 +37,7 @@ object CheckCommand "expiration" {
"-v" = { set_if = "$expiration_verbose$" } "-v" = { set_if = "$expiration_verbose$" }
} }
env.XDG_CACHE_HOME = "/var/cache/nagios"
} }
apply Service "expiration" { apply Service "expiration" {
@ -60,6 +63,13 @@ object Host "bortzmeyer-org" {
} }
``` ```
If needed, create the cache directory accordingly:
```
mkdir /var/cache/nagios
chown nagios: /var/cache/nagios
```
## Zabbix configuration ## Zabbix configuration
For monitoring systems that do not rely on exit codes but on calculation mechanism based on the metric they receive you can use the `-u` option that will only return the expiration date in unixtime format. For monitoring systems that do not rely on exit codes but on calculation mechanism based on the metric they receive you can use the `-u` option that will only return the expiration date in unixtime format.

View File

@ -45,6 +45,9 @@ warning_t = datetime.timedelta(days=7)
unixtime = False unixtime = False
timeout = 20 # Seconds timeout = 20 # Seconds
# Cannot be changed on the command-line (yet)
server = None
def usage(msg=None): def usage(msg=None):
print("Usage: %s -H domain-name [-c critical -w warning -u -t timeout]" % sys.argv[0], end="") print("Usage: %s -H domain-name [-c critical -w warning -u -t timeout]" % sys.argv[0], end="")
if msg is not None and msg != "": if msg is not None and msg != "":
@ -53,6 +56,8 @@ def usage(msg=None):
print("") print("")
def details(): def details():
if database and not database.cachefile:
print(" (WARNING: no cache directory used, please set environement variable XDG_CACHE_HOME or HOME.)", end="")
if verbose: if verbose:
print(" RDAP database \"%s\", version %s published on %s, retrieved on %s, RDAP server is %s" % \ print(" RDAP database \"%s\", version %s published on %s, retrieved on %s, RDAP server is %s" % \
(database.description, database.version, database.publication, database.retrieved, server)) (database.description, database.version, database.publication, database.retrieved, server))
@ -101,7 +106,7 @@ try:
elif option == "--hostname" or option == "-H": elif option == "--hostname" or option == "-H":
domain = value domain = value
elif option == "--timeout" or option == "-t": elif option == "--timeout" or option == "-t":
timeout = int(value) timeout = float(value)
elif option == "--unixtime" or option == "-u": elif option == "--unixtime" or option == "-u":
unixtime = True unixtime = True
elif option == "--verbose" or option == "-v": elif option == "--verbose" or option == "-v":
@ -142,6 +147,7 @@ for server in servers:
response = requests.get("%s/domain/%s" % (server, domain), timeout=timeout) response = requests.get("%s/domain/%s" % (server, domain), timeout=timeout)
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
unknowns += "Timeout when trying to reach %s " % server unknowns += "Timeout when trying to reach %s " % server
continue
if response.status_code != 200: if response.status_code != 200:
errors += "Invalid RDAP return code at %s: %s " % \ errors += "Invalid RDAP return code at %s: %s " % \
(server, response.status_code) (server, response.status_code)

View File

@ -21,7 +21,8 @@ IANABASES = {"domains": "https://data.iana.org/rdap/dns.json",
"v6prefixes": "https://data.iana.org/rdap/ipv6.json", "v6prefixes": "https://data.iana.org/rdap/ipv6.json",
"as": "https://data.iana.org/rdap/asn.json", "as": "https://data.iana.org/rdap/asn.json",
"objects": "https://data.iana.org/rdap/object-tags.json"} "objects": "https://data.iana.org/rdap/object-tags.json"}
CACHE = os.environ["HOME"] + "/.ianardapcaches" CACHE = os.environ["XDG_CACHE_HOME"] + "/ianardap" if "XDG_CACHE_HOME" in os.environ else \
(os.environ["HOME"] + "/.ianardapcaches" if "HOME" in os.environ else None)
MAXAGE = 24 # Hours. Used only if the server no longer gives the information. MAXAGE = 24 # Hours. Used only if the server no longer gives the information.
IANATIMEOUT = 10 # Seconds IANATIMEOUT = 10 # Seconds
MAXTESTS = 3 # Maximum attempts to get the database MAXTESTS = 3 # Maximum attempts to get the database
@ -60,28 +61,33 @@ file (see the documentation of the module).
""" """
cache_valid = False cache_valid = False
if not os.path.exists(cachedir):
os.mkdir(cachedir)
self.category = category self.category = category
cachefile = os.path.join(cachedir, category) self.cachefile = None
if pickleformat: self.lockname = None
self.cachefile = cachefile + ".pickle" self.expirationfile = None
else: if cachedir:
self.cachefile = cachefile + ".json" if not os.path.exists(cachedir):
self.lockname = self.cachefile + ".lock" os.mkdir(cachedir)
self.expirationfile = self.cachefile + ".expires" cachefile = os.path.join(cachedir, category)
if pickleformat:
self.cachefile = cachefile + ".pickle"
else:
self.cachefile = cachefile + ".json"
self.lockname = self.cachefile + ".lock"
self.expirationfile = self.cachefile + ".expires"
if maxage is not None: if maxage is not None:
with open(self.expirationfile, 'w'): self.expirationtime = time.mktime((datetime.datetime.now() + \
self.expirationtime = time.mktime((datetime.datetime.now() + \ datetime.timedelta(hours=maxage)).timetuple())
datetime.timedelta(hours=maxage)).timetuple()) if self.expirationfile:
os.utime(self.expirationfile, with open(self.expirationfile, 'w'):
times = (self.expirationtime, self.expirationtime)) os.utime(self.expirationfile,
times = (self.expirationtime, self.expirationtime))
loaded = False loaded = False
tests = 0 tests = 0
errmsg = "No error" errmsg = "No error"
while not loaded and tests < MAXTESTS: while not loaded and tests < MAXTESTS:
self.lock() self.lock()
if os.path.exists(self.cachefile) and \ if self.cachefile and os.path.exists(self.cachefile) and \
(pathlib.Path(self.expirationfile).exists() and \ (pathlib.Path(self.expirationfile).exists() and \
datetime.datetime.fromtimestamp(os.path.getmtime(self.expirationfile)) > \ datetime.datetime.fromtimestamp(os.path.getmtime(self.expirationfile)) > \
datetime.datetime.now()): datetime.datetime.now()):
@ -139,9 +145,10 @@ file (see the documentation of the module).
try: try:
content = response.content content = response.content
database = json.loads(content) database = json.loads(content)
with open(self.expirationfile, 'w'): if self.expirationfile:
os.utime(self.expirationfile, with open(self.expirationfile, 'w'):
times = (self.expirationtime, self.expirationtime)) os.utime(self.expirationfile,
times = (self.expirationtime, self.expirationtime))
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
tests += 1 tests += 1
errmsg = "Invalid JSON retrieved from %s" % IANABASE errmsg = "Invalid JSON retrieved from %s" % IANABASE
@ -173,7 +180,7 @@ file (see the documentation of the module).
else: # IP addresses will be complicated, because of the else: # IP addresses will be complicated, because of the
# longest prefix rule. # longest prefix rule.
raise Exception("Unsupported category %s" % self.category) raise Exception("Unsupported category %s" % self.category)
if not cache_valid: if self.cachefile and not cache_valid:
self.lock() self.lock()
cache = open(self.cachefile, "wb") cache = open(self.cachefile, "wb")
if pickleformat: if pickleformat:
@ -184,12 +191,14 @@ file (see the documentation of the module).
self.unlock() self.unlock()
def lock(self): def lock(self):
self.lockhandle = open(self.lockname, 'w') if self.lockname:
fcntl.lockf(self.lockhandle, fcntl.LOCK_EX) self.lockhandle = open(self.lockname, 'w')
fcntl.lockf(self.lockhandle, fcntl.LOCK_EX)
def unlock(self): def unlock(self):
fcntl.lockf(self.lockhandle, fcntl.LOCK_UN) if self.lockname:
self.lockhandle.close() fcntl.lockf(self.lockhandle, fcntl.LOCK_UN)
self.lockhandle.close()
def find(self, id): def find(self, id):
"""Get the RDAP server(s), as an array, for a given identifier. None """Get the RDAP server(s), as an array, for a given identifier. None

View File

@ -29,8 +29,9 @@ def test_refresh():
# Force a resfresh # Force a resfresh
database = ianardap.IanaRDAPDatabase(maxage=0) database = ianardap.IanaRDAPDatabase(maxage=0)
assert (database.retrieved > (datetime.datetime.now() - datetime.timedelta(minutes=1))) and \ assert (database.retrieved > (datetime.datetime.now() - datetime.timedelta(minutes=1))) and \
((not database.cachefile) or \
(datetime.datetime.fromtimestamp(os.path.getmtime(database.cachefile)) > \ (datetime.datetime.fromtimestamp(os.path.getmtime(database.cachefile)) > \
(datetime.datetime.now() - datetime.timedelta(minutes=1))) (datetime.datetime.now() - datetime.timedelta(minutes=1))))
def test_find_exists(): def test_find_exists():
database = ianardap.IanaRDAPDatabase() database = ianardap.IanaRDAPDatabase()

View File

@ -28,11 +28,11 @@ tests:
retcode: 1 retcode: 1
partstderr: 'ValueError' partstderr: 'ValueError'
# 2021-07-05: no RDAP server for this TLD # No RDAP server for this TLD
- exe: './check_expire' - exe: './check_expire'
args: args:
- '-H' - '-H'
- 'bie.re' - 'welcome.this-is-not-a-tld'
retcode: 3 retcode: 3
partstdout: 'No RDAP server' partstdout: 'No RDAP server'
@ -45,13 +45,13 @@ tests:
retcode: 2 retcode: 2
partstdout: 'No expiration found' partstdout: 'No expiration found'
# Far away and slow, timeout is expected # With a timeout of 1µs, a timeout is expected
- exe: './check_expire' - exe: './check_expire'
args: args:
- '-t' - '-t'
- '1' - '0.000001'
- '-H' - '-H'
- 'nic.ar' - 'bortzmeyer.org'
retcode: 3 retcode: 3
partstdout: 'Timeout' partstdout: 'Timeout'
@ -143,14 +143,14 @@ tests:
retcode: 0 retcode: 0
stderr: '' stderr: ''
# Iran, expiration date in the past # Iran, expiration date in the past. But down (2024-06-16)
- exe: './check_expire' #- exe: './check_expire'
args: # args:
- '-H' # - '-H'
- 'nic.pars' # - 'nic.pars'
retcode: 2 # retcode: 2
partstdout: "already expired" # partstdout: "already expired"
stderr: '' # stderr: ''
# Brazil # Brazil
- exe: './check_expire' - exe: './check_expire'