diff --git a/.Jenkinsfile b/.Jenkinsfile index 3f488638..3f591b98 100644 --- a/.Jenkinsfile +++ b/.Jenkinsfile @@ -21,11 +21,11 @@ pipeline { def major = versions[0] def minor = versions[0] + '.' + versions[1] def patch = version.trim() - /* No crendentials yet - im.push(major) - im.push(minor) - im.push(patch) - */ + docker.withRegistry('', 'hub.docker') { + im.push(major) + im.push(minor) + im.push(patch) + } } } } @@ -40,9 +40,9 @@ pipeline { im.inside { sh 'echo Test needed' } - /* No crendentials yet - im.push('latest') - */ + docker.withRegistry('', 'hub.docker') { + im.push('latest') + } } } } diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 514a8b3f..00000000 --- a/.drone.yml +++ /dev/null @@ -1,36 +0,0 @@ -kind: pipeline -name: default - -steps: -- name: build tagged docker image - image: plugins/docker - settings: - username: - from_secret: docker_username - password: - from_secret: docker_password - dockerfile: Dockerfile - repo: evolix/ansible-roles - auto_tag: true - environment: - ROLES_VERSION: $DRONE_COMMIT_SHA - when: - event: - - tag - -- name: build latest docker image - image: plugins/docker - settings: - username: - from_secret: docker_username - password: - from_secret: docker_password - dockerfile: Dockerfile - repo: evolix/ansible-roles - tags: latest - environment: - ROLES_VERSION: $DRONE_COMMIT_SHA - when: - branch: - - unstable - diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a162ae9..71f7be80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,45 @@ The **patch** part changes is incremented if multiple releases happen the same m ### Security +## [22.09] 2022-09-19 + +### Added + +* evolinux_users: create only users who have a certain value for the `create` key (default: `always`). +* php: install php-xml with recent PHP versions +* vrrp: add an `ip.yml` task file to help create VRRP addresses +* webapps/nextcloud: Add compatibility with apache2, and apache2 mod_php. +* memcached: NRPE check for multi-instance setup +* munin: Add ipmi_ plugins on dedicated hardware +* proftpd: Add options to override configs (and add a warning if file was overriden) +* proftpd: Allow user auth with ssh keys + + +### Changed + +* evocheck: upstream release 22.09 +* evolinux-base: update-evobackup-canary upstream release 22.06 +* generate-ldif: Support any MariaDB version +* minifirewall: use handlers to restart minifirewall +* openvpn: automate the initialization of the CA and the creation of the server certificate ; use openssl_dhparam module instead of a command +* generate-ldif: support any version of MariaDB (instead of only 10.0, 10.1 and 10.3) +* openvpn: Run OpenVPN with the \_openvpn user and group instead of nobody which is originally for NFS +* nagios-nrpe: Upgrade check_mongo + +### Fixed + +* fail2ban: fix dovecot-evolix regex syntax +* haproxy: make it so that munin doesn't break if there is a non default `haproxy_stats_path` +* mysql: Add missing Munin conf for Debian 11 +* redis: config directory must be owned by the user that runs the service (to be able to write tmp config files in it) +* varnish: make `-j ` the first argument on jessie/stretch as it has to be the first argument there. +* webapps/nextcloud: Add missing dependencies for imagick + +### Removed + +* evocheck: remove failure if deprecated variable is used +* webapps/nextcloud: Drop support for Nginx + ## [22.07.1] 2022-07-28 ### Changed diff --git a/evocheck/files/evocheck.sh b/evocheck/files/evocheck.sh index 1bc54d79..7c01da51 100644 --- a/evocheck/files/evocheck.sh +++ b/evocheck/files/evocheck.sh @@ -1,10 +1,10 @@ #!/bin/bash # EvoCheck -# Script to verify compliance of a Debian/OpenBSD server +# Script to verify compliance of a Linux (Debian) server # powered by Evolix -VERSION="22.07" +VERSION="22.09" readonly VERSION # base functions @@ -30,7 +30,7 @@ END } show_help() { cat < /dev/null | grep -qi 'permitrootlogin no'); then + failed "IS_SSHPERMITROOTNO" "PermitRoot should be set to no" fi } check_evomaintenanceusers() { @@ -1408,8 +1401,6 @@ download_versions() { if is_debian; then versions_url="https://upgrades.evolix.org/versions-${DEBIAN_RELEASE}" - elif is_openbsd; then - versions_url="https://upgrades.evolix.org/versions-${OPENBSD_RELEASE}" else failed "IS_CHECK_VERSIONS" "error determining os release" fi @@ -1536,9 +1527,7 @@ main() { main_output_file=$(mktemp --tmpdir="${TMPDIR:-/tmp}" "evocheck.main.XXXXX") files_to_cleanup="${files_to_cleanup} ${main_output_file}" - #----------------------------------------------------------- - # Tests communs à tous les systèmes - #----------------------------------------------------------- + MINIFW_FILE=$(minifirewall_file) test "${IS_TMP_1777:=1}" = 1 && check_tmp_1777 test "${IS_ROOT_0700:=1}" = 1 && check_root_0700 @@ -1549,221 +1538,111 @@ main() { test "${IS_EVOMAINTENANCECONF:=1}" = 1 && check_evomaintenanceconf test "${IS_PRIVKEYWOLRDREADABLE:=1}" = 1 && check_privatekeyworldreadable - #----------------------------------------------------------- - # Vérifie si c'est une debian et fait les tests appropriés. - #----------------------------------------------------------- - - if is_debian; then - MINIFW_FILE=$(minifirewall_file) - - test "${IS_LSBRELEASE:=1}" = 1 && check_lsbrelease - test "${IS_DPKGWARNING:=1}" = 1 && check_dpkgwarning - test "${IS_UMASKSUDOERS:=1}" = 1 && check_umasksudoers - test "${IS_NRPEPOSTFIX:=1}" = 1 && check_nrpepostfix - test "${IS_MODSECURITY:=1}" = 1 && check_modsecurity - test "${IS_CUSTOMSUDOERS:=1}" = 1 && check_customsudoers - test "${IS_VARTMPFS:=1}" = 1 && check_vartmpfs - test "${IS_SERVEURBASE:=1}" = 1 && check_serveurbase - test "${IS_LOGROTATECONF:=1}" = 1 && check_logrotateconf - test "${IS_SYSLOGCONF:=1}" = 1 && check_syslogconf - test "${IS_DEBIANSECURITY:=1}" = 1 && check_debiansecurity - test "${IS_APTITUDEONLY:=1}" = 1 && check_aptitudeonly - test "${IS_APTITUDE:=1}" = 1 && check_aptitude - test "${IS_APTGETBAK:=1}" = 1 && check_aptgetbak - test "${IS_APTICRON:=0}" = 1 && check_apticron - test "${IS_USRRO:=1}" = 1 && check_usrro - test "${IS_TMPNOEXEC:=1}" = 1 && check_tmpnoexec - test "${IS_MOUNT_FSTAB:=1}" = 1 && check_mountfstab - test "${IS_LISTCHANGESCONF:=1}" = 1 && check_listchangesconf - test "${IS_CUSTOMCRONTAB:=1}" = 1 && check_customcrontab - test "${IS_SSHALLOWUSERS:=1}" = 1 && check_sshallowusers - test "${IS_DISKPERF:=0}" = 1 && check_diskperf - test "${IS_TMOUTPROFILE:=1}" = 1 && check_tmoutprofile - test "${IS_ALERT5BOOT:=1}" = 1 && check_alert5boot - test "${IS_ALERT5MINIFW:=1}" = 1 && check_alert5minifw - test "${IS_ALERT5MINIFW:=1}" = 1 && test "${IS_MINIFW:=1}" = 1 && check_minifw - test "${IS_NRPEPERMS:=1}" = 1 && check_nrpeperms - test "${IS_MINIFWPERMS:=1}" = 1 && check_minifwperms - # Enable when minifirewall is released - test "${IS_MINIFWINCLUDES:=0}" = 1 && check_minifw_includes - test "${IS_NRPEDISKS:=0}" = 1 && check_nrpedisks - test "${IS_NRPEPID:=1}" = 1 && check_nrpepid - test "${IS_GRSECPROCS:=1}" = 1 && check_grsecprocs - test "${IS_APACHEMUNIN:=1}" = 1 && check_apachemunin - test "${IS_MYSQLUTILS:=1}" = 1 && check_mysqlutils - test "${IS_RAIDSOFT:=1}" = 1 && check_raidsoft - test "${IS_AWSTATSLOGFORMAT:=1}" = 1 && check_awstatslogformat - test "${IS_MUNINLOGROTATE:=1}" = 1 && check_muninlogrotate - test "${IS_SQUID:=1}" = 1 && check_squid - test "${IS_EVOMAINTENANCE_FW:=1}" = 1 && check_evomaintenance_fw - test "${IS_MODDEFLATE:=1}" = 1 && check_moddeflate - test "${IS_LOG2MAILRUNNING:=1}" = 1 && check_log2mailrunning - test "${IS_LOG2MAILAPACHE:=1}" = 1 && check_log2mailapache - test "${IS_LOG2MAILMYSQL:=1}" = 1 && check_log2mailmysql - test "${IS_LOG2MAILSQUID:=1}" = 1 && check_log2mailsquid - test "${IS_BINDCHROOT:=1}" = 1 && check_bindchroot - test "${IS_REPVOLATILE:=1}" = 1 && check_repvolatile - test "${IS_NETWORK_INTERFACES:=1}" = 1 && check_network_interfaces - test "${IS_AUTOIF:=1}" = 1 && check_autoif - test "${IS_INTERFACESGW:=1}" = 1 && check_interfacesgw - test "${IS_NETWORKING_SERVICE:=1}" = 1 && check_networking_service - test "${IS_EVOBACKUP:=1}" = 1 && check_evobackup - test "${IS_EVOBACKUP_EXCLUDE_MOUNT:=1}" = 1 && check_evobackup_exclude_mount - test "${IS_USERLOGROTATE:=1}" = 1 && check_userlogrotate - test "${IS_APACHECTL:=1}" = 1 && check_apachectl - test "${IS_APACHESYMLINK:=1}" = 1 && check_apachesymlink - test "${IS_APACHEIPINALLOW:=1}" = 1 && check_apacheipinallow - test "${IS_MUNINAPACHECONF:=1}" = 1 && check_muninapacheconf - test "${IS_SAMBAPINPRIORITY:=1}" = 1 && check_sambainpriority - test "${IS_KERNELUPTODATE:=1}" = 1 && check_kerneluptodate - test "${IS_UPTIME:=1}" = 1 && check_uptime - test "${IS_MUNINRUNNING:=1}" = 1 && check_muninrunning - test "${IS_BACKUPUPTODATE:=1}" = 1 && check_backupuptodate - test "${IS_ETCGIT:=1}" = 1 && check_etcgit - test "${IS_GITPERMS:=1}" = 1 && check_gitperms - test "${IS_NOTUPGRADED:=1}" = 1 && check_notupgraded - test "${IS_TUNE2FS_M5:=1}" = 1 && check_tune2fs_m5 - test "${IS_EVOLINUXSUDOGROUP:=1}" = 1 && check_evolinuxsudogroup - test "${IS_USERINADMGROUP:=1}" = 1 && check_userinadmgroup - test "${IS_APACHE2EVOLINUXCONF:=1}" = 1 && check_apache2evolinuxconf - test "${IS_BACKPORTSCONF:=1}" = 1 && check_backportsconf - test "${IS_BIND9MUNIN:=1}" = 1 && check_bind9munin - test "${IS_BIND9LOGROTATE:=1}" = 1 && check_bind9logrotate - test "${IS_BROADCOMFIRMWARE:=1}" = 1 && check_broadcomfirmware - test "${IS_HARDWARERAIDTOOL:=1}" = 1 && check_hardwareraidtool - test "${IS_LOG2MAILSYSTEMDUNIT:=1}" = 1 && check_log2mailsystemdunit - test "${IS_LISTUPGRADE:=1}" = 1 && check_listupgrade - test "${IS_MARIADBEVOLINUXCONF:=0}" = 1 && check_mariadbevolinuxconf - test "${IS_SQL_BACKUP:=1}" = 1 && check_sql_backup - test "${IS_POSTGRES_BACKUP:=1}" = 1 && check_postgres_backup - test "${IS_MONGO_BACKUP:=1}" = 1 && check_mongo_backup - test "${IS_LDAP_BACKUP:=1}" = 1 && check_ldap_backup - test "${IS_REDIS_BACKUP:=1}" = 1 && check_redis_backup - test "${IS_ELASTIC_BACKUP:=1}" = 1 && check_elastic_backup - test "${IS_MARIADBSYSTEMDUNIT:=1}" = 1 && check_mariadbsystemdunit - test "${IS_MYSQLMUNIN:=1}" = 1 && check_mysqlmunin - test "${IS_MYSQLNRPE:=1}" = 1 && check_mysqlnrpe - test "${IS_PHPEVOLINUXCONF:=0}" = 1 && check_phpevolinuxconf - test "${IS_SQUIDLOGROTATE:=1}" = 1 && check_squidlogrotate - test "${IS_SQUIDEVOLINUXCONF:=1}" = 1 && check_squidevolinuxconf - test "${IS_DUPLICATE_FS_LABEL:=1}" = 1 && check_duplicate_fs_label - test "${IS_EVOLIX_USER:=1}" = 1 && check_evolix_user - test "${IS_EVOACME_CRON:=1}" = 1 && check_evoacme_cron - test "${IS_EVOACME_LIVELINKS:=1}" = 1 && check_evoacme_livelinks - test "${IS_APACHE_CONFENABLED:=1}" = 1 && check_apache_confenabled - test "${IS_MELTDOWN_SPECTRE:=1}" = 1 && check_meltdown_spectre - test "${IS_OLD_HOME_DIR:=0}" = 1 && check_old_home_dir - test "${IS_EVOBACKUP_INCS:=1}" = 1 && check_evobackup_incs - test "${IS_OSPROBER:=1}" = 1 && check_osprober - test "${IS_JESSIE_BACKPORTS:=1}" = 1 && check_jessie_backports - test "${IS_APT_VALID_UNTIL:=1}" = 1 && check_apt_valid_until - test "${IS_CHROOTED_BINARY_UPTODATE:=1}" = 1 && check_chrooted_binary_uptodate - test "${IS_NGINX_LETSENCRYPT_UPTODATE:=1}" = 1 && check_nginx_letsencrypt_uptodate - test "${IS_LXC_CONTAINER_RESOLV_CONF:=1}" = 1 && check_lxc_container_resolv_conf - test "${IS_CHECK_VERSIONS:=1}" = 1 && check_versions - fi - - #----------------------------------------------------------- - # Tests spécifiques à OpenBSD - #----------------------------------------------------------- - - if is_openbsd; then - - if [ "${IS_SOFTDEP:=1}" = 1 ]; then - grep -q "softdep" /etc/fstab || failed "IS_SOFTDEP" - fi - - if [ "${IS_WHEEL:=1}" = 1 ]; then - grep -qE "^%wheel.*$" /etc/sudoers || failed "IS_WHEEL" - fi - - if [ "${IS_SUDOADMIN:=1}" = 1 ]; then - grep -qE "^User_Alias ADMIN=.*$" /etc/sudoers || failed "IS_SUDOADMIN" - fi - - if [ "${IS_PKGMIRROR:=1}" = 1 ]; then - grep -qE "^export PKG_PATH=http://ftp\.fr\.openbsd\.org/pub/OpenBSD/[0-9.]+/packages/[a-z0-9]+/$" /root/.profile \ - || failed "IS_PKGMIRROR" - fi - - if [ "${IS_HISTORY:=1}" = 1 ]; then - f=/root/.profile - { grep -q "^HISTFILE=\$HOME/.histfile" $f \ - && grep -q "^export HISTFILE" $f \ - && grep -q "^HISTSIZE=1000" $f \ - && grep -q "^export HISTSIZE" $f; - } || failed "IS_HISTORY" - fi - - if [ "${IS_VIM:=1}" = 1 ]; then - command -v vim > /dev/null 2>&1 || failed "IS_VIM" - fi - - if [ "${IS_TTYC0SECURE:=1}" = 1 ]; then - grep -Eqv "^ttyC0.*secure$" /etc/ttys || failed "IS_TTYC0SECURE" - fi - - if [ "${IS_CUSTOMSYSLOG:=1}" = 1 ]; then - grep -q "Evolix" /etc/newsyslog.conf || failed "IS_CUSTOMSYSLOG" - fi - - if [ "${IS_NOINETD:=1}" = 1 ]; then - grep -q "inetd=NO" /etc/rc.conf.local 2>/dev/null || failed "IS_NOINETD" - fi - - if [ "${IS_SUDOMAINT:=1}" = 1 ]; then - f=/etc/sudoers - { grep -q "Cmnd_Alias MAINT = /usr/share/scripts/evomaintenance.sh" $f \ - && grep -q "ADMIN ALL=NOPASSWD: MAINT" $f; - } || failed "IS_SUDOMAINT" - fi - - if [ "${IS_POSTGRESQL:=1}" = 1 ]; then - pkg info | grep -q postgresql-client || failed "IS_POSTGRESQL" "postgresql-client is not installed" - fi - - if [ "${IS_NRPE:=1}" = 1 ]; then - { pkg info | grep -qE "nagios-plugins-[0-9.]" \ - && pkg info | grep -q nagios-plugins-ntp \ - && pkg info | grep -q nrpe; - } || failed "IS_NRPE" "NRPE is not installed" - fi - - # if [ "${IS_NRPEDISKS:=1}" = 1 ]; then - # NRPEDISKS=$(grep command.check_disk /etc/nrpe.cfg 2>/dev/null | grep "^command.check_disk[0-9]" | sed -e "s/^command.check_disk\([0-9]\+\).*/\1/" | sort -n | tail -1) - # DFDISKS=$(df -Pl | grep -E -v "(^Filesystem|/lib/init/rw|/dev/shm|udev|rpc_pipefs)" | wc -l) - # [ "$NRPEDISKS" = "$DFDISKS" ] || failed "IS_NRPEDISKS" - # fi - - # Verification du check_mailq dans nrpe.cfg (celui-ci doit avoir l'option "-M postfix" si le MTA est Postfix) - # - # if [ "${IS_NRPEPOSTFIX:=1}" = 1 ]; then - # pkg info | grep -q postfix && ( grep -q "^command.*check_mailq -M postfix" /etc/nrpe.cfg 2>/dev/null || failed "IS_NRPEPOSTFIX" ) - # fi - - if [ "${IS_NRPEDAEMON:=1}" = 1 ]; then - grep -q "echo -n ' nrpe'; /usr/local/sbin/nrpe -d" /etc/rc.local \ - || failed "IS_NREPEDAEMON" - fi - - if [ "${IS_ALERTBOOT:=1}" = 1 ]; then - grep -qE "^date \| mail -sboot/reboot .*evolix.fr$" /etc/rc.local \ - || failed "IS_ALERTBOOT" - fi - - if [ "${IS_RSYNC:=1}" = 1 ]; then - pkg info | grep -q rsync || failed "IS_RSYNC" - fi - - if [ "${IS_CRONPATH:=1}" = 1 ]; then - grep -q "PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin" /var/cron/tabs/root \ - || failed "IS_CRONPATH" - fi - - #TODO - # - Check en profondeur de postfix - # - NRPEDISK et NRPEPOSTFIX - fi + test "${IS_LSBRELEASE:=1}" = 1 && check_lsbrelease + test "${IS_DPKGWARNING:=1}" = 1 && check_dpkgwarning + test "${IS_UMASKSUDOERS:=1}" = 1 && check_umasksudoers + test "${IS_NRPEPOSTFIX:=1}" = 1 && check_nrpepostfix + test "${IS_MODSECURITY:=1}" = 1 && check_modsecurity + test "${IS_CUSTOMSUDOERS:=1}" = 1 && check_customsudoers + test "${IS_VARTMPFS:=1}" = 1 && check_vartmpfs + test "${IS_SERVEURBASE:=1}" = 1 && check_serveurbase + test "${IS_LOGROTATECONF:=1}" = 1 && check_logrotateconf + test "${IS_SYSLOGCONF:=1}" = 1 && check_syslogconf + test "${IS_DEBIANSECURITY:=1}" = 1 && check_debiansecurity + test "${IS_APTITUDEONLY:=1}" = 1 && check_aptitudeonly + test "${IS_APTITUDE:=1}" = 1 && check_aptitude + test "${IS_APTGETBAK:=1}" = 1 && check_aptgetbak + test "${IS_APTICRON:=0}" = 1 && check_apticron + test "${IS_USRRO:=1}" = 1 && check_usrro + test "${IS_TMPNOEXEC:=1}" = 1 && check_tmpnoexec + test "${IS_MOUNT_FSTAB:=1}" = 1 && check_mountfstab + test "${IS_LISTCHANGESCONF:=1}" = 1 && check_listchangesconf + test "${IS_CUSTOMCRONTAB:=1}" = 1 && check_customcrontab + test "${IS_SSHALLOWUSERS:=1}" = 1 && check_sshallowusers + test "${IS_DISKPERF:=0}" = 1 && check_diskperf + test "${IS_TMOUTPROFILE:=1}" = 1 && check_tmoutprofile + test "${IS_ALERT5BOOT:=1}" = 1 && check_alert5boot + test "${IS_ALERT5MINIFW:=1}" = 1 && check_alert5minifw + test "${IS_ALERT5MINIFW:=1}" = 1 && test "${IS_MINIFW:=1}" = 1 && check_minifw + test "${IS_NRPEPERMS:=1}" = 1 && check_nrpeperms + test "${IS_MINIFWPERMS:=1}" = 1 && check_minifwperms + # Enable when minifirewall is released + test "${IS_MINIFWINCLUDES:=0}" = 1 && check_minifw_includes + test "${IS_NRPEDISKS:=0}" = 1 && check_nrpedisks + test "${IS_NRPEPID:=1}" = 1 && check_nrpepid + test "${IS_GRSECPROCS:=1}" = 1 && check_grsecprocs + test "${IS_APACHEMUNIN:=1}" = 1 && check_apachemunin + test "${IS_MYSQLUTILS:=1}" = 1 && check_mysqlutils + test "${IS_RAIDSOFT:=1}" = 1 && check_raidsoft + test "${IS_AWSTATSLOGFORMAT:=1}" = 1 && check_awstatslogformat + test "${IS_MUNINLOGROTATE:=1}" = 1 && check_muninlogrotate + test "${IS_SQUID:=1}" = 1 && check_squid + test "${IS_EVOMAINTENANCE_FW:=1}" = 1 && check_evomaintenance_fw + test "${IS_MODDEFLATE:=1}" = 1 && check_moddeflate + test "${IS_LOG2MAILRUNNING:=1}" = 1 && check_log2mailrunning + test "${IS_LOG2MAILAPACHE:=1}" = 1 && check_log2mailapache + test "${IS_LOG2MAILMYSQL:=1}" = 1 && check_log2mailmysql + test "${IS_LOG2MAILSQUID:=1}" = 1 && check_log2mailsquid + test "${IS_BINDCHROOT:=1}" = 1 && check_bindchroot + test "${IS_REPVOLATILE:=1}" = 1 && check_repvolatile + test "${IS_NETWORK_INTERFACES:=1}" = 1 && check_network_interfaces + test "${IS_AUTOIF:=1}" = 1 && check_autoif + test "${IS_INTERFACESGW:=1}" = 1 && check_interfacesgw + test "${IS_NETWORKING_SERVICE:=1}" = 1 && check_networking_service + test "${IS_EVOBACKUP:=1}" = 1 && check_evobackup + test "${IS_EVOBACKUP_EXCLUDE_MOUNT:=1}" = 1 && check_evobackup_exclude_mount + test "${IS_USERLOGROTATE:=1}" = 1 && check_userlogrotate + test "${IS_APACHECTL:=1}" = 1 && check_apachectl + test "${IS_APACHESYMLINK:=1}" = 1 && check_apachesymlink + test "${IS_APACHEIPINALLOW:=1}" = 1 && check_apacheipinallow + test "${IS_MUNINAPACHECONF:=1}" = 1 && check_muninapacheconf + test "${IS_SAMBAPINPRIORITY:=1}" = 1 && check_sambainpriority + test "${IS_KERNELUPTODATE:=1}" = 1 && check_kerneluptodate + test "${IS_UPTIME:=1}" = 1 && check_uptime + test "${IS_MUNINRUNNING:=1}" = 1 && check_muninrunning + test "${IS_BACKUPUPTODATE:=1}" = 1 && check_backupuptodate + test "${IS_ETCGIT:=1}" = 1 && check_etcgit + test "${IS_GITPERMS:=1}" = 1 && check_gitperms + test "${IS_NOTUPGRADED:=1}" = 1 && check_notupgraded + test "${IS_TUNE2FS_M5:=1}" = 1 && check_tune2fs_m5 + test "${IS_EVOLINUXSUDOGROUP:=1}" = 1 && check_evolinuxsudogroup + test "${IS_USERINADMGROUP:=1}" = 1 && check_userinadmgroup + test "${IS_APACHE2EVOLINUXCONF:=1}" = 1 && check_apache2evolinuxconf + test "${IS_BACKPORTSCONF:=1}" = 1 && check_backportsconf + test "${IS_BIND9MUNIN:=1}" = 1 && check_bind9munin + test "${IS_BIND9LOGROTATE:=1}" = 1 && check_bind9logrotate + test "${IS_BROADCOMFIRMWARE:=1}" = 1 && check_broadcomfirmware + test "${IS_HARDWARERAIDTOOL:=1}" = 1 && check_hardwareraidtool + test "${IS_LOG2MAILSYSTEMDUNIT:=1}" = 1 && check_log2mailsystemdunit + test "${IS_LISTUPGRADE:=1}" = 1 && check_listupgrade + test "${IS_MARIADBEVOLINUXCONF:=0}" = 1 && check_mariadbevolinuxconf + test "${IS_SQL_BACKUP:=1}" = 1 && check_sql_backup + test "${IS_POSTGRES_BACKUP:=1}" = 1 && check_postgres_backup + test "${IS_MONGO_BACKUP:=1}" = 1 && check_mongo_backup + test "${IS_LDAP_BACKUP:=1}" = 1 && check_ldap_backup + test "${IS_REDIS_BACKUP:=1}" = 1 && check_redis_backup + test "${IS_ELASTIC_BACKUP:=1}" = 1 && check_elastic_backup + test "${IS_MARIADBSYSTEMDUNIT:=1}" = 1 && check_mariadbsystemdunit + test "${IS_MYSQLMUNIN:=1}" = 1 && check_mysqlmunin + test "${IS_MYSQLNRPE:=1}" = 1 && check_mysqlnrpe + test "${IS_PHPEVOLINUXCONF:=0}" = 1 && check_phpevolinuxconf + test "${IS_SQUIDLOGROTATE:=1}" = 1 && check_squidlogrotate + test "${IS_SQUIDEVOLINUXCONF:=1}" = 1 && check_squidevolinuxconf + test "${IS_DUPLICATE_FS_LABEL:=1}" = 1 && check_duplicate_fs_label + test "${IS_EVOLIX_USER:=1}" = 1 && check_evolix_user + test "${IS_EVOACME_CRON:=1}" = 1 && check_evoacme_cron + test "${IS_EVOACME_LIVELINKS:=1}" = 1 && check_evoacme_livelinks + test "${IS_APACHE_CONFENABLED:=1}" = 1 && check_apache_confenabled + test "${IS_MELTDOWN_SPECTRE:=1}" = 1 && check_meltdown_spectre + test "${IS_OLD_HOME_DIR:=0}" = 1 && check_old_home_dir + test "${IS_EVOBACKUP_INCS:=1}" = 1 && check_evobackup_incs + test "${IS_OSPROBER:=1}" = 1 && check_osprober + test "${IS_JESSIE_BACKPORTS:=1}" = 1 && check_jessie_backports + test "${IS_APT_VALID_UNTIL:=1}" = 1 && check_apt_valid_until + test "${IS_CHROOTED_BINARY_UPTODATE:=1}" = 1 && check_chrooted_binary_uptodate + test "${IS_NGINX_LETSENCRYPT_UPTODATE:=1}" = 1 && check_nginx_letsencrypt_uptodate + test "${IS_LXC_CONTAINER_RESOLV_CONF:=1}" = 1 && check_lxc_container_resolv_conf + test "${IS_CHECK_VERSIONS:=1}" = 1 && check_versions if [ -f "${main_output_file}" ]; then lines_found=$(wc -l < "${main_output_file}") diff --git a/evocheck/tasks/main.yml b/evocheck/tasks/main.yml index 2032740b..14c6988f 100644 --- a/evocheck/tasks/main.yml +++ b/evocheck/tasks/main.yml @@ -1,12 +1,5 @@ --- -- name: Package install is not supported anymore - fail: - msg: Package install is not supported anymore - when: - - evocheck_force_install is defined - - evocheck_force_install == "package" - - include: install.yml - include: cron.yml diff --git a/evolinux-base/files/update-evobackup-canary.sh b/evolinux-base/files/update-evobackup-canary similarity index 95% rename from evolinux-base/files/update-evobackup-canary.sh rename to evolinux-base/files/update-evobackup-canary index 20fc1a57..868c3be6 100644 --- a/evolinux-base/files/update-evobackup-canary.sh +++ b/evolinux-base/files/update-evobackup-canary @@ -3,7 +3,7 @@ PROGNAME="update-evobackup-canary" REPOSITORY="https://gitea.evolix.org/evolix/evobackup" -VERSION="22.05" +VERSION="22.06" readonly VERSION # base functions @@ -44,8 +44,8 @@ main() { if [ -z "${canary_file:-}" ]; then canary_file="/zzz_evobackup_canary" fi - # This option is supported since (at least) Debian 8 - date=$(date --iso-8601=seconds) + # This option is supported both on OpenBSD which does not use GNU date and on Debian + date=$(date "+%FT%T%z") printf "%s %s\n" "${date}" "${who}" >> "${canary_file}" } diff --git a/evolinux-base/tasks/utils.yml b/evolinux-base/tasks/utils.yml index 8236bd92..2fd4b0c1 100644 --- a/evolinux-base/tasks/utils.yml +++ b/evolinux-base/tasks/utils.yml @@ -17,7 +17,7 @@ - name: update-evobackup-canary script is present copy: - src: "update-evobackup-canary.sh" + src: update-evobackup-canary dest: /usr/local/bin/update-evobackup-canary force: True owner: root @@ -30,11 +30,11 @@ path: /usr/local/sbin/update-evobackup-canary state: absent -- name: dir-check script is present - copy: - src: "dir-check.sh" - dest: /usr/local/bin/dir-check - force: True - owner: root - group: root - mode: "0755" \ No newline at end of file +# - name: dir-check script is present +# copy: +# src: "dir-check.sh" +# dest: /usr/local/bin/dir-check +# force: True +# owner: root +# group: root +# mode: "0755" \ No newline at end of file diff --git a/evolinux-users/README.md b/evolinux-users/README.md index c0f6e9ef..9c7beab4 100644 --- a/evolinux-users/README.md +++ b/evolinux-users/README.md @@ -19,6 +19,7 @@ evolinux_users: groups: "baz" password_hash: 'sdfgsdfgsdfgsdfg' ssh_key: 'ssh-rsa AZERTYXYZ' + create: always bar: name: bar uid: 1002 @@ -30,6 +31,7 @@ evolinux_users: ssh_keys: - 'ssh-rsa QWERTYUIOP' - 'ssh-ed25519 QWERTYUIOP' + create: on_demand ``` * `evolinux_sudo_group`: which group to use for sudo (default: `evolinux-sudo`) diff --git a/evolinux-users/defaults/main.yml b/evolinux-users/defaults/main.yml index 8ff94551..658e4a31 100644 --- a/evolinux-users/defaults/main.yml +++ b/evolinux-users/defaults/main.yml @@ -6,3 +6,6 @@ evolinux_ssh_group: "evolinux-ssh" evolinux_internal_group: "" evolinux_root_disable_ssh: True + +# Defines which groups of users are created +evolinux_users_create: always \ No newline at end of file diff --git a/evolinux-users/tasks/main.yml b/evolinux-users/tasks/main.yml index 1b838e01..d105aefe 100644 --- a/evolinux-users/tasks/main.yml +++ b/evolinux-users/tasks/main.yml @@ -16,7 +16,9 @@ vars: user: "{{ item.value }}" loop: "{{ evolinux_users | dict2items }}" - when: evolinux_users | length > 0 + when: + - user.create == evolinux_users_create + - evolinux_users | length > 0 - name: Configure sudo include: sudo.yml diff --git a/evolinux-users/tasks/ssh.yml b/evolinux-users/tasks/ssh.yml index b0bf8b58..25a08297 100644 --- a/evolinux-users/tasks/ssh.yml +++ b/evolinux-users/tasks/ssh.yml @@ -50,6 +50,7 @@ user: "{{ item.value }}" loop: "{{ evolinux_users | dict2items }}" when: + - user.create == evolinux_users_create - ssh_allowusers - not ssh_allowgroups diff --git a/evolinux-users/tasks/sudo.yml b/evolinux-users/tasks/sudo.yml index 4056e7ad..769e7a4e 100644 --- a/evolinux-users/tasks/sudo.yml +++ b/evolinux-users/tasks/sudo.yml @@ -6,6 +6,7 @@ loop: "{{ evolinux_users | dict2items }}" when: - evolinux_users | length > 0 + - user.create == evolinux_users_create - ansible_distribution_release == "jessie" @@ -16,6 +17,9 @@ vars: user: "{{ item.value }}" loop: "{{ evolinux_users | dict2items }}" + when: + - evolinux_users | length > 0 + - user.create == evolinux_users_create when: - ansible_distribution_major_version is defined - ansible_distribution_major_version is version('9', '>=') diff --git a/fail2ban/files/dovecot-evolix.conf b/fail2ban/files/dovecot-evolix.conf index 5ca484af..e1ef1a3f 100644 --- a/fail2ban/files/dovecot-evolix.conf +++ b/fail2ban/files/dovecot-evolix.conf @@ -1,3 +1,3 @@ -[Definition] -failregex = (?: pop3-login|imap-login): .*(?:Authentication failure|Aborted login \(auth failed|Aborted login \(tried to use disabled|Disconnected \(auth failed|Aborted login \(\d+ authentication attempts).*rip=(?P\S*),.* -ignoreregex = +[Definition] +failregex = (?: pop3-login|imap-login): .*(?:Authentication failure|Aborted login \(auth failed|Aborted login \(tried to use disabled|Disconnected \(auth failed|Aborted login \(\d+ authentication attempts).*rip=,.* +ignoreregex = diff --git a/generate-ldif/templates/generateldif.sh.j2 b/generate-ldif/templates/generateldif.sh.j2 index 17ff759a..229c1443 100755 --- a/generate-ldif/templates/generateldif.sh.j2 +++ b/generate-ldif/templates/generateldif.sh.j2 @@ -408,12 +408,8 @@ EOT fi # MariaDB -if is_pkg_installed mariadb-server-10.3; then - mariadb_version=$(get_pkg_version mariadb-server-10.3) -elif is_pkg_installed mariadb-server-10.1; then - mariadb_version=$(get_pkg_version mariadb-server-10.1) -elif is_pkg_installed mariadb-server-10.0; then - mariadb_version=$(get_pkg_version mariadb-server-10.0) +if is_pkg_installed mariadb-server; then + mariadb_version=$(get_pkg_version mariadb-server) fi if [ -n "${mariadb_version}" ]; then cat <> "${ldif_file}" diff --git a/haproxy/templates/munin.conf.j2 b/haproxy/templates/munin.conf.j2 index 24042f66..149896b2 100644 --- a/haproxy/templates/munin.conf.j2 +++ b/haproxy/templates/munin.conf.j2 @@ -1,4 +1,4 @@ [haproxy_*] {% if haproxy_stats_internal_enable %} -env.url http://{{ haproxy_stats_internal_host }}:{{ haproxy_stats_internal_port }}/;csv;norefresh +env.url http://{{ haproxy_stats_internal_host }}:{{ haproxy_stats_internal_port }}{{ haproxy_stats_path }};csv;norefresh {% endif %} diff --git a/lxc-php/tasks/php74.yml b/lxc-php/tasks/php74.yml index eaae77fd..64677009 100644 --- a/lxc-php/tasks/php74.yml +++ b/lxc-php/tasks/php74.yml @@ -3,7 +3,7 @@ - name: "{{ lxc_php_version }} - Install PHP packages" lxc_container: name: "{{ lxc_php_version }}" - container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mysql php-pgsql php-sqlite3 php-curl php-zip php-mbstring php-zip composer libphp-phpmailer" + container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mysql php-pgsql php-sqlite3 php-curl php-zip php-mbstring php-xml php-zip composer libphp-phpmailer" - name: "{{ lxc_php_version }} - fix bullseye repository" replace: diff --git a/lxc-php/tasks/php80.yml b/lxc-php/tasks/php80.yml index 4f725f0b..47039fe7 100644 --- a/lxc-php/tasks/php80.yml +++ b/lxc-php/tasks/php80.yml @@ -46,7 +46,7 @@ - name: "{{ lxc_php_version }} - Install PHP packages" lxc_container: name: "{{ lxc_php_version }}" - container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mysql php-pgsql php-sqlite3 php-curl php-zip php-mbstring php-zip composer libphp-phpmailer" + container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mysql php-pgsql php-sqlite3 php-curl php-zip php-mbstring php-xml php-zip composer libphp-phpmailer" - name: "{{ lxc_php_version }} - Copy evolinux PHP configuration" template: diff --git a/lxc-php/tasks/php81.yml b/lxc-php/tasks/php81.yml index f4498dd2..8883cbcc 100644 --- a/lxc-php/tasks/php81.yml +++ b/lxc-php/tasks/php81.yml @@ -46,7 +46,7 @@ - name: "{{ lxc_php_version }} - Install PHP packages" lxc_container: name: "{{ lxc_php_version }}" - container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mysql php-pgsql php-sqlite3 php-curl php-zip php-mbstring php-zip composer libphp-phpmailer" + container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mysql php-pgsql php-sqlite3 php-curl php-zip php-mbstring php-xml php-zip composer libphp-phpmailer" - name: "{{ lxc_php_version }} - Copy evolinux PHP configuration" template: diff --git a/memcached/files/check_memcached_instances.sh b/memcached/files/check_memcached_instances.sh new file mode 100644 index 00000000..e97352f7 --- /dev/null +++ b/memcached/files/check_memcached_instances.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# {{ ansible_managed }} + +set -u + +return=0 +nb_crit=0 +nb_warn=0 +nb_ok=0 +nb_unchk=0 +output="" + +vendored_check=/usr/local/lib/nagios/plugins/check_memcached.pl + +if [ -x $vendored_check ]; then + check_bin=$vendored_check +else + echo "UNCHK - can't find check_memcached" + exit 3 +fi + +check_server() { + name=$1 + conf_file=$2 + + host=$(config_var "-l" "${conf_file}") + port=$(config_var "-p" "${conf_file}") + + cmd="${check_bin} -H ${host} -p ${port}" + + result=$($cmd) + ret="${?}" + if [ "${ret}" -ge 2 ]; then + nb_crit=$((nb_crit + 1)) + printf -v output "%s%s\n" "${output}" "${result}" + [ "${return}" -le 2 ] && return=2 + elif [ "${ret}" -ge 1 ]; then + nb_warn=$((nb_warn + 1)) + printf -v output "%s%s\n" "${output}" "${result}" + [ "${return}" -le 1 ] && return=1 + else + nb_ok=$((nb_ok + 1)) + printf -v output "%s%s\n" "${output}" "${result}" + [ "${return}" -le 0 ] && return=0 + fi +} +config_var() { + variable=$1 + file=$2 + test -f "${file}" && grep -E "^${variable}\s+.+$" "${file}" | awk '{ print $2 }' | sed -e "s/^[\"']//" -e "s/[\"']$//" +} + +# default instance +if systemctl is-enabled -q memcached; then + check_server "default" "/etc/memcached.conf" +fi + +# additional instances +conf_files=$(ls -1 /etc/memcached_*.conf 2> /dev/null) +for conf_file in ${conf_files}; do + name=$(basename "${conf_file}" | sed '{s|memcached_||;s|\.conf||}') + if systemctl is-enabled -q "memcached@${name}.service"; then + check_server "${name}" "${conf_file}" + else + nb_unchk=$((nb_unchk + 1)) + output="${output}UNCHK - ${name} (unit is disabled or missing)\n" + fi +done + +[ "${return}" -ge 0 ] && header="OK" +[ "${return}" -ge 1 ] && header="WARNING" +[ "${return}" -ge 2 ] && header="CRITICAL" + +printf "%s - %s UNCHK / %s CRIT / %s WARN / %s OK\n\n" "${header}" "${nb_unchk}" "${nb_crit}" "${nb_warn}" "${nb_ok}" + +printf "%s" "${output}" | grep -E "CRITICAL" +printf "%s" "${output}" | grep -E "WARNING" +printf "%s" "${output}" | grep -E "OK" +printf "%s" "${output}" | grep -E "UNCHK" + +exit "${return}" diff --git a/memcached/tasks/instance-default.yml b/memcached/tasks/instance-default.yml new file mode 100644 index 00000000..635b3576 --- /dev/null +++ b/memcached/tasks/instance-default.yml @@ -0,0 +1,17 @@ + +- name: Memcached is configured. + template: + src: memcached.conf.j2 + dest: /etc/memcached.conf + mode: "0644" + notify: restart memcached + tags: + - memcached + +- name: Memcached is running and enabled on boot. + service: + name: memcached + enabled: yes + state: started + tags: + - memcached diff --git a/memcached/tasks/instance-multi.yml b/memcached/tasks/instance-multi.yml new file mode 100644 index 00000000..61568a5d --- /dev/null +++ b/memcached/tasks/instance-multi.yml @@ -0,0 +1,41 @@ +--- + +- name: Add systemd unit template + copy: + src: memcached@.service + dest: /etc/systemd/system/memcached@.service + tags: + - memcached + +- name: Disable default memcached systemd unit + systemd: + name: memcached + enabled: false + state: stopped + tags: + - memcached + +- name: Make sure memcached.conf is absent + file: + path: /etc/memcached.conf + state: absent + tags: + - memcached + +- name: "Create a configuration file for instance ({{ memcached_instance_name }})" + template: + src: memcached.conf.j2 + dest: /etc/memcached_{{ memcached_instance_name }}.conf + mode: "0644" + tags: + - memcached + +- name: "Enable and start the memcached instance ({{ memcached_instance_name }})" + systemd: + name: memcached@{{ memcached_instance_name }} + enabled: yes + state: started + daemon_reload: yes + masked: no + tags: + - memcached diff --git a/memcached/tasks/main.yml b/memcached/tasks/main.yml index 0159f8d6..86d0aa40 100644 --- a/memcached/tasks/main.yml +++ b/memcached/tasks/main.yml @@ -1,73 +1,15 @@ -- name: ensure packages are installed +- name: Ensure memcached is installed apt: name: memcached state: present tags: - memcached -- name: Memcached is configured. - template: - src: memcached.conf.j2 - dest: /etc/memcached.conf - mode: "0644" - notify: restart memcached - tags: - - memcached - when: memcached_instance_name | length == 0 +- include: instance-default.yml + when: memcached_instance_name is undefined -- name: Memcached is running and enabled on boot. - service: - name: memcached - enabled: yes - state: started - tags: - - memcached - when: memcached_instance_name | length == 0 - -- name: Add systemd template - copy: - src: memcached@.service - dest: /etc/systemd/system/memcached@.service - tags: - - memcached - when: memcached_instance_name | length > 0 - -- name: Delete default memcached systemd configuration file - systemd: - name: memcached - enabled: false - state: stopped - tags: - - memcached - when: memcached_instance_name | length > 0 - -- name: Make sure memcached.conf is absent - file: - path: /etc/memcached.conf - state: absent - tags: - - memcached - when: memcached_instance_name | length > 0 - -- name: Create a configuration file - template: - src: memcached.conf.j2 - dest: /etc/memcached_{{ memcached_instance_name }}.conf - mode: "0644" - tags: - - memcached - when: memcached_instance_name | length > 0 - -- name: Enable and start the memcached instance - systemd: - name: memcached@{{ memcached_instance_name }} - enabled: yes - state: started - daemon_reload: yes - masked: no - tags: - - memcached - when: memcached_instance_name | length > 0 +- include: instance-multi.yml + when: memcached_instance_name is defined - include: munin.yml diff --git a/memcached/tasks/munin.yml b/memcached/tasks/munin.yml index 6e2f6d6f..f97962c4 100644 --- a/memcached/tasks/munin.yml +++ b/memcached/tasks/munin.yml @@ -2,7 +2,7 @@ - name: Choose packages (Oracle) set_fact: multi: "multi_" - when: memcached_instance_name | length > 0 + when: memcached_instance_name is defined - name: is Munin present ? stat: diff --git a/memcached/tasks/nrpe.yml b/memcached/tasks/nrpe.yml index 21070aec..ff0fc8b3 100644 --- a/memcached/tasks/nrpe.yml +++ b/memcached/tasks/nrpe.yml @@ -1,6 +1,4 @@ --- -- include_role: - name: evolix/remount-usr - name: Is nrpe present ? stat: @@ -10,7 +8,12 @@ - block: - name: Install dependencies apt: - name: libcache-memcached-perl + name: + - libcache-memcached-perl + - libmemcached11 + + - include_role: + name: evolix/remount-usr - name: Copy Nagios check for memcached copy: @@ -18,13 +21,29 @@ dest: /usr/local/lib/nagios/plugins/ mode: "0755" - # TODO: install a "multi-instances" check if the memcached_instance_name variable is not null + - name: install check_memcached_instances + copy: + src: check_memcached_instances.sh + dest: /usr/local/lib/nagios/plugins/check_memcached_instances + force: yes + mode: "0755" + owner: root + group: root - - name: Add NRPE check + - name: Add NRPE check (single instance) lineinfile: name: /etc/nagios/nrpe.d/evolix.cfg regexp: '^command\[check_memcached\]=' line: 'command[check_memcached]=/usr/local/lib/nagios/plugins/check_memcached.pl -H 127.0.0.1 -p {{ memcached_port }}' notify: restart nagios-nrpe-server + when: memcached_instance_name is undefined + + - name: Add NRPE check (multi instance) + lineinfile: + name: /etc/nagios/nrpe.d/evolix.cfg + regexp: '^command\[check_memcached\]=' + line: 'command[check_memcached]=/usr/local/lib/nagios/plugins/check_memcached_instances' + notify: restart nagios-nrpe-server + when: memcached_instance_name is defined when: nrpe_evolix_config.stat.exists diff --git a/minifirewall/handlers/main.yml b/minifirewall/handlers/main.yml index 5ba1926c..3c541de5 100644 --- a/minifirewall/handlers/main.yml +++ b/minifirewall/handlers/main.yml @@ -4,3 +4,19 @@ service: name: nagios-nrpe-server state: restarted + +- name: restart minifirewall (modern) + command: /etc/init.d/minifirewall restart + register: minifirewall_init_restart + failed_when: "'minifirewall failed' in minifirewall_init_restart.stdout" + +- name: restart minifirewall (legacy) + command: /etc/init.d/minifirewall restart + register: minifirewall_init_restart + failed_when: "'starting IPTables rules is now finish : OK' not in minifirewall_init_restart.stdout" + +- name: restart minifirewall (noop) + meta: noop + register: minifirewall_init_restart + failed_when: False + changed_when: False \ No newline at end of file diff --git a/minifirewall/tasks/config.legacy.yml b/minifirewall/tasks/config.legacy.yml index 8a7f5990..a151e76c 100644 --- a/minifirewall/tasks/config.legacy.yml +++ b/minifirewall/tasks/config.legacy.yml @@ -197,21 +197,15 @@ path: "{{ minifirewall_main_file }}" register: minifirewall_after -- name: restart minifirewall - command: /etc/init.d/minifirewall restart - register: minifirewall_init_restart - failed_when: "'starting IPTables rules is now finish : OK' not in minifirewall_init_restart.stdout" +- name: Schedule minifirewall restart (legacy) + command: /bin/true + notify: "restart minifirewall (legacy)" when: + - minifirewall_install_mode == 'legacy' - minifirewall_restart_if_needed | bool - minifirewall_is_running.rc == 0 - - minifirewall_before.stat.checksum != minifirewall_after.stat.checksum + - minifirewall_before.stat.checksum != minifirewall_after.stat.checksum or minifirewall_upgrade_script is changed or minifirewall_upgrade_config is changed -- name: restart minifirewall (noop) - meta: noop - register: minifirewall_init_restart - failed_when: False - changed_when: False - when: not (minifirewall_restart_if_needed | bool) - debug: var: minifirewall_init_restart diff --git a/minifirewall/tasks/config.yml b/minifirewall/tasks/config.yml index c11b83e8..b0a1d7a6 100644 --- a/minifirewall/tasks/config.yml +++ b/minifirewall/tasks/config.yml @@ -282,11 +282,11 @@ path: "/etc/default/minifirewall" register: minifirewall_after -- name: restart minifirewall - command: /etc/init.d/minifirewall restart - register: minifirewall_init_restart - failed_when: "'minifirewall failed' in minifirewall_init_restart.stdout" +- name: Schedule minifirewall restart (modern) + command: /bin/true + notify: "restart minifirewall (modern)" when: + - minifirewall_install_mode != 'legacy' - minifirewall_restart_if_needed | bool - minifirewall_is_running.rc == 0 - minifirewall_before.stat.checksum != minifirewall_after.stat.checksum or minifirewall_upgrade_script is changed or minifirewall_upgrade_config is changed diff --git a/minifirewall/tasks/main.yml b/minifirewall/tasks/main.yml index 483f8715..bc56b7dc 100644 --- a/minifirewall/tasks/main.yml +++ b/minifirewall/tasks/main.yml @@ -1,9 +1,5 @@ --- -- name: Compose minifirewall_restart_handler_name variable - set_fact: - minifirewall_restart_handler_name: "{{ minifirewall_restart_if_needed | bool | ternary('restart minifirewall', 'restart minifirewall (noop)') }}" - # Legacy or modern mode? ############################################## - name: Check minifirewall @@ -39,6 +35,25 @@ var: minifirewall_install_mode verbosity: 1 +- name: 'Set minifirewall_restart_handler_name to "noop"' + set_fact: + minifirewall_restart_handler_name: "restart minifirewall (noop)" + when: not (minifirewall_restart_if_needed | bool) + +- name: 'Set minifirewall_restart_handler_name to "legacy"' + set_fact: + minifirewall_restart_handler_name: "restart minifirewall (legacy)" + when: + - minifirewall_restart_if_needed | bool + - minifirewall_install_mode == 'legacy' + +- name: 'Set minifirewall_restart_handler_name to "modern"' + set_fact: + minifirewall_restart_handler_name: "restart minifirewall (modern)" + when: + - minifirewall_restart_if_needed | bool + - minifirewall_install_mode != 'legacy' + ####################################################################### - name: Fail if minifirewall_main_file is defined (legacy mode) @@ -106,18 +121,16 @@ var: minifirewall_restart_force | bool verbosity: 1 -- name: Force restart minifirewall (modern mode) - command: /etc/init.d/minifirewall restart - register: minifirewall_init_restart - failed_when: "'minifirewall failed' in minifirewall_init_restart.stdout" - when: - - minifirewall_install_mode != 'legacy' - - minifirewall_restart_force | bool - -- name: Force restart minifirewall (legacy mode) - command: /etc/init.d/minifirewall restart - register: minifirewall_init_restart - failed_when: "'starting IPTables rules is now finish : OK' not in minifirewall_init_restart.stdout" +- name: Force restart minifirewall (legacy) + command: /bin/true + notify: "restart minifirewall (legacy)" when: - minifirewall_install_mode == 'legacy' + - minifirewall_restart_force | bool + +- name: Force restart minifirewall (modern) + command: /bin/true + notify: "restart minifirewall (modern)" + when: + - minifirewall_install_mode != 'legacy' - minifirewall_restart_force | bool \ No newline at end of file diff --git a/minifirewall/tasks/tail.legacy.yml b/minifirewall/tasks/tail.legacy.yml index 7a13eefa..dc7fbdc9 100644 --- a/minifirewall/tasks/tail.legacy.yml +++ b/minifirewall/tasks/tail.legacy.yml @@ -1,4 +1,22 @@ --- + +- name: Stat minifirewall config file (before) + stat: + path: "/etc/default/minifirewall" + register: minifirewall_before + +- name: Check if minifirewall is running + shell: + cmd: /sbin/iptables -L -n | grep -E "^(DROP\s+udp|ACCEPT\s+icmp)\s+--\s+0\.0\.0\.0\/0\s+0\.0\.0\.0\/0\s*$" + changed_when: False + failed_when: False + check_mode: no + register: minifirewall_is_running + +- debug: + var: minifirewall_is_running + verbosity: 1 + - name: Add some rules at the end of minifirewall file template: src: "{{ item }}" @@ -30,20 +48,14 @@ var: minifirewall_tail_source verbosity: 1 -- name: restart minifirewall - command: /etc/init.d/minifirewall restart - register: minifirewall_init_restart - failed_when: "'starting IPTables rules is now finish : OK' not in minifirewall_init_restart.stdout" +- name: Schedule minifirewall restart (legacy) + command: /bin/true + notify: "restart minifirewall (legacy)" when: - - minifirewall_tail_template is changed + - minifirewall_install_mode == 'legacy' - minifirewall_restart_if_needed | bool - -- name: restart minifirewall (noop) - meta: noop - register: minifirewall_init_restart - failed_when: False - changed_when: False - when: not (minifirewall_restart_if_needed | bool) + - minifirewall_is_running.rc == 0 + - minifirewall_tail_template is changed - debug: var: minifirewall_init_restart diff --git a/minifirewall/tasks/tail.yml b/minifirewall/tasks/tail.yml index 1d708fa4..73d60d9c 100644 --- a/minifirewall/tasks/tail.yml +++ b/minifirewall/tasks/tail.yml @@ -1,4 +1,22 @@ --- + +- name: Stat minifirewall config file (before) + stat: + path: "/etc/default/minifirewall" + register: minifirewall_before + +- name: Check if minifirewall is running + shell: + cmd: /sbin/iptables -L -n | grep -E "^(DROP\s+udp|ACCEPT\s+icmp)\s+--\s+0\.0\.0\.0\/0\s+0\.0\.0\.0\/0\s*$" + changed_when: False + failed_when: False + check_mode: no + register: minifirewall_is_running + +- debug: + var: minifirewall_is_running + verbosity: 1 + - name: Add some rules at the end of minifirewall file template: src: "{{ item }}" @@ -18,20 +36,14 @@ var: minifirewall_tail_template verbosity: 1 -- name: restart minifirewall - command: /etc/init.d/minifirewall restart - register: minifirewall_init_restart - failed_when: "'minifirewall failed' in minifirewall_init_restart.stdout" +- name: Schedule minifirewall restart (modern) + command: /bin/true + notify: "restart minifirewall (modern)" when: - - minifirewall_tail_template is changed + - minifirewall_install_mode != 'legacy' - minifirewall_restart_if_needed | bool - -- name: restart minifirewall (noop) - meta: noop - register: minifirewall_init_restart - failed_when: False - changed_when: False - when: not (minifirewall_restart_if_needed | bool) + - minifirewall_is_running.rc == 0 + - minifirewall_tail_template is changed - debug: var: minifirewall_init_restart diff --git a/munin/tasks/main.yml b/munin/tasks/main.yml index 4720fbe5..a4ea9a49 100644 --- a/munin/tasks/main.yml +++ b/munin/tasks/main.yml @@ -1,12 +1,13 @@ --- -- name: Ensure that Munin is installed +- name: Ensure that Munin (and useful dependencies) is installed apt: name: - munin - munin-node - munin-plugins-core - munin-plugins-extra + - gawk state: present tags: - munin @@ -79,16 +80,32 @@ tags: - munin -- name: Enable sensors plugin unless VM detected +- name: Enable sensors_ plugin on dedicated hardware file: src: /usr/share/munin/plugins/sensors_ - dest: /etc/munin/plugins/sensors_temp + dest: "/etc/munin/plugins/sensors_{{ item }}" state: link - when: ansible_virtualization_role != "guest" + with_items: + - fan + - temp + when: ansible_virtualization_role == "host" notify: restart munin-node tags: - munin +- name: Enable ipmi_ plugin on dedicated hardware + file: + src: /usr/share/munin/plugins/ipmi_ + dest: "/etc/munin/plugins/ipmi_{{ item }}" + state: link + when: ansible_virtualization_role == "host" + notify: restart munin-node + with_items: + - fans + - temp + - power + - volts + - name: adjustments for grsec kernel blockinfile: dest: /etc/munin/plugin-conf.d/munin-node diff --git a/mysql/tasks/munin.yml b/mysql/tasks/munin.yml index 9ee8f95f..7d67065f 100644 --- a/mysql/tasks/munin.yml +++ b/mysql/tasks/munin.yml @@ -66,13 +66,42 @@ - replication notify: restart munin-node - - name: verify Munin configuration for mysql + - name: verify Munin configuration for mysql < Debian 11 replace: dest: /etc/munin/plugin-conf.d/munin-node after: '\[mysql\*\]' regexp: '^env.mysqluser (.+)$' replace: 'env.mysqluser debian-sys-maint' notify: restart munin-node + when: ansible_distribution_major_version is version_compare('11', '<') + + - name: set Munin env.mysqluser option for mysql >= Debian 11 + replace: + dest: /etc/munin/plugin-conf.d/munin-node + after: '\[mysql\*\]' + regexp: '^env.mysqluser (.+)$' + replace: 'env.mysqluser root' + notify: restart munin-node + when: ansible_distribution_major_version is version_compare('11', '>=') + + - name: set Munin env.mysqlopts option for mysql >= Debian 11 + replace: + dest: /etc/munin/plugin-conf.d/munin-node + after: '\[mysql\*\]' + regexp: '^env.mysqlopts (.+)$' + replace: 'env.mysqlopts --defaults-file=/root/.my.cnf' + notify: restart munin-node + when: ansible_distribution_major_version is version_compare('11', '>=') + + - name: set Munin env.mysqlconnection option for mysql >= Debian 11 + replace: + dest: /etc/munin/plugin-conf.d/munin-node + after: '\[mysql\*\]' + regexp: '^env.mysqlconnection (.+)$' + replace: 'env.mysqlconnection DBI:mysql:mysql;mysql_read_default_file=/root/.my.cnf' + notify: restart munin-node + when: ansible_distribution_major_version is version_compare('11', '>=') + when: munin_node_plugins_config.stat.exists tags: diff --git a/nagios-nrpe/files/plugins/check_mongodb b/nagios-nrpe/files/plugins/check_mongodb old mode 100755 new mode 100644 index bc6278ac..cce3a76d --- a/nagios-nrpe/files/plugins/check_mongodb +++ b/nagios-nrpe/files/plugins/check_mongodb @@ -17,24 +17,29 @@ # - Dag Stockstad # - @Andor on github # - Steven Richards - Captainkrtek on github -# - Max Vernimmen +# - Max Vernimmen - @mvernimmen-CG / @mvernimmen on github +# - Kris Nova - @kris@nivenly.com github.com/kris-nova +# - Jan Kantert - firstname@lastname.net # # USAGE # # See the README.md # +from __future__ import print_function +from __future__ import division import sys import time import optparse -import textwrap import re import os +import numbers +import socket try: import pymongo -except ImportError, e: - print e +except ImportError as e: + print(e) sys.exit(2) # As of pymongo v 1.9 the SON API is part of the BSON package, therefore attempt @@ -78,37 +83,35 @@ def performance_data(perf_data, params): def numeric_type(param): - if ((type(param) == float or type(param) == int or param == None)): - return True - return False + return param is None or isinstance(param, numbers.Real) def check_levels(param, warning, critical, message, ok=[]): if (numeric_type(critical) and numeric_type(warning)): if param >= critical: - print "CRITICAL - " + message + print("CRITICAL - " + message) sys.exit(2) elif param >= warning: - print "WARNING - " + message + print("WARNING - " + message) sys.exit(1) else: - print "OK - " + message + print("OK - " + message) sys.exit(0) else: if param in critical: - print "CRITICAL - " + message + print("CRITICAL - " + message) sys.exit(2) if param in warning: - print "WARNING - " + message + print("WARNING - " + message) sys.exit(1) if param in ok: - print "OK - " + message + print("OK - " + message) sys.exit(0) # unexpected param value - print "CRITICAL - Unexpected value : %d" % param + "; " + message + print("CRITICAL - Unexpected value : %d" % param + "; " + message) return 2 @@ -120,21 +123,32 @@ def get_server_status(con): data = con.admin.command(son.SON([('serverStatus', 1)])) return data +def split_host_port(string): + if not string.rsplit(':', 1)[-1].isdigit(): + return (string, None) + string = string.rsplit(':', 1) + host = string[0] # 1st index is always host + port = int(string[1]) + return (host, port) + def main(argv): p = optparse.OptionParser(conflict_handler="resolve", description="This Nagios plugin checks the health of mongodb.") p.add_option('-H', '--host', action='store', type='string', dest='host', default='127.0.0.1', help='The hostname you want to connect to') - p.add_option('-P', '--port', action='store', type='int', dest='port', default=27017, help='The port mongodb is runnung on') + p.add_option('-h', '--host-to-check', action='store', type='string', dest='host_to_check', default=None, help='The hostname you want to check (if this is different from the host you are connecting)') + p.add_option('--rdns-lookup', action='store_true', dest='rdns_lookup', default=False, help='RDNS(PTR) lookup on given host/host-to-check, to convert ip-address to fqdn') + p.add_option('-P', '--port', action='store', type='int', dest='port', default=27017, help='The port mongodb is running on') + p.add_option('--port-to-check', action='store', type='int', dest='port_to_check', default=None, help='The port you want to check (if this is different from the port you are connecting)') p.add_option('-u', '--user', action='store', type='string', dest='user', default=None, help='The username you want to login as') p.add_option('-p', '--pass', action='store', type='string', dest='passwd', default=None, help='The password you want to use for that user') - p.add_option('-W', '--warning', action='store', dest='warning', default=None, help='The warning threshold we want to set') - p.add_option('-C', '--critical', action='store', dest='critical', default=None, help='The critical threshold we want to set') + p.add_option('-W', '--warning', action='store', dest='warning', default=None, help='The warning threshold you want to set') + p.add_option('-C', '--critical', action='store', dest='critical', default=None, help='The critical threshold you want to set') p.add_option('-A', '--action', action='store', type='choice', dest='action', default='connect', help='The action you want to take', choices=['connect', 'connections', 'replication_lag', 'replication_lag_percent', 'replset_state', 'memory', 'memory_mapped', 'lock', - 'flushing', 'last_flush_time', 'index_miss_ratio', 'databases', 'collections', 'database_size', 'database_indexes', 'collection_indexes', 'collection_size', - 'queues', 'oplog', 'journal_commits_in_wl', 'write_data_files', 'journaled', 'opcounters', 'current_lock', 'replica_primary', 'page_faults', - 'asserts', 'queries_per_second', 'page_faults', 'chunks_balance', 'connect_primary', 'collection_state', 'row_count', 'replset_quorum']) + 'flushing', 'last_flush_time', 'index_miss_ratio', 'databases', 'collections', 'database_size', 'database_indexes', 'collection_documents', 'collection_indexes', 'collection_size', + 'collection_storageSize', 'queues', 'oplog', 'journal_commits_in_wl', 'write_data_files', 'journaled', 'opcounters', 'current_lock', 'replica_primary', + 'page_faults', 'asserts', 'queries_per_second', 'page_faults', 'chunks_balance', 'connect_primary', 'collection_state', 'row_count', 'replset_quorum']) p.add_option('--max-lag', action='store_true', dest='max_lag', default=False, help='Get max replication lag (for replication_lag action only)') p.add_option('--mapped-memory', action='store_true', dest='mapped_memory', default=False, help='Get mapped memory instead of resident (if resident memory can not be read)') p.add_option('-D', '--perf-data', action='store_true', dest='perf_data', default=False, help='Enable output of Nagios performance data') @@ -145,12 +159,28 @@ def main(argv): p.add_option('-q', '--querytype', action='store', dest='query_type', default='query', help='The query type to check [query|insert|update|delete|getmore|command] from queries_per_second') p.add_option('-c', '--collection', action='store', dest='collection', default='admin', help='Specify the collection to check') p.add_option('-T', '--time', action='store', type='int', dest='sample_time', default=1, help='Time used to sample number of pages faults') + p.add_option('-M', '--mongoversion', action='store', type='choice', dest='mongo_version', default='2', help='The MongoDB version you are talking with, either 2 or 3', + choices=['2','3']) + p.add_option('-a', '--authdb', action='store', type='string', dest='authdb', default='admin', help='The database you want to authenticate against') + p.add_option('--insecure', action='store_true', dest='insecure', default=False, help="Don't verify SSL/TLS certificates") + p.add_option('--ssl-ca-cert-file', action='store', type='string', dest='ssl_ca_cert_file', default=None, help='Path to Certificate Authority file for SSL') + p.add_option('-f', '--ssl-cert-file', action='store', type='string', dest='cert_file', default=None, help='Path to PEM encoded key and cert for client authentication') + p.add_option('-m','--auth-mechanism', action='store', type='choice', dest='auth_mechanism', default=None, help='Auth mechanism used for auth with mongodb', + choices=['MONGODB-X509','SCRAM-SHA-256','SCRAM-SHA-1']) + p.add_option('--disable_retry_writes', dest='retry_writes_disabled', default=False, action='callback', callback=optional_arg(True), help='Disable retryWrites feature') options, arguments = p.parse_args() host = options.host + host_to_check = options.host_to_check if options.host_to_check else options.host + rdns_lookup = options.rdns_lookup + if (rdns_lookup): + host_to_check = socket.getnameinfo((host_to_check, 0), 0)[0] port = options.port + port_to_check = options.port_to_check if options.port_to_check else options.port user = options.user passwd = options.passwd + authdb = options.authdb + query_type = options.query_type collection = options.collection sample_time = options.sample_time @@ -164,9 +194,15 @@ def main(argv): action = options.action perf_data = options.perf_data max_lag = options.max_lag + mongo_version = options.mongo_version database = options.database ssl = options.ssl replicaset = options.replicaset + insecure = options.insecure + ssl_ca_cert_file = options.ssl_ca_cert_file + cert_file = options.cert_file + auth_mechanism = options.auth_mechanism + retry_writes_disabled = options.retry_writes_disabled if action == 'replica_primary' and replicaset is None: return "replicaset must be passed in when using replica_primary check" @@ -177,31 +213,35 @@ def main(argv): # moving the login up here and passing in the connection # start = time.time() - err, con = mongo_connect(host, port, ssl, user, passwd, replicaset) + err, con = mongo_connect(host, port, ssl, user, passwd, replicaset, authdb, insecure, ssl_ca_cert_file, cert_file, auth_mechanism, retry_writes_disabled=retry_writes_disabled) + if err != 0: + return err + + # Autodetect mongo-version and force pymongo to let us know if it can connect or not. + err, mongo_version = check_version(con) if err != 0: return err conn_time = time.time() - start - conn_time = round(conn_time, 0) if action == "connections": return check_connections(con, warning, critical, perf_data) elif action == "replication_lag": - return check_rep_lag(con, host, port, warning, critical, False, perf_data, max_lag, user, passwd) + return check_rep_lag(con, host_to_check, port_to_check, rdns_lookup, warning, critical, False, perf_data, max_lag, ssl, user, passwd, replicaset, authdb, insecure, ssl_ca_cert_file, cert_file, auth_mechanism, retry_writes_disabled=retry_writes_disabled) elif action == "replication_lag_percent": - return check_rep_lag(con, host, port, warning, critical, True, perf_data, max_lag, user, passwd) + return check_rep_lag(con, host_to_check, port_to_check, rdns_lookup, warning, critical, True, perf_data, max_lag, ssl, user, passwd, replicaset, authdb, insecure, ssl_ca_cert_file, cert_file, auth_mechanism, retry_writes_disabled=retry_writes_disabled) elif action == "replset_state": return check_replset_state(con, perf_data, warning, critical) elif action == "memory": - return check_memory(con, warning, critical, perf_data, options.mapped_memory) + return check_memory(con, warning, critical, perf_data, options.mapped_memory, host) elif action == "memory_mapped": return check_memory_mapped(con, warning, critical, perf_data) elif action == "queues": return check_queues(con, warning, critical, perf_data) elif action == "lock": - return check_lock(con, warning, critical, perf_data) + return check_lock(con, warning, critical, perf_data, mongo_version) elif action == "current_lock": - return check_current_lock(con, host, warning, critical, perf_data) + return check_current_lock(con, host, port, warning, critical, perf_data) elif action == "flushing": return check_flushing(con, warning, critical, True, perf_data) elif action == "last_flush_time": @@ -223,22 +263,26 @@ def main(argv): return check_database_size(con, database, warning, critical, perf_data) elif action == "database_indexes": return check_database_indexes(con, database, warning, critical, perf_data) + elif action == "collection_documents": + return check_collection_documents(con, database, collection, warning, critical, perf_data) elif action == "collection_indexes": return check_collection_indexes(con, database, collection, warning, critical, perf_data) elif action == "collection_size": return check_collection_size(con, database, collection, warning, critical, perf_data) + elif action == "collection_storageSize": + return check_collection_storageSize(con, database, collection, warning, critical, perf_data) elif action == "journaled": return check_journaled(con, warning, critical, perf_data) elif action == "write_data_files": return check_write_to_datafiles(con, warning, critical, perf_data) elif action == "opcounters": - return check_opcounters(con, host, warning, critical, perf_data) + return check_opcounters(con, host, port, warning, critical, perf_data) elif action == "asserts": - return check_asserts(con, host, warning, critical, perf_data) + return check_asserts(con, host, port, warning, critical, perf_data) elif action == "replica_primary": - return check_replica_primary(con, host, warning, critical, perf_data, replicaset) + return check_replica_primary(con, host, warning, critical, perf_data, replicaset, mongo_version) elif action == "queries_per_second": - return check_queries_per_second(con, query_type, warning, critical, perf_data) + return check_queries_per_second(con, query_type, warning, critical, perf_data, mongo_version) elif action == "page_faults": check_page_faults(con, sample_time, warning, critical, perf_data) elif action == "chunks_balance": @@ -255,30 +299,73 @@ def main(argv): return check_connect(host, port, warning, critical, perf_data, user, passwd, conn_time) -def mongo_connect(host=None, port=None, ssl=False, user=None, passwd=None, replica=None): +def mongo_connect(host=None, port=None, ssl=False, user=None, passwd=None, replica=None, authdb="admin", insecure=False, ssl_ca_cert_file=None, ssl_cert=None, auth_mechanism=None, retry_writes_disabled=False): + from pymongo.errors import ConnectionFailure + from pymongo.errors import PyMongoError + import ssl as SSL + + con_args = dict() + + if ssl: + if insecure: + con_args['ssl_cert_reqs'] = SSL.CERT_NONE + else: + con_args['ssl_cert_reqs'] = SSL.CERT_REQUIRED + con_args['ssl'] = ssl + if ssl_ca_cert_file: + con_args['ssl_ca_certs'] = ssl_ca_cert_file + if ssl_cert: + con_args['ssl_certfile'] = ssl_cert + + if retry_writes_disabled: + con_args['retryWrites'] = False + try: # ssl connection for pymongo > 2.3 if pymongo.version >= "2.3": if replica is None: - con = pymongo.MongoClient(host, port) + con = pymongo.MongoClient(host, port, **con_args) else: - con = pymongo.Connection(host, port, read_preference=pymongo.ReadPreference.SECONDARY, ssl=ssl, replicaSet=replica, network_timeout=10) + con = pymongo.MongoClient(host, port, read_preference=pymongo.ReadPreference.SECONDARY, replicaSet=replica, **con_args) else: if replica is None: con = pymongo.Connection(host, port, slave_okay=True, network_timeout=10) else: con = pymongo.Connection(host, port, slave_okay=True, network_timeout=10) - #con = pymongo.Connection(host, port, slave_okay=True, replicaSet=replica, network_timeout=10) + + # we must authenticate the connection, otherwise we won't be able to perform certain operations + if ssl_cert and ssl_ca_cert_file and user and auth_mechanism == 'SCRAM-SHA-256': + con.the_database.authenticate(user, mechanism='SCRAM-SHA-256') + elif ssl_cert and ssl_ca_cert_file and user and auth_mechanism == 'SCRAM-SHA-1': + con.the_database.authenticate(user, mechanism='SCRAM-SHA-1') + elif ssl_cert and ssl_ca_cert_file and user and auth_mechanism == 'MONGODB-X509': + con.the_database.authenticate(user, mechanism='MONGODB-X509') + + try: + result = con.admin.command("ismaster") + except ConnectionFailure: + print("CRITICAL - Connection to Mongo server on %s:%s has failed" % (host, port) ) + sys.exit(2) + + if 'arbiterOnly' in result and result['arbiterOnly'] == True: + print("OK - State: 7 (Arbiter on port %s)" % (port)) + sys.exit(0) if user and passwd: - db = con["admin"] - if not db.authenticate(user, passwd): + db = con[authdb] + try: + db.authenticate(user, password=passwd) + except PyMongoError: sys.exit("Username/Password incorrect") - except Exception, e: + + # Ping to check that the server is responding. + con.admin.command("ping") + + except Exception as e: if isinstance(e, pymongo.errors.AutoReconnect) and str(e).find(" is an arbiter") != -1: # We got a pymongo AutoReconnect exception that tells us we connected to an Arbiter Server # This means: Arbiter is reachable and can answer requests/votes - this is all we need to know from an arbiter - print "OK - State: 7 (Arbiter)" + print("OK - State: 7 (Arbiter)") sys.exit(0) return exit_with_general_critical(e), None return 0, con @@ -288,7 +375,7 @@ def exit_with_general_warning(e): if isinstance(e, SystemExit): return e else: - print "WARNING - General MongoDB warning:", e + print("WARNING - General MongoDB warning:", e) return 1 @@ -296,19 +383,27 @@ def exit_with_general_critical(e): if isinstance(e, SystemExit): return e else: - print "CRITICAL - General MongoDB Error:", e + print("CRITICAL - General MongoDB Error:", e) return 2 def set_read_preference(db): - if pymongo.version >= "2.1": + if pymongo.version >= "2.2": + pymongo.read_preferences.Secondary + else: db.read_preference = pymongo.ReadPreference.SECONDARY +def check_version(con): + try: + server_info = con.server_info() + except Exception as e: + return exit_with_general_critical(e), None + return 0, int(server_info['version'].split('.')[0].strip()) def check_connect(host, port, warning, critical, perf_data, user, passwd, conn_time): warning = warning or 3 critical = critical or 6 - message = "Connection took %i seconds" % conn_time + message = "Connection took %.3f seconds" % conn_time message += performance_data(perf_data, [(conn_time, "connection_time", warning, critical)]) return check_levels(conn_time, warning, critical, message) @@ -330,13 +425,17 @@ def check_connections(con, warning, critical, perf_data): (available, "available_connections")]) return check_levels(used_percent, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) -def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_lag, user, passwd): +def check_rep_lag(con, host, port, rdns_lookup, warning, critical, percent, perf_data, max_lag, ssl=False, user=None, passwd=None, replicaset=None, authdb="admin", insecure=None, ssl_ca_cert_file=None, cert_file=None, auth_mechanism=None, retry_writes_disabled=False): # Get mongo to tell us replica set member name when connecting locally if "127.0.0.1" == host: + if not "me" in list(con.admin.command("ismaster","1").keys()): + print("UNKNOWN - This is not replicated MongoDB") + return 3 + host = con.admin.command("ismaster","1")["me"].split(':')[0] if percent: @@ -348,16 +447,15 @@ def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_la rs_status = {} slaveDelays = {} try: - set_read_preference(con.admin) + #set_read_preference(con.admin) # Get replica set status try: rs_status = con.admin.command("replSetGetStatus") - except pymongo.errors.OperationFailure, e: - if e.code == None and str(e).find('failed: not running with --replSet"'): - print "OK - Not running with replSet" - return 0 - + except pymongo.errors.OperationFailure as e: + if ((e.code == None and str(e).find('failed: not running with --replSet"')) or (e.code == 76 and str(e).find('not running with --replSet"'))): + print("UNKNOWN - Not running with replSet") + return 3 serverVersion = tuple(con.server_info()['version'].split('.')) if serverVersion >= tuple("2.0.0".split(".")): # @@ -377,24 +475,32 @@ def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_la for member in rs_status["members"]: if member["stateStr"] == "PRIMARY": primary_node = member - if member["name"].split(':')[0] == host and int(member["name"].split(':')[1]) == port: + + # if rdns_lookup is true then lookup both values back to their rdns value so we can compare hostname vs fqdn + if rdns_lookup: + member_host, member_port = split_host_port(member.get('name')) + member_host = "{0}:{1}".format(socket.getnameinfo((member_host, 0), 0)[0], member_port) + if member_host == "{0}:{1}".format(socket.getnameinfo((host, 0), 0)[0], port): + host_node = member + # Exact match + elif member.get('name') == "{0}:{1}".format(host, port): host_node = member # Check if we're in the middle of an election and don't have a primary if primary_node is None: - print "WARNING - No primary defined. In an election?" + print("WARNING - No primary defined. In an election?") return 1 # Check if we failed to find the current host # below should never happen if host_node is None: - print "CRITICAL - Unable to find host '" + host + "' in replica set." + print("CRITICAL - Unable to find host '" + host + "' in replica set.") return 2 # Is the specified host the primary? if host_node["stateStr"] == "PRIMARY": if max_lag == False: - print "OK - This is the primary." + print("OK - This is the primary.") return 0 else: #get the maximal replication lag @@ -407,7 +513,7 @@ def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_la data = data + member['name'] + " lag=%d;" % replicationLag maximal_lag = max(maximal_lag, replicationLag) if percent: - err, con = mongo_connect(primary_node['name'].split(':')[0], int(primary_node['name'].split(':')[1]), False, user, passwd) + err, con = mongo_connect(split_host_port(primary_node['name'])[0], int(split_host_port(primary_node['name'])[1]), ssl, user, passwd, replicaset, authdb, insecure, ssl_ca_cert_file, cert_file, auth_mechanism, retry_writes_disabled=retry_writes_disabled) if err != 0: return err primary_timediff = replication_get_time_diff(con) @@ -419,8 +525,8 @@ def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_la message += performance_data(perf_data, [(maximal_lag, "replication_lag", warning, critical)]) return check_levels(maximal_lag, warning, critical, message) elif host_node["stateStr"] == "ARBITER": - print "OK - This is an arbiter" - return 0 + print("UNKNOWN - This is an arbiter") + return 3 # Find the difference in optime between current node and PRIMARY @@ -439,7 +545,7 @@ def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_la lag = float(optime_lag.seconds + optime_lag.days * 24 * 3600) if percent: - err, con = mongo_connect(primary_node['name'].split(':')[0], int(primary_node['name'].split(':')[1]), False, user, passwd) + err, con = mongo_connect(split_host_port(primary_node['name'])[0], int(split_host_port(primary_node['name'])[1]), ssl, user, passwd, replicaset, authdb, insecure, ssl_ca_cert_file, cert_file, auth_mechanism, retry_writes_disabled=retry_writes_disabled) if err != 0: return err primary_timediff = replication_get_time_diff(con) @@ -471,19 +577,19 @@ def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_la # Check if we're in the middle of an election and don't have a primary if primary_node is None: - print "WARNING - No primary defined. In an election?" + print("WARNING - No primary defined. In an election?") sys.exit(1) # Is the specified host the primary? if host_node["stateStr"] == "PRIMARY": - print "OK - This is the primary." + print("OK - This is the primary.") sys.exit(0) # Find the difference in optime between current node and PRIMARY optime_lag = abs(primary_node[1] - host_node["optimeDate"]) lag = optime_lag.seconds if percent: - err, con = mongo_connect(primary_node['name'].split(':')[0], int(primary_node['name'].split(':')[1])) + err, con = mongo_connect(split_host_port(primary_node['name'])[0], int(split_host_port(primary_node['name'])[1]), ssl, user, passwd, replicaset, authdb, insecure, ssl_ca_cert_file, cert_file, auth_mechanism, retry_writes_disabled=retry_writes_disabled) if err != 0: return err primary_timediff = replication_get_time_diff(con) @@ -495,26 +601,34 @@ def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_la message += performance_data(perf_data, [(lag, "replication_lag", warning, critical)]) return check_levels(lag, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) - -def check_memory(con, warning, critical, perf_data, mapped_memory): - # - # These thresholds are basically meaningless, and must be customized to your system's ram - # - - # Get the total system merory and calculate based on that how much memory used by Mongodb is ok or not. +# +# Check the memory usage of mongo. Alerting on this may be hard to get right +# because it'll try to get as much memory as it can. And that's probably +# a good thing. +# +def check_memory(con, warning, critical, perf_data, mapped_memory, host): + # Get the total system memory of this system (This is totally bogus if you + # are running this command remotely) and calculate based on that how much + # memory used by Mongodb is ok or not. meminfo = open('/proc/meminfo').read() matched = re.search(r'^MemTotal:\s+(\d+)', meminfo) - if matched: + if matched: mem_total_kB = int(matched.groups()[0]) - # Old way - #critical = critical or 16 - # The new way. if using >80% then warn, if >90% then critical level - warning = warning or (mem_total_kB * 0.8) / 1024.0 / 1024.0 - critical = critical or (mem_total_kB * 0.9) / 1024.0 / 1024.0 + if host != "127.0.0.1" and not warning: + # Running remotely and value was not set by user, use hardcoded value + warning = 12 + else: + # running locally or user provided value + warning = warning or (mem_total_kB * 0.8) / 1024.0 / 1024.0 + + if host != "127.0.0.1" and not critical: + critical = 16 + else: + critical = critical or (mem_total_kB * 0.9) / 1024.0 / 1024.0 # debugging #print "mem total: {0}kb, warn: {1}GB, crit: {2}GB".format(mem_total_kB,warning, critical) @@ -522,7 +636,7 @@ def check_memory(con, warning, critical, perf_data, mapped_memory): try: data = get_server_status(con) if not data['mem']['supported'] and not mapped_memory: - print "OK - Platform not supported for memory info" + print("OK - Platform not supported for memory info") return 0 # # convert to gigs @@ -559,7 +673,7 @@ def check_memory(con, warning, critical, perf_data, mapped_memory): else: return check_levels(mem_resident, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -572,7 +686,7 @@ def check_memory_mapped(con, warning, critical, perf_data): try: data = get_server_status(con) if not data['mem']['supported']: - print "OK - Platform not supported for memory info" + print("OK - Platform not supported for memory info") return 0 # # convert to gigs @@ -589,38 +703,45 @@ def check_memory_mapped(con, warning, critical, perf_data): message += " %.2fGB mappedWithJournal" % mem_mapped_journal except: mem_mapped_journal = 0 - message += performance_data(perf_data, [("%.2f" % mem_mapped, "memory_mapped"), ("%.2f" % mem_mapped_journal, "mappedWithJournal")]) + message += performance_data(perf_data, [("%.2f" % mem_mapped, "memory_mapped", warning, critical), ("%.2f" % mem_mapped_journal, "mappedWithJournal")]) if not mem_mapped == -1: return check_levels(mem_mapped, warning, critical, message) else: - print "OK - Server does not provide mem.mapped info" + print("OK - Server does not provide mem.mapped info") return 0 - except Exception, e: + except Exception as e: return exit_with_general_critical(e) -def check_lock(con, warning, critical, perf_data): +# +# Return the percentage of the time there was a global Lock +# +def check_lock(con, warning, critical, perf_data, mongo_version): warning = warning or 10 critical = critical or 30 - try: - data = get_server_status(con) - # - # calculate percentage - # - lockTime = data['globalLock']['lockTime'] - totalTime = data['globalLock']['totalTime'] - if lockTime > totalTime: - lock_percentage = 0.00 - else: - lock_percentage = float(lockTime) / float(totalTime) * 100 - message = "Lock Percentage: %.2f%%" % lock_percentage - message += performance_data(perf_data, [("%.2f" % lock_percentage, "lock_percentage", warning, critical)]) - return check_levels(lock_percentage, warning, critical, message) - - except Exception, e: - return exit_with_general_critical(e) + if mongo_version == 2: + try: + data = get_server_status(con) + lockTime = data['globalLock']['lockTime'] + totalTime = data['globalLock']['totalTime'] + # + # calculate percentage + # + if lockTime > totalTime: + lock_percentage = 0.00 + else: + lock_percentage = float(lockTime) / float(totalTime) * 100 + message = "Lock Percentage: %.2f%%" % lock_percentage + message += performance_data(perf_data, [("%.2f" % lock_percentage, "lock_percentage", warning, critical)]) + return check_levels(lock_percentage, warning, critical, message) + except Exception as e: + print("Couldn't get globalLock lockTime info from mongo, are you sure you're not using version 3? See the -M option.") + return exit_with_general_critical(e) + else: + print("OK - MongoDB version 3 doesn't report on global locks") + return 0 def check_flushing(con, warning, critical, avg, perf_data): @@ -632,19 +753,24 @@ def check_flushing(con, warning, critical, avg, perf_data): critical = critical or 15000 try: data = get_server_status(con) - if avg: - flush_time = float(data['backgroundFlushing']['average_ms']) - stat_type = "Average" - else: - flush_time = float(data['backgroundFlushing']['last_ms']) - stat_type = "Last" + try: + data['backgroundFlushing'] + if avg: + flush_time = float(data['backgroundFlushing']['average_ms']) + stat_type = "Average" + else: + flush_time = float(data['backgroundFlushing']['last_ms']) + stat_type = "Last" - message = "%s Flush Time: %.2fms" % (stat_type, flush_time) - message += performance_data(perf_data, [("%.2fms" % flush_time, "%s_flush_time" % stat_type.lower(), warning, critical)]) + message = "%s Flush Time: %.2fms" % (stat_type, flush_time) + message += performance_data(perf_data, [("%.2fms" % flush_time, "%s_flush_time" % stat_type.lower(), warning, critical)]) - return check_levels(flush_time, warning, critical, message) + return check_levels(flush_time, warning, critical, message) + except Exception: + print("OK - flushing stats not available for this storage engine") + return 0 - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -655,6 +781,7 @@ def index_miss_ratio(con, warning, critical, perf_data): data = get_server_status(con) try: + data['indexCounters'] serverVersion = tuple(con.server_info()['version'].split('.')) if serverVersion >= tuple("2.4.0".split(".")): miss_ratio = float(data['indexCounters']['missRatio']) @@ -662,19 +789,24 @@ def index_miss_ratio(con, warning, critical, perf_data): miss_ratio = float(data['indexCounters']['btree']['missRatio']) except KeyError: not_supported_msg = "not supported on this platform" - if data['indexCounters'].has_key('note'): - print "OK - MongoDB says: " + not_supported_msg + try: + data['indexCounters'] + if 'note' in data['indexCounters']: + print("OK - MongoDB says: " + not_supported_msg) + return 0 + else: + print("WARNING - Can't get counter from MongoDB") + return 1 + except Exception: + print("OK - MongoDB says: " + not_supported_msg) return 0 - else: - print "WARNING - Can't get counter from MongoDB" - return 1 message = "Miss Ratio: %.2f" % miss_ratio message += performance_data(perf_data, [("%.2f" % miss_ratio, "index_miss_ratio", warning, critical)]) return check_levels(miss_ratio, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) def check_replset_quorum(con, perf_data): @@ -698,7 +830,7 @@ def check_replset_quorum(con, perf_data): message = "Cluster is not quorate and cannot operate" return check_levels(state, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -713,44 +845,63 @@ def check_replset_state(con, perf_data, warning="", critical=""): except: critical = [8, 4, -1] - ok = range(-1, 8) # should include the range of all posiible values + ok = list(range(-1, 8)) # should include the range of all posiible values try: + worst_state = -2 + message = "" try: try: set_read_preference(con.admin) data = con.admin.command(pymongo.son_manipulator.SON([('replSetGetStatus', 1)])) except: data = con.admin.command(son.SON([('replSetGetStatus', 1)])) - state = int(data['myState']) - except pymongo.errors.OperationFailure, e: - if e.code == None and str(e).find('failed: not running with --replSet"'): - state = -1 + members = data['members'] + my_state = int(data['myState']) + worst_state = my_state + for member in members: + their_state = int(member['state']) + message += " %s: %i (%s)" % (member['name'], their_state, state_text(their_state)) + if state_is_worse(their_state, worst_state, warning, critical): + worst_state = their_state + message += performance_data(perf_data, [(my_state, "state")]) - if state == 8: - message = "State: %i (Down)" % state - elif state == 4: - message = "State: %i (Fatal error)" % state - elif state == 0: - message = "State: %i (Starting up, phase1)" % state - elif state == 3: - message = "State: %i (Recovering)" % state - elif state == 5: - message = "State: %i (Starting up, phase2)" % state - elif state == 1: - message = "State: %i (Primary)" % state - elif state == 2: - message = "State: %i (Secondary)" % state - elif state == 7: - message = "State: %i (Arbiter)" % state - elif state == -1: - message = "Not running with replSet" - else: - message = "State: %i (Unknown state)" % state - message += performance_data(perf_data, [(state, "state")]) - return check_levels(state, warning, critical, message, ok) - except Exception, e: + except pymongo.errors.OperationFailure as e: + if ((e.code == None and str(e).find('failed: not running with --replSet"')) or (e.code == 76 and str(e).find('not running with --replSet"'))): + worst_state = -1 + + return check_levels(worst_state, warning, critical, message, ok) + except Exception as e: return exit_with_general_critical(e) +def state_is_worse(state, worst_state, warning, critical): + if worst_state in critical: + return False + if worst_state in warning: + return state in critical + return (state in warning) or (state in critical) + +def state_text(state): + if state == 8: + return "Down" + elif state == 4: + return "Fatal error" + elif state == 0: + return "Starting up, phase1" + elif state == 3: + return "Recovering" + elif state == 5: + return "Starting up, phase2" + elif state == 1: + return "Primary" + elif state == 2: + return "Secondary" + elif state == 7: + return "Arbiter" + elif state == -1: + return "Not running with replSet" + else: + return "Unknown state" + def check_databases(con, warning, critical, perf_data=None): try: @@ -764,7 +915,7 @@ def check_databases(con, warning, critical, perf_data=None): message = "Number of DBs: %.0f" % count message += performance_data(perf_data, [(count, "databases", warning, critical, message)]) return check_levels(count, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -786,7 +937,7 @@ def check_collections(con, warning, critical, perf_data=None): message += performance_data(perf_data, [(count, "collections", warning, critical, message)]) return check_levels(count, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -823,21 +974,21 @@ def check_database_size(con, database, warning, critical, perf_data): try: set_read_preference(con.admin) data = con[database].command('dbstats') - storage_size = data['storageSize'] / 1024 / 1024 + storage_size = data['storageSize'] // 1024 // 1024 if perf_data: perfdata += " | database_size=%i;%i;%i" % (storage_size, warning, critical) #perfdata += " database=%s" %(database) if storage_size >= critical: - print "CRITICAL - Database size: %.0f MB, Database: %s%s" % (storage_size, database, perfdata) + print("CRITICAL - Database size: %.0f MB, Database: %s%s" % (storage_size, database, perfdata)) return 2 elif storage_size >= warning: - print "WARNING - Database size: %.0f MB, Database: %s%s" % (storage_size, database, perfdata) + print("WARNING - Database size: %.0f MB, Database: %s%s" % (storage_size, database, perfdata)) return 1 else: - print "OK - Database size: %.0f MB, Database: %s%s" % (storage_size, database, perfdata) + print("OK - Database size: %.0f MB, Database: %s%s" % (storage_size, database, perfdata)) return 0 - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -851,20 +1002,42 @@ def check_database_indexes(con, database, warning, critical, perf_data): try: set_read_preference(con.admin) data = con[database].command('dbstats') - index_size = data['indexSize'] / 1024 / 1024 + index_size = data['indexSize'] / 1024 // 1024 if perf_data: perfdata += " | database_indexes=%i;%i;%i" % (index_size, warning, critical) if index_size >= critical: - print "CRITICAL - %s indexSize: %.0f MB %s" % (database, index_size, perfdata) + print("CRITICAL - %s indexSize: %.0f MB %s" % (database, index_size, perfdata)) return 2 elif index_size >= warning: - print "WARNING - %s indexSize: %.0f MB %s" % (database, index_size, perfdata) + print("WARNING - %s indexSize: %.0f MB %s" % (database, index_size, perfdata)) return 1 else: - print "OK - %s indexSize: %.0f MB %s" % (database, index_size, perfdata) + print("OK - %s indexSize: %.0f MB %s" % (database, index_size, perfdata)) return 0 - except Exception, e: + except Exception as e: + return exit_with_general_critical(e) + + +def check_collection_documents(con, database, collection, warning, critical, perf_data): + perfdata = "" + try: + set_read_preference(con.admin) + data = con[database].command('collstats', collection) + documents = data['count'] + if perf_data: + perfdata += " | collection_documents=%i;%i;%i" % (documents, warning, critical) + + if documents >= critical: + print("CRITICAL - %s.%s documents: %s %s" % (database, collection, documents, perfdata)) + return 2 + elif documents >= warning: + print("WARNING - %s.%s documents: %s %s" % (database, collection, documents, perfdata)) + return 1 + else: + print("OK - %s.%s documents: %s %s" % (database, collection, documents, perfdata)) + return 0 + except Exception as e: return exit_with_general_critical(e) @@ -883,15 +1056,15 @@ def check_collection_indexes(con, database, collection, warning, critical, perf_ perfdata += " | collection_indexes=%i;%i;%i" % (total_index_size, warning, critical) if total_index_size >= critical: - print "CRITICAL - %s.%s totalIndexSize: %.0f MB %s" % (database, collection, total_index_size, perfdata) + print("CRITICAL - %s.%s totalIndexSize: %.0f MB %s" % (database, collection, total_index_size, perfdata)) return 2 elif total_index_size >= warning: - print "WARNING - %s.%s totalIndexSize: %.0f MB %s" % (database, collection, total_index_size, perfdata) + print("WARNING - %s.%s totalIndexSize: %.0f MB %s" % (database, collection, total_index_size, perfdata)) return 1 else: - print "OK - %s.%s totalIndexSize: %.0f MB %s" % (database, collection, total_index_size, perfdata) + print("OK - %s.%s totalIndexSize: %.0f MB %s" % (database, collection, total_index_size, perfdata)) return 0 - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -908,7 +1081,7 @@ def check_queues(con, warning, critical, perf_data): message += performance_data(perf_data, [(total_queues, "total_queues", warning, critical), (readers_queues, "readers_queues"), (writers_queues, "writers_queues")]) return check_levels(total_queues, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) def check_collection_size(con, database, collection, warning, critical, perf_data): @@ -923,18 +1096,43 @@ def check_collection_size(con, database, collection, warning, critical, perf_dat perfdata += " | collection_size=%i;%i;%i" % (size, warning, critical) if size >= critical: - print "CRITICAL - %s.%s size: %.0f MB %s" % (database, collection, size, perfdata) + print("CRITICAL - %s.%s size: %.0f MB %s" % (database, collection, size, perfdata)) return 2 elif size >= warning: - print "WARNING - %s.%s size: %.0f MB %s" % (database, collection, size, perfdata) + print("WARNING - %s.%s size: %.0f MB %s" % (database, collection, size, perfdata)) return 1 else: - print "OK - %s.%s size: %.0f MB %s" % (database, collection, size, perfdata) + print("OK - %s.%s size: %.0f MB %s" % (database, collection, size, perfdata)) return 0 - except Exception, e: + except Exception as e: return exit_with_general_critical(e) -def check_queries_per_second(con, query_type, warning, critical, perf_data): + +def check_collection_storageSize(con, database, collection, warning, critical, perf_data): + warning = warning or 100 + critical = critical or 1000 + perfdata = "" + try: + set_read_preference(con.admin) + data = con[database].command('collstats', collection) + storageSize = data['storageSize'] / 1024 / 1024 + if perf_data: + perfdata += " | collection_storageSize=%i;%i;%i" % (storageSize, warning, critical) + + if storageSize >= critical: + print("CRITICAL - %s.%s storageSize: %.0f MB %s" % (database, collection, storageSize, perfdata)) + return 2 + elif storageSize >= warning: + print("WARNING - %s.%s storageSize: %.0f MB %s" % (database, collection, storageSize, perfdata)) + return 1 + else: + print("OK - %s.%s storageSize: %.0f MB %s" % (database, collection, storageSize, perfdata)) + return 0 + except Exception as e: + return exit_with_general_critical(e) + + +def check_queries_per_second(con, query_type, warning, critical, perf_data, mongo_version): warning = warning or 250 critical = critical or 500 @@ -955,10 +1153,17 @@ def check_queries_per_second(con, query_type, warning, critical, perf_data): diff_query = num - last_count['data'][query_type]['count'] diff_ts = ts - last_count['data'][query_type]['ts'] + if diff_ts == 0: + message = "diff_query = " + str(diff_query) + " diff_ts = " + str(diff_ts) + return check_levels(0, warning, critical, message) + query_per_sec = float(diff_query) / float(diff_ts) # update the count now - db.nagios_check.update({u'_id': last_count['_id']}, {'$set': {"data.%s" % query_type: {'count': num, 'ts': int(time.time())}}}) + if mongo_version == 2: + db.nagios_check.update({u'_id': last_count['_id']}, {'$set': {"data.%s" % query_type: {'count': num, 'ts': int(time.time())}}}) + else: + db.nagios_check.update_one({u'_id': last_count['_id']}, {'$set': {"data.%s" % query_type: {'count': num, 'ts': int(time.time())}}}) message = "Queries / Sec: %f" % query_per_sec message += performance_data(perf_data, [(query_per_sec, "%s_per_sec" % query_type, warning, critical, message)]) @@ -967,17 +1172,24 @@ def check_queries_per_second(con, query_type, warning, critical, perf_data): # since it is the first run insert it query_per_sec = 0 message = "First run of check.. no data" - db.nagios_check.update({u'_id': last_count['_id']}, {'$set': {"data.%s" % query_type: {'count': num, 'ts': int(time.time())}}}) + if mongo_version == 2: + db.nagios_check.update({u'_id': last_count['_id']}, {'$set': {"data.%s" % query_type: {'count': num, 'ts': int(time.time())}}}) + else: + db.nagios_check.update_one({u'_id': last_count['_id']}, {'$set': {"data.%s" % query_type: {'count': num, 'ts': int(time.time())}}}) + except TypeError: # # since it is the first run insert it query_per_sec = 0 message = "First run of check.. no data" - db.nagios_check.insert({'check': 'query_counts', 'data': {query_type: {'count': num, 'ts': int(time.time())}}}) + if mongo_version == 2: + db.nagios_check.insert({'check': 'query_counts', 'data': {query_type: {'count': num, 'ts': int(time.time())}}}) + else: + db.nagios_check.insert_one({'check': 'query_counts', 'data': {query_type: {'count': num, 'ts': int(time.time())}}}) return check_levels(query_per_sec, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -1024,7 +1236,7 @@ def check_oplog(con, warning, critical, perf_data): message += performance_data(perf_data, [("%.2f" % hours_in_oplog, 'oplog_time', warning, critical), ("%.2f " % approx_level, 'oplog_time_100_percent_used')]) return check_levels(-approx_level, -warning, -critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -1042,7 +1254,7 @@ Under very high write situations it is normal for this value to be nonzero. """ message += performance_data(perf_data, [(j_commits_in_wl, "j_commits_in_wl", warning, critical)]) return check_levels(j_commits_in_wl, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -1058,7 +1270,7 @@ def check_journaled(con, warning, critical, perf_data): message += performance_data(perf_data, [("%.2f" % journaled, "journaled", warning, critical)]) return check_levels(journaled, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -1075,11 +1287,11 @@ than the amount physically written to disk.""" message += performance_data(perf_data, [("%.2f" % writes, "write_to_data_files", warning, critical)]) return check_levels(writes, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) -def get_opcounters(data, opcounters_name, host): +def get_opcounters(data, opcounters_name, host, port): try: insert = data[opcounters_name]['insert'] query = data[opcounters_name]['query'] @@ -1087,21 +1299,21 @@ def get_opcounters(data, opcounters_name, host): delete = data[opcounters_name]['delete'] getmore = data[opcounters_name]['getmore'] command = data[opcounters_name]['command'] - except KeyError, e: + except KeyError as e: return 0, [0] * 100 total_commands = insert + query + update + delete + getmore + command new_vals = [total_commands, insert, query, update, delete, getmore, command] - return maintain_delta(new_vals, host, opcounters_name) + return maintain_delta(new_vals, host, port, opcounters_name) -def check_opcounters(con, host, warning, critical, perf_data): +def check_opcounters(con, host, port, warning, critical, perf_data): """ A function to get all opcounters delta per minute. In case of a replication - gets the opcounters+opcountersRepl""" warning = warning or 10000 critical = critical or 15000 data = get_server_status(con) - err1, delta_opcounters = get_opcounters(data, 'opcounters', host) - err2, delta_opcounters_repl = get_opcounters(data, 'opcountersRepl', host) + err1, delta_opcounters = get_opcounters(data, 'opcounters', host, port) + err2, delta_opcounters_repl = get_opcounters(data, 'opcountersRepl', host, port) if err1 == 0 and err2 == 0: delta = [(x + y) for x, y in zip(delta_opcounters, delta_opcounters_repl)] delta[0] = delta_opcounters[0] # only the time delta shouldn't be summarized @@ -1109,14 +1321,14 @@ def check_opcounters(con, host, warning, critical, perf_data): message = "Test succeeded , old values missing" message = "Opcounters: total=%d,insert=%d,query=%d,update=%d,delete=%d,getmore=%d,command=%d" % tuple(per_minute_delta) message += performance_data(perf_data, ([(per_minute_delta[0], "total", warning, critical), (per_minute_delta[1], "insert"), - (per_minute_delta[2], "query"), (per_minute_delta[3], "update"), (per_minute_delta[5], "delete"), + (per_minute_delta[2], "query"), (per_minute_delta[3], "update"), (per_minute_delta[4], "delete"), (per_minute_delta[5], "getmore"), (per_minute_delta[6], "command")])) return check_levels(per_minute_delta[0], warning, critical, message) else: return exit_with_general_critical("problem reading data from temp file") -def check_current_lock(con, host, warning, critical, perf_data): +def check_current_lock(con, host, port, warning, critical, perf_data): """ A function to get current lock percentage and not a global one, as check_lock function does""" warning = warning or 10 critical = critical or 30 @@ -1125,7 +1337,7 @@ def check_current_lock(con, host, warning, critical, perf_data): lockTime = float(data['globalLock']['lockTime']) totalTime = float(data['globalLock']['totalTime']) - err, delta = maintain_delta([totalTime, lockTime], host, "locktime") + err, delta = maintain_delta([totalTime, lockTime], host, port, "locktime") if err == 0: lock_percentage = delta[2] / delta[1] * 100 # lockTime/totalTime*100 message = "Current Lock Percentage: %.2f%%" % lock_percentage @@ -1135,7 +1347,7 @@ def check_current_lock(con, host, warning, critical, perf_data): return exit_with_general_warning("problem reading data from temp file") -def check_page_faults(con, host, warning, critical, perf_data): +def check_page_faults(con, host, port, warning, critical, perf_data): """ A function to get page_faults per second from the system""" warning = warning or 10 critical = critical or 30 @@ -1147,7 +1359,7 @@ def check_page_faults(con, host, warning, critical, perf_data): # page_faults unsupported on the underlaying system return exit_with_general_critical("page_faults unsupported on the underlaying system") - err, delta = maintain_delta([page_faults], host, "page_faults") + err, delta = maintain_delta([page_faults], host, port, "page_faults") if err == 0: page_faults_ps = delta[1] / delta[0] message = "Page faults : %.2f ps" % page_faults_ps @@ -1157,7 +1369,7 @@ def check_page_faults(con, host, warning, critical, perf_data): return exit_with_general_warning("problem reading data from temp file") -def check_asserts(con, host, warning, critical, perf_data): +def check_asserts(con, host, port, warning, critical, perf_data): """ A function to get asserts from the system""" warning = warning or 1 critical = critical or 10 @@ -1172,7 +1384,7 @@ def check_asserts(con, host, warning, critical, perf_data): user = asserts['user'] rollovers = asserts['rollovers'] - err, delta = maintain_delta([regular, warning_asserts, msg, user, rollovers], host, "asserts") + err, delta = maintain_delta([regular, warning_asserts, msg, user, rollovers], host, port, "asserts") if err == 0: if delta[5] != 0: @@ -1206,7 +1418,7 @@ def get_stored_primary_server_name(db): return stored_primary_server -def check_replica_primary(con, host, warning, critical, perf_data, replicaset): +def check_replica_primary(con, host, warning, critical, perf_data, replicaset, mongo_version): """ A function to check if the primary server of a replica set has changed """ if warning is None and critical is None: warning = 1 @@ -1229,7 +1441,10 @@ def check_replica_primary(con, host, warning, critical, perf_data, replicaset): saved_primary = "None" if current_primary != saved_primary: last_primary_server_record = {"server": current_primary} - db.last_primary_server.update({"_id": "last_primary"}, {"$set": last_primary_server_record}, upsert=True, safe=True) + if mongo_version == 2: + db.last_primary_server.update({"_id": "last_primary"}, {"$set": last_primary_server_record}, upsert=True) + else: + db.last_primary_server.update_one({"_id": "last_primary"}, {"$set": last_primary_server_record}, upsert=True) message = "Primary server has changed from %s to %s" % (saved_primary, current_primary) primary_status = 1 return check_levels(primary_status, warning, critical, message) @@ -1251,9 +1466,9 @@ def check_page_faults(con, sample_time, warning, critical, perf_data): try: #on linux servers only - page_faults = (int(data2['extra_info']['page_faults']) - int(data1['extra_info']['page_faults'])) / sample_time + page_faults = (int(data2['extra_info']['page_faults']) - int(data1['extra_info']['page_faults'])) // sample_time except KeyError: - print "WARNING - Can't get extra_info.page_faults counter from MongoDB" + print("WARNING - Can't get extra_info.page_faults counter from MongoDB") sys.exit(1) message = "Page Faults: %i" % (page_faults) @@ -1261,7 +1476,7 @@ def check_page_faults(con, sample_time, warning, critical, perf_data): message += performance_data(perf_data, [(page_faults, "page_faults", warning, critical)]) check_levels(page_faults, warning, critical, message) - except Exception, e: + except Exception as e: exit_with_general_critical(e) @@ -1277,35 +1492,35 @@ def chunks_balance(con, database, collection, warning, critical): shards = col.distinct("shard") except: - print "WARNING - Can't get chunks infos from MongoDB" + print("WARNING - Can't get chunks infos from MongoDB") sys.exit(1) if nscount == 0: - print "WARNING - Namespace %s is not sharded" % (nsfilter) + print("WARNING - Namespace %s is not sharded" % (nsfilter)) sys.exit(1) - avgchunksnb = nscount / len(shards) - warningnb = avgchunksnb * warning / 100 - criticalnb = avgchunksnb * critical / 100 + avgchunksnb = nscount // len(shards) + warningnb = avgchunksnb * warning // 100 + criticalnb = avgchunksnb * critical // 100 for shard in shards: delta = abs(avgchunksnb - col.find({"ns": nsfilter, "shard": shard}).count()) message = "Namespace: %s, Shard name: %s, Chunk delta: %i" % (nsfilter, shard, delta) if delta >= criticalnb and delta > 0: - print "CRITICAL - Chunks not well balanced " + message + print("CRITICAL - Chunks not well balanced " + message) sys.exit(2) elif delta >= warningnb and delta > 0: - print "WARNING - Chunks not well balanced " + message + print("WARNING - Chunks not well balanced " + message) sys.exit(1) - print "OK - Chunks well balanced across shards" + print("OK - Chunks well balanced across shards") sys.exit(0) - except Exception, e: + except Exception as e: exit_with_general_critical(e) - print "OK - Chunks well balanced across shards" + print("OK - Chunks well balanced across shards") sys.exit(0) @@ -1321,7 +1536,7 @@ def check_connect_primary(con, warning, critical, perf_data): data = con.admin.command(son.SON([('isMaster', 1)])) if data['ismaster'] == True: - print "OK - This server is primary" + print("OK - This server is primary") return 0 phost = data['primary'].split(':')[0] @@ -1339,17 +1554,17 @@ def check_connect_primary(con, warning, critical, perf_data): return check_levels(pconn_time, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) def check_collection_state(con, database, collection): try: con[database][collection].find_one() - print "OK - Collection %s.%s is reachable " % (database, collection) + print("OK - Collection %s.%s is reachable " % (database, collection)) return 0 - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -1361,14 +1576,18 @@ def check_row_count(con, database, collection, warning, critical, perf_data): return check_levels(count, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) -def build_file_name(host, action): +def build_file_name(host, port, action): #done this way so it will work when run independently and from shell module_name = re.match('(.*//*)*(.*)\..*', __file__).group(2) - return "/tmp/" + module_name + "_data/" + host + "-" + action + ".data" + + if (port == 27017): + return "/tmp/" + module_name + "_data/" + host + "-" + action + ".data" + else: + return "/tmp/" + module_name + "_data/" + host + "-" + str(port) + "-" + action + ".data" def ensure_dir(f): @@ -1381,7 +1600,7 @@ def write_values(file_name, string): f = None try: f = open(file_name, 'w') - except IOError, e: + except IOError as e: #try creating if (e.errno == 2): ensure_dir(file_name) @@ -1400,11 +1619,11 @@ def read_values(file_name): data = f.read() f.close() return 0, data - except IOError, e: + except IOError as e: if (e.errno == 2): #no previous data return 1, '' - except Exception, e: + except Exception as e: return 2, None @@ -1420,8 +1639,8 @@ def calc_delta(old, new): return 0, delta -def maintain_delta(new_vals, host, action): - file_name = build_file_name(host, action) +def maintain_delta(new_vals, host, port, action): + file_name = build_file_name(host, port, action) err, data = read_values(file_name) old_vals = data.split(';') new_vals = [str(int(time.time()))] + new_vals @@ -1442,8 +1661,8 @@ def replication_get_time_diff(con): col = 'oplog.$main' firstc = local[col].find().sort("$natural", 1).limit(1) lastc = local[col].find().sort("$natural", -1).limit(1) - first = firstc.next() - last = lastc.next() + first = next(firstc) + last = next(lastc) tfirst = first["ts"] tlast = last["ts"] delta = tlast.time - tfirst.time diff --git a/nagios-nrpe/files/plugins/check_ssl_local b/nagios-nrpe/files/plugins/check_ssl_local new file mode 100755 index 00000000..860ed676 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_ssl_local @@ -0,0 +1,69 @@ +#!/bin/bash + +# Check permettant de monitorer une liste de certificats se trouvant dans +# /etc/nagios/ssl_local.cfg +# +# Développé par Will (2022) +# + +certs_list_path="/etc/nagios/check_ssl_local_list.cfg" + +# Dates in seconds +_10_days="864000" +_15_days="1296000" + +critical=0 +warning=0 + + +if [[ ! -f "$certs_list_path" ]]; then + touch "$certs_list_path" +fi + +certs_list=$(cat "$certs_list_path" | sed -E 's/(.*)#.*/\1/g' | grep -v -E '^$') + +for cert_path in $certs_list; do + + if [ ! -f "$cert_path" ] && [ ! -d "$cert_path" ]; then + >&2 echo "Warning: path '$cert_path' is not a file or a directory." + warning=1 + continue + fi + + enddate=$(openssl x509 -noout -enddate -in "$cert_path" | cut -d'=' -f2) + + # Check cert expiré (critique) + if ! openssl x509 -checkend 0 -in "$cert_path" &> /dev/null; then + critical=1 + >&2 echo "Critical: Cert '$cert_path' has expired on $enddate." + continue + fi + + # Check cert expire < 10 jours (critique) + if ! openssl x509 -checkend "$_10_days" -in "$cert_path" &> /dev/null; then + critical=1 + >&2 echo "Critical: Cert '$cert_path' will expire on $enddate." + continue + fi + + # Check cert expire < 15 jours (warning) + if ! openssl x509 -checkend "$_15_days" -in "$cert_path" &> /dev/null; then + warning=1 + >&2 echo "Warning: Cert '$cert_path' will expire on $enddate." + continue + fi + + # Cert expire > 15 jours (OK) + echo "Cert '$cert_path' OK." + +done + +if [ $critical -eq 1 ]; then + exit 2 +elif [ $warning -eq 1 ]; then + exit 1 +else + exit 0 +fi + + diff --git a/nagios-nrpe/templates/evolix.cfg.j2 b/nagios-nrpe/templates/evolix.cfg.j2 index b007b3a8..ae0e0abd 100644 --- a/nagios-nrpe/templates/evolix.cfg.j2 +++ b/nagios-nrpe/templates/evolix.cfg.j2 @@ -34,7 +34,7 @@ command[check_pop]=/usr/lib/nagios/plugins/check_pop -H localhost command[check_pops]=/usr/lib/nagios/plugins/check_pop -S -H localhost -p 995 command[check_ftp]=/usr/lib/nagios/plugins/check_ftp -H localhost command[check_http]=/usr/lib/nagios/plugins/check_http -e 301 -I 127.0.0.1 -H localhost -command[check_https]=/usr/lib/nagios/plugins/check_http -e 403 -I 127.0.0.1 -S -p 443 --sni -H ssl.evolix.net +command[check_https]=/usr/lib/nagios/plugins/check_http -e 401,403 -I 127.0.0.1 -S -p 443 --sni -H ssl.evolix.net command[check_bind]=/usr/lib/nagios/plugins/check_dig -l evolix.net -H localhost command[check_unbound]=/usr/lib/nagios/plugins/check_dig -l evolix.net -H localhost command[check_smb]=/usr/lib/nagios/plugins/check_tcp -H 127.0.0.1 -p 445 @@ -48,6 +48,7 @@ command[check_redis]=/usr/lib/nagios/plugins/check_tcp -p 6379 command[check_clamd]=/usr/lib/nagios/plugins/check_clamd -H /var/run/clamav/clamd.ctl -v command[check_clamav_db]=/usr/lib/nagios/plugins/check_file_age -w 86400 -c 172800 -f /var/lib/clamav/evolix.ndb command[check_ssl]=/usr/lib/nagios/plugins/check_http -f follow -I 127.0.0.1 -S -p 443 -H ssl.evolix.net -C 15,5 +command[check_ssl_local]={{ nagios_plugins_directory }}/check_ssl_local command[check_elasticsearch]=/usr/lib/nagios/plugins/check_http -I 127.0.0.1 -u /_cat/health?h=st -p 9200 -r 'red' --invert-regex command[check_memcached]=/usr/lib/nagios/plugins/check_tcp -H 127.0.0.1 -p 11211 command[check_opendkim]=/usr/lib/nagios/plugins/check_tcp -H 127.0.0.1 -p 54321 diff --git a/openvpn/tasks/debian.yml b/openvpn/tasks/debian.yml index 4c2f6c5d..55ca2f8e 100644 --- a/openvpn/tasks/debian.yml +++ b/openvpn/tasks/debian.yml @@ -12,6 +12,14 @@ - client - server +- name: Create the _openvpn user + user: + name: _openvpn + system: yes + create_home: no + home: "/nonexistent" + shell: "/usr/sbin/nologin" + - name: Create the shellpki user user: name: shellpki @@ -62,7 +70,9 @@ group: shellpki - name: Generate dhparam - command: "openssl dhparam -out /etc/shellpki/dh2048.pem 2048" + openssl_dhparam: + path: /etc/shellpki/dh2048.pem + size: 2048 - include_role: name: evolix/remount-usr @@ -239,7 +249,7 @@ - include_role: name: evolix/remount-usr -- name: Copy shellpki script +- name: Copy script to check expirations copy: src: "shellpki/cert-expirations.sh" dest: "/usr/share/scripts/cert-expirations.sh" @@ -253,15 +263,43 @@ special_time: monthly job: '/usr/share/scripts/cert-expirations.sh | mail -E -s "PKI VPN {{ ansible_hostname }} : recapitulatif expirations" {{ client_email }}' -- name: Warn the user about command to execute manually +- name: Generate the CA password + set_fact: + ca_pwd: "{{ lookup('password', '/dev/null length=25 chars=ascii_letters,digits') }}" + check_mode: no + changed_when: no + +- name: Initialization of the CA + shell: 'CA_PASSWORD="{{ ca_pwd }}" shellpki init --non-interactive {{ ansible_fqdn }}' + +- name: Creation of the server's certificate + shell: 'CA_PASSWORD="{{ ca_pwd }}" shellpki create --days 3650 --non-interactive {{ ansible_fqdn }}' + +- name: Get the server key + shell: 'ls -tr /etc/shellpki/private/ | tail -1' + register: ca_key + check_mode: no + changed_when: no + +- name: Configure the server key + replace: + path: /etc/openvpn/server.conf + regexp: 'key /etc/shellpki/private/TO_COMPLETE' + replace: 'key /etc/shellpki/private/{{ ca_key.stdout }}' + +- name: Restart OpenVPN + systemd: + name: "openvpn@server.service" + state: restarted + +- name: Warn the user about manual checks pause: prompt: | /!\ WARNING /!\ - You have to manually create the CA on the server with "shellpki init {{ ansible_fqdn }}". The command will ask you to create a password, and will ask you again to give the same one several times. - You have to manually generate the CRL on the server with "openssl ca -gencrl -keyfile /etc/shellpki/cakey.key -cert /etc/shellpki/cacert.pem -out /etc/shellpki/crl.pem -config /etc/shellpki/openssl.cnf". The previously created password will be asked. - You have to manually create the server's certificate with "shellpki create {{ ansible_fqdn }}". - You have to adjust the config file "/etc/openvpn/server.conf" for the following parameters : local (to check), cert (to check), key (to add), server (to check), push (to complete if needed). - Finally, you can (re)start the OpenVPN service with "systemctl restart openvpn@server.service". + You must check and adjust if necessary the configuration file "/etc/openvpn/server.conf", and then restart the OpenVPN service with "systemctl restart openvpn@server.service". + The "push" parameter may be needed to push a route to the client, so that the client can access that route through OpenVPN. + + Take note of the generated CA password and store it in your password manager : {{ ca_pwd }} Press enter to exit when it's done. diff --git a/openvpn/tasks/openbsd.yml b/openvpn/tasks/openbsd.yml index d3238cea..f5d9e4ff 100644 --- a/openvpn/tasks/openbsd.yml +++ b/openvpn/tasks/openbsd.yml @@ -56,7 +56,9 @@ group: _shellpki - name: Generate dhparam - command: "openssl dhparam -out /etc/shellpki/dh2048.pem 2048" + openssl_dhparam: + path: /etc/shellpki/dh2048.pem + size: 2048 - name: Fix CRL rights in shellpki command lineinfile: @@ -175,7 +177,7 @@ notify: restart nrpe when: nrpe_evolix_config.stat.exists -- name: Copy shellpki script +- name: Copy script to check expirations copy: src: "shellpki/cert-expirations.sh" dest: "/usr/share/scripts/cert-expirations.sh" @@ -189,15 +191,43 @@ special_time: monthly job: '/usr/share/scripts/cert-expirations.sh | mail -E -s "PKI VPN {{ ansible_hostname }} : recapitulatif expirations" {{ client_email }}' -- name: Warn the user about command to execute manually +- name: Generate the CA password + set_fact: + ca_pwd: "{{ lookup('password', '/dev/null length=25 chars=ascii_letters,digits') }}" + check_mode: no + changed_when: no + +- name: Initialization of the CA + shell: 'CA_PASSWORD="{{ ca_pwd }}" shellpki init --non-interactive {{ ansible_fqdn }}' + +- name: Creation of the server's certificate + shell: 'CA_PASSWORD="{{ ca_pwd }}" shellpki create --days 3650 --non-interactive {{ ansible_fqdn }}' + +- name: Get the server key + shell: 'ls -tr /etc/shellpki/private/ | tail -1' + register: ca_key + check_mode: no + changed_when: no + +- name: Configure the server key + replace: + path: /etc/openvpn/server.conf + regexp: 'key /etc/shellpki/private/TO_COMPLETE' + replace: 'key /etc/shellpki/private/{{ ca_key.stdout }}' + +- name: Restart OpenVPN + service: + name: openvpn + state: restarted + +- name: Warn the user about manual checks pause: prompt: | /!\ WARNING /!\ - You have to manually create the CA on the server with "shellpki init {{ ansible_fqdn }}". The command will ask you to create a password, and will ask you again to give the same one several times. - You have to manually generate the CRL on the server with "openssl ca -gencrl -keyfile /etc/shellpki/cakey.key -cert /etc/shellpki/cacert.pem -out /etc/shellpki/crl.pem -config /etc/shellpki/openssl.cnf". The previously created password will be asked. - You have to manually create the server's certificate with "shellpki create {{ ansible_fqdn }}". - You have to adjust the config file "/etc/openvpn/server.conf" for the following parameters : local (to check), cert (to check), key (to add), server (to check), push (to complete if needed). - Finally, you can (re)start the OpenVPN service with "rcctl restart openvpn". + You must check and adjust if necessary the configuration file "/etc/openvpn/server.conf", and then restart the OpenVPN service with "rcctl restart openvpn". + The "push" parameter may be needed to push a route to the client, so that the client can access that route through OpenVPN. + + Take note of the generated CA password and store it in your password manager : {{ ca_pwd }} Press enter to exit when it's done. diff --git a/openvpn/templates/server.conf.j2 b/openvpn/templates/server.conf.j2 index 23ce3e2b..a41b9b22 100644 --- a/openvpn/templates/server.conf.j2 +++ b/openvpn/templates/server.conf.j2 @@ -1,5 +1,5 @@ -user nobody -group nogroup +user _openvpn +group _openvpn local {{ ansible_default_ipv4.address }} port 1194 diff --git a/php/tasks/main_bullseye.yml b/php/tasks/main_bullseye.yml index a1f7d5f5..403a7b76 100644 --- a/php/tasks/main_bullseye.yml +++ b/php/tasks/main_bullseye.yml @@ -30,6 +30,7 @@ - php-sqlite3 - php-curl - php-ssh2 + - php-xml - php-zip - composer - libphp-phpmailer diff --git a/proftpd/README.md b/proftpd/README.md index dae8abef..6e96e05a 100644 --- a/proftpd/README.md +++ b/proftpd/README.md @@ -41,5 +41,5 @@ proftpd_accounts: For generate the sha512 version of yours password : ~~~ -echo "test" | mkpasswd --method=sha-512 - +printf "test" | mkpasswd --stdin --method=sha-512 ~~~ diff --git a/proftpd/defaults/main.yml b/proftpd/defaults/main.yml index 80edecd2..25d60d5b 100644 --- a/proftpd/defaults/main.yml +++ b/proftpd/defaults/main.yml @@ -3,12 +3,16 @@ proftpd_hostname: "{{ ansible_hostname }}" proftpd_fqdn: "{{ ansible_fqdn }}" proftpd_default_address: [] proftpd_ftp_enable: True +proftpd_ftp_override: False proftpd_port: 21 proftpd_ftps_enable: False +proftpd_ftps_override: False proftpd_ftps_port: 990 proftpd_ftps_cert: "/etc/ssl/certs/ssl-cert-snakeoil.pem" proftpd_ftps_key: "/etc/ssl/private/ssl-cert-snakeoil.key" proftpd_sftp_enable: False +proftpd_sftp_override: False +proftpd_sftp_use_publickeys: False proftpd_sftp_port: 22222 proftpd_accounts: [] proftpd_accounts_final: [] diff --git a/proftpd/tasks/accounts.yml b/proftpd/tasks/accounts.yml index 756e0ff0..0ff57272 100644 --- a/proftpd/tasks/accounts.yml +++ b/proftpd/tasks/accounts.yml @@ -60,3 +60,18 @@ when: proftpd_sftp_enable | bool tags: - proftpd + +- name: Allow keys for SFTP account + blockinfile: + dest: "/etc/proftpd/sftp.authorized_keys/{{ item.name }}" + state: present + block: "{{ item.sshkeys }}" + create: yes + mode: 0600 + loop: "{{ proftpd_accounts_final }}" + notify: restart proftpd + when: + - proftpd_sftp_enable | bool + - proftpd_sftp_use_publickeys | bool + tags: + - proftpd diff --git a/proftpd/tasks/main.yml b/proftpd/tasks/main.yml index 457887a1..9ddb6273 100644 --- a/proftpd/tasks/main.yml +++ b/proftpd/tasks/main.yml @@ -20,7 +20,7 @@ src: evolinux.conf.j2 dest: /etc/proftpd/conf.d/z-evolinux.conf mode: "0644" - force: no + force: "{{ proftpd_ftp_override }}" notify: restart proftpd when: proftpd_ftp_enable | bool tags: @@ -31,7 +31,7 @@ src: ftps.conf.j2 dest: /etc/proftpd/conf.d/ftps.conf mode: "0644" - force: no + force: "{{ proftpd_ftps_override }}" notify: restart proftpd when: proftpd_ftps_enable | bool tags: @@ -42,12 +42,26 @@ src: sftp.conf.j2 dest: /etc/proftpd/conf.d/sftp.conf mode: "0644" - force: no + force: "{{ proftpd_sftp_override }}" notify: restart proftpd when: proftpd_sftp_enable | bool tags: - proftpd +- name: SFTP key folder exists if needed + file: + path: /etc/proftpd/sftp.authorized_keys/ + state: directory + mode: "0700" + owner: root + group: root + notify: restart proftpd + when: + - proftpd_sftp_enable | bool + - proftpd_sftp_use_publickeys | bool + tags: + - proftpd + - name: mod_tls_memcache is disabled replace: dest: /etc/proftpd/modules.conf diff --git a/proftpd/templates/evolinux.conf.j2 b/proftpd/templates/evolinux.conf.j2 index 8a810a99..8ad06927 100644 --- a/proftpd/templates/evolinux.conf.j2 +++ b/proftpd/templates/evolinux.conf.j2 @@ -1,5 +1,13 @@ # Evolix's specific configuration +{% if proftpd_ftp_override %} +# WARNING : **Probably** ansible managed +{% endif %} + + + LoadModule mod_ident.c + + ServerName "{{ proftpd_hostname }} FTP Server" ServerIdent on "FTP Server Ready" AccessGrantMsg "Hey, bienvenue %u sur le serveur FTP {{ proftpd_fqdn }} !" diff --git a/proftpd/templates/ftps.conf.j2 b/proftpd/templates/ftps.conf.j2 index 33a2cff3..f9826989 100644 --- a/proftpd/templates/ftps.conf.j2 +++ b/proftpd/templates/ftps.conf.j2 @@ -1,8 +1,12 @@ +{% if proftpd_ftps_override %} +# WARNING : **Probably** ansible managed +{% endif %} + LoadModule mod_tls.c - + TLSEngine on TLSLog /var/log/proftpd/ftps.log TLSProtocol TLSv1 diff --git a/proftpd/templates/sftp.conf.j2 b/proftpd/templates/sftp.conf.j2 index 9a96e5ef..457f638b 100644 --- a/proftpd/templates/sftp.conf.j2 +++ b/proftpd/templates/sftp.conf.j2 @@ -1,3 +1,7 @@ +{% if proftpd_sftp_override %} +# WARNING : **Probably** ansible managed +{% endif %} + LoadModule mod_tls.c @@ -6,15 +10,21 @@ LoadModule mod_sftp.c - + SFTPEngine on Port {{ proftpd_sftp_port }} DefaultRoot ~ SFTPLog /var/log/proftpd/sftp.log TransferLog /var/log/proftpd/xferlog - + +{% if proftpd_sftp_use_publickeys %} + SFTPAuthMethods publickey password + SFTPAuthorizedUserKeys file:/etc/proftpd/sftp.authorized_keys/%u +{% else %} SFTPAuthMethods password +{% endif %} + SFTPHostKey /etc/ssh/ssh_host_ecdsa_key SFTPHostKey /etc/ssh/ssh_host_rsa_key diff --git a/rabbitmq/files/check_rabbitmq.python3 b/rabbitmq/files/check_rabbitmq.python3 new file mode 100644 index 00000000..0a941dd4 --- /dev/null +++ b/rabbitmq/files/check_rabbitmq.python3 @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +from optparse import OptionParser +import shlex +import subprocess +import sys +import requests +import json + +if "check_output" not in dir( subprocess ): # duck punch it in! + def f(*popenargs, **kwargs): + if 'stdout' in kwargs: + raise ValueError('stdout argument not allowed, it will be overridden.') + process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + raise subprocess.CalledProcessError(retcode, cmd) + return output + subprocess.check_output = f + + +class RabbitCmdWrapper(object): + """So basically this just runs rabbitmqctl commands and returns parsed output. + Typically this means you need root privs for this to work. + Made this it's own class so it could be used in other monitoring tools + if desired.""" + + @classmethod + def list_connections(cls): + args = shlex.split("sudo rabbitmqctl list_connections") + cmd_result = subprocess.check_output(args, text=True).strip() + results = cls._parse_list_results(cmd_result) + return results + + @classmethod + def list_queues(cls): + args = shlex.split('sudo rabbitmqctl list_queues') + cmd_result = subprocess.check_output(args, text=True).strip() + results = cls._parse_list_results(cmd_result) + return results + + @classmethod + def status(cls): + args = shlex.split('sudo rabbitmqctl status') + cmd_result = subprocess.check_output(args, text=True).strip() + results = cls._parse_list_results(cmd_result) + return results + + @classmethod + def _parse_list_results(cls, result_string): + results = result_string.strip().split('\n') + #remove text fluff + if "Listing connections ..." in results: results.remove("Listing connections ...") + if "Listing queues ..." in results: results.remove("Listing queues ...") + return_data = [] + for row in results: + return_data.append(row.split('\t')) + return return_data + + +def check_connection_count(critical=0, warning=0): + """Checks to make sure the numbers of connections are within parameters.""" + try: + count = len(RabbitCmdWrapper.list_connections()) + if count >= critical: + print("CRITICAL - Connection Count %d" % count) + sys.exit(2) + elif count >= warning: + print("WARNING - Connection Count %d" % count) + sys.exit(1) + else: + print("OK - Connection Count %d" % count) + except Exception as err: + print("CRITICAL - %s" % err) + + +def check_queues_count(critical=1000, warning=1000): + """ + A blanket check to make sure all queues are within count parameters. + TODO: Possibly break this out so test can be done on individual queues. + """ + try: + critical_q = [] + warning_q = [] + results = RabbitCmdWrapper.list_queues() + for queue in results: + if queue.count == 2: + count = int(queue[1]) + if count >= critical: + critical_q.append("%s: %s" % (queue[0], count)) + elif count >= warning: + warning_q.append("%s: %s" % (queue[0], count)) + if critical_q: + print("CRITICAL - %s" % ", ".join(critical_q)) + sys.exit(2) + elif warning_q: + print("WARNING - %s" % ", ".join(warning_q)) + sys.exit(1) + else: + print("OK - NO QUEUES EXCEED THRESHOLDS") + sys.exit(0) + except Exception as err: + print("CRITICAL - %s" % err) + sys.exit(2) + +def check_mem_usage(critical=75, warning=50): + """Check to make sure the RAM usage of rabbitmq process does not exceed 50%% of its max""" + try: + results = RabbitCmdWrapper.status() + + for idx,val in enumerate(results): + if "memory," in str(val): + mem_used_raw = str(results[idx + 1]) + if "vm_memory_limit" in str(val): + mem_limit_raw = str(val) + + memory_used = float(filter(str.isdigit, mem_used_raw)) + memory_limit = float(filter(str.isdigit, mem_limit_raw)) + percent_usage = int(memory_used/memory_limit * 100) + + if percent_usage > critical: + print("CRITICAL - RABBITMQ RAM USAGE at %s%% of max" % percent_usage) + sys.exit(2) + elif percent_usage > warning: + print("WARNING - RABBITMQ RAM USAGE at %s%% of max" % percent_usage) + sys.exit(1) + else: + print("OK - RABBITMQ RAM USAGE OK at %s%% of max" % percent_usage) + sys.exit(0) + except Exception as err: + print("Critical - %s" % err) + sys.exit(2) + +def check_aliveness(username, password, timeout, cluster): + """Declares a test queue, then publishes and consumes a message. Intended for use by monitoring tools. If everything is working correctly, will return HTTP status 200 with body""" + try: + r = requests.get("http://%s:15672/api/aliveness-test/%%2F" % cluster, auth=(username, password), timeout=timeout) + except requests.exceptions.RequestException as e: # Throw error if rabbitmq is down + print("Critical - %s" % e) + sys.exit(2) + if r.status_code == 200: + print("OK - RABBITMQ Aliveness Test Returns: %s" % r) + sys.exit(0) + elif r.status_code != 200: + print("CRITICAL - RabbitMQ Error: %s" % r.content) + sys.exit(2) + else: + print("UNKNOWN - RABBITMQ Aliveness Test") + sys.ext(1) + +def check_cluster(username, password, timeout, cluster): + """Checks the health of a cluster, if a node is not running mark as offline """ + try: + url = "http://%s:15672/api/nodes" % cluster + r = requests.get(url, auth=(username, password), timeout=timeout) + except requests.exceptions.RequestException as e: # Throw error if no response + print("Critical - %s" % e) + sys.exit(2) + text = r.text + nodes = json.loads(text) + + running_nodes = [] + failed_nodes = [] + for node in nodes: + if not node['running']: + failed_nodes.append(node['name']) + if node['running']: + running_nodes.append(node['name']) + if len(failed_nodes) == 1: + print("WARNING: RabbitMQ cluster is degraged: Not running %s" % failed_nodes[0]) + sys.exit(1) + elif len(failed_nodes) >= 2: + print("CRITICAL: RabbitMQ cluster is critical: Not running %s" % failed_nodes) + sys.exit(2) + else: + print("OK: RabbitMQ cluster members: %s" % (" ".join(running_nodes))) + sys.exit(0) + + +USAGE = """Usage: ./check_rabbitmq -a [action] -C [critical] -W [warning] + Actions: + - connection_count + checks the number of connection in rabbitmq's list_connections + - queues_count + checks the count in each of the queues in rabbitmq's list_queues + - mem_usage + checks to ensure mem usage of rabbitmq process does not exceed 50% + - aliveness + Use the /api/aliveness-test API to send/receive a message. (requires -u username -p password args) + - cluster_status + Parse /api/nodes to check the cluster status. (requires -u username -p password""" + +if __name__ == "__main__": + parser = OptionParser(USAGE) + parser.add_option("-a", "--action", dest="action", + help="Action to Check") + parser.add_option("-C", "--critical", dest="critical", + type="int", help="Critical Threshold") + parser.add_option("-W", "--warning", dest="warning", + type="int", help="Warning Threshold") + parser.add_option("-u", "--username", dest="username", default="guest", + type="string", help="RabbitMQ username, Default guest") + parser.add_option("-p", "--password", dest="password", default="guest", + type="string", help="RabbitMQ password, Default guest") + parser.add_option("-t", "--timeout", dest="timeout", default=1, + type="int", help="Request Timeout, defaults to 1 second") + parser.add_option("-c", "--cluster", dest="cluster", default="localhost", + type="string", help="Cluster IP/DNS name, defaults to localhost") + (options, args) = parser.parse_args() + + if options.action == "connection_count": + check_connection_count(options.critical, options.warning) + elif options.action == "queues_count": + check_queues_count(options.critical, options.warning) + elif options.action == "mem_usage": + check_mem_usage(options.critical, options.warning) + elif options.action == "aliveness": + check_aliveness(options.username, options.password, options.timeout, options.cluster) + elif options.action == "cluster_status": + check_cluster(options.username, options.password, options.timeout, options.cluster) + else: + print("Invalid action: %s" % options.action) + print(USAGE) diff --git a/rabbitmq/tasks/nrpe.yml b/rabbitmq/tasks/nrpe.yml index 4272f57b..ba6b8d47 100644 --- a/rabbitmq/tasks/nrpe.yml +++ b/rabbitmq/tasks/nrpe.yml @@ -24,6 +24,17 @@ group: root mode: "0755" force: yes + when: ansible_distribution_major_version is version('11', '<=') + +- name: check_rabbitmq (Python 3 version) is installed + copy: + src: check_rabbitmq.python3 + dest: /usr/local/lib/nagios/plugins/check_rabbitmq + owner: root + group: root + mode: "0755" + force: yes + when: ansible_distribution_major_version is version('11', '==') - name: check_rabbitmq is available for NRPE lineinfile: diff --git a/redis/tasks/default-server.yml b/redis/tasks/default-server.yml index 08653cfa..10b4d382 100644 --- a/redis/tasks/default-server.yml +++ b/redis/tasks/default-server.yml @@ -11,6 +11,15 @@ tags: - redis +- name: Config directory permissions are set + file: + dest: "{{ redis_conf_dir }}" + mode: "0750" + owner: redis + group: redis + tags: + - redis + - name: Redis is running and enabled on boot. systemd: name: "{{ redis_systemd_name }}" diff --git a/redis/tasks/instance-server.yml b/redis/tasks/instance-server.yml index 3e6af623..3f70733e 100644 --- a/redis/tasks/instance-server.yml +++ b/redis/tasks/instance-server.yml @@ -28,9 +28,9 @@ - name: "Instance '{{ redis_instance_name }}' config directory is present" file: dest: "{{ redis_conf_dir }}" - mode: "0755" - owner: "root" - group: "root" + mode: "0750" + owner: "redis-{{ redis_instance_name }}" + group: "redis-{{ redis_instance_name }}" follow: yes state: directory tags: @@ -39,9 +39,9 @@ - name: "Instance '{{ redis_instance_name }}' config hooks directories are present" file: dest: "{{ _dir }}" - mode: "0755" - owner: "root" - group: "root" + mode: "0750" + owner: "redis-{{ redis_instance_name }}" + group: "redis-{{ redis_instance_name }}" follow: yes state: directory loop: diff --git a/varnish/templates/varnish.conf.jessie.j2 b/varnish/templates/varnish.conf.jessie.j2 index f340323d..c3653708 100644 --- a/varnish/templates/varnish.conf.jessie.j2 +++ b/varnish/templates/varnish.conf.jessie.j2 @@ -2,6 +2,6 @@ [Service] ExecStart= -ExecStart=/usr/sbin/varnishd -F -j {{ varnish_jail }} {{ varnish_addresses | map('regex_replace', '^(.*)$', '-a \\1') | list | join(' ') }} -T {{ varnish_management_address }} -f {{ varnish_config_file }} -S {{ varnish_secret_file }} -s {{ varnish_storage }} -p thread_pools={{ varnish_thread_pools }} -p thread_pool_add_delay={{ varnish_thread_pool_add_delay }} -p thread_pool_min={{ varnish_thread_pool_min }} -p thread_pool_max={{ varnish_thread_pool_max }} +ExecStart=/usr/sbin/varnishd -j {{ varnish_jail }} -F {{ varnish_addresses | map('regex_replace', '^(.*)$', '-a \\1') | list | join(' ') }} -T {{ varnish_management_address }} -f {{ varnish_config_file }} -S {{ varnish_secret_file }} -s {{ varnish_storage }} -p thread_pools={{ varnish_thread_pools }} -p thread_pool_add_delay={{ varnish_thread_pool_add_delay }} -p thread_pool_min={{ varnish_thread_pool_min }} -p thread_pool_max={{ varnish_thread_pool_max }} ExecReload= ExecReload=/etc/varnish/reload-vcl.sh diff --git a/vrrpd/defaults/main.yml b/vrrpd/defaults/main.yml new file mode 100644 index 00000000..f5950a14 --- /dev/null +++ b/vrrpd/defaults/main.yml @@ -0,0 +1,13 @@ +--- + +vrrp_addresses: [] +# - { +# interface: Null # the interface name to run on +# delay: 10 # the advertisement interval (in sec) (default: 1) +# id: Null # the id of the virtual server [1-255] +# priority: Null # the priority of this host in the virtual server (default: 100) +# authentication: Null # authentification type: auth=(none|pw/hexkey|ah/hexkey) hexkey=0x[0-9a-fA-F]+ +# label: Null # use this name is syslog messages (helps when several vrid are running) +# ip: Null # the ip address(es) (and optionnaly subnet mask) of the virtual server +# state: Null # 'started' or 'stopped' +# } \ No newline at end of file diff --git a/vrrpd/tasks/ip.yml b/vrrpd/tasks/ip.yml new file mode 100644 index 00000000..59594395 --- /dev/null +++ b/vrrpd/tasks/ip.yml @@ -0,0 +1,20 @@ +--- + +- name: set unit name + set_fact: + vrrp_systemd_unit_name: "vrrp-{{ vrrp_address.id }}.service" + +- name: add systemd unit + template: + src: vrrp.service.j2 + dest: "/etc/systemd/system/{{ vrrp_systemd_unit_name }}" + force: yes + register: vrrp_systemd_unit + +- name: enable and start systemd unit + systemd: + name: "{{ vrrp_systemd_unit_name }}" + daemon_reload: yes + enabled: yes + state: "{{ vrrp_address.state }}" + when: vrrp_systemd_unit is changed \ No newline at end of file diff --git a/vrrpd/tasks/main.yml b/vrrpd/tasks/main.yml index 5804cb39..44ebe65a 100644 --- a/vrrpd/tasks/main.yml +++ b/vrrpd/tasks/main.yml @@ -14,7 +14,36 @@ tags: - vrrpd -- name: Adjust sysctl config +- name: Adjust sysctl config (except rp_filter) + sysctl: + name: "{{ item.name }}" + value: "{{ item.value }}" + sysctl_file: /etc/sysctl.d/vrrpd.conf + sysctl_set: yes + state: present + loop: + - { name: 'net.ipv4.conf.all.arp_ignore', value: 1 } + - { name: 'net.ipv4.conf.all.arp_announce', value: 2 } + - { name: 'net.ipv4.ip_nonlocal_bind', value: 1 } + tags: + - vrrpd + +- name: look if rp_filter is managed by minifirewall + command: grep "SYSCTL_RP_FILTER=" /etc/default/minifirewall + failed_when: False + changed_when: False + check_mode: no + register: grep_sysctl_rp_filter_minifirewall + +- name: Configure SYSCTL_RP_FILTER in minifirewall + lineinfile: + dest: "/etc/default/minifirewall" + line: "SYSCTL_RP_FILTER='0'" + regexp: "SYSCTL_RP_FILTER=('|\").*('|\")" + create: no + when: grep_sysctl_rp_filter_minifirewall.rc == 0 + +- name: Adjust sysctl config (only rp_filter) sysctl: name: "{{ item.name }}" value: "{{ item.value }}" @@ -23,10 +52,13 @@ state: present loop: - { name: 'net.ipv4.conf.default.rp_filter', value: 0 } - - { name: 'net.ipv4.conf.eth0.rp_filter', value: 0 } - { name: 'net.ipv4.conf.all.rp_filter', value: 0 } - - { name: 'net.ipv4.conf.all.arp_ignore', value: 1 } - - { name: 'net.ipv4.conf.all.arp_announce', value: 2 } - - { name: 'net.ipv4.ip_nonlocal_bind', value: 1 } + when: grep_sysctl_rp_filter_minifirewall.rc != 0 tags: - vrrpd + +- name: Create VRRP address + include: ip.yml + loop: "{{ vrrp_addresses }}" + loop_control: + loop_var: "vrrp_address" \ No newline at end of file diff --git a/vrrpd/templates/vrrp.service.j2 b/vrrpd/templates/vrrp.service.j2 new file mode 100644 index 00000000..4db5d7a9 --- /dev/null +++ b/vrrpd/templates/vrrp.service.j2 @@ -0,0 +1,15 @@ +[Unit] +Description=VRRP Daemon for IP {{ vrrp_address.ip }} on {{ vrrp_address.interface }} +After=network.target + +[Service] +ExecStart=/usr/sbin/vrrpd -i {{ vrrp_address.interface | mandatory }} -x -D -d {{ vrrp_address.delay | mandatory }} -v {{ vrrp_address.id | mandatory }} -p {{ vrrp_address.priority | mandatory }} -a {{ vrrp_address.authentication | mandatory }} -l {{ vrrp_address.label | mandatory }} {{ vrrp_address.ip | mandatory }} +# PIDFile=/var/run/vrrpd_{{ vrrp_address.label }}_{{ vrrp_address.id }}.pid +Restart=on-failure +Type=forking +IgnoreSIGPIPE=no +KillMode=process +RemainAfterExit=yes + +[Install] +WantedBy=default.target \ No newline at end of file diff --git a/webapps/nextcloud/defaults/main.yml b/webapps/nextcloud/defaults/main.yml index 3c1bf40a..5c586620 100644 --- a/webapps/nextcloud/defaults/main.yml +++ b/webapps/nextcloud/defaults/main.yml @@ -1,7 +1,6 @@ --- -nextcloud_webserver: 'nginx' -nextcloud_version: "21.0.0" -nextcloud_archive_name: "nextcloud-{{ nextcloud_version }}.tar.bz2" +nextcloud_version: "latest-24" +nextcloud_archive_name: "{{ nextcloud_version }}.tar.bz2" nextcloud_releases_baseurl: "https://download.nextcloud.com/server/releases/" nextcloud_instance_name: "nextcloud" diff --git a/webapps/nextcloud/handlers/main.yml b/webapps/nextcloud/handlers/main.yml index 2db4770d..46b3b014 100644 --- a/webapps/nextcloud/handlers/main.yml +++ b/webapps/nextcloud/handlers/main.yml @@ -8,3 +8,8 @@ service: name: nginx state: reloaded + +- name: reload apache + service: + name: apache2 + state: reloaded \ No newline at end of file diff --git a/webapps/nextcloud/meta/main.yml b/webapps/nextcloud/meta/main.yml index d5852e32..ed97d539 100644 --- a/webapps/nextcloud/meta/main.yml +++ b/webapps/nextcloud/meta/main.yml @@ -1,4 +1 @@ --- -# dependencies: - # - { role: nginx, when: nextcloud_webserver == 'nginx' } - # - { role: php, php_fpm_enable: True } diff --git a/webapps/nextcloud/tasks/apache-system.yml b/webapps/nextcloud/tasks/apache-system.yml new file mode 100644 index 00000000..490d2f8d --- /dev/null +++ b/webapps/nextcloud/tasks/apache-system.yml @@ -0,0 +1,33 @@ +--- + +- name: "Get PHP Version" + shell: 'php -v | grep "PHP [0-9]." | sed -E "s/PHP ([0-9]\.[0-9]).*/\1/g;"' + register: shell_php + check_mode: no + +- name: "Set variables" + set_fact: + php_version: "{{ shell_php.stdout }}" + +- name: Apply specific PHP settings (apache) + ini_file: + path: "/etc/php/{{ php_version }}/apache2/conf.d/zzz-evolinux-custom.ini" + section: '' + option: "{{ item.option }}" + value: "{{ item.value }}" + notify: reload apache + with_items: + - {option: 'allow_url_fopen', value: 'On'} + - {option: 'disable_functions', value: ''} + - {option: 'max_execution_time', value: '300'} + - {option: 'memory_limit', value: '512M'} + +- name: Apply specific PHP settings (cli) + ini_file: + path: "/etc/php/{{ php_version }}/cli/conf.d/zzz-evolinux-custom.ini" + section: '' + option: "{{ item.option }}" + value: "{{ item.value }}" + with_items: + - {option: 'allow_url_fopen', value: 'On'} + - {option: 'apc.enable_cli', value: 'On'} diff --git a/webapps/nextcloud/tasks/apache-vhost.yml b/webapps/nextcloud/tasks/apache-vhost.yml new file mode 100644 index 00000000..e3f213ca --- /dev/null +++ b/webapps/nextcloud/tasks/apache-vhost.yml @@ -0,0 +1,23 @@ +--- +- name: Copy Apache vhost + template: + src: apache-vhost.conf.j2 + dest: "/etc/apache2/sites-available/{{ nextcloud_instance_name }}.conf" + mode: "0640" + notify: reload apache + tags: + - nextcloud + +- name: Enable Apache vhost + file: + src: "/etc/apache2/sites-available/{{ nextcloud_instance_name }}.conf" + dest: "/etc/apache2/sites-enabled/{{ nextcloud_instance_name }}.conf" + state: link + notify: reload apache + tags: + - nextcloud + +# - name: Generate ssl config +# shell: +# cmd: "/usr/local/sbin/vhost-domains {{ nextcloud_instance_name }} | /usr/local/sbin/make-csr {{ nextcloud_instance_name }}" +# creates: "/etc/nginx/ssl/{{ nextcloud_instance_name }}.conf" \ No newline at end of file diff --git a/webapps/nextcloud/tasks/main.yml b/webapps/nextcloud/tasks/main.yml index a6d39b4b..2823f8f5 100644 --- a/webapps/nextcloud/tasks/main.yml +++ b/webapps/nextcloud/tasks/main.yml @@ -16,10 +16,12 @@ - php-apcu - php-redis - php-bcmath + - php-imagick + - libmagickcore-6.q16-6-extra tags: - nextcloud -# dependency for mysql_user and mysql_db +# dependency for mysql_user and mysql_db - python2 - name: python modules is installed (Ansible dependency) apt: name: @@ -30,7 +32,7 @@ - nextcloud when: ansible_python_version is version('3', '<') -# dependency for mysql_user and mysql_db +# dependency for mysql_user and mysql_db - python3 - name: python3 modules is installed (Ansible dependency) apt: name: @@ -41,12 +43,14 @@ - nextcloud when: ansible_python_version is version('3', '>=') +- include: apache-system.yml + - include: user.yml - include: archive.yml -- include: vhost.yml +- include: apache-vhost.yml -- include: mysql.yml +- include: mysql-user.yml - include: config.yml diff --git a/webapps/nextcloud/tasks/mysql.yml b/webapps/nextcloud/tasks/mysql-user.yml similarity index 100% rename from webapps/nextcloud/tasks/mysql.yml rename to webapps/nextcloud/tasks/mysql-user.yml diff --git a/webapps/nextcloud/tasks/user.yml b/webapps/nextcloud/tasks/user.yml index e89fe41a..8fa3fee1 100644 --- a/webapps/nextcloud/tasks/user.yml +++ b/webapps/nextcloud/tasks/user.yml @@ -1,15 +1,15 @@ --- -- name: Create Nextcloud group +- name: Create {{ nextcloud_user }} unix group group: - name: "{{ nextcloud_instance_name | mandatory }}" + name: "{{ nextcloud_user | mandatory }}" state: present tags: - nextcloud -- name: Create Nextcloud user +- name: Create {{ nextcloud_user | mandatory }} unix user user: name: "{{ nextcloud_user | mandatory }}" - group: "{{ nextcloud_user }}" + group: "{{ nextcloud_user | mandatory }}" home: "{{ nextcloud_home | mandatory }}" shell: '/bin/bash' create_home: True @@ -18,17 +18,11 @@ tags: - nextcloud -- name: Add the user 'www-data' to Nextcloud group - user: - name: www-data - groups: "{{ nextcloud_user | mandatory }}" - append: yes - - name: Create top-level directories file: dest: "{{ item }}" state: directory - mode: "0770" + mode: "0700" owner: "{{ nextcloud_user }}" group: "{{ nextcloud_user }}" loop: diff --git a/webapps/nextcloud/tasks/vhost.yml b/webapps/nextcloud/tasks/vhost.yml deleted file mode 100644 index 1f1592cc..00000000 --- a/webapps/nextcloud/tasks/vhost.yml +++ /dev/null @@ -1,34 +0,0 @@ ---- -- block: - - name: Copy Nginx vhost - template: - src: nginx.conf.j2 - dest: "/etc/nginx/sites-available/{{ nextcloud_instance_name }}.conf" - mode: "0640" - notify: reload nginx - tags: - - nextcloud - - - name: Enable Nginx vhost - file: - src: "/etc/nginx/sites-available/{{ nextcloud_instance_name }}.conf" - dest: "/etc/nginx/sites-enabled/{{ nextcloud_instance_name }}.conf" - state: link - notify: reload nginx - tags: - - nextcloud - - - name: Generate ssl config - shell: - cmd: "/usr/local/sbin/vhost-domains {{ nextcloud_instance_name }} | /usr/local/sbin/make-csr {{ nextcloud_instance_name }}" - creates: "/etc/nginx/ssl/{{ nextcloud_instance_name }}.conf" - - - name: Copy PHP-FPM pool - template: - src: php-fpm.conf.j2 - dest: "/etc/php/7.3/fpm/pool.d/{{ nextcloud_instance_name }}.conf" - mode: "0640" - notify: reload php-fpm - tags: - - nextcloud - when: nextcloud_webserver == 'nginx' diff --git a/webapps/nextcloud/templates/apache-vhost.conf.j2 b/webapps/nextcloud/templates/apache-vhost.conf.j2 new file mode 100644 index 00000000..ff9f621c --- /dev/null +++ b/webapps/nextcloud/templates/apache-vhost.conf.j2 @@ -0,0 +1,41 @@ + + ServerName {{ nextcloud_domains[0] }} + + {% for domain_alias in nextcloud_domains[1:] %} + ServerAlias {{ domain_alias }} + {% endfor %} + + # SSLEngine on + # SSLCertificateFile /etc/letsencrypt/live/{{ nextcloud_instance_name }}/fullchain.pem + # SSLCertificateKeyFile /etc/letsencrypt/live/{{ nextcloud_instance_name }}/privkey.pem + + DocumentRoot {{ nextcloud_webroot }}/ + + + Require all granted + AllowOverride All + Options FollowSymLinks MultiViews + + + Dav off + + + + # SSL Redirect + # RewriteEngine On + # RewriteCond %{HTTPS} !=on + # RewriteCond %{HTTP:X-Forwarded-Proto} !=https + # RewriteRule ^ https://%{HTTP:Host}%{REQUEST_URI} [L,R=permanent] + + # ITK + AssignUserID {{ nextcloud_user }} {{ nextcloud_user }} + + # LOG + CustomLog /var/log/apache2/access.log vhost_combined + ErrorLog /var/log/apache2/error.log + + # PHP + php_admin_value sendmail_path "/usr/sbin/sendmail -t -i -f {{ nextcloud_user }}" + php_admin_value open_basedir "/usr/share/php:{{ nextcloud_home }}:/tmp" + + \ No newline at end of file diff --git a/webapps/nextcloud/templates/nginx.conf.j2 b/webapps/nextcloud/templates/nginx.conf.j2 deleted file mode 100644 index c2b7b7e3..00000000 --- a/webapps/nextcloud/templates/nginx.conf.j2 +++ /dev/null @@ -1,134 +0,0 @@ -upstream php-handler-{{ nextcloud_instance_name }} { - server unix:/var/run/php/php-fpm-{{ nextcloud_instance_name }}.sock; -} - -server { - listen 80; - listen [::]:80; - listen 443 ssl http2; - listen [::]:443 ssl http2; - - server_name {{ nextcloud_domains | join(' ') }}; - - access_log {{ nextcloud_home }}/log/access.log; - error_log {{ nextcloud_home }}/log/error.log; - - include /etc/nginx/snippets/letsencrypt.conf; - include /etc/nginx/ssl/{{ nextcloud_instance_name }}.conf; - - add_header Referrer-Policy "no-referrer" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-Download-Options "noopen" always; - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Permitted-Cross-Domain-Policies "none" always; - add_header X-Robots-Tag "none" always; - add_header X-XSS-Protection "1; mode=block" always; - - # Remove X-Powered-By, which is an information leak - fastcgi_hide_header X-Powered-By; - - root {{ nextcloud_webroot }}; - - location = /robots.txt { - allow all; - log_not_found off; - access_log off; - } - - # Make a regex exception for `/.well-known` so that clients can still - # access it despite the existence of the regex rule - # `location ~ /(\.|autotest|...)` which would otherwise handle requests - # for `/.well-known`. - location ^~ /.well-known { - # The following 6 rules are borrowed from `.htaccess` - - location = /.well-known/carddav { return 301 /remote.php/dav/; } - location = /.well-known/caldav { return 301 /remote.php/dav/; } - # Anything else is dynamically handled by Nextcloud - location ^~ /.well-known { return 301 /index.php$uri; } - location ~ ^/.well-known/acme-challenge/* { allow all; } - - try_files $uri $uri/ =404; - } - - # set max upload size - client_max_body_size 512M; - fastcgi_buffers 64 4K; - - # Enable gzip but do not remove ETag headers - gzip on; - gzip_vary on; - gzip_comp_level 4; - gzip_min_length 256; - gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; - gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; - - - location / { - rewrite ^ /index.php; - } - - location ~ ^\/(?:build|tests|config|lib|3rdparty|templates|data)\/ { - deny all; - } - location ~ ^\/(?:\.|autotest|occ|issue|indie|db_|console) { - deny all; - } - - - location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+|.+\/richdocumentscode\/proxy)\.php(?:$|\/) { - fastcgi_split_path_info ^(.+?\.php)(\/.*|)$; - set $path_info $fastcgi_path_info; - try_files $fastcgi_script_name =404; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $path_info; - fastcgi_param HTTPS on; - # Avoid sending the security headers twice - fastcgi_param modHeadersAvailable true; - # Enable pretty urls - fastcgi_param front_controller_active true; - fastcgi_pass php-handler-{{ nextcloud_instance_name }}; - fastcgi_intercept_errors on; - fastcgi_request_buffering off; - } - - location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) { - try_files $uri/ =404; - index index.php; - } - - # Adding the cache control header for js, css and map files - # Make sure it is BELOW the PHP block - location ~ \.(?:css|js|woff2?|svg|gif|map)$ { - try_files $uri /index.php$request_uri; - add_header Cache-Control "public, max-age=15778463"; - # Add headers to serve security related headers (It is intended to - # have those duplicated to the ones above) - # Before enabling Strict-Transport-Security headers please read into - # this topic first. - #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always; - # - # WARNING: Only add the preload option once you read about - # the consequences in https://hstspreload.org/. This option - # will add the domain to a hardcoded list that is shipped - # in all major browsers and getting removed from this list - # could take several months. - add_header Referrer-Policy "no-referrer" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-Download-Options "noopen" always; - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Permitted-Cross-Domain-Policies "none" always; - add_header X-Robots-Tag "none" always; - add_header X-XSS-Protection "1; mode=block" always; - - # Optional: Don't log access to assets - access_log off; - } - - location ~ \.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$ { - try_files $uri /index.php$request_uri; - # Optional: Don't log access to other assets - access_log off; - } -} diff --git a/webapps/nextcloud/templates/php-fpm.conf.j2 b/webapps/nextcloud/templates/php-fpm.conf.j2 deleted file mode 100644 index 1b4c7861..00000000 --- a/webapps/nextcloud/templates/php-fpm.conf.j2 +++ /dev/null @@ -1,17 +0,0 @@ -[{{ nextcloud_instance_name }}] -user = {{ nextcloud_user }} -group = {{ nextcloud_user }} -listen = /run/php/php-fpm-{{ nextcloud_instance_name }}.sock -listen.owner = {{ nextcloud_user }} -listen.group = {{ nextcloud_user }} - -pm = ondemand -pm.max_children = 50 -pm.process_idle_timeout = 120s -pm.status_path = /fpm_status - -env[HOSTNAME] = $HOSTNAME -env[PATH] = /usr/local/bin:/usr/bin:/bin -env[TMP] = {{ nextcloud_home }}/tmp -env[TMPDIR] = {{ nextcloud_home }}/tmp -env[TEMP] = {{ nextcloud_home }}/tmp