From c10b6ab95cb42f18fb10ca27f7d6c0c5cb97c9ca Mon Sep 17 00:00:00 2001 From: Nemo Date: Sat, 3 Oct 2020 14:19:22 +0200 Subject: [PATCH] Update icinga2_server role --- roles/icinga2_server/defaults/main.yml | 5 + roles/icinga2_server/files/check_dane | 440 ++++++++++++++++++ roles/icinga2_server/files/check_openvpn | 187 ++++++++ roles/icinga2_server/files/check_rdns | 144 ++++++ .../tasks/configure_icinga2_api_feature.yml | 12 +- roles/icinga2_server/tasks/main.yml | 3 +- .../tasks/postconfigure_icinga2.yml | 40 ++ .../templates/conf.d/command-custom.conf.j2 | 143 ++++++ .../templates/conf.d/commands.conf.j2 | 187 ++++++++ .../templates/conf.d/groups.conf.j2 | 44 ++ .../templates/conf.d/notifications.conf.j2 | 33 ++ .../templates/conf.d/services.conf.j2 | 277 +++++++++++ .../templates/conf.d/templates.conf.j2 | 95 ++++ .../templates/conf.d/timeperiods.conf.j2 | 61 +++ .../templates/conf.d/users.conf.j2 | 31 ++ .../templates/constants.conf.j2 | 28 ++ 16 files changed, 1728 insertions(+), 2 deletions(-) create mode 100644 roles/icinga2_server/files/check_dane create mode 100644 roles/icinga2_server/files/check_openvpn create mode 100644 roles/icinga2_server/files/check_rdns create mode 100644 roles/icinga2_server/tasks/postconfigure_icinga2.yml create mode 100644 roles/icinga2_server/templates/conf.d/command-custom.conf.j2 create mode 100644 roles/icinga2_server/templates/conf.d/commands.conf.j2 create mode 100644 roles/icinga2_server/templates/conf.d/groups.conf.j2 create mode 100644 roles/icinga2_server/templates/conf.d/notifications.conf.j2 create mode 100644 roles/icinga2_server/templates/conf.d/services.conf.j2 create mode 100644 roles/icinga2_server/templates/conf.d/templates.conf.j2 create mode 100644 roles/icinga2_server/templates/conf.d/timeperiods.conf.j2 create mode 100644 roles/icinga2_server/templates/conf.d/users.conf.j2 create mode 100644 roles/icinga2_server/templates/constants.conf.j2 diff --git a/roles/icinga2_server/defaults/main.yml b/roles/icinga2_server/defaults/main.yml index 3206100..d368536 100644 --- a/roles/icinga2_server/defaults/main.yml +++ b/roles/icinga2_server/defaults/main.yml @@ -74,6 +74,7 @@ icinga2_server_api_users: icinga2_server_icingaweb2_main_user: john icinga2_server_icingaweb2_main_user_password: needToBeChanged +icinga2_server_icingaweb2_main_user_email: "needToBeChange@example.org" icinga2_server_apache2_service: apache2 icinga2_server_apache2_default_index: "/var/www/html/index.html" icinga2_server_apache2_user: "www-data" @@ -86,3 +87,7 @@ icinga2_server_apache2_modules_to_disable: - negociation icinga2_server_icingaweb2_main_user_password_hash_manual: needToBeChanged +icinga2_server_ticket_salt: "" +icinga2_server_custom_hostgroup: wirebrass + +icinga2_server_nagios_plugins_location: "/usr/lib/nagios/plugins/" diff --git a/roles/icinga2_server/files/check_dane b/roles/icinga2_server/files/check_dane new file mode 100644 index 0000000..1749c6a --- /dev/null +++ b/roles/icinga2_server/files/check_dane @@ -0,0 +1,440 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2014-2016 Felix Geyer +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 or (at your option) +# version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import argparse +import datetime +import dns.exception +import dns.flags +import dns.rdatatype +import dns.rdtypes.ANY.TLSA +import dns.resolver +import hashlib +import re +import socket +import struct +import ssl +import subprocess +import sys + + +VERSION = "1.1" + + +class ProtocolError(Exception): + pass + + +def nagios_ok(msg: str) -> None: + print("DANE OK - " + msg) + sys.exit(0) + + +def nagios_warning(msg: str) -> None: + print("DANE WARNING - " + msg) + sys.exit(1) + + +def nagios_critical(msg: str) -> None: + print("DANE CRITICAL - " + msg) + sys.exit(2) + + +def nagios_unknown(msg: str) -> None: + print("DANE UNKONWN - " + msg) + sys.exit(3) + + +def create_resolver(dnssec: bool = True, + timeout: int = None, + nameserver: str = None) -> dns.resolver.Resolver: + resolver = dns.resolver.Resolver() + + if timeout and timeout != 0: + resolver.lifetime = timeout + + if nameserver: + resolver.nameservers = [nameserver] + + if dnssec: + resolver.edns = 0 + resolver.payload = 1280 + resolver.ednsflags = dns.flags.DO + + return resolver + + +def check_dns_response_auth(response: dns.resolver.Answer) -> bool: + if response.response.flags & dns.flags.AD: + return True + else: + return False + + +def extract_pubkey(cert_binary: bytes) -> bytes: + # should really be done with a python 3 module that exposes this openssl api + + # extract public key in pem format + pubkey_pem = subprocess.check_output(["openssl", "x509", "-pubkey", "-inform", "der", "-noout"], input=cert_binary) + # conver to binary / der format + pubkey_binary = subprocess.check_output(["openssl", "pkey", "-pubin", "-outform", "der"], input=pubkey_pem) + + return pubkey_binary + + +def get_tlsa_records(args: argparse.Namespace) -> dns.resolver.Answer: + resolver = create_resolver(dnssec=args.dnssec, timeout=args.timeout, nameserver=args.nameserver) + + tlsa_domain = "_{}._tcp.{}".format(args.port, args.host) + + try: + tlsa_records = resolver.query(tlsa_domain, dns.rdatatype.TLSA) + except dns.resolver.NXDOMAIN: + nagios_critical("No DNS TLSA record found: {}".format(tlsa_domain)) + except dns.exception.Timeout: + nagios_unknown("DNS query timeout: {}".format(tlsa_domain)) + + if args.dnssec and not check_dns_response_auth(tlsa_records): + nagios_unknown("DNS query not DNSSEC validated") + + return tlsa_records + + +def validate_dane(cert_binary: bytes, + pkix_valid: bool, + tlsa_record: dns.rdtypes.ANY.TLSA.TLSA) -> bool: + if tlsa_record.usage == 3: + pass + elif tlsa_record.usage == 1 and pkix_valid: + pass + else: + # usage=2 unsupported + return False + + if tlsa_record.selector == 0: + data = cert_binary + elif tlsa_record.selector == 1: + data = extract_pubkey(cert_binary) + else: + return False + + if tlsa_record.mtype == 0: + hashed = data + elif tlsa_record.mtype == 1: + hashed = hashlib.sha256(data).digest() + elif tlsa_record.mtype == 2: + hashed = hashlib.sha512(data).digest() + else: + return False + + return hashed == tlsa_record.cert + + +def check_cert_expiry(args: argparse.Namespace, + cert: dict, + days_warning: int, + days_critical: int = None) -> datetime.timedelta: + not_after = datetime.datetime.strptime(cert["notAfter"], "%b %d %H:%M:%S %Y %Z") + date_diff = not_after - datetime.datetime.now() + + if days_critical: + if date_diff <= datetime.timedelta(days_critical): + nagios_critical("{}:{} cert expires in {} days".format(args.host, args.port, date_diff.days)) + + if date_diff <= datetime.timedelta(days_warning): + nagios_warning("{}:{} cert expires in {} days".format(args.host, args.port, date_diff.days)) + + return date_diff + + +def connect_to_host(connect_host: str, + connect_port: int, + args: argparse.Namespace, + check_cert: bool) -> ssl.SSLSocket: + if args.timeout == 0: + socket.setdefaulttimeout(None) + else: + socket.setdefaulttimeout(args.timeout) + + context = ssl.create_default_context() + if not check_cert: + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + + # TODO: use our resolver + try: + sock = socket.create_connection((connect_host, connect_port)) + except OSError as e: + nagios_unknown("Can't establish a connection to {}:{}:\n{}" + .format(connect_host, connect_port, str(e))) + + peername = sock.getpeername()[0] + if ":" in peername: + peername = "[{}]".format(peername) + + try: + if args.starttls == "smtp": + connect_smtp(sock) + elif args.starttls == "ftp": + connect_ftp(sock) + elif args.starttls == "imap": + connect_imap(sock) + elif args.starttls == "xmpp": + connect_xmpp(sock, args.host) + elif args.starttls == "quassel": + connect_quassel(sock, args.host) + except (ProtocolError, OSError) as e: + nagios_unknown("Failed to initiate STARTTLS to {} ({}:{}):\n{}" + .format(connect_host, peername, connect_port, str(e))) + + try: + return context.wrap_socket(sock, server_hostname=args.host) + except (ProtocolError, ssl.SSLError, OSError) as e: + if isinstance(e, ssl.SSLError) and e.reason == "CERTIFICATE_VERIFY_FAILED": + # pass exceptions concerning certificate validation to the caller + raise + else: + nagios_unknown("Can't establish a TLS connection to {} ({}:{}):\n{}" + .format(connect_host, peername, connect_port, str(e))) + + +def smtp_read_response(sock: socket.socket) -> str: + response = "" + line_end = False + while not line_end: + line = sock.recv(1024) + response += line.decode("ASCII") + if len(line) < 4 or line[3] != "-": + line_end = True + return response + + +def connect_smtp(sock: socket.socket) -> None: + smtp_read_response(sock) + sock.sendall(b"EHLO openssl.client.net\r\n") + + response = smtp_read_response(sock) + if "STARTTLS" not in response: + raise ProtocolError("Server doesn't support STARTTLS") + + sock.sendall(b"STARTTLS\r\n") + + smtp_read_response(sock) + + +def connect_ftp(sock: socket.socket) -> None: + buf = sock.recv(1024) + + sock.sendall(b"AUTH TLS\r\n") + + response = sock.recv(1024).decode("ASCII").strip("\r\n\t ") + if not re.search(r"^2\d\d", response): + raise ProtocolError("Server doesn't support STARTTLS ({})".format(response)) + + +def connect_imap(sock: socket.socket) -> None: + sock.recv(1024) + sock.sendall(b". CAPABILITY\n") + + response = smtp_read_response(sock) + if "STARTTLS" not in response: + raise ProtocolError("Server doesn't support STARTTLS") + + sock.sendall(b". STARTTLS\n") + + smtp_read_response(sock) + + +def connect_xmpp(sock: socket.socket, host: str) -> None: + sock.sendall("".format(host).encode("ASCII")) + + buf = sock.recv(1024) + if "") + + buf = sock.recv(1024) + if " None: + MAGIC = 0x42b33f00 + FEATURE_ENCRYPTION = 0x1 + PROTOCOL_DATAGRAM = 0x2 + PROTOCOL_END = (0x1 << 31) + + sock.sendall(struct.pack("!I", MAGIC | FEATURE_ENCRYPTION)) + sock.sendall(struct.pack("!I", PROTOCOL_DATAGRAM | PROTOCOL_END)) + + try: + response = struct.unpack("!I", sock.recv(4))[0] + except struct.error: + raise ProtocolError("No valid response from server") + + protocol_type = (response & 0xff) + connection_features = (response >> 24) + + if not (protocol_type & PROTOCOL_DATAGRAM): + raise ProtocolError("Server doesn't support the protocol") + + if not (connection_features & FEATURE_ENCRYPTION): + raise ProtocolError("Server doesn't support TLS") + + +def main() -> None: + parser = argparse.ArgumentParser(description=""" Nagios/Icinga plugin for checking DANE/TLSA records. + It compares the DANE/TLSA record against the TLS certificate provided + by a service.""") + + parser.add_argument("--host", "-H", dest="host", required=True, help="Hostname to check.") + parser.add_argument("--port", "-p", type=int, required=True, help="TCP port to check.") + parser.add_argument("--connect-host", "--ip", "-I", dest="connect_host", + help="Connect to this host instead of --host.") + parser.add_argument("--connect-port", dest="connect_port", help="Connect to this port instead of --port.") + parser.add_argument("--starttls", + choices=["smtp", "ftp", "imap", "xmpp", "quassel"], + help="Send the protocol-specific messages to enable TLS.") + parser = argparse.ArgumentParser(description=""" Nagios/Icinga plugin for checking DANE/TLSA records. + It compares the DANE/TLSA record against the TLS certificate provided + by a service.""") + + parser.add_argument("--host", "-H", dest="host", required=True, help="Hostname to check.") + parser.add_argument("--port", "-p", type=int, required=True, help="TCP port to check.") + parser.add_argument("--connect-host", "--ip", "-I", dest="connect_host", + help="Connect to this host instead of --host.") + parser.add_argument("--connect-port", dest="connect_port", help="Connect to this port instead of --port.") + parser.add_argument("--starttls", + choices=["smtp", "ftp", "imap", "xmpp", "quassel"], + help="Send the protocol-specific messages to enable TLS.") + parser.add_argument("--check-pkix", action="store_true", + help="Additionally perform traditional checks on the certificate " + "(ca trust path, hostname, expiry).") + parser.add_argument("--min-days-valid", help="Minimum number of days a certificate has to be valid. " + "Format: INTEGER[,INTEGER]. " + "1st is #days for warning, 2nd is critical.") + parser.add_argument("--no-dnssec", dest="dnssec", action="store_false", + help="Continue even when DNS replies aren't DNSSEC authenticated.") + parser.add_argument("--nameserver", help="Use a custom nameserver.") + parser.add_argument("--timeout", type=int, default=10, help="Network timeout in sec. Default: 10") + parser.add_argument("--version", action="version", version="%(prog)s " + VERSION) + args = parser.parse_args() + + pyver = sys.version_info + if pyver[0] < 3 or (pyver[0] == 3 and pyver[1] < 4): + nagios_unknown("check_dane requires Python >= 3.4") + + if args.port < 1 or args.port > 65535: + nagios_unknown("Invalid port") + + if args.min_days_valid and not re.search(r"^\d+(,\d+)?$", args.min_days_valid): + nagios_unknown("--check-cert-expire takes INTEGER[,INTEGER] as arguments") + + if args.timeout < 0: + nagios_unknown("Invalid timeout argument") + + if args.connect_host: + connect_host = args.connect_host + else: + connect_host = args.host + + if args.connect_port: + connect_port = args.connect_port + else: + connect_port = args.port + + tlsa_records = get_tlsa_records(args) + + has_usage1_tlsa = False + for record in tlsa_records: + if record.usage == 1: + has_usage1_tlsa = True + break + + initial_check_pkix = (args.check_pkix or has_usage1_tlsa) + + try: + # validate against PKIX if manually requested or we've found a usage=1 tlsa record + ssl_sock = connect_to_host(connect_host, connect_port, args, initial_check_pkix) + pkix_valid = initial_check_pkix + except (ssl.CertificateError, ssl.SSLError) as e: + if args.check_pkix: + nagios_critical(str(e)) + else: + ssl_sock = connect_to_host(connect_host, connect_port, args, False) + pkix_valid = False + pkix_error = str(e) + + cert_binary = ssl_sock.getpeercert(binary_form=True) + cert_dict = ssl_sock.getpeercert() + ssl_sock.close() + + dane_valid_cert = False + + for tlsa in tlsa_records: + if validate_dane(cert_binary, pkix_valid, tlsa): + dane_valid_cert = True + break + + if not dane_valid_cert: + # test if it would match if it were pkix_valid + additional_msg = "" + for tlsa in tlsa_records: + if validate_dane(cert_binary, True, tlsa): + additional_msg = "\nIt matches a TLSA usage=1 record but fails PKIX validation:\n" + pkix_error + break + + nagios_critical("Certificate doesn't match TLSA record" + additional_msg) + + if pkix_valid and args.min_days_valid: + days_parts = args.min_days_valid.split(",") + + if len(days_parts) == 2: + timedelta_valid = check_cert_expiry(args, cert_dict, int(days_parts[0]), int(days_parts[1])) + else: + timedelta_valid = check_cert_expiry(args, cert_dict, int(days_parts[0])) + + expire_str = ", expires in {} days".format(timedelta_valid.days) + else: + expire_str = "" + + message = "{}:{} cert matches TLSA record".format(args.host, args.port) + if not args.dnssec: + message += " (DNSSEC not validated)" + message += expire_str + + nagios_ok(message) + + +if __name__ == "__main__": + main() + + + message = "{}:{} cert matches TLSA record".format(args.host, args.port) + if not args.dnssec: + message += " (DNSSEC not validated)" + message += expire_str + + nagios_ok(message) + + +if __name__ == "__main__": + main() + +# kate: space-indent on; indent-width 4; + diff --git a/roles/icinga2_server/files/check_openvpn b/roles/icinga2_server/files/check_openvpn new file mode 100644 index 0000000..df12a52 --- /dev/null +++ b/roles/icinga2_server/files/check_openvpn @@ -0,0 +1,187 @@ +#!/usr/bin/env python + +# Check if an OpenVPN server runs on a given UDP or TCP port. +# +# Copyright 2013 Roland Wolters +# Copyright 2016 Alarig Le Lay +# +# Version 20160803 +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import os +import sys +import time +import hmac +import hashlib +import struct +import socket +import argparse +import binascii + +HMAC_CLIENT_KEY_START = 192 +BUFFER_SIZE = 1024 + +ALGORITHMS_AVAILABLE = hashlib.algorithms_available \ + if hasattr(hashlib, "algorithms_available") else hashlib.algorithms + +def ok(msg): + print('OK: %s' % msg) + return 0 + +def critical(msg): + print('CRIT: %s' % msg) + return 2 + +def buildpacket(tcp, key, digestmod): + packet = 1 + ts = int(time.time()) + session = os.urandom(8) + + if key: + # hmac + h = hmac.new(key, digestmod=digestmod) + h.update(struct.pack('>I', packet)) # packet id + h.update(struct.pack('>I', ts)) # net time + h.update(b'\x38') # type + h.update(session) # session id + h.update(struct.pack('>B', 0)) # message packet id array length + h.update(struct.pack('>I', 0)) # message packet id + + # packet + result = b'' + result += b'\x38' # type + result += session # session id + if key: result += h.digest() # hmac + result += struct.pack('>I', packet) # packet id + result += struct.pack('>I', ts) # net time + result += struct.pack('>B', 0) # message packet id array length + result += struct.pack('>I', 0) # message packet id + if tcp: result = struct.pack('>H', len(result)) + result + return result + +def checkserver(host, port, tcp, timeout, key, digest): + packet = buildpacket(tcp, key, digest) + check = checkserver_tcp if tcp else checkserver_udp + return check(host, port, timeout, packet) + +def checkserver_udp(host, port, timeout, packet): + # thanks to glucas for the idea + try: + af, socktype, proto, canonname, sa = socket.getaddrinfo(host, port, \ + socket.AF_UNSPEC, socket.SOCK_DGRAM)[0] + s = socket.socket(af, socktype, proto) + s.settimeout(timeout) + except socket.error: + return critical('Unable to create UDP socket') + + try: + s.sendto(packet, (host, port)) + data, _ = s.recvfrom(BUFFER_SIZE) + reply = binascii.hexlify(data) + return ok('OpenVPN UDP server response (hex): %s' % reply) + except: + return critical('OpenVPN UDP server not responding') + finally: + s.close() + +def checkserver_tcp(host, port, timeout, packet): + try: + af, socktype, proto, canonname, sa = socket.getaddrinfo(host, port, \ + socket.AF_UNSPEC, socket.SOCK_STREAM)[0] + s = socket.socket(af, socktype, proto) + s.settimeout(timeout) + except socket.error: + return critical('Unable to create TCP socket') + + try: + s.connect((host, port)) + s.send(packet) + data = s.recv(BUFFER_SIZE) + if len(data) <= 0: raise RuntimeError + reply = binascii.hexlify(data) + return ok('OpenVPN TCP server response (hex): %s' % reply) + except: + return critical('OpenVPN TCP server not responding') + finally: + s.close() + +def readkey(path): + key = None + try: + with open(path, 'r') as myfile: key = myfile.read() + except: + return None + index_start = key.find('-\n'); + index_end = key.find('\n-', index_start); + if index_start < 0 or index_end < 0 or index_end <= index_start: + return None + index_start += 2 + key = key[index_start:index_end].replace('\n', '').replace('\r', '') + return key + +def optionsparser(argv=None): + parser = argparse.ArgumentParser() + parser.add_argument('-p', '--port', help='set port number (default is %(default)d)', type=int, default=1194) + parser.add_argument('-t', '--tcp', help='use tcp instead of udp', action='store_true') + parser.add_argument('--timeout', help='set timeout (default is %(default)d)', type=int, default=5) + parser.add_argument('--digest', help='set HMAC digest (default is "%(default)s")', default='sha1') + parser.add_argument('--digest-size', help='set HMAC digest size', type=int) + parser.add_argument('--digest-key', help='set HMAC key') + parser.add_argument('--tls-auth', help='set tls-auth file') + parser.add_argument('host', help='the OpenVPN host name or IP') + return parser.parse_args(argv) + +def main(argv=None): + args = optionsparser(argv) + + if args.digest_size and args.digest_size < 0: + critical('digest size must be positive') + if args.tls_auth and args.digest_key: + critical('--tls-auth cannot go with --digest-key') + + key = args.digest_key + digest = args.digest + digest_size = args.digest_size + + digest = digest.lower() + if digest not in ALGORITHMS_AVAILABLE: + return critical('digest not available') + try: + digest = getattr(hashlib, digest) + if not digest_size: digest_size = digest().digest_size + except: + return critical('digest creation failed') + + if args.tls_auth: + key = readkey(args.tls_auth) + if key == None: return critical('cannot read tls auth file') + index_start = HMAC_CLIENT_KEY_START * 2 + index_end = (HMAC_CLIENT_KEY_START + digest_size) * 2 + key = key[index_start:index_end] + + if key: key = binascii.unhexlify(key) + + return checkserver(args.host, args.port, args.tcp, args.timeout, key, digest) + +if __name__ == '__main__': + code = main() + sys.exit(code) + diff --git a/roles/icinga2_server/files/check_rdns b/roles/icinga2_server/files/check_rdns new file mode 100644 index 0000000..41ec997 --- /dev/null +++ b/roles/icinga2_server/files/check_rdns @@ -0,0 +1,144 @@ +#!/bin/sh + +# Copyright (c) 2013 Stefan Huber +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +. $(dirname $0)/utils.sh + + +PROGNAME=$(basename $0) +REVISION="0.1" + +usage() { + + cat < + +Usage: $PROGNAME -H ip-address [OPTIONS] + +Arguments: + -H, --address IP-address The ip-addres on which reverse-DNS is performed. + +Options: + -h, --help Print this text. + -a, --expect=HOST The expected result. + -s, --server=HOST The DNS server to contact. + -t, --timeout=SEC Seconds before lookup times out. (Default: 10) + -w, --warning=MSEC Return warning if lookup time exceeds value. + -c, --critical=MSEC Return critical if lookup time exceeds value. + -V, --version Print version info. +EOF +} + + +TEMP=`getopt -o "H:a:c:hs:t:w:V" --long "help,address:,expect:,critical:,server:,timeout:,warning:,version" -n "$PROGNAME" -- "$@"` +eval set - "$TEMP" + +ADDRESS= +EXPECT= +SERVER= +TIMEOUT=10 +WARNING= +CRITICAL= + +while true; do + case "$1" in + -h | --help ) + usage + exit $STATE_OK ;; + -V | --version ) + echo "$PROGNAME v$REVISION" + exit $STATE_OK ;; + -H | --address ) + ADDRESS="$2"; shift 2 ;; + -a | --expect ) + EXPECT="$2"; shift 2 ;; + -s | --server ) + SERVER="$2"; shift 2 ;; + -t | --timeout ) + TIMEOUT="$2"; shift 2 ;; + -w | --warning ) + WARNING="$2"; shift 2 ;; + -c | --critical ) + CRITICAL="$2"; shift 2 ;; + -- ) + shift + break ;; + * ) + break ;; + esac +done + +if [ -z "$ADDRESS" ]; then + echo "Error: No address given." + usage + exit $STATE_CRITICAL +fi + +DIGOPTS="+time=$TIMEOUT +noquestion +noauthority -t PTR" +[ -z "$SERVER" ] || DIGOPTS="@$SERVER $DIGOPTS" + +RESULT=$(dig $DIGOPTS -x "$ADDRESS"): +DIGSTATUS=$? + +if [ $DIGSTATUS != "0" ]; then + echo "DNS failed: dig exit code $DIGSTATUS |" + exit $STATE_CRITICAL +fi + +# Get the actual result +HOST=$(echo "$RESULT" | grep -m 1 -o "[[:space:]]IN[[:space:]]*PTR[[:space:]].*\.$" | awk '{ print $3 }' ) +if [ -z "$HOST" ]; then + echo "DNS failed: reverse DNS gave no answer. |" + exit $STATE_CRITICAL +fi + + +# Get the query time in msec +QUERYTIME=$(echo "$RESULT" | grep -m 1 "Query time:" | cut -d ":" -f 2 | awk '{ print $1 }') + + +MATCHED= +if [ -n "$EXPECT" ]; then + if [ "$EXPECT" != "$HOST" ]; then + echo "DNS critical - query result \"$HOST\" != \"$EXPECT\", query time: $QUERYTIME msec |" + exit $STATE_CRITICAL + else + MATCHED=" (match ok)" + fi +fi + +if [ -n "$CRITICAL" ] && [ "$QUERYTIME" -gt "$CRITICAL" ]; then + echo "DNS critical - query time $QUERYTIME msec too large ($CRITICAL msec), query result: \"$HOST\"$MATCHED |" + exit $STATE_CRITICAL +fi + +if [ -n "$WARNING" ] && [ "$QUERYTIME" -gt "$WARNING" ]; then + echo "DNS warning - query time $QUERYTIME msec too large ($WARNING msec), query result: \"$HOST\"$MATCHED |" + exit $STATE_WARNING +fi + +echo "DNS OK - query time $QUERYTIME msec, query result: \"$HOST\"$MATCHED |" +exit $STATE_OK + diff --git a/roles/icinga2_server/tasks/configure_icinga2_api_feature.yml b/roles/icinga2_server/tasks/configure_icinga2_api_feature.yml index c17e19b..5d1c994 100644 --- a/roles/icinga2_server/tasks/configure_icinga2_api_feature.yml +++ b/roles/icinga2_server/tasks/configure_icinga2_api_feature.yml @@ -88,7 +88,7 @@ group: "{{ icinga2_server_group }}" mode: 0600 notify: restart icinga2 -# no_log: True + no_log: True - name: execute icinga2 api setup command command: icinga2 api setup @@ -103,5 +103,15 @@ state: present notify: restart icinga2 +- name: constants.conf file installed + template: + src: constants.conf.j2 + dest: /etc/icinga2/constants.conf + owner: "{{ icinga2_server_user }}" + group: "{{ icinga2_server_group }}" + mode: 0644 + notify: restart icinga2 + no_log: True + - name: Flush handlers meta: flush_handlers diff --git a/roles/icinga2_server/tasks/main.yml b/roles/icinga2_server/tasks/main.yml index a9def1c..f2037cb 100644 --- a/roles/icinga2_server/tasks/main.yml +++ b/roles/icinga2_server/tasks/main.yml @@ -6,6 +6,7 @@ #- import_tasks: install_icinga2.yml #- import_tasks: install_mariadb.yml #- import_tasks: install_ido.yml -- import_tasks: configure_icinga2_api_feature.yml +#- import_tasks: configure_icinga2_api_feature.yml #- import_tasks: install_icingaweb2.yml #- import_tasks: configure_icingaweb2.yml +- import_tasks: postconfigure_icinga2.yml diff --git a/roles/icinga2_server/tasks/postconfigure_icinga2.yml b/roles/icinga2_server/tasks/postconfigure_icinga2.yml new file mode 100644 index 0000000..746ba58 --- /dev/null +++ b/roles/icinga2_server/tasks/postconfigure_icinga2.yml @@ -0,0 +1,40 @@ +--- +- name: Icinga2 post config file deployed + template: + src: 'conf.d/{{ item }}.j2' + dest: '/etc/icinga2/conf.d/{{ item }}' + owner: "{{ icinga2_server_user }}" + group: "{{ icinga2_server_group }}" + mode: 0644 + loop: + - commands.conf + - groups.conf + - notifications.conf + - services.conf + - templates.conf + - users.conf + - timeperiods.conf + notify: restart icinga2 + +- name: Icinga2 custom commands deployed + template: + src: 'conf.d/command-custom.conf.j2' + dest: '/usr/share/icinga2/include/command-custom.conf' + owner: root + group: root + mode: 0644 + notify: restart icinga2 + +- name: custom nagios plugins deployed + copy: + src: "{{ item }}" + dest: "{{ icinga2_server_nagios_plugins_location }}/{{ item }}" + owner: root + group: root + mode: 0755 + loop: + - check_dane + - check_openvpn + - check_rdns + notify: restart icinga2 + diff --git a/roles/icinga2_server/templates/conf.d/command-custom.conf.j2 b/roles/icinga2_server/templates/conf.d/command-custom.conf.j2 new file mode 100644 index 0000000..6e1ee34 --- /dev/null +++ b/roles/icinga2_server/templates/conf.d/command-custom.conf.j2 @@ -0,0 +1,143 @@ +object CheckCommand "dane" { + import "plugin-check-command" + + command = [ PluginDir + "/check_dane" ] + + arguments = { + "--host" = { + value = "$dane_host$" + required = true + description = "Hostname to check" + } + "--port" = { + value = "$dane_port$" + required = true + description = "TCP port to check" + } + "--connect-host" = { + value = "$dane_connect_host$" + description = "Connect to this host instead of $dane_host$" + } + "--connect-port" = { + value = "$dane_connect_port$" + description = "Connect to this host instead of $dane_port$" + } + "--starttls" = { + value = "$dane_starttls$" + description = "Send the protocol-specific messages to enable TLS. Possible values: smtp, imap, xmpp, quassel" + } + "--check-pkix" = { + set_if = "$dane_check_pkix$" + description = "Additionally perform traditional checks on the certificate (ca trust path, hostname, expiry)." + } + "--nameserver" = { + value = "$dane_nameserver$" + description = "Use a custom nameserver." + } + "--min-days-valid" = { + value = "$dane_min_days_valid$" + description = "Minimum number of days a certificate has to be valid. Format: INTEGER[,INTEGER]. 1st is #days for warning, 2nd is critical." + } + "--timeout" = { + value = "$dane_timeout$" + description = "Network timeout in sec. Default: 10" + } + "--no-dnssec" = { + set_if = "$dane_no_dnssec$" + description = "Continue even when DNS replies aren't DNSSEC authenticated." + } + } +} + +object CheckCommand "openvpn" { + import "ipv4-or-ipv6" + + command = [ PluginDir + "/check_openvpn", "$openvpn_address$" ] + + arguments = { + "-p" = { + value = "$openvpn_port$" + description = "set port number (default is 1194)" + } + "-t" = { + set_if = "$openvpn_tcp$" + description = "use tcp instead of udp" + } + "--timeout" = { + value = "$openvpn_timeout$" + description = "set timeout in seconds, for udp counted per packet (default is 2)" + } + "--digest" = { + value = "$openvpn_digest$" + description = "set digest algorithm (default is 'sha1')" + } + "--digest-size" = { + value = "$openvpn_digest_size$" + description = "set HMAC digest size" + } + "--digest-key-client" = { + value = "$openvpn_digest_key_client$" + description = "set client HMAC key" + } + "--digest-key-server" = { + value = "$openvpn_key_server$" + description = "set server HMAC key for packet validation" + } + "--tls-auth" = { + value = "$openvpn_tls_auth$" + description = "set tls-auth file" + } + "--tls-auth-inverse" = { + value = "$openvpn_tls_auth_inverse$" + description = "set tls-auth file direction to inverse (1)" + } + "--retrycount" = { + value = "$openvpn_retrycount$" + description = "number of udp retries before giving up (default is 3)" + } + "--no-validation" = { + value = "$openvpn_no_validation$" + description = "do not validate response" + } + } + + vars.openvpn_address = "$check_address$" + vars.openvpn_tls_auth = "$openvpn_takey$" +} + +object CheckCommand "by_ssh_wirebrass" { + import "by_ssh" + + vars.by_ssh_custom_plugins_path = "{{ icinga2_server_nagios_plugins_location }}" + vars.by_ssh_logname = "nagios" + vars.by_ssh_identity = "/var/lib/icinga2/.ssh/id_rsa" + vars.by_ssh_options = [ "ControlMaster=auto","ControlPath=/var/run/icinga2/$host.name$","ControlPersist=10m"] + +} + +object CheckCommand "rdns" { + + import "plugin-check-command" + + command = [ PluginDir + "/check_rdns" ] + + arguments = { + "-H" = { + value = "$rdns_address$" + required = true + description = "Address to reverse" + } + "-a" = { + value = "$rdns_expect$" + required = false + description = "Expected result" + } + "-s" = { + value = "$rdns_server$" + required = false + description = "The DNS server to contact" + } + } + +} + diff --git a/roles/icinga2_server/templates/conf.d/commands.conf.j2 b/roles/icinga2_server/templates/conf.d/commands.conf.j2 new file mode 100644 index 0000000..cee1697 --- /dev/null +++ b/roles/icinga2_server/templates/conf.d/commands.conf.j2 @@ -0,0 +1,187 @@ +/* Command objects */ + +/* Notification Commands + * + * Please check the documentation for all required and + * optional parameters. + */ + +object NotificationCommand "mail-host-notification" { + command = [ ConfigDir + "/scripts/mail-host-notification.sh" ] + + arguments += { + "-4" = "$notification_address$" + "-6" = "$notification_address6$" + "-b" = "$notification_author$" + "-c" = "$notification_comment$" + "-d" = { + required = true + value = "$notification_date$" + } + "-f" = { + value = "$notification_from$" + description = "Set from address. Requires GNU mailutils (Debian/Ubuntu) or mailx (RHEL/SUSE)" + } + "-i" = "$notification_icingaweb2url$" + "-l" = { + required = true + value = "$notification_hostname$" + } + "-n" = { + required = true + value = "$notification_hostdisplayname$" + } + "-o" = { + required = true + value = "$notification_hostoutput$" + } + "-r" = { + required = true + value = "$notification_useremail$" + } + "-s" = { + required = true + value = "$notification_hoststate$" + } + "-t" = { + required = true + value = "$notification_type$" + } + "-v" = "$notification_logtosyslog$" + } + + vars += { + notification_address = "$address$" + notification_address6 = "$address6$" + notification_author = "$notification.author$" + notification_comment = "$notification.comment$" + notification_type = "$notification.type$" + notification_date = "$icinga.long_date_time$" + notification_hostname = "$host.name$" + notification_hostdisplayname = "$host.display_name$" + notification_hostoutput = "$host.output$" + notification_hoststate = "$host.state$" + notification_useremail = "$user.email$" + } +} + +object NotificationCommand "mail-service-notification" { + command = [ ConfigDir + "/scripts/mail-service-notification.sh" ] + + arguments += { + "-4" = "$notification_address$" + "-6" = "$notification_address6$" + "-b" = "$notification_author$" + "-c" = "$notification_comment$" + "-d" = { + required = true + value = "$notification_date$" + } + "-e" = { + required = true + value = "$notification_servicename$" + } + "-f" = { + value = "$notification_from$" + description = "Set from address. Requires GNU mailutils (Debian/Ubuntu) or mailx (RHEL/SUSE)" + } + "-i" = "$notification_icingaweb2url$" + "-l" = { + required = true + value = "$notification_hostname$" + } + "-n" = { + required = true + value = "$notification_hostdisplayname$" + } + "-o" = { + required = true + value = "$notification_serviceoutput$" + } + "-r" = { + required = true + value = "$notification_useremail$" + } + "-s" = { + required = true + value = "$notification_servicestate$" + } + "-t" = { + required = true + value = "$notification_type$" + } + "-u" = { + required = true + value = "$notification_servicedisplayname$" + } + "-v" = "$notification_logtosyslog$" + } + + vars += { + notification_address = "$address$" + notification_address6 = "$address6$" + notification_author = "$notification.author$" + notification_comment = "$notification.comment$" + notification_type = "$notification.type$" + notification_date = "$icinga.long_date_time$" + notification_hostname = "$host.name$" + notification_hostdisplayname = "$host.display_name$" + notification_servicename = "$service.name$" + notification_serviceoutput = "$service.output$" + notification_servicestate = "$service.state$" + notification_useremail = "$user.email$" + notification_servicedisplayname = "$service.display_name$" + } +} + +/* + * If you prefer to use the notification scripts with environment + * variables instead of command line parameters, you can use + * the following commands. They have been updated from < 2.7 + * to support the new notification scripts and should help + * with an upgrade. + * Remove the comment blocks and comment the notification commands above. + */ + +/* + +object NotificationCommand "mail-host-notification" { + command = [ ConfigDir + "/scripts/mail-host-notification.sh" ] + + env = { + NOTIFICATIONTYPE = "$notification.type$" + HOSTDISPLAYNAME = "$host.display_name$" + HOSTNAME = "$host.name$" + HOSTADDRESS = "$address$" + HOSTSTATE = "$host.state$" + LONGDATETIME = "$icinga.long_date_time$" + HOSTOUTPUT = "$host.output$" + NOTIFICATIONAUTHORNAME = "$notification.author$" + NOTIFICATIONCOMMENT = "$notification.comment$" + HOSTDISPLAYNAME = "$host.display_name$" + USEREMAIL = "$user.email$" + } +} + +object NotificationCommand "mail-service-notification" { + command = [ ConfigDir + "/scripts/mail-service-notification.sh" ] + + env = { + NOTIFICATIONTYPE = "$notification.type$" + SERVICENAME = "$service.name$" + HOSTNAME = "$host.name$" + HOSTDISPLAYNAME = "$host.display_name$" + HOSTADDRESS = "$address$" + SERVICESTATE = "$service.state$" + LONGDATETIME = "$icinga.long_date_time$" + SERVICEOUTPUT = "$service.output$" + NOTIFICATIONAUTHORNAME = "$notification.author$" + NOTIFICATIONCOMMENT = "$notification.comment$" + HOSTDISPLAYNAME = "$host.display_name$" + SERVICEDISPLAYNAME = "$service.display_name$" + USEREMAIL = "$user.email$" + } +} + +*/ +include "/usr/share/icinga2/include/command-custom.conf" diff --git a/roles/icinga2_server/templates/conf.d/groups.conf.j2 b/roles/icinga2_server/templates/conf.d/groups.conf.j2 new file mode 100644 index 0000000..cddf1b0 --- /dev/null +++ b/roles/icinga2_server/templates/conf.d/groups.conf.j2 @@ -0,0 +1,44 @@ +/** + * Host group examples. + */ + +object HostGroup "linux-servers" { + display_name = "Linux Servers" + + assign where host.vars.os == "Linux" +} + +object HostGroup "windows-servers" { + display_name = "Windows Servers" + + assign where host.vars.os == "Windows" +} + +{% if icinga2_server_custom_hostgroup is defined %} +object HostGroup "{{ icinga2_server_custom_hostgroup }}" { + display_name = "{{ icinga2_server_custom_hostgroup }}" + assign where host.vars.owner == "{{ icinga2_server_custom_hostgroup }}" +} +{% endif %} +/** + * Service group examples. + */ + +object ServiceGroup "ping" { + display_name = "Ping Checks" + + assign where match("ping*", service.name) +} + +object ServiceGroup "http" { + display_name = "HTTP Checks" + + assign where match("http*", service.check_command) +} + +object ServiceGroup "disk" { + display_name = "Disk Checks" + + assign where match("disk*", service.check_command) +} + diff --git a/roles/icinga2_server/templates/conf.d/notifications.conf.j2 b/roles/icinga2_server/templates/conf.d/notifications.conf.j2 new file mode 100644 index 0000000..d76dd89 --- /dev/null +++ b/roles/icinga2_server/templates/conf.d/notifications.conf.j2 @@ -0,0 +1,33 @@ +/** + * The example notification apply rules. + * + * Only applied if host/service objects have + * the custom variable `notification` defined + * and containing `mail` as key. + * + * Check `hosts.conf` for an example. + */ + +apply Notification "mail-icingaadmin" to Host { + import "mail-host-notification" + user_groups = host.vars.notification.mail.groups + users = host.vars.notification.mail.users + + interval = 1h + + //vars.notification_logtosyslog = true + + assign where host.vars.notification.mail +} + +apply Notification "mail-icingaadmin" to Service { + import "mail-service-notification" + user_groups = host.vars.notification.mail.groups + users = host.vars.notification.mail.users + + interval = 1h + + //vars.notification_logtosyslog = true + + assign where host.vars.notification.mail +} diff --git a/roles/icinga2_server/templates/conf.d/services.conf.j2 b/roles/icinga2_server/templates/conf.d/services.conf.j2 new file mode 100644 index 0000000..82a9448 --- /dev/null +++ b/roles/icinga2_server/templates/conf.d/services.conf.j2 @@ -0,0 +1,277 @@ +/* + * Service apply rules. + * + * The CheckCommand objects `ping4`, `ping6`, etc + * are provided by the plugin check command templates. + * Check the documentation for details. + * + * Tip: Use `icinga2 object list --type Service` to + * list all service objects after running + * configuration validation (`icinga2 daemon -C`). + */ + +/* + * This is an example host based on your + * local host's FQDN. Specify the NodeName + * constant in `constants.conf` or use your + * own description, e.g. "db-host-1". + */ + +/* + * These are generic `ping4` and `ping6` + * checks applied to all hosts having the + * `address` resp. `address6` attribute + * defined. + */ +apply Service "ping4" { + import "generic-service" + + check_command = "ping4" + + assign where host.address +} + +apply Service "ping6" { + import "generic-service" + + check_command = "ping6" + + assign where host.address6 +} + +/* + * Apply the `ssh` service to all hosts + * with the `address` attribute defined and + * the custom attribute `os` set to `Linux`. + */ +apply Service "ssh" { + import "generic-service" + + check_command = "ssh" + + assign where (host.address || host.address6) && host.vars.os == "Linux" +} + +apply Service "dns-forward-ipv4" { + import "generic-service" + + check_command = "dig" + vars.dig_lookup = "$host_name$" + vars.dig_server = "80.67.169.40" + vars.dig_record_type = "A" + vars.dig_ipv4 = "true" + + if (host.vars.dig_expected_address) { + vars.dig_expected_address = host.vars.dig_expected_address + } else { + vars.dig_expected_address = "$address$" + } + + assign where (host.address) && host.vars.os == "Linux" +} + +apply Service "dns-forward-ipv6" { + import "generic-service" + + check_command = "dig" + vars.dig_lookup = "$host_name$" + vars.dig_server = "2001:910:800::12" + vars.dig_record_type = "AAAA" + vars.dig_ipv6 = "true" + + if (host.vars.dig_expected_address6) { + vars.dig_expected_address = host.vars.dig_expected_address6 + } else { + vars.dig_expected_address = "$address6$" + } + + assign where (host.address6) && host.vars.os == "Linux" +} + +apply Service "dns-reverse-ipv4" { + import "generic-service" + + check_command = "rdns" + vars.sla = "24x7" + vars.rdns_server = "80.67.169.40" + + if (host.vars.rdns_expect) { + vars.rdns_expect = host.vars.rdns_expect + } else { + vars.rdns_expect = "$host_name$." + } + + if (host.vars.rdns_address) { + vars.rdns_address = host.vars.rdns_address + } else { + vars.rdns_address = "$address$" + } + + assign where (host.address) && host.vars.os == "Linux"# && host.name != "vm-mw-01.wirebrass.fr" +} + +apply Service "dns-reverse-ipv6" { + import "generic-service" + + check_command = "rdns" + vars.sla = "24x7" + vars.rdns_server = "2001:910:800::12" + + if (host.vars.rdns_expect6) { + vars.rdns_expect = host.vars.rdns_expect6 + } else { + vars.rdns_expect = "$host_name$." + } + + if (host.vars.rdns_address6) { + vars.rdns_address = host.vars.rdns_address6 + } else { + vars.rdns_address = "$address6$" + } + + assign where (host.address6) && host.vars.os == "Linux" && host.name != "vm-ttn-01.wirebrass.fr" && host.name != "vm-fma-01.wirebrass.fr"# && host.name != "vm-mw-01.wirebrass.fr" +} + +apply Service "ntp_by_ssh" { + import "generic-service" + check_command = "by_ssh_wirebrass" + + vars.by_ssh_timeout = 30 + vars.by_ssh_command = "$by_ssh_custom_plugins_path$check_ntp_time -H pool.ntp.org" + + assign where host.vars.os == "Linux" && host.vars.agent_type == "ssh" +} + +apply Service "uptime_by_ssh" { + import "generic-service" + check_command = "by_ssh_wirebrass" + + vars.by_ssh_custom_uptime = 5184000 + vars.by_ssh_command = "test $$(cut -d. -f1 /proc/uptime) -lt $by_ssh_custom_uptime$" + + assign where host.vars.os == "Linux" && host.vars.agent_type == "ssh" +} + +apply Service "load_by_ssh" { + import "generic-service" + check_command = "by_ssh_wirebrass" + + vars.load_by_ssh_warn = "7.0,6.0,6.0" + vars.load_by_ssh_crit = "8.0,7.0,7.5" + + vars.by_ssh_command = "$by_ssh_custom_plugins_path$check_load -w $load_by_ssh_warn$ -c $load_by_ssh_crit$" + + assign where host.vars.os == "Linux" && host.vars.agent_type == "ssh" +} + +apply Service "users_by_ssh" { + import "generic-service" + check_command = "by_ssh_wirebrass" + + vars.users_wgreater = 5 + vars.users_cgreater = 10 + + vars.by_ssh_command = "$by_ssh_custom_plugins_path$check_users -w $users_wgreater$ -c $users_cgreater$" + + assign where host.vars.os == "Linux" && host.vars.agent_type == "ssh" +} + +apply Service "disk_by_ssh_device" { + import "generic-service" + check_command = "by_ssh_wirebrass" + + vars.by_ssh_command = "$by_ssh_custom_plugins_path$check_disk -w 20% -c 10% -A -x /sys/kernel/debug/tracing -x nsfs -x shm -x overlay" + + assign where host.vars.os == "Linux" && host.vars.agent_type == "ssh" +} + +apply Service "procs_zombie_by_ssh" { + import "generic-service" + check_command = "by_ssh_wirebrass" + + vars.by_ssh_command = "$by_ssh_custom_plugins_path$check_procs -w 5 -c 10 -s Z" + + assign where host.vars.os == "Linux" && host.vars.agent_type == "ssh" +} + +apply Service "lastupdate_by_ssh" { + import "generic-service" + check_command = "by_ssh_wirebrass" + + vars.by_ssh_command = "$by_ssh_custom_plugins_path$check_file_age -w 93600 -c 180000 -f /var/lastupdate" + + assign where host.vars.os == "Linux" && host.vars.agent_type == "ssh" && host.name != "hv01.wirebrass.fr" && host.name != "hv02.wirebrass.fr" && host.name != "hv03.wirebrass.fr" && host.name != "radiosupinfo.wirebrass.fr" && host.name != "radio.supinfo.com.wirebrass.fr" + +} + +apply Service "procs_by_ssh" { + import "generic-service" + check_command = "by_ssh_wirebrass" + + vars.procs_by_ssh_warn = 400 + vars.procs_by_ssh_crit = 500 + + vars.by_ssh_command = "$by_ssh_custom_plugins_path$check_procs -w $procs_by_ssh_warn$ -c $procs_by_ssh_crit$" + + assign where host.vars.os == "Linux" && host.vars.agent_type == "ssh" +} + +apply Service for (http_vhost => config in host.vars.http_vhosts) { + import "generic-service" + + check_command = "http" + + vars += config +} + +apply Service for (disk => config in host.vars.disks) { + import "generic-service" + + check_command = "disk" + + vars += config +} + +apply Service "icinga" { + import "generic-service" + + check_command = "icinga" + + assign where host.name == NodeName +} +/* +apply Service "load" { + import "generic-service" + + check_command = "load" +*/ + /* Used by the ScheduledDowntime apply rule in `downtimes.conf`. */ + /*vars.backup_downtime = "02:00-03:00" + + assign where host.name == NodeName +}*/ + +apply Service "procs" { + import "generic-service" + + check_command = "procs" + + assign where host.name == NodeName +} + +apply Service "swap" { + import "generic-service" + + check_command = "swap" + + assign where host.name == NodeName +} + +apply Service "users" { + import "generic-service" + + check_command = "users" + + assign where host.name == NodeName +} + diff --git a/roles/icinga2_server/templates/conf.d/templates.conf.j2 b/roles/icinga2_server/templates/conf.d/templates.conf.j2 new file mode 100644 index 0000000..b8becab --- /dev/null +++ b/roles/icinga2_server/templates/conf.d/templates.conf.j2 @@ -0,0 +1,95 @@ +/* + * Generic template examples. + */ + + +/** + * Provides default settings for hosts. By convention + * all hosts should import this template. + * + * The CheckCommand object `hostalive` is provided by + * the plugin check command templates. + * Check the documentation for details. + */ +template Host "generic-host" { + max_check_attempts = 5 + check_interval = 2m + retry_interval = 1m + + check_command = "hostalive" +} + +/** + * Provides default settings for services. By convention + * all services should import this template. + */ +template Service "generic-service" { + max_check_attempts = 5 + check_interval = 2m + retry_interval = 1m +} + +template Host "{{ icinga2_server_custom_hostgroup }}-host" { + import "generic-host" + + vars.owner = "{{ icinga2_server_custom_hostgroup }}" + vars.notification["mail"] = { + groups = [ "{{ icinga2_server_custom_hostgroup }}" ] + } +} + +/** + * Provides default settings for users. By convention + * all users should inherit from this template. + */ + +template User "generic-user" { + +} + +/** + * Provides default settings for host notifications. + * By convention all host notifications should import + * this template. + */ +template Notification "mail-host-notification" { + command = "mail-host-notification" + + states = [ Up, Down ] + types = [ Problem, Acknowledgement, Recovery, Custom, + FlappingStart, FlappingEnd, + DowntimeStart, DowntimeEnd, DowntimeRemoved ] + + vars += { + // notification_icingaweb2url = "https://www.example.com/icingaweb2" + // notification_from = "Icinga 2 Host Monitoring " + notification_logtosyslog = false + } + + //period = "24x7" + period = "24x7minus4to5" +} + +/** + * Provides default settings for service notifications. + * By convention all service notifications should import + * this template. + */ +template Notification "mail-service-notification" { + command = "mail-service-notification" + + states = [ OK, Warning, Critical, Unknown ] + types = [ Problem, Acknowledgement, Recovery, Custom, + FlappingStart, FlappingEnd, + DowntimeStart, DowntimeEnd, DowntimeRemoved ] + + vars += { + // notification_icingaweb2url = "https://www.example.com/icingaweb2" + // notification_from = "Icinga 2 Service Monitoring " + notification_logtosyslog = false + } + + //period = "24x7" + period = "24x7minus4to5" +} + diff --git a/roles/icinga2_server/templates/conf.d/timeperiods.conf.j2 b/roles/icinga2_server/templates/conf.d/timeperiods.conf.j2 new file mode 100644 index 0000000..0897e28 --- /dev/null +++ b/roles/icinga2_server/templates/conf.d/timeperiods.conf.j2 @@ -0,0 +1,61 @@ +/** + * Sample timeperiods for Icinga 2. + * Check the documentation for details. + */ + +object TimePeriod "24x7" { + display_name = "Icinga 2 24x7 TimePeriod" + ranges = { + "monday" = "00:00-24:00" + "tuesday" = "00:00-24:00" + "wednesday" = "00:00-24:00" + "thursday" = "00:00-24:00" + "friday" = "00:00-24:00" + "saturday" = "00:00-24:00" + "sunday" = "00:00-24:00" + } +} + +object TimePeriod "24x7minus2to3" { + display_name = "Icinga 2 24x7 TimePeriod without 2:00-2:30" + ranges = { + "monday" = "00:00-02:00,02:30-24:00" + "tuesday" = "00:00-02:00,02:30-24:00" + "wednesday" = "00:00-02:00,02:30-24:00" + "thursday" = "00:00-02:00,02:30-24:00" + "friday" = "00:00-02:00,02:30-24:00" + "saturday" = "00:00-02:00,02:30-24:00" + "sunday" = "00:00-02:00,02:30-24:00" + } +} + +object TimePeriod "24x7minus4to5" { + display_name = "Icinga 2 24x7 TimePeriod without 4:00-5:00" + ranges = { + "monday" = "00:00-04:00,05:00-24:00" + "tuesday" = "00:00-04:00,05:00-24:00" + "wednesday" = "00:00-04:00,05:00-24:00" + "thursday" = "00:00-04:00,05:00-24:00" + "friday" = "00:00-04:00,05:00-24:00" + "saturday" = "00:00-04:00,05:00-24:00" + "sunday" = "00:00-04:00,05:00-24:00" + } +} + +object TimePeriod "9to5" { + display_name = "Icinga 2 9to5 TimePeriod" + ranges = { + "monday" = "09:00-17:00" + "tuesday" = "09:00-17:00" + "wednesday" = "09:00-17:00" + "thursday" = "09:00-17:00" + "friday" = "09:00-17:00" + } +} + +object TimePeriod "never" { + display_name = "Icinga 2 never TimePeriod" + ranges = { + } +} + diff --git a/roles/icinga2_server/templates/conf.d/users.conf.j2 b/roles/icinga2_server/templates/conf.d/users.conf.j2 new file mode 100644 index 0000000..c10ecce --- /dev/null +++ b/roles/icinga2_server/templates/conf.d/users.conf.j2 @@ -0,0 +1,31 @@ +/** + * The example user 'icingaadmin' and the example + * group 'icingaadmins'. + */ + +object User "icingaadmin" { + import "generic-user" + + display_name = "Icinga 2 Admin" + groups = [ "icingaadmins" ] + + email = "root@localhost" +} + +object UserGroup "icingaadmins" { + display_name = "Icinga 2 Admin Group" +} + +object User "{{ icinga2_server_icingaweb2_main_user }}" { + import "generic-user" + + display_name = "{{ icinga2_server_icingaweb2_main_user }}" + groups = [ "icingaadmins", "{{ icinga2_server_custom_hostgroup }}" ] + + email = "{{ icinga2_server_icingaweb2_main_user_email }}" +} + +object UserGroup "{{ icinga2_server_custom_hostgroup }}" { + display_name = "{{ icinga2_server_custom_hostgroup }}" +} + diff --git a/roles/icinga2_server/templates/constants.conf.j2 b/roles/icinga2_server/templates/constants.conf.j2 new file mode 100644 index 0000000..3baac5f --- /dev/null +++ b/roles/icinga2_server/templates/constants.conf.j2 @@ -0,0 +1,28 @@ +/** + * This file defines global constants which can be used in + * the other configuration files. + */ + +/* The directory which contains the plugins from the Monitoring Plugins project. */ +const PluginDir = "/usr/lib/nagios/plugins" + +/* The directory which contains the Manubulon plugins. + * Check the documentation, chapter "SNMP Manubulon Plugin Check Commands", for details. + */ +const ManubulonPluginDir = "/usr/lib/nagios/plugins" + +/* The directory which you use to store additional plugins which ITL provides user contributed command definitions for. + * Check the documentation, chapter "Plugins Contribution", for details. + */ +const PluginContribDir = "/usr/lib/nagios/plugins" + +/* Our local instance name. By default this is the server's hostname as returned by `hostname --fqdn`. + * This should be the common name from the API certificate. + */ +const NodeName = "{{ inventory_hostname }}" + +/* Our local zone name. */ +const ZoneName = "{{ inventory_hostname }}" + +/* Secret key for remote node tickets */ +const TicketSalt = "{{ icinga2_server_ticket_salt }}"