diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f8f3596..5821629e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,44 @@ The **patch** part changes is incremented if multiple releases happen the same m ### Security +## [22.05] 2022-05-10 + +### Added + +* etc-git: use "ansible-commit" to efficiently commit all available repositories (including /etc inside LXC) from Ansible +* minifirewall: compatibility with "legacy" version of minifirewall +* minifirewall: configure proxy/backup/sysctl values +* munin: Add possibility to install local plugins, and install dhcp_pool plugin +* nagios-nrpe: Add a check dhcp_pool +* redis: Activate overcommit sysctl +* redis: Add log2mail user to redis group + +### Changed + +* dump-server-state: upstream release 22.04.3 +* evocheck: upstream release 22.04.1 +* evolinux-base: Add non-free repos & install non-free firmware on dedicated hardware +* evolinux-base: rename backup-server-state to dump-server-state +* generate-ldif: Add services check for bkctld +* minifirewall: restore "force-restart" and fix "restart-if-needed" +* minifirewall: tail template follows symlinks +* minifirewall: upstream release 22.05 +* opendkim : add generate opendkim-genkey in sha256 and key 4096 +* openvpn: use a local copy of files instead of cloning an external git repository +* openvpn: use a subnet topology instead of the net30 default topology +* tomcat: Tomcat 9 by default with Debian 11 +* vrrpd: Store sysctl values in specific file + +### Fixed + +* etc-git : Remount /usr in rw for git gc in in /usr/share/scripts/ +* etc-git: Make evocommit fully compatible with OpenBSD +* generate-ldif: Correct generated entries for php-fpm in containers +* keepalived: repair broken role +* minifirewall: fix `failed_when` condition on restart +* postfix: Do not send mails through milters a second time after amavis (in packmail) +* redis: Remount /usr with RW before adding nagios plugin + ## [22.03] 2022-03-02 ### Added @@ -44,8 +82,6 @@ The **patch** part changes is incremented if multiple releases happen the same m * lxc: Fail if /var is nosuid * openvpn: make it compatible with OpenBSD and add some improvements - - ## [22.01.3] 2022-01-31 ### Changed diff --git a/dovecot/handlers/main.yml b/dovecot/handlers/main.yml index 8d1b78d8..7d40488b 100644 --- a/dovecot/handlers/main.yml +++ b/dovecot/handlers/main.yml @@ -8,3 +8,9 @@ service: name: dovecot state: reloaded + +- name: restart log2mail + service: + name: log2mail + state: restarted + diff --git a/dovecot/tasks/main.yml b/dovecot/tasks/main.yml index 49afb681..c9de6045 100644 --- a/dovecot/tasks/main.yml +++ b/dovecot/tasks/main.yml @@ -81,3 +81,25 @@ - include: munin.yml tags: - dovecot + +- name: log2mail is installed + apt: + name: log2mail + state: present + tags: dovecot + +- name: dovecot is configured in log2mail + blockinfile: + path: /etc/log2mail/config/mail.conf + create: true + owner: log2mail + group: adm + mode: "0640" + block: | + file = /var/log/mail.log + pattern = "Out of memory" + mailto = {{ log2mail_alert_email or general_alert_email | mandatory }} + template = /etc/log2mail/mail + notify: restart log2mail + tags: dovecot + diff --git a/etc-git/defaults/main.yml b/etc-git/defaults/main.yml index d0da5e7d..01b83bfd 100644 --- a/etc-git/defaults/main.yml +++ b/etc-git/defaults/main.yml @@ -4,3 +4,4 @@ etc_git_default_commit_message: Ansible run etc_git_monitor_status: True etc_git_purge_index_lock_enabled: True etc_git_purge_index_lock_age: 86400 +etc_git_config_repositories: True diff --git a/etc-git/files/ansible-commit b/etc-git/files/ansible-commit new file mode 100644 index 00000000..d22f4a6a --- /dev/null +++ b/etc-git/files/ansible-commit @@ -0,0 +1,187 @@ +#!/bin/sh + +set -u + +VERSION="22.05" + +show_version() { + cat <, + Jérémy Lecour + and others. + +ansible-commit comes with ABSOLUTELY NO WARRANTY. This is free software, +and you are welcome to redistribute it under certain conditions. +See the GNU General Public Licence for details. +END +} + +show_help() { + cat <&2 + exit 1 + fi + ;; + --message=?*) + # message options, with value speparated by = + MESSAGE=${1#*=} + ;; + --message=) + # message options, without value + printf 'FAILED: "--message" requires a non-empty option argument.\n' >&2 + exit 1 + ;; + --no-lxc) + LXC=0 + ;; + -n|--dry-run) + # disable actual commands + DRY_RUN=1 + ;; + -v|--verbose) + # print verbose information + VERBOSE=1 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + printf 'FAILED: Unknown option (ignored): %s\n' "$1" >&2 + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift +done + +if [ -z "${MESSAGE}" ]; then + echo "FAILED: missing message parameter" >&2 + show_usage + exit 1 +fi +DRY_RUN=${DRY_RUN:-0} +VERBOSE=${VERBOSE:-0} +LXC=${LXC:-1} + +evocommit_bin=$(command -v evocommit) +if [ -z "${evocommit_bin}" ]; then + echo "FAILED: evocommit not found" >&2 + exit 1 +fi + +lxc_ls_bin=$(command -v lxc-ls) +lxc_config_bin=$(command -v lxc-config) + +main \ No newline at end of file diff --git a/etc-git/files/etc-git-optimize b/etc-git/files/etc-git-optimize index 3d4932ee..56967e8f 100644 --- a/etc-git/files/etc-git-optimize +++ b/etc-git/files/etc-git-optimize @@ -6,6 +6,12 @@ repositories="/etc /etc/bind/ /usr/share/scripts" for repository in ${repositories}; do if [ -d "${repository}/.git" ]; then + if [ ${repository} = "/usr/share/scripts" ]; then + mount -o remount,rw /usr + fi git --git-dir="${repository}/.git" gc --quiet + if [ ${repository} = "/usr/share/scripts" ]; then + mount -o remount /usr + fi fi done diff --git a/etc-git/files/evocommit b/etc-git/files/evocommit index 36050d02..5a7f798b 100644 --- a/etc-git/files/evocommit +++ b/etc-git/files/evocommit @@ -2,13 +2,13 @@ set -u -VERSION="21.10" +VERSION="22.04" show_version() { cat <, +Copyright 2022 Evolix , Jérémy Lecour and others. @@ -63,6 +63,7 @@ remount_repository_readwrite() { if [ "$(get_system)" = "OpenBSD" ]; then partition=$(stat -f '%Sd' $1) mount -u -w /dev/${partition} 2>/dev/null + syslog "Re-mount ${mountpoint} as read-write to commit in repository $1" else mountpoint=$(stat -c '%m' $1) mount -o remount,rw ${mountpoint} @@ -73,6 +74,7 @@ remount_repository_readonly() { if [ "$(get_system)" = "OpenBSD" ]; then partition=$(stat -f '%Sd' $1) mount -u -r /dev/${partition} 2>/dev/null + syslog "Re-mount ${mountpoint} as read-only after commit to repository $1" else mountpoint=$(stat -c '%m' $1) mount -o remount,ro ${mountpoint} 2>/dev/null @@ -92,8 +94,12 @@ main() { rc=0 lock="${GIT_DIR}/index.lock" if [ -f "${lock}" ]; then - limit=$(date +"%s" -d "now - 1 hour") - updated_at=$(stat -c "%Y" "${lock}") + limit=$(($(date +"%s") - (1 * 60 * 60))) + if [ "$(get_system)" = "OpenBSD" ]; then + updated_at=$(stat -f "%m" "${lock}") + else + updated_at=$(stat -c "%Y" "${lock}") + fi if [ "$updated_at" -lt "$limit" ]; then rm -f "${lock}" fi @@ -262,4 +268,4 @@ if [ -d "${GIT_DIR}" ]; then else echo "There is no Git repository in '${REPOSITORY}'" >&2 exit 1 -fi \ No newline at end of file +fi diff --git a/etc-git/tasks/commit.yml b/etc-git/tasks/commit.yml index 3f993771..c92e3c6a 100644 --- a/etc-git/tasks/commit.yml +++ b/etc-git/tasks/commit.yml @@ -1,52 +1,9 @@ --- -# /etc -- name: Is /etc a git repository - stat: - path: /etc/.git - register: _etc_git - -- name: "evocommit /etc" - command: "/usr/local/bin/evocommit --ansible --repository /etc --message \"{{ commit_message | mandatory }}\"" +- name: "Execute ansible-commit" + command: "/usr/local/bin/ansible-commit --verbose --message \"{{ commit_message | mandatory }}\"" changed_when: - - _etc_git_commit.stdout - - "'CHANGED:' in _etc_git_commit.stdout" - ignore_errors: yes - register: _etc_git_commit - when: - - _etc_git.stat.exists - - _etc_git.stat.isdir - -# /etc/bind -- name: Is /etc/bind a git repository - stat: - path: /etc/bind/.git - register: _etc_bind_git - -- name: "evocommit /etc/bind" - command: "/usr/local/bin/evocommit --ansible --repository /etc/bind --message \"{{ commit_message | mandatory }}\"" - changed_when: - - _etc_bind_git_commit.stdout - - "'CHANGED:' in _etc_bind_git_commit.stdout" - ignore_errors: yes - register: _etc_bind_git_commit - when: - - _etc_bind_git.stat.exists - - _etc_bind_git.stat.isdir - -# /usr/share/scripts -- name: Is /usr/share/scripts a git repository - stat: - path: /usr/share/scripts/.git - register: _usr_share_scripts_git - -- name: "evocommit /usr/share/scripts" - command: "/usr/local/bin/evocommit --ansible --repository /usr/share/scripts --message \"{{ commit_message | mandatory }}\"" - changed_when: - - _usr_share_scripts_git_commit.stdout - - "'CHANGED:' in _usr_share_scripts_git_commit.stdout" - ignore_errors: yes - register: _usr_share_scripts_git_commit - when: - - _usr_share_scripts_git.stat.exists - - _usr_share_scripts_git.stat.isdir + - _ansible_commit.stdout + - "'CHANGED:' in _ansible_commit.stdout" + ignore_errors: True + register: _ansible_commit \ No newline at end of file diff --git a/etc-git/tasks/lxc_commit.yml b/etc-git/tasks/lxc_commit.yml new file mode 100644 index 00000000..26fc8738 --- /dev/null +++ b/etc-git/tasks/lxc_commit.yml @@ -0,0 +1,35 @@ +--- +- name: "Assert that we have been called with `container` defined" + assert: + that: + - container is defined + +- name: "Define path to /etc in {{ container }} container" + set_fact: + container_etc: "{{ ('/var/lib/lxc', container, 'rootfs/etc') | path_join }}" + +- name: "Check if /etc is a git repository in {{ container }}" + stat: + path: "{{ (container_etc, '.git') | path_join }}" + get_attributes: no + get_checksum: no + get_mime: no + register: "container_etc_git" + +- name: "Evocommit /etc of {{ container }}" + command: + argv: + - /usr/local/bin/evocommit + - '--ansible' + - '--repository' + - "{{ container_etc }}" + - '--message' + - "{{ commit_message | mandatory }}" + changed_when: + - "container_etc_git_commit.stdout" + - "'CHANGED:' in container_etc_git_commit.stdout" + ignore_errors: yes + register: "container_etc_git_commit" + when: + - "container_etc_git.stat.exists" + - "container_etc_git.stat.isdir" diff --git a/etc-git/tasks/main.yml b/etc-git/tasks/main.yml index 11be1899..f71ba552 100644 --- a/etc-git/tasks/main.yml +++ b/etc-git/tasks/main.yml @@ -6,109 +6,16 @@ state: present tags: - etc-git + when: + - ansible_distribution == "Debian" -- include_role: - name: evolix/remount-usr - -- name: "evocommit script is installed" - copy: - src: evocommit - dest: /usr/local/bin/evocommit - mode: "0755" - force: yes +- name: Install and configure utilities + include: utils.yml tags: - etc-git -- include: repository.yml - vars: - repository_path: "/etc" - gitignore_items: - - "aliases.db" - - "*.swp" - - "postfix/sa-blacklist.access" - - "postfix/*.db" - - "postfix/spamd.cidr" - - "evobackup/.keep-*" - - "letsencrypt/.certbot.lock" - -- name: verify /usr/share/scripts presence - stat: - path: /usr/share/scripts - register: _usr_share_scripts - -- include: repository.yml - vars: - repository_path: "/usr/share/scripts" - gitignore_items: [] - when: - - _usr_share_scripts.stat.isdir - - ansible_distribution_major_version is version('10', '>=') - -- name: "etc-git-optimize script is installed" - copy: - src: etc-git-optimize - dest: /usr/share/scripts/etc-git-optimize - mode: "0755" - force: yes +- name: Configure repositories + include: repositories.yml tags: - etc-git - -- name: "etc-git-status script is installed" - copy: - src: etc-git-status - dest: /usr/share/scripts/etc-git-status - mode: "0755" - force: yes - tags: - - etc-git - -- name: Check if cron is installed - shell: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'" - args: - executable: /bin/bash - failed_when: False - changed_when: False - check_mode: no - register: is_cron_installed - -- block: - - name: Legacy cron jobs for /etc/.git status are absent - file: - dest: "{{ item }}" - state: absent - loop: - - /etc/cron.monthly/optimize-etc-git - - /etc/cron.d/etc-git-status - - - name: Cron job for monthly git optimization - cron: - name: "Monthly optimization" - cron_file: etc-git - special_time: "monthly" - user: root - job: "/usr/share/scripts/etc-git-optimize" - - - name: Cron job for hourly git status - cron: - name: "Hourly warning for unclean Git repository if nobody is connected" - cron_file: etc-git - special_time: "hourly" - user: root - job: "who > /dev/null || /usr/share/scripts/etc-git-status" - state: "{{ etc_git_monitor_status | bool | ternary('present','absent') }}" - - - name: Cron job for daily git status - cron: - name: "Daily warning for unclean Git repository" - cron_file: etc-git - user: root - job: "/usr/share/scripts/etc-git-status" - minute: "21" - hour: "21" - weekday: "*" - day: "*" - month: "*" - state: "{{ etc_git_monitor_status | bool | ternary('present','absent') }}" - when: is_cron_installed.rc == 0 - tags: - - etc-git \ No newline at end of file + when: etc_git_config_repositories | bool \ No newline at end of file diff --git a/etc-git/tasks/repositories.yml b/etc-git/tasks/repositories.yml new file mode 100644 index 00000000..71ff0665 --- /dev/null +++ b/etc-git/tasks/repositories.yml @@ -0,0 +1,37 @@ +--- + +- include: repository.yml + vars: + repository_path: "/etc" + gitignore_items: + - "aliases.db" + - "*.swp" + - "postfix/sa-blacklist.access" + - "postfix/*.db" + - "postfix/spamd.cidr" + - "evobackup/.keep-*" + - "letsencrypt/.certbot.lock" + tags: + - etc-git + +- name: verify /usr/share/scripts presence + stat: + path: /usr/share/scripts + register: _usr_share_scripts + tags: + - etc-git + +- include_role: + name: evolix/remount-usr + when: + - _usr_share_scripts.stat.isdir + +- include: repository.yml + vars: + repository_path: "/usr/share/scripts" + gitignore_items: [] + when: + - _usr_share_scripts.stat.isdir + - ansible_distribution_major_version is version('10', '>=') + tags: + - etc-git \ No newline at end of file diff --git a/etc-git/tasks/utils.yml b/etc-git/tasks/utils.yml new file mode 100644 index 00000000..cd060de1 --- /dev/null +++ b/etc-git/tasks/utils.yml @@ -0,0 +1,93 @@ +--- + +- include_role: + name: evolix/remount-usr + tags: + - etc-git + +- name: "evocommit script is installed" + copy: + src: evocommit + dest: /usr/local/bin/evocommit + mode: "0755" + force: yes + tags: + - etc-git + +- name: "ansible-commit script is installed" + copy: + src: ansible-commit + dest: /usr/local/bin/ansible-commit + mode: "0755" + force: yes + tags: + - etc-git + +- name: "etc-git-optimize script is installed" + copy: + src: etc-git-optimize + dest: /usr/share/scripts/etc-git-optimize + mode: "0755" + force: yes + tags: + - etc-git + +- name: "etc-git-status script is installed" + copy: + src: etc-git-status + dest: /usr/share/scripts/etc-git-status + mode: "0755" + force: yes + tags: + - etc-git + +- name: Check if cron is installed + shell: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'" + args: + executable: /bin/bash + failed_when: False + changed_when: False + check_mode: no + register: is_cron_installed + +- block: + - name: Legacy cron jobs for /etc/.git status are absent + file: + dest: "{{ item }}" + state: absent + loop: + - /etc/cron.monthly/optimize-etc-git + - /etc/cron.d/etc-git-status + + - name: Cron job for monthly git optimization + cron: + name: "Monthly optimization" + cron_file: etc-git + special_time: "monthly" + user: root + job: "/usr/share/scripts/etc-git-optimize" + + - name: Cron job for hourly git status + cron: + name: "Hourly warning for unclean Git repository if nobody is connected" + cron_file: etc-git + special_time: "hourly" + user: root + job: "who > /dev/null || /usr/share/scripts/etc-git-status" + state: "{{ etc_git_monitor_status | bool | ternary('present','absent') }}" + + - name: Cron job for daily git status + cron: + name: "Daily warning for unclean Git repository" + cron_file: etc-git + user: root + job: "/usr/share/scripts/etc-git-status" + minute: "21" + hour: "21" + weekday: "*" + day: "*" + month: "*" + state: "{{ etc_git_monitor_status | bool | ternary('present','absent') }}" + when: is_cron_installed.rc == 0 + tags: + - etc-git \ No newline at end of file diff --git a/evobackup-client/handlers/main.yml b/evobackup-client/handlers/main.yml index fc1b7739..de71f634 100644 --- a/evobackup-client/handlers/main.yml +++ b/evobackup-client/handlers/main.yml @@ -2,8 +2,9 @@ - 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" - changed_when: "'starting IPTables rules is now finish : OK' in minifirewall_init_restart.stdout" + failed_when: + - "'starting IPTables rules is now finish : OK' not in minifirewall_init_restart.stdout" + - "'minifirewall started' not in minifirewall_init_restart.stdout" - name: 'created new jail' command: "bkctld restart {{ evolinux_hostname }}" diff --git a/evocheck/files/evocheck.sh b/evocheck/files/evocheck.sh index fb8a6eeb..4f24ae79 100644 --- a/evocheck/files/evocheck.sh +++ b/evocheck/files/evocheck.sh @@ -4,7 +4,7 @@ # Script to verify compliance of a Debian/OpenBSD server # powered by Evolix -VERSION="21.10.4" +VERSION="22.04.1" readonly VERSION # base functions @@ -13,7 +13,7 @@ show_version() { cat <, +Copyright 2009-2022 Evolix , Romain Dessort , Benoit Série , Gregory Colpart , @@ -142,9 +142,9 @@ failed() { RC=1 if [ "${QUIET}" != 1 ]; then if [ -n "${check_comments}" ] && [ "${VERBOSE}" = 1 ]; then - printf "%s FAILED! %s\n" "${check_name}" "${check_comments}" 2>&1 + printf "%s FAILED! %s\n" "${check_name}" "${check_comments}" >> "${main_output_file}" else - printf "%s FAILED!\n" "${check_name}" 2>&1 + printf "%s FAILED!\n" "${check_name}" >> "${main_output_file}" fi fi } @@ -234,7 +234,8 @@ check_syslogconf() { check_debiansecurity() { if is_debian_bullseye; then # https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.html#security-archive - pattern="^deb https://deb\.debian\.org/debian-security/? bullseye-security main" + # https://www.debian.org/security/ + pattern="^deb https://(deb|security)\.debian\.org/debian-security/? bullseye-security main" elif is_debian_buster; then pattern="^deb http://security\.debian\.org/debian-security/? buster/updates main" elif is_debian_stretch; then @@ -328,8 +329,11 @@ check_tmoutprofile() { check_alert5boot() { if is_debian_buster || is_debian_bullseye; then grep -qs "^date" /usr/share/scripts/alert5.sh || failed "IS_ALERT5BOOT" "boot mail is not sent by alert5 init script" - test -f /etc/systemd/system/alert5.service || failed "IS_ALERT5BOOT" "alert5 unit file is missing" - systemctl is-enabled alert5 -q || failed "IS_ALERT5BOOT" "alert5 unit is not enabled" + if [ -f /etc/systemd/system/alert5.service ]; then + systemctl is-enabled alert5.service -q || failed "IS_ALERT5BOOT" "alert5 unit is not enabled" + else + failed "IS_ALERT5BOOT" "alert5 unit file is missing" + fi else if [ -n "$(find /etc/rc2.d/ -name 'S*alert5')" ]; then grep -q "^date" /etc/rc2.d/S*alert5 || failed "IS_ALERT5BOOT" "boot mail is not sent by alert5 init script" @@ -567,7 +571,7 @@ check_network_interfaces() { # Verify if all if are in auto check_autoif() { if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - interfaces=$(/sbin/ip address show up | grep "^[0-9]*:" | grep -E -v "(lo|vnet|docker|veth|tun|tap|macvtap|vrrp)" | cut -d " " -f 2 | tr -d : | cut -d@ -f1 | tr "\n" " ") + interfaces=$(/sbin/ip address show up | grep "^[0-9]*:" | grep -E -v "(lo|vnet|docker|veth|tun|tap|macvtap|vrrp|lxcbr)" | cut -d " " -f 2 | tr -d : | cut -d@ -f1 | tr "\n" " ") else interfaces=$(/sbin/ifconfig -s | tail -n +2 | grep -E -v "^(lo|vnet|docker|veth|tun|tap|macvtap|vrrp)" | cut -d " " -f 1 |tr "\n" " ") fi @@ -592,18 +596,21 @@ check_evobackup() { } # Vérification de l'exclusion des montages (NFS) dans les sauvegardes check_evobackup_exclude_mount() { - excludes_file=$(mktemp) - # shellcheck disable=SC2064 - trap "rm -f ${excludes_file}" 0 + excludes_file=$(mktemp --tmpdir="${TMPDIR:-/tmp}" "evocheck.evobackup_exclude_mount.XXXXX") + files_to_cleanup="${files_to_cleanup} ${excludes_file}" + # shellcheck disable=SC2044 for evobackup_file in $(find /etc/cron* -name '*evobackup*' | grep -v -E ".disabled$"); do - grep -- "--exclude " "${evobackup_file}" | grep -E -o "\"[^\"]+\"" | tr -d '"' > "${excludes_file}" - not_excluded=$(findmnt --type nfs,nfs4,fuse.sshfs, -o target --noheadings | grep -v -f "${excludes_file}") - for mount in ${not_excluded}; do - failed "IS_EVOBACKUP_EXCLUDE_MOUNT" "${mount} is not excluded from ${evobackup_file} backup script" - done + # If rsync is not limited by "one-file-system" + # then we verify that every mount is excluded + if ! grep -q -- "^\s*--one-file-system" "${evobackup_file}"; then + grep -- "--exclude " "${evobackup_file}" | grep -E -o "\"[^\"]+\"" | tr -d '"' > "${excludes_file}" + not_excluded=$(findmnt --type nfs,nfs4,fuse.sshfs, -o target --noheadings | grep -v -f "${excludes_file}") + for mount in ${not_excluded}; do + failed "IS_EVOBACKUP_EXCLUDE_MOUNT" "${mount} is not excluded from ${evobackup_file} backup script" + done + fi done - rm -rf "${excludes_file}" } # Verification de la presence du userlogrotate check_userlogrotate() { @@ -809,8 +816,10 @@ check_tune2fs_m5() { check_evolinuxsudogroup() { if is_debian_stretch || is_debian_buster || is_debian_bullseye; then if grep -q "^evolinux-sudo:" /etc/group; then - grep -qE '^%evolinux-sudo +ALL ?= ?\(ALL:ALL\) ALL' /etc/sudoers.d/evolinux \ - || failed "IS_EVOLINUXSUDOGROUP" "missing evolinux-sudo directive in sudoers file" + if [ -f /etc/sudoers.d/evolinux ]; then + grep -qE '^%evolinux-sudo +ALL ?= ?\(ALL:ALL\) ALL' /etc/sudoers.d/evolinux \ + || failed "IS_EVOLINUXSUDOGROUP" "missing evolinux-sudo directive in sudoers file" + fi fi fi } @@ -827,7 +836,7 @@ check_userinadmgroup() { } check_apache2evolinuxconf() { if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - if test -d /etc/apache2; then + if is_installed apache2; then { test -L /etc/apache2/conf-enabled/z-evolinux-defaults.conf \ && test -L /etc/apache2/conf-enabled/zzz-evolinux-custom.conf \ && test -f /etc/apache2/ipaddr_whitelist.conf; @@ -1006,6 +1015,8 @@ check_mysqlmunin() { test "${VERBOSE}" = 1 || break fi done + munin-run mysql_commands 2> /dev/null > /dev/null + test $? -eq 0 || failed "IS_MYSQLMUNIN" "Munin plugin mysql_commands returned an error" fi fi } @@ -1062,8 +1073,10 @@ check_squidevolinuxconf() { check_duplicate_fs_label() { # Do it only if thereis blkid binary BLKID_BIN=$(command -v blkid) - if [ -x "$BLKID_BIN" ]; then - tmpFile=$(mktemp -p /tmp) + if [ -n "$BLKID_BIN" ]; then + tmpFile=$(mktemp --tmpdir="${TMPDIR:-/tmp}" "evocheck.duplicate_fs_label.XXXXX") + files_to_cleanup="${files_to_cleanup} ${tmpFile}" + parts=$($BLKID_BIN -c /dev/null | grep -ve raid_member -e EFI_SYSPART | grep -Eo ' LABEL=".*"' | cut -d'"' -f2) for part in $parts; do echo "$part" >> "$tmpFile" @@ -1076,7 +1089,6 @@ check_duplicate_fs_label() { labels=$(echo -n $tmpOutput | tr '\n' ' ') failed "IS_DUPLICATE_FS_LABEL" "Duplicate labels: $labels" fi - rm "$tmpFile" else failed "IS_DUPLICATE_FS_LABEL" "blkid not found in ${PATH}" fi @@ -1367,7 +1379,7 @@ download_versions() { elif is_openbsd; then versions_url="https://upgrades.evolix.org/versions-${OPENBSD_RELEASE}" else - failed "IS_VERSIONS_CHECK" "error determining os release" + failed "IS_CHECK_VERSIONS" "error determining os release" fi # fetch timeout, in seconds @@ -1380,9 +1392,9 @@ download_versions() { elif command -v GET; then GET -t ${timeout}s "${versions_url}" > "${versions_file}" else - failed "IS_VERSIONS_CHECK" "failed to find curl, wget or GET" + failed "IS_CHECK_VERSIONS" "failed to find curl, wget or GET" fi - test "$?" -eq 0 || failed "IS_VERSIONS_CHECK" "failed to download ${versions_url} to ${versions_file}" + test "$?" -eq 0 || failed "IS_CHECK_VERSIONS" "failed to download ${versions_url} to ${versions_file}" } get_command() { local program @@ -1395,6 +1407,7 @@ get_command() { listupgrade) command -v "evolistupgrade.sh" ;; old-kernel-autoremoval) command -v "old-kernel-autoremoval.sh" ;; mysql-queries-killer) command -v "mysql-queries-killer.sh" ;; + minifirewall) echo "/etc/init.d/minifirewall" ;; ## General case, where the program name is the same as the command name *) command -v "${program}" ;; @@ -1415,6 +1428,9 @@ get_version() { add-vm) grep '^VERSION=' "${command}" | head -1 | cut -d '=' -f 2 ;; + minifirewall) + ${command} status | head -1 | cut -d ' ' -f 3 + ;; ## Let's try the --version flag before falling back to grep for the constant kvmstats) if ${command} --version > /dev/null 2> /dev/null; then @@ -1440,11 +1456,11 @@ check_version() { actual_version=$(get_version "${program}" "${command}") # printf "program:%s expected:%s actual:%s\n" "${program}" "${expected_version}" "${actual_version}" if [ -z "${actual_version}" ]; then - failed "IS_VERSIONS_CHECK" "failed to lookup actual version of ${program}" + failed "IS_CHECK_VERSIONS" "failed to lookup actual version of ${program}" elif dpkg --compare-versions "${actual_version}" lt "${expected_version}"; then - failed "IS_VERSIONS_CHECK" "${program} version ${actual_version} is older than expected version ${expected_version}" + failed "IS_CHECK_VERSIONS" "${program} version ${actual_version} is older than expected version ${expected_version}" elif dpkg --compare-versions "${actual_version}" gt "${expected_version}"; then - failed "IS_VERSIONS_CHECK" "${program} version ${actual_version} is newer than expected version ${expected_version}, you should update tour index." + failed "IS_CHECK_VERSIONS" "${program} version ${actual_version} is newer than expected version ${expected_version}, you should update your index." else : # Version check OK fi @@ -1457,9 +1473,9 @@ add_to_path() { echo "$PATH" | grep -qF "${new_path}" || export PATH="${PATH}:${new_path}" } check_versions() { - versions_file=$(mktemp --tmpdir=/tmp "evocheck-versions.XXXXX") - # shellcheck disable=SC2064 - trap "rm -f ${versions_file}" 0 + versions_file=$(mktemp --tmpdir="${TMPDIR:-/tmp}" "evocheck.versions.XXXXX") + files_to_cleanup="${files_to_cleanup} ${versions_file}" + download_versions "${versions_file}" add_to_path "/usr/share/scripts" @@ -1473,12 +1489,10 @@ check_versions() { if [ -n "${version}" ]; then check_version "${program}" "${version}" else - failed "IS_VERSIONS_CHECK" "failed to lookup expected version for ${program}" + failed "IS_CHECK_VERSIONS" "failed to lookup expected version for ${program}" fi fi done - - rm -f "${versions_file}" } main() { @@ -1487,6 +1501,9 @@ main() { # Detect operating system name, version and release detect_os + 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 #----------------------------------------------------------- @@ -1715,8 +1732,21 @@ main() { # - NRPEDISK et NRPEPOSTFIX fi + if [ -f "${main_output_file}" ]; then + lines_found=$(wc -l < "${main_output_file}") + # shellcheck disable=SC2086 + if [ ${lines_found} -gt 0 ]; then + + cat "${main_output_file}" 2>&1 + fi + fi + exit ${RC} } +cleanup_temp_files() { + # shellcheck disable=SC2086 + rm -f ${files_to_cleanup} +} PROGNAME=$(basename "$0") # shellcheck disable=SC2034 @@ -1730,6 +1760,10 @@ readonly ARGS export LANG=C export LANGUAGE=C +files_to_cleanup="" +# shellcheck disable=SC2064 +trap cleanup_temp_files 0 + # Source configuration file # shellcheck disable=SC1091 test -f /etc/evocheck.cf && . /etc/evocheck.cf diff --git a/evolinux-base/files/backup-server-state.sh b/evolinux-base/files/backup-server-state.sh deleted file mode 100644 index 8e64c423..00000000 --- a/evolinux-base/files/backup-server-state.sh +++ /dev/null @@ -1,1019 +0,0 @@ -#!/bin/sh - -PROGNAME="backup-server-state" - -VERSION="22.01.3" -readonly VERSION - -backup_dir= -rc=0 - -# base functions - -show_version() { - cat <, - Jérémy Lecour - and others. - -${PROGNAME} comes with ABSOLUTELY NO WARRANTY.This is free software, -and you are welcome to redistribute it under certain conditions. -See the GNU General Public License v3.0 for details. -END -} -show_help() { - cat < "${backup_dir}/apt-config.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* apt-config OK" - else - debug "* apt-config ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* apt-config is not found" - fi -} - -backup_dpkg_full() { - debug "Backup DPkg full state" - - dir_state_status="/var/lib/dpkg/status" - - apt_config_bin=$(command -v apt-config) - - if [ -n "${apt_config_bin}" ]; then - eval "$(${apt_config_bin} shell dir_state_status Dir::State::status)" - fi - - dpkg_dir=$(dirname "${dir_state_status}") - - last_result=$(mkdir -p "${backup_dir}${dpkg_dir}" && chmod -R 755 "${backup_dir}${dpkg_dir}") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* mkdir/chmod OK" - else - debug "* mkdir/chmod ERROR" - debug "${last_result}" - rc=10 - fi - - rsync_bin=$(command -v rsync) - - if [ -n "${rsync_bin}" ]; then - last_result=$(${rsync_bin} -ah --itemize-changes --exclude='*-old' "${dpkg_dir}/" "${backup_dir}${dpkg_dir}/") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* rsync OK" - else - debug "* rsync ERROR :" - debug "${last_result}" - rc=10 - fi - else - debug "* rsync not found" - - last_result=$(cp -r "${dpkg_dir}/*" "${backup_dir}${dpkg_dir}/" && rm -rf "${backup_dir}${dpkg_dir}/*-old") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* cp OK" - else - debug "* cp ERROR :" - debug "${last_result}" - rc=10 - fi - fi -} - -backup_dpkg_status() { - debug "Backup DPkg status" - - dir_state_status="/var/lib/dpkg/status" - - apt_config_bin=$(command -v apt-config) - - if [ -n "${apt_config_bin}" ]; then - eval "$(${apt_config_bin} shell dir_state_status Dir::State::status)" - fi - - last_result=$(cp "${dir_state_status}" "${backup_dir}/dpkg-status.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* cp OK" - else - debug "* cp ERROR :" - debug "${last_result}" - rc=10 - fi -} - -backup_packages() { - debug "Backup list of installed package" - - dpkg_bin=$(command -v dpkg) - - if [ -n "${dpkg_bin}" ]; then - last_result=$(${dpkg_bin} --get-selections "*" > "${backup_dir}/current_packages.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* dpkg OK" - else - debug "* dpkg ERROR :" - debug "${last_result}" - rc=10 - fi - else - debug "* dpkg not found" - fi -} - -backup_uname() { - debug "Backup uname" - - last_result=$(uname -a > "${backup_dir}/uname.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* uname OK" - else - debug "* uname ERROR" - debug "${last_result}" - rc=10 - fi -} - -backup_uptime() { - debug "Backup uptime" - - last_result=$(uptime > "${backup_dir}/uptime.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* uptime OK" - else - debug "* uptime ERROR" - debug "${last_result}" - rc=10 - fi -} - -backup_processes() { - debug "Backup process list" - - last_result=$(ps fauxw > "${backup_dir}/ps.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* ps OK" - else - debug "* ps ERROR" - debug "${last_result}" - rc=10 - fi - - pstree_bin=$(command -v pstree) - - if [ -n "${pstree_bin}" ]; then - last_result=$(${pstree_bin} -pan > "${backup_dir}/pstree.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* pstree OK" - else - debug "* pstree ERROR" - debug "${last_result}" - rc=10 - fi - fi -} - -backup_netstat() { - debug "Backup network status" - - ss_bin=$(command -v ss) - - if [ -n "${ss_bin}" ]; then - last_result=$(${ss_bin} -tanpul > "${backup_dir}/netstat-ss.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* ss OK" - else - debug "* ss ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* ss not found" - fi - - netstat_bin=$(command -v netstat) - - if [ -n "${netstat_bin}" ]; then - last_result=$(netstat -laputen > "${backup_dir}/netstat-legacy.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* netstat OK" - else - debug "* netstat ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* netstat not found" - fi -} - -backup_netcfg() { - debug "Backup network configuration" - - ip_bin=$(command -v ip) - - if [ -n "${ip_bin}" ]; then - last_result=$(${ip_bin} address show > "${backup_dir}/ip-address.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* ip address OK" - else - debug "* ip address ERROR" - debug "${last_result}" - rc=10 - fi - - last_result=$(${ip_bin} route show > "${backup_dir}/ip-route.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* ip route OK" - else - debug "* ip route ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* ip not found" - - ifconfig_bin=$(command -v ifconfig) - - if [ -n "${ifconfig_bin}" ]; then - last_result=$(${ifconfig_bin} > "${backup_dir}/ifconfig.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* ifconfig OK" - else - debug "* ifconfig ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* ifconfig not found" - fi - fi -} - -backup_iptables() { - debug "Backup iptables" - - iptables_bin=$(command -v iptables) - - if [ -n "${iptables_bin}" ]; then - last_result=$({ ${iptables_bin} -L -n -v; ${iptables_bin} -t filter -L -n -v; } > "${backup_dir}/iptables.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* iptables OK" - else - debug "* iptables ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* iptables not found" - fi - - iptables_save_bin=$(command -v iptables-save) - - if [ -n "${iptables_save_bin}" ]; then - last_result=$(${iptables_save_bin} > "${backup_dir}/iptables-save.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* iptables-save OK" - else - debug "* iptables-save ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* iptables-save not found" - fi -} - -backup_sysctl() { - debug "Backup sysctl values" - - sysctl_bin=$(command -v sysctl) - - if [ -n "${sysctl_bin}" ]; then - last_result=$(${sysctl_bin} -a | sort -h > "${backup_dir}/sysctl.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* sysctl OK" - else - debug "* sysctl ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* sysctl not found" - fi -} - -backup_virsh() { - debug "Backup virsh list" - - virsh_bin=$(command -v virsh) - - if [ -n "${virsh_bin}" ]; then - last_result=$(${virsh_bin} list --all > "${backup_dir}/virsh-list.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* virsh list OK" - else - debug "* virsh list ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* virsh not found" - fi -} - -backup_lxc() { - debug "Backup lxc list" - - lxc_ls_bin=$(command -v lxc-ls) - - if [ -n "${lxc_ls_bin}" ]; then - last_result=$(${lxc_ls_bin} --fancy > "${backup_dir}/lxc-list.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* lxc list OK" - else - debug "* lxc list ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* lxc-ls not found" - fi -} - -backup_disks() { - debug "Backup disks" - - lsblk_bin=$(command -v lsblk) - awk_bin=$(command -v awk) - - if [ -n "${lsblk_bin}" ] && [ -n "${awk_bin}" ]; then - disks=$(${lsblk_bin} -l | grep disk | grep -v -E '(drbd|fd[0-9]+)' | ${awk_bin} '{print $1}') - for disk in ${disks}; do - dd_bin=$(command -v dd) - if [ -n "${dd_bin}" ]; then - last_result=$(${dd_bin} if="/dev/${disk}" of="${backup_dir}/MBR-${disk}" bs=512 count=1 2>&1) - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* dd ${disk} OK" - else - debug "* dd ${disk} ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* dd not found" - fi - fdisk_bin=$(command -v fdisk) - if [ -n "${fdisk_bin}" ]; then - last_result=$(${fdisk_bin} -l "/dev/${disk}" > "${backup_dir}/partitions-${disk}" 2>&1) - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* fdisk ${disk} OK" - else - debug "* fdisk ${disk} ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* fdisk not found" - fi - done - cat "${backup_dir}"/partitions-* > "${backup_dir}/partitions" - else - if [ -n "${lsblk_bin}" ]; then - debug "* lsblk not found" - fi - if [ -n "${awk_bin}" ]; then - debug "* awk not found" - fi - fi -} - -backup_mount() { - debug "Backup mount points" - - findmnt_bin=$(command -v findmnt) - - if [ -n "${findmnt_bin}" ]; then - last_result=$(${findmnt_bin} > "${backup_dir}/mount.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* mount points OK" - else - debug "* mount points ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* findmnt not found" - - mount_bin=$(command -v mount) - - if [ -n "${mount_bin}" ]; then - last_result=$(${mount_bin} > "${backup_dir}/mount.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* mount points OK" - else - debug "* mount points ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* mount not found" - fi - fi -} - -backup_df() { - debug "Backup df" - - df_bin=$(command -v df) - - if [ -n "${df_bin}" ]; then - last_result=$(${df_bin} --portability > "${backup_dir}/df.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* df OK" - else - debug "* df ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* df not found" - fi -} - -backup_dmesg() { - debug "Backup dmesg" - - dmesg_bin=$(command -v dmesg) - - if [ -n "${dmesg_bin}" ]; then - last_result=$(${dmesg_bin} > "${backup_dir}/dmesg.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* dmesg OK" - else - debug "* dmesg ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* dmesg not found" - fi -} - -backup_mysql_processes() { - debug "Backup mysql processes" - - mysqladmin_bin=$(command -v mysqladmin) - - if [ -n "${mysqladmin_bin}" ]; then - last_result=$(${mysqladmin_bin} --verbose processlist > "${backup_dir}/mysql-processlist.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* mysqladmin OK" - else - debug "* mysqladmin ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* mysqladmin not found" - fi -} - -backup_systemctl() { - debug "Backup services" - - systemctl_bin=$(command -v systemctl) - - if [ -n "${systemctl_bin}" ]; then - last_result=$(${systemctl_bin} --no-legend --state=failed --type=service > "${backup_dir}/systemctl-failed-services.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* failed services OK" - else - debug "* failed services ERROR" - debug "${last_result}" - rc=10 - fi - else - debug "* systemctl not found" - fi -} - - -main() { - if [ -z "${backup_dir}" ]; then - echo "ERROR: You must provide the --backup-dir argument" >&2 - exit 1 - fi - - if [ -d "${backup_dir}" ]; then - if [ "${FORCE}" != "1" ]; then - echo "ERROR: The backup directory ${backup_dir} already exists. Delete it first." >&2 - exit 2 - fi - else - create_backup_dir - fi - - if [ "${DO_ETC}" -eq 1 ]; then - backup_etc - fi - if [ "${DO_DPKG_FULL}" -eq 1 ]; then - backup_dpkg_full - fi - if [ "${DO_DPKG_STATUS}" -eq 1 ]; then - backup_dpkg_status - fi - if [ "${DO_APT_STATES}" -eq 1 ]; then - backup_apt_states - fi - if [ "${DO_APT_CONFIG}" -eq 1 ]; then - backup_apt_config - fi - if [ "${DO_PACKAGES}" -eq 1 ]; then - backup_packages - fi - if [ "${DO_PROCESSES}" -eq 1 ]; then - backup_processes - fi - if [ "${DO_UPTIME}" -eq 1 ]; then - backup_uptime - fi - if [ "${DO_UNAME}" -eq 1 ]; then - backup_uname - fi - if [ "${DO_NETSTAT}" -eq 1 ]; then - backup_netstat - fi - if [ "${DO_NETCFG}" -eq 1 ]; then - backup_netcfg - fi - if [ "${DO_IPTABLES}" -eq 1 ]; then - backup_iptables - fi - if [ "${DO_SYSCTL}" -eq 1 ]; then - backup_sysctl - fi - if [ "${DO_VIRSH}" -eq 1 ]; then - backup_virsh - fi - if [ "${DO_LXC}" -eq 1 ]; then - backup_lxc - fi - if [ "${DO_DISKS}" -eq 1 ]; then - backup_disks - fi - if [ "${DO_MOUNT}" -eq 1 ]; then - backup_mount - fi - if [ "${DO_DF}" -eq 1 ]; then - backup_df - fi - if [ "${DO_DMESG}" -eq 1 ]; then - backup_dmesg - fi - if [ "${DO_MYSQL_PROCESSES}" -eq 1 ]; then - backup_mysql_processes - fi - if [ "${DO_SYSTEMCTL}" -eq 1 ]; then - backup_systemctl - fi - - - debug "=> Your backup is available at ${backup_dir}" - exit ${rc} -} - -# parse options -# based on https://gist.github.com/deshion/10d3cb5f88a21671e17a -while :; do - case $1 in - -h|-\?|--help) - show_help - exit 0 - ;; - -V|--version) - show_version - exit 0 - ;; - -v|--verbose) - VERBOSE=1 - ;; - - -f|--force) - FORCE=1 - ;; - - -d|--backup-dir) - # with value separated by space - if [ -n "$2" ]; then - backup_dir=$2 - shift - else - printf 'ERROR: "-d|--backup-dir" requires a non-empty option argument.\n' >&2 - exit 1 - fi - ;; - --backup-dir=?*) - # with value speparated by = - backup_dir=${1#*=} - ;; - --backup-dir=) - # without value - printf 'ERROR: "--backup-dir" requires a non-empty option argument.\n' >&2 - exit 1 - ;; - - --etc) - DO_ETC=1 - ;; - --no-etc) - DO_ETC=0 - ;; - - --dpkg-full) - DO_DPKG_FULL=1 - ;; - --no-dpkg-full) - DO_DPKG_FULL=0 - ;; - - --dpkg-status) - DO_DPKG_STATUS=1 - ;; - --no-dpkg-status) - DO_DPKG_STATUS=0 - ;; - - --apt-states) - DO_APT_STATES=1 - ;; - --no-apt-states) - DO_APT_STATES=0 - ;; - - --apt-config) - DO_APT_CONFIG=1 - ;; - --no-apt-config) - DO_APT_CONFIG=0 - ;; - - --packages) - DO_PACKAGES=1 - ;; - --no-packages) - DO_PACKAGES=0 - ;; - - --processes) - DO_PROCESSES=1 - ;; - --no-processes) - DO_PROCESSES=0 - ;; - - --uptime) - DO_UPTIME=1 - ;; - --no-uptime) - DO_UPTIME=0 - ;; - - --uname) - DO_UNAME=1 - ;; - --no-uname) - DO_UNAME=0 - ;; - - --netstat) - DO_NETSTAT=1 - ;; - --no-netstat) - DO_NETSTAT=0 - ;; - - --netcfg) - DO_NETCFG=1 - ;; - --no-netcfg) - DO_NETCFG=0 - ;; - - --iptables) - DO_IPTABLES=1 - ;; - --no-iptables) - DO_IPTABLES=0 - ;; - - --sysctl) - DO_SYSCTL=1 - ;; - --no-sysctl) - DO_SYSCTL=0 - ;; - - --virsh) - DO_VIRSH=1 - ;; - --no-virsh) - DO_VIRSH=0 - ;; - - --lxc) - DO_LXC=1 - ;; - --no-lxc) - DO_LXC=0 - ;; - - --disks) - DO_DISKS=1 - ;; - --no-disks) - DO_DISKS=0 - ;; - - --mount) - DO_MOUNT=1 - ;; - --no-mount) - DO_MOUNT=0 - ;; - - --df) - DO_DF=1 - ;; - --no-df) - DO_DF=0 - ;; - - --dmesg) - DO_DMESG=1 - ;; - --no-dmesg) - DO_DMESG=0 - ;; - - --mysql-processes) - DO_MYSQL_PROCESSES=1 - ;; - --no-mysql-processes) - DO_MYSQL_PROCESSES=0 - ;; - - --systemctl) - DO_SYSTEMCTL=1 - ;; - --no-systemctl) - DO_SYSTEMCTL=0 - ;; - - --) - # End of all options. - shift - break - ;; - -?*) - # ignore unknown options - printf 'WARN: Unknown option : %s\n' "$1" >&2 - exit 1 - ;; - *) - # Default case: If no more options then break out of the loop. - break - ;; - esac - - shift -done - -# Default values -: "${VERBOSE:=0}" -: "${FORCE:=0}" -: "${DO_ETC:=0}" -: "${DO_DPKG_FULL:=0}" -: "${DO_DPKG_STATUS:=1}" -: "${DO_APT_STATES:=1}" -: "${DO_APT_CONFIG:=1}" -: "${DO_PACKAGES:=1}" -: "${DO_PROCESSES:=1}" -: "${DO_UNAME:=1}" -: "${DO_UPTIME:=1}" -: "${DO_NETSTAT:=1}" -: "${DO_NETCFG:=1}" -: "${DO_IPTABLES:=1}" -: "${DO_SYSCTL:=1}" -: "${DO_VIRSH:=1}" -: "${DO_LXC:=1}" -: "${DO_DISKS:=1}" -: "${DO_MOUNT:=1}" -: "${DO_DF:=1}" -: "${DO_DMESG:=1}" -: "${DO_MYSQL_PROCESSES:=1}" -: "${DO_SYSTEMCTL:=1}" - -export LC_ALL=C - -set -u - -main diff --git a/evolinux-base/files/dump-server-state.sh b/evolinux-base/files/dump-server-state.sh new file mode 100644 index 00000000..5f76413f --- /dev/null +++ b/evolinux-base/files/dump-server-state.sh @@ -0,0 +1,1177 @@ +#!/bin/sh + +PROGNAME="dump-server-state" +REPOSITORY="https://gitea.evolix.org/evolix/dump-server-state" + +VERSION="22.04.3" +readonly VERSION + +dump_dir= +rc=0 + +# base functions + +show_version() { + cat <, + Jérémy Lecour , + Éric Morino , + Brice Waegeneire + and others. + +${REPOSITORY} + +${PROGNAME} comes with ABSOLUTELY NO WARRANTY.This is free software, +and you are welcome to redistribute it under certain conditions. +See the GNU General Public License v3.0 for details. +END +} +show_help() { + cat < "${dump_dir}/apt-config.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* apt-config OK" + else + debug "* apt-config ERROR" + debug "${last_result}" + rc=10 + fi + else + debug "* apt-config is not found" + fi +} + +task_dpkg_full() { + debug "Task: DPkg full state" + + dir_state_status="/var/lib/dpkg/status" + + apt_config_bin=$(command -v apt-config) + + if [ -n "${apt_config_bin}" ]; then + eval "$(${apt_config_bin} shell dir_state_status Dir::State::status)" + fi + + dpkg_dir=$(dirname "${dir_state_status}") + + last_result=$(mkdir -p "${dump_dir}${dpkg_dir}" && chmod -R 755 "${dump_dir}${dpkg_dir}") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* mkdir/chmod OK" + else + debug "* mkdir/chmod ERROR" + debug "${last_result}" + rc=10 + fi + + rsync_bin=$(command -v rsync) + + if [ -n "${rsync_bin}" ]; then + last_result=$(${rsync_bin} -ah --itemize-changes --exclude='*-old' "${dpkg_dir}/" "${dump_dir}${dpkg_dir}/") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* rsync OK" + else + debug "* rsync ERROR :" + debug "${last_result}" + rc=10 + fi + else + debug "* rsync not found" + + last_result=$(cp -r "${dpkg_dir}/*" "${dump_dir}${dpkg_dir}/" && rm -rf "${dump_dir}${dpkg_dir}/*-old") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* cp OK" + else + debug "* cp ERROR :" + debug "${last_result}" + rc=10 + fi + fi +} + +task_dpkg_status() { + debug "Task: DPkg status" + + dir_state_status="/var/lib/dpkg/status" + + apt_config_bin=$(command -v apt-config) + + if [ -n "${apt_config_bin}" ]; then + eval "$(${apt_config_bin} shell dir_state_status Dir::State::status)" + fi + + last_result=$(cp "${dir_state_status}" "${dump_dir}/dpkg-status.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* cp OK" + else + debug "* cp ERROR :" + debug "${last_result}" + rc=10 + fi +} + +task_packages() { + debug "Task: List of installed package" + + dpkg_bin=$(command -v dpkg) + + if [ -n "${dpkg_bin}" ]; then + last_result=$(${dpkg_bin} --get-selections "*" > "${dump_dir}/current_packages.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* dpkg OK" + else + debug "* dpkg ERROR :" + debug "${last_result}" + rc=10 + fi + else + debug "* dpkg not found" + fi +} + +task_uname() { + debug "Task: uname" + + last_result=$(uname -a > "${dump_dir}/uname.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* uname OK" + else + debug "* uname ERROR" + debug "${last_result}" + rc=10 + fi +} + +task_uptime() { + debug "Task: uptime" + + last_result=$(uptime > "${dump_dir}/uptime.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* uptime OK" + else + debug "* uptime ERROR" + debug "${last_result}" + rc=10 + fi +} + +task_processes() { + debug "Task: Process list" + + last_result=$(ps fauxw > "${dump_dir}/ps.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* ps OK" + else + debug "* ps ERROR" + debug "${last_result}" + rc=10 + fi + + pstree_bin=$(command -v pstree) + + if [ -n "${pstree_bin}" ]; then + last_result=$(${pstree_bin} -pan > "${dump_dir}/pstree.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* pstree OK" + else + debug "* pstree ERROR" + debug "${last_result}" + rc=10 + fi + fi +} + +task_netstat() { + debug "Task: Network status" + + ss_bin=$(command -v ss) + + if [ -n "${ss_bin}" ]; then + last_result=$(${ss_bin} -tanpul > "${dump_dir}/netstat-ss.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* ss OK" + else + debug "* ss ERROR" + debug "${last_result}" + rc=10 + fi + else + debug "* ss not found" + fi + + netstat_bin=$(command -v netstat) + + if [ -n "${netstat_bin}" ]; then + last_result=$(netstat -laputen > "${dump_dir}/netstat-legacy.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* netstat OK" + else + debug "* netstat ERROR" + debug "${last_result}" + rc=10 + fi + else + debug "* netstat not found" + fi +} + +task_netcfg() { + debug "Task: Network configuration" + + ip_bin=$(command -v ip) + + if [ -n "${ip_bin}" ]; then + last_result=$(${ip_bin} address show > "${dump_dir}/ip-address.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* ip address OK" + else + debug "* ip address ERROR" + debug "${last_result}" + rc=10 + fi + + last_result=$(${ip_bin} route show > "${dump_dir}/ip-route.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* ip route OK" + else + debug "* ip route ERROR" + debug "${last_result}" + rc=10 + fi + else + debug "* ip not found" + + ifconfig_bin=$(command -v ifconfig) + + if [ -n "${ifconfig_bin}" ]; then + last_result=$(${ifconfig_bin} > "${dump_dir}/ifconfig.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* ifconfig OK" + else + debug "* ifconfig ERROR" + debug "${last_result}" + rc=10 + fi + else + debug "* ifconfig not found" + fi + fi +} + +task_iptables() { + debug "Task: iptables" + + iptables_bin=$(command -v iptables) + ip6tables_bin=$(command -v ip6tables) + + if [ -n "${iptables_bin}" ]; then + last_result=$({ + printf "#### iptables --list ###############################\n" + ${iptables_bin} --list --numeric --verbose --line-numbers + printf "\n### iptables --table nat --list ####################\n" + ${iptables_bin} --table nat --list --numeric --verbose --line-numbers + printf "\n#### iptables --table mangle --list ################\n" + ${iptables_bin} --table mangle --list --numeric --verbose --line-numbers + if [ -n "${ip6tables_bin}" ]; then + printf "\n#### ip6tables --list ##############################\n" + ${ip6tables_bin} --list --numeric --verbose --line-numbers + printf "\n#### ip6tables --table mangle --list ###############\n" + ${ip6tables_bin} --table mangle --list --numeric --verbose --line-numbers + fi + } > "${dump_dir}/iptables-v.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* iptables -v OK" + else + debug "* iptables -v ERROR" + debug "${last_result}" + # Ignore errors because we don't know if this is nft related or a real error + # rc=10 + fi + + last_result=$({ + printf "#### iptables --list ###############################\n" + ${iptables_bin} --list --numeric + printf "\n### iptables --table nat --list ####################\n" + ${iptables_bin} --table nat --list --numeric + printf "\n#### iptables --table mangle --list ################\n" + ${iptables_bin} --table mangle --list --numeric + if [ -n "${ip6tables_bin}" ]; then + printf "\n#### ip6tables --list ##############################\n" + ${ip6tables_bin} --list --numeric + printf "\n#### ip6tables --table mangle --list ###############\n" + ${ip6tables_bin} --table mangle --list --numeric + fi + } > "${dump_dir}/iptables.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* iptables OK" + else + debug "* iptables ERROR" + debug "${last_result}" + # Ignore errors because we don't know if this is nft related or a real error + # rc=10 + fi + else + debug "* iptables not found" + fi + + iptables_save_bin=$(command -v iptables-save) + + if [ -n "${iptables_save_bin}" ]; then + last_result=$(${iptables_save_bin} > "${dump_dir}/iptables-save.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* iptables-save OK" + else + debug "* iptables-save ERROR" + debug "${last_result}" + # Ignore errors because we don't know if this is nft related or a real error + # rc=10 + fi + else + debug "* iptables-save not found" + fi + + nft_bin=$(command -v nft) + + if [ -n "${nft_bin}" ]; then + last_result=$(${nft_bin} list ruleset > "${dump_dir}/nft-ruleset.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* nft ruleset OK" + else + debug "* nft ruleset ERROR" + debug "${last_result}" + rc=10 + fi + fi +} + +task_sysctl() { + debug "Task: sysctl values" + + sysctl_bin=$(command -v sysctl) + + if [ -n "${sysctl_bin}" ]; then + last_result=$(${sysctl_bin} -a --ignore 2>/dev/null | sort -h > "${dump_dir}/sysctl.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* sysctl OK" + else + debug "* sysctl ERROR" + debug "${last_result}" + rc=10 + fi + else + debug "* sysctl not found" + fi +} + +task_virsh() { + debug "Task: virsh list" + + virsh_bin=$(command -v virsh) + + if [ -n "${virsh_bin}" ]; then + last_result=$(${virsh_bin} list --all > "${dump_dir}/virsh-list.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* virsh list OK" + else + debug "* virsh list ERROR" + debug "${last_result}" + rc=10 + fi + else + debug "* virsh not found" + fi +} + +task_lxc() { + debug "Task: lxc list" + + lxc_ls_bin=$(command -v lxc-ls) + + if [ -n "${lxc_ls_bin}" ]; then + last_result=$(${lxc_ls_bin} --fancy > "${dump_dir}/lxc-list.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* lxc list OK" + else + debug "* lxc list ERROR" + debug "${last_result}" + rc=10 + fi + else + debug "* lxc-ls not found" + fi +} + +task_disks() { + debug "Task: Disks" + + lsblk_bin=$(command -v lsblk) + awk_bin=$(command -v awk) + + if [ -n "${lsblk_bin}" ] && [ -n "${awk_bin}" ]; then + disks=$(${lsblk_bin} -l | grep disk | grep -v -E '(drbd|fd[0-9]+)' | ${awk_bin} '{print $1}') + for disk in ${disks}; do + dd_bin=$(command -v dd) + if [ -n "${dd_bin}" ]; then + last_result=$(${dd_bin} if="/dev/${disk}" of="${dump_dir}/MBR-${disk}" bs=512 count=1 2>&1) + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* dd ${disk} OK" + else + debug "* dd ${disk} ERROR" + debug "${last_result}" + rc=10 + fi + else + debug "* dd not found" + fi + fdisk_bin=$(command -v fdisk) + if [ -n "${fdisk_bin}" ]; then + last_result=$(${fdisk_bin} -l "/dev/${disk}" > "${dump_dir}/partitions-${disk}" 2>&1) + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* fdisk ${disk} OK" + else + debug "* fdisk ${disk} ERROR" + debug "${last_result}" + rc=10 + fi + else + debug "* fdisk not found" + fi + done + cat "${dump_dir}"/partitions-* > "${dump_dir}/partitions" + else + if [ -n "${lsblk_bin}" ]; then + debug "* lsblk not found" + fi + if [ -n "${awk_bin}" ]; then + debug "* awk not found" + fi + fi +} + +task_mount() { + debug "Task: Mount points" + + findmnt_bin=$(command -v findmnt) + + if [ -n "${findmnt_bin}" ]; then + last_result=$(${findmnt_bin} > "${dump_dir}/mount.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* mount points OK" + else + debug "* mount points ERROR" + debug "${last_result}" + rc=10 + fi + else + debug "* findmnt not found" + + mount_bin=$(command -v mount) + + if [ -n "${mount_bin}" ]; then + last_result=$(${mount_bin} > "${dump_dir}/mount.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* mount points OK" + else + debug "* mount points ERROR" + debug "${last_result}" + rc=10 + fi + else + debug "* mount not found" + fi + fi +} + +task_df() { + debug "Task: df" + + df_bin=$(command -v df) + + if [ -n "${df_bin}" ]; then + last_result=$(${df_bin} --portability > "${dump_dir}/df.txt" 2>&1) + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* df OK" + else + debug "* df ERROR" + debug "${last_result}" + rc=10 + fi + else + debug "* df not found" + fi +} + +task_dmesg() { + debug "Task: dmesg" + + dmesg_bin=$(command -v dmesg) + + if [ -n "${dmesg_bin}" ]; then + last_result=$(${dmesg_bin} > "${dump_dir}/dmesg.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* dmesg OK" + else + debug "* dmesg ERROR" + debug "${last_result}" + rc=10 + fi + else + debug "* dmesg not found" + fi +} + +task_mysql_processes() { + debug "Task: MySQL processes" + + mysqladmin_bin=$(command -v mysqladmin) + + if [ -n "${mysqladmin_bin}" ]; then + # Look for local MySQL or MariaDB process + if pgrep mysqld > /dev/null || pgrep mariadbd > /dev/null; then + if ${mysqladmin_bin} ping > /dev/null 2>&1; then + ${mysqladmin_bin} --verbose processlist > "${dump_dir}/mysql-processlist.txt" 2> "${dump_dir}/mysql-processlist.err" + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* mysqladmin OK" + else + debug "* mysqladmin ERROR" + debug < "${dump_dir}/mysql-processlist.err" + rm "${dump_dir}/mysql-processlist.err" + rc=10 + fi + else + debug "* unable to ping with mysqladmin" + fi + else + debug "* no mysqld or mariadbd process is running" + fi + else + debug "* mysqladmin not found" + fi +} + +task_systemctl() { + debug "Task: Systemd services" + + systemctl_bin=$(command -v systemctl) + + if [ -n "${systemctl_bin}" ]; then + last_result=$(${systemctl_bin} --no-legend --state=failed --type=service > "${dump_dir}/systemctl-failed-services.txt") + last_rc=$? + + if [ ${last_rc} -eq 0 ]; then + debug "* failed services OK" + else + debug "* failed services ERROR" + debug "${last_result}" + rc=10 + fi + else + debug "* systemctl not found" + fi +} + +main() { + if [ -z "${dump_dir}" ]; then + echo "ERROR: You must provide the --dump-dir argument" >&2 + exit 1 + fi + + if [ -d "${dump_dir}" ]; then + if [ "${FORCE}" != "1" ]; then + echo "ERROR: The dump directory ${dump_dir} already exists. Delete it first." >&2 + exit 2 + fi + else + create_dump_dir + fi + + if [ "${TASK_ETC}" -eq 1 ]; then + task_etc + fi + if [ "${TASK_DPKG_FULL}" -eq 1 ]; then + task_dpkg_full + fi + if [ "${TASK_DPKG_STATUS}" -eq 1 ]; then + task_dpkg_status + fi + if [ "${TASK_APT_STATES}" -eq 1 ]; then + task_apt_states + fi + if [ "${TASK_APT_CONFIG}" -eq 1 ]; then + task_apt_config + fi + if [ "${TASK_PACKAGES}" -eq 1 ]; then + task_packages + fi + if [ "${TASK_PROCESSES}" -eq 1 ]; then + task_processes + fi + if [ "${TASK_UPTIME}" -eq 1 ]; then + task_uptime + fi + if [ "${TASK_UNAME}" -eq 1 ]; then + task_uname + fi + if [ "${TASK_NETSTAT}" -eq 1 ]; then + task_netstat + fi + if [ "${TASK_NETCFG}" -eq 1 ]; then + task_netcfg + fi + if [ "${TASK_IPTABLES}" -eq 1 ]; then + task_iptables + fi + if [ "${TASK_SYSCTL}" -eq 1 ]; then + task_sysctl + fi + if [ "${TASK_VIRSH}" -eq 1 ]; then + task_virsh + fi + if [ "${TASK_LXC}" -eq 1 ]; then + task_lxc + fi + if [ "${TASK_DISKS}" -eq 1 ]; then + task_disks + fi + if [ "${TASK_MOUNT}" -eq 1 ]; then + task_mount + fi + if [ "${TASK_DF}" -eq 1 ]; then + task_df + fi + if [ "${TASK_DMESG}" -eq 1 ]; then + task_dmesg + fi + if [ "${TASK_MYSQL_PROCESSES}" -eq 1 ]; then + task_mysql_processes + fi + if [ "${TASK_SYSTEMCTL}" -eq 1 ]; then + task_systemctl + fi + + + debug "=> Your dump is available at ${dump_dir}" + exit ${rc} +} + +# parse options +# based on https://gist.github.com/deshion/10d3cb5f88a21671e17a +while :; do + case $1 in + -h|-\?|--help) + show_help + exit 0 + ;; + -V|--version) + show_version + exit 0 + ;; + -v|--verbose) + VERBOSE=1 + ;; + + -f|--force) + FORCE=1 + ;; + + -d|--dump-dir) + # with value separated by space + if [ -n "$2" ]; then + dump_dir=$2 + shift + else + printf 'ERROR: "-d|--dump-dir" requires a non-empty option argument.\n' >&2 + exit 1 + fi + ;; + --dump-dir=?*) + # with value speparated by = + dump_dir=${1#*=} + ;; + --dump-dir=) + # without value + printf 'ERROR: "--dump-dir" requires a non-empty option argument.\n' >&2 + exit 1 + ;; + + --backup-dir) + printf 'WARNING: "--backup-dir" is deprecated in favor of "--dump-dir".\n' + if [ -n "${dump_dir}" ]; then + debug "Dump directory is already set, let's ignore this one." + else + debug "Dump directory is not set already, let's stay backward compatible." + # with value separated by space + if [ -n "$2" ]; then + dump_dir=$2 + shift + else + printf 'ERROR: "--backup-dir" requires a non-empty option argument.\n' >&2 + exit 1 + fi + fi + ;; + --backup-dir=?*) + # with value speparated by = + printf 'WARNING: "--backup-dir" is deprecated in favor of "--dump-dir".\n' + if [ -n "${dump_dir}" ]; then + debug "Dump directory is already set, let's ignore this one." + else + debug "Dump directory is not set already, let's stay backward compatible." + dump_dir=${1#*=} + fi + ;; + --backup-dir=) + # without value + printf 'WARNING: "--backup-dir" is deprecated in favor of "--dump-dir".\n' + if [ -n "${dump_dir}" ]; then + debug "Dump directory is already set, let's ignore this one." + else + printf 'ERROR: "--backup-dir" requires a non-empty option argument.\n' >&2 + exit 1 + fi + ;; + + --all) + for option in \ + TASK_ETC \ + TASK_DPKG_FULL \ + TASK_DPKG_STATUS \ + TASK_APT_STATES \ + TASK_APT_CONFIG \ + TASK_PACKAGES \ + TASK_PROCESSES \ + TASK_UNAME \ + TASK_UPTIME \ + TASK_NETSTAT \ + TASK_NETCFG \ + TASK_IPTABLES \ + TASK_SYSCTL \ + TASK_VIRSH \ + TASK_LXC \ + TASK_DISKS \ + TASK_MOUNT \ + TASK_DF \ + TASK_DMESG \ + TASK_MYSQL_PROCESSES \ + TASK_SYSTEMCTL + do + eval "${option}=1" + done + ;; + + --none) + for option in \ + TASK_ETC \ + TASK_DPKG_FULL \ + TASK_DPKG_STATUS \ + TASK_APT_STATES \ + TASK_APT_CONFIG \ + TASK_PACKAGES \ + TASK_PROCESSES \ + TASK_UNAME \ + TASK_UPTIME \ + TASK_NETSTAT \ + TASK_NETCFG \ + TASK_IPTABLES \ + TASK_SYSCTL \ + TASK_VIRSH \ + TASK_LXC \ + TASK_DISKS \ + TASK_MOUNT \ + TASK_DF \ + TASK_DMESG \ + TASK_MYSQL_PROCESSES \ + TASK_SYSTEMCTL + do + eval "${option}=0" + done + ;; + + --etc) + TASK_ETC=1 + ;; + --no-etc) + TASK_ETC=0 + ;; + + --dpkg-full) + TASK_DPKG_FULL=1 + ;; + --no-dpkg-full) + TASK_DPKG_FULL=0 + ;; + + --dpkg-status) + TASK_DPKG_STATUS=1 + ;; + --no-dpkg-status) + TASK_DPKG_STATUS=0 + ;; + + --apt-states) + TASK_APT_STATES=1 + ;; + --no-apt-states) + TASK_APT_STATES=0 + ;; + + --apt-config) + TASK_APT_CONFIG=1 + ;; + --no-apt-config) + TASK_APT_CONFIG=0 + ;; + + --packages) + TASK_PACKAGES=1 + ;; + --no-packages) + TASK_PACKAGES=0 + ;; + + --processes) + TASK_PROCESSES=1 + ;; + --no-processes) + TASK_PROCESSES=0 + ;; + + --uptime) + TASK_UPTIME=1 + ;; + --no-uptime) + TASK_UPTIME=0 + ;; + + --uname) + TASK_UNAME=1 + ;; + --no-uname) + TASK_UNAME=0 + ;; + + --netstat) + TASK_NETSTAT=1 + ;; + --no-netstat) + TASK_NETSTAT=0 + ;; + + --netcfg) + TASK_NETCFG=1 + ;; + --no-netcfg) + TASK_NETCFG=0 + ;; + + --iptables) + TASK_IPTABLES=1 + ;; + --no-iptables) + TASK_IPTABLES=0 + ;; + + --sysctl) + TASK_SYSCTL=1 + ;; + --no-sysctl) + TASK_SYSCTL=0 + ;; + + --virsh) + TASK_VIRSH=1 + ;; + --no-virsh) + TASK_VIRSH=0 + ;; + + --lxc) + TASK_LXC=1 + ;; + --no-lxc) + TASK_LXC=0 + ;; + + --disks) + TASK_DISKS=1 + ;; + --no-disks) + TASK_DISKS=0 + ;; + + --mount) + TASK_MOUNT=1 + ;; + --no-mount) + TASK_MOUNT=0 + ;; + + --df) + TASK_DF=1 + ;; + --no-df) + TASK_DF=0 + ;; + + --dmesg) + TASK_DMESG=1 + ;; + --no-dmesg) + TASK_DMESG=0 + ;; + + --mysql-processes) + TASK_MYSQL_PROCESSES=1 + ;; + --no-mysql-processes) + TASK_MYSQL_PROCESSES=0 + ;; + + --systemctl) + TASK_SYSTEMCTL=1 + ;; + --no-systemctl) + TASK_SYSTEMCTL=0 + ;; + + --) + # End of all options. + shift + break + ;; + -?*) + # ignore unknown options + printf 'WARN: Unknown option : %s\n' "$1" >&2 + exit 1 + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift +done + +# Default values +: "${VERBOSE:=0}" +: "${FORCE:=0}" +: "${TASK_ETC:=0}" +: "${TASK_DPKG_FULL:=0}" +: "${TASK_DPKG_STATUS:=1}" +: "${TASK_APT_STATES:=1}" +: "${TASK_APT_CONFIG:=1}" +: "${TASK_PACKAGES:=1}" +: "${TASK_PROCESSES:=1}" +: "${TASK_UNAME:=1}" +: "${TASK_UPTIME:=1}" +: "${TASK_NETSTAT:=1}" +: "${TASK_NETCFG:=1}" +: "${TASK_IPTABLES:=1}" +: "${TASK_SYSCTL:=1}" +: "${TASK_VIRSH:=1}" +: "${TASK_LXC:=1}" +: "${TASK_DISKS:=1}" +: "${TASK_MOUNT:=1}" +: "${TASK_DF:=1}" +: "${TASK_DMESG:=1}" +: "${TASK_MYSQL_PROCESSES:=1}" +: "${TASK_SYSTEMCTL:=1}" + +export LC_ALL=C + +set -u + +main diff --git a/evolinux-base/files/topdefaultrc b/evolinux-base/files/topdefaultrc index b49be289..1faac8ba 100644 --- a/evolinux-base/files/topdefaultrc +++ b/evolinux-base/files/topdefaultrc @@ -1,15 +1,15 @@ top's Config File (Linux processes with windows) Id:j, Mode_altscr=0, Mode_irixps=1, Delay_time=3.0, Curwin=0 -Def fieldscur=Ä·&')*+,-./012568>?ABCFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz - winflags=193844, sortindx=18, maxtasks=0, graph_cpus=0, graph_mems=0, double_up=0, combine_cpus=0 - summclr=1, msgsclr=1, headclr=3, taskclr=1 -Job fieldscur=(Ä»@<)*+,-./012568>?ABCFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz - winflags=193844, sortindx=0, maxtasks=0, graph_cpus=0, graph_mems=0, double_up=0, combine_cpus=0 - summclr=6, msgsclr=6, headclr=7, taskclr=6 -Mem fieldscur=?@ABCFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz - winflags=193844, sortindx=3, maxtasks=0, graph_cpus=0, graph_mems=0, double_up=0, combine_cpus=0 - summclr=3, msgsclr=3, headclr=2, taskclr=3 +Def fieldscur=¥¨³´»½À¼Ä·º¹Å&')*+,-./012568>?ABCFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz + winflags=177460, sortindx=18, maxtasks=0, graph_cpus=0, graph_mems=0, double_up=0, combine_cpus=0 + summclr=1, msgsclr=1, headclr=3, taskclr=1 +Job fieldscur=¥¦¹·º(³´Ä»½@<§Å)*+,-./012568>?ABCFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz + winflags=193844, sortindx=0, maxtasks=0, graph_cpus=0, graph_mems=0, double_up=0, combine_cpus=0 + summclr=6, msgsclr=6, headclr=7, taskclr=6 +Mem fieldscur=¥º»<½¾¿ÀÁMBNÃD34·Å&'()*+,-./0125689FGHIJKLOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz + winflags=193844, sortindx=21, maxtasks=0, graph_cpus=0, graph_mems=0, double_up=0, combine_cpus=0 + summclr=5, msgsclr=5, headclr=4, taskclr=5 +Usr fieldscur=¥¦§¨ª°¹·ºÄÅ)+,-./1234568;<=>?@ABCFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz + winflags=193844, sortindx=3, maxtasks=0, graph_cpus=0, graph_mems=0, double_up=0, combine_cpus=0 + summclr=3, msgsclr=3, headclr=2, taskclr=3 Fixed_widest=0, Summ_mscale=1, Task_mscale=0, Zero_suppress=0 diff --git a/evolinux-base/handlers/main.yml b/evolinux-base/handlers/main.yml index 80b7378e..7331a245 100644 --- a/evolinux-base/handlers/main.yml +++ b/evolinux-base/handlers/main.yml @@ -72,3 +72,8 @@ name: postfix state: reloaded +- name: restart log2mail + service: + name: log2mail + state: restarted + diff --git a/evolinux-base/tasks/hardware.yml b/evolinux-base/tasks/hardware.yml index ab838284..2e68cc36 100644 --- a/evolinux-base/tasks/hardware.yml +++ b/evolinux-base/tasks/hardware.yml @@ -32,11 +32,14 @@ ## Dedicated hardware -- name: Install freepmi when it's dedicated hardware +- name: Install some additionnals tools when it dedicated hardware apt: name: - libipc-run-perl - freeipmi + - ipmitool + - firmware-linux-nonfree + - intel-microcode state: present tags: - packages diff --git a/evolinux-base/tasks/log2mail.yml b/evolinux-base/tasks/log2mail.yml index e6f624c1..35ce19cf 100644 --- a/evolinux-base/tasks/log2mail.yml +++ b/evolinux-base/tasks/log2mail.yml @@ -16,3 +16,20 @@ daemon-reload: yes state: started enabled: yes + +- name: log2mail config is present + blockinfile: + dest: /etc/log2mail/config/default + owner: log2mail + group: adm + mode: "0640" + marker: "# {mark} ANSIBLE MANAGED RULES FOR DEFAULT INSTANCE" + block: | + file = /var/log/syslog + pattern = "Out of memory: Kill" + mailto = {{ log2mail_alert_email or general_alert_email | mandatory }} + template = /etc/log2mail/mail + notify: restart log2mail + tags: + - log2mail + diff --git a/evolinux-base/tasks/main.yml b/evolinux-base/tasks/main.yml index 5a0532a3..dba5e97b 100644 --- a/evolinux-base/tasks/main.yml +++ b/evolinux-base/tasks/main.yml @@ -14,6 +14,7 @@ apt_install_basics: "{{ evolinux_apt_replace_default_sources }}" apt_install_evolix_public: "{{ evolinux_apt_public_sources }}" apt_upgrade: "{{ evolinux_apt_upgrade }}" + apt_basics_components: "{{ 'main contrib non-free' if ansible_virtualization_role == 'host' else 'main' }}" when: evolinux_apt_include | bool - name: /etc versioning with Git diff --git a/evolinux-base/tasks/top.yml b/evolinux-base/tasks/top.yml index 12eff20c..64fdf6b6 100644 --- a/evolinux-base/tasks/top.yml +++ b/evolinux-base/tasks/top.yml @@ -2,6 +2,6 @@ - name: Deploy top configuration file copy: # The config format is unredable; ATM it only add the SWAP column - src: htoprc + src: topdefaultrc dest: /etc/topdefaultrc mode: "0644" diff --git a/evolinux-base/tasks/utils.yml b/evolinux-base/tasks/utils.yml index a5ff56fa..084f8b35 100644 --- a/evolinux-base/tasks/utils.yml +++ b/evolinux-base/tasks/utils.yml @@ -3,15 +3,22 @@ - include_role: name: evolix/remount-usr -- name: backup-server-state script is present +- name: dump-server-state script is present copy: - src: "backup-server-state.sh" - dest: /usr/local/sbin/backup-server-state + src: "dump-server-state.sh" + dest: /usr/local/sbin/dump-server-state force: True owner: root group: root mode: "0750" +- name: symlink backup-server-state to dump-server-state + file: + src: /usr/local/sbin/dump-server-state + dest: /usr/local/sbin/backup-server-state + state: link + force: yes + - name: "/sbin/deny script is present" copy: src: deny.sh diff --git a/evomaintenance/handlers/main.yml b/evomaintenance/handlers/main.yml index 85884f73..37c9af95 100644 --- a/evomaintenance/handlers/main.yml +++ b/evomaintenance/handlers/main.yml @@ -3,8 +3,9 @@ - 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" - changed_when: "'starting IPTables rules is now finish : OK' in minifirewall_init_restart.stdout" + failed_when: + - "'starting IPTables rules is now finish : OK' not in minifirewall_init_restart.stdout" + - "'minifirewall started' not in minifirewall_init_restart.stdout" - name: restart minifirewall (noop) meta: noop diff --git a/evomaintenance/tasks/install_vendor_other.yml b/evomaintenance/tasks/install_vendor_other.yml new file mode 100644 index 00000000..a28eeab3 --- /dev/null +++ b/evomaintenance/tasks/install_vendor_other.yml @@ -0,0 +1,31 @@ +--- + +- include_role: + name: evolix/remount-usr + tags: + - evomaintenance + +- name: /usr/share/scripts exists + file: + dest: /usr/share/scripts + mode: "0700" + owner: root + group: root + state: directory + tags: + - evomaintenance + +- name: Evomaintenance script and template are installed + copy: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + owner: root + group: root + mode: "{{ item.mode }}" + force: yes + backup: yes + loop: + - { src: 'evomaintenance.sh', dest: '/usr/share/scripts/', mode: '0700' } + - { src: 'evomaintenance.tpl', dest: '/usr/share/scripts/', mode: '0600' } + tags: + - evomaintenance \ No newline at end of file diff --git a/evomaintenance/tasks/main.yml b/evomaintenance/tasks/main.yml index 0a4e5010..1f4a6f55 100644 --- a/evomaintenance/tasks/main.yml +++ b/evomaintenance/tasks/main.yml @@ -10,6 +10,12 @@ - evomaintenance_install_vendor | bool - ansible_distribution == "Debian" +- include: install_vendor_other.yml + when: + - evomaintenance_install_vendor | bool + - ansible_distribution != "Debian" + + - include: config.yml - include: minifirewall.yml diff --git a/generate-ldif/templates/generateldif.sh.j2 b/generate-ldif/templates/generateldif.sh.j2 index 3b2a6fd2..86bfc0eb 100755 --- a/generate-ldif/templates/generateldif.sh.j2 +++ b/generate-ldif/templates/generateldif.sh.j2 @@ -608,11 +608,11 @@ if is_pkg_installed lxc; then if lxc-ls | grep -q php56 ; then cat <> "${ldif_file}" -dn: ServiceName=ServiceName=php-fpm56,${computer_dn} +dn: ServiceName=php-fpm56,${computer_dn} NagiosEnabled: TRUE ipServiceProtocol: tcp objectClass: EvoService -ServiceName: PHP-FPM (multiphp) +ServiceName: php-fpm56 ipServicePort: 443 ServiceType: web ServiceVersion: PHP-FPM 5.6 (multiphp) @@ -622,11 +622,11 @@ fi if lxc-ls | grep -q php70 ; then cat <> "${ldif_file}" -dn: ServiceName=ServiceName=php-fpm70,${computer_dn} +dn: ServiceName=php-fpm70,${computer_dn} NagiosEnabled: TRUE ipServiceProtocol: tcp objectClass: EvoService -ServiceName: PHP-FPM (multiphp) +ServiceName: php-fpm70 ipServicePort: 443 ServiceType: web ServiceVersion: PHP-FPM 7.0 (multiphp) @@ -636,11 +636,11 @@ fi if lxc-ls | grep -q php73 ; then cat <> "${ldif_file}" -dn: ServiceName=ServiceName=php-fpm73,${computer_dn} +dn: ServiceName=php-fpm73,${computer_dn} NagiosEnabled: TRUE ipServiceProtocol: tcp objectClass: EvoService -ServiceName: PHP-FPM (multiphp) +ServiceName: php-fpm73 ipServicePort: 443 ServiceType: web ServiceVersion: PHP-FPM 7.3 (multiphp) @@ -650,11 +650,11 @@ fi if lxc-ls | grep -q php74 ; then cat <> "${ldif_file}" -dn: ServiceName=ServiceName=php-fpm74,${computer_dn} +dn: ServiceName=php-fpm74,${computer_dn} NagiosEnabled: TRUE ipServiceProtocol: tcp objectClass: EvoService -ServiceName: PHP-FPM (multiphp) +ServiceName: php-fpm74 ipServicePort: 443 ServiceType: web ServiceVersion: PHP-FPM 7.4 (multiphp) @@ -664,11 +664,11 @@ fi if lxc-ls | grep -q php80 ; then cat <> "${ldif_file}" -dn: ServiceName=ServiceName=php-fpm80,${computer_dn} +dn: ServiceName=php-fpm80,${computer_dn} NagiosEnabled: TRUE ipServiceProtocol: tcp objectClass: EvoService -ServiceName: PHP-FPM (multiphp) +ServiceName: php-fpm80 ipServicePort: 443 ServiceType: web ServiceVersion: PHP-FPM 8.0 (multiphp) @@ -678,11 +678,11 @@ fi if lxc-ls | grep -q php81 ; then cat <> "${ldif_file}" -dn: ServiceName=ServiceName=php-fpm81,${computer_dn} +dn: ServiceName=php-fpm81,${computer_dn} NagiosEnabled: TRUE ipServiceProtocol: tcp objectClass: EvoService -ServiceName: PHP-FPM (multiphp) +ServiceName: php-fpm81 ipServicePort: 443 ServiceType: web ServiceVersion: PHP-FPM 8.1 (multiphp) @@ -709,6 +709,37 @@ EOT fi +# bkctld +if is_pkg_installed bkctld; then + bkctld_version=$(get_pkg_version bkctld) +fi +if [ -n "${bkctld_version}" ]; then + cat <> "${ldif_file}" + +dn: ServiceName=bkctld_jails,${computer_dn} +NagiosEnabled: TRUE +objectClass: EvoService +ServiceName: bkctld_jails +ServiceType: backup +ServiceVersion: bkctld ${bkctld_version} + +dn: ServiceName=bkctld_setup,${computer_dn} +NagiosEnabled: TRUE +objectClass: EvoService +ServiceName: bkctld_setup +ServiceType: backup +ServiceVersion: bkctld ${bkctld_version} + +dn: ServiceName=disk-worktime,${computer_dn} +NagiosEnabled: TRUE +objectClass: EvoService +ServiceName: disk-worktime +ServiceType: disk +ServiceVersion: Undefined +EOT +fi + + # test if we have a stdout if [ -t 1 ]; then echo "Output is in ${ldif_file}" diff --git a/keepalived/tasks/main.yml b/keepalived/tasks/main.yml index 807713a8..e468da58 100644 --- a/keepalived/tasks/main.yml +++ b/keepalived/tasks/main.yml @@ -28,7 +28,7 @@ owner: root group: root force: yes - tags: + tags: - keepalived - nrpe diff --git a/kvm-host/tasks/ssh.yml b/kvm-host/tasks/ssh.yml index 54f69652..3c097abc 100644 --- a/kvm-host/tasks/ssh.yml +++ b/kvm-host/tasks/ssh.yml @@ -33,7 +33,7 @@ state: present special_time: "hourly" user: root - job: "rsync -a --delete /etc/libvirt/qemu/*xml {{ hostvars[kvm_pair]['lan.ip'] }}:/root/libvirt-{{ inventory_hostname }}/" + job: "if ls /etc/libvirt/qemu/*xml > /dev/null 2> /dev/null; then rsync -a --delete /etc/libvirt/qemu/*xml {{ hostvars[kvm_pair]['lan.ip'] }}:/root/libvirt-{{ inventory_hostname }}/; fi" when: - kvm_pair is defined - kvm_pair is not none diff --git a/lxc/templates/default.conf b/lxc/templates/default.conf index c4b38d42..a4f75d38 100644 --- a/lxc/templates/default.conf +++ b/lxc/templates/default.conf @@ -36,4 +36,6 @@ lxc.start.auto = 1 {% if ansible_distribution_major_version is version('9', '>') %} # Set LXC container unconfined in AppArmor lxc.apparmor.profile = unconfined +{% else %} +lxc.aa_profile = unconfined {% endif %} diff --git a/minifirewall/defaults/main.yml b/minifirewall/defaults/main.yml index fd4e726b..18d7d5b3 100644 --- a/minifirewall/defaults/main.yml +++ b/minifirewall/defaults/main.yml @@ -1,24 +1,35 @@ --- -minifirewall_main_file: /etc/default/minifirewall -minifirewall_tail_file: /etc/default/minifirewall.tail +# possible values: Null (default), modern or legacy +minifirewall_install_mode: Null + +# BEGIN legacy variables +minifirewall_legacy_main_file: /etc/default/minifirewall +minifirewall_legacy_tail_file: /etc/default/minifirewall.tail +# END legacy variabes + +minifirewall_tail_file: /etc/minifirewall.d/zzz-tail minifirewall_tail_included: False minifirewall_tail_force: True +# Overwrite files completely minifirewall_force_upgrade_script: False minifirewall_force_upgrade_config: False -minifirewall_git_url: "https://forge.evolix.org/minifirewall.git" -minifirewall_checkout_path: "/tmp/minifirewall" +# Update specific values in configuration +minifirewall_update_config: True + minifirewall_int: "{{ ansible_default_ipv4.interface }}" minifirewall_ipv6: "on" minifirewall_intlan: "{{ ansible_default_ipv4.address }}/32" minifirewall_docker: "off" minifirewall_default_trusted_ips: [] +minifirewall_legacy_fallback_trusted_ips: ['0.0.0.0/0'] +minifirewall_fallback_trusted_ips: ['0.0.0.0/0', '::/0'] minifirewall_additional_trusted_ips: [] -# and default to ['0.0.0.0/0'] if the result is still empty -minifirewall_trusted_ips: "{{ minifirewall_default_trusted_ips | union(minifirewall_additional_trusted_ips) | unique | default(['0.0.0.0/0'], true) }}" +# and default to ['0.0.0.0/0', '::/0'] if the result is still empty +minifirewall_trusted_ips: "{{ minifirewall_default_trusted_ips | union(minifirewall_additional_trusted_ips) | unique }}" minifirewall_privilegied_ips: [] minifirewall_protected_ports_tcp: [22] @@ -31,7 +42,7 @@ minifirewall_private_ports_tcp: [5666] minifirewall_private_ports_udp: [] # Keep a null value to leave the setting as is -# otherwise use an Array, eg. "minifirewall_ssh_ok: ['0.0.0.0/0']" +# otherwise use an Array, eg. "minifirewall_ssh_ok: ['0.0.0.0/0', '::/0']" minifirewall_dns_servers: Null minifirewall_http_sites: Null minifirewall_https_sites: Null @@ -41,6 +52,22 @@ minifirewall_smtp_ok: Null minifirewall_smtp_secure_ok: Null minifirewall_ntp_ok: Null +minifirewall_proxy: "off" +minifirewall_proxyport: 8888 +minifirewall_proxybypass: + - "${INTLAN}" + - "127.0.0.0/8" + - "::1/128" +minifirewall_backupservers: Null + +minifirewall_sysctl_icmp_echo_ignore_broadcasts : Null +minifirewall_sysctl_icmp_ignore_bogus_error_responses : Null +minifirewall_sysctl_accept_source_route : Null +minifirewall_sysctl_tcp_syncookies : Null +minifirewall_sysctl_icmp_redirects : Null +minifirewall_sysctl_rp_filter : Null +minifirewall_sysctl_log_martians : Null + minifirewall_autostart: False minifirewall_restart_if_needed: True minifirewall_restart_force: False diff --git a/minifirewall/files/blacklist-countries.sh b/minifirewall/files/blacklist-countries.sh new file mode 100644 index 00000000..3a3d20db --- /dev/null +++ b/minifirewall/files/blacklist-countries.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +ripedeny_file=/var/tmp/ripe_deny + +cd /var/tmp + +rm -f $ripedeny_file + +GET http://antispam00.evolix.org/spam/ripe.cidr.md5 > ripe.cidr.md5 +GET http://antispam00.evolix.org/spam/ripe.cidr > ripe.cidr + +for i in CN KR RU; do + + grep "^$i|" ripe.cidr >> $ripedeny_file + +done + +/sbin/iptables -F NEEDRESTRICT + +for i in $(cat $ripedeny_file); do + BLOCK=$(echo $i | cut -d"|" -f2) + /sbin/iptables -I NEEDRESTRICT -s $BLOCK -j DROP +done diff --git a/minifirewall/files/minifirewall b/minifirewall/files/minifirewall new file mode 100755 index 00000000..7dae5787 --- /dev/null +++ b/minifirewall/files/minifirewall @@ -0,0 +1,1099 @@ +#!/bin/sh +# shellcheck disable=SC2059 + +# minifirewall is a shell script for easy firewalling on a standalone server +# It uses netfilter/iptables http://netfilter.org/ designed for recent Linux kernel +# See https://gitea.evolix.org/evolix/minifirewall + +# Copyright (c) 2007-2022 Evolix +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 +# of the License. + +# Description +# script for standalone server + +# Start or stop minifirewall +# + +### BEGIN INIT INFO +# Provides: minifirewall +# Required-Start: +# Required-Stop: +# Should-Start: $network $syslog $named +# Should-Stop: $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: start and stop the firewall +# Description: Firewall designed for standalone server +### END INIT INFO + +VERSION="22.05" + +NAME="minifirewall" +# shellcheck disable=SC2034 +DESC="Firewall designed for standalone server" + +set -u + +# Variables configuration +######################### + +config_file="/etc/default/minifirewall" +includes_dir="/etc/minifirewall.d" + +# iptables paths +IPT=$(command -v iptables) +if [ -z "${IPT}" ]; then + echo "Unable to find 'iptables\` command in PATH." >&2 + exit 1 +fi +IPT6=$(command -v ip6tables) +if [ -z "${IPT6}" ]; then + echo "Unable to find 'ip6tables\` command in PATH." >&2 + exit 1 +fi + +# TCP/IP variables +LOOPBACK='127.0.0.0/8' +CLASSA='10.0.0.0/8' +CLASSB='172.16.0.0/12' +CLASSC='192.168.0.0/16' +CLASSD='224.0.0.0/4' +CLASSE='240.0.0.0/5' +ALL='0.0.0.0' +BROAD='255.255.255.255' +PORTSROOT='0:1023' +PORTSUSER='1024:65535' + +# Configuration + +INT='' +IPV6='' +DOCKER='' +INTLAN='' +TRUSTEDIPS='' +PRIVILEGIEDIPS='' +SERVICESTCP1p='' +SERVICESUDP1p='' +SERVICESTCP1='' +SERVICESUDP1='' +SERVICESTCP2='' +SERVICESUDP2='' +SERVICESTCP3='' +SERVICESUDP3='' +DNSSERVEURS='' +HTTPSITES='' +HTTPSSITES='' +FTPSITES='' +SSHOK='' +SMTPOK='' +SMTPSECUREOK='' +NTPOK='' +PROXY='' +PROXYBYPASS='' +PROXYPORT='' +BACKUPSERVERS='' + +LEGACY_CONFIG='off' + +STATE_FILE_LATEST='/var/run/minifirewall_state_latest' +STATE_FILE_CURRENT='/var/run/minifirewall_state_current' +STATE_FILE_PREVIOUS='/var/run/minifirewall_state_previous' +STATE_FILE_DIFF='/var/run/minifirewall_state_diff' + +LOGGER_BIN=$(command -v logger) + +# No colors by default +RED='' +GREEN='' +YELLOW='' +BLUE='' +MAGENTA='' +CYAN='' +WHITE='' +BOLD='' +RESET='' +# check if stdout is a terminal... +if [ -t 1 ]; then + + # see if it supports colors... + ncolors=$(tput colors) + + if [ -n "${ncolors}" ] && [ ${ncolors} -ge 8 ]; then + RED=$(tput setaf 1) + GREEN=$(tput setaf 2) + YELLOW=$(tput setaf 3) + BLUE=$(tput setaf 4) + MAGENTA=$(tput setaf 5) + CYAN=$(tput setaf 6) + WHITE=$(tput setaf 7) + BOLD=$(tput bold) + RESET='\e[m' + fi +fi + +## pseudo dry-run : +## Uncomment and call these functions instead of the real iptables and ip6tables commands +# IPT="fake_iptables" +# IPT6="fake_ip6tables" +# fake_iptables() { +# printf "DRY-RUN iptables %s\n" "$*" +# } +# fake_ip6tables() { +# printf "DRY-RUN ip6tables %s\n" "$*" +# } +## Beware that commands executed from included files are not modified by this trick. + +syslog_info() { + if [ -x "${LOGGER_BIN}" ]; then + ${LOGGER_BIN} -t "${NAME}" -p daemon.info "$1" + fi +} +syslog_error() { + if [ -x "${LOGGER_BIN}" ]; then + ${LOGGER_BIN} -t "${NAME}" -p daemon.error "$1" + fi +} +sort_values() { + echo "$*" | tr ' ' '\n' | sort -h +} +is_ipv6_enabled() { + test "${IPV6}" != "off" +} +is_docker_enabled() { + test "${DOCKER}" = "on" +} +is_proxy_enabled() { + test "${PROXY}" = "on" +} +is_ipv6() { + echo "$1" | grep -q ':' +} +is_legacy_config() { + test "${LEGACY_CONFIG}" != "off" +} +chain_exists() { + chain_name="$1" + if [ $# -ge 2 ]; then + intable="--table $2" + else + intable="" + fi + # shellcheck disable=SC2086 + iptables ${intable} -nL "${chain_name}" >/dev/null 2>&1 +} +source_file_or_error() { + file=$1 + syslog_info "sourcing \`${file}'" + printf "${BLUE}sourcing \`%s'${RESET}\n" "${file}" + + tmpfile=$(mktemp --tmpdir=/tmp minifirewall.XXX) + . "${file}" 2>"${tmpfile}" >&2 + + if [ -s "${tmpfile}" ]; then + syslog_error "Error while sourcing ${file}" + printf "${RED}%s returns standard or error output (see below). Stopping.${RESET}\n" ${file} >&2 + cat "${tmpfile}" + exit 1 + fi + rm -f "${tmpfile}" +} +source_configuration() { + if ! test -f ${config_file}; then + printf "${RED}%s does not exist${RESET}\n" "${config_file}" >&2 + + ## We still want to deal with this really old configuration file + ## even if it has been deprecated since Debian 8 + old_config_file="/etc/firewall.rc" + if test -f ${old_config_file}; then + printf "${YELLOW}%s is deprecated and ignored. Rename it to %s${RESET}\n" "${old_config_file}" "${config_file}" >&2 + fi + + exit 1 + fi + + # If we find something other than a blank line, a comment or a variable assignment + if grep --quiet --extended-regexp --invert-match "^\s*(#|$|\w+=)" "${config_file}"; then + # Backward compatible mode + ########################### + + printf "${YELLOW}legacy config detected${RESET}\n" + LEGACY_CONFIG='on' + + # Non-backward compatible mode + ############################### + + # If we ever want to remove the backward compatible mode + # we can remove the two lines above and uncomment the lines below. + # They break if any iptables/ip6tables command is found in the configuration file + + # echo "iptables/ip6tables commands found in ${config_file}." >&2 + # echo "Move them in included files (in ${includes_dir})." >&2 + # exit 1 + fi + + if is_legacy_config; then + # In this mode, we extract all variable definitions + # to a temporary file that we can source. + # It allow iptables/ip6tables commands to remain in the configuration file + # and not interfere with the configuration step. + + tmp_config_file=$(mktemp --tmpdir=/tmp minifirewall.XXX) + # get only variable assignments + grep -E "^\s*\w+=" "${config_file}" > "${tmp_config_file}" + + source_file_or_error "${tmp_config_file}" + rm -f "${tmp_config_file}" + else + source_file_or_error "${config_file}" + fi +} +source_includes() { + if [ -d "${includes_dir}" ]; then + include_files=$(find ${includes_dir} -type f -readable -not -name '*.*' | sort -h) + for include_file in ${include_files}; do + source_file_or_error "${include_file}" + done + fi +} +check_unpersisted_state() { + cmp_bin=$(command -v cmp) + diff_bin=$(command -v diff) + + if [ -z "${cmp_bin}" ]; then + printf "${YELLOW}skip state comparison (Can't find cmp command)${RESET}\n" >&2 + elif [ -z "${diff_bin}" ]; then + printf "${YELLOW}skip state comparison (Can't find diff command)${RESET}\n" >&2 + else + # store current state + mkdir -p "$(dirname "${STATE_FILE_CURRENT}")" + status_without_numbers > "${STATE_FILE_CURRENT}" + + # clean previous diff file + rm -f "${STATE_FILE_DIFF}" + + if [ -f "${STATE_FILE_LATEST}" ]; then + cmp_result=$(cmp "${STATE_FILE_LATEST}" "${STATE_FILE_CURRENT}") + cmp_rc=$? + + if [ ${cmp_rc} -eq 0 ]; then + # echo " rules have not changed since latest start" + : + elif [ ${cmp_rc} -eq 1 ]; then + diff -u "${STATE_FILE_LATEST}" "${STATE_FILE_CURRENT}" > "${STATE_FILE_DIFF}" + printf "${YELLOW}WARNING: current state is different than persisted state, check %s${RESET}\n" "${STATE_FILE_DIFF}" >&2 + else + printf "${RED}ERROR comparing rules:${RESET}\n" >&2 + echo "${cmp_result}" >&2 + fi + fi + # cleanup + rm -f "${STATE_FILE_CURRENT}" + fi +} +report_state_changes() { + cmp_bin=$(command -v cmp) + diff_bin=$(command -v diff) + + if [ -z "${cmp_bin}" ]; then + printf "${YELLOW}skip state comparison (Can't find cmp command)${RESET}\n" >&2 + return + elif [ -z "${diff_bin}" ]; then + printf "${YELLOW}skip state comparison (Can't find diff command)${RESET}\n" >&2 + else + # If there is a known state + # let's compare it with the current state + if [ -f "${STATE_FILE_LATEST}" ]; then + check_unpersisted_state + fi + + # Then reset the known state + mkdir -p "$(dirname "${STATE_FILE_LATEST}")" + status_without_numbers > "${STATE_FILE_LATEST}" + + # But if there is a previous known state + # let's compare with the new known state + if [ -f "${STATE_FILE_PREVIOUS}" ]; then + cmp_result=$(cmp "${STATE_FILE_PREVIOUS}" "${STATE_FILE_LATEST}") + cmp_rc=$? + + if [ ${cmp_rc} -eq 0 ]; then + # echo "Rules have not changed since previous start" + : + elif [ ${cmp_rc} -eq 1 ]; then + diff -u "${STATE_FILE_PREVIOUS}" "${STATE_FILE_LATEST}" > "${STATE_FILE_DIFF}" + printf "${YELLOW}INFO: rules have changed since latest start, check %s${RESET}\n" "${STATE_FILE_DIFF}" >&2 + else + printf "${RED}ERROR comparing rules:${RESET}\n" >&2 + echo "${cmp_result}" >&2 + fi + fi + fi +} + +start() { + syslog_info "starting" + printf "${BOLD}${NAME} starting${RESET}\n" + + # Stop and warn if error! + set -e + trap 'printf "${RED}${NAME} failed : an error occured during startup.${RESET}\n"; syslog_error "failed" ' INT TERM EXIT + + # sysctl network security settings + ################################## + + # Set 1 to ignore broadcast pings (default) + : "${SYSCTL_ICMP_ECHO_IGNORE_BROADCASTS:=1}" + # Set 1 to ignore bogus ICMP responses (default) + : "${SYSCTL_ICMP_IGNORE_BOGUS_ERROR_RESPONSES:=1}" + # Set 0 to disable source routing (default) + : "${SYSCTL_ACCEPT_SOURCE_ROUTE:=0}" + # Set 1 to enable TCP SYN cookies (default) + # cf http://cr.yp.to/syncookies.html + : "${SYSCTL_TCP_SYNCOOKIES:=1}" + # Set 0 to disable ICMP redirects (default) + : "${SYSCTL_ICMP_REDIRECTS:=0}" + # Set 1 to enable Reverse Path filtering (default) + # Set 0 if VRRP is used + : "${SYSCTL_RP_FILTER:=1}" + # Set 1 to log packets with inconsistent address (default) + : "${SYSCTL_LOG_MARTIANS:=1}" + + if [ "${SYSCTL_ICMP_ECHO_IGNORE_BROADCASTS}" = "1" ] || [ "${SYSCTL_ICMP_ECHO_IGNORE_BROADCASTS}" = "0" ]; then + echo "${SYSCTL_ICMP_ECHO_IGNORE_BROADCASTS}" > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts + else + printf "${RED}ERROR: invalid %s value '%s', must be '0' or '1'.\n" "SYSCTL_ICMP_ECHO_IGNORE_BROADCASTS" "${SYSCTL_ICMP_ECHO_IGNORE_BROADCASTS}" >&2 + exit 1 + fi + + if [ "${SYSCTL_ICMP_IGNORE_BOGUS_ERROR_RESPONSES}" = "1" ] || [ "${SYSCTL_ICMP_IGNORE_BOGUS_ERROR_RESPONSES}" = "0" ]; then + echo "${SYSCTL_ICMP_IGNORE_BOGUS_ERROR_RESPONSES}" > /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses + else + printf "${RED}ERROR: invalid %s value '%s', must be '0' or '1'.\n" "SYSCTL_ICMP_IGNORE_BOGUS_ERROR_RESPONSES" "${SYSCTL_ICMP_IGNORE_BOGUS_ERROR_RESPONSES}" >&2 + exit 1 + fi + + if [ "${SYSCTL_ACCEPT_SOURCE_ROUTE}" = "1" ] || [ "${SYSCTL_ACCEPT_SOURCE_ROUTE}" = "0" ]; then + for proc_sys_file in /proc/sys/net/ipv4/conf/*/accept_source_route; do + echo "${SYSCTL_ACCEPT_SOURCE_ROUTE}" > "${proc_sys_file}" + done + else + printf "${RED}ERROR: invalid %s value '%s', must be '0' or '1'.\n" "SYSCTL_ACCEPT_SOURCE_ROUTE" "${SYSCTL_ACCEPT_SOURCE_ROUTE}" >&2 + exit 1 + fi + + if [ "${SYSCTL_TCP_SYNCOOKIES}" = "1" ] || [ "${SYSCTL_TCP_SYNCOOKIES}" = "0" ]; then + echo "${SYSCTL_TCP_SYNCOOKIES}" > /proc/sys/net/ipv4/tcp_syncookies + else + printf "${RED}ERROR: invalid %s value '%s', must be '0' or '1'.\n" "SYSCTL_TCP_SYNCOOKIES" "${SYSCTL_TCP_SYNCOOKIES}" >&2 + exit 1 + fi + + if [ "${SYSCTL_ICMP_REDIRECTS}" = "1" ] || [ "${SYSCTL_ICMP_REDIRECTS}" = "0" ]; then + for proc_sys_file in /proc/sys/net/ipv4/conf/*/accept_redirects; do + echo "${SYSCTL_ICMP_REDIRECTS}" > "${proc_sys_file}" + done + for proc_sys_file in /proc/sys/net/ipv4/conf/*/send_redirects; do + echo "${SYSCTL_ICMP_REDIRECTS}" > "${proc_sys_file}" + done + else + printf "${RED}ERROR: invalid %s value '%s', must be '0' or '1'.\n" "SYSCTL_ICMP_REDIRECTS" "${SYSCTL_ICMP_REDIRECTS}" >&2 + exit 1 + fi + + if [ "${SYSCTL_RP_FILTER}" = "1" ] || [ "${SYSCTL_RP_FILTER}" = "0" ]; then + for proc_sys_file in /proc/sys/net/ipv4/conf/*/rp_filter; do + echo "${SYSCTL_RP_FILTER}" > "${proc_sys_file}" + done + else + printf "${RED}ERROR: invalid %s value '%s', must be '0' or '1'.\n" "SYSCTL_RP_FILTER" "${SYSCTL_RP_FILTER}" >&2 + exit 1 + fi + + if [ "${SYSCTL_LOG_MARTIANS}" = "1" ] || [ "${SYSCTL_LOG_MARTIANS}" = "0" ]; then + for proc_sys_file in /proc/sys/net/ipv4/conf/*/log_martians; do + echo "${SYSCTL_LOG_MARTIANS}" > "${proc_sys_file}" + done + else + printf "${RED}ERROR: invalid %s value '%s', must be '0' or '1'.\n" "SYSCTL_LOG_MARTIANS" "${SYSCTL_LOG_MARTIANS}" >&2 + exit 1 + fi + + # IPTables configuration + ######################## + + ${IPT} -N LOG_DROP + ${IPT} -A LOG_DROP -j LOG --log-prefix '[IPTABLES DROP] : ' + ${IPT} -A LOG_DROP -j DROP + ${IPT} -N LOG_ACCEPT + ${IPT} -A LOG_ACCEPT -j LOG --log-prefix '[IPTABLES ACCEPT] : ' + ${IPT} -A LOG_ACCEPT -j ACCEPT + if is_ipv6_enabled; then + ${IPT6} -N LOG_DROP + ${IPT6} -A LOG_DROP -j LOG --log-prefix '[IPTABLES DROP] : ' + ${IPT6} -A LOG_DROP -j DROP + ${IPT6} -N LOG_ACCEPT + ${IPT6} -A LOG_ACCEPT -j LOG --log-prefix '[IPTABLES ACCEPT] : ' + ${IPT6} -A LOG_ACCEPT -j ACCEPT + fi + + # Source additional rules and commands + # * from legacy configuration file (/etc/default/minifirewall) + # * from configuration directory (/etc/minifirewall.d/*) + source_includes + + # IP/ports lists are sorted to have consistent ordering + # You can disable this feature by simply commenting the following lines + LOOPBACK=$(sort_values ${LOOPBACK}) + INTLAN=$(sort_values ${INTLAN}) + TRUSTEDIPS=$(sort_values ${TRUSTEDIPS}) + PRIVILEGIEDIPS=$(sort_values ${PRIVILEGIEDIPS}) + SERVICESTCP1p=$(sort_values ${SERVICESTCP1p}) + SERVICESUDP1p=$(sort_values ${SERVICESUDP1p}) + SERVICESTCP1=$(sort_values ${SERVICESTCP1}) + SERVICESUDP1=$(sort_values ${SERVICESUDP1}) + SERVICESTCP2=$(sort_values ${SERVICESTCP2}) + SERVICESUDP2=$(sort_values ${SERVICESUDP2}) + SERVICESTCP3=$(sort_values ${SERVICESTCP3}) + SERVICESUDP3=$(sort_values ${SERVICESUDP3}) + DNSSERVEURS=$(sort_values ${DNSSERVEURS}) + HTTPSITES=$(sort_values ${HTTPSITES}) + HTTPSSITES=$(sort_values ${HTTPSSITES}) + FTPSITES=$(sort_values ${FTPSITES}) + SSHOK=$(sort_values ${SSHOK}) + SMTPOK=$(sort_values ${SMTPOK}) + SMTPSECUREOK=$(sort_values ${SMTPSECUREOK}) + NTPOK=$(sort_values ${NTPOK}) + PROXYBYPASS=$(sort_values ${PROXYBYPASS}) + BACKUPSERVERS=$(sort_values ${BACKUPSERVERS}) + + # Trusted ip addresses + ${IPT} -N ONLYTRUSTED + ${IPT} -A ONLYTRUSTED -j LOG_DROP + if is_ipv6_enabled; then + ${IPT6} -N ONLYTRUSTED + ${IPT6} -A ONLYTRUSTED -j LOG_DROP + fi + for ip in ${TRUSTEDIPS}; do + if is_ipv6 ${ip}; then + if is_ipv6_enabled; then + ${IPT6} -I ONLYTRUSTED -s ${ip} -j ACCEPT + fi + else + ${IPT} -I ONLYTRUSTED -s ${ip} -j ACCEPT + fi + done + + # Privilegied ip addresses + # (trusted ip addresses *are* privilegied) + ${IPT} -N ONLYPRIVILEGIED + ${IPT} -A ONLYPRIVILEGIED -j ONLYTRUSTED + if is_ipv6_enabled; then + ${IPT6} -N ONLYPRIVILEGIED + ${IPT6} -A ONLYPRIVILEGIED -j ONLYTRUSTED + fi + for ip in ${PRIVILEGIEDIPS}; do + if is_ipv6 ${ip}; then + if is_ipv6_enabled; then + ${IPT6} -I ONLYPRIVILEGIED -s ${ip} -j ACCEPT + fi + else + ${IPT} -I ONLYPRIVILEGIED -s ${ip} -j ACCEPT + fi + done + + # Chain for restrictions (blacklist IPs/ranges) + ${IPT} -N NEEDRESTRICT + if is_ipv6_enabled; then + ${IPT6} -N NEEDRESTRICT + fi + + # We allow all on loopback interface + ${IPT} -A INPUT -i lo -j ACCEPT + if is_ipv6_enabled; then + ${IPT6} -A INPUT -i lo -j ACCEPT + fi + # if OUTPUTDROP + ${IPT} -A OUTPUT -o lo -j ACCEPT + if is_ipv6_enabled; then + ${IPT6} -A OUTPUT -o lo -j ACCEPT + fi + + # We avoid "martians" packets, typical when W32/Blaster virus + # attacked windowsupdate.com and DNS was changed to 127.0.0.1 + # ${IPT} -t NAT -I PREROUTING -s ${LOOPBACK} -i ! lo -j DROP + for IP in ${LOOPBACK}; do + if is_ipv6 ${IP}; then + if is_ipv6_enabled; then + ${IPT6} -A INPUT -s ${IP} ! -i lo -j DROP + fi + else + ${IPT} -A INPUT -s ${IP} ! -i lo -j DROP + fi + done + + if is_docker_enabled; then + # WARN: IPv6 not yet supported for Docker rules + ${IPT} -N MINIFW-DOCKER-TRUSTED + ${IPT} -A MINIFW-DOCKER-TRUSTED -j DROP + + ${IPT} -N MINIFW-DOCKER-PRIVILEGED + ${IPT} -A MINIFW-DOCKER-PRIVILEGED -j MINIFW-DOCKER-TRUSTED + ${IPT} -A MINIFW-DOCKER-PRIVILEGED -j RETURN + + ${IPT} -N MINIFW-DOCKER-PUB + ${IPT} -A MINIFW-DOCKER-PUB -j MINIFW-DOCKER-PRIVILEGED + ${IPT} -A MINIFW-DOCKER-PUB -j RETURN + + # Flush DOCKER-USER if exist, create it if absent + if chain_exists 'DOCKER-USER'; then + ${IPT} -F DOCKER-USER + else + ${IPT} -N DOCKER-USER + fi; + + # Pipe new connection through MINIFW-DOCKER-PUB + ${IPT} -A DOCKER-USER -i ${INT} -m state --state NEW -j MINIFW-DOCKER-PUB + ${IPT} -A DOCKER-USER -j RETURN + fi + + + # Local services restrictions + ############################# + + # Allow services for ${INTLAN} (local server or local network) + for IP in ${INTLAN}; do + if is_ipv6 ${IP}; then + if is_ipv6_enabled; then + ${IPT6} -A INPUT -s ${IP} -j ACCEPT + fi + else + ${IPT} -A INPUT -s ${IP} -j ACCEPT + fi + done + + # Enable protection chain for sensible services + for port in ${SERVICESTCP1p}; do + ${IPT} -A INPUT -p tcp --dport ${port} -j NEEDRESTRICT + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p tcp --dport ${port} -j NEEDRESTRICT + fi + done + + for port in ${SERVICESUDP1p}; do + ${IPT} -A INPUT -p udp --dport ${port} -j NEEDRESTRICT + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p udp --dport ${port} -j NEEDRESTRICT + fi + done + + # Public service + for port in ${SERVICESTCP1}; do + ${IPT} -A INPUT -p tcp --dport ${port} -j ACCEPT + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p tcp --dport ${port} -j ACCEPT + fi + done + + for port in ${SERVICESUDP1}; do + ${IPT} -A INPUT -p udp --dport ${port} -j ACCEPT + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p udp --dport ${port} -j ACCEPT + fi + done + + # Privilegied services + for port in ${SERVICESTCP2}; do + ${IPT} -A INPUT -p tcp --dport ${port} -j ONLYPRIVILEGIED + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p tcp --dport ${port} -j ONLYPRIVILEGIED + fi + done + + for port in ${SERVICESUDP2}; do + ${IPT} -A INPUT -p udp --dport ${port} -j ONLYPRIVILEGIED + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p udp --dport ${port} -j ONLYPRIVILEGIED + fi + done + + # Private services + for port in ${SERVICESTCP3}; do + ${IPT} -A INPUT -p tcp --dport ${port} -j ONLYTRUSTED + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p tcp --dport ${port} -j ONLYTRUSTED + fi + done + + for port in ${SERVICESUDP3}; do + ${IPT} -A INPUT -p udp --dport ${port} -j ONLYTRUSTED + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p udp --dport ${port} -j ONLYTRUSTED + fi + done + + + if is_docker_enabled; then + # WARN: IPv6 not yet supported + + # Public services defined in SERVICESTCP1 & SERVICESUDP1 + for dstport in ${SERVICESTCP1}; do + ${IPT} -I MINIFW-DOCKER-PUB -p tcp --dport "${dstport}" -j RETURN + done + + for dstport in ${SERVICESUDP1}; do + ${IPT} -I MINIFW-DOCKER-PUB -p udp --dport "${dstport}" -j RETURN + done + + # Privileged services (accessible from privileged & trusted IPs) + for dstport in ${SERVICESTCP2}; do + for srcip in ${PRIVILEGIEDIPS}; do + if ! is_ipv6 ${srcip}; then + ${IPT} -I MINIFW-DOCKER-PRIVILEGED -p tcp -s "${srcip}" --dport "${dstport}" -j RETURN + fi + done + + for srcip in ${TRUSTEDIPS}; do + if ! is_ipv6 ${srcip}; then + ${IPT} -I MINIFW-DOCKER-PRIVILEGED -p tcp -s "${srcip}" --dport "${dstport}" -j RETURN + fi + done + done + + for dstport in ${SERVICESUDP2}; do + for srcip in ${PRIVILEGIEDIPS}; do + if ! is_ipv6 ${srcip}; then + ${IPT} -I MINIFW-DOCKER-PRIVILEGED -p udp -s "${srcip}" --dport "${dstport}" -j RETURN + fi + done + + for srcip in ${TRUSTEDIPS}; do + if ! is_ipv6 ${srcip}; then + ${IPT} -I MINIFW-DOCKER-PRIVILEGED -p udp -s "${srcip}" --dport "${dstport}" -j RETURN + fi + done + done + + # Trusted services (accessible from trusted IPs) + for dstport in ${SERVICESTCP3}; do + for srcip in ${TRUSTEDIPS}; do + if ! is_ipv6 ${srcip}; then + ${IPT} -I MINIFW-DOCKER-TRUSTED -p tcp -s "${srcip}" --dport "${dstport}" -j RETURN + fi + done + done + + for dstport in ${SERVICESUDP3}; do + for srcip in ${TRUSTEDIPS}; do + if ! is_ipv6 ${srcip}; then + ${IPT} -I MINIFW-DOCKER-TRUSTED -p udp -s "${srcip}" --dport "${dstport}" -j RETURN + fi + done + done + fi + + # External services + ################### + + # DNS authorizations + for IP in ${DNSSERVEURS}; do + if is_ipv6 ${IP}; then + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p tcp ! --syn --sport 53 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + ${IPT6} -A INPUT -p udp --sport 53 --dport ${PORTSUSER} -s ${IP} -m state --state ESTABLISHED,RELATED -j ACCEPT + ${IPT6} -A OUTPUT -o ${INT} -p udp -d ${IP} --dport 53 --match state --state NEW -j ACCEPT + fi + else + ${IPT} -A INPUT -p tcp ! --syn --sport 53 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + ${IPT} -A INPUT -p udp --sport 53 --dport ${PORTSUSER} -s ${IP} -m state --state ESTABLISHED,RELATED -j ACCEPT + ${IPT} -A OUTPUT -o ${INT} -p udp -d ${IP} --dport 53 --match state --state NEW -j ACCEPT + fi + done + + # HTTP (TCP/80) authorizations + for IP in ${HTTPSITES}; do + if is_ipv6 ${IP}; then + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p tcp ! --syn --sport 80 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + fi + else + ${IPT} -A INPUT -p tcp ! --syn --sport 80 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + fi + done + + # HTTPS (TCP/443) authorizations + for IP in ${HTTPSSITES}; do + if is_ipv6 ${IP}; then + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p tcp ! --syn --sport 443 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + fi + else + ${IPT} -A INPUT -p tcp ! --syn --sport 443 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + fi + done + + # FTP (so complex protocol...) authorizations + for IP in ${FTPSITES}; do + if is_ipv6 ${IP}; then + if is_ipv6_enabled; then + # requests on Control connection + ${IPT6} -A INPUT -p tcp ! --syn --sport 21 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + # FTP port-mode on Data Connection + ${IPT6} -A INPUT -p tcp --sport 20 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + # FTP passive-mode on Data Connection + # WARNING, this allow all connections on TCP ports > 1024 + ${IPT6} -A INPUT -p tcp ! --syn --sport ${PORTSUSER} --dport ${PORTSUSER} -s ${IP} -j ACCEPT + fi + else + # requests on Control connection + ${IPT} -A INPUT -p tcp ! --syn --sport 21 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + # FTP port-mode on Data Connection + ${IPT} -A INPUT -p tcp --sport 20 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + # FTP passive-mode on Data Connection + # WARNING, this allow all connections on TCP ports > 1024 + ${IPT} -A INPUT -p tcp ! --syn --sport ${PORTSUSER} --dport ${PORTSUSER} -s ${IP} -j ACCEPT + fi + done + + # SSH authorizations + for IP in ${SSHOK}; do + if is_ipv6 ${IP}; then + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p tcp ! --syn --sport 22 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + fi + else + ${IPT} -A INPUT -p tcp ! --syn --sport 22 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + fi + done + + # SMTP authorizations + for IP in ${SMTPOK}; do + if is_ipv6 ${IP}; then + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p tcp ! --syn --sport 25 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + fi + else + ${IPT} -A INPUT -p tcp ! --syn --sport 25 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + fi + done + + # secure SMTP (TCP/465 et TCP/587) authorizations + for IP in ${SMTPSECUREOK}; do + if is_ipv6 ${IP}; then + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p tcp ! --syn --sport 465 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + ${IPT6} -A INPUT -p tcp ! --syn --sport 587 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + fi + else + ${IPT} -A INPUT -p tcp ! --syn --sport 465 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + ${IPT} -A INPUT -p tcp ! --syn --sport 587 --dport ${PORTSUSER} -s ${IP} -j ACCEPT + fi + done + + # NTP authorizations + for IP in ${NTPOK}; do + if is_ipv6 ${IP}; then + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p udp --sport 123 -s ${IP} -j ACCEPT + ${IPT6} -A OUTPUT -o ${INT} -p udp -d ${IP} --dport 123 --match state --state NEW -j ACCEPT + fi + else + ${IPT} -A INPUT -p udp --sport 123 -s ${IP} -j ACCEPT + ${IPT} -A OUTPUT -o ${INT} -p udp -d ${IP} --dport 123 --match state --state NEW -j ACCEPT + fi + done + + # Proxy (Squid) + if is_proxy_enabled; then + # WARN: Squid only listen on IPv4 yet + # TODO: verify that the pattern used for IPv4 is relevant with IPv6 + + ${IPT} -t nat -A OUTPUT -p tcp --dport 80 -m owner --uid-owner proxy -j ACCEPT + for dstip in ${PROXYBYPASS}; do + if ! is_ipv6 ${dstip}; then + ${IPT} -t nat -A OUTPUT -p tcp --dport 80 -d "${dstip}" -j ACCEPT + fi + done + ${IPT} -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port "${PROXYPORT:-'8888'}" + fi + + # Output for backup servers + for server in ${BACKUPSERVERS}; do + server_port=$(echo "${server}" | awk -F : '{print $(NF)}') + server_ip=$(echo "${server}" | sed -e "s/:${server_port}$//") + + if [ -n "${server_ip}" ] && [ -n "${server_port}" ]; then + if is_ipv6 ${server_ip}; then + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p tcp --sport "${server_port}" --dport 1024:65535 -s "${server_ip}" -m state --state ESTABLISHED,RELATED -j ACCEPT + fi + else + ${IPT} -A INPUT -p tcp --sport "${server_port}" --dport 1024:65535 -s "${server_ip}" -m state --state ESTABLISHED,RELATED -j ACCEPT + fi + else + printf "${RED}ERROR: unrecognized syntax for BACKUPSERVERS '%s\`. Use space-separated IP:PORT tuples.${RESET}\n" "${server}" >&2 + exit 1 + fi + done + + # Always allow ICMP + ${IPT} -A INPUT -p icmp -j ACCEPT + if is_ipv6_enabled; then + ${IPT6} -A INPUT -p icmpv6 -j ACCEPT + fi + + # source config file for remaining commands + if is_legacy_config; then + source_file_or_error "${config_file}" + fi + + # IPTables policy + ################# + + # by default DROP INPUT packets + ${IPT} -P INPUT DROP + if is_ipv6_enabled; then + ${IPT6} -P INPUT DROP + fi + + # by default, no FORWARDING (deprecated for Virtual Machines) + #echo 0 > /proc/sys/net/ipv4/ip_forward + #${IPT} -P FORWARD DROP + #${IPT6} -P FORWARD DROP + + # by default allow OUTPUT packets... but drop UDP packets (see OUTPUTDROP to drop OUTPUT packets) + ${IPT} -P OUTPUT ACCEPT + if is_ipv6_enabled; then + ${IPT6} -P OUTPUT ACCEPT + fi + + ${IPT} -A OUTPUT -o ${INT} -p udp --dport 33434:33523 --match state --state NEW -j ACCEPT + if is_ipv6_enabled; then + ${IPT6} -A OUTPUT -o ${INT} -p udp --dport 33434:33523 --match state --state NEW -j ACCEPT + fi + + ${IPT} -A OUTPUT -p udp --match state --state ESTABLISHED,RELATED -j ACCEPT + if is_ipv6_enabled; then + ${IPT6} -A OUTPUT -p udp --match state --state ESTABLISHED,RELATED -j ACCEPT + fi + + ${IPT} -A OUTPUT -p udp -j DROP + if is_ipv6_enabled; then + ${IPT6} -A OUTPUT -p udp -j DROP + fi + + # Finish + ######################## + + trap - INT TERM EXIT + + syslog_info "started" + printf "${GREEN}${BOLD}${NAME} started${RESET}\n" + + # No need to exit on error anymore + set +e + + report_state_changes +} + +stop() { + syslog_info "stopping" + printf "${BOLD}${NAME} stopping${RESET}\n" + + printf "${BLUE}flushing all rules and accepting everything${RESET}\n" + + mkdir -p "$(dirname "${STATE_FILE_PREVIOUS}")" + status_without_numbers > "${STATE_FILE_PREVIOUS}" + + # Delete all rules + ${IPT} -F INPUT + if is_ipv6_enabled; then + ${IPT6} -F INPUT + fi + + ${IPT} -F OUTPUT + if is_ipv6_enabled; then + ${IPT6} -F OUTPUT + fi + + ${IPT} -F LOG_DROP + ${IPT} -F LOG_ACCEPT + ${IPT} -F ONLYTRUSTED + ${IPT} -F ONLYPRIVILEGIED + ${IPT} -F NEEDRESTRICT + if is_ipv6_enabled; then + ${IPT6} -F LOG_DROP + ${IPT6} -F LOG_ACCEPT + ${IPT6} -F ONLYTRUSTED + ${IPT6} -F ONLYPRIVILEGIED + ${IPT6} -F NEEDRESTRICT + fi + + ${IPT} -t mangle -F + if is_ipv6_enabled; then + ${IPT6} -t mangle -F + fi + + if is_docker_enabled; then + # WARN: IPv6 not yet supported + + ${IPT} -F DOCKER-USER + ${IPT} -A DOCKER-USER -j RETURN + + ${IPT} -F MINIFW-DOCKER-PUB + ${IPT} -X MINIFW-DOCKER-PUB + ${IPT} -F MINIFW-DOCKER-PRIVILEGED + ${IPT} -X MINIFW-DOCKER-PRIVILEGED + ${IPT} -F MINIFW-DOCKER-TRUSTED + ${IPT} -X MINIFW-DOCKER-TRUSTED + else + ${IPT} -t nat -F + fi + + # Accept all + ${IPT} -P INPUT ACCEPT + if is_ipv6_enabled; then + ${IPT6} -P INPUT ACCEPT + fi + + ${IPT} -P OUTPUT ACCEPT + if is_ipv6_enabled; then + ${IPT6} -P OUTPUT ACCEPT + fi + #${IPT} -P FORWARD ACCEPT + #${IPT} -t nat -P PREROUTING ACCEPT + #${IPT} -t nat -P POSTROUTING ACCEPT + + # Delete non-standard chains + ${IPT} -X LOG_DROP + ${IPT} -X LOG_ACCEPT + ${IPT} -X ONLYPRIVILEGIED + ${IPT} -X ONLYTRUSTED + ${IPT} -X NEEDRESTRICT + if is_ipv6_enabled; then + ${IPT6} -X LOG_DROP + ${IPT6} -X LOG_ACCEPT + ${IPT6} -X ONLYPRIVILEGIED + ${IPT6} -X ONLYTRUSTED + ${IPT6} -X NEEDRESTRICT + fi + + rm -f "${STATE_FILE_LATEST}" "${STATE_FILE_CURRENT}" + + syslog_info "stopped" + printf "${GREEN}${BOLD}${NAME} stopped${RESET}\n" +} + +status() { + printf "${BLUE}#### iptables --list ###############################${RESET}\n" + ${IPT} --list --numeric --verbose --line-numbers + printf "\n${BLUE}#### iptables --table nat --list ###################${RESET}\n" + ${IPT} --table nat --list --numeric --verbose --line-numbers + printf "\n${BLUE}#### iptables --table mangle --list ################${RESET}\n" + ${IPT} --table mangle --list --numeric --verbose --line-numbers + if is_ipv6_enabled; then + printf "\n${BLUE}#### ip6tables --list ##############################${RESET}\n" + ${IPT6} --list --numeric --verbose --line-numbers + printf "\n${BLUE}#### ip6tables --table mangle --list ###############${RESET}\n" + ${IPT6} --table mangle --list --numeric --verbose --line-numbers + fi +} + +status_without_numbers() { + printf "${BLUE}#### iptables --list ###############################${RESET}\n" + ${IPT} --list --numeric + printf "\n${BLUE}#### iptables --table nat --list ###################${RESET}\n" + ${IPT} --table nat --list --numeric + printf "\n${BLUE}#### iptables --table mangle --list ################${RESET}\n" + ${IPT} --table mangle --list --numeric + if is_ipv6_enabled; then + printf "\n${BLUE}#### ip6tables --list ##############################${RESET}\n" + ${IPT6} --list --numeric + printf "\n${BLUE}#### ip6tables --table mangle --list ###############${RESET}\n" + ${IPT6} --table mangle --list --numeric + fi +} + +reset() { + syslog_info "resetting" + printf "${BOLD}${NAME} resetting${RESET}\n" + + ${IPT} -Z + if is_ipv6_enabled; then + ${IPT6} -Z + fi + + ${IPT} -t nat -Z + + ${IPT} -t mangle -Z + if is_ipv6_enabled; then + ${IPT6} -t mangle -Z + fi + + syslog_info "reset" + printf "${GREEN}${BOLD}${NAME} reset${RESET}\n" +} +show_version() { + cat <. + +${NAME} comes with ABSOLUTELY NO WARRANTY. +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 3 +of the License. +END +} + +case "${1:-''}" in + start) + source_configuration + check_unpersisted_state + + start + ;; + + stop) + source_configuration + check_unpersisted_state + + stop + ;; + + status) + source_configuration + check_unpersisted_state + + status + ;; + + reset) + source_configuration + check_unpersisted_state + + reset + ;; + + restart) + source_configuration + check_unpersisted_state + + stop + start + ;; + + version) + show_version + ;; + + *) + echo "Usage: $0 {start|stop|restart|status|reset|version}" + exit 1 + ;; +esac + +exit 0 diff --git a/minifirewall/files/minifirewall.conf b/minifirewall/files/minifirewall.conf index 47be78bf..1cd73d7f 100644 --- a/minifirewall/files/minifirewall.conf +++ b/minifirewall/files/minifirewall.conf @@ -1,31 +1,37 @@ # Configuration for minifirewall : https://gitea.evolix.org/evolix/minifirewall -# Version 20.12 — 2020-12-01 22:55:35 +# Version 22.03.1 — 2022-03-15 +# shellcheck shell=sh disable=SC2034 # Main interface INT='eth0' # IPv6 -IPV6=on +IPV6='on' # Docker Mode # Changes the behaviour of minifirewall to not break the containers' network # For instance, turning it on will disable nat table purge -# Also, we'll add the DOCKER-USER chain, in iptable +# Also, we'll add the DOCKER-USER chain, in iptables +# +# WARNING : If the port mapping is different between the host and the container +# (ie: Listen on :8090 on host, but :8080 in container) +# then you need to give the port used inside the container DOCKER='off' -# Trusted IPv4 local network -# ...will be often IP/32 if you don't trust anything -INTLAN='192.168.0.2/32' +# Trusted local network +# ...will be often IPv4/32 or IPv6/128 if you don't trust anything +INTLAN='192.0.2.1/32 2001:db8::1/128' -# Trusted IPv4 addresses for private and semi-public services -TRUSTEDIPS='31.170.9.129 62.212.121.90 31.170.8.4 82.65.34.85 54.37.106.210 51.210.84.146' +# Trusted IP addresses for private and semi-public services +# TODO: add all our IPv6 adresses +TRUSTEDIPS='31.170.9.129 2a01:9500:37:129::/64 62.212.121.90 31.170.8.4 2a01:9500::fada/128 82.65.34.85 54.37.106.210 51.210.84.146' -# Privilegied IPv4 addresses for semi-public services +# Privilegied IP addresses for semi-public services # (no need to add again TRUSTEDIPS) PRIVILEGIEDIPS='' -# Local services IPv4/IPv6 restrictions +# Local services IP restrictions ####################################### # Protected services @@ -45,62 +51,86 @@ SERVICESUDP2='' SERVICESTCP3='5666' SERVICESUDP3='' -# Standard output IPv4 access restrictions + +# Standard output IPv4/IPv6 access restrictions ########################################## # DNS authorizations # (if you have local DNS server, set 0.0.0.0/0) -DNSSERVEURS='0.0.0.0/0' +DNSSERVEURS='0.0.0.0/0 ::/0' # HTTP authorizations # (you can use DNS names but set cron to reload minifirewall regularly) # (if you have HTTP proxy, set 0.0.0.0/0) -# HTTPSITES='security.debian.org pub.evolix.net security-cdn.debian.org mirror.evolix.org backports.debian.org hwraid.le-vert.net antispam00.evolix.org spamassassin.apache.org sa-update.space-pro.be sa-update.secnap.net www.sa-update.pccc.com sa-update.dnswl.org ocsp.int-x3.letsencrypt.org' -HTTPSITES='0.0.0.0/0' +HTTPSITES='0.0.0.0/0 ::/0' # HTTPS authorizations -HTTPSSITES='0.0.0.0/0' +HTTPSSITES='0.0.0.0/0 ::/0' # FTP authorizations FTPSITES='' # SSH authorizations -SSHOK='0.0.0.0/0' +SSHOK='0.0.0.0/0 ::/0' # SMTP authorizations -SMTPOK='0.0.0.0/0' +SMTPOK='0.0.0.0/0 ::/0' # SMTP secure authorizations (ports TCP/465 and TCP/587) SMTPSECUREOK='' # NTP authorizations -NTPOK='0.0.0.0/0' +NTPOK='0.0.0.0/0 ::/0' + +# Proxy (Squid) +PROXY='off' +# (proxy port) +PROXYPORT='8888' +# (destinations that bypass the proxy) +PROXYBYPASS="${INTLAN} 127.0.0.0/8 ::1/128" + +# Backup servers +# (add IP:PORT for each one, example: '192.168.10.1:1234 192.168.10.2:5678') +BACKUPSERVERS='' -# IPv6 Specific rules +# Includes ##################### -# Example: allow SSH from Trusted IPv6 addresses -/sbin/ip6tables -A INPUT -i $INT -p tcp --dport 22 -s 2a01:9500:37:129::/64 -j ACCEPT +# Files in /etc/minifirewall.d/* (without "." in name) +# are automatically included in alphanumerical order. +# +# Within included files, you can use those helper functions : +# * is_ipv6_enabled: returns true if IPv6 is enabled, or false +# * is_docker_enabled: returns true if Docker mode is eabled, or false +# * is_proxy_enabled: returns true if Proxy mode is enabled , or false -# Example: allow outgoing SSH/HTTP/HTTPS/SMTP/DNS traffic -/sbin/ip6tables -A INPUT -i $INT -p tcp --sport 22 --match state --state ESTABLISHED,RELATED -j ACCEPT -/sbin/ip6tables -A INPUT -i $INT -p tcp --sport 80 --match state --state ESTABLISHED,RELATED -j ACCEPT -/sbin/ip6tables -A INPUT -i $INT -p tcp --sport 443 --match state --state ESTABLISHED,RELATED -j ACCEPT -/sbin/ip6tables -A INPUT -i $INT -p tcp --sport 25 --match state --state ESTABLISHED,RELATED -j ACCEPT -/sbin/ip6tables -A INPUT -i $INT -p udp --sport 53 --match state --state ESTABLISHED,RELATED -j ACCEPT -/sbin/ip6tables -A INPUT -i $INT -p tcp --sport 53 --match state --state ESTABLISHED,RELATED -j ACCEPT -# Example: allow output DNS, NTP and traceroute traffic -/sbin/ip6tables -A OUTPUT -o $INT -p udp --dport 53 --match state --state NEW -j ACCEPT -/sbin/ip6tables -A OUTPUT -o $INT -p udp --dport 123 --match state --state NEW -j ACCEPT -#/sbin/ip6tables -A OUTPUT -o $INT -p udp --dport 33434:33523 --match state --state NEW -j ACCEPT +# Custom sysctl values (advanced) +################################# -# Example: allow DHCPv6 -/sbin/ip6tables -A INPUT -i $INT -p udp --dport 546 -d fe80::/64 -j ACCEPT -/sbin/ip6tables -A OUTPUT -o $INT -p udp --dport 547 -j ACCEPT +# In most cases, the default values set by minifirewall are good. +# If you really know what you are doing, +# you can uncomment some lines and customize the values. -# IPv4 Specific rules -##################### +# Set 1 to ignore broadcast pings (default) +# SYSCTL_ICMP_ECHO_IGNORE_BROADCASTS='1' -# /sbin/iptables ... +# Set 1 to ignore bogus ICMP responses (default) +# SYSCTL_ICMP_IGNORE_BOGUS_ERROR_RESPONSES='1' + +# Set 0 to disable source routing (default) +# SYSCTL_ACCEPT_SOURCE_ROUTE='0' + +# Set 1 to enable TCP SYN cookies (default) +# SYSCTL_TCP_SYNCOOKIES='1' + +# Set 0 to disable ICMP redirects (default) +# SYSCTL_ICMP_REDIRECTS='0' + +# Set 1 to enable Reverse Path filtering (default) +# Set 0 if VRRP is used +# SYSCTL_RP_FILTER='1' + +# Set 1 to log packets with inconsistent address (default) +# SYSCTL_LOG_MARTIANS='1' \ No newline at end of file diff --git a/minifirewall/files/minifirewall.d/zzz-custom b/minifirewall/files/minifirewall.d/zzz-custom new file mode 100644 index 00000000..7ac24f06 --- /dev/null +++ b/minifirewall/files/minifirewall.d/zzz-custom @@ -0,0 +1,11 @@ +### custom minifirewall commands +# +# You can add any custom command in files like this; +# either this one, or others in the same directory. +# They are executed as shell scripts. +# They are automatically included in alphanumerical order. +# +# Within included files, you can use those helper functions : +# * is_ipv6_enabled: returns true if IPv6 is enabled, or false +# * is_docker_enabled: returns true if Docker mode is eabled, or false +# * is_proxy_enabled: returns true if Proxy mode is enabled , or false diff --git a/minifirewall/files/minifirewall.d/zzzz-ban b/minifirewall/files/minifirewall.d/zzzz-ban new file mode 100644 index 00000000..6dc516b0 --- /dev/null +++ b/minifirewall/files/minifirewall.d/zzzz-ban @@ -0,0 +1,7 @@ +### ban rules +# +# If you have ban rules in /root/ban.iptables +# (either manually or with /usr/share/scripts/blacklist-countries.sh) +# ou can automatically import them with the following command: +# +# cat /root/ban.iptables | iptables-restore -n diff --git a/minifirewall/files/minifirewall.legacy.conf b/minifirewall/files/minifirewall.legacy.conf new file mode 100644 index 00000000..47be78bf --- /dev/null +++ b/minifirewall/files/minifirewall.legacy.conf @@ -0,0 +1,106 @@ +# Configuration for minifirewall : https://gitea.evolix.org/evolix/minifirewall +# Version 20.12 — 2020-12-01 22:55:35 + +# Main interface +INT='eth0' + +# IPv6 +IPV6=on + +# Docker Mode +# Changes the behaviour of minifirewall to not break the containers' network +# For instance, turning it on will disable nat table purge +# Also, we'll add the DOCKER-USER chain, in iptable +DOCKER='off' + +# Trusted IPv4 local network +# ...will be often IP/32 if you don't trust anything +INTLAN='192.168.0.2/32' + +# Trusted IPv4 addresses for private and semi-public services +TRUSTEDIPS='31.170.9.129 62.212.121.90 31.170.8.4 82.65.34.85 54.37.106.210 51.210.84.146' + +# Privilegied IPv4 addresses for semi-public services +# (no need to add again TRUSTEDIPS) +PRIVILEGIEDIPS='' + + +# Local services IPv4/IPv6 restrictions +####################################### + +# Protected services +# (add also in Public services if needed) +SERVICESTCP1p='22222' +SERVICESUDP1p='' + +# Public services (IPv4/IPv6) +SERVICESTCP1='22222' +SERVICESUDP1='' + +# Semi-public services (IPv4) +SERVICESTCP2='22' +SERVICESUDP2='' + +# Private services (IPv4) +SERVICESTCP3='5666' +SERVICESUDP3='' + +# Standard output IPv4 access restrictions +########################################## + +# DNS authorizations +# (if you have local DNS server, set 0.0.0.0/0) +DNSSERVEURS='0.0.0.0/0' + +# HTTP authorizations +# (you can use DNS names but set cron to reload minifirewall regularly) +# (if you have HTTP proxy, set 0.0.0.0/0) +# HTTPSITES='security.debian.org pub.evolix.net security-cdn.debian.org mirror.evolix.org backports.debian.org hwraid.le-vert.net antispam00.evolix.org spamassassin.apache.org sa-update.space-pro.be sa-update.secnap.net www.sa-update.pccc.com sa-update.dnswl.org ocsp.int-x3.letsencrypt.org' +HTTPSITES='0.0.0.0/0' + +# HTTPS authorizations +HTTPSSITES='0.0.0.0/0' + +# FTP authorizations +FTPSITES='' + +# SSH authorizations +SSHOK='0.0.0.0/0' + +# SMTP authorizations +SMTPOK='0.0.0.0/0' + +# SMTP secure authorizations (ports TCP/465 and TCP/587) +SMTPSECUREOK='' + +# NTP authorizations +NTPOK='0.0.0.0/0' + + +# IPv6 Specific rules +##################### + +# Example: allow SSH from Trusted IPv6 addresses +/sbin/ip6tables -A INPUT -i $INT -p tcp --dport 22 -s 2a01:9500:37:129::/64 -j ACCEPT + +# Example: allow outgoing SSH/HTTP/HTTPS/SMTP/DNS traffic +/sbin/ip6tables -A INPUT -i $INT -p tcp --sport 22 --match state --state ESTABLISHED,RELATED -j ACCEPT +/sbin/ip6tables -A INPUT -i $INT -p tcp --sport 80 --match state --state ESTABLISHED,RELATED -j ACCEPT +/sbin/ip6tables -A INPUT -i $INT -p tcp --sport 443 --match state --state ESTABLISHED,RELATED -j ACCEPT +/sbin/ip6tables -A INPUT -i $INT -p tcp --sport 25 --match state --state ESTABLISHED,RELATED -j ACCEPT +/sbin/ip6tables -A INPUT -i $INT -p udp --sport 53 --match state --state ESTABLISHED,RELATED -j ACCEPT +/sbin/ip6tables -A INPUT -i $INT -p tcp --sport 53 --match state --state ESTABLISHED,RELATED -j ACCEPT + +# Example: allow output DNS, NTP and traceroute traffic +/sbin/ip6tables -A OUTPUT -o $INT -p udp --dport 53 --match state --state NEW -j ACCEPT +/sbin/ip6tables -A OUTPUT -o $INT -p udp --dport 123 --match state --state NEW -j ACCEPT +#/sbin/ip6tables -A OUTPUT -o $INT -p udp --dport 33434:33523 --match state --state NEW -j ACCEPT + +# Example: allow DHCPv6 +/sbin/ip6tables -A INPUT -i $INT -p udp --dport 546 -d fe80::/64 -j ACCEPT +/sbin/ip6tables -A OUTPUT -o $INT -p udp --dport 547 -j ACCEPT + +# IPv4 Specific rules +##################### + +# /sbin/iptables ... diff --git a/minifirewall/tasks/config.legacy.yml b/minifirewall/tasks/config.legacy.yml new file mode 100644 index 00000000..8a7f5990 --- /dev/null +++ b/minifirewall/tasks/config.legacy.yml @@ -0,0 +1,218 @@ +--- + +- debug: + var: minifirewall_trusted_ips + verbosity: 1 +- debug: + var: minifirewall_privilegied_ips + verbosity: 1 + +- name: Stat minifirewall config file (before) + stat: + path: "{{ minifirewall_main_file }}" + register: minifirewall_before + +- name: Check if minifirewall is running + shell: /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: Begin marker for IP addresses + lineinfile: + dest: "{{ minifirewall_main_file }}" + line: "# BEGIN ANSIBLE MANAGED BLOCK FOR IPS" + insertbefore: '^# Main interface' + create: no + +- name: End marker for IP addresses + lineinfile: + dest: "{{ minifirewall_main_file }}" + create: no + line: "# END ANSIBLE MANAGED BLOCK FOR IPS" + insertafter: '^PRIVILEGIEDIPS=' + +- name: Verify that at least 1 trusted IP is provided + assert: + that: minifirewall_trusted_ips | length > 0 + msg: You must provide at least 1 trusted IP + +- debug: + msg: "Warning: minifirewall_trusted_ips='0.0.0.0/0', the firewall is useless!" + when: minifirewall_trusted_ips == ["0.0.0.0/0"] + +- name: Configure IP addresses + blockinfile: + dest: "{{ minifirewall_main_file }}" + marker: "# {mark} ANSIBLE MANAGED BLOCK FOR IPS" + block: | + # Main interface + INT='{{ minifirewall_int }}' + + # IPv6 + IPV6='{{ minifirewall_ipv6 }}' + + # Docker Mode + # Changes the behaviour of minifirewall to not break the containers' network + # For instance, turning it on will disable nat table purge + # Also, we'll add the DOCKER-USER chain, in iptable + DOCKER='{{ minifirewall_docker }}' + + # Trusted IPv4 local network + # ...will be often IP/32 if you don't trust anything + INTLAN='{{ minifirewall_intlan }}' + + # Trusted IPv4 addresses for private and semi-public services + TRUSTEDIPS='{{ minifirewall_trusted_ips | join(' ') }}' + + # Privilegied IPv4 addresses for semi-public services + # (no need to add again TRUSTEDIPS) + PRIVILEGIEDIPS='{{ minifirewall_privilegied_ips | join(' ') }}' + create: no + register: minifirewall_config_ips + +- name: Begin marker for ports + lineinfile: + dest: "{{ minifirewall_main_file }}" + line: "# BEGIN ANSIBLE MANAGED BLOCK FOR PORTS" + insertbefore: '^# Protected services' + create: no + +- name: End marker for ports + lineinfile: + dest: "{{ minifirewall_main_file }}" + line: "# END ANSIBLE MANAGED BLOCK FOR PORTS" + insertafter: '^SERVICESUDP3=' + create: no + +- name: Configure ports + blockinfile: + dest: "{{ minifirewall_main_file }}" + marker: "# {mark} ANSIBLE MANAGED BLOCK FOR PORTS" + block: | + # Protected services + # (add also in Public services if needed) + SERVICESTCP1p='{{ minifirewall_protected_ports_tcp | join(' ') }}' + SERVICESUDP1p='{{ minifirewall_protected_ports_udp | join(' ') }}' + + # Public services (IPv4/IPv6) + SERVICESTCP1='{{ minifirewall_public_ports_tcp | join(' ') }}' + SERVICESUDP1='{{ minifirewall_public_ports_udp | join(' ') }}' + + # Semi-public services (IPv4) + SERVICESTCP2='{{ minifirewall_semipublic_ports_tcp | join(' ') }}' + SERVICESUDP2='{{ minifirewall_semipublic_ports_udp | join(' ') }}' + + # Private services (IPv4) + SERVICESTCP3='{{ minifirewall_private_ports_tcp | join(' ') }}' + SERVICESUDP3='{{ minifirewall_private_ports_udp | join(' ') }}' + create: no + register: minifirewall_config_ports + +- name: Configure DNSSERVEURS + lineinfile: + dest: "{{ minifirewall_main_file }}" + line: "DNSSERVEURS='{{ minifirewall_dns_servers | join(' ') }}'" + regexp: "DNSSERVEURS='.*'" + create: no + when: minifirewall_dns_servers is not none + +- name: Configure HTTPSITES + lineinfile: + dest: "{{ minifirewall_main_file }}" + line: "HTTPSITES='{{ minifirewall_http_sites | join(' ') }}'" + regexp: "HTTPSITES='.*'" + create: no + when: minifirewall_http_sites is not none + +- name: Configure HTTPSSITES + lineinfile: + dest: "{{ minifirewall_main_file }}" + line: "HTTPSSITES='{{ minifirewall_https_sites | join(' ') }}'" + regexp: "HTTPSSITES='.*'" + create: no + when: minifirewall_https_sites is not none + +- name: Configure FTPSITES + lineinfile: + dest: "{{ minifirewall_main_file }}" + line: "FTPSITES='{{ minifirewall_ftp_sites | join(' ') }}'" + regexp: "FTPSITES='.*'" + create: no + when: minifirewall_ftp_sites is not none + +- name: Configure SSHOK + lineinfile: + dest: "{{ minifirewall_main_file }}" + line: "SSHOK='{{ minifirewall_ssh_ok | join(' ') }}'" + regexp: "SSHOK='.*'" + create: no + when: minifirewall_ssh_ok is not none + +- name: Configure SMTPOK + lineinfile: + dest: "{{ minifirewall_main_file }}" + line: "SMTPOK='{{ minifirewall_smtp_ok | join(' ') }}'" + regexp: "SMTPOK='.*'" + create: no + when: minifirewall_smtp_ok is not none + +- name: Configure SMTPSECUREOK + lineinfile: + dest: "{{ minifirewall_main_file }}" + line: "SMTPSECUREOK='{{ minifirewall_smtp_secure_ok | join(' ') }}'" + regexp: "SMTPSECUREOK='.*'" + create: no + when: minifirewall_smtp_secure_ok is not none + +- name: Configure NTPOK + lineinfile: + dest: "{{ minifirewall_main_file }}" + line: "NTPOK='{{ minifirewall_ntp_ok | join(' ') }}'" + regexp: "NTPOK='.*'" + create: no + when: minifirewall_ntp_ok is not none + +- name: evomaintenance + lineinfile: + dest: "{{ minifirewall_main_file }}" + line: "/sbin/iptables -A INPUT -p tcp --sport 5432 --dport 1024:65535 -s {{ item }} -m state --state ESTABLISHED,RELATED -j ACCEPT" + insertafter: "^# EvoMaintenance" + loop: "{{ evomaintenance_hosts }}" + +- name: remove minifirewall example rule for the evomaintenance + lineinfile: + dest: "{{ minifirewall_main_file }}" + regexp: '^#.*(--sport 5432).*(-s X\.X\.X\.X)' + state: absent + when: evomaintenance_hosts | length > 0 + +- name: Stat minifirewall config file (after) + stat: + 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" + when: + - minifirewall_restart_if_needed | bool + - minifirewall_is_running.rc == 0 + - minifirewall_before.stat.checksum != minifirewall_after.stat.checksum + +- 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 + verbosity: 2 diff --git a/minifirewall/tasks/config.yml b/minifirewall/tasks/config.yml index 04ed3a9c..1ddb9695 100644 --- a/minifirewall/tasks/config.yml +++ b/minifirewall/tasks/config.yml @@ -9,11 +9,12 @@ - name: Stat minifirewall config file (before) stat: - path: "{{ minifirewall_main_file }}" + path: "/etc/default/minifirewall" register: minifirewall_before - name: Check if minifirewall is running - shell: /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*$" + 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 @@ -25,14 +26,14 @@ - name: Begin marker for IP addresses lineinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" line: "# BEGIN ANSIBLE MANAGED BLOCK FOR IPS" insertbefore: '^# Main interface' create: no - name: End marker for IP addresses lineinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" create: no line: "# END ANSIBLE MANAGED BLOCK FOR IPS" insertafter: '^PRIVILEGIEDIPS=' @@ -43,12 +44,16 @@ msg: You must provide at least 1 trusted IP - debug: - msg: "Warning: minifirewall_trusted_ips='0.0.0.0/0', the firewall is useless!" - when: minifirewall_trusted_ips == ["0.0.0.0/0"] + msg: "Warning: minifirewall_trusted_ips contains '0.0.0.0/0', the firewall is useless on IPv4!" + when: "'0.0.0.0/0' in minifirewall_trusted_ips" + +- debug: + msg: "Warning: minifirewall_trusted_ips contains '::/0', the firewall is useless on IPv6!" + when: "'::/0' in minifirewall_trusted_ips" - name: Configure IP addresses blockinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" marker: "# {mark} ANSIBLE MANAGED BLOCK FOR IPS" block: | # Main interface @@ -60,8 +65,12 @@ # Docker Mode # Changes the behaviour of minifirewall to not break the containers' network # For instance, turning it on will disable nat table purge - # Also, we'll add the DOCKER-USER chain, in iptable - DOCKER='{{ minifirewall_docker }}' + # Also, we'll add the DOCKER-USER chain, in iptables + # + # WARNING : If the port mapping is different between the host and the container + # (ie: Listen on :8090 on host, but :8080 in container) + # then you need to give the port used inside the container + DOCKER='off' # Trusted IPv4 local network # ...will be often IP/32 if you don't trust anything @@ -78,21 +87,21 @@ - name: Begin marker for ports lineinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" line: "# BEGIN ANSIBLE MANAGED BLOCK FOR PORTS" insertbefore: '^# Protected services' create: no - name: End marker for ports lineinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" line: "# END ANSIBLE MANAGED BLOCK FOR PORTS" insertafter: '^SERVICESUDP3=' create: no - name: Configure ports blockinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" marker: "# {mark} ANSIBLE MANAGED BLOCK FOR PORTS" block: | # Protected services @@ -116,106 +125,171 @@ - name: Configure DNSSERVEURS lineinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" line: "DNSSERVEURS='{{ minifirewall_dns_servers | join(' ') }}'" - regexp: "DNSSERVEURS='.*'" + regexp: "DNSSERVEURS=('|\").*('|\")" create: no when: minifirewall_dns_servers is not none - name: Configure HTTPSITES lineinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" line: "HTTPSITES='{{ minifirewall_http_sites | join(' ') }}'" - regexp: "HTTPSITES='.*'" + regexp: "HTTPSITES=('|\").*('|\")" create: no when: minifirewall_http_sites is not none - name: Configure HTTPSSITES lineinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" line: "HTTPSSITES='{{ minifirewall_https_sites | join(' ') }}'" - regexp: "HTTPSSITES='.*'" + regexp: "HTTPSSITES=('|\").*('|\")" create: no when: minifirewall_https_sites is not none - name: Configure FTPSITES lineinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" line: "FTPSITES='{{ minifirewall_ftp_sites | join(' ') }}'" - regexp: "FTPSITES='.*'" + regexp: "FTPSITES=('|\").*('|\")" create: no when: minifirewall_ftp_sites is not none - name: Configure SSHOK lineinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" line: "SSHOK='{{ minifirewall_ssh_ok | join(' ') }}'" - regexp: "SSHOK='.*'" + regexp: "SSHOK=('|\").*('|\")" create: no when: minifirewall_ssh_ok is not none - name: Configure SMTPOK lineinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" line: "SMTPOK='{{ minifirewall_smtp_ok | join(' ') }}'" - regexp: "SMTPOK='.*'" + regexp: "SMTPOK=('|\").*('|\")" create: no when: minifirewall_smtp_ok is not none - name: Configure SMTPSECUREOK lineinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" line: "SMTPSECUREOK='{{ minifirewall_smtp_secure_ok | join(' ') }}'" - regexp: "SMTPSECUREOK='.*'" + regexp: "SMTPSECUREOK=('|\").*('|\")" create: no when: minifirewall_smtp_secure_ok is not none - name: Configure NTPOK lineinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" line: "NTPOK='{{ minifirewall_ntp_ok | join(' ') }}'" - regexp: "NTPOK='.*'" + regexp: "NTPOK=('|\").*('|\")" create: no when: minifirewall_ntp_ok is not none -- name: evomaintenance +- name: Configure PROXY lineinfile: - dest: "{{ minifirewall_main_file }}" - line: "/sbin/iptables -A INPUT -p tcp --sport 5432 --dport 1024:65535 -s {{ item }} -m state --state ESTABLISHED,RELATED -j ACCEPT" - insertafter: "^# EvoMaintenance" - loop: "{{ evomaintenance_hosts }}" + dest: "/etc/default/minifirewall" + line: "PROXY='{{ minifirewall_proxy }}'" + regexp: "PROXY=('|\").*('|\")" + create: no + when: minifirewall_proxy is not none -- name: remove minifirewall example rule for the evomaintenance +- name: Configure PROXYPORT lineinfile: - dest: "{{ minifirewall_main_file }}" - regexp: '^#.*(--sport 5432).*(-s X\.X\.X\.X)' - state: absent - when: evomaintenance_hosts | length > 0 + dest: "/etc/default/minifirewall" + line: "PROXYPORT='{{ minifirewall_proxyport }}'" + regexp: "PROXYPORT=('|\").*('|\")" + create: no + when: minifirewall_proxyport is not none + +# Warning: keep double quotes for the value, +# since we often reference a shell variable that needs to be interpolated +- name: Configure PROXYBYPASS + lineinfile: + dest: "/etc/default/minifirewall" + line: "PROXYBYPASS=\"{{ minifirewall_proxybypass | join(' ') }}\"" + regexp: "PROXYBYPASS=('|\").*('|\")" + create: no + when: minifirewall_proxybypass is not none + +- name: Configure BACKUPSERVERS + lineinfile: + dest: "/etc/default/minifirewall" + line: "BACKUPSERVERS='{{ minifirewall_backupservers | join(' ') }}'" + regexp: "BACKUPSERVERS=('|\").*('|\")" + create: no + when: minifirewall_backupservers is not none + +- name: Configure SYSCTL_ICMP_ECHO_IGNORE_BROADCASTS + lineinfile: + dest: "/etc/default/minifirewall" + line: "SYSCTL_ICMP_ECHO_IGNORE_BROADCASTS='{{ minifirewall_sysctl_icmp_echo_ignore_broadcasts }}'" + regexp: "SYSCTL_ICMP_ECHO_IGNORE_BROADCASTS=('|\").*('|\")" + create: no + when: minifirewall_sysctl_icmp_echo_ignore_broadcasts is not none + +- name: Configure SYSCTL_ICMP_IGNORE_BOGUS_ERROR_RESPONSES + lineinfile: + dest: "/etc/default/minifirewall" + line: "SYSCTL_ICMP_IGNORE_BOGUS_ERROR_RESPONSES='{{ minifirewall_sysctl_icmp_ignore_bogus_error_responses }}'" + regexp: "SYSCTL_ICMP_IGNORE_BOGUS_ERROR_RESPONSES=('|\").*('|\")" + create: no + when: minifirewall_sysctl_icmp_ignore_bogus_error_responses is not none + +- name: Configure SYSCTL_ACCEPT_SOURCE_ROUTE + lineinfile: + dest: "/etc/default/minifirewall" + line: "SYSCTL_ACCEPT_SOURCE_ROUTE='{{ minifirewall_sysctl_accept_source_route }}'" + regexp: "SYSCTL_ACCEPT_SOURCE_ROUTE=('|\").*('|\")" + create: no + when: minifirewall_sysctl_accept_source_route is not none + +- name: Configure SYSCTL_TCP_SYNCOOKIES + lineinfile: + dest: "/etc/default/minifirewall" + line: "SYSCTL_TCP_SYNCOOKIES='{{ minifirewall_sysctl_tcp_syncookies }}'" + regexp: "SYSCTL_TCP_SYNCOOKIES=('|\").*('|\")" + create: no + when: minifirewall_sysctl_tcp_syncookies is not none + +- name: Configure SYSCTL_ICMP_REDIRECTS + lineinfile: + dest: "/etc/default/minifirewall" + line: "SYSCTL_ICMP_REDIRECTS='{{ minifirewall_sysctl_icmp_redirects }}'" + regexp: "SYSCTL_ICMP_REDIRECTS=('|\").*('|\")" + create: no + when: minifirewall_sysctl_icmp_redirects is not none + +- name: Configure SYSCTL_RP_FILTER + lineinfile: + dest: "/etc/default/minifirewall" + line: "SYSCTL_RP_FILTER='{{ minifirewall_sysctl_rp_filter }}'" + regexp: "SYSCTL_RP_FILTER=('|\").*('|\")" + create: no + when: minifirewall_sysctl_rp_filter is not none + +- name: Configure SYSCTL_LOG_MARTIANS + lineinfile: + dest: "/etc/default/minifirewall" + line: "SYSCTL_LOG_MARTIANS='{{ minifirewall_sysctl_log_martians }}'" + regexp: "SYSCTL_LOG_MARTIANS=('|\").*('|\")" + create: no + when: minifirewall_sysctl_log_martians is not none - name: Stat minifirewall config file (after) stat: - path: "{{ minifirewall_main_file }}" + path: "/etc/default/minifirewall" register: minifirewall_after - name: restart minifirewall - # service: - # name: minifirewall - # state: restarted 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" - changed_when: "'starting IPTables rules is now finish : OK' in minifirewall_init_restart.stdout" + failed_when: "'minifirewall failed' in minifirewall_init_restart.stdout" when: - minifirewall_restart_if_needed | bool - minifirewall_is_running.rc == 0 - - minifirewall_before.stat.checksum != minifirewall_after.stat.checksum - -- name: restart minifirewall (noop) - meta: noop - register: minifirewall_init_restart - failed_when: False - changed_when: False - when: not (minifirewall_restart_if_needed | bool) + - minifirewall_before.stat.checksum != minifirewall_after.stat.checksum or minifirewall_upgrade_script is changed or minifirewall_upgrade_config is changed - debug: var: minifirewall_init_restart diff --git a/minifirewall/tasks/install.legacy.yml b/minifirewall/tasks/install.legacy.yml new file mode 100644 index 00000000..323426b5 --- /dev/null +++ b/minifirewall/tasks/install.legacy.yml @@ -0,0 +1,24 @@ +--- + +- name: dependencies are satisfied + apt: + name: iptables + state: present + +- name: init script is copied + template: + src: minifirewall.legacy.j2 + dest: /etc/init.d/minifirewall + force: "{{ minifirewall_force_upgrade_script | default('no') }}" + mode: "0700" + owner: root + group: root + +- name: configuration is copied + copy: + src: minifirewall.legacy.conf + dest: "{{ minifirewall_main_file }}" + force: "{{ minifirewall_force_upgrade_config | default('no') }}" + mode: "0600" + owner: root + group: root diff --git a/minifirewall/tasks/install.yml b/minifirewall/tasks/install.yml index 5d6438ed..daac6f81 100644 --- a/minifirewall/tasks/install.yml +++ b/minifirewall/tasks/install.yml @@ -6,19 +6,38 @@ state: present - name: init script is copied - template: - src: minifirewall.j2 + copy: + src: minifirewall dest: /etc/init.d/minifirewall force: "{{ minifirewall_force_upgrade_script | default('no') }}" mode: "0700" owner: root group: root + register: minifirewall_upgrade_script - name: configuration is copied copy: src: minifirewall.conf - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" force: "{{ minifirewall_force_upgrade_config | default('no') }}" mode: "0600" owner: root group: root + register: minifirewall_upgrade_config + +- name: includes directory is present + file: + path: /etc/minifirewall.d/ + state: directory + owner: root + group: root + mode: "0700" + +- name: examples for includes are present + copy: + src: "minifirewall.d/" + dest: "/etc/minifirewall.d/" + force: "no" + mode: "0600" + owner: root + group: root diff --git a/minifirewall/tasks/main.yml b/minifirewall/tasks/main.yml index 2a053d4f..4a838ee9 100644 --- a/minifirewall/tasks/main.yml +++ b/minifirewall/tasks/main.yml @@ -4,19 +4,105 @@ set_fact: minifirewall_restart_handler_name: "{{ minifirewall_restart_if_needed | bool | ternary('restart minifirewall', 'restart minifirewall (noop)') }}" -- include: install.yml +# Legacy or modern mode? ############################################## -- include: config.yml +- name: Check minifirewall + stat: + path: /etc/init.d/minifirewall + register: _minifirewall_check -- include: nrpe.yml - -- include: activate.yml - -- include: tail.yml - when: minifirewall_tail_included | bool - -- name: Force restart minifirewall - command: /bin/true - notify: restart minifirewall +# Legacy versions of minifirewall don't define the VERSION variable +- name: Look for minifirewall version + shell: "grep -E '^\\s*VERSION=' /etc/init.d/minifirewall" + failed_when: False changed_when: False - when: minifirewall_restart_force | bool + check_mode: False + register: _minifirewall_version_check + +- name: Set install mode to legacy if needed + set_fact: + minifirewall_install_mode: legacy + minifirewall_main_file: "{{ minifirewall_legacy_main_file }}" + minifirewall_tail_file: "{{ minifirewall_legacy_tail_file }}" + when: + - minifirewall_install_mode != 'modern' + - not (minifirewall_force_upgrade_script | bool) + - _minifirewall_version_check.rc == 1 # grep didn't find but the file exists + +- name: Set install mode to modern if not legacy + set_fact: + minifirewall_install_mode: modern + when: minifirewall_install_mode != 'legacy' + +- name: Debug install mode + debug: + var: minifirewall_install_mode + verbosity: 1 + +####################################################################### + +- name: Fail if minifirewall_main_file is defined (legacy mode) + fail: + msg: "Variable minifirewall_main_file is deprecated and not configurable anymore." + when: + - minifirewall_install_mode != 'legacy' + - minifirewall_main_file is defined + +- name: Install tasks (modern mode) + include: install.yml + when: minifirewall_install_mode != 'legacy' + +- name: Install tasks (legacy mode) + include: install.legacy.yml + when: minifirewall_install_mode == 'legacy' + +- name: Config tasks (modern mode) + include: config.yml + when: + - minifirewall_install_mode != 'legacy' + - minifirewall_update_config | bool + +- name: Config tasks (legacy mode) + include: config.legacy.yml + when: + - minifirewall_install_mode == 'legacy' + - minifirewall_update_config | bool + +- name: Utils tasks + include: utils.yml + +- name: NRPE tasks + include: nrpe.yml + +- name: Activation tasks + include: activate.yml + +- name: Tail tasks (modern mode) + include: tail.yml + when: + - minifirewall_install_mode != 'legacy' + - minifirewall_tail_included | bool + +- name: Tail tasks (legacy mode) + include: tail.legacy.yml + when: + - minifirewall_install_mode == 'legacy' + - minifirewall_tail_included | bool + +# Restart? + +- 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" + 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 new file mode 100644 index 00000000..7a13eefa --- /dev/null +++ b/minifirewall/tasks/tail.legacy.yml @@ -0,0 +1,50 @@ +--- +- name: Add some rules at the end of minifirewall file + template: + src: "{{ item }}" + dest: "{{ minifirewall_tail_file }}" + force: "{{ minifirewall_tail_force | bool }}" + follow: yes + loop: "{{ query('first_found', templates) }}" + vars: + templates: + - "templates/minifirewall-tail/minifirewall.{{ inventory_hostname }}.tail.j2" + - "templates/minifirewall-tail/minifirewall.{{ host_group | default('all') }}.tail.j2" + - "templates/minifirewall-tail/minifirewall.default.tail.j2" + - "templates/minifirewall.default.tail.j2" + register: minifirewall_tail_template + +- debug: + var: minifirewall_tail_template + verbosity: 1 + +- name: source minifirewall.tail at the end of the main file + blockinfile: + dest: "{{ minifirewall_main_file }}" + marker: "# {mark} ANSIBLE MANAGED EXTERNAL RULES" + block: ". {{ minifirewall_tail_file }}" + insertbefore: EOF + register: minifirewall_tail_source + +- debug: + 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" + when: + - minifirewall_tail_template is changed + - 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) + +- debug: + var: minifirewall_init_restart + verbosity: 1 diff --git a/minifirewall/tasks/tail.yml b/minifirewall/tasks/tail.yml index 0af9925d..1d708fa4 100644 --- a/minifirewall/tasks/tail.yml +++ b/minifirewall/tasks/tail.yml @@ -18,26 +18,10 @@ var: minifirewall_tail_template verbosity: 1 -- name: source minifirewall.tail at the end of the main file - blockinfile: - dest: "{{ minifirewall_main_file }}" - marker: "# {mark} ANSIBLE MANAGED EXTERNAL RULES" - block: ". {{ minifirewall_tail_file }}" - insertbefore: EOF - register: minifirewall_tail_source - -- debug: - var: minifirewall_tail_source - verbosity: 1 - - name: restart minifirewall - # service: - # name: minifirewall - # state: restarted 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" - changed_when: "'starting IPTables rules is now finish : OK' in minifirewall_init_restart.stdout" + failed_when: "'minifirewall failed' in minifirewall_init_restart.stdout" when: - minifirewall_tail_template is changed - minifirewall_restart_if_needed | bool diff --git a/minifirewall/tasks/utils.yml b/minifirewall/tasks/utils.yml new file mode 100644 index 00000000..775bdd95 --- /dev/null +++ b/minifirewall/tasks/utils.yml @@ -0,0 +1,21 @@ +--- + +- include_role: + name: evolix/remount-usr + +- name: /usr/share/scripts exists + file: + dest: /usr/share/scripts + mode: "0700" + owner: root + group: root + state: directory + +- name: blacklist-countries.sh is copied + copy: + src: blacklist-countries.sh + dest: /usr/share/scripts/blacklist-countries.sh + force: "no" + mode: "0700" + owner: root + group: root \ No newline at end of file diff --git a/minifirewall/templates/minifirewall.j2 b/minifirewall/templates/minifirewall.legacy.j2 old mode 100755 new mode 100644 similarity index 100% rename from minifirewall/templates/minifirewall.j2 rename to minifirewall/templates/minifirewall.legacy.j2 diff --git a/munin/files/plugins/dhcp_pool b/munin/files/plugins/dhcp_pool new file mode 100644 index 00000000..c33da5a7 --- /dev/null +++ b/munin/files/plugins/dhcp_pool @@ -0,0 +1,213 @@ +#!/usr/bin/perl -w +# +# Copyright (C) 2008 Rien Broekstra +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 dated June, +# 1991. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# +# Munin plugin to measure saturation of DHCP pools. +# +# Configuration variables: +# +# conffile - path to dhcpd's configuration file (default "/etc/dhcpd.conf") +# leasefile - path to dhcpd's leases file (default "/var/lib/dhcp/dhcpd.leases") +# +# Parameters: +# +# config (required) +# +# Version 1.0, 2-12-2008 +# +#%# family=auto +#%# capabilities=autoconf + +use POSIX; +use Time::Local; +use strict; + +my $CONFFILE = exists $ENV{'conffile'} ? $ENV{'conffile'} : "/etc/dhcp/dhcpd.conf"; +my $LEASEFILE = exists $ENV{'leasefile'} ? $ENV{'leasefile'} : "/var/lib/dhcp/dhcpd.leases"; + +if ( defined $ARGV[0] and $ARGV[0] eq "autoconf" ) { + if (-e ${CONFFILE} and -e ${LEASEFILE}) { + my %pools; + %pools = determine_pools(); + if (%pools) { + print "yes\n"; + } else { + print "no (no pools defined in config)\n"; + } + } else { + print "no (no config or lease file)\n"; + } +} +elsif ( defined $ARGV[0] and $ARGV[0] eq "config" ) { + my (%pools, $start, $label); + + # Print general information + print "graph_title DHCP pool usage (in %)\n"; + print "graph_args --upper-limit 100 -l 0\n"; + print "graph_vlabel %\n"; +#___ORI___# print "graph_category network\n"; + print "graph_category dhcpd\n"; + + # Determine the available IP pools + %pools = determine_pools(); + + # Print a label for each pool + foreach $start (sort (keys %pools)) { + $label = ip2string($start); + $label =~ s/\./\_/g; + print "_$label.label Pool " . ip2string($start) . " - " . ip2string($start + $pools{$start} - 1) . "\n"; + print "_$label.warning 90\n"; + print "_$label.critical 100\n"; + } +} +else { + my (@activeleases, %pools, $start, $end, $size, $free, $label, $lease); + + # Determine all leased IP addresses + @activeleases = determine_active_leases(); + + # Determine the available IP pools + %pools = determine_pools(); + + # For each pool, count how many leases from that pool are currently active + foreach $start (keys %pools) { + $size = $pools{$start}; + $end = $start+$size-1; + $free = $size; + + foreach $lease (@activeleases) { + if ($lease >= $start && $lease <= $end) { + $free--; + } + } + $label = ip2string($start); + $label =~ s/\./\_/g; + print "_$label.value ".sprintf("%.1f", 100*($size-$free)/$size)."\n"; + } +} + +# Parse dhcpd.conf for range statements. +# +# Returns a hash with start IP -> size +sub determine_pools { + my (%pools, @conffile, $line, $start, $end, $size); + + open(CONFFILE, "<${CONFFILE}") || exit -1; + @conffile = ; + close (CONFFILE); + + foreach $line (@conffile) { + next if $line =~ /^\s*#/; + + if ($line =~ /range[\s]+([\d]+\.[\d]+\.[\d]+\.[\d]+)[\s]+([\d]+\.[\d]+\.[\d]+\.[\d]+)/) { + $start = string2ip($1); + $end = string2ip($2); + + defined($start) || next; + defined($end) || next; + + # The range statement gives the lowest and highest IP addresses in a range. + $size = $end - $start + 1; + + $pools{$start} = $size; + } + } + return %pools; +} + +# Very simple parser for dhcpd.leases. This will break very easily if dhcpd decides to +# format the file differently. Ideally a simple recursive-descent parser should be used. +# +# Returns an array with currently leased IP's +sub determine_active_leases { + my (@leasefile, $startdate, $enddate, $lease, @activeleases, $mytz, $line, %saw); + + open(LEASEFILE, "<${LEASEFILE}") || exit -1; + @leasefile = ; + close (LEASEFILE); + + @activeleases = (); + + # Portable way of converting a GMT date/time string to timestamp is setting TZ to UTC, and then calling mktime() + $mytz = $ENV{'TZ'}; + $ENV{'TZ'} = 'UTC 0'; + tzset(); + + foreach $line (@leasefile) { + if ($line =~ /lease ([\d]+\.[\d]+\.[\d]+\.[\d]+)/) { + $lease = string2ip($1); + defined($lease) || next; + + undef $startdate; + undef $enddate; + } + elsif ($line =~ /starts \d ([\d]{4})\/([\d]{2})\/([\d]{2}) ([\d]{2}):([\d]{2}):([\d]{2})/) { + $startdate = mktime($6, $5, $4, $3, $2-1, $1-1900, 0, 0); + } + elsif ($line =~ /ends \d ([\d]{4})\/([\d]{2})\/([\d]{2}) ([\d]{2}):([\d]{2}):([\d]{2})/) { + $enddate = mktime($6, $5, $4, $3, $2-1, $1-1900, 0, 0); + } + elsif ($line =~ /binding state active/) { + if (defined($enddate) && defined($startdate) && defined($lease)) { + if ($startdate < time() && $enddate > time()) { + push (@activeleases, $lease); + } + } + } + + } + + # Set TZ back to its original setting + if (defined($mytz)) { + $ENV{'TZ'} = $mytz; + } + else { + delete $ENV{'TZ'}; + } + tzset(); + + # Sort the array, strip doubles, and return + return grep(!$saw{$_}++, @activeleases); +} + +# +# Helper routine to convert an IP address a.b.c.d into an integer +# +# Returns an integer representation of an IP address +sub string2ip { + my $string = shift; + defined($string) || return undef; + if ($string =~ /([\d]+)\.([\d]+)\.([\d]+)\.([\d]+)/) { + if ($1 < 0 || $1 > 255 || $2 < 0 || $2 > 255 || $3 < 0 || $3 > 255 || $4 < 0 || $4 > 255) { + return undef; + } + else { + return $1 << 24 | $2 << 16 | $3 << 8 | $4; + } + } + return undef; +} + +# +# Returns a dotted quad notation of an +# +sub ip2string { + my $ip = shift; + defined ($ip) || return undef; + return sprintf ("%d.%d.%d.%d", ($ip >> 24) & 0xff, ($ip >> 16) & 0xff, ($ip >> 8) & 0xff, $ip & 0xff); +} diff --git a/munin/tasks/main.yml b/munin/tasks/main.yml index d7cf8e2a..4720fbe5 100644 --- a/munin/tasks/main.yml +++ b/munin/tasks/main.yml @@ -12,6 +12,10 @@ - munin - packages +- name: Ensure /usr is still writable + include_role: + name: evolix/remount-usr + - block: - name: Replace localdomain in Munin config replace: @@ -31,6 +35,18 @@ tags: - munin +- include_role: + name: evolix/remount-usr + +- name: Install some Munin plugins (disabled) + copy: + src: 'plugins/{{ item }}' + dest: '/usr/share/munin/plugins/{{ item }}' + loop: + - dhcp_pool + tags: + - munin + - name: Ensure some Munin plugins are disabled file: path: '/etc/munin/plugins/{{ item }}' diff --git a/nagios-nrpe/files/plugins/check_dhcp_pool b/nagios-nrpe/files/plugins/check_dhcp_pool new file mode 100755 index 00000000..29157c2e --- /dev/null +++ b/nagios-nrpe/files/plugins/check_dhcp_pool @@ -0,0 +1,223 @@ +#!/usr/bin/perl -w +# +# Copyright (C) 2008 Rien Broekstra +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 dated June, +# 1991. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# +# Configuration variables: +# +# conffile - path to dhcpd's configuration file (default "/etc/dhcpd.conf") +# leasefile - path to dhcpd's leases file (default "/var/lib/dhcp/dhcpd.leases") +# + +use POSIX; +use Time::Local; +use strict; + +my $CONFFILE = exists $ENV{'conffile'} ? $ENV{'conffile'} : "/etc/dhcp/dhcpd.conf"; +my $LEASEFILE = exists $ENV{'leasefile'} ? $ENV{'leasefile'} : "/var/lib/dhcp/dhcpd.leases"; +my $WARNING_LEVEL = 70; +my $CRITICAL_LEVEL = 90; + +my (@activeleases, %dhcp_pools, $pool_start, $pool_end, $pool_size, $pool_free, $pool_usage, $pool_status, $label, $lease, $nagios_return_code, $nagios_ok, $nagios_warning, $nagios_critical, @nagios_text, @nagios_perfdata); + +# Determine all leased IP addresses +@activeleases = determine_active_leases(); + +# Determine the available IP pools +%dhcp_pools = determine_pools(); + +# Nagios return code +$nagios_return_code = 0; +$nagios_ok = 0; +$nagios_warning = 0; +$nagios_critical = 0; + +# For each pool, count how many leases from that pool are currently active +foreach $pool_start (keys %dhcp_pools) { + $pool_size = $dhcp_pools{$pool_start}; + $pool_end = $pool_start+$pool_size-1; + $pool_free = $pool_size; + + foreach $lease (@activeleases) { + if ($lease >= $pool_start && $lease <= $pool_end) { + $pool_free--; + } + } + + $label = ip2string($pool_start)."-".ip2string($pool_end); + $pool_usage = sprintf("%.1f", 100*($pool_size-$pool_free)/$pool_size); + + if ($pool_usage >= $CRITICAL_LEVEL) { + $nagios_return_code = 2; + $nagios_critical++; + $pool_status = "CRITICAL"; + } elsif ($pool_usage >= $WARNING_LEVEL) { + if ($nagios_return_code == 0 ) { + $nagios_return_code = 1; + } + $nagios_warning++; + $pool_status = "WARNING"; + } + else { + $nagios_ok++; + $pool_status = "OK"; + } + + push(@nagios_text, "$pool_status : $label - $pool_usage \n"); + push(@nagios_perfdata, "$label=$pool_usage%;$WARNING_LEVEL%;$CRITICAL_LEVEL%;;" ); + # 'label'=value[UOM];[warn];[crit];; + +} + + +print nagios_code_2_txt($nagios_return_code)." - ".$nagios_critical." CRIT / ".$nagios_warning." WARN / ".$nagios_ok." OK \n\n"; + +print grep(/CRITICAL/, @nagios_text); +print grep(/WARNING/, @nagios_text); +print grep(/OK/, @nagios_text); + +print "|@nagios_perfdata"; + +exit $nagios_return_code; + + +################ +###### FUNCTIONS + +# Parse dhcpd.conf for range statements. +# +# Returns a hash with start IP -> size +sub determine_pools { + my (%pools, @conffile, $line, $start, $end, $size); + + open(CONFFILE, "<${CONFFILE}") || exit -1; + @conffile = ; + close (CONFFILE); + + foreach $line (@conffile) { + next if $line =~ /^\s*#/; + + if ($line =~ /range[\s]+([\d]+\.[\d]+\.[\d]+\.[\d]+)[\s]+([\d]+\.[\d]+\.[\d]+\.[\d]+)/) { + $start = string2ip($1); + $end = string2ip($2); + + defined($start) || next; + defined($end) || next; + + # The range statement gives the lowest and highest IP addresses in a range. + $size = $end - $start + 1; + + $pools{$start} = $size; + } + } + return %pools; +} + +# Very simple parser for dhcpd.leases. This will break very easily if dhcpd decides to +# format the file differently. Ideally a simple recursive-descent parser should be used. +# +# Returns an array with currently leased IP's +sub determine_active_leases { + my (@leasefile, $startdate, $enddate, $lease, @activeleases, $mytz, $line, %saw); + + open(LEASEFILE, "<${LEASEFILE}") || exit -1; + @leasefile = ; + close (LEASEFILE); + + @activeleases = (); + + # Portable way of converting a GMT date/time string to timestamp is setting TZ to UTC, and then calling mktime() + $mytz = $ENV{'TZ'}; + $ENV{'TZ'} = 'UTC 0'; + tzset(); + + foreach $line (@leasefile) { + if ($line =~ /lease ([\d]+\.[\d]+\.[\d]+\.[\d]+)/) { + $lease = string2ip($1); + defined($lease) || next; + + undef $startdate; + undef $enddate; + } + elsif ($line =~ /starts \d ([\d]{4})\/([\d]{2})\/([\d]{2}) ([\d]{2}):([\d]{2}):([\d]{2})/) { + $startdate = mktime($6, $5, $4, $3, $2-1, $1-1900, 0, 0); + } + elsif ($line =~ /ends \d ([\d]{4})\/([\d]{2})\/([\d]{2}) ([\d]{2}):([\d]{2}):([\d]{2})/) { + $enddate = mktime($6, $5, $4, $3, $2-1, $1-1900, 0, 0); + } + elsif ($line =~ /binding state active/) { + if (defined($enddate) && defined($startdate) && defined($lease)) { + if ($startdate < time() && $enddate > time()) { + push (@activeleases, $lease); + } + } + } + + } + + # Set TZ back to its original setting + if (defined($mytz)) { + $ENV{'TZ'} = $mytz; + } + else { + delete $ENV{'TZ'}; + } + tzset(); + + # Sort the array, strip doubles, and return + return grep(!$saw{$_}++, @activeleases); +} + +# +# Helper routine to convert an IP address a.b.c.d into an integer +# +# Returns an integer representation of an IP address +sub string2ip { + my $string = shift; + defined($string) || return undef; + if ($string =~ /([\d]+)\.([\d]+)\.([\d]+)\.([\d]+)/) { + if ($1 < 0 || $1 > 255 || $2 < 0 || $2 > 255 || $3 < 0 || $3 > 255 || $4 < 0 || $4 > 255) { + return undef; + } + else { + return $1 << 24 | $2 << 16 | $3 << 8 | $4; + } + } + return undef; +} + +# +# Returns a dotted quad notation of an +# +sub ip2string { + my $ip = shift; + defined ($ip) || return undef; + return sprintf ("%d.%d.%d.%d", ($ip >> 24) & 0xff, ($ip >> 16) & 0xff, ($ip >> 8) & 0xff, $ip & 0xff); +} + + +# +# Return textual status of return code +# +sub nagios_code_2_txt{ + my $code = shift; + defined ($code) || return undef; + + if($code == 0 ) { return "OK" } + elsif( $code == 1 ) { return "WARNING" } + elsif( $code == 2 ) { return "CRITICAL" } +} diff --git a/nagios-nrpe/files/plugins/check_mount_rw b/nagios-nrpe/files/plugins/check_mount_rw index 06ffbd45..c26f8553 100755 --- a/nagios-nrpe/files/plugins/check_mount_rw +++ b/nagios-nrpe/files/plugins/check_mount_rw @@ -1,30 +1,40 @@ #!/bin/sh +# +# Verify that given mountpoints have 'read-write' option. -output=$(mktemp --tmpdir $(basename $0).XXXXXXXXXX) +output=$(mktemp --tmpdir $(basename "$0").XXXXXXXXXX) critical_count=0 ok_count=0 trap "rm -f $output" EXIT for mountpoint in $@; do + # We verify no mointpoints have 'read-only' option instead of checking + # for 'read-write' option, because there could be multiple device + # mounted on a sigle path. In that edge case only checking for the + # presence of the 'read-write' option would yeild a flase positive. if findmnt -O ro --noheadings "$mountpoint" 1>/dev/null 2>&1; then echo "CRITICAL - $mountpoint" >> "$output" - critical_count=$(( critical_count + 1)) + critical_count=$(( critical_count + 1)) else echo "OK - $mountpoint" >> "$output" - ok_count=$(( ok_count + 1)) + ok_count=$(( ok_count + 1)) fi done total_count=$(( ok_count + critical_count )) +plural='' +test "$total_count" -gt 1 && plural='s' + if [ $ok_count -eq $total_count ]; then - printf "OK - %d/%d no read-only mountpoint\n\n" "$ok_count" "$total_count" + printf "OK - %d/%d mountpoint%s have 'read-write' option\n\n" \ + "$ok_count" "$total_count" "$plural" cat "$output" exit 0 else - printf "CRITICAL - %d/%d read-only mountpoint\n\n" "$critical_count" "$total_count" + printf "CRITICAL - %d/%d mountpoint%s don't have 'read-write' option\n\n" \ + "$critical_count" "$total_count" "$plural" cat "$output" exit 2 fi - diff --git a/nagios-nrpe/templates/evolix.cfg.j2 b/nagios-nrpe/templates/evolix.cfg.j2 index 80eaaaf2..d3d102f0 100644 --- a/nagios-nrpe/templates/evolix.cfg.j2 +++ b/nagios-nrpe/templates/evolix.cfg.j2 @@ -57,6 +57,7 @@ command[check_bkctld_jails]=sudo /usr/sbin/bkctld check-jails command[check_bkctld]=sudo /usr/sbin/bkctld check command[check_postgrey]=/usr/lib/nagios/plugins/check_tcp -p10023 command[check_influxdb]=/usr/lib/nagios/plugins/check_http -I 127.0.0.1 -u /health -p 8086 -r '"status":"pass"' +command[check_dhcpd]=/usr/lib/nagios/plugins/check_procs -c1:1 -C dhcpd -t 60 # Local checks (not packaged) command[check_mem]={{ nagios_plugins_directory }}/check_mem -f -C -w 20 -c 10 @@ -83,6 +84,7 @@ command[check_php-fpm80]=sudo {{ nagios_plugins_directory }}/check_phpfpm_multi command[check_php-fpm81]=sudo {{ nagios_plugins_directory }}/check_phpfpm_multi /var/lib/lxc/php81/rootfs/etc/php/8.1/fpm/pool.d/ command[check_ipmi_sensors]=sudo /usr/lib/nagios/plugins/check_ipmi_sensor command[check_raid_status]=/usr/lib/nagios/plugins/check_raid +command[check_dhcp_pool]={{ nagios_plugins_directory }}/check_dhcp_pool # Check HTTP "many". Use this to check many websites (http, https, ports, sockets and SSL certificates). # Beware! All checks must not take more than 10s! diff --git a/nginx/files/nginx/evolinux-defaults.conf b/nginx/files/nginx/evolinux-defaults.conf index 4d300c4d..22eb7fef 100644 --- a/nginx/files/nginx/evolinux-defaults.conf +++ b/nginx/files/nginx/evolinux-defaults.conf @@ -1,4 +1,5 @@ # Evolix default customizations + server_tokens off; server_names_hash_max_size 512; server_names_hash_bucket_size 128; diff --git a/opendkim/files/opendkim-add.sh b/opendkim/files/opendkim-add.sh index 4e11f8cc..d70fdd4b 100644 --- a/opendkim/files/opendkim-add.sh +++ b/opendkim/files/opendkim-add.sh @@ -10,7 +10,7 @@ domain="$(echo "$1"|xargs)" if [ ! -f "/etc/ssl/private/dkim-${servername}.private" ]; then echo "Generate DKIM keys ..." - opendkim-genkey -D /etc/ssl/private/ -r -d "${domain}" -s "dkim-${servername}" + opendkim-genkey -h sha256 -b 4096 -D /etc/ssl/private/ -r -d "${domain}" -s "dkim-${servername}" chown opendkim:opendkim "/etc/ssl/private/dkim-${servername}.private" chmod 640 "/etc/ssl/private/dkim-${servername}.private" mv "/etc/ssl/private/dkim-${servername}.txt" "/etc/ssl/certs/" diff --git a/openvpn/README.md b/openvpn/README.md index 27b507d4..ddaffcce 100644 --- a/openvpn/README.md +++ b/openvpn/README.md @@ -23,6 +23,6 @@ Then, you can use `shellpki` to generate client certificates. * `openvpn_netmask`: netmask of the network to use for OpenVPN * `openvpn_netmask_cidr`: automatically generated prefix length of the netmask, in CIDR notation -## TODO +## Dependencies -* See TODO tasks in tasks/*.yml +* Files in `files/shellpki/*` are gotten from the upstream [shellpki](https://gitea.evolix.org/evolix/shellpki) and must be updated when the upstream is. diff --git a/openvpn/files/shellpki/cert-expirations.sh b/openvpn/files/shellpki/cert-expirations.sh new file mode 100644 index 00000000..9e27dcc7 --- /dev/null +++ b/openvpn/files/shellpki/cert-expirations.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +carp=$(/sbin/ifconfig carp0 2>/dev/null | grep 'status' | cut -d' ' -f2) + +if [ "$carp" = "backup" ]; then + exit 0 +fi + +echo "Warning : all times are in UTC !\n" + +echo "CA certificate:" +openssl x509 -enddate -noout -in /etc/shellpki/cacert.pem \ + | cut -d '=' -f 2 \ + | sed -e "s/^\(.*\)\ \(20..\).*/- \2 \1/" + +echo "" + +echo "Client certificates:" +cat /etc/shellpki/index.txt \ + | grep ^V \ + | awk -F "/" '{print $1,$5}' \ + | awk '{print $2,$5}' \ + | sed 's/CN=//' \ + | sed -E 's/([[:digit:]]{2})([[:digit:]]{2})([[:digit:]]{2})([[:digit:]]{2})([[:digit:]]{2})([[:digit:]]{2})Z (.*)/- 20\1 \2 \3 \4:\5:\6 \7/' \ + | awk '{if ($3 == "01") $3="Jan"; else if ($3 == "02") $3="Feb"; else if ($3 == "03") $3="Mar"; else if ($3 == "04") $3="Apr"; else if ($3 == "05") $3="May"; else if ($3 == "06") $3="Jun"; else if ($3 == "07") $3="Jul"; else if ($3 == "08") $3="Aug"; else if ($3 == "09") $3="Sep"; else if ($3 == "10") $3="Oct"; else if ($3 == "11") $3="Nov"; else if ($3 == "12") $3="Dec"; print $0;}' \ + | sort -n -k 2 -k 3M -k 4 diff --git a/openvpn/files/shellpki/openssl.cnf b/openvpn/files/shellpki/openssl.cnf new file mode 100644 index 00000000..2c87f10d --- /dev/null +++ b/openvpn/files/shellpki/openssl.cnf @@ -0,0 +1,58 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = /etc/shellpki +certs = $dir/certs +new_certs_dir = $dir/tmp +database = $dir/index.txt +certificate = $dir/cacert.pem +serial = $dir/serial +crl = $dir/crl.pem +private_key = $dir/cakey.key +RANDFILE = $dir/.rand +default_days = 365 +default_crl_days= 365 +default_md = sha256 +preserve = no +policy = policy_match + +[ policy_match ] +countryName = supplied +stateOrProvinceName = supplied +organizationName = supplied +organizationalUnitName = optional +commonName = supplied +emailAddress = supplied + +[ req ] +default_bits = 2048 +distinguished_name = req_distinguished_name + +[ v3_ca ] +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer:always +basicConstraints = CA:true + +[ v3_ocsp ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = OCSPSigning + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = FR +countryName_min = 2 +countryName_max = 2 +stateOrProvinceName = State or Province +stateOrProvinceName_default = 13 +localityName = Locality Name (eg, city) +localityName_default = Marseille +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Evolix +organizationalUnitName = Organizational Unit Name (eg, section) +commonName = Common Name (eg, your name or your server\'s hostname) +commonName_max = 64 +emailAddress = Email Address +emailAddress_default = security@evolix.net +emailAddress_max = 40 diff --git a/openvpn/files/shellpki/shellpki b/openvpn/files/shellpki/shellpki new file mode 100755 index 00000000..5d139866 --- /dev/null +++ b/openvpn/files/shellpki/shellpki @@ -0,0 +1,1106 @@ +#!/bin/sh +# +# shellpki is a wrapper around OpenSSL to manage a small PKI +# + +set -u + +VERSION="22.04" + +show_version() { + cat <, + Thomas Martin , + Gregory Colpart , + Romain Dessort , + Benoit Série , + Victor Laborie , + Daniel Jakots , + Patrick Marchand , + Jérémy Lecour , + Jérémy Dubois + and others. + +shellpki comes with ABSOLUTELY NO WARRANTY. This is free software, +and you are welcome to redistribute it under certain conditions. +See the MIT Licence for details. +END +} + +show_usage() { + cat < [options] [CommonName] +Warning: [options] always must be before [CommonName] and after + +EOF +show_usage_init +show_usage_create +show_usage_revoke +show_usage_list +show_usage_check +show_usage_ocsp + + cat < + + Options + --non-interactive do not prompt the user, and exit if an error occurs + +EOF +} + +show_usage_create() { + cat < + + Options + -f, --file, --csr-file create a client certificate from a CSR (doesn't need key) + -p, --password prompt the user for a password to set on the client key + --password-file if provided with a path to a readable file, the first line is read and set as password on the client key + --days specify how many days the certificate should be valid + --end-date specify until which date the certificate should be valid, in "YYYY/MM/DD hh:mm:ss" format, UTC +0 + --non-interactive do not prompt the user, and exit if an error occurs + --replace-existing if the certificate already exists, revoke it before creating a new one + +EOF +} + +show_usage_revoke() { + cat < + + Options + --non-interactive do not prompt the user, and exit if an error occurs + +EOF +} + +show_usage_list() { + cat < + + Options + -a, --all list all certificates: valid and revoked ones + -v, --valid list all valid certificates + -r, --revoked list all revoked certificates + +EOF +} + +show_usage_check() { + cat < + +EOF +} + +error() { + echo "${1}" >&2 + exit 1 +} + +warning() { + echo "${1}" >&2 +} + +verify_ca_password() { + "${OPENSSL_BIN}" rsa \ + -in "${CA_KEY}" \ + -passin pass:"${CA_PASSWORD}" \ + >/dev/null 2>&1 +} +get_real_path() { + # --canonicalize is supported on Linux + # -f is supported on Linux and OpenBSD + readlink -f -- "${1}" +} + +ask_ca_password() { + attempt=${1:-0} + max_attempts=3 + + trap 'unset CA_PASSWORD' 0 + + if [ ! -f "${CA_KEY}" ]; then + error "You must initialize your PKI with \`shellpki init' !" + fi + if [ "${attempt}" -gt 0 ]; then + warning "Invalid password, retry." + fi + if [ "${attempt}" -ge "${max_attempts}" ]; then + error "Maximum number of attempts reached (${max_attempts})." + fi + if [ -z "${CA_PASSWORD:-}" ]; then + if [ "${non_interactive}" -eq 1 ]; then + error "In non-interactive mode, you must pass CA_PASSWORD as environment variable" + fi + stty -echo + printf "Password for CA key: " + read -r CA_PASSWORD + stty echo + printf "\n" + fi + if [ -z "${CA_PASSWORD:-}" ] || ! verify_ca_password; then + unset CA_PASSWORD + attempt=$(( attempt + 1 )) + ask_ca_password "${attempt}" + fi +} +ask_user_password() { + trap 'unset PASSWORD' 0 + + if [ -z "${PASSWORD:-}" ]; then + if [ "${non_interactive}" -eq 1 ]; then + error "In non-interactive mode, you must pass PASSWORD as environment variable or use --password-file" + fi + stty -echo + printf "Password for user key: " + read -r PASSWORD + stty echo + printf "\n" + fi + if [ -z "${PASSWORD:-}" ]; then + warning "Warning: empty password from input" + fi +} +replace_existing_or_abort() { + cn=${1:?} + if [ "${non_interactive}" -eq 1 ]; then + if [ "${replace_existing}" -eq 1 ]; then + revoke --non-interactive "${cn}" + else + error "${cn} already exists, use \`--replace-existing' to force" + fi + else + if [ "${replace_existing}" -eq 1 ]; then + revoke "${cn}" + else + printf "%s already exists, do you want to revoke and recreate it ? [y/N] " "${cn}" + read -r REPLY + resp=$(echo "${REPLY}" | tr 'Y' 'y') + + if [ "${resp}" = "y" ]; then + revoke "${cn}" + else + error "Aborted" + fi + fi + fi +} + +init() { + umask 0177 + + [ -d "${CA_DIR}" ] || mkdir -m 0750 "${CA_DIR}" + [ -d "${CRT_DIR}" ] || mkdir -m 0750 "${CRT_DIR}" + [ -f "${INDEX_FILE}" ] || touch "${INDEX_FILE}" + [ -f "${CRL}" ] || touch "${CRL}" + [ -f "${SERIAL}" ] || echo "01" > "${SERIAL}" + + non_interactive=0 + + # Parse options + # based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-} in + --non-interactive) + non_interactive=1 + ;; + --) + # End of all options. + shift + break + ;; + -?*) + # ignore unknown options + warning "Warning: unknown option (ignored): \`$1'" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + cn="${1:-}" + if [ -z "${cn}" ]; then + show_usage_init >&2 + exit 1 + fi + + if [ -f "${CA_KEY}" ]; then + if [ "${non_interactive}" -eq 1 ]; then + error "${CA_KEY} already exists, erase it manually if you want to start over." + else + printf "%s already exists, do you really want to erase it ? [y/N] " "${CA_KEY}" + read -r REPLY + resp=$(echo "${REPLY}" | tr 'Y' 'y') + if [ "${resp}" = "y" ]; then + rm -f "${CA_KEY}" "${CA_CERT}" + fi + fi + fi + + passout_arg="" + if [ -n "${CA_PASSWORD:-}" ]; then + passout_arg="-passout pass:${CA_PASSWORD}" + elif [ "${non_interactive}" -eq 1 ]; then + error "In non-interactive mode, you must pass CA_PASSWORD as environment variable." + fi + + if [ ! -f "${CA_KEY}" ]; then + "${OPENSSL_BIN}" genrsa \ + -out "${CA_KEY}" \ + ${passout_arg} \ + -aes256 \ + "${CA_KEY_LENGTH}" \ + >/dev/null 2>&1 + # shellcheck disable=SC2181 + if [ "$?" -ne 0 ]; then + error "Error generating the CA key" + fi + fi + + if [ -f "${CA_CERT}" ]; then + if [ "${non_interactive}" -eq 1 ]; then + error "${CA_CERT} already exists, erase it manually if you want to start over." + else + printf "%s already exists, do you really want to erase it ? [y/N] " "${CA_CERT}" + read -r REPLY + resp=$(echo "${REPLY}" | tr 'Y' 'y') + if [ "${resp}" = "y" ]; then + rm "${CA_CERT}" + fi + fi + fi + + if [ ! -f "${CA_CERT}" ]; then + ask_ca_password 0 + fi + + if [ ! -f "${CA_CERT}" ]; then + "${OPENSSL_BIN}" req \ + -new \ + -batch \ + -sha512 \ + -x509 \ + -days 3650 \ + -extensions v3_ca \ + -passin pass:"${CA_PASSWORD}" \ + -key "${CA_KEY}" \ + -out "${CA_CERT}" \ + -config /dev/stdin <&2 + exit 1 + fi + ocsp_csr_file="${CSR_DIR}/ocsp.csr" + + url=$(echo "${ocsp_uri}" | cut -d':' -f1) + port=$(echo "${ocsp_uri}" | cut -d':' -f2) + + if [ ! -f "${OCSP_KEY}" ]; then + "${OPENSSL_BIN}" genrsa \ + -out "${OCSP_KEY}" \ + "${KEY_LENGTH}" \ + >/dev/null 2>&1 + # shellcheck disable=SC2181 + if [ "$?" -ne 0 ]; then + error "Error generating the OCSP key" + fi + fi + + "${OPENSSL_BIN}" req \ + -batch \ + -new \ + -key "${OCSP_KEY}" \ + -out "${ocsp_csr_file}" \ + -config /dev/stdin < /dev/null) + # shellcheck disable=SC2181 + if [ "$?" -ne 0 ]; then + error "Invalid end date format: \`${end_date}' can't be parsed by date(1). Expected format: YYYY/MM/DD [hh[:mm[:ss]]]." + else + crt_expiration_arg="-enddate ${cert_end_date}" + fi + elif [ "${SYSTEM}" = "openbsd" ]; then + cert_end_date=$(TZ=:Zulu date -f "%C%y/%m/%d %H:%M:%S" -j "${end_date}" +"%Y%m%d%H%M%SZ" 2> /dev/null) + # shellcheck disable=SC2181 + if [ "$?" -ne 0 ]; then + error "Invalid end date format: \`${end_date}' can't be parsed by date(1). Expected format: YYYY/MM/DD hh:mm:ss." + else + crt_expiration_arg="-enddate ${cert_end_date}" + fi + else + error "System ${SYSTEM} not supported." + fi + fi + if [ "${non_interactive}" -eq 1 ]; then + batch_arg="-batch" + else + batch_arg="" + fi + + if [ "${from_csr}" -eq 1 ]; then + if [ "${ask_pass}" -eq 1 ]; then + warning "Warning: -p|--password is ignored with -f|--file|--crt-file" + fi + if [ -n "${password_file:-}" ]; then + warning "Warning: --password-file is ignored with -f|--file|--crt-file" + fi + + crt_file="${CRT_DIR}/${cn}.crt" + + # ask for CA passphrase + ask_ca_password 0 + + # check if csr_file is a CSR + "${OPENSSL_BIN}" req \ + -noout \ + -subject \ + -in "${csr_file}" \ + >/dev/null 2>&1 + # shellcheck disable=SC2181 + if [ "$?" -ne 0 ]; then + error "${csr_file} is not a valid CSR !" + fi + + # check if csr_file contain a CN + "${OPENSSL_BIN}" req \ + -noout \ + -subject \ + -in "${csr_file}" \ + | grep -Eo "CN\s*=[^,/]*" \ + >/dev/null 2>&1 + # shellcheck disable=SC2181 + if [ "$?" -ne 0 ]; then + error "${csr_file} doesn't contain a CommonName !" + fi + + # get CN from CSR + cn=$("${OPENSSL_BIN}" req -noout -subject -in "${csr_file}" | grep -Eo "CN\s*=[^,/]*" | cut -d'=' -f2 | xargs) + + # check if CN already exists + if [ -f "${crt_file}" ]; then + replace_existing_or_abort "${cn}" + fi + + # ca sign and generate cert + if [ "${non_interactive}" -eq 1 ]; then + batch_arg="-batch" + else + batch_arg="" + fi + "${OPENSSL_BIN}" ca \ + ${batch_arg} \ + -config "${CONF_FILE}" \ + -in "${csr_file}" \ + -passin pass:"${CA_PASSWORD}" \ + -out "${crt_file}" \ + ${crt_expiration_arg} + # shellcheck disable=SC2181 + if [ "$?" -ne 0 ]; then + error "Error generating the certificate" + else + echo "The certificate file is available at \`${crt_file}'" + fi + else + if [ -z "${cn}" ]; then + show_usage_create >&2 + exit 1 + fi + csr_file="${CSR_DIR}/${cn}-${SUFFIX}.csr" + crt_file="${CRT_DIR}/${cn}.crt" + key_file="${KEY_DIR}/${cn}-${SUFFIX}.key" + ovpn_file="${OVPN_DIR}/${cn}-${SUFFIX}.ovpn" + pkcs12_file="${PKCS12_DIR}/${cn}-${SUFFIX}.p12" + + # ask for CA passphrase + ask_ca_password 0 + + if [ "${ask_pass}" -eq 1 ]; then + ask_user_password + fi + + # check if CN already exists + if [ -f "${crt_file}" ]; then + replace_existing_or_abort "${cn}" + fi + + # generate private key + pass_args="" + if [ -n "${password_file:-}" ]; then + pass_args="-aes256 -passout file:${password_file}" + elif [ -n "${PASSWORD:-}" ]; then + pass_args="-aes256 -passout pass:${PASSWORD}" + fi + "${OPENSSL_BIN}" genrsa \ + -out "${key_file}" \ + ${pass_args} \ + "${KEY_LENGTH}" \ + >/dev/null 2>&1 + # shellcheck disable=SC2181 + if [ "$?" -eq 0 ]; then + echo "The KEY file is available at \`${key_file}'" + else + error "Error generating the private key" + fi + + # generate csr req + pass_args="" + if [ -n "${password_file:-}" ]; then + pass_args="-passin file:${password_file}" + elif [ -n "${PASSWORD:-}" ]; then + pass_args="-passin pass:${PASSWORD}" + fi + "${OPENSSL_BIN}" req \ + -batch \ + -new \ + -key "${key_file}" \ + -out "${csr_file}" \ + ${pass_args} \ + -config /dev/stdin </dev/null 2>&1 + # shellcheck disable=SC2181 + if [ "$?" -ne 0 ]; then + rm -f "${crt_file}" + fi + if [ ! -f "${crt_file}" ]; then + error "Error in CSR creation" + fi + + chmod 640 "${crt_file}" + + echo "The CRT file is available in ${crt_file}" + + # generate pkcs12 format + pass_args="" + if [ -n "${password_file:-}" ]; then + # Hack for pkcs12 : + # If passin and passout files are the same path, it expects 2 lines + # so we make a temporary copy of the password file + password_file_out=$(mktemp) + cp "${password_file}" "${password_file_out}" + pass_args="-passin file:${password_file} -passout file:${password_file_out}" + elif [ -n "${PASSWORD:-}" ]; then + pass_args="-passin pass:${PASSWORD} -passout pass:${PASSWORD}" + else + pass_args="-passout pass:" + fi + "${OPENSSL_BIN}" pkcs12 \ + -export \ + -nodes \ + -inkey "${key_file}" \ + -in "${crt_file}" \ + -out "${pkcs12_file}" \ + ${pass_args} + # shellcheck disable=SC2181 + if [ "$?" -ne 0 ]; then + error "Error generating the pkcs12 file" + fi + + if [ -n "${password_file_out:-}" ]; then + # Hack for pkcs12 : + # Destroy the temporary file + rm -f "${password_file_out}" + fi + + chmod 640 "${pkcs12_file}" + echo "The PKCS12 config file is available at \`${pkcs12_file}'" + + # generate openvpn format + if [ -e "${CA_DIR}/ovpn.conf" ]; then + cat "${CA_DIR}/ovpn.conf" - > "${ovpn_file}" < +$(cat "${CA_CERT}") + + + +$(cat "${crt_file}") + + + +$(cat "${key_file}") + +EOF + chmod 640 "${ovpn_file}" + echo "The OpenVPN config file is available at \`${ovpn_file}'" + fi + + # Copy files if destination exists + if [ -d "${COPY_DIR}" ]; then + for file in "${crt_file}" "${key_file}" "${pkcs12_file}" "${ovpn_file}"; do + if [ -f "${file}" ]; then + new_file="${COPY_DIR}/$(basename "${file}")" + if [ "${replace_existing}" -eq 1 ]; then + cp -f "${file}" "${COPY_DIR}/" + else + if [ "${non_interactive}" -eq 1 ]; then + if [ -f "${new_file}" ]; then + echo "File \`${file}' has not been copied to \`${new_file}', it already exists" >&2 + continue + else + cp "${file}" "${COPY_DIR}/" + fi + else + cp -i "${file}" "${COPY_DIR}/" + fi + fi + echo "File \`${file}' has been copied to \`${new_file}'" + fi + done + + # shellcheck disable=SC2086 + chown -R ${PKI_USER}:${PKI_USER} "${COPY_DIR}/" + chmod -R u=rwX,g=rwX,o= "${COPY_DIR}/" + fi + fi +} + +revoke() { + non_interactive=0 + + # Parse options + # based on https://gist.github.com/deshion/10d3cb5f88a21671e17a + while :; do + case ${1:-} in + --non-interactive) + non_interactive=1 + ;; + --) + # End of all options. + shift + break + ;; + -?*) + # ignore unknown options + warning "Warning: unknown option (ignored): \`$1'" + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift + done + + # The name of the certificate + cn="${1:-}" + + if [ -z "${cn}" ]; then + show_usage_revoke >&2 + exit 1 + fi + + crt_file="${CRT_DIR}/${cn}.crt" + # check if CRT exists + if [ ! -f "${crt_file}" ]; then + error "Unknow CN: ${cn} (\`${crt_file}' not found)" + fi + + # check if CRT is a valid + "${OPENSSL_BIN}" x509 \ + -noout \ + -subject \ + -in "${crt_file}" \ + >/dev/null 2>&1 + # shellcheck disable=SC2181 + if [ "$?" -ne 0 ]; then + error "${crt_file} is not a valid CRT, you must delete it !" + fi + + # ask for CA passphrase + ask_ca_password 0 + + echo "Revoke certificate ${crt_file} :" + "${OPENSSL_BIN}" ca \ + -config "${CONF_FILE}" \ + -passin pass:"${CA_PASSWORD}" \ + -revoke "${crt_file}" + # shellcheck disable=SC2181 + if [ "$?" -eq 0 ]; then + rm "${crt_file}" + fi + + "${OPENSSL_BIN}" ca \ + -config "${CONF_FILE}" \ + -passin pass:"${CA_PASSWORD}" \ + -gencrl \ + -out "${CRL}" +} + +list() { + if [ ! -f "${INDEX_FILE}" ]; then + exit 0 + fi + + if [ -z "${1:-}" ]; then + show_usage_list >&2 + exit 1 + fi + + while :; do + case "${1:-}" in + -a|--all) + list_valid=0 + list_revoked=0 + ;; + -v|--valid) + list_valid=0 + list_revoked=1 + ;; + -r|--revoked) + list_valid=1 + list_revoked=0 + ;; + -?*) + warning "unknow option ${1} (ignored)" + ;; + *) + break + ;; + esac + shift + done + + if [ "${list_valid}" -eq 0 ]; then + certs=$(grep "^V" "${INDEX_FILE}") + fi + + if [ "${list_revoked}" -eq 0 ]; then + certs=$(grep "^R" "${INDEX_FILE}") + fi + + if [ "${list_valid}" -eq 0 ] && [ "${list_revoked}" -eq 0 ]; then + certs=$(cat "${INDEX_FILE}") + fi + + echo "${certs}" | grep -Eo "CN\s*=[^,/]*" | cut -d'=' -f2 | xargs -n1 +} + +cert_end_date() { + "${OPENSSL_BIN}" x509 -noout -enddate -in "${1}" | cut -d'=' -f2 +} + +check() { + # default expiration alert + # TODO: permit override with parameters + min_day=90 + cur_epoch=$(date -u +'%s') + + for cert in "${CRT_DIR}"/*; do + end_date=$(cert_end_date "${cert}") + end_epoch=$(date -ud "${end_date}" +'%s') + diff_epoch=$(( end_epoch - cur_epoch )) + diff_day=$(( diff_epoch / 60 / 60 / 24 )) + if [ "${diff_day}" -lt "${min_day}" ]; then + if [ "${diff_day}" -le 0 ]; then + echo "${cert} has expired" + else + echo "${cert} expire in ${diff_day} days" + fi + fi + done +} + +is_user() { + getent passwd "${1}" >/dev/null +} +is_group() { + getent group "${1}" >/dev/null +} + +main() { + # Know what system we are on, because OpenBSD and Linux do not implement date(1) in the same way + SYSTEM=$(uname | tr '[:upper:]' '[:lower:]') + + # default config + # TODO: override with /etc/default/shellpki + CONF_FILE="/etc/shellpki/openssl.cnf" + + if [ "$(uname)" = "OpenBSD" ]; then + PKI_USER="_shellpki" + else + PKI_USER="shellpki" + fi + + if [ "${USER}" != "root" ] && [ "${USER}" != "${PKI_USER}" ]; then + error "Please become root before running ${0} !" + fi + + # retrieve CA path from config file + CA_DIR=$(grep -E "^dir" "${CONF_FILE}" | cut -d'=' -f2 | xargs -n1) + CA_KEY=$(grep -E "^private_key" "${CONF_FILE}" | cut -d'=' -f2 | xargs -n1 | sed "s~\$dir~${CA_DIR}~") + CA_CERT=$(grep -E "^certificate" "${CONF_FILE}" | cut -d'=' -f2 | xargs -n1 | sed "s~\$dir~${CA_DIR}~") + OCSP_KEY="${CA_DIR}/ocsp.key" + OCSP_CERT="${CA_DIR}/ocsp.pem" + CRT_DIR=$(grep -E "^certs" "${CONF_FILE}" | cut -d'=' -f2 | xargs -n1 | sed "s~\$dir~${CA_DIR}~") + TMP_DIR=$(grep -E "^new_certs_dir" "${CONF_FILE}" | cut -d'=' -f2 | xargs -n1 | sed "s~\$dir~${CA_DIR}~") + INDEX_FILE=$(grep -E "^database" "${CONF_FILE}" | cut -d'=' -f2 | xargs -n1 | sed "s~\$dir~${CA_DIR}~") + SERIAL=$(grep -E "^serial" "${CONF_FILE}" | cut -d'=' -f2 | xargs -n1 | sed "s~\$dir~${CA_DIR}~") + CRL=$(grep -E "^crl" "${CONF_FILE}" | cut -d'=' -f2 | xargs -n1 | sed "s~\$dir~${CA_DIR}~") + + # directories for clients key, csr, crt + KEY_DIR="${CA_DIR}/private" + CSR_DIR="${CA_DIR}/requests" + PKCS12_DIR="${CA_DIR}/pkcs12" + OVPN_DIR="${CA_DIR}/openvpn" + + COPY_DIR="$(dirname "${CONF_FILE}")/copy_output" + + CA_KEY_LENGTH=4096 + if [ "${CA_KEY_LENGTH}" -lt 4096 ]; then + error "CA key must be at least 4096 bits long." + fi + KEY_LENGTH=2048 + if [ "${KEY_LENGTH}" -lt 2048 ]; then + error "User key must be at least 2048 bits long." + fi + + OPENSSL_BIN=$(command -v openssl) + SUFFIX=$(TZ=:Zulu /bin/date +"%Y%m%d%H%M%SZ") + + if ! is_user "${PKI_USER}" || ! is_group "${PKI_USER}"; then + error "You must create ${PKI_USER} user and group !" + fi + + if [ ! -e "${CONF_FILE}" ]; then + error "${CONF_FILE} is missing" + fi + + mkdir -p "${CA_DIR}" "${CRT_DIR}" "${KEY_DIR}" "${CSR_DIR}" "${PKCS12_DIR}" "${OVPN_DIR}" "${TMP_DIR}" + + command=${1:-help} + + case "${command}" in + init) + shift + init "$@" + ;; + + ocsp) + shift + ocsp "$@" + ;; + + create) + shift + create "$@" + ;; + + revoke) + shift + revoke "$@" + ;; + + list) + shift + list "$@" + ;; + + check) + shift + check "$@" + ;; + + version|--version) + show_version + exit 0 + ;; + + help|--help) + show_usage + exit 0 + ;; + + *) + show_usage >&2 + exit 1 + ;; + esac + + # fix right + chown -R "${PKI_USER}":"${PKI_USER}" "${CA_DIR}" + chmod 750 "${CA_DIR}" "${CRT_DIR}" "${KEY_DIR}" "${CSR_DIR}" "${PKCS12_DIR}" "${OVPN_DIR}" "${TMP_DIR}" + chmod 600 "${INDEX_FILE}"* "${SERIAL}"* "${CA_KEY}" "${CRL}" + chmod 640 "${CA_CERT}" +} + +main "$@" diff --git a/openvpn/tasks/debian.yml b/openvpn/tasks/debian.yml index f94e19b6..3ace1f4c 100644 --- a/openvpn/tasks/debian.yml +++ b/openvpn/tasks/debian.yml @@ -12,11 +12,6 @@ - client - server -- name: Clone shellpki repo - git: - repo: "https://gitea.evolix.org/evolix/shellpki.git" - dest: /root/shellpki - - name: Create the shellpki user user: name: shellpki @@ -38,30 +33,14 @@ - name: Copy shellpki files copy: - src: "{{ item.source }}" + src: "shellpki/{{ item.source }}" dest: "{{ item.destination }}" - remote_src: yes - with_items: - - { source: "/root/shellpki/openssl.cnf", destination: "/etc/shellpki/openssl.cnf" } - - { source: "/root/shellpki/shellpki", destination: "/usr/local/sbin/shellpki" } - -- include_role: - name: evolix/remount-usr - -- name: Change files permissions - file: - dest: "{{ item.dest }}" mode: "{{ item.mode }}" owner: "{{ item.owner }}" group: "{{ item.group }}" with_items: - - { dest: "/etc/shellpki/openssl.cnf", mode: "0640", owner: "shellpki", group: "shellpki" } - - { dest: "/usr/local/sbin/shellpki", mode: "0755", owner: "root", group: "root" } - -- name: Delete local shellpki repo - file: - state: absent - dest: "/root/shellpki" + - { source: "openssl.cnf", destination: "/etc/shellpki/openssl.cnf", mode: "0640", owner: "shellpki", group: "shellpki" } + - { source: "shellpki", destination: "/usr/local/sbin/shellpki", mode: "0755", owner: "root", group: "root" } - name: Add sudo rights lineinfile: @@ -251,30 +230,16 @@ notify: restart nagios-nrpe-server when: nrpe_evolix_config.stat.exists -# BEGIN TODO : Get this script from master branch when cloning it at the beginning when dev branch is merged with master (this script is currently not available on master branch) -- name: Clone dev branch of shellpki repo - git: - repo: "https://gitea.evolix.org/evolix/shellpki.git" - dest: /root/shellpki-dev - version: dev - - include_role: name: evolix/remount-usr - name: Copy shellpki script copy: - src: "/root/shellpki-dev/cert-expirations.sh" + src: "shellpki/cert-expirations.sh" dest: "/usr/share/scripts/cert-expirations.sh" mode: "0700" owner: root group: root - remote_src: yes - -- name: Delete local shellpki-dev repo - file: - state: absent - dest: "/root/shellpki-dev" -# END TODO - name: Install cron to warn about certificates expiration cron: diff --git a/openvpn/tasks/openbsd.yml b/openvpn/tasks/openbsd.yml index 2edbec70..18cd0156 100644 --- a/openvpn/tasks/openbsd.yml +++ b/openvpn/tasks/openbsd.yml @@ -13,11 +13,6 @@ group: wheel mode: "0755" -- name: Clone shellpki repo - git: - repo: "https://gitea.evolix.org/evolix/shellpki.git" - dest: /root/shellpki - - name: Create the shellpki user user: name: _shellpki @@ -36,27 +31,14 @@ - name: Copy shellpki files copy: - src: "{{ item.source }}" + src: "shellpki/{{ item.source }}" dest: "{{ item.destination }}" - remote_src: yes - with_items: - - { source: "/root/shellpki/openssl.cnf", destination: "/etc/shellpki/openssl.cnf" } - - { source: "/root/shellpki/shellpki", destination: "/usr/local/sbin/shellpki" } - -- name: Change files permissions - file: - dest: "{{ item.dest }}" mode: "{{ item.mode }}" owner: "{{ item.owner }}" group: "{{ item.group }}" with_items: - - { dest: "/etc/shellpki/openssl.cnf", mode: "0640", owner: "_shellpki", group: "_shellpki"} - - { dest: "/usr/local/sbin/shellpki", mode: "0755", owner: "root", group: "wheel" } - -- name: Delete local shellpki repo - file: - state: absent - dest: "/root/shellpki" + - { source: "openssl.cnf", destination: "/etc/shellpki/openssl.cnf", mode: "0640", owner: "_shellpki", group: "_shellpki" } + - { source: "shellpki", destination: "/usr/local/sbin/shellpki", mode: "0755", owner: "root", group: "wheel" } - name: Add sudo rights lineinfile: @@ -193,27 +175,13 @@ notify: restart nrpe when: nrpe_evolix_config.stat.exists -# BEGIN TODO : Get this script from master branch when cloning it at the beginning when dev branch is merged with master (this script is currently not available on master branch) -- name: Clone dev branch of shellpki repo - git: - repo: "https://gitea.evolix.org/evolix/shellpki.git" - dest: /root/shellpki-dev - version: dev - - name: Copy shellpki script copy: - src: "/root/shellpki-dev/cert-expirations.sh" + src: "shellpki/cert-expirations.sh" dest: "/usr/share/scripts/cert-expirations.sh" mode: "0700" owner: root group: wheel - remote_src: yes - -- name: Delete local shellpki-dev repo - file: - state: absent - dest: "/root/shellpki-dev" -# END TODO - name: Install cron to warn about certificates expiration cron: diff --git a/openvpn/templates/server.conf.j2 b/openvpn/templates/server.conf.j2 index a89701e8..23ce3e2b 100644 --- a/openvpn/templates/server.conf.j2 +++ b/openvpn/templates/server.conf.j2 @@ -6,6 +6,7 @@ port 1194 proto udp dev tun mode server +topology subnet keepalive 10 120 tls-exit diff --git a/postfix/templates/packmail_master.cf.j2 b/postfix/templates/packmail_master.cf.j2 index 50aeeec4..9627fcb3 100644 --- a/postfix/templates/packmail_master.cf.j2 +++ b/postfix/templates/packmail_master.cf.j2 @@ -158,7 +158,7 @@ smtp-amavis unix - - y - 2 lmtp -o smtpd_hard_error_limit=1000 -o smtpd_client_connection_count_limit=0 -o smtpd_client_connection_rate_limit=0 - -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks + -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters pre-cleanup unix n - n - 0 cleanup -o virtual_alias_maps= diff --git a/redis/README.md b/redis/README.md index 9b43635b..850af13a 100644 --- a/redis/README.md +++ b/redis/README.md @@ -1,4 +1,4 @@ -# munin +# Redis Installation and basic configuration of Redis. diff --git a/redis/tasks/default-log2mail.yml b/redis/tasks/default-log2mail.yml index 21628b0c..3c50cab7 100644 --- a/redis/tasks/default-log2mail.yml +++ b/redis/tasks/default-log2mail.yml @@ -17,3 +17,14 @@ tags: - redis - log2mail + +- name: log2mail user is in redis group + user: + name: log2mail + groups: redis + append: yes + state: present + notify: restart log2mail + tags: + - redis + - log2mail \ No newline at end of file diff --git a/redis/tasks/default-munin.yml b/redis/tasks/default-munin.yml index 3f0fe6f4..7856741e 100644 --- a/redis/tasks/default-munin.yml +++ b/redis/tasks/default-munin.yml @@ -49,6 +49,7 @@ - used_keys - used_memory notify: restart munin-node + when: not ansible_check_mode tags: - redis diff --git a/redis/tasks/instance-munin.yml b/redis/tasks/instance-munin.yml index 80c67c6f..bc8d8e9a 100644 --- a/redis/tasks/instance-munin.yml +++ b/redis/tasks/instance-munin.yml @@ -49,6 +49,7 @@ - used_keys - used_memory notify: restart munin-node + when: not ansible_check_mode tags: - redis diff --git a/redis/tasks/main.yml b/redis/tasks/main.yml index 90f0aa12..10598aa6 100644 --- a/redis/tasks/main.yml +++ b/redis/tasks/main.yml @@ -22,6 +22,14 @@ - packages when: redis_sentinel_install | bool +- name: Linux kernel overcommit memory setting is enabled + sysctl: + name: "vm.overcommit_memory" + value: "1" + sysctl_file: "/etc/sysctl.d/evolinux-redis.conf" + state: present + reload: yes + - name: Get Redis version shell: "redis-server -v | grep -Eo '(v=\\S+)' | cut -d'=' -f 2 | grep -E '^([0-9]|\\.)+$'" changed_when: false diff --git a/redis/tasks/nrpe.yml b/redis/tasks/nrpe.yml index b317c4e6..9e042479 100644 --- a/redis/tasks/nrpe.yml +++ b/redis/tasks/nrpe.yml @@ -79,6 +79,10 @@ - redis - nrpe +- name: "Remount /usr with RW for 'install check_redis instance'" + include_role: + name: evolix/remount-usr + - name: install check_redis_instances copy: src: check_redis_instances.sh diff --git a/squid/defaults/main.yml b/squid/defaults/main.yml index 2188d606..8cbf43d4 100644 --- a/squid/defaults/main.yml +++ b/squid/defaults/main.yml @@ -7,4 +7,3 @@ squid_whitelist_items: [] squid_localproxy_enable: False -minifirewall_main_file: /etc/default/minifirewall diff --git a/squid/tasks/minifirewall.legacy.yml b/squid/tasks/minifirewall.legacy.yml new file mode 100644 index 00000000..f7e78ee5 --- /dev/null +++ b/squid/tasks/minifirewall.legacy.yml @@ -0,0 +1,43 @@ +--- +- name: Check if Minifirewall is present + stat: + path: "/etc/default/minifirewall" + check_mode: no + register: minifirewall_test + +- block: + - name: HTTPSITES list is commented in minifirewall + replace: + dest: "/etc/default/minifirewall" + regexp: "^(HTTPSITES='[^0-9])" + replace: '#\1' + notify: restart minifirewall + + - name: all HTTPSITES are authorized in minifirewall + lineinfile: + dest: "/etc/default/minifirewall" + line: "HTTPSITES='0.0.0.0/0'" + regexp: "HTTPSITES='.*'" + insertafter: "^#HTTPSITES=" + notify: restart minifirewall + + - name: add iptables rules for the proxy + lineinfile: + dest: "/etc/default/minifirewall" + regexp: "^#? *{{ item }}" + line: "{{ item }}" + insertafter: "^# Proxy" + loop: + - "/sbin/iptables -t nat -A OUTPUT -p tcp --dport 80 -m owner --uid-owner proxy -j ACCEPT" + - "/sbin/iptables -t nat -A OUTPUT -p tcp --dport 80 -d {{ squid_address }} -j ACCEPT" + - "/sbin/iptables -t nat -A OUTPUT -p tcp --dport 80 -d 127.0.0.0/8 -j ACCEPT" + - "/sbin/iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port 8888" + notify: restart minifirewall + + - name: remove minifirewall example rule for the proxy + lineinfile: + dest: "/etc/default/minifirewall" + regexp: '^#.*(-t nat).*(-d X\.X\.X\.X)' + state: absent + notify: restart minifirewall + when: minifirewall_test.stat.exists diff --git a/squid/tasks/minifirewall.yml b/squid/tasks/minifirewall.yml index e878b0a8..5abdf9df 100644 --- a/squid/tasks/minifirewall.yml +++ b/squid/tasks/minifirewall.yml @@ -1,29 +1,37 @@ --- - name: Check if Minifirewall is present stat: - path: "{{ minifirewall_main_file }}" + path: "/etc/default/minifirewall" check_mode: no register: minifirewall_test - block: - name: HTTPSITES list is commented in minifirewall replace: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" regexp: "^(HTTPSITES='[^0-9])" replace: '#\1' notify: restart minifirewall - name: all HTTPSITES are authorized in minifirewall lineinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" line: "HTTPSITES='0.0.0.0/0'" regexp: "HTTPSITES='.*'" insertafter: "^#HTTPSITES=" notify: restart minifirewall - - name: add iptables rules for the proxy + # The PROXY variable means that minifirewall is "modern" + - name: Look for PROXY variable + shell: "grep -E '^\\s*PROXY=' /etc/default/minifirewall" + failed_when: False + changed_when: False + check_mode: False + register: _minifirewall_proxy_var_check + + - name: Set proxy configuration for minifirewall (legacy mode) lineinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" regexp: "^#? *{{ item }}" line: "{{ item }}" insertafter: "^# Proxy" @@ -33,11 +41,21 @@ - "/sbin/iptables -t nat -A OUTPUT -p tcp --dport 80 -d 127.0.0.0/8 -j ACCEPT" - "/sbin/iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port 8888" notify: restart minifirewall + when: _minifirewall_proxy_var_check.rc == 1 - - name: remove minifirewall example rule for the proxy + - name: remove minifirewall example rule for the proxy (legacy mode) lineinfile: - dest: "{{ minifirewall_main_file }}" + dest: "/etc/default/minifirewall" regexp: '^#.*(-t nat).*(-d X\.X\.X\.X)' state: absent notify: restart minifirewall + when: _minifirewall_proxy_var_check.rc == 1 + + - name: Set proxy configuration for minifirewall (modern mode) + replace: + dest: "/etc/default/minifirewall" + replace: "PROXY='on'" + regexp: "PROXY='.*'" + notify: restart minifirewall + when: _minifirewall_proxy_var_check.rc == 0 when: minifirewall_test.stat.exists diff --git a/tomcat/tasks/packages.yml b/tomcat/tasks/packages.yml index 9b7995cc..f1b968cc 100644 --- a/tomcat/tasks/packages.yml +++ b/tomcat/tasks/packages.yml @@ -21,9 +21,9 @@ - ansible_distribution_release == "buster" - tomcat_version is not defined -- name: Set Tomcat version to 10 on Debian 11 if missing +- name: Set Tomcat version to 9 on Debian 11 if missing set_fact: - tomcat_version: 10 + tomcat_version: 9 when: - ansible_distribution_release == "bullseye" - tomcat_version is not defined diff --git a/vrrpd/tasks/main.yml b/vrrpd/tasks/main.yml index 74dfa5c2..5804cb39 100644 --- a/vrrpd/tasks/main.yml +++ b/vrrpd/tasks/main.yml @@ -3,6 +3,8 @@ include_role: name: evolix/apt tasks_from: evolix_public.yml + tags: + - vrrpd - name: Install vrrpd packages apt: @@ -10,12 +12,13 @@ allow_unauthenticated: yes state: present tags: - - vrrpd + - vrrpd - name: Adjust sysctl config sysctl: name: "{{ item.name }}" value: "{{ item.value }}" + sysctl_file: /etc/sysctl.d/vrrpd.conf sysctl_set: yes state: present loop: @@ -26,4 +29,4 @@ - { name: 'net.ipv4.conf.all.arp_announce', value: 2 } - { name: 'net.ipv4.ip_nonlocal_bind', value: 1 } tags: - - vrrpd + - vrrpd