diff --git a/.Jenkinsfile b/.Jenkinsfile new file mode 100644 index 00000000..d10526b2 --- /dev/null +++ b/.Jenkinsfile @@ -0,0 +1,64 @@ +pipeline { + agent { label 'docker' } + + environment { + ROLES_VERSION = "${env.GIT_COMMIT}" + } + + stages { + stage('Anible Lint') { + agent { + docker { + image 'evolix/ansible-lint:latest' + } + } + steps { + script { + sh 'for role_dir in ./*/; do HOME=$WORKSPACE_TMP ansible-lint -p $role_dir || : ; done' + recordIssues(tools: [ansibleLint()]) + } + } + } + + stage('Build tagged docker image') { + when { + buildingTag() + } + steps { + script { + def im = docker.build("evolix/ansible-roles:build${env.BUILD_ID}") + im.inside { + sh 'echo Test needed' + } + def version = TAG_NAME + def versions = version.split('\\.') + def major = versions[0] + def minor = versions[0] + '.' + versions[1] + def patch = version.trim() + docker.withRegistry('', 'hub.docker') { + im.push(major) + im.push(minor) + im.push(patch) + } + } + } + } + + stage('Build latest docker image') { + when { + branch 'unstable' + } + steps { + script { + def im = docker.build("evolix/ansible-roles:build${env.BUILD_ID}") + im.inside { + sh 'echo Test needed' + } + docker.withRegistry('', 'hub.docker') { + im.push('latest') + } + } + } + } + } +} diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 514a8b3f..00000000 --- a/.drone.yml +++ /dev/null @@ -1,36 +0,0 @@ -kind: pipeline -name: default - -steps: -- name: build tagged docker image - image: plugins/docker - settings: - username: - from_secret: docker_username - password: - from_secret: docker_password - dockerfile: Dockerfile - repo: evolix/ansible-roles - auto_tag: true - environment: - ROLES_VERSION: $DRONE_COMMIT_SHA - when: - event: - - tag - -- name: build latest docker image - image: plugins/docker - settings: - username: - from_secret: docker_username - password: - from_secret: docker_password - dockerfile: Dockerfile - repo: evolix/ansible-roles - tags: latest - environment: - ROLES_VERSION: $DRONE_COMMIT_SHA - when: - branch: - - unstable - diff --git a/.gitignore b/.gitignore index 102ea9f6..080ae1f8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .kateproject.d .vagrant/ *.swp +.vscode \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..799fe466 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.associations": { + "*.yml": "ansible", + "*.yaml": "ansible" + }, + "yaml.format.enable": false, + "ansible.python.interpreterPath": "/bin/python" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 3423f57e..0ad148c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,28 +8,376 @@ The **major** part of the version is the year The **minor** part changes is the month The **patch** part changes is incremented if multiple releases happen the same month + ## [Unreleased] ### Added +* userlogrotate: rotate also php.log. +* nagios-nrpe: add a NRPE check-local command with completion. +* policy_pam: New role allowing to manage password policy with pam_pwquality & pam_pwhistory +* docker-host: added var for user namespace setting +* dovecot: fix old_stats plugin for Dovecot 2.3. +* dovecot: add Munin plugins dovecot1 and dovecot_stats (patched) +* fail2ban: add default variable fail2ban_dbpurgeage_default + ### Changed -* evocheck: upstream release 22.03.1 -* evolinux-base: backup-server-state release 22.03.3 -* evolinux-base: Add non-free repos & install non-free firmware on dedicated hardware -* generate-ldif: Add services check for bkctld -* minifirewall: upstream release 22.03.3 and use includes directory -* openvpn: use a subnet topology instead of the net30 default topology +* all: change syntax "force: [yes,no]" → "force: [true,false]" +* all: change syntax "become: [yes,no]" → "become: [true,false]" +* elasticsearch: improve networking configuration +* evolinux-users: remove Stretch references in tasks that also apply to next Debian versions +* minifirewall: upstream release 23.07 +* minifirewall: update nrpe script to check active configuration +* mysql: improve shell syntax for mysql_skip script +* pbbouncer: minor fixes +* varnish: Allow the systemd template to be overriden with a template outside of the role +* postfix (packmail or when postfix_slow_transport_include is True): change `minimal_backoff_time` from 2h to 15m (see HowtoPostfix) +* postfix (packmail) : optimize Amavis integration ### Fixed -* Repair keepalived role -* generate-ldif: Correct generated entries for php-fpm in containers +* elasticsearch: comment the Xlog:gc line instead of changing it completely +* nagios-nrpe: remount /usr **after** installing the packages +* packweb-apache,nagios-nrpe: add missing task and config for PHP 8.2 container +* potsfix: add missing `localhost.$mydomain` to mydestination ### Removed +* dovecot: remove Munin plugin dovecot (not working) + ### Security +## [23.04] 2023-04-23 + +### Added + +* graylog: new role +* lxc-php: add support for PHP 8.2 container + +### Changed + +* Use FQCN (Fully Qualified Collection Name) +* apt: with Debian 12, backports are installed but disabled by default +* openvpn: updated the README file +* pgbouncer: add handler to restart the service + +### Fixed + +* generate-ldif: Support for Debian 12 + +## [23.03.1] 2023-03-16 + +### Added + +* pgbouncer: new role + +### Changed + +* apt: deb822 migration python script is looked relative to shell script +* listupgrade: remove old typo version of the cron task +* minifirewall: support protocols in numeric form + +## [23.03] 2023-03-16 + +### Added + +* apache: add task to enable mailgraph on default vhost and index.html +* apt: add move-apt-keyrings script/tasks +* apt: add tools to migrate sources to deb822 format +* fail2ban: add "Internal login failure" to Dovecot filter +* lxc: copy `/etc/profile.d/evolinux.sh` from host into container +* nagios-nrpe: add tasks/files for a wrapper +* nagios-nrpe: Print pool config path in check_phpfpm_multi output +* php: add `php_version` variable when sury is activated for each Debian version +* php: add a way to choose which version to install using sury repository +* postfix: Add task to enable mailgraph on packmail +* postgresql: configure max_connections +* userlogrotate: create dedicated role, separated from packweb-apache +* varnish: add `varnish_update_config` variable to disable configuration update + +### Changed + +* Use systemd module instead of command +* Removed all `warn: False` args in command, shell and other modules as it's been deprecated and will give a hard fail in ansible-core 2.14.0. +* apt: Use pub.evolix.org instead of pub.evolix.net +* bind: refactor role +* elasticsearch: Disable garabge collector logging (JDK >= 9) +* evolinux-users: Update sudoers template to remove commands allowed without password +* listupgrade: upstream release 23.03.3 +* kvmstats: use virsh domstats | awk to get guests informations +* nagios-nrpe : Rewrite `check_vrrpd` for a better check (check `rp_filter`, `vrrpd` and `uvrrpd` compatible, use arguments, …) +* openvpn: Change `check_openvpn` destination file to comply with recent EvoBSD change +* postfix: come back to default value of `notify_classes` for pack mails. +* userlogrotate: set rotate date format in right order (YYYY-MM-DD)! +* webapps/nextcloud : Change default data directory to be outside web root +* webapps/nextcloud : Small enhancement on the vhost template to lock out data dir +* yarn: update apt key + +### Fixed + +* Proper jinja spacing +* clamav: set `MaxConnectionQueueLength` to its default value (200), custom (15) was way too small and caused recurring failures in Postfix. +* docker-host: fix type in `daemon.json` and remove host configuration that is already in the systemd service by default +* evolinux-base: ensure dbus is started and enabled (not by default in the case of an offline netinst) +* haproxy: fix missing admin ACL in stats module access permissions +* openvpn: fix the client cipher configuration to match the server cipher configuration +* php: fix error introduced in #33503e4538 (`False` evaluated as a String instead of Boolean) +* php: install using Sury repositories on Bullseye +* postfix (packmail only): disable `concurrency_failed_cohort_limit` for destination smtp-amavis to prevent the suspension of this destination when Amavis fails to answer. Indeed, we configure the suspension delay quite long in `minimal_backoff_time` (2h) and `maximal_backoff_time` (6h) to reduce the risk of ban from external SMTPs. +* postfix: avoid Amavis transport to be considered dead when restarted. +* postfix: remove unused `aliases_scope=sub` from virtual_aliases.cf (it generated warnings) +* userlogrotate: fix bug introduced in commit 2e54944a246 (rotated files were not zipped) +* userlogrotate: skip zipping if .gz log already exists (prevents interactive question) + +### Removed + +* evolinux-base: subversion is not installed anymore + + +## [22.12] 2022-12-14 + +### Added + +* all: add signed-by option for additional APT sources +* all: preliminary work to support Debian 12 +* all: use proper keyrings directory for APT version +* evolinux-base: replace regular kernel by cloud kernel on virtual servers +* lxc-php: set php-fpm umask to `007` +* nagios-nrpe: `check_ceph_*` +* nagios-nrpe: `check_haproxy_stats` supports DRAIN status +* packweb-apache: enable `log_forensic` module +* rabbitmq: add link in default page +* varnish: create special tmp directory for syntax validation +* postfix: add localhost.$mydomain to mydestination + +### Changed + +* certbot: auto-detect HAPEE version in renewal hook +* evocheck: install script according to Debian version +* evolinux-base: `utils.yml` can be excluded +* evolinux-todo: execute tasks only for Debian distribution (because this task is a dependency for others roles used on different distributions) +* evolinux-user: add sudoers privilege for check `php_fpm81` +* evomaintenance: allow missing API endpoint if APi is disabled +* java: use default JRE package when version is not specified +* keepalived: change exit code (_warning_ if running but not on expected state ; _critical_ if not running) +* listupgrade: better detection for PostgreSQL +* listupgrade: sort/uniq of packages/services lists in email template +* lxc-solr: detect the real partition options +* lxc-solr: download URL according to Solr Version +* lxc-solr: set homedir and port at install +* minifirewall: whitelist deb.freexian.com +* openvpn: shellpki upstream release 22.12.2 +* openvpn: specifies that the mail for expirations is for OpenVPN +* packweb-apache: manual dependencies resolution +* redis: some values should be quoted +* redis: variable to disable transparent hugepage (default: do nothing) +* squid: whitelist `deb.freexian.com` +* varnish: better package facts usage with check mode and tags +* varnish: systemd override depends on Varnish version instead of Debian version + +### Fixed + +* evolinux-user: Fix sudoers privilege for check `php_fpm80` +* nagios-nrpe: Fix check opendkim for recent change in listening port +* openvpn: Fix mode of shellpki script +* proftpd: Fix format of public key files controlled by Ansible +* proftpd: Fix mode of public key directory and files (they have to be accessible by `proftpd:nobody`) +* varnish: fix missing state, that blocked the task + +### Removed + +* openvpn: Deleted the task fixing the CRL rights since it has been fixed in upstream + + +## [22.09] 2022-09-19 + +### Added + +* evolinux_users: create only users who have a certain value for the `create` key (default: `always`). +* php: install php-xml with recent PHP versions +* vrrp: add an `ip.yml` task file to help create VRRP addresses +* webapps/nextcloud: Add compatibility with apache2, and apache2 mod_php. +* memcached: NRPE check for multi-instance setup +* munin: Add ipmi_ plugins on dedicated hardware +* proftpd: Add options to override configs (and add a warning if file was overriden) +* proftpd: Allow user auth with ssh keys + + +### Changed + +* evocheck: upstream release 22.09 +* evolinux-base: update-evobackup-canary upstream release 22.06 +* generate-ldif: Support any MariaDB version +* minifirewall: use handlers to restart minifirewall +* openvpn: automate the initialization of the CA and the creation of the server certificate ; use openssl_dhparam module instead of a command +* generate-ldif: support any version of MariaDB (instead of only 10.0, 10.1 and 10.3) +* openvpn: Run OpenVPN with the \_openvpn user and group instead of nobody which is originally for NFS +* nagios-nrpe: Upgrade check_mongo + +### Fixed + +* fail2ban: fix dovecot-evolix regex syntax +* haproxy: make it so that munin doesn't break if there is a non default `haproxy_stats_path` +* mysql: Add missing Munin conf for Debian 11 +* redis: config directory must be owned by the user that runs the service (to be able to write tmp config files in it) +* varnish: make `-j ` the first argument on jessie/stretch as it has to be the first argument there. +* webapps/nextcloud: Add missing dependencies for imagick + +### Removed + +* evocheck: remove failure if deprecated variable is used +* webapps/nextcloud: Drop support for Nginx + +## [22.07.1] 2022-07-28 + +### Changed + +* evocheck: upstream release 22.07 +* evomaintenance: upstream release 22.07 +* mongodb: replace version_compare() with version() +* nagios-nrpe: check_disk1 returns only alerts +* nagios-nrpe: use regexp to exclude paths/devices in check_disk1 + +## [22.07] 2022-07-08 + +### Added + +* fail2ban: Ensure apply dbpurgeage from stretch and buster + +## [22.07] 2022-07-06 + +### Added + +* evolinux-base: session timeout is configurable (default: 36000 seconds = 10 hours) +* haproxy: add haproxy_allow_ip_nonlocal_bind to set sysctl value (optional) +* kvm-host: fix depreciation of "drbd-overview" by "drbdadm status" in add-vm.sh +* openvpn: configure logrotate + +### Changed + +* openvpn: minimal rights on /etc/shellpki/ and crl.pem + +### Fixed + +* evolinux-base: Update PermitRootLogin task to work on Debian 11 +* evolinux-user: Update PermitRootLogin task to work on Debian 11 +* minifirewall: docker mode is configurable + +## [22.06.3] 2022-06-17 + +### Changed + +* evolinux-base: blacklist and do not install megaclisas-status package on incompatible servers + +## [22.06.2] 2022-06-10 + +### Added + +* postgresql: add variable to configure binding addresses (default: 127.0.0.1) + +### Changed + +* evocheck: upstream release 22.06.2 +* fail2ban: Give the possibility to override jail.local (with fail2ban_override_jaillocal) +* fail2ban: If jail.local was overriden, add a warning +* fail2ban: Allow to tune some jail settings (maxretry, bantime, findtime) with ansible +* fail2ban: Allow to tune the default action with ansible +* fail2ban: Change default action to ban only (instead of ban + mail with whois report) +* fail2ban: Configure recidive jail (off by default) + extend dbpurgeage +* redis: binding is possible on multiple interfaces (breaking change) + +### Fixed + +* Enforce String notation for mode +* postgresql: fix nested loop for Munin plugins +* postgresql: Fix task order when using pgdg repo +* postgresql: Install the right pg version + +## [22.06.1] 2022-06-06 + +### Changed + +* evocheck: upstream release 22.06.1 +* minifirewall: upstream release 22.06 +* mysql: evomariabackup release 22.06.1 +* mysql: reorganize evomariabackup to use mtree instead of our own dir-check + +## [22.06] 2022-06-03 + +### Added + +* certbot: add hapee (HAProxy Enterprise Edition) deploy hook +* evolinux-base: add dir-check script +* evolinux-base: add update-evobackup-canary script +* mysql: add post-backup-hook to evomariabackup +* mysql: use dir-check inside evomariabackup + +### Changed + +* docker: Allow "live-restore" to be toggled with docker_conf_live_restore +* evocheck: upstream release 22.06 +* evolinux-base: Replacement of variable `evolinux_packages_hardware` by `ansible_virtualization_role == "host"` automatize host type detection and avoids installing smartd & other on VM. +* minifirewall: tail template follows symlinks +* mysql: add "set crypt_use_gpgme=no" Mutt option, for mysqltuner + +### Fixed + +* Role `postfix`: Add missing `localhost.localdomain localhost` to `mydestination` variable which caused undelivered of some local mails. + +## [22.05.1] 2022-05-12 + +### Added + +* docker : Introduce new default settings + allow to change the docker data directory +* docker : Introduce new variables to tweak daemon settings + +### Changed + +* evocheck: upstream release 22.05 + +### Removed + +* docker : Removed Debian Jessie support + +## [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 @@ -52,8 +400,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 @@ -267,6 +613,7 @@ The **patch** part changes is incremented if multiple releases happen the same m ### Added +* bookworm-detect: transitional role to help dealing with unreleased bookworm version * dovecot: Update munin plugin & configure it * dovecot: vmail uid/gid are configurable * evoacme: variable to disable Debian version check (default: False) diff --git a/amavis/files/amavis_purge_virusmails b/amavis/files/amavis_purge_virusmails new file mode 100644 index 00000000..ba7ef51a --- /dev/null +++ b/amavis/files/amavis_purge_virusmails @@ -0,0 +1,2 @@ +#!/bin/bash +find /var/lib/amavis/virusmails/ -type f -mtime +30 -delete diff --git a/amavis/handlers/main.yml b/amavis/handlers/main.yml index 62049999..6d76108b 100644 --- a/amavis/handlers/main.yml +++ b/amavis/handlers/main.yml @@ -1,5 +1,5 @@ --- - name: restart amavis - service: + ansible.builtin.service: name: amavis state: restarted diff --git a/amavis/tasks/main.yml b/amavis/tasks/main.yml index 1b0932d5..da46721e 100644 --- a/amavis/tasks/main.yml +++ b/amavis/tasks/main.yml @@ -1,6 +1,6 @@ --- - name: install Amavis - apt: + ansible.builtin.apt: name: - postgrey - amavisd-new @@ -9,10 +9,19 @@ - amavis - name: configure Amavis - template: + ansible.builtin.template: src: amavis.conf.j2 dest: /etc/amavis/conf.d/49-evolinux-defaults mode: "0644" notify: restart amavis tags: - amavis + +- name: Install purge custom cron + ansible.builtin.copy: + src: amavis_purge_virusmails + dest: /etc/cron.daily/amavis_purge_virusmails + mode: "0755" + tags: + - amavis + - amavis_purge_cron diff --git a/amazon-ec2/amazon-ec2-evolinux.yml b/amazon-ec2/amazon-ec2-evolinux.yml index d4e125a7..23f1f358 100644 --- a/amazon-ec2/amazon-ec2-evolinux.yml +++ b/amazon-ec2/amazon-ec2-evolinux.yml @@ -9,16 +9,16 @@ aws_region: ca-central-1 tasks: - - include_role: + - ansible.builtin.include_role: name: evolix/amazon-ec2 tasks_from: setup.yml - - include_role: + - ansible.builtin.include_role: name: evolix/amazon-ec2 tasks_from: create-instance.yml - name: Install Evolinux hosts: launched-instances - become: yes + become: true vars_files: - 'vars/secrets.yml' @@ -51,7 +51,7 @@ - mysql post_tasks: - - include_role: + - ansible.builtin.include_role: name: evolix/etc-git tasks_from: commit.yml vars: diff --git a/amazon-ec2/tasks/create-instance.yml b/amazon-ec2/tasks/create-instance.yml index a3f84b1a..7dd4ef3f 100644 --- a/amazon-ec2/tasks/create-instance.yml +++ b/amazon-ec2/tasks/create-instance.yml @@ -1,36 +1,36 @@ --- - name: Launch new instance(s) - ec2: + amazon.aws.ec2: state: present - aws_access_key: "{{aws_access_key}}" - aws_secret_key: "{{aws_secret_key}}" - region: "{{aws_region}}" - image: "{{ec2_base_ami}}" - instance_type: "{{ec2_instance_type}}" - count: "{{ec2_instance_count}}" - assign_public_ip: "{{ec2_public_ip}}" - group: "{{ec2_security_group.name}}" - key_name: "{{ec2_keyname}}" + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + region: "{{ aws_region }}" + image: "{{ ec2_base_ami }}" + instance_type: "{{ ec2_instance_type }}" + count: "{{ ec2_instance_count }}" + assign_public_ip: "{{ ec2_public_ip }}" + group: "{{ ec2_security_group.name }}" + key_name: "{{ ec2_keyname }}" wait: yes register: ec2 - name: Add newly created instance(s) to inventory - add_host: - hostname: "{{item.public_dns_name}}" + ansible.builtin.add_host: + hostname: "{{ item.public_dns_name }}" groupname: launched-instances ansible_user: admin ansible_ssh_common_args: "-o StrictHostKeyChecking=no" - loop: "{{ec2.instances}}" + loop: "{{ ec2.instances }}" -- debug: - msg: "Your newly created instance is reachable at: {{item.public_dns_name}}" - loop: "{{ec2.instances}}" +- ansible.builtin.debug: + msg: "Your newly created instance is reachable at: {{ item.public_dns_name }}" + loop: "{{ ec2.instances }}" - name: Wait for SSH to come up on all instances (give up after 2m) - wait_for: + ansible.builtin.wait_for: state: started - host: "{{item.public_dns_name}}" + host: "{{ item.public_dns_name }}" port: 22 timeout: 120 - loop: "{{ec2.instances}}" + loop: "{{ ec2.instances }}" diff --git a/amazon-ec2/tasks/post-install.yml b/amazon-ec2/tasks/post-install.yml index 369f4941..80f624a8 100644 --- a/amazon-ec2/tasks/post-install.yml +++ b/amazon-ec2/tasks/post-install.yml @@ -1,5 +1,5 @@ --- - name: Remove admin user - user: + ansible.builtin.user: name: admin state: absent diff --git a/amazon-ec2/tasks/setup.yml b/amazon-ec2/tasks/setup.yml index fe136fa1..d3bc00a5 100644 --- a/amazon-ec2/tasks/setup.yml +++ b/amazon-ec2/tasks/setup.yml @@ -1,7 +1,7 @@ --- - name: Create default security group - ec2_group: + amazon.aws.ec2_group: name: "{{ ec2_security_group.name }}" state: present aws_access_key: "{{ aws_access_key }}" @@ -12,7 +12,7 @@ rules_egress: "{{ ec2_security_group.rules_egress }}" - name: Create key pair - ec2_key: + amazon.aws.ec2_key: name: "{{ ec2_keyname }}" state: present aws_access_key: "{{ aws_access_key }}" diff --git a/apache/handlers/main.yml b/apache/handlers/main.yml index 96daa368..e8e31627 100644 --- a/apache/handlers/main.yml +++ b/apache/handlers/main.yml @@ -1,15 +1,15 @@ --- - name: restart apache - service: + ansible.builtin.service: name: apache2 state: restarted - name: reload apache - service: + ansible.builtin.service: name: apache2 state: reloaded - name: restart munin-node - service: + ansible.builtin.service: name: munin-node state: restarted diff --git a/apache/tasks/auth.yml b/apache/tasks/auth.yml index fd01517c..596c63e9 100644 --- a/apache/tasks/auth.yml +++ b/apache/tasks/auth.yml @@ -1,21 +1,21 @@ --- - name: Init ipaddr_whitelist.conf file - copy: + ansible.builtin.copy: src: ipaddr_whitelist.conf dest: /etc/apache2/ipaddr_whitelist.conf owner: root group: root mode: "0640" - force: no + force: false tags: - apache - name: Load IP whitelist task - include: ip_whitelist.yml + ansible.builtin.import_tasks: ip_whitelist.yml - name: include private IP whitelist for server-status - lineinfile: + ansible.builtin.lineinfile: dest: /etc/apache2/mods-available/status.conf line: " include /etc/apache2/ipaddr_whitelist.conf" insertafter: 'SetHandler server-status' @@ -24,19 +24,19 @@ - apache - name: Copy private_htpasswd - copy: + ansible.builtin.copy: src: private_htpasswd dest: /etc/apache2/private_htpasswd owner: root group: root mode: "0640" - force: no + force: false notify: reload apache tags: - apache - name: add user:pwd to private htpasswd - lineinfile: + ansible.builtin.lineinfile: dest: /etc/apache2/private_htpasswd line: "{{ item }}" state: present @@ -46,7 +46,7 @@ - apache - name: remove user:pwd from private htpasswd - lineinfile: + ansible.builtin.lineinfile: dest: /etc/apache2/private_htpasswd line: "{{ item }}" state: absent diff --git a/apache/tasks/ip_whitelist.yml b/apache/tasks/ip_whitelist.yml index 18f4a681..5060f56e 100644 --- a/apache/tasks/ip_whitelist.yml +++ b/apache/tasks/ip_whitelist.yml @@ -1,7 +1,7 @@ --- - name: add IP addresses to private IP whitelist - lineinfile: + ansible.builtin.lineinfile: dest: /etc/apache2/ipaddr_whitelist.conf line: "Require ip {{ item }}" state: present @@ -12,7 +12,7 @@ - ips - name: remove IP addresses from private IP whitelist - lineinfile: + ansible.builtin.lineinfile: dest: /etc/apache2/ipaddr_whitelist.conf line: "Require ip {{ item }}" state: absent diff --git a/apache/tasks/log2mail.yml b/apache/tasks/log2mail.yml index 3b0650b7..f0f1853d 100644 --- a/apache/tasks/log2mail.yml +++ b/apache/tasks/log2mail.yml @@ -1,19 +1,19 @@ --- - name: log2mail is installed - apt: + ansible.builtin.apt: name: log2mail state: present tags: - apache - name: Add log2mail config for Apache segfaults - template: + ansible.builtin.template: src: log2mail-apache.j2 dest: "/etc/log2mail/config/apache" owner: log2mail group: adm mode: "0644" - force: no + force: false tags: - apache diff --git a/apache/tasks/main.yml b/apache/tasks/main.yml index 1a028205..78dabc61 100644 --- a/apache/tasks/main.yml +++ b/apache/tasks/main.yml @@ -1,7 +1,7 @@ --- - name: packages are installed (Debian 9 or later) - apt: + ansible.builtin.apt: name: - apache2 - libapache2-mod-evasive @@ -14,7 +14,7 @@ when: ansible_distribution_major_version is version('9', '>=') - name: itk package is installed if required (Debian 9 or later) - apt: + ansible.builtin.apt: name: - libapache2-mpm-itk state: present @@ -26,7 +26,7 @@ - apache_mpm == "itk" - name: packages are installed (jessie) - apt: + ansible.builtin.apt: name: - apache2-mpm-itk - libapache2-mod-evasive @@ -39,7 +39,7 @@ when: ansible_distribution_release == "jessie" - name: basic modules are enabled - apache2_module: + community.general.apache2_module: name: '{{ item }}' state: present loop: @@ -55,7 +55,7 @@ - apache - name: basic modules are enabled - apache2_module: + community.general.apache2_module: name: '{{ item }}' state: present loop: @@ -67,31 +67,31 @@ - name: Copy Apache defaults config file - copy: + ansible.builtin.copy: src: evolinux-defaults.conf dest: "/etc/apache2/conf-available/z-evolinux-defaults.conf" owner: root group: root mode: "0640" - force: yes + force: true notify: reload apache tags: - apache - name: Copy Apache custom config file - copy: + ansible.builtin.copy: src: evolinux-custom.conf dest: "/etc/apache2/conf-available/zzz-evolinux-custom.conf" owner: root group: root mode: "0640" - force: no + force: false notify: reload apache tags: - apache - name: disable status.conf - file: + ansible.builtin.file: dest: /etc/apache2/mods-enabled/status.conf state: absent notify: reload apache @@ -99,7 +99,8 @@ - apache - name: Ensure Apache config files are enabled - command: "a2enconf {{ item }}" + ansible.builtin.command: + cmd: "a2enconf {{ item }}" register: command_result changed_when: "'Enabling' in command_result.stderr" loop: @@ -109,37 +110,38 @@ tags: - apache -- include: auth.yml +- ansible.builtin.include: auth.yml tags: - apache - name: default vhost is installed - template: + ansible.builtin.template: src: evolinux-default.conf.j2 dest: /etc/apache2/sites-available/000-evolinux-default.conf mode: "0640" - force: no + force: false notify: reload apache tags: - apache - name: default vhost is enabled - file: + ansible.builtin.file: src: /etc/apache2/sites-available/000-evolinux-default.conf dest: /etc/apache2/sites-enabled/000-default.conf state: link - force: yes + force: true notify: reload apache when: apache_evolinux_default_enabled | bool tags: - apache -- include: server_status.yml +- ansible.builtin.include: server_status.yml tags: - apache - name: is umask already present? - command: "grep -E '^umask ' /etc/apache2/envvars" + ansible.builtin.command: + cmd: "grep -E '^umask ' /etc/apache2/envvars" failed_when: False changed_when: False register: envvar_grep_umask @@ -148,7 +150,7 @@ - apache - name: Add a mark in envvars for umask - blockinfile: + ansible.builtin.blockinfile: dest: /etc/apache2/envvars marker: "## {mark} ANSIBLE MANAGED BLOCK" block: | @@ -159,13 +161,13 @@ tags: - apache -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr tags: - apache - name: /usr/share/scripts exists - file: + ansible.builtin.file: dest: /usr/share/scripts mode: "0700" owner: root @@ -175,16 +177,16 @@ - apache - name: "Install save_apache_status.sh" - copy: + ansible.builtin.copy: src: save_apache_status.sh dest: /usr/share/scripts/save_apache_status.sh mode: "0755" - force: no + force: false tags: - apache - name: "logrotate: {{ apache_logrotate_frequency }}" - replace: + ansible.builtin.replace: dest: /etc/logrotate.d/apache2 regexp: "(daily|weekly|monthly)" replace: "{{ apache_logrotate_frequency }}" @@ -192,19 +194,19 @@ - apache - name: "logrotate: rotate {{ apache_logrotate_rotate }}" - replace: + ansible.builtin.replace: dest: /etc/logrotate.d/apache2 regexp: '^(\s+rotate) \d+$' replace: '\1 {{ apache_logrotate_rotate }}' tags: - apache -- include: log2mail.yml +- ansible.builtin.include: log2mail.yml when: apache_log2mail_include tags: - apache -- include: munin.yml +- ansible.builtin.include: munin.yml when: apache_munin_include | bool tags: - apache diff --git a/apache/tasks/munin.yml b/apache/tasks/munin.yml index fe07a5cf..af3c1a21 100644 --- a/apache/tasks/munin.yml +++ b/apache/tasks/munin.yml @@ -1,7 +1,7 @@ --- - name: "Install munin-node and core plugins packages" - apt: + ansible.builtin.apt: name: - munin-node - munin-plugins-core @@ -11,7 +11,7 @@ - munin - name: "Enable Munin plugins" - file: + ansible.builtin.file: src: "/usr/share/munin/plugins/{{ item }}" dest: "/etc/munin/plugins/{{ item }}" state: link @@ -25,7 +25,7 @@ - munin - name: "Install fcgi packages for Munin graphs" - apt: + ansible.builtin.apt: name: - libapache2-mod-fcgid - libcgi-fast-perl @@ -36,7 +36,8 @@ - munin - name: "Enable libapache2-mod-fcgid" - command: a2enmod fcgid + ansible.builtin.command: + cmd: a2enmod fcgid register: cmd_enable_fcgid changed_when: "'Module fcgid already enabled' not in cmd_enable_fcgid.stdout" notify: restart apache @@ -45,7 +46,7 @@ - munin - name: "Apache has access to /var/log/munin/" - file: + ansible.builtin.file: path: /var/log/munin/ group: www-data tags: diff --git a/apache/tasks/server_status.yml b/apache/tasks/server_status.yml index efd2b00e..271a8739 100644 --- a/apache/tasks/server_status.yml +++ b/apache/tasks/server_status.yml @@ -1,7 +1,7 @@ --- - name: server status dirname exists - file: + ansible.builtin.file: dest: "{{ apache_serverstatus_suffix_file | dirname }}" mode: "0700" owner: root @@ -9,62 +9,71 @@ state: directory - name: set apache serverstatus suffix if provided - copy: + ansible.builtin.copy: dest: "{{ apache_serverstatus_suffix_file }}" # The last character "\u000A" is a line feed (LF), it's better to keep it content: "{{ apache_serverstatus_suffix }}\u000A" - force: yes + force: true when: apache_serverstatus_suffix | length > 0 - name: generate random string for server-status suffix - shell: "apg -a 1 -M N -n 1 > {{ apache_serverstatus_suffix_file }}" + ansible.builtin.shell: + cmd: "apg -a 1 -M N -n 1 > {{ apache_serverstatus_suffix_file }}" args: creates: "{{ apache_serverstatus_suffix_file }}" - name: read apache server status suffix - command: "tail -n 1 {{ apache_serverstatus_suffix_file }}" + ansible.builtin.command: + cmd: "tail -n 1 {{ apache_serverstatus_suffix_file }}" changed_when: False check_mode: no register: new_apache_serverstatus_suffix - name: overwrite apache_serverstatus_suffix - set_fact: + ansible.builtin.set_fact: apache_serverstatus_suffix: "{{ new_apache_serverstatus_suffix.stdout }}" -- debug: +- ansible.builtin.debug: var: apache_serverstatus_suffix verbosity: 1 - name: replace server-status suffix in default site index - replace: + ansible.builtin.replace: dest: /var/www/index.html regexp: '__SERVERSTATUS_SUFFIX__' replace: "{{ apache_serverstatus_suffix }}" - name: add server-status suffix in default site index if missing - replace: + ansible.builtin.replace: dest: /var/www/index.html regexp: '"/server-status-?"' replace: '"/server-status-{{ apache_serverstatus_suffix }}"' - name: add server-status suffix in default VHost - replace: + ansible.builtin.replace: dest: /etc/apache2/sites-available/000-evolinux-default.conf regexp: '' replace: '' notify: reload apache - name: Munin configuration has a section for apache - lineinfile: + ansible.builtin.lineinfile: dest: /etc/munin/plugin-conf.d/munin-node line: "[apache_*]" create: no - name: apache-status URL is configured for Munin - lineinfile: + ansible.builtin.lineinfile: dest: /etc/munin/plugin-conf.d/munin-node line: "env.url http://{{ apache_serverstatus_host }}/server-status-{{ apache_serverstatus_suffix }}?auto" regexp: 'env.url http://[^\\/]+/server-status' insertafter: "[apache_*]" create: no notify: restart munin-node + +- name: add mailgraph URL in index.html + ansible.builtin.lineinfile: + dest: /var/www/index.html + state: present + line: '
  • Stats Mail
  • ' + insertbefore: "" diff --git a/apache/templates/evolinux-default.conf.j2 b/apache/templates/evolinux-default.conf.j2 index 68cdcf84..effa55c6 100644 --- a/apache/templates/evolinux-default.conf.j2 +++ b/apache/templates/evolinux-default.conf.j2 @@ -35,6 +35,15 @@ Include /etc/apache2/ipaddr_whitelist.conf + # Mailgraph configuration + Alias /mailgraph /usr/share/mailgraph + + DirectoryIndex mailgraph.cgi + Require all granted + Options +FollowSymLinks +ExecCGI + AddHandler cgi-script .cgi + + CustomLog /var/log/apache2/access.log vhost_combined ErrorLog /var/log/apache2/error.log LogLevel warn @@ -118,6 +127,15 @@ Include /etc/apache2/ipaddr_whitelist.conf
    + # Mailgraph configuration + Alias /mailgraph /usr/share/mailgraph + + DirectoryIndex mailgraph.cgi + Require all granted + Options +FollowSymLinks +ExecCGI + AddHandler cgi-script .cgi + + # BEGIN phpMyAdmin section # END phpMyAdmin section diff --git a/apt/defaults/main.yml b/apt/defaults/main.yml index e5093c6e..3720d893 100644 --- a/apt/defaults/main.yml +++ b/apt/defaults/main.yml @@ -8,6 +8,8 @@ apt_upgrade: False apt_install_basics: True apt_basics_components: "main" +# With Debian 12+ and the deb822 format of source files +# backports are always installed but enabled according to `apt_install_backports` apt_install_backports: False apt_backports_components: "main" @@ -25,3 +27,5 @@ apt_check_hold_cron_hour: "*/4" apt_check_hold_cron_weekday: "*" apt_check_hold_cron_day: "*" apt_check_hold_cron_month: "*" + +apt_keyring_dir: "{{ ansible_distribution_major_version is version('12', '<') | ternary('/etc/apt/trusted.gpg.d', '/etc/apt/keyrings') }}" \ No newline at end of file diff --git a/apt/files/bookworm_backports_preferences b/apt/files/bookworm_backports_preferences new file mode 100644 index 00000000..eaf76d52 --- /dev/null +++ b/apt/files/bookworm_backports_preferences @@ -0,0 +1,3 @@ +Package: * +Pin: release a=bookworm-backports +Pin-Priority: 50 diff --git a/apt/files/deb822-migration.py b/apt/files/deb822-migration.py new file mode 100755 index 00000000..a8873923 --- /dev/null +++ b/apt/files/deb822-migration.py @@ -0,0 +1,152 @@ +#!/bin/env python3 + +import re +import sys +import os +import select +import apt +import apt_pkg + +# Order matters ! +destinations = { + "debian-security": "security.sources", + ".*-backports": "backports.sources", + ".debian.org": "system.sources", + "mirror.evolix.org": "system.sources", + "pub.evolix.net": "evolix_public_old.sources", + "pub.evolix.org": "evolix_public.sources", + "artifacts.elastic.co": "elastic.sources", + "download.docker.com": "docker.sources", + "downloads.linux.hpe.com": "hp.sources", + "pkg.jenkins-ci.org": "jenkins.sources", + "packages.sury.org": "sury.sources", + "repo.mongodb.org": "mongodb.sources", + "apt.newrelic.com": "newrelic.sources", + "deb.nodesource.com": "nodesource.sources", + "dl.yarnpkg.com": "yarn.sources", + "apt.postgresql.org": "postgresql.sources", + "packages.microsoft.com/repos/vscode": "microsoft-vscode.sources", + "packages.microsoft.com/repos/ms-teams": "microsoft-teams.sources", + "updates.signal.org": "signal.sources", + "downloads.1password.com/linux/debian": "1password.sources", + "download.virtualbox.org": "virtualbox.sources" +} + +sources_parts = apt_pkg.config.find_dir('Dir::Etc::sourceparts') + +def split_options(raw): + table = str.maketrans({ + "[": None, + "]": None + }) + options = raw.translate(table).split(' ') + + return options + +def auto_destination(uri): + basename = uri + basename = re.sub('\[[^\]]+\]', '', basename) + basename = re.sub('\w+://', '', basename) + basename = '_'.join(re.sub('[^a-zA-Z0-9]', ' ', basename).split()) + return '%s.sources' % basename + + +def destination(matches): + for search_str in destinations.keys(): + search_pattern = re.compile(f'{search_str}(/|\s|$)') + if re.search(search_pattern, matches['uri']) or re.search(search_pattern, matches["suite"]): + return destinations[search_str] + # fallback if nothing matches + return auto_destination(matches['uri']) + +def prepare_sources(lines): + sources = {} + pattern = re.compile('^(?: *(?Pdeb|deb-src)) +(?P\[.+\] ?)*(?P\w+:\/\/\S+) +(?P\S+)(?: +(?P.*))?$') + + for line in lines: + matches = re.match(pattern, line) + + if matches is not None: + dest = destination(matches) + options = {} + + if matches.group('options'): + for option in split_options(matches['options']): + if "=" in option: + key, value = option.split("=") + options[key] = value + + if dest in sources: + sources[dest]["Types"].add(matches["type"]) + sources[dest]["URIs"] = matches["uri"] + sources[dest]["Suites"].add(matches["suite"]) + sources[dest]["Components"].update(matches["components"].split(' ')) + else: + source = { + "Types": {matches['type']}, + "URIs": matches['uri'], + "Enabled": "yes", + } + + if matches.group('suite'): + source["Suites"] = set(matches['suite'].split(' ')) + + if matches.group('components'): + source["Components"] = set(matches['components'].split(' ')) + + if "arch" in options: + if "Architectures" in source: + source["Architectures"].append(options["arch"]) + else: + source["Architectures"] = {options["arch"]} + + if "signed-by" in options: + if "Signed-by" in source: + source["Signed-by"].append(options["signed-by"]) + else: + source["Signed-by"] = {options["signed-by"]} + + if "lang" in options: + if "Languages" in source: + source["Languages"].append(options["lang"]) + else: + source["Languages"] = {options["lang"]} + + if "target" in options: + if "Targets" in source: + source["Targets"].append(options["target"]) + else: + source["Targets"] = {options["target"]} + + sources[dest] = source + return sources + +def save_sources(sources, output_dir): + # print(output_dir) + # print(sources) + for dest, source in sources.items(): + source_path = output_dir + dest + + with open(source_path, 'w') as file: + for key, value in source.items(): + if isinstance(value, str): + file.write("{}: {}\n".format(key, value)) + else: + file.write("{}: {}\n".format(key, ' '.join(value))) + +def main(): + if select.select([sys.stdin, ], [], [], 0.0)[0]: + sources = prepare_sources(sys.stdin) + # elif len(sys.argv) > 1: + # sources = prepare_sources([sys.argv[1]]) + else: + print("You must provide source lines to stdin", file=sys.stderr) + sys.exit(1) + + output_dir = apt_pkg.config.find_dir('Dir::Etc::sourceparts') + save_sources(sources, output_dir) + +if __name__ == "__main__": + main() + +sys.exit(0) \ No newline at end of file diff --git a/apt/files/deb822-migration.sh b/apt/files/deb822-migration.sh new file mode 100755 index 00000000..10fb7889 --- /dev/null +++ b/apt/files/deb822-migration.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +deb822_migrate_script=$(command -v deb822-migration.py) + +if [ -z "${deb822_migrate_script}" ]; then + deb822_migrate_script="$(dirname "$0")/deb822-migration.py" +fi +if [ ! -x "${deb822_migrate_script}" ]; then + >&2 echo "ERROR: '${deb822_migrate_script}' not found or not executable" + exit 1 +fi + +sources_from_file() { + grep --extended-regexp "^\s*(deb|deb-src) " $1 +} + +rc=0 +count=0 + +if [ -f /etc/apt/sources.list ]; then + sources_from_file /etc/apt/sources.list | ${deb822_migrate_script} + python_rc=$? + + if [ ${python_rc} -eq 0 ]; then + mv /etc/apt/sources.list /etc/apt/sources.list.bak + echo "OK: /etc/apt/sources.list" + count=$(( count + 1 )) + else + >&2 echo "ERROR: failed migration for /etc/apt/sources.list" + rc=1 + fi +fi + +for file in $(find /etc/apt/sources.list.d -mindepth 1 -maxdepth 1 -type f -name '*.list'); do + sources_from_file "${file}" | ${deb822_migrate_script} + python_rc=$? + + if [ ${python_rc} -eq 0 ]; then + mv "${file}" "${file}.bak" + echo "OK: ${file}" + count=$(( count + 1 )) + else + >&2 echo "ERROR: failed migration for ${file}" + rc=1 + fi +done + +echo "${count} file(s) migrated" +exit ${rc} \ No newline at end of file diff --git a/apt/files/move-apt-keyrings.sh b/apt/files/move-apt-keyrings.sh new file mode 100644 index 00000000..2d266412 --- /dev/null +++ b/apt/files/move-apt-keyrings.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +# Move apt repository key from /etc/apt/trusted.gpg.d/ to /etc/apt/keyrings/ and add "signed-by" tag in source list +# +# Example: move-apt-keyrings.sh http://repo.mongodb.org/apt/debian mongodb-server-[0-9\\.]+.asc + +repository_pattern=$1 +key=$2 + +found_files=$(grep --files-with-matches --recursive --extended-regexp "${repository_pattern}" "/etc/apt/sources.list.d/*.list") + +old_key_file="/etc/apt/trusted.gpg.d/${key}" +new_key_file="/etc/apt/keyrings/${key}" + +for file in ${found_files}; do + if ! grep --quiet "signed-by" "${file}"; then + signed_by="signed-by=${new_key_file}" + if grep --quiet "deb(-src)? \[" "${file}"; then + sed -i "s@deb\(-src\)\? \[\([^]]\+\)\]@deb\1 [\2 ${signed_by}]@" "${file}" + else + sed -i "s@deb\(-src\)\? @deb\1 [${signed_by}] @" "${file}" + fi + fi +done + +if [ -f "${old_key_file}" ] && [ ! -f "${new_key_file}" ]; then + mv "${old_key_file}" "${new_key_file}" +fi +if [ -f "${new_key_file}" ]; then + chmod 644 "${new_key_file}" + chown root: "${new_key_file}" +fi diff --git a/apt/files/pub_evolix.asc b/apt/files/pub_evolix.asc new file mode 100644 index 00000000..4a21bdfe --- /dev/null +++ b/apt/files/pub_evolix.asc @@ -0,0 +1,87 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +oA9QflpnDubMnCve +=ZCml +-----END PGP PUBLIC KEY BLOCK----- diff --git a/apt/files/pub_evolix.gpg b/apt/files/pub_evolix.gpg new file mode 100644 index 00000000..ab6ab33f Binary files /dev/null and b/apt/files/pub_evolix.gpg differ diff --git a/apt/tasks/backports.deb822.yml b/apt/tasks/backports.deb822.yml new file mode 100644 index 00000000..0382892d --- /dev/null +++ b/apt/tasks/backports.deb822.yml @@ -0,0 +1,28 @@ +--- + +- name: Backports deb822 sources list is installed + ansible.builtin.template: + src: '{{ ansible_distribution_release }}_backports.sources.j2' + dest: /etc/apt/sources.list.d/backports.sources + force: true + mode: "0640" + register: apt_backports_sources + tags: + - apt + +- name: Backports configuration + ansible.builtin.copy: + src: '{{ ansible_distribution_release }}_backports_preferences' + dest: /etc/apt/preferences.d/0-backports-defaults + force: true + mode: "0640" + register: apt_backports_config + tags: + - apt + +- name: Apt update + ansible.builtin.apt: + update_cache: yes + when: apt_backports_sources is changed or apt_backports_config is changed + tags: + - apt diff --git a/apt/tasks/backports.oneline.yml b/apt/tasks/backports.oneline.yml new file mode 100644 index 00000000..11de5c52 --- /dev/null +++ b/apt/tasks/backports.oneline.yml @@ -0,0 +1,45 @@ +--- +- name: No backports config in default sources.list + ansible.builtin.lineinfile: + dest: /etc/apt/sources.list + regexp: "backports" + state: absent + tags: + - apt + +- name: Backports sources list is installed + ansible.builtin.template: + src: '{{ ansible_distribution_release }}_backports.list.j2' + dest: /etc/apt/sources.list.d/backports.list + force: true + mode: "0640" + register: apt_backports_list + tags: + - apt + +- name: Backports configuration + ansible.builtin.copy: + src: '{{ ansible_distribution_release }}_backports_preferences' + dest: /etc/apt/preferences.d/0-backports-defaults + force: true + mode: "0640" + register: apt_backports_config + tags: + - apt + +- name: Archived backport are accepted (jessie) + ansible.builtin.lineinfile: + dest: '/etc/apt/apt.conf.d/99no-check-valid-until' + line: 'Acquire::Check-Valid-Until no;' + create: yes + state: present + tags: + - apt + when: ansible_distribution_release == "jessie" + +- name: Apt update + ansible.builtin.apt: + update_cache: yes + tags: + - apt + when: apt_backports_list is changed or apt_backports_config is changed diff --git a/apt/tasks/backports.yml b/apt/tasks/backports.yml index aecf6194..6ebf65ab 100644 --- a/apt/tasks/backports.yml +++ b/apt/tasks/backports.yml @@ -1,45 +1,13 @@ --- -- name: No backports config in default sources.list - lineinfile: - dest: /etc/apt/sources.list - regexp: "backports" - state: absent - tags: - - apt -- name: Backports sources list is installed - template: - src: '{{ ansible_distribution_release }}_backports.list.j2' - dest: /etc/apt/sources.list.d/backports.list - force: yes - mode: "0640" - register: apt_backports_list - tags: - - apt +# Backward compatibility task file -- name: Backports configuration - copy: - src: '{{ ansible_distribution_release }}_backports_preferences' - dest: /etc/apt/preferences.d/0-backports-defaults - force: yes - mode: "0640" - register: apt_backports_config - tags: - - apt +- name: Install backports repositories (Debian <12) + ansible.builtin.import_tasks: backports.oneline.yml + when: + - ansible_distribution_major_version is version('12', '<') -- name: Archived backport are accepted (jessie) - lineinfile: - dest: '/etc/apt/apt.conf.d/99no-check-valid-until' - line: 'Acquire::Check-Valid-Until no;' - create: yes - state: present - when: ansible_distribution_release == "jessie" - tags: - - apt - -- name: Apt update - apt: - update_cache: yes - when: apt_backports_list is changed or apt_backports_config is changed - tags: - - apt +- name: Install backports repositories (Debian >=12) + ansible.builtin.import_tasks: backports.deb822.yml + when: + - ansible_distribution_major_version is version('12', '>=') \ No newline at end of file diff --git a/apt/tasks/basics.deb822.yml b/apt/tasks/basics.deb822.yml new file mode 100644 index 00000000..617e6c92 --- /dev/null +++ b/apt/tasks/basics.deb822.yml @@ -0,0 +1,45 @@ +--- + +- name: Change basics repositories + ansible.builtin.template: + src: "{{ ansible_distribution_release }}_basics.sources.j2" + dest: /etc/apt/sources.list.d/system.sources + mode: "0644" + force: true + register: apt_basic_sources + tags: + - apt + +- name: Change security repositories + ansible.builtin.template: + src: "{{ ansible_distribution_release }}_security.sources.j2" + dest: /etc/apt/sources.list.d/security.sources + mode: "0644" + force: true + register: apt_security_sources + tags: + - apt + +- name: Find one-line APT sources + ansible.builtin.find: + paths: /etc/apt + patterns: '*.list' + register: list_files + +- name: Disable one-line-formatted sources + ansible.builtin.command: + cmd: "mv --verbose {{ item.path }} {{ item.path }}.bak" + environment: + LC_ALL: C + loop: "{{ list_files.files }}" + register: rename_cmd + changed_when: "'renamed' in rename_cmd.stdout" + tags: + - apt + +- name: Apt update + ansible.builtin.apt: + update_cache: yes + tags: + - apt + when: apt_basic_list is changed or apt_security_sources is changed diff --git a/apt/tasks/basics.oneline.yml b/apt/tasks/basics.oneline.yml new file mode 100644 index 00000000..f949a0b2 --- /dev/null +++ b/apt/tasks/basics.oneline.yml @@ -0,0 +1,18 @@ +--- + +- name: Change basics repositories + ansible.builtin.template: + src: "{{ ansible_distribution_release }}_basics.list.j2" + dest: /etc/apt/sources.list + mode: "0644" + force: true + register: apt_basic_list + tags: + - apt + +- name: Apt update + ansible.builtin.apt: + update_cache: yes + tags: + - apt + when: apt_basic_list is changed diff --git a/apt/tasks/basics.yml b/apt/tasks/basics.yml index 33c79129..885f33f5 100644 --- a/apt/tasks/basics.yml +++ b/apt/tasks/basics.yml @@ -1,33 +1,13 @@ --- -- name: Change basics repositories - template: - src: "{{ ansible_distribution_release }}_basics.list.j2" - dest: /etc/apt/sources.list - mode: "0644" - force: yes - register: apt_basic_list - tags: - - apt +# Backward compatibility task file -- name: Clean GANDI sources.list.d/debian-security.list - file: - path: '{{ item }}' - state: absent - loop: - - /etc/apt/sources.list.d/debian-security.list - - /etc/apt/sources.list.d/debian-jessie.list - - /etc/apt/sources.list.d/debian-stretch.list - - /etc/apt/sources.list.d/debian-buster.list - - /etc/apt/sources.list.d/debian-bullseye.list - - /etc/apt/sources.list.d/debian-update.list - when: apt_clean_gandi_sourceslist | bool - tags: - - apt +- name: Install basics repositories (Debian <12) + ansible.builtin.import_tasks: basics.oneline.yml + when: + - ansible_distribution_major_version is version('12', '<') -- name: Apt update - apt: - update_cache: yes - when: apt_basic_list is changed - tags: - - apt +- name: Install basics repositories (Debian >=12) + ansible.builtin.import_tasks: basics.deb822.yml + when: + - ansible_distribution_major_version is version('12', '>=') \ No newline at end of file diff --git a/apt/tasks/config.yml b/apt/tasks/config.yml index 7befa375..b403ab03 100644 --- a/apt/tasks/config.yml +++ b/apt/tasks/config.yml @@ -1,7 +1,7 @@ --- - name: Evolinux config for APT - lineinfile: + ansible.builtin.lineinfile: dest: /etc/apt/apt.conf.d/z-evolinux.conf line: "{{ item.line }}" regexp: "{{ item.regexp }}" @@ -12,12 +12,12 @@ - { line: "APT::Install-Recommends \"false\";", regexp: 'APT::Install-Recommends' } - { line: "APT::Install-Suggests \"false\";", regexp: 'APT::Install-Suggests' } - { line: "APT::Periodic::Enable \"0\";", regexp: 'APT::Periodic::Enable' } - when: apt_evolinux_config | bool tags: - apt + when: apt_evolinux_config | bool - name: DPkg invoke hooks - lineinfile: + ansible.builtin.lineinfile: dest: /etc/apt/apt.conf.d/z-evolinux.conf line: "{{ item }}" create: yes @@ -28,14 +28,14 @@ - "DPkg::Pre-Invoke { \"df /usr | grep -q /usr && mount -oremount,rw /usr || true\"; };" - "DPkg::Post-Invoke { \"df /tmp | grep -q /tmp && mount -oremount /tmp || true\"; };" - "DPkg::Post-Invoke { \"df /usr | grep -q /usr && mount -oremount /usr || true\"; };" - when: apt_hooks | bool tags: - apt + when: apt_hooks | bool - name: Remove Aptitude - apt: + ansible.builtin.apt: name: aptitude state: absent - when: apt_remove_aptitude | bool tags: - apt + when: apt_remove_aptitude | bool diff --git a/apt/tasks/evolix_public.deb822.yml b/apt/tasks/evolix_public.deb822.yml new file mode 100644 index 00000000..84d193b3 --- /dev/null +++ b/apt/tasks/evolix_public.deb822.yml @@ -0,0 +1,45 @@ +--- + +- name: Look for legacy apt keyring + ansible.builtin.stat: + path: /etc/apt/trusted.gpg + register: _trusted_gpg_keyring + tags: + - apt + +- name: Evolix embedded GPG key is absent + ansible.builtin.apt_key: + id: "B8612B5D" + keyring: /etc/apt/trusted.gpg + state: absent + tags: + - apt + when: _trusted_gpg_keyring.stat.exists + +- name: Add Evolix GPG key + ansible.builtin.copy: + src: pub_evolix.asc + dest: "{{ apt_keyring_dir }}/pub_evolix.asc" + force: true + mode: "0644" + owner: root + group: root + tags: + - apt + +- name: Evolix public list is installed + ansible.builtin.template: + src: evolix_public.sources.j2 + dest: /etc/apt/sources.list.d/evolix_public.sources + force: true + mode: "0640" + register: apt_evolix_public + tags: + - apt + +- name: Apt update + ansible.builtin.apt: + update_cache: yes + tags: + - apt + when: apt_evolix_public is changed diff --git a/apt/tasks/evolix_public.oneline.yml b/apt/tasks/evolix_public.oneline.yml new file mode 100644 index 00000000..deff0b7d --- /dev/null +++ b/apt/tasks/evolix_public.oneline.yml @@ -0,0 +1,45 @@ +--- + +- name: Look for legacy apt keyring + ansible.builtin.stat: + path: /etc/apt/trusted.gpg + register: _trusted_gpg_keyring + tags: + - apt + +- name: Evolix embedded GPG key is absent + ansible.builtin.apt_key: + id: "B8612B5D" + keyring: /etc/apt/trusted.gpg + state: absent + tags: + - apt + when: _trusted_gpg_keyring.stat.exists + +- name: Add Evolix GPG key + ansible.builtin.copy: + src: pub_evolix.asc + dest: "{{ apt_keyring_dir }}/pub_evolix.asc" + force: true + mode: "0644" + owner: root + group: root + tags: + - apt + +- name: Evolix public list is installed + ansible.builtin.template: + src: evolix_public.list.j2 + dest: /etc/apt/sources.list.d/evolix_public.list + force: true + mode: "0640" + register: apt_evolix_public + tags: + - apt + +- name: Apt update + ansible.builtin.apt: + update_cache: yes + tags: + - apt + when: apt_evolix_public is changed diff --git a/apt/tasks/evolix_public.yml b/apt/tasks/evolix_public.yml index 8352e666..8795a6a5 100644 --- a/apt/tasks/evolix_public.yml +++ b/apt/tasks/evolix_public.yml @@ -1,45 +1,13 @@ --- -- name: Look for legacy apt keyring - stat: - path: /etc/apt/trusted.gpg - register: _trusted_gpg_keyring - tags: - - apt +# Backward compatibility task file -- name: Evolix embedded GPG key is absent - apt_key: - id: "B8612B5D" - keyring: /etc/apt/trusted.gpg - state: absent - when: _trusted_gpg_keyring.stat.exists - tags: - - apt +- name: Install Evolix Public repositories (Debian <12) + ansible.builtin.import_tasks: evolix_public.oneline.yml + when: + - ansible_distribution_major_version is version('12', '<') -- name: Add Evolix GPG key - copy: - src: reg.asc - dest: /etc/apt/trusted.gpg.d/reg.asc - force: yes - mode: "0644" - owner: root - group: root - tags: - - apt - -- name: Evolix public list is installed - template: - src: evolix_public.list.j2 - dest: /etc/apt/sources.list.d/evolix_public.list - force: yes - mode: "0640" - register: apt_evolix_public - tags: - - apt - -- name: Apt update - apt: - update_cache: yes - when: apt_evolix_public is changed - tags: - - apt +- name: Install Evolix Public repositories (Debian >=12) + ansible.builtin.import_tasks: evolix_public.deb822.yml + when: + - ansible_distribution_major_version is version('12', '>=') \ No newline at end of file diff --git a/apt/tasks/hold_packages.yml b/apt/tasks/hold_packages.yml index 691f3763..e92b7b44 100644 --- a/apt/tasks/hold_packages.yml +++ b/apt/tasks/hold_packages.yml @@ -1,8 +1,11 @@ --- +- ansible.builtin.include_role: + name: evolix/remount-usr + - name: "hold packages (apt)" - shell: "set -o pipefail && (dpkg -l {{ item }} 2>/dev/null | grep -q -E '^(i|h)i') && ((apt-mark showhold | grep --quiet {{ item }}) || apt-mark hold {{ item }})" - args: + ansible.builtin.shell: + cmd: "set -o pipefail && (dpkg -l {{ item }} 2>/dev/null | grep -q -E '^(i|h)i') && ((apt-mark showhold | grep --quiet {{ item }}) || apt-mark hold {{ item }})" executable: /bin/bash check_mode: no register: apt_mark @@ -15,7 +18,7 @@ - apt - name: "/etc/evolinux is present" - file: + ansible.builtin.file: dest: /etc/evolinux mode: "0700" state: directory @@ -23,7 +26,7 @@ - apt - name: "hold packages (config)" - lineinfile: + ansible.builtin.lineinfile: dest: /etc/evolinux/apt_hold_packages.cf line: "{{ item }}" create: True @@ -33,8 +36,8 @@ - apt - name: "unhold packages (apt)" - shell: "set -o pipefail && (dpkg -l {{ item }} 2>/dev/null | grep -q -E '^(i|h)i') && ((apt-mark showhold | grep --quiet {{ item }}) && apt-mark unhold {{ item }})" - args: + ansible.builtin.shell: + cmd: "set -o pipefail && (dpkg -l {{ item }} 2>/dev/null | grep -q -E '^(i|h)i') && ((apt-mark showhold | grep --quiet {{ item }}) && apt-mark unhold {{ item }})" executable: /bin/bash check_mode: no register: apt_mark @@ -45,7 +48,7 @@ - apt - name: "unhold packages (config)" - lineinfile: + ansible.builtin.lineinfile: dest: /etc/evolinux/apt_hold_packages.cf line: "{{ item }}" create: True @@ -55,7 +58,7 @@ - apt - name: /usr/share/scripts exists - file: + ansible.builtin.file: dest: /usr/share/scripts mode: "0700" owner: root @@ -65,25 +68,26 @@ - apt - name: Check scripts is installed - copy: + ansible.builtin.copy: src: check_held_packages.sh dest: /usr/share/scripts/check_held_packages.sh - force: yes + force: true mode: "0755" tags: - apt - name: Check if Cron is installed - shell: "dpkg --list 'cron' 2>/dev/null | grep -q -E '^(i|h)i'" + ansible.builtin.shell: + cmd: "dpkg --list 'cron' 2>/dev/null | grep -q -E '^(i|h)i'" register: is_cron - changed_when: false - failed_when: false + changed_when: False + failed_when: False check_mode: no tags: - apt - name: Check for held packages (script) - cron: + ansible.builtin.cron: cron_file: apt-hold-packages name: check_held_packages job: "/usr/share/scripts/check_held_packages.sh" @@ -94,6 +98,6 @@ day: "{{ apt_check_hold_cron_day }}" month: "{{ apt_check_hold_cron_month }}" state: "present" - when: is_cron.rc == 0 tags: - apt + when: is_cron.rc == 0 diff --git a/apt/tasks/main.yml b/apt/tasks/main.yml index 353dca36..295f42f1 100644 --- a/apt/tasks/main.yml +++ b/apt/tasks/main.yml @@ -1,52 +1,116 @@ --- - name: "Compatibility check" - fail: - msg: only compatible with Debian >= 8 - when: - - ansible_distribution != "Debian" or ansible_distribution_major_version is version('8', '<') + ansible.builtin.assert: + that: + - ansible_distribution == "Debian" + - ansible_distribution_major_version is version('8', '>=') + msg: Only compatible with Debian >= 8 + tags: + - apt + +- name: "apt-transport-https is installed for https repositories (before Buster)" + ansible.builtin.apt: + name: + - apt-transport-https + tags: + - apt + when: ansible_distribution_major_version is version('10', '<') + +- name: "certificates are installed for https repositories" + ansible.builtin.apt: + name: + - ca-certificates tags: - apt - name: Custom configuration - include: config.yml + ansible.builtin.import_tasks: config.yml when: apt_config | bool tags: - apt -- name: Install basics repositories - include: basics.yml - when: apt_install_basics | bool +- name: Install basics repositories (Debian <12) + ansible.builtin.import_tasks: basics.oneline.yml tags: - apt + when: + - apt_install_basics | bool + - ansible_distribution_major_version is version('12', '<') -- name: Install APT Backports repository - include: backports.yml - when: apt_install_backports | bool +- name: Install basics repositories (Debian >=12) + ansible.builtin.import_tasks: basics.deb822.yml tags: - apt + when: + - apt_install_basics | bool + - ansible_distribution_major_version is version('12', '>=') -- name: Install Evolix Public APT repository - include: evolix_public.yml - when: apt_install_evolix_public | bool +- name: Install backports repositories (Debian <12) + ansible.builtin.import_tasks: backports.oneline.yml tags: - apt + when: + - apt_install_backports | bool + - ansible_distribution_major_version is version('12', '<') + +# With Debian 12+ and the deb822 format of source files +# backports are always installed but enabled according to `apt_install_backports` +- name: Install backports repositories (Debian >=12) + ansible.builtin.import_tasks: backports.deb822.yml + tags: + - apt + when: + - ansible_distribution_major_version is version('12', '>=') + + +- name: Install Evolix Public repositories (Debian <12) + ansible.builtin.import_tasks: evolix_public.oneline.yml + tags: + - apt + when: + - apt_install_evolix_public | bool + - ansible_distribution_major_version is version('12', '<') + +- name: Install Evolix Public repositories (Debian >=12) + ansible.builtin.import_tasks: evolix_public.deb822.yml + tags: + - apt + when: + - apt_install_evolix_public | bool + - ansible_distribution_major_version is version('12', '>=') + +- name: Clean GANDI sources + ansible.builtin.file: + path: '{{ item }}' + state: absent + loop: + - /etc/apt/sources.list.d/debian-security.list + - /etc/apt/sources.list.d/debian-jessie.list + - /etc/apt/sources.list.d/debian-stretch.list + - /etc/apt/sources.list.d/debian-buster.list + - /etc/apt/sources.list.d/debian-bullseye.list + - /etc/apt/sources.list.d/debian-update.list + tags: + - apt + when: apt_clean_gandi_sourceslist | bool + - name: Install check for packages marked hold - include: hold_packages.yml + ansible.builtin.import_tasks: hold_packages.yml when: apt_install_hold_packages | bool tags: - apt - name: Updating APT cache - apt: + ansible.builtin.apt: update_cache: yes changed_when: False tags: - apt - name: Upgrading system - apt: + ansible.builtin.apt: upgrade: dist when: apt_upgrade | bool tags: diff --git a/apt/tasks/migrate-to-deb822.yml b/apt/tasks/migrate-to-deb822.yml new file mode 100644 index 00000000..18aa1580 --- /dev/null +++ b/apt/tasks/migrate-to-deb822.yml @@ -0,0 +1,32 @@ +--- +- ansible.builtin.include_role: + name: evolix/remount-usr + +- name: /usr/share/scripts exists + ansible.builtin.file: + dest: /usr/share/scripts + mode: "0700" + owner: root + group: root + state: directory + tags: + - apt + +- name: Migration scripts are installed + ansible.builtin.copy: + src: "{{ item }}" + dest: "/usr/share/scripts/{{ item }}" + force: true + mode: "0755" + loop: + - deb822-migration.py + - deb822-migration.sh + tags: + - apt + +- name: Exec migration script + ansible.builtin.command: + cmd: /usr/share/scripts/deb822-migration.sh + ignore_errors: yes + tags: + - apt diff --git a/apt/tasks/move-apt-keyring.yml b/apt/tasks/move-apt-keyring.yml new file mode 100644 index 00000000..5b0cdd9b --- /dev/null +++ b/apt/tasks/move-apt-keyring.yml @@ -0,0 +1,53 @@ +--- + +- name: New APT keyrings directory is present + ansible.builtin.file: + path: /etc/apt/keyrings + state: directory + mode: "0755" + owner: root + group: root + +- ansible.builtin.include_role: + name: evolix/remount-usr + +- name: /usr/share/scripts exists + ansible.builtin.file: + dest: /usr/share/scripts + mode: "0700" + owner: root + group: root + state: directory + tags: + - apt + +- name: migration script is present + ansible.builtin.copy: + src: move-apt-keyrings.sh + dest: /usr/share/scripts/move-apt-keyrings.sh + mode: "0755" + owner: root + group: root + +- name: Move repository signing key + ansible.builtin.command: + cmd: "/usr/share/scripts/move-apt-keyrings.sh \"{{ item.repository_pattern }}\" \"{{ item.key }}\"" + loop: + - { repository_pattern: "http://pub.evolix.net/", key: "reg.asc" } + - { repository_pattern: "http://pub.evolix.org/evolix", key: "pub_evolix.asc" } + - { repository_pattern: "https://pub.evolix.org/evolix", key: "pub_evolix.asc" } + - { repository_pattern: "https://artifacts.elastic.co/packages/[^/]+/apt", key: "elastics.asc" } + - { repository_pattern: "https://download.docker.com/linux/debian", key: "docker-debian.asc" } + - { repository_pattern: "https://downloads.linux.hpe.com/SDR/repo/mcp", key: "hpePublicKey2048_key1.asc" } + - { repository_pattern: "http://pkg.jenkins-ci.org/debian-stable", key: "jenkins.asc" } + - { repository_pattern: "https://packages.sury.org/php/", key: "sury.gpg" } + - { repository_pattern: "http://repo.mongodb.org/apt/debian", key: "mongodb-server-[0-9\\.]+.asc" } + - { repository_pattern: "http://apt.newrelic.com/debian/", key: "newrelic.asc" } + - { repository_pattern: "https://deb.nodesource.com/", key: "nodesource.asc" } + - { repository_pattern: "https://dl.yarnpkg.com/debian/", key: "yarn.asc" } + - { repository_pattern: "http://apt.postgresql.org/pub/repos/apt/", key: "postgresql.asc" } + register: _cmd + +- name: Debug command + ansible.builtin.debug: + var: _cmd diff --git a/apt/templates/bookworm_backports.sources.j2 b/apt/templates/bookworm_backports.sources.j2 new file mode 100644 index 00000000..31ac2f3b --- /dev/null +++ b/apt/templates/bookworm_backports.sources.j2 @@ -0,0 +1,7 @@ +# {{ ansible_managed }} + +Types: deb +URIs: http://mirror.evolix.org/debian +Suites: bullseye-backports +Components: {{ apt_backports_components | mandatory }} +Enabled: {{ apt_install_backports | bool | ternary('yes', 'no') }} diff --git a/apt/templates/bookworm_basics.sources.j2 b/apt/templates/bookworm_basics.sources.j2 new file mode 100644 index 00000000..5a0cd3aa --- /dev/null +++ b/apt/templates/bookworm_basics.sources.j2 @@ -0,0 +1,7 @@ +# {{ ansible_managed }} + +Types: deb +URIs: http://mirror.evolix.org/debian +Suites: bookworm bookworm-updates +Components: {{ apt_basics_components | mandatory }} +Enabled: yes diff --git a/apt/templates/bookworm_security.sources.j2 b/apt/templates/bookworm_security.sources.j2 new file mode 100644 index 00000000..56180957 --- /dev/null +++ b/apt/templates/bookworm_security.sources.j2 @@ -0,0 +1,7 @@ +# {{ ansible_managed }} + +Types: deb +URIs: https://security.debian.org/debian-security +Suites: bookworm-security +Components: {{ apt_basics_components | mandatory }} +Enabled: yes diff --git a/apt/templates/bullseye_basics.list.j2 b/apt/templates/bullseye_basics.list.j2 index 94b0995d..55f32b8d 100644 --- a/apt/templates/bullseye_basics.list.j2 +++ b/apt/templates/bullseye_basics.list.j2 @@ -2,4 +2,4 @@ deb http://mirror.evolix.org/debian bullseye {{ apt_basics_components | mandatory }} deb http://mirror.evolix.org/debian/ bullseye-updates {{ apt_basics_components | mandatory }} -deb https://deb.debian.org/debian-security bullseye-security {{ apt_basics_components | mandatory }} \ No newline at end of file +deb http://security.debian.org/debian-security bullseye-security {{ apt_basics_components | mandatory }} diff --git a/apt/templates/evolix_public.list.j2 b/apt/templates/evolix_public.list.j2 index 06de99c0..e00899e7 100644 --- a/apt/templates/evolix_public.list.j2 +++ b/apt/templates/evolix_public.list.j2 @@ -1,3 +1,3 @@ # {{ ansible_managed }} -deb http://pub.evolix.net/ {{ ansible_distribution_release }}/ +deb [signed-by={{ apt_keyring_dir }}/pub_evolix.asc] http://pub.evolix.org/evolix {{ ansible_distribution_release }} main diff --git a/apt/templates/evolix_public.sources.j2 b/apt/templates/evolix_public.sources.j2 new file mode 100644 index 00000000..defd1282 --- /dev/null +++ b/apt/templates/evolix_public.sources.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +Types:deb +URIs: http://pub.evolix.org/evolix +Suites: {{ ansible_distribution_release }} +Components: main +Signed-by: {{ apt_keyring_dir }}/pub_evolix.asc +Enabled: yes diff --git a/apt/templates/stretch_backports.list.j2 b/apt/templates/stretch_backports.list.j2 index 4f69547d..ffd6f98f 100644 --- a/apt/templates/stretch_backports.list.j2 +++ b/apt/templates/stretch_backports.list.j2 @@ -1,3 +1,3 @@ # {{ ansible_managed }} -deb http://mirror.evolix.org/debian stretch-backports {{ apt_backports_components | mandatory }} +deb http://archive.debian.org/debian stretch-backports {{ apt_backports_components | mandatory }} diff --git a/bind/defaults/main.yml b/bind/defaults/main.yml index 99b33e13..c34490f8 100644 --- a/bind/defaults/main.yml +++ b/bind/defaults/main.yml @@ -8,4 +8,5 @@ bind_systemd_service_path: /etc/systemd/system/bind9.service bind_statistics_file: /var/run/named.stats bind_log_file: /var/log/bind.log bind_query_file: /var/log/bind_queries.log +bind_query_file_enabled: False bind_cache_dir: /var/cache/bind diff --git a/bind/handlers/main.yml b/bind/handlers/main.yml index 8bb61a21..5461579d 100644 --- a/bind/handlers/main.yml +++ b/bind/handlers/main.yml @@ -1,19 +1,21 @@ --- - name: reload systemd - command: systemctl daemon-reload + ansible.builtin.systemd: + daemon-reload: yes + - name: restart apparmor - service: + ansible.builtin.systemd: name: apparmor state: restarted - name: restart bind - service: + ansible.builtin.systemd: name: bind9 state: restarted - name: restart munin-node - service: + ansible.builtin.systemd: name: munin-node state: restarted diff --git a/bind/tasks/authoritative.yml b/bind/tasks/authoritative.yml new file mode 100644 index 00000000..7fbd827d --- /dev/null +++ b/bind/tasks/authoritative.yml @@ -0,0 +1,11 @@ +--- + +- name: Set bind configuration for authoritative server + ansible.builtin.template: + src: named.conf.options_authoritative.j2 + dest: /etc/bind/named.conf.options + owner: bind + group: bind + mode: "0644" + force: true + notify: restart bind \ No newline at end of file diff --git a/bind/tasks/main.yml b/bind/tasks/main.yml index d1348cd2..1e20eee2 100644 --- a/bind/tasks/main.yml +++ b/bind/tasks/main.yml @@ -1,75 +1,60 @@ # Until chroot-bind.sh is migrated to ansible, we hardcode the chroot paths. - name: set chroot variables - set_fact: - bind_log_file: /var/log/bind.log - bind_query_file: /var/log/bind_queries.log - bind_cache_dir: /var/cache/bind - bind_statistics_file: /var/run/named.stats - bind_chroot_path: /var/chroot-bind + ansible.builtin.set_fact: + bind_log_file: /var/log/bind.log + bind_query_file: /var/log/bind_queries.log + bind_cache_dir: /var/cache/bind + bind_statistics_file: /var/run/named.stats + bind_chroot_path: /var/chroot-bind when: bind_chroot_set | bool +- name: Check AppArmor + ansible.builtin.shell: + cmd: systemctl is-active apparmor || systemctl is-enabled apparmor + failed_when: False + changed_when: False + check_mode: no + register: check_apparmor + - name: configure apparmor - template: + ansible.builtin.template: src: apparmor.usr.sbin.named.j2 dest: /etc/apparmor.d/usr.sbin.named owner: root group: root - mode: '0644' - force: yes + mode: "0644" + force: true notify: restart apparmor + when: check_apparmor.rc == 0 - name: package are installed - apt: + ansible.builtin.apt: name: - bind9 - dnstop state: present -- name: Set bind configuration for recursive server - template: - src: named.conf.options_recursive.j2 - dest: /etc/bind/named.conf.options - owner: bind - group: bind - mode: "0644" - force: yes - notify: restart bind - when: bind_recursive_server | bool - -- name: enable zones.rfc1918 for recursive server - lineinfile: - dest: /etc/bind/named.conf.local - line: 'include "/etc/bind/zones.rfc1918";' - regexp: "zones.rfc1918" - notify: restart bind - when: bind_recursive_server | bool - -- name: Set bind configuration for authoritative server - template: - src: named.conf.options_authoritative.j2 - dest: /etc/bind/named.conf.options - owner: bind - group: bind - mode: "0644" - force: yes - notify: restart bind +- ansible.builtin.include: authoritative.yml when: bind_authoritative_server | bool -- name: Create systemd service - template: - src: bind9.service.j2 +- ansible.builtin.include: recursive.yml + when: bind_recursive_server | bool + +- name: Create systemd service for Debian 8 (Jessie) + ansible.builtin.template: + src: bind9.service.jessie.j2 dest: "{{ bind_systemd_service_path }}" owner: root group: root mode: "0644" - force: yes + force: true notify: - - reload systemd - - restart bind + - reload systemd + - restart bind when: ansible_distribution_release == "jessie" - name: "touch {{ bind_log_file }} if non chroot" - file: + ansible.builtin.file: path: "{{ bind_log_file }}" owner: bind group: adm @@ -78,7 +63,7 @@ when: not (bind_chroot_set | bool) - name: "touch {{ bind_query_file }} if non chroot" - file: + ansible.builtin.file: path: "{{ bind_query_file }}" owner: bind group: adm @@ -87,29 +72,30 @@ when: not (bind_chroot_set | bool) - name: send chroot-bind.sh in /root - copy: + ansible.builtin.copy: src: chroot-bind.sh dest: /root/chroot-bind.sh mode: "0700" owner: root - force: yes + force: true backup: yes when: bind_chroot_set | bool - name: exec chroot-bind.sh - command: "/root/chroot-bind.sh" + ansible.builtin.command: + cmd: "/root/chroot-bind.sh" register: chrootbind_run changed_when: False when: bind_chroot_set | bool -- debug: +- ansible.builtin.debug: var: chrootbind_run.stdout_lines when: - bind_chroot_set | bool - chrootbind_run.stdout | length > 0 - name: Modify OPTIONS in /etc/default/bind9 for chroot - replace: + ansible.builtin.replace: dest: /etc/default/bind9 regexp: '^OPTIONS=.*' replace: 'OPTIONS="-u bind -t {{ bind_chroot_path }}"' @@ -117,13 +103,13 @@ when: bind_chroot_set | bool - name: logrotate for bind - template: + ansible.builtin.template: src: logrotate_bind.j2 dest: /etc/logrotate.d/bind9 owner: root group: root mode: "0644" - force: yes + force: true notify: restart bind -- include: munin.yml +- ansible.builtin.include: munin.yml diff --git a/bind/tasks/munin.yml b/bind/tasks/munin.yml index f97ddf85..fee99750 100644 --- a/bind/tasks/munin.yml +++ b/bind/tasks/munin.yml @@ -1,7 +1,7 @@ --- - name: is Munin present ? - stat: + ansible.builtin.stat: path: /etc/munin/plugin-conf.d/munin-node check_mode: no register: munin_node_plugins_config @@ -10,7 +10,7 @@ - munin - name: Enable munin plugins for authoritative server - file: + ansible.builtin.file: src: "/usr/share/munin/plugins/{{ item }}" dest: "/etc/munin/plugins/{{ item }}" state: link @@ -18,39 +18,39 @@ - bind9 - bind9_rndc notify: restart munin-node - when: - - bind_authoritative_server - - munin_node_plugins_config.stat.exists tags: - bind - munin + when: + - bind_authoritative_server | bool + - munin_node_plugins_config.stat.exists - name: Enable munin plugins for recursive server - file: + ansible.builtin.file: src: "/usr/share/munin/plugins/{{ item }}" dest: "/etc/munin/plugins/{{ item }}" state: link loop: - bind9 - - bind9_rndc notify: restart munin-node - when: - - bind_recursive_server - - munin_node_plugins_config.stat.exists tags: - bind - munin + when: + - bind_recursive_server | bool + - bind_query_file_enabled | bool + - munin_node_plugins_config.stat.exists - name: Add munin plugin configuration - template: + ansible.builtin.template: src: munin-env_bind9.j2 dest: /etc/munin/plugin-conf.d/bind9 owner: root group: root mode: "0644" - force: yes + force: true notify: restart munin-node - when: munin_node_plugins_config.stat.exists tags: - bind - munin + when: munin_node_plugins_config.stat.exists diff --git a/bind/tasks/recursive.yml b/bind/tasks/recursive.yml new file mode 100644 index 00000000..887f206e --- /dev/null +++ b/bind/tasks/recursive.yml @@ -0,0 +1,19 @@ +--- + + +- name: Set bind configuration for recursive server + ansible.builtin.template: + src: named.conf.options_recursive.j2 + dest: /etc/bind/named.conf.options + owner: bind + group: bind + mode: "0644" + force: true + notify: restart bind + +- name: enable zones.rfc1918 for recursive server + ansible.builtin.lineinfile: + dest: /etc/bind/named.conf.local + line: 'include "/etc/bind/zones.rfc1918";' + regexp: "zones.rfc1918" + notify: restart bind diff --git a/bind/templates/apparmor.usr.sbin.named.j2 b/bind/templates/apparmor.usr.sbin.named.j2 index 9a554437..d9f0be04 100644 --- a/bind/templates/apparmor.usr.sbin.named.j2 +++ b/bind/templates/apparmor.usr.sbin.named.j2 @@ -56,7 +56,9 @@ # some people like to put logs in /var/log/named/ instead of having # syslog do the heavy lifting. {{ bind_log_file }} rw, +{% if bind_query_file_enabled | bool %} {{ bind_query_file }} rw, +{% endif %} # gssapi /var/lib/sss/pubconf/krb5.include.d/** r, diff --git a/bind/templates/bind9.service.j2 b/bind/templates/bind9.service.jessie.j2 similarity index 100% rename from bind/templates/bind9.service.j2 rename to bind/templates/bind9.service.jessie.j2 diff --git a/bind/templates/logrotate_bind.j2 b/bind/templates/logrotate_bind.j2 index 3fe1589e..c7ec3c30 100644 --- a/bind/templates/logrotate_bind.j2 +++ b/bind/templates/logrotate_bind.j2 @@ -1,7 +1,7 @@ -{% if bind_chroot_set %} -{{ bind_chroot_path }}{{bind_log_file}} { +{% if bind_chroot_set | bool %} +{{ bind_chroot_path }}{{ bind_log_file }} { {% else %} -{{bind_log_file}} { +{{ bind_log_file }} { {% endif %} weekly missingok diff --git a/bind/templates/munin-env_bind9.j2 b/bind/templates/munin-env_bind9.j2 index de88b27a..2af70548 100644 --- a/bind/templates/munin-env_bind9.j2 +++ b/bind/templates/munin-env_bind9.j2 @@ -1,9 +1,17 @@ [bind*] user root -env.logfile {% if bind_chroot_set %}{{ bind_chroot_path }}{% endif %}{{ bind_query_file }} +{% if bind_query_file_enabled | bool %} +{% if bind_chroot_set | bool %} +env.logfile {{ bind_chroot_path }}{{ bind_query_file }} +{% else %} +env.logfile {{ bind_query_file }} +{% endif %} +{% endif %} + {% if bind_authoritative_server %} env.querystats {% if bind_chroot_set %}{{ bind_chroot_path }}{% endif %}{{ bind_statistics_file }} {% endif %} + env.MUNIN_PLUGSTATE /var/lib/munin timeout 120 diff --git a/bind/templates/named.conf.options_authoritative.j2 b/bind/templates/named.conf.options_authoritative.j2 index 8f48cbb5..4b6065b6 100644 --- a/bind/templates/named.conf.options_authoritative.j2 +++ b/bind/templates/named.conf.options_authoritative.j2 @@ -1,7 +1,7 @@ -acl "foo" { - ::ffff:192.0.2.21; 192.0.2.21; - 2001:db8::21; -}; +// acl "foo" { +// ::ffff:192.0.2.21; 192.0.2.21; +// 2001:db8::21; +// }; options { directory "{{ bind_cache_dir }}"; @@ -20,16 +20,20 @@ options { logging { category default { default_file; }; +{% if bind_query_file_enabled | bool %} category queries { query_logging; }; +{% endif %} channel default_file { file "{{ bind_log_file }}"; severity info; }; +{% if bind_query_file_enabled | bool %} channel query_logging { file "{{ bind_query_file }}" versions 2 size 128M; print-category yes; print-severity yes; print-time yes; }; +{% endif %} }; diff --git a/bind/templates/named.conf.options_recursive.j2 b/bind/templates/named.conf.options_recursive.j2 index 27246d13..931ac71d 100644 --- a/bind/templates/named.conf.options_recursive.j2 +++ b/bind/templates/named.conf.options_recursive.j2 @@ -9,16 +9,20 @@ options { logging { category default { default_file; }; +{% if bind_query_file_enabled | bool %} category queries { query_logging; }; +{% endif %} channel default_file { file "{{ bind_log_file }}"; severity info; }; +{% if bind_query_file_enabled | bool %} channel query_logging { file "{{ bind_query_file }}" versions 2 size 128M; print-category yes; print-severity yes; print-time yes; }; +{% endif %} }; diff --git a/bookworm-detect/tasks/main.yml b/bookworm-detect/tasks/main.yml new file mode 100644 index 00000000..c0c50fdd --- /dev/null +++ b/bookworm-detect/tasks/main.yml @@ -0,0 +1,11 @@ +--- + +- ansible.builtin.debug: + var: ansible_lsb + +# Force facts until Debian 12 is released because Ansible is dumb +- ansible.builtin.set_fact: + ansible_distribution_major_version: 12 + ansible_distribution: "Debian" + ansible_distribution_release: "bookworm" + when: "'bookworm' in ansible_lsb.codename or 'bookworm' in ansible_lsb.description" \ No newline at end of file diff --git a/bullseye-detect/tasks/main.yml b/bullseye-detect/tasks/main.yml index 6f97db0a..e18d826b 100644 --- a/bullseye-detect/tasks/main.yml +++ b/bullseye-detect/tasks/main.yml @@ -1,7 +1,7 @@ --- # Force facts until Debian 11 is released because Ansible is dumb -- set_fact: +- ansible.builtin.set_fact: ansible_distribution_major_version: 11 ansible_distribution: "Debian" ansible_distribution_release: "bullseye" diff --git a/certbot/files/hooks/deploy/hapee.sh b/certbot/files/hooks/deploy/hapee.sh new file mode 100644 index 00000000..89b04452 --- /dev/null +++ b/certbot/files/hooks/deploy/hapee.sh @@ -0,0 +1,94 @@ +#!/bin/sh + +error() { + >&2 echo "${PROGNAME}: $1" + exit 1 +} +debug() { + if [ "${VERBOSE}" = "1" ] && [ "${QUIET}" != "1" ]; then + >&2 echo "${PROGNAME}: $1" + fi +} +daemon_found_and_running() { + readonly hapee_main_pid=$(ps -u root u | grep hapee-lb | grep -v grep | awk '{print $2}') + if [ -n "${hapee_main_pid}" ] && [ -d "/proc/${hapee_main_pid}" ] ; then + readonly hapee_bin=$(readlink "/proc/${hapee_main_pid}/exe") + readonly hapee_config_file=$(cat "/proc/${hapee_main_pid}/cmdline" | tr "\0" " " | grep --only-matching --extended-regexp -- "-f \S+" | awk '{print $2}') + readonly hapee_pid_file=$(cat "/proc/${hapee_main_pid}/cmdline" | tr "\0" " " | grep --only-matching --extended-regexp -- "-p \S+" | awk '{print $2}') + readonly hapee_service_name="$(basename -s .pid "${hapee_pid_file}").service" + + kill -0 "${hapee_main_pid}" && test -n "${hapee_bin}" && test -f "${hapee_config_file}" && systemctl -q is-active "${hapee_service_name}" + else + return 1 + fi +} +found_renewed_lineage() { + test -f "${RENEWED_LINEAGE}/fullchain.pem" && test -f "${RENEWED_LINEAGE}/privkey.pem" +} +config_check() { + ${hapee_bin} -c -f "${hapee_config_file}" > /dev/null 2>&1 +} +concat_files() { + # shellcheck disable=SC2174 + mkdir --mode=700 --parents "${hapee_cert_dir}" + chown root: "${hapee_cert_dir}" + + debug "Concatenating certificate files to ${hapee_cert_file}" + cat "${RENEWED_LINEAGE}/fullchain.pem" "${RENEWED_LINEAGE}/privkey.pem" > "${hapee_cert_file}" + chmod 600 "${hapee_cert_file}" + chown root: "${hapee_cert_file}" +} +cert_and_key_mismatch() { + hapee_cert_md5=$(openssl x509 -noout -modulus -in "${hapee_cert_file}" | openssl md5) + hapee_key_md5=$(openssl rsa -noout -modulus -in "${hapee_cert_file}" | openssl md5) + + test "${hapee_cert_md5}" != "${hapee_key_md5}" +} +detect_hapee_cert_dir() { + # get last field or line wich defines the crt directory + config_cert_dir=$(grep -r -o -E -h '^\s*bind .* crt /etc/\S+' "${hapee_config_file}" | head -1 | awk '{ print $(NF)}') + if [ -n "${config_cert_dir}" ]; then + debug "Cert directory is configured with ${config_cert_dir}" + echo "${config_cert_dir}" + else + error "Cert directory not found." + fi +} +main() { + if [ -z "${RENEWED_LINEAGE}" ]; then + error "This script must be called only by certbot!" + fi + + if daemon_found_and_running; then + readonly hapee_cert_dir=$(detect_hapee_cert_dir) + + if found_renewed_lineage; then + hapee_cert_file="${hapee_cert_dir}/$(basename "${RENEWED_LINEAGE}").pem" + failed_cert_file="/root/$(basename "${RENEWED_LINEAGE}").failed.pem" + + concat_files + + if cert_and_key_mismatch; then + mv "${hapee_cert_file}" "${failed_cert_file}" + error "Key and cert don't match, we moved the file to ${failed_cert_file} for inspection" + fi + + if config_check; then + debug "HAPEE detected... reloading" + systemctl reload "${hapee_service_name}" + else + error "HAPEE config is broken, you must fix it !" + fi + else + error "Couldn't find ${RENEWED_LINEAGE}/fullchain.pem or ${RENEWED_LINEAGE}/privkey.pem" + fi + else + debug "HAPEE is not running or missing. Skip." + fi +} + +readonly PROGNAME=$(basename "$0") +readonly VERBOSE=${VERBOSE:-"0"} +readonly QUIET=${QUIET:-"0"} + +main diff --git a/certbot/files/hooks/deploy/sync_remote.sh b/certbot/files/hooks/deploy/sync_remote.sh index 7fc3ecf4..bbbe6f5f 100644 --- a/certbot/files/hooks/deploy/sync_remote.sh +++ b/certbot/files/hooks/deploy/sync_remote.sh @@ -28,10 +28,6 @@ main() { if [ -z "${RENEWED_LINEAGE}" ]; then error "Missing RENEWED_LINEAGE environment variable (usually provided by certbot)." fi - if [ -z "${servers}" ]; then - debug "Empty server list, skip." - exit 0 - fi if found_renewed_lineage; then RENEWED_DOMAINS=${RENEWED_DOMAINS:-$(domain_from_cert)} diff --git a/certbot/handlers/main.yml b/certbot/handlers/main.yml index 4363ed3d..54f114e2 100644 --- a/certbot/handlers/main.yml +++ b/certbot/handlers/main.yml @@ -1,23 +1,24 @@ --- - name: reload nginx - service: + ansible.builtin.systemd: name: nginx state: reloaded - name: reload apache - service: + ansible.builtin.systemd: name: apache2 state: reloaded - name: reload haproxy - service: + ansible.builtin.systemd: name: haproxy state: reloaded - name: systemd daemon-reload - systemd: + ansible.builtin.systemd: daemon_reload: yes - name: install letsencrypt-auto - command: /usr/local/bin/letsencrypt-auto --noninteractive --install-only --no-self-upgrade + ansible.builtin.command: + cmd: /usr/local/bin/letsencrypt-auto --noninteractive --install-only --no-self-upgrade diff --git a/certbot/tasks/acme-challenge.yml b/certbot/tasks/acme-challenge.yml index 56b0c099..acd93fe0 100644 --- a/certbot/tasks/acme-challenge.yml +++ b/certbot/tasks/acme-challenge.yml @@ -1,51 +1,52 @@ --- - name: Certbot work directory is present - file: + ansible.builtin.file: dest: "{{ certbot_work_dir }}" state: directory mode: "0755" - name: Check if Nginx is installed - stat: + ansible.builtin.stat: path: /etc/nginx register: is_nginx - name: ACME challenge for Nginx is installed - template: + ansible.builtin.template: src: acme-challenge/nginx.conf.j2 dest: /etc/nginx/snippets/letsencrypt.conf - force: yes + force: true notify: reload nginx when: is_nginx.stat.exists - name: Check if Apache is installed - stat: + ansible.builtin.stat: path: /usr/sbin/apachectl register: is_apache - name: ACME challenge for Apache block: - name: ACME challenge for Apache is installed - template: + ansible.builtin.template: src: acme-challenge/apache.conf.j2 dest: /etc/apache2/conf-available/letsencrypt.conf - force: yes + force: true notify: reload apache - name: ACME challenge for Apache is enabled - command: "a2enconf letsencrypt" + ansible.builtin.command: + cmd: "a2enconf letsencrypt" register: command_result changed_when: "'Enabling' in command_result.stderr" notify: reload apache when: is_apache.stat.exists - name: Check if HAProxy is installed - stat: + ansible.builtin.stat: path: /etc/haproxy register: is_haproxy - name: ACME challenge for HAProxy is installed - debug: + ansible.builtin.debug: msg: "ACME challenge configuration for HAProxy must be configured manually" when: is_haproxy.stat.exists diff --git a/certbot/tasks/install-legacy.yml b/certbot/tasks/install-legacy.yml index 446e557a..157c8dc1 100644 --- a/certbot/tasks/install-legacy.yml +++ b/certbot/tasks/install-legacy.yml @@ -1,41 +1,42 @@ --- - name: certbot package is removed - apt: + ansible.builtin.apt: name: certbot state: absent -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr # copied and customized from https://raw.githubusercontent.com/certbot/certbot/v1.14.0/letsencrypt-auto - name: Let's Encrypt script is present - copy: + ansible.builtin.copy: src: letsencrypt-auto dest: /usr/local/bin/letsencrypt-auto mode: '0755' owner: root group: root - force: yes + force: true notify: install letsencrypt-auto - name: Check certbot script - stat: + ansible.builtin.stat: path: /usr/local/bin/certbot register: certbot_path - name: Rename certbot script if present - command: "mv /usr/local/bin/certbot /usr/local/bin/certbot.bak" + ansible.builtin.command: + cmd: "mv /usr/local/bin/certbot /usr/local/bin/certbot.bak" when: certbot_path.stat.exists - name: Let's Encrypt script is symlinked as certbot - file: + ansible.builtin.file: src: "/usr/local/bin/letsencrypt-auto" dest: "/usr/local/bin/certbot" state: link - name: systemd artefacts are absent - file: + ansible.builtin.file: dest: "{{ item }}" state: absent loop: @@ -45,14 +46,14 @@ notify: systemd daemon-reload - name: custom crontab is present - copy: + ansible.builtin.copy: src: cron_jessie dest: /etc/cron.d/certbot - force: yes + force: true when: certbot_custom_crontab | bool - name: disable self-upgrade - ini_file: + community.general.ini_file: dest: "/etc/letsencrypt/cli.ini" section: null option: "no-self-upgrade" diff --git a/certbot/tasks/install-package.yml b/certbot/tasks/install-package.yml index 06247db4..c12b49e4 100644 --- a/certbot/tasks/install-package.yml +++ b/certbot/tasks/install-package.yml @@ -1,6 +1,6 @@ --- - name: certbot package is installed - apt: + ansible.builtin.apt: name: certbot state: latest diff --git a/certbot/tasks/main.yml b/certbot/tasks/main.yml index cede35a6..3dcb1334 100644 --- a/certbot/tasks/main.yml +++ b/certbot/tasks/main.yml @@ -1,28 +1,28 @@ --- - name: "System compatibility checks" - assert: + ansible.builtin.assert: that: - ansible_distribution == "Debian" - ansible_distribution_major_version is version('8', '>=') msg: only compatible with Debian 9+ - name: Install legacy script on Debian 8 - include: install-legacy.yml + ansible.builtin.include: install-legacy.yml when: - ansible_distribution == "Debian" - ansible_distribution_major_version is version('9', '<') - name: Install package on Debian 9+ - include: install-package.yml + ansible.builtin.include: install-package.yml when: - ansible_distribution == "Debian" - ansible_distribution_major_version is version('9', '>=') -- include: acme-challenge.yml +- ansible.builtin.include: acme-challenge.yml - name: Deploy hooks are present - copy: + ansible.builtin.copy: src: hooks/deploy/ dest: /etc/letsencrypt/renewal-hooks/deploy/ mode: "0700" @@ -30,7 +30,7 @@ group: root - name: Manual deploy hook is present - copy: + ansible.builtin.copy: src: hooks/manual-deploy.sh dest: /etc/letsencrypt/renewal-hooks/manual-deploy.sh mode: "0700" @@ -38,7 +38,7 @@ group: root - name: "sync_remote is configured with servers" - lineinfile: + ansible.builtin.lineinfile: dest: /etc/letsencrypt/renewal-hooks/deploy/sync_remote.cf regexp: "^servers=" line: "servers=\"{{ certbot_hooks_sync_remote_servers | join(' ') }}\"" @@ -46,14 +46,15 @@ # begining of backward compatibility tasks - name: Move deploy/commit-etc.sh to deploy/z-commit-etc.sh if present - command: "mv /etc/letsencrypt/renewal-hooks/deploy/commit-etc.sh /etc/letsencrypt/renewal-hooks/deploy/z-commit-etc.sh" + ansible.builtin.command: + cmd: "mv /etc/letsencrypt/renewal-hooks/deploy/commit-etc.sh /etc/letsencrypt/renewal-hooks/deploy/z-commit-etc.sh" args: removes: /etc/letsencrypt/renewal-hooks/deploy/commit-etc.sh creates: /etc/letsencrypt/renewal-hooks/deploy/z-commit-etc.sh # end of backward compatibility tasks - name: "certbot lock is ignored by Git" - lineinfile: + ansible.builtin.lineinfile: dest: /etc/.gitignore line: letsencrypt/.certbot.lock create: yes diff --git a/clamav/handlers/main.yml b/clamav/handlers/main.yml index e053f01a..c931807b 100644 --- a/clamav/handlers/main.yml +++ b/clamav/handlers/main.yml @@ -1,5 +1,5 @@ --- - name: restart clamav - service: + ansible.builtin.service: name: clamav-daemon state: restarted diff --git a/clamav/tasks/main.yml b/clamav/tasks/main.yml index 6d1da3eb..7044ddce 100644 --- a/clamav/tasks/main.yml +++ b/clamav/tasks/main.yml @@ -1,6 +1,6 @@ --- - name: configure clamav-daemon - debconf: + ansible.builtin.debconf: name: clamav-daemon question: "{{ item.key }}" value: "{{ item.value }}" @@ -13,7 +13,7 @@ - { key: 'clamav-daemon/StreamMaxLength', type: 'string', value: '25' } - { key: 'clamav-daemon/ReadTimeout', type: 'string', value: '180' } - { key: 'clamav-daemon/StatsEnabled', type: 'boolean', value: 'false' } - - { key: 'clamav-daemon/MaxConnectionQueueLength', type: 'string', value: '15' } + - { key: 'clamav-daemon/MaxConnectionQueueLength', type: 'string', value: '200' } - { key: 'clamav-daemon/LogRotate', type: 'boolean', value: 'true' } - { key: 'clamav-daemon/AllowAllMatchScan', type: 'boolean', value: 'true' } - { key: 'clamav-daemon/ScanOnAccess', type: 'boolean', value: 'false' } @@ -52,7 +52,7 @@ - clamav - name: configure clamav-freshclam - debconf: + ansible.builtin.debconf: name: clamav-freshclam question: "{{ item.key }}" value: "{{ item.value }}" @@ -73,7 +73,7 @@ - clamav - name: install ClamAV - apt: + ansible.builtin.apt: name: - clamav-daemon - clamav @@ -92,7 +92,7 @@ - clamav - name: add clamav user to amavis group - user: + ansible.builtin.user: name: clamav groups: amavis append: True @@ -100,7 +100,7 @@ - clamav - name: allow supplementary groups - replace: + ansible.builtin.replace: dest: /etc/clamav/clamd.conf regexp: 'AllowSupplementaryGroups false' replace: 'AllowSupplementaryGroups true' diff --git a/dhcpd/handlers/main.yml b/dhcpd/handlers/main.yml index 09f93269..8cfa9eb0 100644 --- a/dhcpd/handlers/main.yml +++ b/dhcpd/handlers/main.yml @@ -1,5 +1,5 @@ --- - name: restart dhcp - service: + ansible.builtin.service: name: isc-dhcp-server state: restarted diff --git a/dhcpd/tasks/main.yml b/dhcpd/tasks/main.yml index 828a219f..214c5d58 100644 --- a/dhcpd/tasks/main.yml +++ b/dhcpd/tasks/main.yml @@ -1,4 +1,4 @@ - name: ensure packages are installed - apt: + ansible.builtin.apt: name: isc-dhcp-server state: present diff --git a/docker-host/defaults/main.yml b/docker-host/defaults/main.yml index 6393a962..bc5dc88f 100644 --- a/docker-host/defaults/main.yml +++ b/docker-host/defaults/main.yml @@ -1,17 +1,35 @@ --- -# If docher_home sets to /home/, the partition should be mounted with exec -# option. -docker_home: /srv/docker -docker_tmpdir: "{{docker_home}}/tmp" +# If docher_home sets to /home/, the partition should be mounted with exec option. +docker_home: /var/lib/docker +docker_tmpdir: "{{ docker_home }}/tmp" -docker_remote_access_enabled: True +# Chose to use iptables instead of docker-proxy userland process +docker_conf_use_iptables: False + +# Disable the possibility for containers processes to gain new privileges +docker_conf_no_newprivileges: False + +# Toggle live restore (need to be disabled in swarm mode) +docker_conf_live_restore: True + +# Toggle user namespace +docker_conf_user_namespace: True + +# Disable all default network connectivity +docker_conf_disable_default_networking: False + +# Remote access +docker_remote_access_enabled: False docker_daemon_port: 2376 docker_daemon_listening_ip: 0.0.0.0 -docker_tls_enabled: True -docker_tls_path: "{{docker_home}}/tls" +# TLS +docker_tls_enabled: False +docker_tls_path: "{{ docker_home }}/tls" docker_tls_ca: ca/ca.pem docker_tls_ca_key: ca/ca-key.pem docker_tls_cert: server/cert.pem docker_tls_key: server/key.pem docker_tls_csr: server/server.csr + +apt_keyring_dir: "{{ ansible_distribution_major_version is version('12', '<') | ternary('/etc/apt/trusted.gpg.d', '/etc/apt/keyrings') }}" \ No newline at end of file diff --git a/docker-host/files/docker_preferences b/docker-host/files/docker_preferences deleted file mode 100644 index 1a68427d..00000000 --- a/docker-host/files/docker_preferences +++ /dev/null @@ -1,3 +0,0 @@ -Package: python-docker -Pin: release a=jessie-backports -Pin-Priority: 999 diff --git a/docker-host/handlers/main.yml b/docker-host/handlers/main.yml index 8b484b49..46d42215 100644 --- a/docker-host/handlers/main.yml +++ b/docker-host/handlers/main.yml @@ -1,9 +1,10 @@ --- - name: reload systemd - command: systemctl daemon-reload + ansible.builtin.systemd: + daemon-reload: yes - name: restart docker - service: + ansible.builtin.systemd: name: docker state: restarted enabled: yes diff --git a/docker-host/tasks/jessie_backports.yml b/docker-host/tasks/jessie_backports.yml deleted file mode 100644 index e7c7e94f..00000000 --- a/docker-host/tasks/jessie_backports.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -- include_role: - name: evolix/apt - tasks_from: backports.yml - tags: - - packages - -- name: Prefer python-docker package from jessie-backports - copy: - src: docker_preferences - dest: /etc/apt/preferences.d/999-docker - force: yes - mode: "0640" - register: docker_apt_preferences - tags: - - packages - -- name: update apt - apt: - update_cache: yes - when: docker_apt_preferences is changed - tags: - - packages diff --git a/docker-host/tasks/main.yml b/docker-host/tasks/main.yml index 026181f6..f4175297 100644 --- a/docker-host/tasks/main.yml +++ b/docker-host/tasks/main.yml @@ -1,7 +1,7 @@ # This role installs the docker daemon --- - name: Remove older docker packages - apt: + ansible.builtin.apt: name: - docker - docker-engine @@ -9,81 +9,83 @@ state: absent - name: Install source requirements - apt: + ansible.builtin.apt: name: - - apt-transport-https - ca-certificates - gnupg2 state: present - update_cache: yes -- name: Add Docker repository - apt_repository: - repo: 'deb [arch=amd64] https://download.docker.com/linux/debian {{ ansible_distribution_release }} stable' +- name: Install apt-transport-https (Debian <10) + ansible.builtin.apt: + name: + - apt-transport-https state: present - update_cache: no - filename: docker.list - -- include: jessie_backports.yml - when: ansible_distribution_release == 'jessie' + when: ansible_distribution_major_version is version('10', '<') - name: Add Docker's official GPG key - copy: + ansible.builtin.copy: src: docker-debian.asc - dest: /etc/apt/trusted.gpg.d/docker-debian.asc - force: yes + dest: "{{ apt_keyring_dir }}/docker-debian.asc" + force: true mode: "0644" owner: root group: root +- name: Add Docker repository (Debian <12) + ansible.builtin.apt_repository: + repo: 'deb [signed-by={{ apt_keyring_dir }}/docker-debian.asc] https://download.docker.com/linux/debian {{ ansible_distribution_release }} stable' + filename: docker + state: present + update_cache: yes + when: ansible_distribution_major_version is version('12', '<') + +- name: Add Docker repository (Debian >=12) + ansible.builtin.template: + src: docker.sources.j2 + dest: /etc/apt/sources.list.d/docker.sources + state: present + register: docker_sources + when: ansible_distribution_major_version is version('12', '>=') + +- name: Update APT cache + ansible.builtin.apt: + update_cache: yes + when: docker_sources is changed + - name: Install Docker - apt: + ansible.builtin.apt: name: - docker-ce - docker-ce-cli - containerd.io - update_cache: yes - name: python-docker is installed - apt: + ansible.builtin.apt: name: python-docker state: present when: ansible_python_version is version('3', '<') - name: python3-docker is installed - apt: + ansible.builtin.apt: name: python3-docker state: present when: ansible_python_version is version('3', '>=') - name: Copy Docker daemon configuration file - template: + ansible.builtin.template: src: daemon.json.j2 dest: /etc/docker/daemon.json notify: restart docker -- name: Create override directory for docker unit - file: - name: /etc/systemd/system/docker.service.d/ - state: directory - mode: "0755" - -- name: Remove options in ExecStart from docker unit - copy: - src: docker.conf - dest: /etc/systemd/system/docker.service.d/ - mode: "0644" - notify: reload systemd - - name: Creating Docker tmp directory - file: + ansible.builtin.file: path: "{{ docker_tmpdir }}" state: directory mode: "0644" owner: root - name: Creating Docker TLS directory - file: + ansible.builtin.file: path: "{{ docker_tls_path }}" state: directory mode: "0644" @@ -91,7 +93,7 @@ when: docker_tls_enabled | bool - name: Copy shellpki utility to Docker TLS directory - template: + ansible.builtin.template: src: "{{ item }}.j2" dest: "{{ docker_tls_path }}/{{ item }}" mode: "0744" @@ -101,12 +103,13 @@ when: docker_tls_enabled | bool - name: Check if certs are already created - stat: + ansible.builtin.stat: path: "{{ docker_tls_path }}/certs" register: tls_certs_stat - name: Creating a CA, server key - command: "{{ docker_tls_path }}/shellpki.sh init" + ansible.builtin.command: + cmd: "{{ docker_tls_path }}/shellpki.sh init" when: - docker_tls_enabled | bool - not tls_certs_stat.stat.isdir diff --git a/docker-host/templates/daemon.json.j2 b/docker-host/templates/daemon.json.j2 index ab6cac19..92d60f8d 100644 --- a/docker-host/templates/daemon.json.j2 +++ b/docker-host/templates/daemon.json.j2 @@ -1,16 +1,32 @@ { "debug": false - {% if docker_tls_enabled %} - , - "tls": true, - "tlscert": "{{ docker_tls_path }}/{{ docker_tls_cert }}", - "tlscacert": "{{ docker_tls_path }}/{{ docker_tls_ca }}", - "tlskey": "{{ docker_tls_path }}/{{ docker_tls_key }}" - {% endif %} - , - {% if docker_remote_access_enabled %} - "hosts": ["tcp://{{ docker_daemon_listening_ip }}:{{ docker_daemon_port }}", "fd://"] - {% else %} - "hosts": ["fd://"] - {% endif %} + {# Docker data-dir (default to /var/lib/docker) #} + ,"data-root": "{{ docker_home }}" + {# Keep containers running while docker daemon downtime #} + ,"live-restore": {{ docker_conf_live_restore | to_json }} +{% if docker_conf_user_namespace %} + {# Turn on user namespace remaping #} + ,"userns-remap": "default" +{% endif %} +{% if docker_conf_use_iptables %} + {# Use iptables instead of docker-proxy #} + ,"userland-proxy": false + ,"iptables": true +{% endif %} + {# Disable the possibility for containers processes to gain new privileges #} + ,"no-new-privileges": {{ docker_conf_no_newprivileges | to_json }} +{% if docker_conf_disable_default_networking %} + {# Disable all default network connectivity #} + ,"bridge": "none" + ,"icc": false +{% endif %} +{% if docker_tls_enabled %} + ,"tls": true + ,"tlscert": "{{ docker_tls_path }}/{{ docker_tls_cert }}" + ,"tlscacert": "{{ docker_tls_path }}/{{ docker_tls_ca }}" + ,"tlskey": "{{ docker_tls_path }}/{{ docker_tls_key }}" +{% endif %} +{% if docker_remote_access_enabled %} + ,"hosts": ["tcp://{{ docker_daemon_listening_ip }}:{{ docker_daemon_port }}", "fd://"] +{% endif %} } diff --git a/docker-host/templates/docker.sources.j2 b/docker-host/templates/docker.sources.j2 new file mode 100644 index 00000000..5e349774 --- /dev/null +++ b/docker-host/templates/docker.sources.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +Types: deb +URIs: https://download.docker.com/linux/debian +Suites: {{ ansible_distribution_release }} +Components: stable +Signed-by: {{ apt_keyring_dir }}/docker-debian.asc +Enabled: yes \ No newline at end of file diff --git a/dovecot/README.md b/dovecot/README.md index 736b95dc..8652006f 100644 --- a/dovecot/README.md +++ b/dovecot/README.md @@ -2,6 +2,8 @@ Installation and basic configuration of dovecot +Do not use this role to update Dovecot 2.2 to 2.3. + ## Tasks Minimal configuration is in `tasks/main.yml` @@ -9,3 +11,14 @@ Minimal configuration is in `tasks/main.yml` ## Available variables The full list of variables (with default values) can be found in `defaults/main.yml`. + +## Munin plugins + +### dovecot_stats_ + +Note : This is an Evolix patched version. + +This plugin can be installed only when installin a server, because it needs Dovevcot plugin stats (Dovecot 2.2) or old_stats (Dovecot 2.3), which previously were not activated by default. + +To skip this plugin installation, use "--skip-tags dovecot_stats_". + diff --git a/dovecot/files/munin_config b/dovecot/files/munin_config deleted file mode 100644 index 1a0553d8..00000000 --- a/dovecot/files/munin_config +++ /dev/null @@ -1,2 +0,0 @@ -[dovecot] -group adm diff --git a/dovecot/files/munin_plugin b/dovecot/files/munin_plugin deleted file mode 100755 index f12c2b04..00000000 --- a/dovecot/files/munin_plugin +++ /dev/null @@ -1,128 +0,0 @@ -#! /bin/bash -# -# Munin Plugin -# to count logins to your dovecot mailserver -# -# Created by Dominik Schulz -# http://developer.gauner.org/munin/ -# Contributions by: -# - Stephane Enten -# - Steve Schnepp -# - pcy (make 'Connected Users' DERIVE, check existence of logfile in autoconf) -# -# Parameters understood: -# -# config (required) -# autoconf (optional - used by munin-config) -# -# Config variables: -# -# logfile - Where to find the syslog file -# -# Add the following line to a file in /etc/munin/plugin-conf.d: -# env.logfile /var/log/your/logfile.log -# -# Magic markers (optional - used by munin-config and installation scripts): -# -#%# family=auto -#%# capabilities=autoconf - -###################### -# Configuration -###################### -EXPR_BIN=/usr/bin/expr -LOGFILE=${logfile:-/var/log/mail.log} -###################### - -if [ "$1" = "autoconf" ]; then - [ -f "$LOGFILE" ] && echo yes || echo "no (logfile $LOGFILE not found)" - exit 0 -fi - -if [ "$1" = "config" ]; then - echo 'graph_title Dovecot Logins' - echo 'graph_category mail' - echo 'graph_args --base 1000 -l 0' - echo 'graph_vlabel Login Counters' - - for t in Total TLS SSL IMAP POP3 - do - field=$(echo $t | tr '[:upper:]' '[:lower:]') - echo "login_$field.label $t Logins" - echo "login_$field.type DERIVE" - echo "login_$field.min 0" - done - - echo 'connected.label Connected Users' - echo "connected.type DERIVE" - - exit 0 -fi - -###################### -# Total Logins -###################### -echo -en "login_total.value " -VALUE=$(egrep -c '[dovecot]?.*Login' $LOGFILE) -if [ ! -z "$VALUE" ]; then - echo "$VALUE" -else - echo "0" -fi -echo -n -###################### -# Connected Users -###################### -DISCONNECTS=$(egrep -c '[dovecot]?.*Disconnected' $LOGFILE) -CONNECTS=$(egrep -c '[dovecot]?.*Login' $LOGFILE) -VALUE=$($EXPR_BIN $CONNECTS - $DISCONNECTS) -if [ -z "$VALUE" ] || [ "$VALUE" -lt 0 ]; then - VALUE=0 -fi -echo -en "connected.value " -echo $VALUE -echo -n -###################### -# TLS Logins -###################### -echo -en "login_tls.value " -VALUE=$(egrep -c '[dovecot]?.*Login.*TLS' $LOGFILE) -if [ ! -z "$VALUE" ]; then - echo "$VALUE" -else - echo "0" -fi -echo -n -###################### -# SSL Logins -###################### -echo -en "login_ssl.value " -VALUE=$(egrep -c '[dovecot]?.*Login.*SSL' $LOGFILE) -if [ ! -z "$VALUE" ]; then - echo "$VALUE" -else - echo "0" -fi -echo -n -###################### -# IMAP Logins -###################### -echo -en "login_imap.value " -VALUE=$(egrep -c '[dovecot]?.*imap.*Login' $LOGFILE) -if [ ! -z "$VALUE" ]; then - echo "$VALUE" -else - echo "0" -fi -echo -n -###################### -# POP3 Logins -###################### -echo -en "login_pop3.value " -VALUE=$(egrep -c '[dovecot]?.*pop3.*Login' $LOGFILE) -if [ ! -z "$VALUE" ]; then - echo "$VALUE" -else - echo "0" -fi -echo -n diff --git a/dovecot/files/munin_plugin_dovecot1 b/dovecot/files/munin_plugin_dovecot1 new file mode 100644 index 00000000..83f4d897 --- /dev/null +++ b/dovecot/files/munin_plugin_dovecot1 @@ -0,0 +1,242 @@ +#!/usr/bin/perl + +#%# family=auto +#%# capabilities=autoconf + +use Munin::Plugin; + +$pos = undef; +$connected = 0; +$connectedimap = 0; +$connectedpop3 = 0; +$connections = 0; +$connectionsimap = 0; +$connectionspop3 = 0; +$login = 0; +$pop3login = 0; +$imaplogin = 0; +$tls = 0; +$ssl = 0; +$aborted = 0; + +($dirname = $0) =~ s/[^\/]+$//; + +$dovelogfile = 0 ; + +$logfile = $ENV{'LOGFILE'} || '/var/log/mail.log'; + +if ( $logfile =~ /dovecot/ ) { + $dovelogfile = 1 ; +} + +# Use an overridden $PATH for all external programs if needed +$DOVEADM = "doveadm"; + +if ( $ARGV[0] and $ARGV[0] eq "autoconf" ) { + + if (! -x $DOVEADM) { + print "no (no doveadm)\n"; + exit(0); + } + + if (! -f $logfile) { + print "no (logfile $logfile does not exist)\n"; + exit(0); + } + + if (-r "$logfile") { + print "yes\n"; + exit 0; + } else { + print "no (logfile not readable)\n"; + } + exit 0; +} + +if (-f "$logfile.0") { + $rotlogfile = $logfile . ".0"; +} elsif (-f "$logfile.1") { + $rotlogfile = $logfile . ".1"; +} elsif (-f "$logfile.01") { + $rotlogfile = $logfile . ".01"; +} else { + $rotlogfile = $logfile . ".0"; +} + +if ( $ARGV[0] and $ARGV[0] eq "config" ) { + print "multigraph dovecot_connections\n"; + print "graph_title Dovecot connections\n"; + print "graph_args --base 1000 -l 0 --no-gridfit --slope-mode\n"; + print "graph_vlabel connections\n"; + print "graph_category mail\n"; + print "connections.label Connections open\n"; + print "connections.type GAUGE\n"; + print "connections.draw LINE1\n"; + print "connections.min 0\n"; + print "connectionsimap.label IMAP\n"; + print "connectionsimap.type GAUGE\n"; + print "connectionsimap.draw AREA\n"; + print "connectionsimap.min 0\n"; + print "connectionspop3.label POP3\n"; + print "connectionspop3.type GAUGE\n"; + print "connectionspop3.draw STACK\n"; + print "connectionspop3.min 0\n"; + + print "multigraph dovecot_connected\n"; + print "graph_title Dovecot connected users\n"; + print "graph_args --base 1000 -l 0 --no-gridfit --slope-mode\n"; + print "graph_vlabel connections\n"; + print "graph_category mail\n"; + print "connected.label Connected users\n"; + print "connected.type GAUGE\n"; + print "connected.draw LINE1\n"; + print "connected.min 0\n"; + print "connectedimap.label IMAP\n"; + print "connectedimap.type GAUGE\n"; + print "connectedimap.draw AREA\n"; + print "connectedimap.min 0\n"; + print "connectedpop3.label POP3\n"; + print "connectedpop3.type GAUGE\n"; + print "connectedpop3.draw STACK\n"; + print "connectedpop3.min 0\n"; + + print "multigraph dovecot_logins\n"; + print "graph_title Dovecot logins\n"; + print "graph_args --base 1000 -l 0 --no-gridfit --slope-mode\n"; + print "graph_vlabel logins/5 minute\n"; + print "graph_category mail\n"; + print "login.label Logins\n"; + print "login.type GAUGE\n"; + print "login.draw LINE1\n"; + print "login.min 0\n"; + print "imaplogin.label IMAP logins\n"; + print "imaplogin.type GAUGE\n"; + print "imaplogin.draw LINE1\n"; + print "imaplogin.min 0\n"; + print "pop3login.label POP3 logins\n"; + print "pop3login.type GAUGE\n"; + print "pop3login.draw LINE1\n"; + print "pop3login.min 0\n"; + print "tls.label TLS\n"; + print "tls.type GAUGE\n"; + print "tls.draw LINE1\n"; + print "tls.min 0\n"; + print "ssl.label SSL\n"; + print "ssl.type GAUGE\n"; + print "ssl.draw LINE1\n"; + print "ssl.min 0\n"; + print "aborted.label Aborted logins\n"; + print "aborted.type GAUGE\n"; + print "aborted.draw LINE1\n"; + print "aborted.min 0\n"; + exit 0; +} + +if (! -f $logfile and ! -f $rotlogfile) { + print "multigraph dovecot_connections\n"; + print "connections.value U"; + print "connectionsimap.value U"; + print "connectionspop3.value U"; + print "multigraph dovecot_connected\n"; + print "connected.value U\n"; + print "connectedimap.value U\n"; + print "connectedpop3.value U\n"; + print "multigraph dovecot_logins\n"; + print "login.value U\n"; + print "pop3login.value U\n"; + print "imaplogin.value U\n"; + print "tls.value U\n"; + print "ssl.value U\n"; + print "aborted.value U\n"; + + exit 0; +} + +# dit kan beter maar twee calls zijn toch nodig also we niet zelf aggegreren +# suggestie: doveadm who -1 | awk '{print $1" "$2" "$4}' | sort | uniq -c +$connectedimap = `$DOVEADM -f flow who | grep imap | wc -l`; +$connectedpop3 = `$DOVEADM -f flow who | grep pop3 | wc -l`; +$connectionsimap = `$DOVEADM -f flow who -1 | grep imap | wc -l`; +$connectionspop3 = `$DOVEADM -f flow who -1 | grep pop3 | wc -l`; + +#trim +$connectedimap =~ s/\s+$//; +$connectedpop3 =~ s/\s+$//; +$connectionsimap =~ s/\s+$//; +$connectionspop3 =~ s/\s+$//; + +$connected = $connectedimap + $connectedpop3; +$connections = $connectionsimap + $connectionspop3; + +my ($pos) = restore_state(); + +$startsize = (stat $logfile)[7]; + +if (!defined $pos) { + # Initial run. + $pos = $startsize; +} + +if ($startsize < $pos) { + # Log rotated + parseDovecotfile ($rotlogfile, $pos, (stat $rotlogfile)[7]); + $pos = 0; +} + +parseDovecotfile ($logfile, $pos, $startsize); +$pos = $startsize; + +save_state($pos); + +print "multigraph dovecot_connections\n"; +print "connections.value $connections\n"; +print "connectionsimap.value $connectionsimap\n"; +print "connectionspop3.value $connectionspop3\n"; +print "multigraph dovecot_connected\n"; +print "connected.value $connected\n"; +print "connectedimap.value $connectedimap\n"; +print "connectedpop3.value $connectedpop3\n"; +print "multigraph dovecot_logins\n"; +print "login.value $login\n"; +print "pop3login.value $pop3login\n"; +print "imaplogin.value $imaplogin\n"; +print "tls.value $tls\n"; +print "ssl.value $ssl\n"; +print "aborted.value $aborted\n"; + + +sub parseDovecotfile { + my ($fname, $start, $stop) = @_; + open (logf, $fname) or exit 3; + seek (logf, $start, 0) or exit 2; + + while (tell (logf) < $stop) { + my $line =; + chomp ($line); + + if ( $dovelogfile == 0 and $line !~ m/dovecot/) { next; } + else { + if ($line =~ m/Aborted/) { + $aborted++; + + } elsif ($line =~ m/Login:/) { + $login++; + + if ( $line =~ m/TLS/) { + $tls++; + } elsif ($line =~ m/SSL/) { + $ssl++; + } + + if ( $line =~ m/pop3-login:/) { + $pop3login++; + } elsif ($line =~ m/imap-login:/) { + $imaplogin++; + } + } + } + } + close(logf); +} + +# vim:syntax=perl diff --git a/dovecot/files/munin_plugin_dovecot_stats_ b/dovecot/files/munin_plugin_dovecot_stats_ new file mode 100644 index 00000000..6daf9bae --- /dev/null +++ b/dovecot/files/munin_plugin_dovecot_stats_ @@ -0,0 +1,158 @@ +#!/bin/bash +: <<=cut + +=head1 NAME + +dovecot_stats_ - Munin plugin to display statistics for the dovecot mail server + +=head1 CONFIGURATION + +This plugin must be run with permissions to run "doveadm". That usually means root, but to test, run the following as any user: + + doveadm who + +If you get a permission denied message, check the permissions on the socket mentioned in the error line. + +=head1 MAGIC MARKERS + + #%# family=contrib + #%# capability=autoconf suggest + +=head1 AUTHOR + +Paul Saunders + +=cut + +. $MUNIN_LIBDIR/plugins/plugin.sh +is_multigraph + +if [[ "$1" == "autoconf" ]]; then + if [[ -x /usr/bin/doveadm ]]; then + echo yes + else + echo no + fi + exit 0 +fi + +# Dovecot 2.3 changes the stas format, but we can still access the older version with "doveadm oldstats". +dovecot_version=$(/usr/sbin/dovecot --version | awk '{print $1}') + +verlte() { + [ "$1" = "$2" ] && return 1 || [ "$2" = "`echo -e "$1\n$2" | sort -V | head -n1`" ] +} + +verlt() { + [ "$1" = "$2" ] && return 1 || verlte $2 $1 +} + +# The stats command is "stats" unless the version is NOT less than 2.3, in which case it's "oldstats". +stats_command="stats" +verlt $dovecot_version 2.3 || stats_command="oldstats" + + +if [[ "$1" == "suggest" ]]; then + doveadm $stats_command dump domain|awk 'NR!=1 {print $1}' + exit 0 +fi + +domain=$(basename $0) +domain=${domain#dovecot_stats_} + +if [[ -z $domain ]]; then + exit 1 +fi + +if [[ "$1" == "config" ]]; then + cat <=12) + ansible.builtin.template: + src: elastic.sources.j2 + dest: /etc/apt/sources.list.d/elastic.sources + state: present + register: elastic_sources + when: ansible_distribution_major_version is version('12', '>=') + +- name: Update APT cache + ansible.builtin.apt: + update_cache: yes + when: elastic_sources is changed \ No newline at end of file diff --git a/elasticsearch/tasks/bootstrap_checks.yml b/elasticsearch/tasks/bootstrap_checks.yml index b1f79046..3626bd17 100644 --- a/elasticsearch/tasks/bootstrap_checks.yml +++ b/elasticsearch/tasks/bootstrap_checks.yml @@ -1,7 +1,8 @@ --- - name: Read maximum map count - command: "sysctl -n vm.max_map_count" + ansible.builtin.command: + cmd: "sysctl -n vm.max_map_count" register: max_map_count failed_when: False changed_when: False @@ -9,16 +10,16 @@ - config - name: Maximum map count check - sysctl: + ansible.posix.sysctl: name: vm.max_map_count - value: 262144 + value: "262144" sysctl_file: /etc/sysctl.d/elasticsearch.conf when: max_map_count | int < 262144 tags: - config - name: bootstrap.memory_lock - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/elasticsearch.yml line: "bootstrap.memory_lock: true" regexp: "^bootstrap.memory_lock:" @@ -27,12 +28,12 @@ - config - name: Create a system config directory for systemd overrides - file: + ansible.builtin.file: path: /etc/systemd/system/elasticsearch.service.d state: directory - name: Override memory config in systemd unit - ini_file: + community.general.ini_file: dest: /etc/systemd/system/elasticsearch.service.d/elasticsearch.conf section: Service option: "LimitMEMLOCK" diff --git a/elasticsearch/tasks/configuration.yml b/elasticsearch/tasks/configuration.yml index 83dd130a..223eff90 100644 --- a/elasticsearch/tasks/configuration.yml +++ b/elasticsearch/tasks/configuration.yml @@ -1,7 +1,7 @@ --- - name: Configure cluster name - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/elasticsearch.yml line: "cluster.name: {{ elasticsearch_cluster_name }}" regexp: "^cluster.name:" @@ -11,7 +11,7 @@ - config - name: Configure node name - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/elasticsearch.yml line: "node.name: {{ elasticsearch_node_name }}" regexp: "^node.name:" @@ -20,9 +20,9 @@ - config - name: Configure network host - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/elasticsearch.yml - line: "network.host: {{ elasticsearch_network_host }}" + line: "network.host: {{ elasticsearch_network_host }}" regexp: "^network.host:" insertafter: "^# *network.host:" when: elasticsearch_network_host | default("", True) | length > 0 @@ -30,36 +30,97 @@ - config - name: Configure network publish_host - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/elasticsearch.yml - line: "network.publish_host: {{ elasticsearch_network_publish_host }}" + line: "network.publish_host: {{ elasticsearch_network_publish_host }}" regexp: "^network.publish_host:" insertafter: "^network.host:" when: elasticsearch_network_publish_host | default("", True) | length > 0 tags: - config -- name: Configure http publish_host - lineinfile: +- name: Configure network port + ansible.builtin.lineinfile: dest: /etc/elasticsearch/elasticsearch.yml - line: "http.publish_host: {{ elasticsearch_http_publish_host }}" + line: "network.port: {{ elasticsearch_network_port }}" + regexp: "^network.port:" + insertafter: "^network.host:" + when: elasticsearch_network_port | default("", True) | length > 0 + tags: + - config + +- name: Configure http host + ansible.builtin.lineinfile: + dest: /etc/elasticsearch/elasticsearch.yml + line: "http.host: {{ elasticsearch_http_host }}" + regexp: "^http.host:" + insertafter: "^# *http.host:" + when: elasticsearch_http_host | default("", True) | length > 0 + tags: + - config + +- name: Configure http publish_host + ansible.builtin.lineinfile: + dest: /etc/elasticsearch/elasticsearch.yml + line: "http.publish_host: {{ elasticsearch_http_publish_host }}" regexp: "^http.publish_host:" insertafter: "^http.port:" when: elasticsearch_http_publish_host | default("", True) | length > 0 tags: - config +- name: Configure http port + ansible.builtin.lineinfile: + dest: /etc/elasticsearch/elasticsearch.yml + line: "http.port: {{ elasticsearch_http_port }}" + regexp: "^http.port:" + insertafter: "^http.host:" + when: elasticsearch_http_port | default("", True) | length > 0 + tags: + - config + +- name: Configure transport host + ansible.builtin.lineinfile: + dest: /etc/elasticsearch/elasticsearch.yml + line: "transport.host: {{ elasticsearch_transport_host }}" + regexp: "^transport.host:" + insertafter: "^# *transport.host:" + when: elasticsearch_transport_host | default("", True) | length > 0 + tags: + - config + +- name: Configure transport publish_host + ansible.builtin.lineinfile: + dest: /etc/elasticsearch/elasticsearch.yml + line: "transport.publish_host: {{ elasticsearch_transport_publish_host }}" + regexp: "^transport.publish_host:" + insertafter: "^transport.host:" + when: elasticsearch_transport_publish_host | default("", True) | length > 0 + tags: + - config + +- name: Configure transport port + ansible.builtin.lineinfile: + dest: /etc/elasticsearch/elasticsearch.yml + line: "transport.port: {{ elasticsearch_transport_port }}" + regexp: "^transport.port:" + insertafter: "^transport.host:" + when: elasticsearch_transport_port | default("", True) | length > 0 + tags: + - config + - name: Configure discovery seed hosts - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/elasticsearch.yml line: "discovery.seed_hosts: {{ elasticsearch_discovery_seed_hosts | to_yaml(default_flow_style=True) }}" regexp: "^discovery.seed_hosts:" + insertafter: "^# *discovery.seed_hosts:" when: elasticsearch_discovery_seed_hosts | default([], True) | length > 0 tags: - config - name: Configure empty discovery seed hosts - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/elasticsearch.yml regexp: "^discovery.seed_hosts:" state: absent @@ -68,7 +129,7 @@ - config - name: Configure initial master nodes - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/elasticsearch.yml line: "cluster.initial_master_nodes: {{ elasticsearch_cluster_initial_master_nodes | to_yaml(default_flow_style=True) }}" regexp: "^cluster.initial_master_nodes:" @@ -77,7 +138,7 @@ - config - name: Configure empty initial master nodes - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/elasticsearch.yml regexp: "^cluster.initial_master_nodes:" state: absent @@ -86,7 +147,7 @@ - config - name: Configure RESTART_ON_UPGRADE - lineinfile: + ansible.builtin.lineinfile: dest: /etc/default/elasticsearch line: "RESTART_ON_UPGRADE={{ elasticsearch_restart_on_upgrade | bool | ternary('true','false') }}" regexp: "^RESTART_ON_UPGRADE=" @@ -95,31 +156,39 @@ - config - name: JVM Heap size (min) is set - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/jvm.options.d/evolinux.options regexp: "^-Xms" line: "-Xms{{ elasticsearch_jvm_xms }}" create: yes owner: root group: elasticsearch - mode: 0640 + mode: "0640" tags: - config - name: JVM Heap size (max) is set - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/jvm.options.d/evolinux.options regexp: "^-Xmx" line: "-Xmx{{ elasticsearch_jvm_xmx }}" create: yes owner: root group: elasticsearch - mode: 0640 + mode: "0640" + tags: + - config + +- name: Disable garbage collector logs + ansible.builtin.replace: + dest: /etc/elasticsearch/jvm.options + regexp: '^([^#]*-Xlog:gc.+)' + replace: '#\1' tags: - config - name: Configure cluster members - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/elasticsearch.yml line: "discovery.zen.ping.unicast.hosts: {{ elasticsearch_cluster_members }}" regexp: "^discovery.zen.ping.unicast.hosts:" @@ -129,7 +198,7 @@ - config - name: Configure minimum master nodes - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/elasticsearch.yml line: "discovery.zen.minimum_master_nodes: {{ elasticsearch_minimum_master_nodes }}" regexp: "^discovery.zen.minimum_master_nodes:" diff --git a/elasticsearch/tasks/curator.yml b/elasticsearch/tasks/curator.yml index c7c44259..4cf7c9d5 100644 --- a/elasticsearch/tasks/curator.yml +++ b/elasticsearch/tasks/curator.yml @@ -1,11 +1,11 @@ --- - name: Use the correct debian repository - set_fact: + ansible.builtin.set_fact: curator_debian_repository: '{% if ansible_distribution_release == "jessie" %}debian{% else %}debian9{% endif %}' - name: Curator sources list is available - apt_repository: + ansible.builtin.apt_repository: repo: "deb https://packages.elastic.co/curator/5/{{ curator_debian_repository }} stable main" filename: curator update_cache: yes @@ -15,7 +15,7 @@ - packages - name: Curator package is installed - apt: + ansible.builtin.apt: name: elasticsearch-curator state: present tags: diff --git a/elasticsearch/tasks/datadir.yml b/elasticsearch/tasks/datadir.yml index c0c20f05..c442ae42 100644 --- a/elasticsearch/tasks/datadir.yml +++ b/elasticsearch/tasks/datadir.yml @@ -3,14 +3,14 @@ - name: Set real datadir value when customized block: - name: "Is custom datadir present ?" - stat: + ansible.builtin.stat: path: "{{ elasticsearch_custom_datadir }}" register: elasticsearch_custom_datadir_test check_mode: no - name: "read the real datadir" - command: readlink -f /var/lib/elasticsearch - changed_when: false + ansible.builtin.command: readlink -f /var/lib/elasticsearch + changed_when: False register: elasticsearch_current_real_datadir_test check_mode: no tags: @@ -22,23 +22,24 @@ - name: Datadir is moved to custom path block: - name: elasticsearch is stopped - service: + ansible.builtin.service: name: elasticsearch state: stopped - name: Move elasticsearch datadir to custom datadir - command: mv {{ elasticsearch_current_real_datadir_test.stdout }} {{ elasticsearch_custom_datadir }} + ansible.builtin.command: + cmd: mv {{ elasticsearch_current_real_datadir_test.stdout }} {{ elasticsearch_custom_datadir }} args: creates: "{{ elasticsearch_custom_datadir }}" - name: Symlink {{ elasticsearch_custom_datadir }} to /var/lib/elasticsearch - file: + ansible.builtin.file: src: "{{ elasticsearch_custom_datadir }}" dest: '/var/lib/elasticsearch' state: link - name: elasticsearch is started - service: + ansible.builtin.service: name: elasticsearch state: started tags: diff --git a/elasticsearch/tasks/logs.yml b/elasticsearch/tasks/logs.yml index 01829dc9..0569ef07 100644 --- a/elasticsearch/tasks/logs.yml +++ b/elasticsearch/tasks/logs.yml @@ -1,8 +1,8 @@ --- - name: Check if cron is installed - shell: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'" - args: + ansible.builtin.shell: + cmd: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'" executable: /bin/bash check_mode: no failed_when: False @@ -10,10 +10,11 @@ register: is_cron_installed - name: "log rotation script" - template: + ansible.builtin.template: src: rotate_elasticsearch_logs.j2 dest: /etc/cron.daily/rotate_elasticsearch_logs owner: root group: root mode: "0750" when: is_cron_installed.rc == 0 + diff --git a/elasticsearch/tasks/main.yml b/elasticsearch/tasks/main.yml index 6f5ccc8c..132089c7 100644 --- a/elasticsearch/tasks/main.yml +++ b/elasticsearch/tasks/main.yml @@ -1,21 +1,21 @@ --- -- include: packages.yml +- ansible.builtin.include: packages.yml -- include: configuration.yml +- ansible.builtin.include: configuration.yml -- include: bootstrap_checks.yml +- ansible.builtin.include: bootstrap_checks.yml -- include: tmpdir.yml +- ansible.builtin.include: tmpdir.yml -- include: datadir.yml +- ansible.builtin.include: datadir.yml -- include: logs.yml +- ansible.builtin.include: logs.yml -- include: additional_scripts.yml +- ansible.builtin.include: additional_scripts.yml -- include: plugin_head.yml +- ansible.builtin.include: plugin_head.yml when: elasticsearch_plugin_head | bool -- include: curator.yml +- ansible.builtin.include: curator.yml when: elasticsearch_curator | bool diff --git a/elasticsearch/tasks/packages.yml b/elasticsearch/tasks/packages.yml index 826fee1e..2d5ca6b8 100644 --- a/elasticsearch/tasks/packages.yml +++ b/elasticsearch/tasks/packages.yml @@ -1,63 +1,24 @@ --- -- name: APT https transport is enabled - apt: - name: apt-transport-https - state: present - tags: - - elasticsearch - - packages +- name: APT sources + ansible.builtin.include_tasks: apt_sources.yml + args: + apply: + tags: + - elasticsearch + - packages -- name: Look for legacy apt keyring - stat: - path: /etc/apt/trusted.gpg - register: _trusted_gpg_keyring - tags: - - elasticsearch - - packages - -- name: Elastic embedded GPG key is absent - apt_key: - id: "D88E42B4" - keyring: /etc/apt/trusted.gpg - state: absent - when: _trusted_gpg_keyring.stat.exists - tags: - - elasticsearch - - packages - -- name: Elastic GPG key is installed - copy: - src: elastic.asc - dest: /etc/apt/trusted.gpg.d/elastic.asc - force: yes - mode: "0644" - owner: root - group: root - tags: - - elasticsearch - - packages - -- name: Elastic sources list is available - apt_repository: - repo: "deb https://artifacts.elastic.co/packages/{{ elastic_stack_version | mandatory }}/apt stable main" - filename: elastic +- name: Elasticsearch is installed + ansible.builtin.apt: + name: elasticsearch state: present update_cache: yes tags: - elasticsearch - packages -- name: Elasticsearch is installed - apt: - name: elasticsearch - state: present - tags: - - elasticsearch - - packages - - name: Elasticsearch service is enabled - service: + ansible.builtin.systemd: name: elasticsearch enabled: yes tags: diff --git a/elasticsearch/tasks/plugin_head.yml b/elasticsearch/tasks/plugin_head.yml index 2f7cae39..24a34643 100644 --- a/elasticsearch/tasks/plugin_head.yml +++ b/elasticsearch/tasks/plugin_head.yml @@ -1,7 +1,7 @@ --- - name: "User {{ elasticsearch_plugin_head_owner }} is present" - user: + ansible.builtin.user: name: "{{ elasticsearch_plugin_head_owner }}" home: "{{ elasticsearch_plugin_head_home }}" createhome: yes @@ -11,7 +11,7 @@ - name: Head plugin is installed block: - name: Head repository is checked-out - git: + ansible.builtin.git: repo: "https://github.com/mobz/elasticsearch-head.git" dest: "{{ elasticsearch_plugin_head_clone_dir }}" clone: yes @@ -19,12 +19,12 @@ - packages - name: Create tmpdir - file: + ansible.builtin.file: dest: "{{ elasticsearch_plugin_head_tmp_dir }}" state: directory - name: NPM packages for head are installed - npm: + community.general.npm: path: "{{ elasticsearch_plugin_head_clone_dir }}" tags: - packages @@ -32,10 +32,10 @@ environment: TMPDIR: "{{ elasticsearch_plugin_head_tmp_dir }}" become_user: "{{ elasticsearch_plugin_head_owner }}" - become: yes + become: true - name: Elasticsearch HTTP/CORS are enabled - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/elasticsearch.yml line: "http.cors.enabled: true" regexp: "^http.cors.enabled:" @@ -46,7 +46,7 @@ - elasticsearch - name: Elasticsearch HTTP/CORS accepts all origins - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/elasticsearch.yml line: "http.cors.allow-origin: \"*\"" regexp: "^http.cors.allow-origin:" @@ -57,7 +57,7 @@ - elasticsearch - name: Install systemd unit - template: + ansible.builtin.template: src: elasticsearch-head.service.j2 dest: /etc/systemd/system/elasticsearch-head.service tags: @@ -65,7 +65,7 @@ - systemd - name: Enable systemd unit - systemd: + ansible.builtin.systemd: name: elasticsearch-head daemon_reload: yes enabled: yes diff --git a/elasticsearch/tasks/tmpdir.yml b/elasticsearch/tasks/tmpdir.yml index c9ad3c19..e3601fb8 100644 --- a/elasticsearch/tasks/tmpdir.yml +++ b/elasticsearch/tasks/tmpdir.yml @@ -1,7 +1,8 @@ --- - name: Check if /tmp is noexec - shell: "cat /etc/fstab | grep -E \" +/tmp\" | grep noexec" + ansible.builtin.shell: + cmd: "cat /etc/fstab | grep -E \" +/tmp\" | grep noexec" register: fstab_tmp_noexec failed_when: False changed_when: False @@ -9,13 +10,13 @@ - name: Tmpdir is moved to custom path block: - - set_fact: + - ansible.builtin.set_fact: _elasticsearch_custom_tmpdir: "{{ elasticsearch_custom_tmpdir | default(elasticsearch_default_tmpdir, True) | mandatory }}" tags: - elasticsearch - name: "Create {{ _elasticsearch_custom_tmpdir }}" - file: + ansible.builtin.file: path: "{{ _elasticsearch_custom_tmpdir }}" owner: elasticsearch group: elasticsearch @@ -25,14 +26,14 @@ - elasticsearch - name: change JVM tmpdir (< 6.x) - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/jvm.options.d/evolinux.options line: "-Djava.io.tmpdir={{ _elasticsearch_custom_tmpdir }}" regexp: "^-Djava.io.tmpdir=" create: yes owner: root group: elasticsearch - mode: 0640 + mode: "0640" notify: - restart elasticsearch tags: @@ -40,7 +41,7 @@ when: elastic_stack_version is version('6', '<') - name: check if ES_TMPDIR is available (>= 6.x) - lineinfile: + ansible.builtin.lineinfile: dest: /etc/default/elasticsearch line: "ES_TMPDIR={{ _elasticsearch_custom_tmpdir }}" regexp: "^ES_TMPDIR=" @@ -53,7 +54,7 @@ # Note : Should not do any changes as -Djava.io.tmpdir=${ES_TMPDIR} is already here in the default config. - name: change JVM tmpdir (>= 6.x) - lineinfile: + ansible.builtin.lineinfile: dest: /etc/elasticsearch/jvm.options line: "-Djava.io.tmpdir=${ES_TMPDIR}" regexp: "^-Djava.io.tmpdir=" diff --git a/elasticsearch/templates/elastic.sources.j2 b/elasticsearch/templates/elastic.sources.j2 new file mode 100644 index 00000000..93df736d --- /dev/null +++ b/elasticsearch/templates/elastic.sources.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +Types: deb +URIs: https://artifacts.elastic.co/packages/{{ elastic_stack_version | mandatory }}/apt +Suites: stable +Components: main +Signed-by: {{ apt_keyring_dir }}/elastic.asc +Enabled: yes \ No newline at end of file diff --git a/elasticsearch/templates/rotate_elasticsearch_logs.j2 b/elasticsearch/templates/rotate_elasticsearch_logs.j2 index 849a9ca1..981ca433 100644 --- a/elasticsearch/templates/rotate_elasticsearch_logs.j2 +++ b/elasticsearch/templates/rotate_elasticsearch_logs.j2 @@ -8,7 +8,6 @@ MAX_AGE={{ elasticsearch_log_rotate_days | mandatory }} # Compress logs find ${LOG_DIR} -type f -user ${USER} -name "*.log.????-??-??" -exec gzip --best {} \; find ${LOG_DIR} -type f -user ${USER} -name "*-????-??-??.log" -exec gzip --best {} \; -find ${LOG_DIR} -type f -user ${USER} -name "*.log.??" -not -name "*.gz" -exec gzip --best {} \; # Delete old logs -find ${LOG_DIR} -type f -user ${USER} -name "*gz" -ctime +${MAX_AGE} -delete \ No newline at end of file +find ${LOG_DIR} -type f -user ${USER} -name "*gz" -ctime +${MAX_AGE} -delete 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..55c02934 100644 --- a/etc-git/tasks/commit.yml +++ b/etc-git/tasks/commit.yml @@ -1,52 +1,10 @@ --- -# /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" + ansible.builtin.command: + cmd: "/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..1c3d0d67 --- /dev/null +++ b/etc-git/tasks/lxc_commit.yml @@ -0,0 +1,35 @@ +--- +- name: "Assert that we have been called with `container` defined" + ansible.builtin.assert: + that: + - container is defined + +- name: "Define path to /etc in {{ container }} container" + ansible.builtin.set_fact: + container_etc: "{{ ('/var/lib/lxc', container, 'rootfs/etc') | path_join }}" + +- name: "Check if /etc is a git repository in {{ container }}" + ansible.builtin.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 }}" + ansible.builtin.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 34c4a1ca..bae705d3 100644 --- a/etc-git/tasks/main.yml +++ b/etc-git/tasks/main.yml @@ -1,116 +1,21 @@ --- - name: Git is installed (Debian) - apt: + ansible.builtin.apt: name: git state: present tags: - etc-git - when: + 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 + ansible.builtin.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 + ansible.builtin.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..d9d64ad6 --- /dev/null +++ b/etc-git/tasks/repositories.yml @@ -0,0 +1,37 @@ +--- + +- ansible.builtin.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 + ansible.builtin.stat: + path: /usr/share/scripts + register: _usr_share_scripts + tags: + - etc-git + +- ansible.builtin.include_role: + name: evolix/remount-usr + when: + - _usr_share_scripts.stat.isdir + +- ansible.builtin.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/repository.yml b/etc-git/tasks/repository.yml index 80987da2..426eab41 100644 --- a/etc-git/tasks/repository.yml +++ b/etc-git/tasks/repository.yml @@ -1,21 +1,21 @@ --- -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - when: repository_path is search ("/usr") + when: repository_path is search("/usr") - name: "{{ repository_path }} is versioned with git" - command: "git init ." + ansible.builtin.command: + cmd: "git init ." args: chdir: "{{ repository_path }}" creates: "{{ repository_path }}/.git/" - warn: no register: git_init tags: - etc-git - name: Git user.email is configured - git_config: + community.general.git_config: name: user.email repo: "{{ repository_path }}" scope: local @@ -24,7 +24,7 @@ - etc-git - name: "{{ repository_path }}/.git is restricted to root" - file: + ansible.builtin.file: path: "{{ repository_path }}/.git" owner: root mode: "0700" @@ -33,17 +33,17 @@ - etc-git - name: "{{ repository_path }}/.gitignore is present" - copy: + ansible.builtin.copy: src: gitignore dest: "{{ repository_path }}/.gitignore" owner: root mode: "0600" - force: no + force: false tags: - etc-git - name: "Some entries MUST be in the {{ repository_path }}/.gitignore file" - lineinfile: + ansible.builtin.lineinfile: dest: "{{ repository_path }}/.gitignore" line: "{{ item }}" loop: "{{ gitignore_items | default([]) }}" @@ -51,10 +51,10 @@ - etc-git - name: "does {{ repository_path }}/ have any commit?" - command: "git log" + ansible.builtin.command: + cmd: "git log" args: chdir: "{{ repository_path }}" - warn: no changed_when: False failed_when: False register: git_log @@ -63,10 +63,10 @@ - etc-git - name: initial commit is present? - shell: "git add -A . && git commit -m \"Initial commit via Ansible\"" + ansible.builtin.shell: + cmd: "git add -A . && git commit -m \"Initial commit via Ansible\"" args: chdir: "{{ repository_path }}" - warn: no register: git_commit when: git_log.rc != 0 or (git_init is defined and git_init is changed) tags: diff --git a/etc-git/tasks/utils.yml b/etc-git/tasks/utils.yml new file mode 100644 index 00000000..e33589b3 --- /dev/null +++ b/etc-git/tasks/utils.yml @@ -0,0 +1,93 @@ +--- + +- ansible.builtin.include_role: + name: evolix/remount-usr + tags: + - etc-git + +- name: "evocommit script is installed" + ansible.builtin.copy: + src: evocommit + dest: /usr/local/bin/evocommit + mode: "0755" + force: true + tags: + - etc-git + +- name: "ansible-commit script is installed" + ansible.builtin.copy: + src: ansible-commit + dest: /usr/local/bin/ansible-commit + mode: "0755" + force: true + tags: + - etc-git + +- name: "etc-git-optimize script is installed" + ansible.builtin.copy: + src: etc-git-optimize + dest: /usr/share/scripts/etc-git-optimize + mode: "0755" + force: true + tags: + - etc-git + +- name: "etc-git-status script is installed" + ansible.builtin.copy: + src: etc-git-status + dest: /usr/share/scripts/etc-git-status + mode: "0755" + force: true + tags: + - etc-git + +- name: Check if cron is installed + ansible.builtin.shell: + cmd: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'" + 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 + ansible.builtin.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 + ansible.builtin.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 + ansible.builtin.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 + ansible.builtin.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/evoacme/handlers/main.yml b/evoacme/handlers/main.yml index 1ea11783..b188bfe7 100644 --- a/evoacme/handlers/main.yml +++ b/evoacme/handlers/main.yml @@ -1,25 +1,27 @@ - name: newaliases - command: newaliases + ansible.builtin.command: + cmd: newaliases - name: Test Apache conf - command: apache2ctl -t + ansible.builtin.command: + cmd: apache2ctl -t notify: "Reload Apache conf" - name: reload apache2 - service: + ansible.builtin.service: name: apache2 state: reloaded - name: apt update - apt: + ansible.builtin.apt: update_cache: yes - name: reload squid3 - service: + ansible.builtin.service: name: squid3 state: reloaded - name: reload squid - service: + ansible.builtin.service: name: squid state: reloaded diff --git a/evoacme/tasks/certbot.yml b/evoacme/tasks/certbot.yml index 26327569..bc844393 100644 --- a/evoacme/tasks/certbot.yml +++ b/evoacme/tasks/certbot.yml @@ -1,27 +1,29 @@ --- - name: Do no install certbot crontab - set_fact: + ansible.builtin.set_fact: certbot_custom_crontab: False -- include_role: +- ansible.builtin.include_role: name: evolix/certbot -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - name: Disable /etc/cron.d/certbot - command: mv -f /etc/cron.d/certbot /etc/cron.d/certbot.disabled + ansible.builtin.command: + cmd: mv -f /etc/cron.d/certbot /etc/cron.d/certbot.disabled args: removes: /etc/cron.d/certbot - name: Disable /etc/cron.daily/certbot - command: mv -f /etc/cron.daily/certbot /etc/cron.daily/certbot.disabled + ansible.builtin.command: + cmd: mv -f /etc/cron.daily/certbot /etc/cron.daily/certbot.disabled args: removes: /etc/cron.daily/certbot - name: Install evoacme custom cron - copy: + ansible.builtin.copy: src: evoacme.cron dest: /etc/cron.daily/evoacme mode: "0755" diff --git a/evoacme/tasks/conf.yml b/evoacme/tasks/conf.yml index 402fbdcf..125feb32 100644 --- a/evoacme/tasks/conf.yml +++ b/evoacme/tasks/conf.yml @@ -1,5 +1,5 @@ --- -- ini_file: +- community.general.ini_file: dest: "{{ evoacme_crt_dir }}/openssl.cnf" section: 'req' option: "{{ item.name }}" @@ -11,7 +11,7 @@ - { name: 'prompt', var: 'no' } - name: Update openssl conf - ini_file: + community.general.ini_file: dest: "{{ evoacme_crt_dir }}/openssl.cnf" section: 'req_dn' option: "{{ item.name }}" @@ -25,7 +25,7 @@ - { name: 'emailAddress', var: "{{ evoacme_ssl_email }}" } - name: Copy new evoacme conf - template: + ansible.builtin.template: src: templates/evoacme.conf.j2 dest: /etc/default/evoacme owner: root diff --git a/evoacme/tasks/evoacme_hook.yml b/evoacme/tasks/evoacme_hook.yml index 2951fa00..14963944 100644 --- a/evoacme/tasks/evoacme_hook.yml +++ b/evoacme/tasks/evoacme_hook.yml @@ -1,18 +1,19 @@ --- - name: "Create {{ hook_name }} hook directory" - file: + ansible.builtin.file: dest: "{{ evoacme_hooks_dir }}" state: directory - name: "Search for {{ hook_name }} hook" - command: "find {{ evoacme_hooks_dir }} -type f \\( -name '{{ hook_name }}' -o -name '{{ hook_name }}.*' \\)" + ansible.builtin.command: + cmd: "find {{ evoacme_hooks_dir }} -type f \\( -name '{{ hook_name }}' -o -name '{{ hook_name }}.*' \\)" check_mode: no changed_when: False register: _find_hook - name: "Copy {{ hook_name }} hook if missing" - copy: + ansible.builtin.copy: src: "hooks/{{ hook_name }}" dest: "{{ evoacme_hooks_dir }}/{{ hook_name }}" mode: "0750" diff --git a/evoacme/tasks/main.yml b/evoacme/tasks/main.yml index 1cc84c5d..29e3e89f 100644 --- a/evoacme/tasks/main.yml +++ b/evoacme/tasks/main.yml @@ -1,16 +1,16 @@ --- - name: Verify Debian version - assert: + ansible.builtin.assert: that: - ansible_distribution == "Debian" - ansible_distribution_major_version is version('9', '>=') msg: only compatible with Debian >= 9 when: not (evoacme_disable_debian_check | bool) -- include: certbot.yml +- ansible.builtin.include: certbot.yml -- include: permissions.yml +- ansible.builtin.include: permissions.yml # Enable this task if you want to deploy hooks # - include: evoacme_hook.yml @@ -18,6 +18,6 @@ # hook_name: "{{ item }}" # loop: [] -- include: conf.yml +- ansible.builtin.include: conf.yml -- include: scripts.yml +- ansible.builtin.include: scripts.yml diff --git a/evoacme/tasks/permissions.yml b/evoacme/tasks/permissions.yml index 69bcbe12..4d10ff7e 100644 --- a/evoacme/tasks/permissions.yml +++ b/evoacme/tasks/permissions.yml @@ -1,7 +1,7 @@ --- - name: Fix crt directory permissions - file: + ansible.builtin.file: path: "{{ evoacme_crt_dir }}" mode: "0755" owner: root @@ -9,7 +9,7 @@ state: directory - name: "Fix hooks directory permissions" - file: + ansible.builtin.file: path: "{{ evoacme_hooks_dir }}" mode: "0700" owner: root @@ -17,7 +17,7 @@ state: directory - name: Fix log directory permissions - file: + ansible.builtin.file: path: "{{ evoacme_log_dir }}" mode: "0755" owner: root @@ -25,7 +25,7 @@ state: directory - name: Fix challenge directory permissions - file: + ansible.builtin.file: path: "{{ evoacme_acme_dir }}" mode: "0755" owner: root diff --git a/evoacme/tasks/scripts.yml b/evoacme/tasks/scripts.yml index 89aacff8..e70e990f 100644 --- a/evoacme/tasks/scripts.yml +++ b/evoacme/tasks/scripts.yml @@ -1,10 +1,10 @@ --- -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - name: Create CSR dir - file: + ansible.builtin.file: path: "{{ evoacme_csr_dir }}" state: directory owner: root @@ -12,7 +12,7 @@ mode: "0755" - name: Copy make-csr.sh script - copy: + ansible.builtin.copy: src: make-csr.sh dest: /usr/local/sbin/make-csr owner: root @@ -20,7 +20,7 @@ mode: "0755" - name: Copy vhost-domains.sh script - copy: + ansible.builtin.copy: src: vhost-domains.sh dest: /usr/local/sbin/vhost-domains owner: root @@ -28,7 +28,7 @@ mode: "0755" - name: Copy evoacme script - copy: + ansible.builtin.copy: src: evoacme.sh dest: /usr/local/sbin/evoacme owner: root @@ -36,7 +36,7 @@ mode: "0755" - name: Delete scripts in old location - file: + ansible.builtin.file: path: "/usr/local/bin/{{ item }}" state: absent loop: diff --git a/evoacme/templates/evoacme.conf.j2 b/evoacme/templates/evoacme.conf.j2 index 27405f56..a42e0782 100644 --- a/evoacme/templates/evoacme.conf.j2 +++ b/evoacme/templates/evoacme.conf.j2 @@ -1,9 +1,9 @@ ### File generated by Ansible ### -SSL_KEY_DIR=${SSL_KEY_DIR:-{{ evoacme_ssl_key_dir }}} -ACME_DIR=${ACME_DIR:-{{ evoacme_acme_dir }}} -CSR_DIR=${CSR_DIR:-{{ evoacme_csr_dir }}} -CRT_DIR=${CRT_DIR:-{{ evoacme_crt_dir }}} +SSL_KEY_DIR=${SSL_KEY_DIR:-"{{ evoacme_ssl_key_dir }}"} +ACME_DIR=${ACME_DIR:-"{{ evoacme_acme_dir }}"} +CSR_DIR=${CSR_DIR:-"{{ evoacme_csr_dir }}"} +CRT_DIR=${CRT_DIR:-"{{ evoacme_crt_dir }}"} HOOKS_DIR=${HOOKS_DIR:-"{{ evoacme_hooks_dir }}"} -LOG_DIR=${LOG_DIR:-{{ evoacme_log_dir }}} -SSL_MINDAY=${SSL_MINDAY:-{{ evoacme_ssl_minday }}} +LOG_DIR=${LOG_DIR:-"{{ evoacme_log_dir }}"} +SSL_MINDAY=${SSL_MINDAY:-"{{ evoacme_ssl_minday }}"} diff --git a/evoacme/tests/vagrant.yml b/evoacme/tests/vagrant.yml index 9eb9077d..83466e63 100644 --- a/evoacme/tests/vagrant.yml +++ b/evoacme/tests/vagrant.yml @@ -1,6 +1,6 @@ - hosts: default gather_facts: yes - become: yes + become: true roles: # - squid diff --git a/evobackup-client/handlers/main.yml b/evobackup-client/handlers/main.yml index fc1b7739..f7d98aa9 100644 --- a/evobackup-client/handlers/main.yml +++ b/evobackup-client/handlers/main.yml @@ -1,16 +1,20 @@ --- - name: restart minifirewall - command: /etc/init.d/minifirewall restart + ansible.builtin.command: + cmd: /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 }}" + ansible.builtin.command: + cmd: "bkctld restart {{ evolinux_hostname }}" delegate_to: "{{ evobackup_client__hosts[0].ip }}" - name: 'jail updated' - command: "bkctld restart {{ evolinux_hostname }}" + ansible.builtin.command: + cmd: "bkctld restart {{ evolinux_hostname }}" # - "bkctld sync {{ evolinux_hostname }}" delegate_to: "{{ evobackup_client__hosts[0].ip }}" when: evobackup_client__hosts | length > 1 diff --git a/evobackup-client/tasks/jail.yml b/evobackup-client/tasks/jail.yml index fbb6080c..5eb0c36e 100644 --- a/evobackup-client/tasks/jail.yml +++ b/evobackup-client/tasks/jail.yml @@ -1,7 +1,8 @@ --- - name: 'create jail' - command: "bkctld init {{ evolinux_hostname }}" + ansible.builtin.command: + cmd: "bkctld init {{ evolinux_hostname }}" args: creates: "/backup/jails/{{ evolinux_hostname }}/" become: true @@ -15,7 +16,8 @@ # temp fix for bkctld 2.x because the ip and key command return 1 # if the jail is not started, see https://gitea.evolix.org/evolix/evobackup/issues/31 - name: 'start jail' - command: "bkctld restart {{ evolinux_hostname }}" + ansible.builtin.command: + cmd: "bkctld restart {{ evolinux_hostname }}" become: true delegate_to: "{{ evobackup_client__hosts[0].ip }}" tags: @@ -23,7 +25,8 @@ - evobackup_client_jail - name: 'add ip to jail' - command: "bkctld ip {{ evolinux_hostname }} {{ ansible_host }}" + ansible.builtin.command: + cmd: "bkctld ip {{ evolinux_hostname }} {{ ansible_host }}" become: true delegate_to: "{{ evobackup_client__hosts[0].ip }}" notify: 'jail updated' @@ -32,7 +35,8 @@ - evobackup_client_jail - name: 'add key to jail' - command: "bkctld key {{ evolinux_hostname }} /root/{{ evolinux_hostname }}.pub" + ansible.builtin.command: + cmd: "bkctld key {{ evolinux_hostname }} /root/{{ evolinux_hostname }}.pub" become: true delegate_to: "{{ evobackup_client__hosts[0].ip }}" notify: 'jail updated' @@ -41,7 +45,8 @@ - evobackup_client_jail - name: 'get jail port' - command: "bkctld port {{ evolinux_hostname }}" + ansible.builtin.command: + cmd: "bkctld port {{ evolinux_hostname }}" become: true register: bkctld_port delegate_to: "{{ evobackup_client__hosts[0].ip }}" @@ -50,7 +55,7 @@ - evobackup_client_jail - name: 'register jail port' - set_fact: + ansible.builtin.set_fact: evobackup_ssh_port={{ bkctld_port.stdout }} tags: - evobackup_client diff --git a/evobackup-client/tasks/main.yml b/evobackup-client/tasks/main.yml index a2dd4405..4b01a276 100644 --- a/evobackup-client/tasks/main.yml +++ b/evobackup-client/tasks/main.yml @@ -1,26 +1,26 @@ --- -- include: "ssh_key.yml" +- ansible.builtin.include: "ssh_key.yml" tags: - evobackup_client - evobackup_client_backup_ssh_key -- include: "jail.yml" +- ansible.builtin.include: "jail.yml" tags: - evobackup_client - evobackup_client_jail -- include: "upload_scripts.yml" +- ansible.builtin.include: "upload_scripts.yml" tags: - evobackup_client - evobackup_client_backup_scripts -- include: "open_ssh_ports.yml" +- ansible.builtin.include: "open_ssh_ports.yml" tags: - evobackup_client - evobackup_client_backup_firewall -- include: "verify_ssh.yml" +- ansible.builtin.include: "verify_ssh.yml" tags: - evobackup_client - evobackup_client_backup_hosts diff --git a/evobackup-client/tasks/open_ssh_ports.yml b/evobackup-client/tasks/open_ssh_ports.yml index 3d1701ef..837996e4 100644 --- a/evobackup-client/tasks/open_ssh_ports.yml +++ b/evobackup-client/tasks/open_ssh_ports.yml @@ -1,7 +1,7 @@ --- - name: Is there a Minifirewall ? - stat: + ansible.builtin.stat: path: /etc/default/minifirewall register: evobackup_client__minifirewall tags: @@ -9,7 +9,7 @@ - evobackup_client_backup_firewall - name: Add backup SSH port in /etc/default/minifirewall - blockinfile: + ansible.builtin.blockinfile: dest: /etc/default/minifirewall marker: "# {mark} {{ item.name }}" block: | diff --git a/evobackup-client/tasks/ssh_key.yml b/evobackup-client/tasks/ssh_key.yml index 6438634e..1b2617f9 100644 --- a/evobackup-client/tasks/ssh_key.yml +++ b/evobackup-client/tasks/ssh_key.yml @@ -1,7 +1,7 @@ --- - name: Create SSH key - user: + ansible.builtin.user: name: root generate_ssh_key: true ssh_key_file: "{{ evobackup_client__root_key_path }}" @@ -12,7 +12,7 @@ - evobackup_client_backup_ssh_key - name: Print SSH key - debug: + ansible.builtin.debug: var: evobackup_client__root_key.ssh_public_key when: evobackup_client__root_key.ssh_public_key is defined tags: @@ -20,7 +20,7 @@ - evobackup_client_backup_ssh_key - name: 'copy ssh public key to backup server' - copy: + ansible.builtin.copy: content: "{{ evobackup_client__root_key.ssh_public_key }}" dest: "/root/{{ evolinux_hostname }}.pub" become: true diff --git a/evobackup-client/tasks/upload_scripts.yml b/evobackup-client/tasks/upload_scripts.yml index 79e5d7db..1349a72d 100644 --- a/evobackup-client/tasks/upload_scripts.yml +++ b/evobackup-client/tasks/upload_scripts.yml @@ -1,11 +1,11 @@ --- - name: Upload evobackup script - template: + ansible.builtin.template: src: "{{ item }}" dest: "{{ evobackup_client__cron_path }}" force: true - mode: 0755 + mode: "0755" loop: "{{ query('first_found', templates) }}" vars: templates: diff --git a/evobackup-client/tasks/verify_ssh.yml b/evobackup-client/tasks/verify_ssh.yml index d48fb455..07238f9e 100644 --- a/evobackup-client/tasks/verify_ssh.yml +++ b/evobackup-client/tasks/verify_ssh.yml @@ -1,7 +1,7 @@ --- - name: Verify evolix backup servers - known_hosts: + ansible.builtin.known_hosts: path: /root/.ssh/known_hosts name: "[{{ item.name }}]:{{ item.port }}" key: "[{{ item.name }}]:{{ item.port }} {{ item.fingerprint }}" diff --git a/evocheck/files/evocheck.cf b/evocheck/files/evocheck.cf index 9eca204e..983363f9 100644 --- a/evocheck/files/evocheck.cf +++ b/evocheck/files/evocheck.cf @@ -1,5 +1,3 @@ -# Managed by Ansible -# # Configuration for evocheck # Use this file to change configuration values defined in evocheck.sh # Ex : IS_TMP_1777=0 diff --git a/evocheck/files/evocheck.jessie.sh b/evocheck/files/evocheck.jessie.sh new file mode 100755 index 00000000..05b5f8d1 --- /dev/null +++ b/evocheck/files/evocheck.jessie.sh @@ -0,0 +1,1307 @@ +#!/bin/bash + +# EvoCheck +# Script to verify compliance of a Linux (Debian) server +# powered by Evolix + +VERSION="23.04.01" +readonly VERSION + +# base functions + +show_version() { + cat <, + Romain Dessort , + Benoit Série , + Gregory Colpart , + Jérémy Lecour , + Tristan Pilat , + Victor Laborie , + Alexis Ben Miloud--Josselin , + and others. + +evocheck 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 <&2 + echo "This version is built for Debian 8 only." >&2 + exit + fi + + DEBIAN_RELEASE="jessie" + fi +} + +is_pack_web(){ + test -e /usr/share/scripts/web-add.sh || test -e /usr/share/scripts/evoadmin/web-add.sh +} +is_pack_samba(){ + test -e /usr/share/scripts/add.pl +} +is_installed(){ + for pkg in "$@"; do + dpkg -l "$pkg" 2> /dev/null | grep -q -E '^(i|h)i' || return 1 + done +} + +# logging + +failed() { + check_name=$1 + shift + check_comments=$* + + RC=1 + if [ "${QUIET}" != 1 ]; then + if [ -n "${check_comments}" ] && [ "${VERBOSE}" = 1 ]; then + printf "%s FAILED! %s\n" "${check_name}" "${check_comments}" >> "${main_output_file}" + else + printf "%s FAILED!\n" "${check_name}" >> "${main_output_file}" + fi + fi +} + +# check functions + +check_lsbrelease(){ + if [ -x "${LSB_RELEASE_BIN}" ]; then + ## only the major version matters + lhs=$(${LSB_RELEASE_BIN} --release --short | cut -d "." -f 1) + rhs=$(cut -d "." -f 1 < /etc/debian_version) + test "$lhs" = "$rhs" || failed "IS_LSBRELEASE" "release is not consistent between lsb_release (${lhs}) and /etc/debian_version (${rhs})" + else + failed "IS_LSBRELEASE" "lsb_release is missing or not executable" + fi +} + +# Verifying check_mailq in Nagios NRPE config file. (Option "-M postfix" need to be set if the MTA is Postfix) +check_nrpepostfix() { + if is_installed postfix; then + { test -e /etc/nagios/nrpe.cfg \ + && grep -qr "^command.*check_mailq -M postfix" /etc/nagios/nrpe.*; + } || failed "IS_NRPEPOSTFIX" "NRPE \"check_mailq\" for postfix is missing" + fi +} +# Check if mod-security config file is present +check_customsudoers() { + grep -E -qr "umask=0077" /etc/sudoers* || failed "IS_CUSTOMSUDOERS" "missing umask=0077 in sudoers file" +} +check_vartmpfs() { + FINDMNT_BIN=$(command -v findmnt) + if [ -x "${FINDMNT_BIN}" ]; then + ${FINDMNT_BIN} /var/tmp --type tmpfs --noheadings > /dev/null || failed "IS_VARTMPFS" "/var/tmp is not a tmpfs" + else + df /var/tmp | grep -q tmpfs || failed "IS_VARTMPFS" "/var/tmp is not a tmpfs" + fi +} +check_serveurbase() { + is_installed serveur-base || failed "IS_SERVEURBASE" "serveur-base package is not installed" +} +check_logrotateconf() { + test -e /etc/logrotate.d/zsyslog || failed "IS_LOGROTATECONF" "missing zsyslog in logrotate.d" +} +check_syslogconf() { + grep -q "^# Syslog for Pack Evolix serveur" /etc/*syslog.conf \ + || failed "IS_SYSLOGCONF" "syslog evolix config file missing" +} +check_debiansecurity() { + # Look for enabled "Debian-Security" sources from the "Debian" origin + apt-cache policy | grep "\bl=Debian-Security\b" | grep "\bo=Debian\b" | grep --quiet "\bc=main\b" + test $? -eq 0 || failed "IS_DEBIANSECURITY" "missing Debian-Security repository" +} +check_aptitude() { + test -e /usr/bin/aptitude && failed "IS_APTITUDE" "aptitude may not be installed on Debian >=8" +} +check_aptgetbak() { + test -e /usr/bin/apt-get.bak && failed "IS_APTGETBAK" "prohibit the installation of apt-get.bak with dpkg-divert(1)" +} +check_usrro() { + grep /usr /etc/fstab | grep -qE "\bro\b" || failed "IS_USRRO" "missing ro directive on fstab for /usr" +} +check_tmpnoexec() { + FINDMNT_BIN=$(command -v findmnt) + if [ -x "${FINDMNT_BIN}" ]; then + options=$(${FINDMNT_BIN} --noheadings --first-only --output OPTIONS /tmp) + echo "${options}" | grep -qE "\bnoexec\b" || failed "IS_TMPNOEXEC" "/tmp is not mounted with 'noexec'" + else + mount | grep "on /tmp" | grep -qE "\bnoexec\b" || failed "IS_TMPNOEXEC" "/tmp is not mounted with 'noexec' (WARNING: findmnt(8) is not found)" + fi +} +check_mountfstab() { + # Test if lsblk available, if not skip this test... + LSBLK_BIN=$(command -v lsblk) + if test -x "${LSBLK_BIN}"; then + for mountPoint in $(${LSBLK_BIN} -o MOUNTPOINT -l -n | grep '/'); do + grep -Eq "$mountPoint\W" /etc/fstab \ + || failed "IS_MOUNT_FSTAB" "partition(s) detected mounted but no presence in fstab" + done + fi +} +check_listchangesconf() { + if [ -e "/etc/apt/listchanges.conf" ]; then + lines=$(grep -cE "(which=both|confirm=1)" /etc/apt/listchanges.conf) + if [ "$lines" != 2 ]; then + failed "IS_LISTCHANGESCONF" "apt-listchanges config is incorrect" + fi + else + failed "IS_LISTCHANGESCONF" "apt-listchanges config is missing" + fi +} +check_customcrontab() { + found_lines=$(grep -c -E "^(17 \*|25 6|47 6|52 6)" /etc/crontab) + test "$found_lines" = 4 && failed "IS_CUSTOMCRONTAB" "missing custom field in crontab" +} +check_sshallowusers() { + grep -E -qir "(AllowUsers|AllowGroups)" /etc/ssh/sshd_config /etc/ssh/sshd_config.d \ + || failed "IS_SSHALLOWUSERS" "missing AllowUsers or AllowGroups directive in sshd_config" +} +check_diskperf() { + perfFile="/root/disk-perf.txt" + test -e $perfFile || failed "IS_DISKPERF" "missing ${perfFile}" +} +check_tmoutprofile() { + grep -sq "TMOUT=" /etc/profile /etc/profile.d/evolinux.sh || failed "IS_TMOUTPROFILE" "TMOUT is not set" +} +check_alert5boot() { + 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" + elif [ -n "$(find /etc/init.d/ -name 'alert5')" ]; then + grep -q "^date" /etc/init.d/alert5 || failed "IS_ALERT5BOOT" "boot mail is not sent by alert5 int script" + else + failed "IS_ALERT5BOOT" "alert5 init script is missing" + fi +} +check_alert5minifw() { + if [ -n "$(find /etc/rc2.d/ -name 'S*alert5')" ]; then + grep -q "^/etc/init.d/minifirewall" /etc/rc2.d/S*alert5 \ + || failed "IS_ALERT5MINIFW" "Minifirewall is not started by alert5 init script" + elif [ -n "$(find /etc/init.d/ -name 'alert5')" ]; then + grep -q "^/etc/init.d/minifirewall" /etc/init.d/alert5 \ + || failed "IS_ALERT5MINIFW" "Minifirewall is not started by alert5 init script" + else + failed "IS_ALERT5MINIFW" "alert5 init script is missing" + fi +} +check_minifw() { + /sbin/iptables -L -n | grep -q -E "^ACCEPT\s*all\s*--\s*31\.170\.8\.4\s*0\.0\.0\.0/0\s*$" \ + || failed "IS_MINIFW" "minifirewall seems not started" +} +check_nrpeperms() { + if [ -d /etc/nagios ]; then + nagiosDir="/etc/nagios" + actual=$(stat --format "%a" $nagiosDir) + expected="750" + test "$expected" = "$actual" || failed "IS_NRPEPERMS" "${nagiosDir} must be ${expected}" + fi +} +check_minifwperms() { + if [ -f "/etc/default/minifirewall" ]; then + actual=$(stat --format "%a" "/etc/default/minifirewall") + expected="600" + test "$expected" = "$actual" || failed "IS_MINIFWPERMS" "/etc/default/minifirewall must be ${expected}" + fi +} +check_nrpedisks() { + NRPEDISKS=$(grep command.check_disk /etc/nagios/nrpe.cfg | grep "^command.check_disk[0-9]" | sed -e "s/^command.check_disk\([0-9]\+\).*/\1/" | sort -n | tail -1) + DFDISKS=$(df -Pl | grep -c -E -v "(^Filesystem|/lib/init/rw|/dev/shm|udev|rpc_pipefs)") + test "$NRPEDISKS" = "$DFDISKS" || failed "IS_NRPEDISKS" "there must be $DFDISKS check_disk in nrpe.cfg" +} +check_nrpepid() { + { test -e /etc/nagios/nrpe.cfg \ + && grep -q "^pid_file=/var/run/nagios/nrpe.pid" /etc/nagios/nrpe.cfg; + } || failed "IS_NRPEPID" "missing or wrong pid_file directive in nrpe.cfg" +} +check_grsecprocs() { + if uname -a | grep -q grsec; then + { grep -q "^command.check_total_procs..sudo" /etc/nagios/nrpe.cfg \ + && grep -A1 "^\[processes\]" /etc/munin/plugin-conf.d/munin-node | grep -q "^user root"; + } || failed "IS_GRSECPROCS" "missing munin's plugin processes directive for grsec" + fi +} +check_apachemunin() { + if test -e /etc/apache2/apache2.conf; then + pattern="/server-status-[[:alnum:]]{4,}" + { grep -r -q -s -E "^env.url.*${pattern}" /etc/munin/plugin-conf.d \ + && { grep -q -s -E "${pattern}" /etc/apache2/apache2.conf \ + || grep -q -s -E "${pattern}" /etc/apache2/mods-enabled/status.conf; + }; + } || failed "IS_APACHEMUNIN" "server status is not properly configured" + fi +} +# Verification mytop + Munin si MySQL +check_mysqlutils() { + MYSQL_ADMIN=${MYSQL_ADMIN:-mysqladmin} + if is_installed mysql-server; then + # You can configure MYSQL_ADMIN in evocheck.cf + if ! grep -qs "^user *= *${MYSQL_ADMIN}" /root/.my.cnf; then + failed "IS_MYSQLUTILS" "${MYSQL_ADMIN} missing in /root/.my.cnf" + fi + if ! test -x /usr/bin/mytop; then + if ! test -x /usr/local/bin/mytop; then + failed "IS_MYSQLUTILS" "mytop binary missing" + fi + fi + if ! grep -qs '^user *=' /root/.mytop; then + failed "IS_MYSQLUTILS" "credentials missing in /root/.mytop" + fi + fi +} +# Verification de la configuration du raid soft (mdadm) +check_raidsoft() { + if test -e /proc/mdstat && grep -q md /proc/mdstat; then + { grep -q "^AUTOCHECK=true" /etc/default/mdadm \ + && grep -q "^START_DAEMON=true" /etc/default/mdadm \ + && grep -qv "^MAILADDR ___MAIL___" /etc/mdadm/mdadm.conf; + } || failed "IS_RAIDSOFT" "missing or wrong config for mdadm" + fi +} +# Verification du LogFormat de AWStats +check_awstatslogformat() { + if is_installed apache2 awstats; then + awstatsFile="/etc/awstats/awstats.conf.local" + grep -qE '^LogFormat=1' $awstatsFile \ + || failed "IS_AWSTATSLOGFORMAT" "missing or wrong LogFormat directive in $awstatsFile" + fi +} +# Verification de la présence de la config logrotate pour Munin +check_muninlogrotate() { + { test -e /etc/logrotate.d/munin-node \ + && test -e /etc/logrotate.d/munin; + } || failed "IS_MUNINLOGROTATE" "missing lorotate file for munin" +} +# Verification de l'activation de Squid dans le cas d'un pack mail +check_squid() { + squidconffile="/etc/squid*/squid.conf" + if is_pack_web && (is_installed squid || is_installed squid3); then + host=$(hostname -i) + # shellcheck disable=SC2086 + http_port=$(grep -E "^http_port\s+[0-9]+" $squidconffile | awk '{ print $2 }') + { grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -m owner --uid-owner proxy -j ACCEPT" "/etc/default/minifirewall" \ + && grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -d $host -j ACCEPT" "/etc/default/minifirewall" \ + && grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -d 127.0.0.(1|0/8) -j ACCEPT" "/etc/default/minifirewall" \ + && grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port.* $http_port" "/etc/default/minifirewall"; + } || grep -qE "^PROXY='?on'?" "/etc/default/minifirewall" \ + || failed "IS_SQUID" "missing squid rules in minifirewall" + fi +} +check_evomaintenance_fw() { + if [ -f "/etc/default/minifirewall" ]; then + hook_db=$(grep -E '^\s*HOOK_DB' /etc/evomaintenance.cf | tr -d ' ' | cut -d= -f2) + rulesNumber=$(grep -c "/sbin/iptables -A INPUT -p tcp --sport 5432 --dport 1024:65535 -s .* -m state --state ESTABLISHED,RELATED -j ACCEPT" "/etc/default/minifirewall") + if [ "$hook_db" = "1" ] && [ "$rulesNumber" -lt 2 ]; then + failed "IS_EVOMAINTENANCE_FW" "HOOK_DB is enabled but missing evomaintenance rules in minifirewall" + fi + fi +} +# Verification de la conf et de l'activation de mod-deflate +check_moddeflate() { + f=/etc/apache2/mods-enabled/deflate.conf + if is_installed apache2.2; then + { test -e $f && grep -q "AddOutputFilterByType DEFLATE text/html text/plain text/xml" $f \ + && grep -q "AddOutputFilterByType DEFLATE text/css" $f \ + && grep -q "AddOutputFilterByType DEFLATE application/x-javascript application/javascript" $f; + } || failed "IS_MODDEFLATE" "missing AddOutputFilterByType directive for apache mod deflate" + fi +} +# Verification de la conf log2mail +check_log2mailrunning() { + if is_pack_web && is_installed log2mail; then + pgrep log2mail >/dev/null || failed "IS_LOG2MAILRUNNING" "log2mail is not running" + fi +} +check_log2mailapache() { + conf=/etc/log2mail/config/default + if is_pack_web && is_installed log2mail; then + grep -s -q "^file = /var/log/apache2/error.log" $conf \ + || failed "IS_LOG2MAILAPACHE" "missing log2mail directive for apache" + fi +} +check_log2mailmysql() { + if is_pack_web && is_installed log2mail; then + grep -s -q "^file = /var/log/syslog" /etc/log2mail/config/{default,mysql,mysql.conf} \ + || failed "IS_LOG2MAILMYSQL" "missing log2mail directive for mysql" + fi +} +check_log2mailsquid() { + if is_pack_web && is_installed log2mail; then + grep -s -q "^file = /var/log/squid.*/access.log" /etc/log2mail/config/* \ + || failed "IS_LOG2MAILSQUID" "missing log2mail directive for squid" + fi +} +# Verification si bind est chroote +check_bindchroot() { + if is_installed bind9; then + if netstat -utpln | grep "/named" | grep :53 | grep -qvE "(127.0.0.1|::1)"; then + if grep -q '^OPTIONS=".*-t' /etc/default/bind9 && grep -q '^OPTIONS=".*-u' /etc/default/bind9; then + md5_original=$(md5sum /usr/sbin/named | cut -f 1 -d ' ') + md5_chrooted=$(md5sum /var/chroot-bind/usr/sbin/named | cut -f 1 -d ' ') + if [ "$md5_original" != "$md5_chrooted" ]; then + failed "IS_BINDCHROOT" "the chrooted bind binary is different than the original binary" + fi + else + failed "IS_BINDCHROOT" "bind process is not chrooted" + fi + fi + fi +} +# /etc/network/interfaces should be present, we don't manage systemd-network yet +check_network_interfaces() { + if ! test -f /etc/network/interfaces; then + IS_AUTOIF=0 + IS_INTERFACESGW=0 + failed "IS_NETWORK_INTERFACES" "systemd network configuration is not supported yet" + fi +} +# Verify if all if are in auto +check_autoif() { + interfaces=$(/sbin/ifconfig -s | tail -n +2 | grep -E -v "^(lo|vnet|docker|veth|tun|tap|macvtap|vrrp)" | cut -d " " -f 1 |tr "\n" " ") + for interface in $interfaces; do + if grep -Rq "^iface $interface" /etc/network/interfaces* && ! grep -Rq "^auto $interface" /etc/network/interfaces*; then + failed "IS_AUTOIF" "Network interface \`${interface}' is statically defined but not set to auto" + test "${VERBOSE}" = 1 || break + fi + done +} +# Network conf verification +check_interfacesgw() { + number=$(grep -Ec "^[^#]*gateway [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" /etc/network/interfaces) + test "$number" -gt 1 && failed "IS_INTERFACESGW" "there is more than 1 IPv4 gateway" + number=$(grep -Ec "^[^#]*gateway [0-9a-fA-F]+:" /etc/network/interfaces) + test "$number" -gt 1 && failed "IS_INTERFACESGW" "there is more than 1 IPv6 gateway" +} +# Verification de la mise en place d'evobackup +check_evobackup() { + evobackup_found=$(find /etc/cron* -name '*evobackup*' | wc -l) + test "$evobackup_found" -gt 0 || failed "IS_EVOBACKUP" "missing evobackup cron" +} +# Vérification de l'exclusion des montages (NFS) dans les sauvegardes +check_evobackup_exclude_mount() { + 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 + # if the file seems to be a backup script, with an Rsync invocation + if grep -q "^\s*rsync" "${evobackup_file}"; then + # 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 + fi + done +} +# Verification de la presence du userlogrotate +check_userlogrotate() { + if is_pack_web; then + test -x /etc/cron.weekly/userlogrotate || failed "IS_USERLOGROTATE" "missing userlogrotate cron" + fi +} +# Verification de la syntaxe de la conf d'Apache +check_apachectl() { + if is_installed apache2; then + /usr/sbin/apache2ctl configtest 2>&1 | grep -q "^Syntax OK$" \ + || failed "IS_APACHECTL" "apache errors detected, run a configtest" + fi +} +# Check if there is regular files in Apache sites-enabled. +check_apachesymlink() { + if is_installed apache2; then + apacheFind=$(find /etc/apache2/sites-enabled ! -type l -type f -print) + nbApacheFind=$(wc -m <<< "$apacheFind") + if [[ $nbApacheFind -gt 1 ]]; then + if [[ $VERBOSE == 1 ]]; then + while read -r line; do + failed "IS_APACHESYMLINK" "Not a symlink: $line" + done <<< "$apacheFind" + else + failed "IS_APACHESYMLINK" + fi + fi + fi +} +# Check if there is real IP addresses in Allow/Deny directives (no trailing space, inline comments or so). +check_apacheipinallow() { + # Note: Replace "exit 1" by "print" in Perl code to debug it. + if is_installed apache2; then + grep -IrE "^[^#] *(Allow|Deny) from" /etc/apache2/ \ + | grep -iv "from all" \ + | grep -iv "env=" \ + | perl -ne 'exit 1 unless (/from( [\da-f:.\/]+)+$/i)' \ + || failed "IS_APACHEIPINALLOW" "bad (Allow|Deny) directives in apache" + fi +} +# Check if default Apache configuration file for munin is absent (or empty or commented). +check_muninapacheconf() { + muninconf="/etc/apache2/conf-available/munin.conf" + if is_installed apache2; then + test -e $muninconf && grep -vEq "^( |\t)*#" "$muninconf" \ + && failed "IS_MUNINAPACHECONF" "default munin configuration may be commented or disabled" + fi +} +# Check if default Apache configuration file for phpMyAdmin is absent (or empty or commented). +check_phpmyadminapacheconf() { + phpmyadminconf0="/etc/apache2/conf-available/phpmyadmin.conf" + phpmyadminconf1="/etc/apache2/conf-enabled/phpmyadmin.conf" + if is_installed apache2; then + test -e $phpmyadminconf0 && grep -vEq "^( |\t)*#" "$phpmyadminconf0" \ + && failed "IS_PHPMYADMINAPACHECONF" "default phpmyadmin configuration ($phpmyadminconf0) may be commented or disabled" + test -e $phpmyadminconf1 && grep -vEq "^( |\t)*#" "$phpmyadminconf1" \ + && failed "IS_PHPMYADMINAPACHECONF" "default phpmyadmin configuration ($phpmyadminconf1) may be commented or disabled" + fi +} +# Verification si le système doit redémarrer suite màj kernel. +check_kerneluptodate() { + if is_installed linux-image*; then + # shellcheck disable=SC2012 + kernel_installed_at=$(date -d "$(ls --full-time -lcrt /boot | tail -n1 | awk '{print $6}')" +%s) + last_reboot_at=$(($(date +%s) - $(cut -f1 -d '.' /proc/uptime))) + if [ "$kernel_installed_at" -gt "$last_reboot_at" ]; then + failed "IS_KERNELUPTODATE" "machine is running an outdated kernel, reboot advised" + fi + fi +} +# Check if the server is running for more than a year. +check_uptime() { + if is_installed linux-image*; then + limit=$(date -d "now - 2 year" +%s) + last_reboot_at=$(($(date +%s) - $(cut -f1 -d '.' /proc/uptime))) + if [ "$limit" -gt "$last_reboot_at" ]; then + failed "IS_UPTIME" "machine has an uptime of more than 2 years, reboot on new kernel advised" + fi + fi +} +# Check if munin-node running and RRD files are up to date. +check_muninrunning() { + if ! pgrep munin-node >/dev/null; then + failed "IS_MUNINRUNNING" "Munin is not running" + elif [ -d "/var/lib/munin/" ] && [ -d "/var/cache/munin/" ]; then + limit=$(date +"%s" -d "now - 10 minutes") + + if [ -n "$(find /var/lib/munin/ -name '*load-g.rrd')" ]; then + updated_at=$(stat -c "%Y" /var/lib/munin/*/*load-g.rrd |sort |tail -1) + [ "$limit" -gt "$updated_at" ] && failed "IS_MUNINRUNNING" "Munin load RRD has not been updated in the last 10 minutes" + else + failed "IS_MUNINRUNNING" "Munin is not installed properly (load RRD not found)" + fi + + if [ -n "$(find /var/cache/munin/www/ -name 'load-day.png')" ]; then + updated_at=$(stat -c "%Y" /var/cache/munin/www/*/*/load-day.png |sort |tail -1) + grep -sq "^graph_strategy cron" /etc/munin/munin.conf && [ "$limit" -gt "$updated_at" ] && failed "IS_MUNINRUNNING" "Munin load PNG has not been updated in the last 10 minutes" + else + failed "IS_MUNINRUNNING" "Munin is not installed properly (load PNG not found)" + fi + else + failed "IS_MUNINRUNNING" "Munin is not installed properly (main directories are missing)" + fi +} +# Check if files in /home/backup/ are up-to-date +check_backupuptodate() { + backup_dir="/home/backup" + if [ -d "${backup_dir}" ]; then + if [ -n "$(ls -A ${backup_dir})" ]; then + find "${backup_dir}" -maxdepth 1 -type f | while read -r file; do + limit=$(date +"%s" -d "now - 2 day") + updated_at=$(stat -c "%Y" "$file") + + if [ "$limit" -gt "$updated_at" ]; then + failed "IS_BACKUPUPTODATE" "$file has not been backed up" + test "${VERBOSE}" = 1 || break; + fi + done + else + failed "IS_BACKUPUPTODATE" "${backup_dir}/ is empty" + fi + else + failed "IS_BACKUPUPTODATE" "${backup_dir}/ is missing" + fi +} +check_etcgit() { + export GIT_DIR="/etc/.git" GIT_WORK_TREE="/etc" + git rev-parse --is-inside-work-tree > /dev/null 2>&1 \ + || failed "IS_ETCGIT" "/etc is not a git repository" +} +# Check if /etc/.git/ has read/write permissions for root only. +check_gitperms() { + GIT_DIR="/etc/.git" + if test -d $GIT_DIR; then + expected="700" + actual=$(stat -c "%a" $GIT_DIR) + [ "$expected" = "$actual" ] || failed "IS_GITPERMS" "$GIT_DIR must be $expected" + fi +} +# Check if no package has been upgraded since $limit. +check_notupgraded() { + last_upgrade=0 + upgraded=false + for log in /var/log/dpkg.log*; do + if zgrep -qsm1 upgrade "$log"; then + # There is at least one upgrade + upgraded=true + break + fi + done + if $upgraded; then + last_upgrade=$(date +%s -d "$(zgrep -h upgrade /var/log/dpkg.log* | sort -n | tail -1 | cut -f1 -d ' ')") + fi + if grep -qs '^mailto="listupgrade-todo@' /etc/evolinux/listupgrade.cnf \ + || grep -qs -E '^[[:digit:]]+[[:space:]]+[[:digit:]]+[[:space:]]+[^\*]' /etc/cron.d/listupgrade; then + # Manual upgrade process + limit=$(date +%s -d "now - 180 days") + else + # Regular process + limit=$(date +%s -d "now - 90 days") + fi + install_date=0 + if [ -d /var/log/installer ]; then + install_date=$(stat -c %Z /var/log/installer) + fi + # Check install_date if the system never received an upgrade + if [ "$last_upgrade" -eq 0 ]; then + [ "$install_date" -lt "$limit" ] && failed "IS_NOTUPGRADED" "The system has never been updated" + else + [ "$last_upgrade" -lt "$limit" ] && failed "IS_NOTUPGRADED" "The system hasn't been updated for too long" + fi +} +# Check if reserved blocks for root is at least 5% on every mounted partitions. +check_tune2fs_m5() { + min=5 + parts=$(grep -E "ext(3|4)" /proc/mounts | cut -d ' ' -f1 | tr -s '\n' ' ') + FINDMNT_BIN=$(command -v findmnt) + for part in $parts; do + blockCount=$(dumpe2fs -h "$part" 2>/dev/null | grep -e "Block count:" | grep -Eo "[0-9]+") + # If buggy partition, skip it. + if [ -z "$blockCount" ]; then + continue + fi + reservedBlockCount=$(dumpe2fs -h "$part" 2>/dev/null | grep -e "Reserved block count:" | grep -Eo "[0-9]+") + # Use awk to have a rounded percentage + # python is slow, bash is unable and bc rounds weirdly + percentage=$(awk "BEGIN { pc=100*${reservedBlockCount}/${blockCount}; i=int(pc); print (pc-i<0.5)?i:i+1 }") + + if [ "$percentage" -lt "${min}" ]; then + if [ -x "${FINDMNT_BIN}" ]; then + mount=$(${FINDMNT_BIN} --noheadings --first-only --output TARGET "${part}") + else + mount="unknown mount point" + fi + failed "IS_TUNE2FS_M5" "Partition ${part} (${mount}) has less than ${min}% reserved blocks (${percentage}%)" + fi + done +} +check_broadcomfirmware() { + LSPCI_BIN=$(command -v lspci) + if [ -x "${LSPCI_BIN}" ]; then + if ${LSPCI_BIN} | grep -q 'NetXtreme II'; then + { is_installed firmware-bnx2 \ + && grep -q "^deb http://mirror.evolix.org/debian.* non-free" /etc/apt/sources.list; + } || failed "IS_BROADCOMFIRMWARE" "missing non-free repository" + fi + else + failed "IS_BROADCOMFIRMWARE" "lspci not found in ${PATH}" + fi +} +check_hardwareraidtool() { + LSPCI_BIN=$(command -v lspci) + if [ -x "${LSPCI_BIN}" ]; then + if ${LSPCI_BIN} | grep -q 'MegaRAID'; then + # shellcheck disable=SC2015 + is_installed megacli && { is_installed megaclisas-status || is_installed megaraidsas-status; } \ + || failed "IS_HARDWARERAIDTOOL" "Mega tools not found" + fi + if ${LSPCI_BIN} | grep -q 'Hewlett-Packard Company Smart Array'; then + is_installed cciss-vol-status || failed "IS_HARDWARERAIDTOOL" "cciss-vol-status not installed" + fi + else + failed "IS_HARDWARERAIDTOOL" "lspci not found in ${PATH}" + fi +} +check_listupgrade() { + test -f /etc/cron.d/listupgrade \ + || failed "IS_LISTUPGRADE" "missing listupgrade cron" + test -x /usr/share/scripts/listupgrade.sh \ + || failed "IS_LISTUPGRADE" "missing listupgrade script or not executable" +} +check_sql_backup() { + if (is_installed "mysql-server" || is_installed "mariadb-server"); then + # You could change the default path in /etc/evocheck.cf + SQL_BACKUP_PATH=${SQL_BACKUP_PATH:-"/home/backup/mysql.bak.gz"} + for backup_path in ${SQL_BACKUP_PATH}; do + if [ ! -f "${backup_path}" ]; then + failed "IS_SQL_BACKUP" "MySQL dump is missing (${backup_path})" + test "${VERBOSE}" = 1 || break + fi + done + fi +} +check_postgres_backup() { + if is_installed "postgresql-9*" || is_installed "postgresql-1*"; then + # If you use something like barman, you should disable this check + # You could change the default path in /etc/evocheck.cf + POSTGRES_BACKUP_PATH=${POSTGRES_BACKUP_PATH:-"/home/backup/pg.dump.bak*"} + for backup_path in ${POSTGRES_BACKUP_PATH}; do + if [ ! -f "${backup_path}" ]; then + failed "IS_POSTGRES_BACKUP" "PostgreSQL dump is missing (${backup_path})" + test "${VERBOSE}" = 1 || break + fi + done + fi +} +check_mongo_backup() { + if is_installed "mongodb-org-server"; then + # You could change the default path in /etc/evocheck.cf + MONGO_BACKUP_PATH=${MONGO_BACKUP_PATH:-"/home/backup/mongodump"} + if [ -d "$MONGO_BACKUP_PATH" ]; then + for file in "${MONGO_BACKUP_PATH}"/*/*.{json,bson}*; do + # Skip indexes file. + if ! [[ "$file" =~ indexes ]]; then + limit=$(date +"%s" -d "now - 2 day") + updated_at=$(stat -c "%Y" "$file") + if [ -f "$file" ] && [ "$limit" -gt "$updated_at" ]; then + failed "IS_MONGO_BACKUP" "MongoDB hasn't been dumped for more than 2 days" + break + fi + fi + done + else + failed "IS_MONGO_BACKUP" "MongoDB dump directory is missing (${MONGO_BACKUP_PATH})" + fi + fi +} +check_ldap_backup() { + if is_installed slapd; then + # You could change the default path in /etc/evocheck.cf + LDAP_BACKUP_PATH=${LDAP_BACKUP_PATH:-"/home/backup/ldap.bak"} + test -f "$LDAP_BACKUP_PATH" || failed "IS_LDAP_BACKUP" "LDAP dump is missing (${LDAP_BACKUP_PATH})" + fi +} +check_redis_backup() { + if is_installed redis-server; then + # You could change the default path in /etc/evocheck.cf + # REDIS_BACKUP_PATH may contain space-separated paths, example: + # REDIS_BACKUP_PATH='/home/backup/redis-instance1/dump.rdb /home/backup/redis-instance2/dump.rdb' + REDIS_BACKUP_PATH=${REDIS_BACKUP_PATH:-"/home/backup/redis/dump.rdb"} + for file in ${REDIS_BACKUP_PATH}; do + test -f "${file}" || failed "IS_REDIS_BACKUP" "Redis dump is missing (${file})" + done + fi +} +check_elastic_backup() { + if is_installed elasticsearch; then + # You could change the default path in /etc/evocheck.cf + ELASTIC_BACKUP_PATH=${ELASTIC_BACKUP_PATH:-"/home/backup-elasticsearch"} + test -d "$ELASTIC_BACKUP_PATH" || failed "IS_ELASTIC_BACKUP" "Elastic snapshot is missing (${ELASTIC_BACKUP_PATH})" + fi +} +check_duplicate_fs_label() { + # Do it only if thereis blkid binary + BLKID_BIN=$(command -v blkid) + 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" + done + tmpOutput=$(sort < "$tmpFile" | uniq -d) + # If there is no duplicate, uniq will have no output + # So, if $tmpOutput is not null, there is a duplicate + if [ -n "$tmpOutput" ]; then + # shellcheck disable=SC2086 + labels=$(echo -n $tmpOutput | tr '\n' ' ') + failed "IS_DUPLICATE_FS_LABEL" "Duplicate labels: $labels" + fi + else + failed "IS_DUPLICATE_FS_LABEL" "blkid not found in ${PATH}" + fi +} +check_evolix_user() { + grep -q -E "^evolix:" /etc/passwd \ + && failed "IS_EVOLIX_USER" "evolix user should be deleted, used only for install" +} +check_evoacme_cron() { + if [ -f "/usr/local/sbin/evoacme" ]; then + # Old cron file, should be deleted + test -f /etc/cron.daily/certbot && failed "IS_EVOACME_CRON" "certbot cron is incompatible with evoacme" + # evoacme cron file should be present + test -f /etc/cron.daily/evoacme || failed "IS_EVOACME_CRON" "evoacme cron is missing" + fi +} +check_evoacme_livelinks() { + EVOACME_BIN=$(command -v evoacme) + if [ -x "$EVOACME_BIN" ]; then + # Sometimes evoacme is installed but no certificates has been generated + numberOfLinks=$(find /etc/letsencrypt/ -type l | wc -l) + if [ "$numberOfLinks" -gt 0 ]; then + for live in /etc/letsencrypt/*/live; do + actualLink=$(readlink -f "$live") + actualVersion=$(basename "$actualLink") + + certDir=$(dirname "$live") + certName=$(basename "$certDir") + # shellcheck disable=SC2012 + lastCertDir=$(ls -ds "${certDir}"/[0-9]* | tail -1) + lastVersion=$(basename "$lastCertDir") + + if [[ "$lastVersion" != "$actualVersion" ]]; then + failed "IS_EVOACME_LIVELINKS" "Certificate \`$certName' hasn't been updated" + test "${VERBOSE}" = 1 || break + fi + done + fi + fi +} +check_apache_confenabled() { + # Starting from Jessie and Apache 2.4, /etc/apache2/conf.d/ + # must be replaced by conf-available/ and config files symlinked + # to conf-enabled/ + if [ -f /etc/apache2/apache2.conf ]; then + test -d /etc/apache2/conf.d/ \ + && failed "IS_APACHE_CONFENABLED" "apache's conf.d directory must not exists" + grep -q 'Include conf.d' /etc/apache2/apache2.conf \ + && failed "IS_APACHE_CONFENABLED" "apache2.conf must not Include conf.d" + fi +} +check_meltdown_spectre() { + # For Jessie this is quite complicated to verify and we need to use kernel config file + if grep -q "BOOT_IMAGE=" /proc/cmdline; then + kernelPath=$(grep -Eo 'BOOT_IMAGE=[^ ]+' /proc/cmdline | cut -d= -f2) + kernelVer=${kernelPath##*/vmlinuz-} + kernelConfig="config-${kernelVer}" + # Sometimes autodetection of kernel config file fail, so we test if the file really exists. + if [ -f "/boot/${kernelConfig}" ]; then + grep -Eq '^CONFIG_PAGE_TABLE_ISOLATION=y' "/boot/$kernelConfig" \ + || failed "IS_MELTDOWN_SPECTRE" \ + "PAGE_TABLE_ISOLATION must be enabled in kernel, outdated kernel?" + grep -Eq '^CONFIG_RETPOLINE=y' "/boot/$kernelConfig" \ + || failed "IS_MELTDOWN_SPECTRE" \ + "RETPOLINE must be enabled in kernel, outdated kernel?" + fi + fi +} +check_old_home_dir() { + homeDir=${homeDir:-/home} + for dir in "$homeDir"/*; do + statResult=$(stat -c "%n has owner %u resolved as %U" "$dir" \ + | grep -Eve '.bak' -e '\.[0-9]{2}-[0-9]{2}-[0-9]{4}' \ + | grep "UNKNOWN") + # There is at least one dir matching + if [[ -n "$statResult" ]]; then + failed "IS_OLD_HOME_DIR" "$statResult" + test "${VERBOSE}" = 1 || break + fi + done +} +check_tmp_1777() { + actual=$(stat --format "%a" /tmp) + expected="1777" + test "$expected" = "$actual" || failed "IS_TMP_1777" "/tmp must be $expected" +} +check_root_0700() { + actual=$(stat --format "%a" /root) + expected="700" + test "$expected" = "$actual" || failed "IS_ROOT_0700" "/root must be $expected" +} +check_usrsharescripts() { + actual=$(stat --format "%a" /usr/share/scripts) + expected="700" + test "$expected" = "$actual" || failed "IS_USRSHARESCRIPTS" "/usr/share/scripts must be $expected" +} +check_sshpermitrootno() { + sshd_args="-C addr=,user=,host=,laddr=,lport=0" + # shellcheck disable=SC2086 + if ! (sshd -T ${sshd_args} 2> /dev/null | grep -qi 'permitrootlogin no'); then + failed "IS_SSHPERMITROOTNO" "PermitRoot should be set to no" + fi +} +check_evomaintenanceusers() { + if [ -f /etc/sudoers.d/evolinux ]; then + sudoers="/etc/sudoers.d/evolinux" + else + sudoers="/etc/sudoers" + fi + # combine users from User_Alias and sudo group + users=$({ grep "^User_Alias *ADMIN" $sudoers | cut -d= -f2 | tr -d " "; grep "^sudo" /etc/group | cut -d: -f 4; } | tr "," "\n" | sort -u) + for user in $users; do + user_home=$(getent passwd "$user" | cut -d: -f6) + if [ -n "$user_home" ] && [ -d "$user_home" ]; then + if ! grep -qs "^trap.*sudo.*evomaintenance.sh" "${user_home}"/.*profile; then + failed "IS_EVOMAINTENANCEUSERS" "${user} doesn't have an evomaintenance trap" + test "${VERBOSE}" = 1 || break + fi + fi + done +} +check_evomaintenanceconf() { + f=/etc/evomaintenance.cf + if [ -e "$f" ]; then + perms=$(stat -c "%a" $f) + test "$perms" = "600" || failed "IS_EVOMAINTENANCECONF" "Wrong permissions on \`$f' ($perms instead of 600)" + + { grep "^export PGPASSWORD" $f | grep -qv "your-passwd" \ + && grep "^PGDB" $f | grep -qv "your-db" \ + && grep "^PGTABLE" $f | grep -qv "your-table" \ + && grep "^PGHOST" $f | grep -qv "your-pg-host" \ + && grep "^FROM" $f | grep -qv "jdoe@example.com" \ + && grep "^FULLFROM" $f | grep -qv "John Doe " \ + && grep "^URGENCYFROM" $f | grep -qv "mama.doe@example.com" \ + && grep "^URGENCYTEL" $f | grep -qv "06.00.00.00.00" \ + && grep "^REALM" $f | grep -qv "example.com" + } || failed "IS_EVOMAINTENANCECONF" "evomaintenance is not correctly configured" + else + failed "IS_EVOMAINTENANCECONF" "Configuration file \`$f' is missing" + fi +} +check_privatekeyworldreadable() { + # a simple globbing fails if directory is empty + if [ -n "$(ls -A /etc/ssl/private/)" ]; then + for f in /etc/ssl/private/*; do + perms=$(stat -L -c "%a" "$f") + if [ "${perms: -1}" != 0 ]; then + failed "IS_PRIVKEYWOLRDREADABLE" "$f is world-readable" + test "${VERBOSE}" = 1 || break + fi + done + fi +} +check_evobackup_incs() { + if is_installed bkctld; then + bkctld_cron_file=${bkctld_cron_file:-/etc/cron.d/bkctld} + if [ -f "${bkctld_cron_file}" ]; then + root_crontab=$(grep -v "^#" "${bkctld_cron_file}") + echo "${root_crontab}" | grep -q "bkctld inc" || failed "IS_EVOBACKUP_INCS" "\`bkctld inc' is missing in ${bkctld_cron_file}" + echo "${root_crontab}" | grep -qE "(check-incs.sh|bkctld check-incs)" || failed "IS_EVOBACKUP_INCS" "\`check-incs.sh' is missing in ${bkctld_cron_file}" + else + failed "IS_EVOBACKUP_INCS" "Crontab \`${bkctld_cron_file}' is missing" + fi + fi +} + +check_osprober() { + if is_installed os-prober qemu-kvm; then + failed "IS_OSPROBER" \ + "Removal of os-prober package is recommended as it can cause serious issue on KVM server" + fi +} + +check_jessie_backports() { + jessieBackports=$(grep -hs "jessie-backports" /etc/apt/sources.list /etc/apt/sources.list.d/*) + if test -n "$jessieBackports"; then + if ! grep -q "archive.debian.org" <<< "$jessieBackports"; then + failed "IS_JESSIE_BACKPORTS" "You must use deb http://archive.debian.org/debian/ jessie-backports main" + fi + fi +} + +check_apt_valid_until() { + aptvalidFile="/etc/apt/apt.conf.d/99no-check-valid-until" + aptvalidText="Acquire::Check-Valid-Until no;" + if grep -qs "archive.debian.org" /etc/apt/sources.list /etc/apt/sources.list.d/*; then + if ! grep -qs "$aptvalidText" /etc/apt/apt.conf.d/*; then + failed "IS_APT_VALID_UNTIL" \ + "As you use archive.mirror.org you need ${aptvalidFile}: ${aptvalidText}" + fi + fi +} + +check_chrooted_binary_uptodate() { + # list of processes to check + process_list="sshd" + for process_name in ${process_list}; do + # what is the binary path? + original_bin=$(command -v "${process_name}") + for pid in $(pgrep ${process_name}); do + process_bin=$(realpath "/proc/${pid}/exe") + # Is the process chrooted? + real_root=$(realpath "/proc/${pid}/root") + if [ "${real_root}" != "/" ]; then + chrooted_md5=$(md5sum "${process_bin}" | cut -f 1 -d ' ') + original_md5=$(md5sum "${original_bin}" | cut -f 1 -d ' ') + # compare md5 checksums + if [ "$original_md5" != "$chrooted_md5" ]; then + failed "IS_CHROOTED_BINARY_UPTODATE" "${process_bin} (${pid}) is different than ${original_bin}." + test "${VERBOSE}" = 1 || break + fi + fi + done + done +} +check_nginx_letsencrypt_uptodate() { + if [ -d /etc/nginx ]; then + snippets=$(find /etc/nginx -type f -name "letsencrypt.conf") + if [ -n "${snippets}" ]; then + while read -r snippet; do + if ! grep -qE "^\s*alias\s+/.+/\.well-known/acme-challenge" "${snippet}"; then + failed "IS_NGINX_LETSENCRYPT_UPTODATE" "Nginx snippet ${snippet} is not compatible with Nginx on Debian 8." + fi + done <<< "${snippets}" + fi + fi +} + +check_lxc_container_resolv_conf() { + if is_installed lxc; then + container_list=$(lxc-ls) + current_resolvers=$(grep nameserver /etc/resolv.conf | sed 's/nameserver//g' ) + + for container in $container_list; do + if [ -f "/var/lib/lxc/${container}/rootfs/etc/resolv.conf" ]; then + + while read -r resolver; do + if ! grep -qE "^nameserver\s+${resolver}" "/var/lib/lxc/${container}/rootfs/etc/resolv.conf"; then + failed "IS_LXC_CONTAINER_RESOLV_CONF" "resolv.conf miss-match beween host and container : missing nameserver ${resolver} in container ${container} resolv.conf" + fi + done <<< "${current_resolvers}" + + else + failed "IS_LXC_CONTAINER_RESOLV_CONF" "resolv.conf missing in container ${container}" + fi + done + fi +} +download_versions() { + local file + file=${1:-} + + ## The file is supposed to list programs : each on a line, then its latest version number + ## Examples: + # evoacme 21.06 + # evomaintenance 0.6.4 + + versions_url="https://upgrades.evolix.org/versions-${DEBIAN_RELEASE}" + + # fetch timeout, in seconds + timeout=10 + + if command -v curl > /dev/null; then + curl --max-time ${timeout} --fail --silent --output "${versions_file}" "${versions_url}" + elif command -v wget > /dev/null; then + wget --timeout=${timeout} --quiet "${versions_url}" -O "${versions_file}" + elif command -v GET; then + GET -t ${timeout}s "${versions_url}" > "${versions_file}" + else + failed "IS_CHECK_VERSIONS" "failed to find curl, wget or GET" + fi + test "$?" -eq 0 || failed "IS_CHECK_VERSIONS" "failed to download ${versions_url} to ${versions_file}" +} +get_command() { + local program + program=${1:-} + + case "${program}" in + ## Special cases where the program name is different than the command name + evocheck) echo "${0}" ;; + evomaintenance) command -v "evomaintenance.sh" ;; + 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}" ;; + esac +} +get_version() { + local program + local command + program=${1:-} + command=${2:-} + + case "${program}" in + ## Special case if `command --version => 'command` is not the standard way to get the version + # my_command) + # /path/to/my_command --get-version + # ;; + + add-vm) + grep '^VERSION=' "${command}" | head -1 | cut -d '=' -f 2 + ;; + minifirewall) + ${command} version | 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 + ${command} --version 2> /dev/null | head -1 | cut -d ' ' -f 3 + else + grep '^VERSION=' "${command}" | head -1 | cut -d '=' -f 2 + fi + ;; + + ## General case to get the version + *) ${command} --version 2> /dev/null | head -1 | cut -d ' ' -f 3 ;; + esac +} +check_version() { + local program + local expected_version + program=${1:-} + expected_version=${2:-} + + command=$(get_command "${program}") + if [ -n "${command}" ]; then + # shellcheck disable=SC2086 + 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_CHECK_VERSIONS" "failed to lookup actual version of ${program}" + elif dpkg --compare-versions "${actual_version}" lt "${expected_version}"; then + 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_CHECK_VERSIONS" "${program} version ${actual_version} is newer than expected version ${expected_version}, you should update your index." + else + : # Version check OK + fi + fi +} +add_to_path() { + local new_path + new_path=${1:-} + + echo "$PATH" | grep -qF "${new_path}" || export PATH="${PATH}:${new_path}" +} +check_versions() { + 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" + + grep -v '^ *#' < "${versions_file}" | while IFS= read -r line; do + local program + local version + program=$(echo "${line}" | cut -d ' ' -f 1) + version=$(echo "${line}" | cut -d ' ' -f 2) + + if [ -n "${program}" ]; then + if [ -n "${version}" ]; then + check_version "${program}" "${version}" + else + failed "IS_CHECK_VERSIONS" "failed to lookup expected version for ${program}" + fi + fi + done +} + +main() { + # Default return code : 0 = no error + RC=0 + # 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}" + + test "${IS_TMP_1777:=1}" = 1 && check_tmp_1777 + test "${IS_ROOT_0700:=1}" = 1 && check_root_0700 + test "${IS_USRSHARESCRIPTS:=1}" = 1 && check_usrsharescripts + test "${IS_SSHPERMITROOTNO:=1}" = 1 && check_sshpermitrootno + test "${IS_EVOMAINTENANCEUSERS:=1}" = 1 && check_evomaintenanceusers + # Verification de la configuration d'evomaintenance + test "${IS_EVOMAINTENANCECONF:=1}" = 1 && check_evomaintenanceconf + test "${IS_PRIVKEYWOLRDREADABLE:=1}" = 1 && check_privatekeyworldreadable + + test "${IS_LSBRELEASE:=1}" = 1 && check_lsbrelease + test "${IS_NRPEPOSTFIX:=1}" = 1 && check_nrpepostfix + test "${IS_CUSTOMSUDOERS:=1}" = 1 && check_customsudoers + test "${IS_VARTMPFS:=1}" = 1 && check_vartmpfs + test "${IS_SERVEURBASE:=1}" = 1 && check_serveurbase + test "${IS_LOGROTATECONF:=1}" = 1 && check_logrotateconf + test "${IS_SYSLOGCONF:=1}" = 1 && check_syslogconf + test "${IS_DEBIANSECURITY:=1}" = 1 && check_debiansecurity + test "${IS_APTITUDE:=1}" = 1 && check_aptitude + test "${IS_APTGETBAK:=1}" = 1 && check_aptgetbak + test "${IS_USRRO:=1}" = 1 && check_usrro + test "${IS_TMPNOEXEC:=1}" = 1 && check_tmpnoexec + test "${IS_MOUNT_FSTAB:=1}" = 1 && check_mountfstab + test "${IS_LISTCHANGESCONF:=1}" = 1 && check_listchangesconf + test "${IS_CUSTOMCRONTAB:=1}" = 1 && check_customcrontab + test "${IS_SSHALLOWUSERS:=1}" = 1 && check_sshallowusers + test "${IS_DISKPERF:=0}" = 1 && check_diskperf + test "${IS_TMOUTPROFILE:=1}" = 1 && check_tmoutprofile + test "${IS_ALERT5BOOT:=1}" = 1 && check_alert5boot + test "${IS_ALERT5MINIFW:=1}" = 1 && check_alert5minifw + test "${IS_ALERT5MINIFW:=1}" = 1 && test "${IS_MINIFW:=1}" = 1 && check_minifw + test "${IS_NRPEPERMS:=1}" = 1 && check_nrpeperms + test "${IS_MINIFWPERMS:=1}" = 1 && check_minifwperms + test "${IS_NRPEDISKS:=0}" = 1 && check_nrpedisks + test "${IS_NRPEPID:=1}" = 1 && check_nrpepid + test "${IS_GRSECPROCS:=1}" = 1 && check_grsecprocs + test "${IS_APACHEMUNIN:=1}" = 1 && check_apachemunin + test "${IS_MYSQLUTILS:=1}" = 1 && check_mysqlutils + test "${IS_RAIDSOFT:=1}" = 1 && check_raidsoft + test "${IS_AWSTATSLOGFORMAT:=1}" = 1 && check_awstatslogformat + test "${IS_MUNINLOGROTATE:=1}" = 1 && check_muninlogrotate + test "${IS_SQUID:=1}" = 1 && check_squid + test "${IS_EVOMAINTENANCE_FW:=1}" = 1 && check_evomaintenance_fw + test "${IS_MODDEFLATE:=1}" = 1 && check_moddeflate + test "${IS_LOG2MAILRUNNING:=1}" = 1 && check_log2mailrunning + test "${IS_LOG2MAILAPACHE:=1}" = 1 && check_log2mailapache + test "${IS_LOG2MAILMYSQL:=1}" = 1 && check_log2mailmysql + test "${IS_LOG2MAILSQUID:=1}" = 1 && check_log2mailsquid + test "${IS_BINDCHROOT:=1}" = 1 && check_bindchroot + test "${IS_NETWORK_INTERFACES:=1}" = 1 && check_network_interfaces + test "${IS_AUTOIF:=1}" = 1 && check_autoif + test "${IS_INTERFACESGW:=1}" = 1 && check_interfacesgw + test "${IS_EVOBACKUP:=1}" = 1 && check_evobackup + test "${IS_EVOBACKUP_EXCLUDE_MOUNT:=1}" = 1 && check_evobackup_exclude_mount + test "${IS_USERLOGROTATE:=1}" = 1 && check_userlogrotate + test "${IS_APACHECTL:=1}" = 1 && check_apachectl + test "${IS_APACHESYMLINK:=1}" = 1 && check_apachesymlink + test "${IS_APACHEIPINALLOW:=1}" = 1 && check_apacheipinallow + test "${IS_MUNINAPACHECONF:=1}" = 1 && check_muninapacheconf + test "${IS_PHPMYADMINAPACHECONF:=1}" = 1 && check_phpmyadminapacheconf + test "${IS_KERNELUPTODATE:=1}" = 1 && check_kerneluptodate + test "${IS_UPTIME:=1}" = 1 && check_uptime + test "${IS_MUNINRUNNING:=1}" = 1 && check_muninrunning + test "${IS_BACKUPUPTODATE:=1}" = 1 && check_backupuptodate + test "${IS_ETCGIT:=1}" = 1 && check_etcgit + test "${IS_GITPERMS:=1}" = 1 && check_gitperms + test "${IS_NOTUPGRADED:=1}" = 1 && check_notupgraded + test "${IS_TUNE2FS_M5:=1}" = 1 && check_tune2fs_m5 + test "${IS_BROADCOMFIRMWARE:=1}" = 1 && check_broadcomfirmware + test "${IS_HARDWARERAIDTOOL:=1}" = 1 && check_hardwareraidtool + test "${IS_LISTUPGRADE:=1}" = 1 && check_listupgrade + test "${IS_SQL_BACKUP:=1}" = 1 && check_sql_backup + test "${IS_POSTGRES_BACKUP:=1}" = 1 && check_postgres_backup + test "${IS_MONGO_BACKUP:=1}" = 1 && check_mongo_backup + test "${IS_LDAP_BACKUP:=1}" = 1 && check_ldap_backup + test "${IS_REDIS_BACKUP:=1}" = 1 && check_redis_backup + test "${IS_ELASTIC_BACKUP:=1}" = 1 && check_elastic_backup + test "${IS_DUPLICATE_FS_LABEL:=1}" = 1 && check_duplicate_fs_label + test "${IS_EVOLIX_USER:=1}" = 1 && check_evolix_user + test "${IS_EVOACME_CRON:=1}" = 1 && check_evoacme_cron + test "${IS_EVOACME_LIVELINKS:=1}" = 1 && check_evoacme_livelinks + test "${IS_APACHE_CONFENABLED:=1}" = 1 && check_apache_confenabled + test "${IS_MELTDOWN_SPECTRE:=1}" = 1 && check_meltdown_spectre + test "${IS_OLD_HOME_DIR:=0}" = 1 && check_old_home_dir + test "${IS_EVOBACKUP_INCS:=1}" = 1 && check_evobackup_incs + test "${IS_OSPROBER:=1}" = 1 && check_osprober + test "${IS_JESSIE_BACKPORTS:=1}" = 1 && check_jessie_backports + test "${IS_APT_VALID_UNTIL:=1}" = 1 && check_apt_valid_until + test "${IS_CHROOTED_BINARY_UPTODATE:=1}" = 1 && check_chrooted_binary_uptodate + test "${IS_NGINX_LETSENCRYPT_UPTODATE:=1}" = 1 && check_nginx_letsencrypt_uptodate + test "${IS_LXC_CONTAINER_RESOLV_CONF:=1}" = 1 && check_lxc_container_resolv_conf + test "${IS_CHECK_VERSIONS:=1}" = 1 && check_versions + + if [ -f "${main_output_file}" ]; then + lines_found=$(wc -l < "${main_output_file}") + # 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 +readonly PROGNAME + +# shellcheck disable=SC2124 +ARGS=$@ +readonly ARGS + +# Disable LANG* +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 + +# Parse options +# based on https://gist.github.com/deshion/10d3cb5f88a21671e17a +while :; do + case $1 in + -h|-\?|--help) + show_help + exit 0 + ;; + --version) + show_version + exit 0 + ;; + --cron) + IS_KERNELUPTODATE=0 + IS_UPTIME=0 + IS_MELTDOWN_SPECTRE=0 + IS_CHECK_VERSIONS=0 + IS_NETWORKING_SERVICE=0 + ;; + -v|--verbose) + VERBOSE=1 + ;; + -q|--quiet) + QUIET=1 + VERBOSE=0 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + if [ "${QUIET}" != 1 ]; then + printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2 + fi + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift +done + +# shellcheck disable=SC2086 +main ${ARGS} diff --git a/evocheck/files/evocheck.sh b/evocheck/files/evocheck.sh old mode 100644 new mode 100755 index 2f01afae..52441988 --- a/evocheck/files/evocheck.sh +++ b/evocheck/files/evocheck.sh @@ -1,10 +1,10 @@ #!/bin/bash # EvoCheck -# Script to verify compliance of a Debian/OpenBSD server +# Script to verify compliance of a Linux (Debian) server # powered by Evolix -VERSION="22.03.1" +VERSION="23.04.01" readonly VERSION # base functions @@ -19,7 +19,8 @@ Copyright 2009-2022 Evolix , Gregory Colpart , Jérémy Lecour , Tristan Pilat , - Victor Laborie + Victor Laborie , + Alexis Ben Miloud--Josselin , and others. evocheck comes with ABSOLUTELY NO WARRANTY. This is free software, @@ -29,7 +30,7 @@ END } show_help() { cat <&2 + echo "This version is built for Debian 9 and later." >&2 + exit + fi + if [ -x "${LSB_RELEASE_BIN}" ]; then DEBIAN_RELEASE=$(${LSB_RELEASE_BIN} --codename --short) else - case ${DEBIAN_VERSION} in - 5) DEBIAN_RELEASE="lenny";; - 6) DEBIAN_RELEASE="squeeze";; - 7) DEBIAN_RELEASE="wheezy";; - 8) DEBIAN_RELEASE="jessie";; - 9) DEBIAN_RELEASE="stretch";; + case ${DEBIAN_MAIN_VERSION} in + 9) DEBIAN_RELEASE="stretch";; 10) DEBIAN_RELEASE="buster";; 11) DEBIAN_RELEASE="bullseye";; 12) DEBIAN_RELEASE="bookworm";; esac fi - elif [ "$(uname -s)" = "OpenBSD" ]; then - # use a better release name - OPENBSD_RELEASE=$(uname -r) fi } -is_debian() { - test -n "${DEBIAN_RELEASE}" -} -is_debian_lenny() { - test "${DEBIAN_RELEASE}" = "lenny" -} -is_debian_squeeze() { - test "${DEBIAN_RELEASE}" = "squeeze" -} -is_debian_wheezy() { - test "${DEBIAN_RELEASE}" = "wheezy" -} -is_debian_jessie() { - test "${DEBIAN_RELEASE}" = "jessie" -} is_debian_stretch() { test "${DEBIAN_RELEASE}" = "stretch" } @@ -100,15 +85,6 @@ is_debian_bullseye() { is_debian_bookworm() { test "${DEBIAN_RELEASE}" = "bookworm" } -debian_release() { - printf "%s" "${DEBIAN_RELEASE}" -} -debian_version() { - printf "%s" "${DEBIAN_VERSION}" -} -is_openbsd() { - test -n "${OPENBSD_RELEASE}" -} is_pack_web(){ test -e /usr/share/scripts/web-add.sh || test -e /usr/share/scripts/evoadmin/web-add.sh @@ -121,19 +97,20 @@ is_installed(){ dpkg -l "$pkg" 2> /dev/null | grep -q -E '^(i|h)i' || return 1 done } -minifirewall_file() { - case ${DEBIAN_RELEASE} in - lenny) echo "/etc/firewall.rc" ;; - squeeze) echo "/etc/firewall.rc" ;; - wheezy) echo "/etc/firewall.rc" ;; - jessie) echo "/etc/default/minifirewall" ;; - stretch) echo "/etc/default/minifirewall" ;; - *) echo "/etc/default/minifirewall" ;; - esac -} # logging +log() { + date=$(/bin/date +"${DATE_FORMAT}") + if [ "${1}" != '' ]; then + printf "[%s] %s: %s\\n" "$date" "${PROGNAME}" "${1}" >> "${LOGFILE}" + else + while read line; do + printf "[%s] %s: %s\\n" "$date" "${PROGNAME}" "${line}" >> "${LOGFILE}" + done < /dev/stdin + fi +} + failed() { check_name=$1 shift @@ -147,6 +124,9 @@ failed() { printf "%s FAILED!\n" "${check_name}" >> "${main_output_file}" fi fi + + # Always log verbose + log "${check_name} FAILED! ${check_comments}" } # check functions @@ -162,54 +142,31 @@ check_lsbrelease(){ fi } check_dpkgwarning() { - if is_debian_squeeze; then - if [ "$IS_USRRO" = 1 ] || [ "$IS_TMPNOEXEC" = 1 ]; then - count=$(grep -c -E -i "(Pre-Invoke ..echo Are you sure to have rw on|Post-Invoke ..echo Dont forget to mount -o remount)" /etc/apt/apt.conf) - test "$count" = 2 || failed "IS_DPKGWARNING" "Pre/Post-Invoke are missing." - fi - elif is_debian_wheezy; then - if [ "$IS_USRRO" = 1 ] || [ "$IS_TMPNOEXEC" = 1 ]; then - test -e /etc/apt/apt.conf.d/80evolinux \ - || failed "IS_DPKGWARNING" "/etc/apt/apt.conf.d/80evolinux is missing" - test -e /etc/apt/apt.conf \ - && failed "IS_DPKGWARNING" "/etc/apt/apt.conf is missing" - fi - elif is_debian_stretch || is_debian_buster || is_debian_bullseye; then - test -e /etc/apt/apt.conf.d/z-evolinux.conf \ - || failed "IS_DPKGWARNING" "/etc/apt/apt.conf.d/z-evolinux.conf is missing" - fi + test -e /etc/apt/apt.conf.d/z-evolinux.conf \ + || failed "IS_DPKGWARNING" "/etc/apt/apt.conf.d/z-evolinux.conf is missing" } -check_umasksudoers(){ - if is_debian_squeeze; then - grep -q "^Defaults.*umask=0077" /etc/sudoers \ - || failed "IS_UMASKSUDOERS" "sudoers must set umask to 0077" +# Check if localhost, localhost.localdomain and localhost.$mydomain are set in Postfix mydestination option. +check_postfix_mydestination() { + # shellcheck disable=SC2016 + if ! grep mydestination /etc/postfix/main.cf | grep --quiet -E 'localhost([[:blank:]]|$)'; then + failed "IS_POSTFIX_MYDESTINATION" "'localhost' s missing in Postfix mydestination option." + fi + if ! grep mydestination /etc/postfix/main.cf | grep --quiet 'localhost\.localdomain'; then + failed "IS_POSTFIX_MYDESTINATION" "'localhost.localdomain' is missing in Postfix mydestination option." + fi + if ! grep mydestination /etc/postfix/main.cf | grep --quiet 'localhost\.\$mydomain'; then + failed "IS_POSTFIX_MYDESTINATION" "'localhost.\$mydomain' is missing in Postfix mydestination option." fi } # Verifying check_mailq in Nagios NRPE config file. (Option "-M postfix" need to be set if the MTA is Postfix) check_nrpepostfix() { if is_installed postfix; then - if is_debian_squeeze; then - grep -q "^command.*check_mailq -M postfix" /etc/nagios/nrpe.cfg \ - || failed "IS_NRPEPOSTFIX" "NRPE \"check_mailq\" for postfix is missing" - else - { test -e /etc/nagios/nrpe.cfg \ - && grep -qr "^command.*check_mailq -M postfix" /etc/nagios/nrpe.*; - } || failed "IS_NRPEPOSTFIX" "NRPE \"check_mailq\" for postfix is missing" - fi + { test -e /etc/nagios/nrpe.cfg \ + && grep -qr "^command.*check_mailq -M postfix" /etc/nagios/nrpe.*; + } || failed "IS_NRPEPOSTFIX" "NRPE \"check_mailq\" for postfix is missing" fi } # Check if mod-security config file is present -check_modsecurity() { - if is_debian_squeeze; then - if is_installed libapache-mod-security; then - test -e /etc/apache2/conf.d/mod-security2.conf || failed "IS_MODSECURITY" "missing configuration file" - fi - elif is_debian_wheezy; then - if is_installed libapache2-modsecurity; then - test -e /etc/apache2/conf.d/mod-security2.conf || failed "IS_MODSECURITY" "missing configuration file" - fi - fi -} check_customsudoers() { grep -E -qr "umask=0077" /etc/sudoers* || failed "IS_CUSTOMSUDOERS" "missing umask=0077 in sudoers file" } @@ -232,45 +189,15 @@ check_syslogconf() { || failed "IS_SYSLOGCONF" "syslog evolix config file missing" } 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" - elif is_debian_buster; then - pattern="^deb http://security\.debian\.org/debian-security/? buster/updates main" - elif is_debian_stretch; then - pattern="^deb http://security\.debian\.org/debian-security/? stretch/updates main" - else - pattern="^deb.*security" - fi - - source_file="/etc/apt/sources.list" - grep -qE "${pattern}" "${source_file}" || failed "IS_DEBIANSECURITY" "missing debian security repository" -} -check_aptitudeonly() { - if is_debian_squeeze || is_debian_wheezy; then - test -e /usr/bin/apt-get && failed "IS_APTITUDEONLY" \ - "only aptitude may be enabled on Debian <=7, apt-get should be disabled" - fi + # Look for enabled "Debian-Security" sources from the "Debian" origin + apt-cache policy | grep "\bl=Debian-Security\b" | grep "\bo=Debian\b" | grep --quiet "\bc=main\b" + test $? -eq 0 || failed "IS_DEBIANSECURITY" "missing Debian-Security repository" } check_aptitude() { - if is_debian_jessie || is_debian_stretch || is_debian_buster || is_debian_bullseye; then - test -e /usr/bin/aptitude && failed "IS_APTITUDE" "aptitude may not be installed on Debian >=8" - fi + test -e /usr/bin/aptitude && failed "IS_APTITUDE" "aptitude may not be installed on Debian >=8" } check_aptgetbak() { - if is_debian_jessie || is_debian_stretch || is_debian_buster || is_debian_bullseye; then - test -e /usr/bin/apt-get.bak && failed "IS_APTGETBAK" "prohibit the installation of apt-get.bak with dpkg-divert(1)" - fi -} -check_apticron() { - status="OK" - test -e /etc/cron.d/apticron || status="fail" - test -e /etc/cron.daily/apticron && status="fail" - test "$status" = "fail" || test -e /usr/bin/apt-get.bak || status="fail" - - if is_debian_squeeze || is_debian_wheezy; then - test "$status" = "fail" && failed "IS_APTICRON" "apticron must be in cron.d not cron.daily" - fi + test -e /usr/bin/apt-get.bak && failed "IS_APTGETBAK" "prohibit the installation of apt-get.bak with dpkg-divert(1)" } check_usrro() { grep /usr /etc/fstab | grep -qE "\bro\b" || failed "IS_USRRO" "missing ro directive on fstab for /usr" @@ -295,19 +222,8 @@ check_mountfstab() { fi } check_listchangesconf() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - if is_installed apt-listchanges; then - failed "IS_LISTCHANGESCONF" "apt-listchanges must not be installed on Debian >=9" - fi - else - if [ -e "/etc/apt/listchanges.conf" ]; then - lines=$(grep -cE "(which=both|confirm=1)" /etc/apt/listchanges.conf) - if [ "$lines" != 2 ]; then - failed "IS_LISTCHANGESCONF" "apt-listchanges config is incorrect" - fi - else - failed "IS_LISTCHANGESCONF" "apt-listchanges config is missing" - fi + if is_installed apt-listchanges; then + failed "IS_LISTCHANGESCONF" "apt-listchanges must not be installed on Debian >=9" fi } check_customcrontab() { @@ -326,42 +242,47 @@ check_tmoutprofile() { grep -sq "TMOUT=" /etc/profile /etc/profile.d/evolinux.sh || failed "IS_TMOUTPROFILE" "TMOUT is not set" } check_alert5boot() { - if is_debian_buster || is_debian_bullseye; then + if is_debian_stretch; then + 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" + elif [ -n "$(find /etc/init.d/ -name 'alert5')" ]; then + grep -q "^date" /etc/init.d/alert5 || failed "IS_ALERT5BOOT" "boot mail is not sent by alert5 int script" + else + failed "IS_ALERT5BOOT" "alert5 init script is missing" + fi + else grep -qs "^date" /usr/share/scripts/alert5.sh || failed "IS_ALERT5BOOT" "boot mail is not sent by alert5 init script" 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" - else - failed "IS_ALERT5BOOT" "alert5 init script is missing" - fi fi } check_alert5minifw() { - if is_debian_buster || is_debian_bullseye; then - grep -qs "^/etc/init.d/minifirewall" /usr/share/scripts/alert5.sh \ - || failed "IS_ALERT5MINIFW" "Minifirewall is not started by alert5 script or script is missing" - else + if is_debian_stretch; then if [ -n "$(find /etc/rc2.d/ -name 'S*alert5')" ]; then grep -q "^/etc/init.d/minifirewall" /etc/rc2.d/S*alert5 \ || failed "IS_ALERT5MINIFW" "Minifirewall is not started by alert5 init script" + elif [ -n "$(find /etc/init.d/ -name 'alert5')" ]; then + grep -q "^/etc/init.d/minifirewall" /etc/init.d/alert5 \ + || failed "IS_ALERT5MINIFW" "Minifirewall is not started by alert5 init script" else failed "IS_ALERT5MINIFW" "alert5 init script is missing" fi + else + grep -qs "^/etc/init.d/minifirewall" /usr/share/scripts/alert5.sh \ + || failed "IS_ALERT5MINIFW" "Minifirewall is not started by alert5 script or script is missing" fi } check_minifw() { /sbin/iptables -L -n | grep -q -E "^ACCEPT\s*all\s*--\s*31\.170\.8\.4\s*0\.0\.0\.0/0\s*$" \ - || failed "IS_MINIFW" "minifirewall seems not starded" + || failed "IS_MINIFW" "minifirewall seems not started" } check_minifw_includes() { if is_debian_bullseye; then - if grep -q -e '/sbin/iptables' -e '/sbin/ip6tables' "${MINIFW_FILE}"; then - failed "IS_MINIFWINCLUDES" "minifirewall has direct iptables invocations in ${MINIFW_FILE} that should go in /etc/minifirewall.d/" + if grep -q -e '/sbin/iptables' -e '/sbin/ip6tables' "/etc/default/minifirewall"; then + failed "IS_MINIFWINCLUDES" "minifirewall has direct iptables invocations in /etc/default/minifirewall that should go in /etc/minifirewall.d/" fi fi } @@ -374,10 +295,10 @@ check_nrpeperms() { fi } check_minifwperms() { - if [ -f "$MINIFW_FILE" ]; then - actual=$(stat --format "%a" "$MINIFW_FILE") + if [ -f "/etc/default/minifirewall" ]; then + actual=$(stat --format "%a" "/etc/default/minifirewall") expected="600" - test "$expected" = "$actual" || failed "IS_MINIFWPERMS" "${MINIFW_FILE} must be ${expected}" + test "$expected" = "$actual" || failed "IS_MINIFWPERMS" "/etc/default/minifirewall must be ${expected}" fi } check_nrpedisks() { @@ -390,7 +311,7 @@ check_nrpepid() { { test -e /etc/nagios/nrpe.cfg \ && grep -q "^pid_file=/run/nagios/nrpe.pid" /etc/nagios/nrpe.cfg; } || failed "IS_NRPEPID" "missing or wrong pid_file directive in nrpe.cfg" - elif ! is_debian_squeeze; then + else { test -e /etc/nagios/nrpe.cfg \ && grep -q "^pid_file=/var/run/nagios/nrpe.pid" /etc/nagios/nrpe.cfg; } || failed "IS_NRPEPID" "missing or wrong pid_file directive in nrpe.cfg" @@ -405,20 +326,11 @@ check_grsecprocs() { } check_apachemunin() { if test -e /etc/apache2/apache2.conf; then - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - { test -h /etc/apache2/mods-enabled/status.load \ - && test -h /etc/munin/plugins/apache_accesses \ - && test -h /etc/munin/plugins/apache_processes \ - && test -h /etc/munin/plugins/apache_volume; - } || failed "IS_APACHEMUNIN" "missing munin plugins for Apache" - else - pattern="/server-status-[[:alnum:]]{4,}" - { grep -r -q -s -E "^env.url.*${pattern}" /etc/munin/plugin-conf.d \ - && { grep -q -s -E "${pattern}" /etc/apache2/apache2.conf \ - || grep -q -s -E "${pattern}" /etc/apache2/mods-enabled/status.conf; - }; - } || failed "IS_APACHEMUNIN" "server status is not properly configured" - fi + { test -h /etc/apache2/mods-enabled/status.load \ + && test -h /etc/munin/plugins/apache_accesses \ + && test -h /etc/munin/plugins/apache_processes \ + && test -h /etc/munin/plugins/apache_volume; + } || failed "IS_APACHEMUNIN" "missing munin plugins for Apache" fi } # Verification mytop + Munin si MySQL @@ -426,7 +338,7 @@ check_mysqlutils() { MYSQL_ADMIN=${MYSQL_ADMIN:-mysqladmin} if is_installed mysql-server; then # With Debian 11 and later, root can connect to MariaDB with the socket - if is_debian_wheezy || is_debian_jessie || is_debian_stretch || is_debian_buster; then + if is_debian_stretch || is_debian_buster; then # You can configure MYSQL_ADMIN in evocheck.cf if ! grep -qs "^user *= *${MYSQL_ADMIN}" /root/.my.cnf; then failed "IS_MYSQLUTILS" "${MYSQL_ADMIN} missing in /root/.my.cnf" @@ -467,27 +379,23 @@ check_muninlogrotate() { } # Verification de l'activation de Squid dans le cas d'un pack mail check_squid() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - squidconffile="/etc/squid/evolinux-custom.conf" - else - squidconffile="/etc/squid*/squid.conf" - fi + squidconffile="/etc/squid/evolinux-custom.conf" if is_pack_web && (is_installed squid || is_installed squid3); then host=$(hostname -i) # shellcheck disable=SC2086 http_port=$(grep -E "^http_port\s+[0-9]+" $squidconffile | awk '{ print $2 }') - { grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -m owner --uid-owner proxy -j ACCEPT" "$MINIFW_FILE" \ - && grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -d $host -j ACCEPT" "$MINIFW_FILE" \ - && grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -d 127.0.0.(1|0/8) -j ACCEPT" "$MINIFW_FILE" \ - && grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port.* $http_port" "$MINIFW_FILE"; - } || grep -qE "^PROXY='?on'?" "$MINIFW_FILE" \ + { grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -m owner --uid-owner proxy -j ACCEPT" "/etc/default/minifirewall" \ + && grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -d $host -j ACCEPT" "/etc/default/minifirewall" \ + && grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -d 127.0.0.(1|0/8) -j ACCEPT" "/etc/default/minifirewall" \ + && grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port.* $http_port" "/etc/default/minifirewall"; + } || grep -qE "^PROXY='?on'?" "/etc/default/minifirewall" \ || failed "IS_SQUID" "missing squid rules in minifirewall" fi } check_evomaintenance_fw() { - if [ -f "$MINIFW_FILE" ]; then + if [ -f "/etc/default/minifirewall" ]; then hook_db=$(grep -E '^\s*HOOK_DB' /etc/evomaintenance.cf | tr -d ' ' | cut -d= -f2) - rulesNumber=$(grep -c "/sbin/iptables -A INPUT -p tcp --sport 5432 --dport 1024:65535 -s .* -m state --state ESTABLISHED,RELATED -j ACCEPT" "$MINIFW_FILE") + rulesNumber=$(grep -c "/sbin/iptables -A INPUT -p tcp --sport 5432 --dport 1024:65535 -s .* -m state --state ESTABLISHED,RELATED -j ACCEPT" "/etc/default/minifirewall") if [ "$hook_db" = "1" ] && [ "$rulesNumber" -lt 2 ]; then failed "IS_EVOMAINTENANCE_FW" "HOOK_DB is enabled but missing evomaintenance rules in minifirewall" fi @@ -510,11 +418,7 @@ check_log2mailrunning() { fi } check_log2mailapache() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - conf=/etc/log2mail/config/apache - else - conf=/etc/log2mail/config/default - fi + conf=/etc/log2mail/config/apache if is_pack_web && is_installed log2mail; then grep -s -q "^file = /var/log/apache2/error.log" $conf \ || failed "IS_LOG2MAILAPACHE" "missing log2mail directive for apache" @@ -548,17 +452,6 @@ check_bindchroot() { fi fi } -# Verification de la présence du depot volatile -check_repvolatile() { - if is_debian_lenny; then - grep -qE "^deb http://volatile.debian.org/debian-volatile" /etc/apt/sources.list \ - || failed "IS_REPVOLATILE" "missing debian-volatile repository" - fi - if is_debian_squeeze; then - grep -qE "^deb.*squeeze-updates" /etc/apt/sources.list \ - || failed "IS_REPVOLATILE" "missing squeeze-updates repository" - fi -} # /etc/network/interfaces should be present, we don't manage systemd-network yet check_network_interfaces() { if ! test -f /etc/network/interfaces; then @@ -569,14 +462,10 @@ 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|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 + interfaces=$(/sbin/ip address show up | grep "^[0-9]*:" | grep -E -v "(lo|vnet|docker|veth|tun|tap|macvtap|vrrp|lxcbr|wg)" | cut -d " " -f 2 | tr -d : | cut -d@ -f1 | tr "\n" " ") for interface in $interfaces; do - if ! grep -q "^auto $interface" /etc/network/interfaces; then - failed "IS_AUTOIF" "Network interface \`${interface}' is not set to auto" + if grep -Rq "^iface $interface" /etc/network/interfaces* && ! grep -Rq "^auto $interface" /etc/network/interfaces*; then + failed "IS_AUTOIF" "Network interface \`${interface}' is statically defined but not set to auto" test "${VERBOSE}" = 1 || break fi done @@ -588,23 +477,54 @@ check_interfacesgw() { number=$(grep -Ec "^[^#]*gateway [0-9a-fA-F]+:" /etc/network/interfaces) test "$number" -gt 1 && failed "IS_INTERFACESGW" "there is more than 1 IPv6 gateway" } +# Verification de l’état du service networking +check_networking_service() { + if systemctl is-enabled networking.service > /dev/null; then + if ! systemctl is-active networking.service > /dev/null; then + failed "IS_NETWORKING_SERVICE" "networking.service is not active" + fi + fi +} # Verification de la mise en place d'evobackup check_evobackup() { evobackup_found=$(find /etc/cron* -name '*evobackup*' | wc -l) test "$evobackup_found" -gt 0 || failed "IS_EVOBACKUP" "missing evobackup cron" } +# Vérification de la mise en place d'un cron de purge de la base SQLite de Fail2ban +check_fail2ban_purge() { + if is_debian_stretch || is_debian_buster; then + if is_installed fail2ban; then + test -f /etc/cron.daily/fail2ban_dbpurge || failed "IS_FAIL2BAN_PURGE" "missing script fail2ban_dbpurge cron" + fi + fi +} +# Vérification qu'il ne reste pas des jails nommées ssh non renommées en sshd +check_ssh_fail2ban_jail_renamed() { + if is_installed fail2ban && [ -f /etc/fail2ban/jail.local ]; then + if grep --quiet --fixed-strings "[ssh]" /etc/fail2ban/jail.local; then + failed "IS_SSH_FAIL2BAN_JAIL_RENAMED" "Jail ssh must be renamed sshd in fail2ban >= 0.9." + fi + fi +} # Vérification de l'exclusion des montages (NFS) dans les sauvegardes check_evobackup_exclude_mount() { - excludes_file=$(mktemp --tmpdir=${TMPDIR:-/tmp} "evocheck.evobackup_exclude_mount.XXXXX") - files_to_cleanup="${files_to_cleanup} ${excludes_file}" + excludes_file=$(mktemp --tmpdir "evocheck.evobackup_exclude_mount.XXXXX") + 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 the file seems to be a backup script, with an Rsync invocation + if grep -q "^\s*rsync" "${evobackup_file}"; then + # 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 + fi done } # Verification de la presence du userlogrotate @@ -649,23 +569,21 @@ check_apacheipinallow() { } # Check if default Apache configuration file for munin is absent (or empty or commented). check_muninapacheconf() { - if is_debian_squeeze || is_debian_wheezy; then - muninconf="/etc/apache2/conf.d/munin" - else - muninconf="/etc/apache2/conf-available/munin.conf" - fi + muninconf="/etc/apache2/conf-available/munin.conf" if is_installed apache2; then test -e $muninconf && grep -vEq "^( |\t)*#" "$muninconf" \ && failed "IS_MUNINAPACHECONF" "default munin configuration may be commented or disabled" fi } -# Verification de la priorité du package samba si les backports sont utilisés -check_sambainpriority() { - if is_debian_lenny && is_pack_samba; then - if grep -qrE "^[^#].*backport" /etc/apt/sources.list{,.d}; then - priority=$(grep -E -A2 "^Package:.*samba" /etc/apt/preferences | grep -A1 "^Pin: release a=lenny-backports" | grep "^Pin-Priority:" | cut -f2 -d" ") - test "$priority" -gt 500 || failed "IS_SAMBAPINPRIORITY" "bad pinning priority for samba" - fi +# Check if default Apache configuration file for phpMyAdmin is absent (or empty or commented). +check_phpmyadminapacheconf() { + phpmyadminconf0="/etc/apache2/conf-available/phpmyadmin.conf" + phpmyadminconf1="/etc/apache2/conf-enabled/phpmyadmin.conf" + if is_installed apache2; then + test -e $phpmyadminconf0 && grep -vEq "^( |\t)*#" "$phpmyadminconf0" \ + && failed "IS_PHPMYADMINAPACHECONF" "default phpmyadmin configuration ($phpmyadminconf0) may be commented or disabled" + test -e $phpmyadminconf1 && grep -vEq "^( |\t)*#" "$phpmyadminconf1" \ + && failed "IS_PHPMYADMINAPACHECONF" "default phpmyadmin configuration ($phpmyadminconf1) may be commented or disabled" fi } # Verification si le système doit redémarrer suite màj kernel. @@ -718,12 +636,11 @@ check_backupuptodate() { backup_dir="/home/backup" if [ -d "${backup_dir}" ]; then if [ -n "$(ls -A ${backup_dir})" ]; then - # shellcheck disable=SC2231 - for file in ${backup_dir}/*; do + find "${backup_dir}" -maxdepth 1 -type f | while read -r file; do limit=$(date +"%s" -d "now - 2 day") updated_at=$(stat -c "%Y" "$file") - if [ -f "$file" ] && [ "$limit" -gt "$updated_at" ]; then + if [ "$limit" -gt "$updated_at" ]; then failed "IS_BACKUPUPTODATE" "$file has not been backed up" test "${VERBOSE}" = 1 || break; fi @@ -761,7 +678,7 @@ check_notupgraded() { fi done if $upgraded; then - last_upgrade=$(date +%s -d "$(zgrep -h upgrade /var/log/dpkg.log* | sort -n | tail -1 | cut -f1 -d ' ')") + last_upgrade=$(date +%s -d "$(zgrep --no-filename --no-messages upgrade /var/log/dpkg.log* | sort -n | tail -1 | cut -f1 -d ' ')") fi if grep -qs '^mailto="listupgrade-todo@' /etc/evolinux/listupgrade.cnf \ || grep -qs -E '^[[:digit:]]+[[:space:]]+[[:digit:]]+[[:space:]]+[^\*]' /etc/cron.d/listupgrade; then @@ -809,60 +726,48 @@ check_tune2fs_m5() { done } check_evolinuxsudogroup() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - if grep -q "^evolinux-sudo:" /etc/group; then - 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 + if grep -q "^evolinux-sudo:" /etc/group; then + 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 } check_userinadmgroup() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - users=$(grep "^evolinux-sudo:" /etc/group | awk -F: '{print $4}' | tr ',' ' ') - for user in $users; do - if ! groups "$user" | grep -q adm; then - failed "IS_USERINADMGROUP" "User $user doesn't belong to \`adm' group" - test "${VERBOSE}" = 1 || break - fi - done - fi + users=$(grep "^evolinux-sudo:" /etc/group | awk -F: '{print $4}' | tr ',' ' ') + for user in $users; do + if ! groups "$user" | grep -q adm; then + failed "IS_USERINADMGROUP" "User $user doesn't belong to \`adm' group" + test "${VERBOSE}" = 1 || break + fi + done } check_apache2evolinuxconf() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; 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; - } || failed "IS_APACHE2EVOLINUXCONF" "missing custom evolinux apache config" - fi + 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; + } || failed "IS_APACHE2EVOLINUXCONF" "missing custom evolinux apache config" fi } check_backportsconf() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - grep -qsE "^[^#].*backports" /etc/apt/sources.list \ - && failed "IS_BACKPORTSCONF" "backports can't be in main sources list" - if grep -qsE "^[^#].*backports" /etc/apt/sources.list.d/*.list; then - grep -qsE "^[^#].*backports" /etc/apt/preferences.d/* \ - || failed "IS_BACKPORTSCONF" "backports must have preferences" - fi + grep -qsE "^[^#].*backports" /etc/apt/sources.list \ + && failed "IS_BACKPORTSCONF" "backports can't be in main sources list" + if grep -qsE "^[^#].*backports" /etc/apt/sources.list.d/*.list; then + grep -qsE "^[^#].*backports" /etc/apt/preferences.d/* \ + || failed "IS_BACKPORTSCONF" "backports must have preferences" fi } check_bind9munin() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - if is_installed bind9; then - { test -L /etc/munin/plugins/bind9 \ - && test -e /etc/munin/plugin-conf.d/bind9; - } || failed "IS_BIND9MUNIN" "missing bind plugin for munin" - fi + if is_installed bind9; then + { test -L /etc/munin/plugins/bind9 \ + && test -e /etc/munin/plugin-conf.d/bind9; + } || failed "IS_BIND9MUNIN" "missing bind plugin for munin" fi } check_bind9logrotate() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - if is_installed bind9; then - test -e /etc/logrotate.d/bind9 || failed "IS_BIND9LOGROTATE" "missing bind logrotate file" - fi + if is_installed bind9; then + test -e /etc/logrotate.d/bind9 || failed "IS_BIND9LOGROTATE" "missing bind logrotate file" fi } check_broadcomfirmware() { @@ -893,14 +798,12 @@ check_hardwareraidtool() { fi } check_log2mailsystemdunit() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - systemctl -q is-active log2mail.service \ - || failed "IS_LOG2MAILSYSTEMDUNIT" "log2mail unit not running" - test -f /etc/systemd/system/log2mail.service \ - || failed "IS_LOG2MAILSYSTEMDUNIT" "missing log2mail unit file" - test -f /etc/init.d/log2mail \ - && failed "IS_LOG2MAILSYSTEMDUNIT" "/etc/init.d/log2mail may be deleted (use systemd unit)" - fi + systemctl -q is-active log2mail.service \ + || failed "IS_LOG2MAILSYSTEMDUNIT" "log2mail unit not running" + test -f /etc/systemd/system/log2mail.service \ + || failed "IS_LOG2MAILSYSTEMDUNIT" "missing log2mail unit file" + test -f /etc/init.d/log2mail \ + && failed "IS_LOG2MAILSYSTEMDUNIT" "/etc/init.d/log2mail may be deleted (use systemd unit)" } check_listupgrade() { test -f /etc/cron.d/listupgrade \ @@ -909,13 +812,11 @@ check_listupgrade() { || failed "IS_LISTUPGRADE" "missing listupgrade script or not executable" } check_mariadbevolinuxconf() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - if is_installed mariadb-server; then - { test -f /etc/mysql/mariadb.conf.d/z-evolinux-defaults.cnf \ - && test -f /etc/mysql/mariadb.conf.d/zzz-evolinux-custom.cnf; - } || failed "IS_MARIADBEVOLINUXCONF" "missing mariadb custom config" + if is_installed mariadb-server; then + { test -f /etc/mysql/mariadb.conf.d/z-evolinux-defaults.cnf \ + && test -f /etc/mysql/mariadb.conf.d/zzz-evolinux-custom.cnf; + } || failed "IS_MARIADBEVOLINUXCONF" "missing mariadb custom config" fi - fi } check_sql_backup() { if (is_installed "mysql-server" || is_installed "mariadb-server"); then @@ -947,7 +848,7 @@ check_mongo_backup() { # You could change the default path in /etc/evocheck.cf MONGO_BACKUP_PATH=${MONGO_BACKUP_PATH:-"/home/backup/mongodump"} if [ -d "$MONGO_BACKUP_PATH" ]; then - for file in "${MONGO_BACKUP_PATH}"/*/*.{json,bson}; do + for file in "${MONGO_BACKUP_PATH}"/*/*.{json,bson}*; do # Skip indexes file. if ! [[ "$file" =~ indexes ]]; then limit=$(date +"%s" -d "now - 2 day") @@ -973,8 +874,19 @@ check_ldap_backup() { check_redis_backup() { if is_installed redis-server; then # You could change the default path in /etc/evocheck.cf - REDIS_BACKUP_PATH=${REDIS_BACKUP_PATH:-"/home/backup/dump.rdb"} - test -f "$REDIS_BACKUP_PATH" || failed "IS_REDIS_BACKUP" "Redis dump is missing (${REDIS_BACKUP_PATH})" + # REDIS_BACKUP_PATH may contain space-separated paths, example: + # REDIS_BACKUP_PATH='/home/backup/redis-instance1/dump.rdb /home/backup/redis-instance2/dump.rdb' + # Old default path: /home/backup/dump.rdb + # New default path: /home/backup/redis/dump.rdb + if [ -z "${REDIS_BACKUP_PATH}" ]; then + if ! [ -f "/home/backup/dump.rdb" ] && ! [ -f "/home/backup/redis/dump.rdb" ]; then + failed "IS_REDIS_BACKUP" "Redis dump is missing (/home/backup/dump.rdb or /home/backup/redis/dump.rdb)." + fi + else + for file in ${REDIS_BACKUP_PATH}; do + test -f "${file}" || failed "IS_REDIS_BACKUP" "Redis dump ${file} is missing." + done + fi fi } check_elastic_backup() { @@ -996,81 +908,71 @@ check_mariadbsystemdunit() { fi } check_mysqlmunin() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - if is_installed mariadb-server; then - for file in mysql_bytes mysql_queries mysql_slowqueries \ - mysql_threads mysql_connections mysql_files_tables \ - mysql_innodb_bpool mysql_innodb_bpool_act mysql_innodb_io \ - mysql_innodb_log mysql_innodb_rows mysql_innodb_semaphores \ - mysql_myisam_indexes mysql_qcache mysql_qcache_mem \ - mysql_sorts mysql_tmp_tables; do + if is_installed mariadb-server; then + for file in mysql_bytes mysql_queries mysql_slowqueries \ + mysql_threads mysql_connections mysql_files_tables \ + mysql_innodb_bpool mysql_innodb_bpool_act mysql_innodb_io \ + mysql_innodb_log mysql_innodb_rows mysql_innodb_semaphores \ + mysql_myisam_indexes mysql_qcache mysql_qcache_mem \ + mysql_sorts mysql_tmp_tables; do - if [[ ! -L /etc/munin/plugins/$file ]]; then - failed "IS_MYSQLMUNIN" "missing munin plugin '$file'" - 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 + if [[ ! -L /etc/munin/plugins/$file ]]; then + failed "IS_MYSQLMUNIN" "missing munin plugin '$file'" + 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 } check_mysqlnrpe() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - if is_installed mariadb-server; then - nagios_file=~nagios/.my.cnf - if ! test -f ${nagios_file}; then - failed "IS_MYSQLNRPE" "${nagios_file} is missing" - elif [ "$(stat -c %U ${nagios_file})" != "nagios" ] \ - || [ "$(stat -c %a ${nagios_file})" != "600" ]; then - failed "IS_MYSQLNRPE" "${nagios_file} has wrong permissions" - else - grep -q -F "command[check_mysql]=/usr/lib/nagios/plugins/check_mysql" /etc/nagios/nrpe.d/evolix.cfg \ - || failed "IS_MYSQLNRPE" "check_mysql is missing" - fi + if is_installed mariadb-server; then + nagios_file=~nagios/.my.cnf + if ! test -f ${nagios_file}; then + failed "IS_MYSQLNRPE" "${nagios_file} is missing" + elif [ "$(stat -c %U ${nagios_file})" != "nagios" ] \ + || [ "$(stat -c %a ${nagios_file})" != "600" ]; then + failed "IS_MYSQLNRPE" "${nagios_file} has wrong permissions" + else + grep -q -F "command[check_mysql]=/usr/lib/nagios/plugins/check_mysql" /etc/nagios/nrpe.d/evolix.cfg \ + || failed "IS_MYSQLNRPE" "check_mysql is missing" fi fi } check_phpevolinuxconf() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - is_debian_stretch && phpVersion="7.0" - is_debian_buster && phpVersion="7.3" - is_debian_bullseye && phpVersion="7.4" - if is_installed php; then - { test -f /etc/php/${phpVersion}/cli/conf.d/z-evolinux-defaults.ini \ - && test -f /etc/php/${phpVersion}/cli/conf.d/zzz-evolinux-custom.ini - } || failed "IS_PHPEVOLINUXCONF" "missing php evolinux config" - fi + is_debian_stretch && phpVersion="7.0" + is_debian_buster && phpVersion="7.3" + is_debian_bullseye && phpVersion="7.4" + if is_installed php; then + { test -f "/etc/php/${phpVersion}/cli/conf.d/z-evolinux-defaults.ini" \ + && test -f "/etc/php/${phpVersion}/cli/conf.d/zzz-evolinux-custom.ini" + } || failed "IS_PHPEVOLINUXCONF" "missing php evolinux config" fi } check_squidlogrotate() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - if is_installed squid; then - grep -q -e monthly -e daily /etc/logrotate.d/squid \ - || failed "IS_SQUIDLOGROTATE" "missing squid logrotate file" - fi + if is_installed squid; then + grep -q -e monthly -e daily /etc/logrotate.d/squid \ + || failed "IS_SQUIDLOGROTATE" "missing squid logrotate file" fi } check_squidevolinuxconf() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - if is_installed squid; then - { grep -qs "^CONFIG=/etc/squid/evolinux-defaults.conf$" /etc/default/squid \ - && test -f /etc/squid/evolinux-defaults.conf \ - && test -f /etc/squid/evolinux-whitelist-defaults.conf \ - && test -f /etc/squid/evolinux-whitelist-custom.conf \ - && test -f /etc/squid/evolinux-acl.conf \ - && test -f /etc/squid/evolinux-httpaccess.conf \ - && test -f /etc/squid/evolinux-custom.conf; - } || failed "IS_SQUIDEVOLINUXCONF" "missing squid evolinux config" - fi + if is_installed squid; then + { grep -qs "^CONFIG=/etc/squid/evolinux-defaults.conf$" /etc/default/squid \ + && test -f /etc/squid/evolinux-defaults.conf \ + && test -f /etc/squid/evolinux-whitelist-defaults.conf \ + && test -f /etc/squid/evolinux-whitelist-custom.conf \ + && test -f /etc/squid/evolinux-acl.conf \ + && test -f /etc/squid/evolinux-httpaccess.conf \ + && test -f /etc/squid/evolinux-custom.conf; + } || failed "IS_SQUIDEVOLINUXCONF" "missing squid evolinux config" fi } check_duplicate_fs_label() { # Do it only if thereis blkid binary BLKID_BIN=$(command -v blkid) if [ -n "$BLKID_BIN" ]; then - tmpFile=$(mktemp --tmpdir=${TMPDIR:-/tmp} "evocheck.duplicate_fs_label.XXXXX") - files_to_cleanup="${files_to_cleanup} ${tmpFile}" + tmpFile=$(mktemp --tmpdir "evocheck.duplicate_fs_label.XXXXX") + 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 @@ -1128,41 +1030,20 @@ check_apache_confenabled() { # Starting from Jessie and Apache 2.4, /etc/apache2/conf.d/ # must be replaced by conf-available/ and config files symlinked # to conf-enabled/ - if is_debian_jessie || is_debian_stretch || is_debian_buster || is_debian_bullseye; then - if [ -f /etc/apache2/apache2.conf ]; then - test -d /etc/apache2/conf.d/ \ - && failed "IS_APACHE_CONFENABLED" "apache's conf.d directory must not exists" - grep -q 'Include conf.d' /etc/apache2/apache2.conf \ - && failed "IS_APACHE_CONFENABLED" "apache2.conf must not Include conf.d" - fi + if [ -f /etc/apache2/apache2.conf ]; then + test -d /etc/apache2/conf.d/ \ + && failed "IS_APACHE_CONFENABLED" "apache's conf.d directory must not exists" + grep -q 'Include conf.d' /etc/apache2/apache2.conf \ + && failed "IS_APACHE_CONFENABLED" "apache2.conf must not Include conf.d" fi } check_meltdown_spectre() { - # For Stretch, detection is easy as the kernel use # /sys/devices/system/cpu/vulnerabilities/ - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - for vuln in meltdown spectre_v1 spectre_v2; do - test -f "/sys/devices/system/cpu/vulnerabilities/$vuln" \ - || failed "IS_MELTDOWN_SPECTRE" "vulnerable to $vuln" - test "${VERBOSE}" = 1 || break - done - # For Jessie this is quite complicated to verify and we need to use kernel config file - elif is_debian_jessie; then - if grep -q "BOOT_IMAGE=" /proc/cmdline; then - kernelPath=$(grep -Eo 'BOOT_IMAGE=[^ ]+' /proc/cmdline | cut -d= -f2) - kernelVer=${kernelPath##*/vmlinuz-} - kernelConfig="config-${kernelVer}" - # Sometimes autodetection of kernel config file fail, so we test if the file really exists. - if [ -f "/boot/${kernelConfig}" ]; then - grep -Eq '^CONFIG_PAGE_TABLE_ISOLATION=y' "/boot/$kernelConfig" \ - || failed "IS_MELTDOWN_SPECTRE" \ - "PAGE_TABLE_ISOLATION must be enabled in kernel, outdated kernel?" - grep -Eq '^CONFIG_RETPOLINE=y' "/boot/$kernelConfig" \ - || failed "IS_MELTDOWN_SPECTRE" \ - "RETPOLINE must be enabled in kernel, outdated kernel?" - fi - fi - fi + for vuln in meltdown spectre_v1 spectre_v2; do + test -f "/sys/devices/system/cpu/vulnerabilities/$vuln" \ + || failed "IS_MELTDOWN_SPECTRE" "vulnerable to $vuln" + test "${VERBOSE}" = 1 || break + done } check_old_home_dir() { homeDir=${homeDir:-/home} @@ -1193,28 +1074,24 @@ check_usrsharescripts() { test "$expected" = "$actual" || failed "IS_USRSHARESCRIPTS" "/usr/share/scripts must be $expected" } check_sshpermitrootno() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - if grep -q "^PermitRoot" /etc/ssh/sshd_config; then - grep -E -qi "PermitRoot.*no" /etc/ssh/sshd_config \ - || failed "IS_SSHPERMITROOTNO" "PermitRoot should be set at no" - fi + sshd_args="-C addr=,user=,host=,laddr=,lport=0" + if is_debian_stretch; then + # Noop, we'll use the default $sshd_args + : + elif is_debian_buster; then + sshd_args="${sshd_args},rdomain=" else - grep -E -qi "PermitRoot.*no" /etc/ssh/sshd_config \ - || failed "IS_SSHPERMITROOTNO" "PermitRoot should be set at no" + # NOTE: From Debian Bullseye 11 onward, with OpenSSH 8.1, the argument + # -T doesn't require the additional -C. + sshd_args= + fi + # shellcheck disable=SC2086 + if ! (sshd -T ${sshd_args} 2> /dev/null | grep -qi 'permitrootlogin no'); then + failed "IS_SSHPERMITROOTNO" "PermitRoot should be set to no" fi } check_evomaintenanceusers() { - if is_debian_stretch || is_debian_buster || is_debian_bullseye; then - users=$(getent group evolinux-sudo | cut -d':' -f4 | tr ',' ' ') - else - if [ -f /etc/sudoers.d/evolinux ]; then - sudoers="/etc/sudoers.d/evolinux" - else - sudoers="/etc/sudoers" - fi - # combine users from User_Alias and sudo group - users=$({ grep "^User_Alias *ADMIN" $sudoers | cut -d= -f2 | tr -d " "; grep "^sudo" /etc/group | cut -d: -f 4; } | tr "," "\n" | sort -u) - fi + users=$(getent group evolinux-sudo | cut -d':' -f4 | tr ',' ' ') for user in $users; do user_home=$(getent passwd "$user" | cut -d: -f6) if [ -n "$user_home" ] && [ -d "$user_home" ]; then @@ -1262,8 +1139,8 @@ check_evobackup_incs() { bkctld_cron_file=${bkctld_cron_file:-/etc/cron.d/bkctld} if [ -f "${bkctld_cron_file}" ]; then root_crontab=$(grep -v "^#" "${bkctld_cron_file}") - echo "${root_crontab}" | grep -q "bkctld inc" || failed "IS_EVOBACKUP_INCS" "\`bkctld inc' is missing in ${bkctld_cron_file}" - echo "${root_crontab}" | grep -qE "(check-incs.sh|bkctld check-incs)" || failed "IS_EVOBACKUP_INCS" "\`check-incs.sh' is missing in ${bkctld_cron_file}" + echo "${root_crontab}" | grep -q "bkctld inc" || failed "IS_EVOBACKUP_INCS" "'bkctld inc' is missing in ${bkctld_cron_file}" + echo "${root_crontab}" | grep -qE "(check-incs.sh|bkctld check-incs)" || failed "IS_EVOBACKUP_INCS" "'check-incs.sh' is missing in ${bkctld_cron_file}" else failed "IS_EVOBACKUP_INCS" "Crontab \`${bkctld_cron_file}' is missing" fi @@ -1277,17 +1154,6 @@ check_osprober() { fi } -check_jessie_backports() { - if is_debian_jessie; then - jessieBackports=$(grep -hs "jessie-backports" /etc/apt/sources.list /etc/apt/sources.list.d/*) - if test -n "$jessieBackports"; then - if ! grep -q "archive.debian.org" <<< "$jessieBackports"; then - failed "IS_JESSIE_BACKPORTS" "You must use deb http://archive.debian.org/debian/ jessie-backports main" - fi - fi - fi -} - check_apt_valid_until() { aptvalidFile="/etc/apt/apt.conf.d/99no-check-valid-until" aptvalidText="Acquire::Check-Valid-Until no;" @@ -1305,7 +1171,7 @@ check_chrooted_binary_uptodate() { for process_name in ${process_list}; do # what is the binary path? original_bin=$(command -v "${process_name}") - for pid in $(pgrep ${process_name}); do + for pid in $(pgrep "${process_name}"); do process_bin=$(realpath "/proc/${pid}/exe") # Is the process chrooted? real_root=$(realpath "/proc/${pid}/root") @@ -1326,20 +1192,13 @@ check_nginx_letsencrypt_uptodate() { snippets=$(find /etc/nginx -type f -name "letsencrypt.conf") if [ -n "${snippets}" ]; then while read -r snippet; do - if is_debian_jessie; then - if ! grep -qE "^\s*alias\s+/.+/\.well-known/acme-challenge" "${snippet}"; then - failed "IS_NGINX_LETSENCRYPT_UPTODATE" "Nginx snippet ${snippet} is not compatible with Nginx on Debian 8." - fi - else - if grep -qE "^\s*alias\s+/.+/\.well-known/acme-challenge" "${snippet}"; then - failed "IS_NGINX_LETSENCRYPT_UPTODATE" "Nginx snippet ${snippet} is not compatible with Nginx on Debian 9+." - fi + if grep -qE "^\s*alias\s+/.+/\.well-known/acme-challenge" "${snippet}"; then + failed "IS_NGINX_LETSENCRYPT_UPTODATE" "Nginx snippet ${snippet} is not compatible with Nginx on Debian 9+." fi done <<< "${snippets}" fi fi } - check_lxc_container_resolv_conf() { if is_installed lxc; then container_list=$(lxc-ls) @@ -1360,6 +1219,38 @@ check_lxc_container_resolv_conf() { done fi } +# Check that there are containers if lxc is installed. +check_no_lxc_container() { + if is_installed lxc; then + containers_count=$(lxc-ls | wc -l) + if [ "$containers_count" -eq 0 ]; then + failed "IS_NO_LXC_CONTAINER" "LXC is installed but have no container. Consider removing it." + fi + fi +} +# Check that in LXC containers, phpXX-fpm services have UMask set to 0007. +check_lxc_php_fpm_service_umask_set() { + if is_installed lxc; then + php_containers_list=$(lxc-ls --filter php) + missing_umask="" + for container in $php_containers_list; do + # Translate container name in service name + if [ "$container" = "php56" ]; then + service="php5-fpm" + else + service="${container:0:4}.${container:4}-fpm" + fi + umask=$(lxc-attach --name "${container}" -- systemctl show -p UMask "$service" | cut -d "=" -f2) + if [ "$umask" != "0007" ]; then + missing_umask="${missing_umask} ${container}" + fi + done + if [ -n "${missing_umask}" ]; then + failed "IS_LXC_PHP_FPM_SERVICE_UMASK_SET" "UMask is not set to 0007 in PHP-FPM services of theses containers : ${missing_umask}." + fi + fi +} + download_versions() { local file file=${1:-} @@ -1369,13 +1260,7 @@ download_versions() { # evoacme 21.06 # evomaintenance 0.6.4 - if is_debian; then - versions_url="https://upgrades.evolix.org/versions-${DEBIAN_RELEASE}" - elif is_openbsd; then - versions_url="https://upgrades.evolix.org/versions-${OPENBSD_RELEASE}" - else - failed "IS_VERSIONS_CHECK" "error determining os release" - fi + versions_url="https://upgrades.evolix.org/versions-${DEBIAN_RELEASE}" # fetch timeout, in seconds timeout=10 @@ -1387,9 +1272,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 @@ -1424,7 +1309,7 @@ get_version() { grep '^VERSION=' "${command}" | head -1 | cut -d '=' -f 2 ;; minifirewall) - ${command} status | head -1 | cut -d ' ' -f 3 + ${command} version | head -1 | cut -d ' ' -f 3 ;; ## Let's try the --version flag before falling back to grep for the constant kvmstats) @@ -1451,11 +1336,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 @@ -1468,8 +1353,8 @@ add_to_path() { echo "$PATH" | grep -qF "${new_path}" || export PATH="${PATH}:${new_path}" } check_versions() { - versions_file=$(mktemp --tmpdir=${TMPDIR:-/tmp} "evocheck.versions.XXXXX") - files_to_cleanup="${files_to_cleanup} ${versions_file}" + versions_file=$(mktemp --tmpdir "evocheck.versions.XXXXX") + files_to_cleanup+=("${versions_file}") download_versions "${versions_file}" add_to_path "/usr/share/scripts" @@ -1484,7 +1369,7 @@ 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 @@ -1496,12 +1381,8 @@ 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 - #----------------------------------------------------------- + main_output_file=$(mktemp --tmpdir "evocheck.main.XXXXX") + files_to_cleanup+=("${main_output_file}") test "${IS_TMP_1777:=1}" = 1 && check_tmp_1777 test "${IS_ROOT_0700:=1}" = 1 && check_root_0700 @@ -1512,223 +1393,115 @@ main() { test "${IS_EVOMAINTENANCECONF:=1}" = 1 && check_evomaintenanceconf test "${IS_PRIVKEYWOLRDREADABLE:=1}" = 1 && check_privatekeyworldreadable - #----------------------------------------------------------- - # Vérifie si c'est une debian et fait les tests appropriés. - #----------------------------------------------------------- - - if is_debian; then - MINIFW_FILE=$(minifirewall_file) - - test "${IS_LSBRELEASE:=1}" = 1 && check_lsbrelease - test "${IS_DPKGWARNING:=1}" = 1 && check_dpkgwarning - test "${IS_UMASKSUDOERS:=1}" = 1 && check_umasksudoers - test "${IS_NRPEPOSTFIX:=1}" = 1 && check_nrpepostfix - test "${IS_MODSECURITY:=1}" = 1 && check_modsecurity - test "${IS_CUSTOMSUDOERS:=1}" = 1 && check_customsudoers - test "${IS_VARTMPFS:=1}" = 1 && check_vartmpfs - test "${IS_SERVEURBASE:=1}" = 1 && check_serveurbase - test "${IS_LOGROTATECONF:=1}" = 1 && check_logrotateconf - test "${IS_SYSLOGCONF:=1}" = 1 && check_syslogconf - test "${IS_DEBIANSECURITY:=1}" = 1 && check_debiansecurity - test "${IS_APTITUDEONLY:=1}" = 1 && check_aptitudeonly - test "${IS_APTITUDE:=1}" = 1 && check_aptitude - test "${IS_APTGETBAK:=1}" = 1 && check_aptgetbak - test "${IS_APTICRON:=0}" = 1 && check_apticron - test "${IS_USRRO:=1}" = 1 && check_usrro - test "${IS_TMPNOEXEC:=1}" = 1 && check_tmpnoexec - test "${IS_MOUNT_FSTAB:=1}" = 1 && check_mountfstab - test "${IS_LISTCHANGESCONF:=1}" = 1 && check_listchangesconf - test "${IS_CUSTOMCRONTAB:=1}" = 1 && check_customcrontab - test "${IS_SSHALLOWUSERS:=1}" = 1 && check_sshallowusers - test "${IS_DISKPERF:=0}" = 1 && check_diskperf - test "${IS_TMOUTPROFILE:=1}" = 1 && check_tmoutprofile - test "${IS_ALERT5BOOT:=1}" = 1 && check_alert5boot - test "${IS_ALERT5MINIFW:=1}" = 1 && check_alert5minifw - test "${IS_ALERT5MINIFW:=1}" = 1 && test "${IS_MINIFW:=1}" = 1 && check_minifw - test "${IS_NRPEPERMS:=1}" = 1 && check_nrpeperms - test "${IS_MINIFWPERMS:=1}" = 1 && check_minifwperms - # Enable when minifirewall is released - test "${IS_MINIFWINCLUDES:=0}" = 1 && check_minifw_includes - test "${IS_NRPEDISKS:=0}" = 1 && check_nrpedisks - test "${IS_NRPEPID:=1}" = 1 && check_nrpepid - test "${IS_GRSECPROCS:=1}" = 1 && check_grsecprocs - test "${IS_APACHEMUNIN:=1}" = 1 && check_apachemunin - test "${IS_MYSQLUTILS:=1}" = 1 && check_mysqlutils - test "${IS_RAIDSOFT:=1}" = 1 && check_raidsoft - test "${IS_AWSTATSLOGFORMAT:=1}" = 1 && check_awstatslogformat - test "${IS_MUNINLOGROTATE:=1}" = 1 && check_muninlogrotate - test "${IS_SQUID:=1}" = 1 && check_squid - test "${IS_EVOMAINTENANCE_FW:=1}" = 1 && check_evomaintenance_fw - test "${IS_MODDEFLATE:=1}" = 1 && check_moddeflate - test "${IS_LOG2MAILRUNNING:=1}" = 1 && check_log2mailrunning - test "${IS_LOG2MAILAPACHE:=1}" = 1 && check_log2mailapache - test "${IS_LOG2MAILMYSQL:=1}" = 1 && check_log2mailmysql - test "${IS_LOG2MAILSQUID:=1}" = 1 && check_log2mailsquid - test "${IS_BINDCHROOT:=1}" = 1 && check_bindchroot - test "${IS_REPVOLATILE:=1}" = 1 && check_repvolatile - test "${IS_NETWORK_INTERFACES:=1}" = 1 && check_network_interfaces - test "${IS_AUTOIF:=1}" = 1 && check_autoif - test "${IS_INTERFACESGW:=1}" = 1 && check_interfacesgw - test "${IS_EVOBACKUP:=1}" = 1 && check_evobackup - test "${IS_EVOBACKUP_EXCLUDE_MOUNT:=1}" = 1 && check_evobackup_exclude_mount - test "${IS_USERLOGROTATE:=1}" = 1 && check_userlogrotate - test "${IS_APACHECTL:=1}" = 1 && check_apachectl - test "${IS_APACHESYMLINK:=1}" = 1 && check_apachesymlink - test "${IS_APACHEIPINALLOW:=1}" = 1 && check_apacheipinallow - test "${IS_MUNINAPACHECONF:=1}" = 1 && check_muninapacheconf - test "${IS_SAMBAPINPRIORITY:=1}" = 1 && check_sambainpriority - test "${IS_KERNELUPTODATE:=1}" = 1 && check_kerneluptodate - test "${IS_UPTIME:=1}" = 1 && check_uptime - test "${IS_MUNINRUNNING:=1}" = 1 && check_muninrunning - test "${IS_BACKUPUPTODATE:=1}" = 1 && check_backupuptodate - test "${IS_ETCGIT:=1}" = 1 && check_etcgit - test "${IS_GITPERMS:=1}" = 1 && check_gitperms - test "${IS_NOTUPGRADED:=1}" = 1 && check_notupgraded - test "${IS_TUNE2FS_M5:=1}" = 1 && check_tune2fs_m5 - test "${IS_EVOLINUXSUDOGROUP:=1}" = 1 && check_evolinuxsudogroup - test "${IS_USERINADMGROUP:=1}" = 1 && check_userinadmgroup - test "${IS_APACHE2EVOLINUXCONF:=1}" = 1 && check_apache2evolinuxconf - test "${IS_BACKPORTSCONF:=1}" = 1 && check_backportsconf - test "${IS_BIND9MUNIN:=1}" = 1 && check_bind9munin - test "${IS_BIND9LOGROTATE:=1}" = 1 && check_bind9logrotate - test "${IS_BROADCOMFIRMWARE:=1}" = 1 && check_broadcomfirmware - test "${IS_HARDWARERAIDTOOL:=1}" = 1 && check_hardwareraidtool - test "${IS_LOG2MAILSYSTEMDUNIT:=1}" = 1 && check_log2mailsystemdunit - test "${IS_LISTUPGRADE:=1}" = 1 && check_listupgrade - test "${IS_MARIADBEVOLINUXCONF:=0}" = 1 && check_mariadbevolinuxconf - test "${IS_SQL_BACKUP:=1}" = 1 && check_sql_backup - test "${IS_POSTGRES_BACKUP:=1}" = 1 && check_postgres_backup - test "${IS_MONGO_BACKUP:=1}" = 1 && check_mongo_backup - test "${IS_LDAP_BACKUP:=1}" = 1 && check_ldap_backup - test "${IS_REDIS_BACKUP:=1}" = 1 && check_redis_backup - test "${IS_ELASTIC_BACKUP:=1}" = 1 && check_elastic_backup - test "${IS_MARIADBSYSTEMDUNIT:=1}" = 1 && check_mariadbsystemdunit - test "${IS_MYSQLMUNIN:=1}" = 1 && check_mysqlmunin - test "${IS_MYSQLNRPE:=1}" = 1 && check_mysqlnrpe - test "${IS_PHPEVOLINUXCONF:=0}" = 1 && check_phpevolinuxconf - test "${IS_SQUIDLOGROTATE:=1}" = 1 && check_squidlogrotate - test "${IS_SQUIDEVOLINUXCONF:=1}" = 1 && check_squidevolinuxconf - test "${IS_DUPLICATE_FS_LABEL:=1}" = 1 && check_duplicate_fs_label - test "${IS_EVOLIX_USER:=1}" = 1 && check_evolix_user - test "${IS_EVOACME_CRON:=1}" = 1 && check_evoacme_cron - test "${IS_EVOACME_LIVELINKS:=1}" = 1 && check_evoacme_livelinks - test "${IS_APACHE_CONFENABLED:=1}" = 1 && check_apache_confenabled - test "${IS_MELTDOWN_SPECTRE:=1}" = 1 && check_meltdown_spectre - test "${IS_OLD_HOME_DIR:=0}" = 1 && check_old_home_dir - test "${IS_EVOBACKUP_INCS:=1}" = 1 && check_evobackup_incs - test "${IS_OSPROBER:=1}" = 1 && check_osprober - test "${IS_JESSIE_BACKPORTS:=1}" = 1 && check_jessie_backports - test "${IS_APT_VALID_UNTIL:=1}" = 1 && check_apt_valid_until - test "${IS_CHROOTED_BINARY_UPTODATE:=1}" = 1 && check_chrooted_binary_uptodate - test "${IS_NGINX_LETSENCRYPT_UPTODATE:=1}" = 1 && check_nginx_letsencrypt_uptodate - test "${IS_LXC_CONTAINER_RESOLV_CONF:=1}" = 1 && check_lxc_container_resolv_conf - test "${IS_CHECK_VERSIONS:=1}" = 1 && check_versions - fi - - #----------------------------------------------------------- - # Tests spécifiques à OpenBSD - #----------------------------------------------------------- - - if is_openbsd; then - - if [ "${IS_SOFTDEP:=1}" = 1 ]; then - grep -q "softdep" /etc/fstab || failed "IS_SOFTDEP" - fi - - if [ "${IS_WHEEL:=1}" = 1 ]; then - grep -qE "^%wheel.*$" /etc/sudoers || failed "IS_WHEEL" - fi - - if [ "${IS_SUDOADMIN:=1}" = 1 ]; then - grep -qE "^User_Alias ADMIN=.*$" /etc/sudoers || failed "IS_SUDOADMIN" - fi - - if [ "${IS_PKGMIRROR:=1}" = 1 ]; then - grep -qE "^export PKG_PATH=http://ftp\.fr\.openbsd\.org/pub/OpenBSD/[0-9.]+/packages/[a-z0-9]+/$" /root/.profile \ - || failed "IS_PKGMIRROR" - fi - - if [ "${IS_HISTORY:=1}" = 1 ]; then - f=/root/.profile - { grep -q "^HISTFILE=\$HOME/.histfile" $f \ - && grep -q "^export HISTFILE" $f \ - && grep -q "^HISTSIZE=1000" $f \ - && grep -q "^export HISTSIZE" $f; - } || failed "IS_HISTORY" - fi - - if [ "${IS_VIM:=1}" = 1 ]; then - command -v vim > /dev/null 2>&1 || failed "IS_VIM" - fi - - if [ "${IS_TTYC0SECURE:=1}" = 1 ]; then - grep -Eqv "^ttyC0.*secure$" /etc/ttys || failed "IS_TTYC0SECURE" - fi - - if [ "${IS_CUSTOMSYSLOG:=1}" = 1 ]; then - grep -q "Evolix" /etc/newsyslog.conf || failed "IS_CUSTOMSYSLOG" - fi - - if [ "${IS_NOINETD:=1}" = 1 ]; then - grep -q "inetd=NO" /etc/rc.conf.local 2>/dev/null || failed "IS_NOINETD" - fi - - if [ "${IS_SUDOMAINT:=1}" = 1 ]; then - f=/etc/sudoers - { grep -q "Cmnd_Alias MAINT = /usr/share/scripts/evomaintenance.sh" $f \ - && grep -q "ADMIN ALL=NOPASSWD: MAINT" $f; - } || failed "IS_SUDOMAINT" - fi - - if [ "${IS_POSTGRESQL:=1}" = 1 ]; then - pkg info | grep -q postgresql-client || failed "IS_POSTGRESQL" "postgresql-client is not installed" - fi - - if [ "${IS_NRPE:=1}" = 1 ]; then - { pkg info | grep -qE "nagios-plugins-[0-9.]" \ - && pkg info | grep -q nagios-plugins-ntp \ - && pkg info | grep -q nrpe; - } || failed "IS_NRPE" "NRPE is not installed" - fi - - # if [ "${IS_NRPEDISKS:=1}" = 1 ]; then - # NRPEDISKS=$(grep command.check_disk /etc/nrpe.cfg 2>/dev/null | grep "^command.check_disk[0-9]" | sed -e "s/^command.check_disk\([0-9]\+\).*/\1/" | sort -n | tail -1) - # DFDISKS=$(df -Pl | grep -E -v "(^Filesystem|/lib/init/rw|/dev/shm|udev|rpc_pipefs)" | wc -l) - # [ "$NRPEDISKS" = "$DFDISKS" ] || failed "IS_NRPEDISKS" - # fi - - # Verification du check_mailq dans nrpe.cfg (celui-ci doit avoir l'option "-M postfix" si le MTA est Postfix) - # - # if [ "${IS_NRPEPOSTFIX:=1}" = 1 ]; then - # pkg info | grep -q postfix && ( grep -q "^command.*check_mailq -M postfix" /etc/nrpe.cfg 2>/dev/null || failed "IS_NRPEPOSTFIX" ) - # fi - - if [ "${IS_NRPEDAEMON:=1}" = 1 ]; then - grep -q "echo -n ' nrpe'; /usr/local/sbin/nrpe -d" /etc/rc.local \ - || failed "IS_NREPEDAEMON" - fi - - if [ "${IS_ALERTBOOT:=1}" = 1 ]; then - grep -qE "^date \| mail -sboot/reboot .*evolix.fr$" /etc/rc.local \ - || failed "IS_ALERTBOOT" - fi - - if [ "${IS_RSYNC:=1}" = 1 ]; then - pkg info | grep -q rsync || failed "IS_RSYNC" - fi - - if [ "${IS_CRONPATH:=1}" = 1 ]; then - grep -q "PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin" /var/cron/tabs/root \ - || failed "IS_CRONPATH" - fi - - #TODO - # - Check en profondeur de postfix - # - NRPEDISK et NRPEPOSTFIX - fi + test "${IS_LSBRELEASE:=1}" = 1 && check_lsbrelease + test "${IS_DPKGWARNING:=1}" = 1 && check_dpkgwarning + test "${IS_POSTFIX_MYDESTINATION:=1}" = 1 && check_postfix_mydestination + test "${IS_NRPEPOSTFIX:=1}" = 1 && check_nrpepostfix + test "${IS_CUSTOMSUDOERS:=1}" = 1 && check_customsudoers + test "${IS_VARTMPFS:=1}" = 1 && check_vartmpfs + test "${IS_SERVEURBASE:=1}" = 1 && check_serveurbase + test "${IS_LOGROTATECONF:=1}" = 1 && check_logrotateconf + test "${IS_SYSLOGCONF:=1}" = 1 && check_syslogconf + test "${IS_DEBIANSECURITY:=1}" = 1 && check_debiansecurity + test "${IS_APTITUDE:=1}" = 1 && check_aptitude + test "${IS_APTGETBAK:=1}" = 1 && check_aptgetbak + test "${IS_USRRO:=1}" = 1 && check_usrro + test "${IS_TMPNOEXEC:=1}" = 1 && check_tmpnoexec + test "${IS_MOUNT_FSTAB:=1}" = 1 && check_mountfstab + test "${IS_LISTCHANGESCONF:=1}" = 1 && check_listchangesconf + test "${IS_CUSTOMCRONTAB:=1}" = 1 && check_customcrontab + test "${IS_SSHALLOWUSERS:=1}" = 1 && check_sshallowusers + test "${IS_DISKPERF:=0}" = 1 && check_diskperf + test "${IS_TMOUTPROFILE:=1}" = 1 && check_tmoutprofile + test "${IS_ALERT5BOOT:=1}" = 1 && check_alert5boot + test "${IS_ALERT5MINIFW:=1}" = 1 && check_alert5minifw + test "${IS_ALERT5MINIFW:=1}" = 1 && test "${IS_MINIFW:=1}" = 1 && check_minifw + test "${IS_NRPEPERMS:=1}" = 1 && check_nrpeperms + test "${IS_MINIFWPERMS:=1}" = 1 && check_minifwperms + # Enable when minifirewall is released + test "${IS_MINIFWINCLUDES:=0}" = 1 && check_minifw_includes + test "${IS_NRPEDISKS:=0}" = 1 && check_nrpedisks + test "${IS_NRPEPID:=1}" = 1 && check_nrpepid + test "${IS_GRSECPROCS:=1}" = 1 && check_grsecprocs + test "${IS_APACHEMUNIN:=1}" = 1 && check_apachemunin + test "${IS_MYSQLUTILS:=1}" = 1 && check_mysqlutils + test "${IS_RAIDSOFT:=1}" = 1 && check_raidsoft + test "${IS_AWSTATSLOGFORMAT:=1}" = 1 && check_awstatslogformat + test "${IS_MUNINLOGROTATE:=1}" = 1 && check_muninlogrotate + test "${IS_SQUID:=1}" = 1 && check_squid + test "${IS_EVOMAINTENANCE_FW:=1}" = 1 && check_evomaintenance_fw + test "${IS_MODDEFLATE:=1}" = 1 && check_moddeflate + test "${IS_LOG2MAILRUNNING:=1}" = 1 && check_log2mailrunning + test "${IS_LOG2MAILAPACHE:=1}" = 1 && check_log2mailapache + test "${IS_LOG2MAILMYSQL:=1}" = 1 && check_log2mailmysql + test "${IS_LOG2MAILSQUID:=1}" = 1 && check_log2mailsquid + test "${IS_BINDCHROOT:=1}" = 1 && check_bindchroot + test "${IS_NETWORK_INTERFACES:=1}" = 1 && check_network_interfaces + test "${IS_AUTOIF:=1}" = 1 && check_autoif + test "${IS_INTERFACESGW:=1}" = 1 && check_interfacesgw + test "${IS_NETWORKING_SERVICE:=1}" = 1 && check_networking_service + test "${IS_EVOBACKUP:=1}" = 1 && check_evobackup + test "${IS_PURGE_FAIL2BAN:=1}" = 1 && check_fail2ban_purge + test "${IS_SSH_FAIL2BAN_JAIL_RENAMED:=1}" = 1 && check_ssh_fail2ban_jail_renamed + test "${IS_EVOBACKUP_EXCLUDE_MOUNT:=1}" = 1 && check_evobackup_exclude_mount + test "${IS_USERLOGROTATE:=1}" = 1 && check_userlogrotate + test "${IS_APACHECTL:=1}" = 1 && check_apachectl + test "${IS_APACHESYMLINK:=1}" = 1 && check_apachesymlink + test "${IS_APACHEIPINALLOW:=1}" = 1 && check_apacheipinallow + test "${IS_MUNINAPACHECONF:=1}" = 1 && check_muninapacheconf + test "${IS_PHPMYADMINAPACHECONF:=1}" = 1 && check_phpmyadminapacheconf + test "${IS_KERNELUPTODATE:=1}" = 1 && check_kerneluptodate + test "${IS_UPTIME:=1}" = 1 && check_uptime + test "${IS_MUNINRUNNING:=1}" = 1 && check_muninrunning + test "${IS_BACKUPUPTODATE:=1}" = 1 && check_backupuptodate + test "${IS_ETCGIT:=1}" = 1 && check_etcgit + test "${IS_GITPERMS:=1}" = 1 && check_gitperms + test "${IS_NOTUPGRADED:=1}" = 1 && check_notupgraded + test "${IS_TUNE2FS_M5:=1}" = 1 && check_tune2fs_m5 + test "${IS_EVOLINUXSUDOGROUP:=1}" = 1 && check_evolinuxsudogroup + test "${IS_USERINADMGROUP:=1}" = 1 && check_userinadmgroup + test "${IS_APACHE2EVOLINUXCONF:=1}" = 1 && check_apache2evolinuxconf + test "${IS_BACKPORTSCONF:=1}" = 1 && check_backportsconf + test "${IS_BIND9MUNIN:=1}" = 1 && check_bind9munin + test "${IS_BIND9LOGROTATE:=1}" = 1 && check_bind9logrotate + test "${IS_BROADCOMFIRMWARE:=1}" = 1 && check_broadcomfirmware + test "${IS_HARDWARERAIDTOOL:=1}" = 1 && check_hardwareraidtool + test "${IS_LOG2MAILSYSTEMDUNIT:=1}" = 1 && check_log2mailsystemdunit + test "${IS_LISTUPGRADE:=1}" = 1 && check_listupgrade + test "${IS_MARIADBEVOLINUXCONF:=0}" = 1 && check_mariadbevolinuxconf + test "${IS_SQL_BACKUP:=1}" = 1 && check_sql_backup + test "${IS_POSTGRES_BACKUP:=1}" = 1 && check_postgres_backup + test "${IS_MONGO_BACKUP:=1}" = 1 && check_mongo_backup + test "${IS_LDAP_BACKUP:=1}" = 1 && check_ldap_backup + test "${IS_REDIS_BACKUP:=1}" = 1 && check_redis_backup + test "${IS_ELASTIC_BACKUP:=1}" = 1 && check_elastic_backup + test "${IS_MARIADBSYSTEMDUNIT:=1}" = 1 && check_mariadbsystemdunit + test "${IS_MYSQLMUNIN:=1}" = 1 && check_mysqlmunin + test "${IS_MYSQLNRPE:=1}" = 1 && check_mysqlnrpe + test "${IS_PHPEVOLINUXCONF:=0}" = 1 && check_phpevolinuxconf + test "${IS_SQUIDLOGROTATE:=1}" = 1 && check_squidlogrotate + test "${IS_SQUIDEVOLINUXCONF:=1}" = 1 && check_squidevolinuxconf + test "${IS_DUPLICATE_FS_LABEL:=1}" = 1 && check_duplicate_fs_label + test "${IS_EVOLIX_USER:=1}" = 1 && check_evolix_user + test "${IS_EVOACME_CRON:=1}" = 1 && check_evoacme_cron + test "${IS_EVOACME_LIVELINKS:=1}" = 1 && check_evoacme_livelinks + test "${IS_APACHE_CONFENABLED:=1}" = 1 && check_apache_confenabled + test "${IS_MELTDOWN_SPECTRE:=1}" = 1 && check_meltdown_spectre + test "${IS_OLD_HOME_DIR:=0}" = 1 && check_old_home_dir + test "${IS_EVOBACKUP_INCS:=1}" = 1 && check_evobackup_incs + test "${IS_OSPROBER:=1}" = 1 && check_osprober + test "${IS_APT_VALID_UNTIL:=1}" = 1 && check_apt_valid_until + test "${IS_CHROOTED_BINARY_UPTODATE:=1}" = 1 && check_chrooted_binary_uptodate + test "${IS_NGINX_LETSENCRYPT_UPTODATE:=1}" = 1 && check_nginx_letsencrypt_uptodate + test "${IS_LXC_CONTAINER_RESOLV_CONF:=1}" = 1 && check_lxc_container_resolv_conf + test "${IS_NO_LXC_CONTAINER:=1}" = 1 && check_no_lxc_container + test "${IS_LXC_PHP_FPM_SERVICE_UMASK_SET:=1}" = 1 && check_lxc_php_fpm_service_umask_set + test "${IS_CHECK_VERSIONS:=1}" = 1 && check_versions if [ -f "${main_output_file}" ]; then - if [ $(cat "${main_output_file}" | wc -l) -gt 0 ]; then + lines_found=$(wc -l < "${main_output_file}") + # shellcheck disable=SC2086 + if [ ${lines_found} -gt 0 ]; then cat "${main_output_file}" 2>&1 fi @@ -1736,9 +1509,12 @@ main() { exit ${RC} } -cleanup_temp_files() { - # shellcheck disable=SC2086 - rm -f ${files_to_cleanup} +cleanup() { + # Cleanup tmp files + # shellcheck disable=SC2086,SC2317 + rm -f ${files_to_cleanup[@]} + + log "$PROGNAME exit." } PROGNAME=$(basename "$0") @@ -1749,17 +1525,23 @@ readonly PROGNAME ARGS=$@ readonly ARGS +LOGFILE="/var/log/evocheck.log" +readonly LOGFILE + +CONFIGFILE="/etc/evocheck.cf" +readonly CONFIGFILE + +DATE_FORMAT="%Y-%m-%d %H:%M:%S" +# shellcheck disable=SC2034 +readonly DATEFORMAT + # Disable LANG* 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 +test -f "${CONFIGFILE}" && . "${CONFIGFILE}" # Parse options # based on https://gist.github.com/deshion/10d3cb5f88a21671e17a @@ -1778,6 +1560,7 @@ while :; do IS_UPTIME=0 IS_MELTDOWN_SPECTRE=0 IS_CHECK_VERSIONS=0 + IS_NETWORKING_SERVICE=0 ;; -v|--verbose) VERBOSE=1 @@ -1806,5 +1589,24 @@ while :; do shift done +# Keep this after "show_version(); exit 0" which is called by check_versions +# to avoid logging exit twice. +declare -a files_to_cleanup +files_to_cleanup="" +# shellcheck disable=SC2064 +trap cleanup EXIT INT TERM + +log '-----------------------------------------------' +log "Running $PROGNAME $VERSION..." + +# Log config file content +if [ -f "${CONFIGFILE}" ]; then + log "Runtime configuration (${CONFIGFILE}):" + sed -e '/^[[:blank:]]*#/d; s/#.*//; /^[[:blank:]]*$/d' "${CONFIGFILE}" | log +fi + # shellcheck disable=SC2086 main ${ARGS} + +log "End of $PROGNAME execution." + diff --git a/evocheck/files/evocheck.wheezy.sh b/evocheck/files/evocheck.wheezy.sh new file mode 100755 index 00000000..461540b3 --- /dev/null +++ b/evocheck/files/evocheck.wheezy.sh @@ -0,0 +1,1252 @@ +#!/bin/bash + +# EvoCheck +# Script to verify compliance of a Linux (Debian) server +# powered by Evolix + +VERSION="23.04.01" +readonly VERSION + +# base functions + +show_version() { + cat <, + Romain Dessort , + Benoit Série , + Gregory Colpart , + Jérémy Lecour , + Tristan Pilat , + Victor Laborie , + Alexis Ben Miloud--Josselin , + and others. + +evocheck 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 <&2 + echo "This version is built for Debian 7 only." >&2 + exit + fi + + if [ -x "${LSB_RELEASE_BIN}" ]; then + DEBIAN_RELEASE=$(${LSB_RELEASE_BIN} --codename --short) + else + case ${DEBIAN_MAIN_VERSION} in + 5) DEBIAN_RELEASE="lenny";; + 6) DEBIAN_RELEASE="squeeze";; + 7) DEBIAN_RELEASE="wheezy";; + esac + fi + fi +} + +is_debian_lenny() { + test "${DEBIAN_RELEASE}" = "lenny" +} +is_debian_squeeze() { + test "${DEBIAN_RELEASE}" = "squeeze" +} +is_debian_wheezy() { + test "${DEBIAN_RELEASE}" = "wheezy" +} + +is_pack_web(){ + test -e /usr/share/scripts/web-add.sh || test -e /usr/share/scripts/evoadmin/web-add.sh +} +is_pack_samba(){ + test -e /usr/share/scripts/add.pl +} +is_installed(){ + for pkg in "$@"; do + dpkg -l "$pkg" 2> /dev/null | grep -q -E '^(i|h)i' || return 1 + done +} + +# logging + +failed() { + check_name=$1 + shift + check_comments=$* + + RC=1 + if [ "${QUIET}" != 1 ]; then + if [ -n "${check_comments}" ] && [ "${VERBOSE}" = 1 ]; then + printf "%s FAILED! %s\n" "${check_name}" "${check_comments}" >> "${main_output_file}" + else + printf "%s FAILED!\n" "${check_name}" >> "${main_output_file}" + fi + fi +} + +# check functions + +check_lsbrelease(){ + if [ -x "${LSB_RELEASE_BIN}" ]; then + ## only the major version matters + lhs=$(${LSB_RELEASE_BIN} --release --short | cut -d "." -f 1) + rhs=$(cut -d "." -f 1 < /etc/debian_version) + test "$lhs" = "$rhs" || failed "IS_LSBRELEASE" "release is not consistent between lsb_release (${lhs}) and /etc/debian_version (${rhs})" + else + failed "IS_LSBRELEASE" "lsb_release is missing or not executable" + fi +} +check_dpkgwarning() { + if [ "$IS_USRRO" = 1 ] || [ "$IS_TMPNOEXEC" = 1 ]; then + test -e /etc/apt/apt.conf.d/80evolinux \ + || failed "IS_DPKGWARNING" "/etc/apt/apt.conf.d/80evolinux is missing" + test -e /etc/apt/apt.conf \ + && failed "IS_DPKGWARNING" "/etc/apt/apt.conf is missing" + fi +} +# Verifying check_mailq in Nagios NRPE config file. (Option "-M postfix" need to be set if the MTA is Postfix) +check_nrpepostfix() { + if is_installed postfix; then + { test -e /etc/nagios/nrpe.cfg \ + && grep -qr "^command.*check_mailq -M postfix" /etc/nagios/nrpe.*; + } || failed "IS_NRPEPOSTFIX" "NRPE \"check_mailq\" for postfix is missing" + fi +} +# Check if mod-security config file is present +check_modsecurity() { + if is_installed libapache2-modsecurity; then + test -e /etc/apache2/conf.d/mod-security2.conf || failed "IS_MODSECURITY" "missing configuration file" + fi +} +check_customsudoers() { + grep -E -qr "umask=0077" /etc/sudoers* || failed "IS_CUSTOMSUDOERS" "missing umask=0077 in sudoers file" +} +check_vartmpfs() { + FINDMNT_BIN=$(command -v findmnt) + if [ -x "${FINDMNT_BIN}" ]; then + ${FINDMNT_BIN} /var/tmp --type tmpfs --noheadings > /dev/null || failed "IS_VARTMPFS" "/var/tmp is not a tmpfs" + else + df /var/tmp | grep -q tmpfs || failed "IS_VARTMPFS" "/var/tmp is not a tmpfs" + fi +} +check_serveurbase() { + is_installed serveur-base || failed "IS_SERVEURBASE" "serveur-base package is not installed" +} +check_logrotateconf() { + test -e /etc/logrotate.d/zsyslog || failed "IS_LOGROTATECONF" "missing zsyslog in logrotate.d" +} +check_syslogconf() { + grep -q "^# Syslog for Pack Evolix serveur" /etc/*syslog.conf \ + || failed "IS_SYSLOGCONF" "syslog evolix config file missing" +} +check_debiansecurity() { + # Look for enabled "Debian-Security" sources from the "Debian" origin + apt-cache policy | grep "\bl=Debian-Security\b" | grep "\bo=Debian\b" | grep --quiet "\bc=main\b" + test $? -eq 0 || failed "IS_DEBIANSECURITY" "missing Debian-Security repository" +} +check_aptitudeonly() { + test -e /usr/bin/apt-get && failed "IS_APTITUDEONLY" \ + "only aptitude may be enabled on Debian <=7, apt-get should be disabled" +} + +check_apticron() { + status="OK" + test -e /etc/cron.d/apticron || status="fail" + test -e /etc/cron.daily/apticron && status="fail" + test "$status" = "fail" || test -e /usr/bin/apt-get.bak || status="fail" + + test "$status" = "fail" && failed "IS_APTICRON" "apticron must be in cron.d not cron.daily" +} +check_usrro() { + grep /usr /etc/fstab | grep -qE "\bro\b" || failed "IS_USRRO" "missing ro directive on fstab for /usr" +} +check_tmpnoexec() { + FINDMNT_BIN=$(command -v findmnt) + if [ -x "${FINDMNT_BIN}" ]; then + options=$(${FINDMNT_BIN} --noheadings --first-only --output OPTIONS /tmp) + echo "${options}" | grep -qE "\bnoexec\b" || failed "IS_TMPNOEXEC" "/tmp is not mounted with 'noexec'" + else + mount | grep "on /tmp" | grep -qE "\bnoexec\b" || failed "IS_TMPNOEXEC" "/tmp is not mounted with 'noexec' (WARNING: findmnt(8) is not found)" + fi +} +check_mountfstab() { + # Test if lsblk available, if not skip this test... + LSBLK_BIN=$(command -v lsblk) + if test -x "${LSBLK_BIN}"; then + for mountPoint in $(${LSBLK_BIN} -o MOUNTPOINT -l -n | grep '/'); do + grep -Eq "$mountPoint\W" /etc/fstab \ + || failed "IS_MOUNT_FSTAB" "partition(s) detected mounted but no presence in fstab" + done + fi +} +check_listchangesconf() { + if [ -e "/etc/apt/listchanges.conf" ]; then + lines=$(grep -cE "(which=both|confirm=1)" /etc/apt/listchanges.conf) + if [ "$lines" != 2 ]; then + failed "IS_LISTCHANGESCONF" "apt-listchanges config is incorrect" + fi + else + failed "IS_LISTCHANGESCONF" "apt-listchanges config is missing" + fi +} +check_customcrontab() { + found_lines=$(grep -c -E "^(17 \*|25 6|47 6|52 6)" /etc/crontab) + test "$found_lines" = 4 && failed "IS_CUSTOMCRONTAB" "missing custom field in crontab" +} +check_sshallowusers() { + grep -E -qir "(AllowUsers|AllowGroups)" /etc/ssh/sshd_config /etc/ssh/sshd_config.d \ + || failed "IS_SSHALLOWUSERS" "missing AllowUsers or AllowGroups directive in sshd_config" +} +check_diskperf() { + perfFile="/root/disk-perf.txt" + test -e $perfFile || failed "IS_DISKPERF" "missing ${perfFile}" +} +check_tmoutprofile() { + grep -sq "TMOUT=" /etc/profile /etc/profile.d/evolinux.sh || failed "IS_TMOUTPROFILE" "TMOUT is not set" +} +check_alert5boot() { + 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" + elif [ -n "$(find /etc/init.d/ -name 'alert5')" ]; then + grep -q "^date" /etc/init.d/alert5 || failed "IS_ALERT5BOOT" "boot mail is not sent by alert5 int script" + else + failed "IS_ALERT5BOOT" "alert5 init script is missing" + fi +} +check_alert5minifw() { + if [ -n "$(find /etc/rc2.d/ -name 'S*alert5')" ]; then + grep -q "^/etc/init.d/minifirewall" /etc/rc2.d/S*alert5 \ + || failed "IS_ALERT5MINIFW" "Minifirewall is not started by alert5 init script" + elif [ -n "$(find /etc/init.d/ -name 'alert5')" ]; then + grep -q "^/etc/init.d/minifirewall" /etc/init.d/alert5 \ + || failed "IS_ALERT5MINIFW" "Minifirewall is not started by alert5 init script" + else + failed "IS_ALERT5MINIFW" "alert5 init script is missing" + fi +} +check_minifw() { + /sbin/iptables -L -n | grep -q -E "^ACCEPT\s*all\s*--\s*31\.170\.8\.4\s*0\.0\.0\.0/0\s*$" \ + || failed "IS_MINIFW" "minifirewall seems not started" +} +check_nrpeperms() { + if [ -d /etc/nagios ]; then + nagiosDir="/etc/nagios" + actual=$(stat --format "%a" $nagiosDir) + expected="750" + test "$expected" = "$actual" || failed "IS_NRPEPERMS" "${nagiosDir} must be ${expected}" + fi +} +check_minifwperms() { + if [ -f "/etc/firewall.rc" ]; then + actual=$(stat --format "%a" "/etc/firewall.rc") + expected="600" + test "$expected" = "$actual" || failed "IS_MINIFWPERMS" "/etc/firewall.rc must be ${expected}" + fi +} +check_nrpedisks() { + NRPEDISKS=$(grep command.check_disk /etc/nagios/nrpe.cfg | grep "^command.check_disk[0-9]" | sed -e "s/^command.check_disk\([0-9]\+\).*/\1/" | sort -n | tail -1) + DFDISKS=$(df -Pl | grep -c -E -v "(^Filesystem|/lib/init/rw|/dev/shm|udev|rpc_pipefs)") + test "$NRPEDISKS" = "$DFDISKS" || failed "IS_NRPEDISKS" "there must be $DFDISKS check_disk in nrpe.cfg" +} +check_nrpepid() { + { test -e /etc/nagios/nrpe.cfg \ + && grep -q "^pid_file=/var/run/nagios/nrpe.pid" /etc/nagios/nrpe.cfg; + } || failed "IS_NRPEPID" "missing or wrong pid_file directive in nrpe.cfg" +} +check_grsecprocs() { + if uname -a | grep -q grsec; then + { grep -q "^command.check_total_procs..sudo" /etc/nagios/nrpe.cfg \ + && grep -A1 "^\[processes\]" /etc/munin/plugin-conf.d/munin-node | grep -q "^user root"; + } || failed "IS_GRSECPROCS" "missing munin's plugin processes directive for grsec" + fi +} +check_apachemunin() { + if test -e /etc/apache2/apache2.conf; then + pattern="/server-status-[[:alnum:]]{4,}" + { grep -r -q -s -E "^env.url.*${pattern}" /etc/munin/plugin-conf.d \ + && { grep -q -s -E "${pattern}" /etc/apache2/apache2.conf \ + || grep -q -s -E "${pattern}" /etc/apache2/mods-enabled/status.conf; + }; + } || failed "IS_APACHEMUNIN" "server status is not properly configured" + fi +} +# Verification mytop + Munin si MySQL +check_mysqlutils() { + MYSQL_ADMIN=${MYSQL_ADMIN:-mysqladmin} + if is_installed mysql-server; then + # You can configure MYSQL_ADMIN in evocheck.cf + if ! grep -qs "^user *= *${MYSQL_ADMIN}" /root/.my.cnf; then + failed "IS_MYSQLUTILS" "${MYSQL_ADMIN} missing in /root/.my.cnf" + fi + if ! test -x /usr/bin/mytop; then + if ! test -x /usr/local/bin/mytop; then + failed "IS_MYSQLUTILS" "mytop binary missing" + fi + fi + if ! grep -qs '^user *=' /root/.mytop; then + failed "IS_MYSQLUTILS" "credentials missing in /root/.mytop" + fi + fi +} +# Verification de la configuration du raid soft (mdadm) +check_raidsoft() { + if test -e /proc/mdstat && grep -q md /proc/mdstat; then + { grep -q "^AUTOCHECK=true" /etc/default/mdadm \ + && grep -q "^START_DAEMON=true" /etc/default/mdadm \ + && grep -qv "^MAILADDR ___MAIL___" /etc/mdadm/mdadm.conf; + } || failed "IS_RAIDSOFT" "missing or wrong config for mdadm" + fi +} +# Verification du LogFormat de AWStats +check_awstatslogformat() { + if is_installed apache2 awstats; then + awstatsFile="/etc/awstats/awstats.conf.local" + grep -qE '^LogFormat=1' $awstatsFile \ + || failed "IS_AWSTATSLOGFORMAT" "missing or wrong LogFormat directive in $awstatsFile" + fi +} +# Verification de la présence de la config logrotate pour Munin +check_muninlogrotate() { + { test -e /etc/logrotate.d/munin-node \ + && test -e /etc/logrotate.d/munin; + } || failed "IS_MUNINLOGROTATE" "missing lorotate file for munin" +} +# Verification de l'activation de Squid dans le cas d'un pack mail +check_squid() { + squidconffile="/etc/squid*/squid.conf" + if is_pack_web && (is_installed squid || is_installed squid3); then + host=$(hostname -i) + # shellcheck disable=SC2086 + http_port=$(grep -E "^http_port\s+[0-9]+" $squidconffile | awk '{ print $2 }') + { grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -m owner --uid-owner proxy -j ACCEPT" "/etc/firewall.rc" \ + && grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -d $host -j ACCEPT" "/etc/firewall.rc" \ + && grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -d 127.0.0.(1|0/8) -j ACCEPT" "/etc/firewall.rc" \ + && grep -qE "^[^#]*iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port.* $http_port" "/etc/firewall.rc"; + } || grep -qE "^PROXY='?on'?" "/etc/firewall.rc" \ + || failed "IS_SQUID" "missing squid rules in minifirewall" + fi +} +check_evomaintenance_fw() { + if [ -f "/etc/firewall.rc" ]; then + hook_db=$(grep -E '^\s*HOOK_DB' /etc/evomaintenance.cf | tr -d ' ' | cut -d= -f2) + rulesNumber=$(grep -c "/sbin/iptables -A INPUT -p tcp --sport 5432 --dport 1024:65535 -s .* -m state --state ESTABLISHED,RELATED -j ACCEPT" "/etc/firewall.rc") + if [ "$hook_db" = "1" ] && [ "$rulesNumber" -lt 2 ]; then + failed "IS_EVOMAINTENANCE_FW" "HOOK_DB is enabled but missing evomaintenance rules in minifirewall" + fi + fi +} +# Verification de la conf et de l'activation de mod-deflate +check_moddeflate() { + f=/etc/apache2/mods-enabled/deflate.conf + if is_installed apache2.2; then + { test -e $f && grep -q "AddOutputFilterByType DEFLATE text/html text/plain text/xml" $f \ + && grep -q "AddOutputFilterByType DEFLATE text/css" $f \ + && grep -q "AddOutputFilterByType DEFLATE application/x-javascript application/javascript" $f; + } || failed "IS_MODDEFLATE" "missing AddOutputFilterByType directive for apache mod deflate" + fi +} +# Verification de la conf log2mail +check_log2mailrunning() { + if is_pack_web && is_installed log2mail; then + pgrep log2mail >/dev/null || failed "IS_LOG2MAILRUNNING" "log2mail is not running" + fi +} +check_log2mailapache() { + conf=/etc/log2mail/config/default + if is_pack_web && is_installed log2mail; then + grep -s -q "^file = /var/log/apache2/error.log" $conf \ + || failed "IS_LOG2MAILAPACHE" "missing log2mail directive for apache" + fi +} +check_log2mailmysql() { + if is_pack_web && is_installed log2mail; then + grep -s -q "^file = /var/log/syslog" /etc/log2mail/config/{default,mysql,mysql.conf} \ + || failed "IS_LOG2MAILMYSQL" "missing log2mail directive for mysql" + fi +} +check_log2mailsquid() { + if is_pack_web && is_installed log2mail; then + grep -s -q "^file = /var/log/squid.*/access.log" /etc/log2mail/config/* \ + || failed "IS_LOG2MAILSQUID" "missing log2mail directive for squid" + fi +} +# Verification si bind est chroote +check_bindchroot() { + if is_installed bind9; then + if netstat -utpln | grep "/named" | grep :53 | grep -qvE "(127.0.0.1|::1)"; then + if grep -q '^OPTIONS=".*-t' /etc/default/bind9 && grep -q '^OPTIONS=".*-u' /etc/default/bind9; then + md5_original=$(md5sum /usr/sbin/named | cut -f 1 -d ' ') + md5_chrooted=$(md5sum /var/chroot-bind/usr/sbin/named | cut -f 1 -d ' ') + if [ "$md5_original" != "$md5_chrooted" ]; then + failed "IS_BINDCHROOT" "the chrooted bind binary is different than the original binary" + fi + else + failed "IS_BINDCHROOT" "bind process is not chrooted" + fi + fi + fi +} +# /etc/network/interfaces should be present, we don't manage systemd-network yet +check_network_interfaces() { + if ! test -f /etc/network/interfaces; then + IS_AUTOIF=0 + IS_INTERFACESGW=0 + failed "IS_NETWORK_INTERFACES" "systemd network configuration is not supported yet" + fi +} +# Verify if all if are in auto +check_autoif() { + interfaces=$(/sbin/ifconfig -s | tail -n +2 | grep -E -v "^(lo|vnet|docker|veth|tun|tap|macvtap|vrrp)" | cut -d " " -f 1 |tr "\n" " ") + for interface in $interfaces; do + if grep -Rq "^iface $interface" /etc/network/interfaces* && ! grep -Rq "^auto $interface" /etc/network/interfaces*; then + failed "IS_AUTOIF" "Network interface \`${interface}' is statically defined but not set to auto" + test "${VERBOSE}" = 1 || break + fi + done +} +# Network conf verification +check_interfacesgw() { + number=$(grep -Ec "^[^#]*gateway [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" /etc/network/interfaces) + test "$number" -gt 1 && failed "IS_INTERFACESGW" "there is more than 1 IPv4 gateway" + number=$(grep -Ec "^[^#]*gateway [0-9a-fA-F]+:" /etc/network/interfaces) + test "$number" -gt 1 && failed "IS_INTERFACESGW" "there is more than 1 IPv6 gateway" +} +# Verification de la mise en place d'evobackup +check_evobackup() { + evobackup_found=$(find /etc/cron* -name '*evobackup*' | wc -l) + test "$evobackup_found" -gt 0 || failed "IS_EVOBACKUP" "missing evobackup cron" +} +# Vérification de l'exclusion des montages (NFS) dans les sauvegardes +check_evobackup_exclude_mount() { + 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 + # if the file seems to be a backup script, with an Rsync invocation + if grep -q "^\s*rsync" "${evobackup_file}"; then + # 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 + fi + done +} +# Verification de la presence du userlogrotate +check_userlogrotate() { + if is_pack_web; then + test -x /etc/cron.weekly/userlogrotate || failed "IS_USERLOGROTATE" "missing userlogrotate cron" + fi +} +# Verification de la syntaxe de la conf d'Apache +check_apachectl() { + if is_installed apache2; then + /usr/sbin/apache2ctl configtest 2>&1 | grep -q "^Syntax OK$" \ + || failed "IS_APACHECTL" "apache errors detected, run a configtest" + fi +} +# Check if there is regular files in Apache sites-enabled. +check_apachesymlink() { + if is_installed apache2; then + apacheFind=$(find /etc/apache2/sites-enabled ! -type l -type f -print) + nbApacheFind=$(wc -m <<< "$apacheFind") + if [[ $nbApacheFind -gt 1 ]]; then + if [[ $VERBOSE == 1 ]]; then + while read -r line; do + failed "IS_APACHESYMLINK" "Not a symlink: $line" + done <<< "$apacheFind" + else + failed "IS_APACHESYMLINK" + fi + fi + fi +} +# Check if there is real IP addresses in Allow/Deny directives (no trailing space, inline comments or so). +check_apacheipinallow() { + # Note: Replace "exit 1" by "print" in Perl code to debug it. + if is_installed apache2; then + grep -IrE "^[^#] *(Allow|Deny) from" /etc/apache2/ \ + | grep -iv "from all" \ + | grep -iv "env=" \ + | perl -ne 'exit 1 unless (/from( [\da-f:.\/]+)+$/i)' \ + || failed "IS_APACHEIPINALLOW" "bad (Allow|Deny) directives in apache" + fi +} +# Check if default Apache configuration file for munin is absent (or empty or commented). +check_muninapacheconf() { + muninconf="/etc/apache2/conf.d/munin" + if is_installed apache2; then + test -e $muninconf && grep -vEq "^( |\t)*#" "$muninconf" \ + && failed "IS_MUNINAPACHECONF" "default munin configuration may be commented or disabled" + fi +} +# Check if default Apache configuration file for phpMyAdmin is absent (or empty or commented). +check_phpmyadminapacheconf() { + phpmyadminconf0="/etc/apache2/conf-available/phpmyadmin.conf" + phpmyadminconf1="/etc/apache2/conf-enabled/phpmyadmin.conf" + if is_installed apache2; then + test -e $phpmyadminconf0 && grep -vEq "^( |\t)*#" "$phpmyadminconf0" \ + && failed "IS_PHPMYADMINAPACHECONF" "default phpmyadmin configuration ($phpmyadminconf0) may be commented or disabled" + test -e $phpmyadminconf1 && grep -vEq "^( |\t)*#" "$phpmyadminconf1" \ + && failed "IS_PHPMYADMINAPACHECONF" "default phpmyadmin configuration ($phpmyadminconf1) may be commented or disabled" + fi +} +# Verification si le système doit redémarrer suite màj kernel. +check_kerneluptodate() { + if is_installed linux-image*; then + # shellcheck disable=SC2012 + kernel_installed_at=$(date -d "$(ls --full-time -lcrt /boot | tail -n1 | awk '{print $6}')" +%s) + last_reboot_at=$(($(date +%s) - $(cut -f1 -d '.' /proc/uptime))) + if [ "$kernel_installed_at" -gt "$last_reboot_at" ]; then + failed "IS_KERNELUPTODATE" "machine is running an outdated kernel, reboot advised" + fi + fi +} +# Check if the server is running for more than a year. +check_uptime() { + if is_installed linux-image*; then + limit=$(date -d "now - 2 year" +%s) + last_reboot_at=$(($(date +%s) - $(cut -f1 -d '.' /proc/uptime))) + if [ "$limit" -gt "$last_reboot_at" ]; then + failed "IS_UPTIME" "machine has an uptime of more than 2 years, reboot on new kernel advised" + fi + fi +} +# Check if munin-node running and RRD files are up to date. +check_muninrunning() { + if ! pgrep munin-node >/dev/null; then + failed "IS_MUNINRUNNING" "Munin is not running" + elif [ -d "/var/lib/munin/" ] && [ -d "/var/cache/munin/" ]; then + limit=$(date +"%s" -d "now - 10 minutes") + + if [ -n "$(find /var/lib/munin/ -name '*load-g.rrd')" ]; then + updated_at=$(stat -c "%Y" /var/lib/munin/*/*load-g.rrd |sort |tail -1) + [ "$limit" -gt "$updated_at" ] && failed "IS_MUNINRUNNING" "Munin load RRD has not been updated in the last 10 minutes" + else + failed "IS_MUNINRUNNING" "Munin is not installed properly (load RRD not found)" + fi + + if [ -n "$(find /var/cache/munin/www/ -name 'load-day.png')" ]; then + updated_at=$(stat -c "%Y" /var/cache/munin/www/*/*/load-day.png |sort |tail -1) + grep -sq "^graph_strategy cron" /etc/munin/munin.conf && [ "$limit" -gt "$updated_at" ] && failed "IS_MUNINRUNNING" "Munin load PNG has not been updated in the last 10 minutes" + else + failed "IS_MUNINRUNNING" "Munin is not installed properly (load PNG not found)" + fi + else + failed "IS_MUNINRUNNING" "Munin is not installed properly (main directories are missing)" + fi +} +# Check if files in /home/backup/ are up-to-date +check_backupuptodate() { + backup_dir="/home/backup" + if [ -d "${backup_dir}" ]; then + if [ -n "$(ls -A ${backup_dir})" ]; then + find "${backup_dir}" -maxdepth 1 -type f | while read -r file; do + limit=$(date +"%s" -d "now - 2 day") + updated_at=$(stat -c "%Y" "$file") + + if [ "$limit" -gt "$updated_at" ]; then + failed "IS_BACKUPUPTODATE" "$file has not been backed up" + test "${VERBOSE}" = 1 || break; + fi + done + else + failed "IS_BACKUPUPTODATE" "${backup_dir}/ is empty" + fi + else + failed "IS_BACKUPUPTODATE" "${backup_dir}/ is missing" + fi +} +check_etcgit() { + export GIT_DIR="/etc/.git" GIT_WORK_TREE="/etc" + git rev-parse --is-inside-work-tree > /dev/null 2>&1 \ + || failed "IS_ETCGIT" "/etc is not a git repository" +} +# Check if /etc/.git/ has read/write permissions for root only. +check_gitperms() { + GIT_DIR="/etc/.git" + if test -d $GIT_DIR; then + expected="700" + actual=$(stat -c "%a" $GIT_DIR) + [ "$expected" = "$actual" ] || failed "IS_GITPERMS" "$GIT_DIR must be $expected" + fi +} +# Check if no package has been upgraded since $limit. +check_notupgraded() { + last_upgrade=0 + upgraded=false + for log in /var/log/dpkg.log*; do + if zgrep -qsm1 upgrade "$log"; then + # There is at least one upgrade + upgraded=true + break + fi + done + if $upgraded; then + last_upgrade=$(date +%s -d "$(zgrep -h upgrade /var/log/dpkg.log* | sort -n | tail -1 | cut -f1 -d ' ')") + fi + if grep -qs '^mailto="listupgrade-todo@' /etc/evolinux/listupgrade.cnf \ + || grep -qs -E '^[[:digit:]]+[[:space:]]+[[:digit:]]+[[:space:]]+[^\*]' /etc/cron.d/listupgrade; then + # Manual upgrade process + limit=$(date +%s -d "now - 180 days") + else + # Regular process + limit=$(date +%s -d "now - 90 days") + fi + install_date=0 + if [ -d /var/log/installer ]; then + install_date=$(stat -c %Z /var/log/installer) + fi + # Check install_date if the system never received an upgrade + if [ "$last_upgrade" -eq 0 ]; then + [ "$install_date" -lt "$limit" ] && failed "IS_NOTUPGRADED" "The system has never been updated" + else + [ "$last_upgrade" -lt "$limit" ] && failed "IS_NOTUPGRADED" "The system hasn't been updated for too long" + fi +} +# Check if reserved blocks for root is at least 5% on every mounted partitions. +check_tune2fs_m5() { + min=5 + parts=$(grep -E "ext(3|4)" /proc/mounts | cut -d ' ' -f1 | tr -s '\n' ' ') + FINDMNT_BIN=$(command -v findmnt) + for part in $parts; do + blockCount=$(dumpe2fs -h "$part" 2>/dev/null | grep -e "Block count:" | grep -Eo "[0-9]+") + # If buggy partition, skip it. + if [ -z "$blockCount" ]; then + continue + fi + reservedBlockCount=$(dumpe2fs -h "$part" 2>/dev/null | grep -e "Reserved block count:" | grep -Eo "[0-9]+") + # Use awk to have a rounded percentage + # python is slow, bash is unable and bc rounds weirdly + percentage=$(awk "BEGIN { pc=100*${reservedBlockCount}/${blockCount}; i=int(pc); print (pc-i<0.5)?i:i+1 }") + + if [ "$percentage" -lt "${min}" ]; then + if [ -x "${FINDMNT_BIN}" ]; then + mount=$(${FINDMNT_BIN} --noheadings --first-only --output TARGET "${part}") + else + mount="unknown mount point" + fi + failed "IS_TUNE2FS_M5" "Partition ${part} (${mount}) has less than ${min}% reserved blocks (${percentage}%)" + fi + done +} + +check_broadcomfirmware() { + LSPCI_BIN=$(command -v lspci) + if [ -x "${LSPCI_BIN}" ]; then + if ${LSPCI_BIN} | grep -q 'NetXtreme II'; then + { is_installed firmware-bnx2 \ + && grep -q "^deb http://mirror.evolix.org/debian.* non-free" /etc/apt/sources.list; + } || failed "IS_BROADCOMFIRMWARE" "missing non-free repository" + fi + else + failed "IS_BROADCOMFIRMWARE" "lspci not found in ${PATH}" + fi +} +check_hardwareraidtool() { + LSPCI_BIN=$(command -v lspci) + if [ -x "${LSPCI_BIN}" ]; then + if ${LSPCI_BIN} | grep -q 'MegaRAID'; then + # shellcheck disable=SC2015 + is_installed megacli && { is_installed megaclisas-status || is_installed megaraidsas-status; } \ + || failed "IS_HARDWARERAIDTOOL" "Mega tools not found" + fi + if ${LSPCI_BIN} | grep -q 'Hewlett-Packard Company Smart Array'; then + is_installed cciss-vol-status || failed "IS_HARDWARERAIDTOOL" "cciss-vol-status not installed" + fi + else + failed "IS_HARDWARERAIDTOOL" "lspci not found in ${PATH}" + fi +} +check_sql_backup() { + if (is_installed "mysql-server" || is_installed "mariadb-server"); then + # You could change the default path in /etc/evocheck.cf + SQL_BACKUP_PATH=${SQL_BACKUP_PATH:-"/home/backup/mysql.bak.gz"} + for backup_path in ${SQL_BACKUP_PATH}; do + if [ ! -f "${backup_path}" ]; then + failed "IS_SQL_BACKUP" "MySQL dump is missing (${backup_path})" + test "${VERBOSE}" = 1 || break + fi + done + fi +} +check_postgres_backup() { + if is_installed "postgresql-9*" || is_installed "postgresql-1*"; then + # If you use something like barman, you should disable this check + # You could change the default path in /etc/evocheck.cf + POSTGRES_BACKUP_PATH=${POSTGRES_BACKUP_PATH:-"/home/backup/pg.dump.bak*"} + for backup_path in ${POSTGRES_BACKUP_PATH}; do + if [ ! -f "${backup_path}" ]; then + failed "IS_POSTGRES_BACKUP" "PostgreSQL dump is missing (${backup_path})" + test "${VERBOSE}" = 1 || break + fi + done + fi +} +check_mongo_backup() { + if is_installed "mongodb-org-server"; then + # You could change the default path in /etc/evocheck.cf + MONGO_BACKUP_PATH=${MONGO_BACKUP_PATH:-"/home/backup/mongodump"} + if [ -d "$MONGO_BACKUP_PATH" ]; then + for file in "${MONGO_BACKUP_PATH}"/*/*.{json,bson}*; do + # Skip indexes file. + if ! [[ "$file" =~ indexes ]]; then + limit=$(date +"%s" -d "now - 2 day") + updated_at=$(stat -c "%Y" "$file") + if [ -f "$file" ] && [ "$limit" -gt "$updated_at" ]; then + failed "IS_MONGO_BACKUP" "MongoDB hasn't been dumped for more than 2 days" + break + fi + fi + done + else + failed "IS_MONGO_BACKUP" "MongoDB dump directory is missing (${MONGO_BACKUP_PATH})" + fi + fi +} +check_ldap_backup() { + if is_installed slapd; then + # You could change the default path in /etc/evocheck.cf + LDAP_BACKUP_PATH=${LDAP_BACKUP_PATH:-"/home/backup/ldap.bak"} + test -f "$LDAP_BACKUP_PATH" || failed "IS_LDAP_BACKUP" "LDAP dump is missing (${LDAP_BACKUP_PATH})" + fi +} +check_redis_backup() { + if is_installed redis-server; then + # You could change the default path in /etc/evocheck.cf + # REDIS_BACKUP_PATH may contain space-separated paths, example: + # REDIS_BACKUP_PATH='/home/backup/redis-instance1/dump.rdb /home/backup/redis-instance2/dump.rdb' + REDIS_BACKUP_PATH=${REDIS_BACKUP_PATH:-"/home/backup/redis/dump.rdb"} + for file in ${REDIS_BACKUP_PATH}; do + test -f "${file}" || failed "IS_REDIS_BACKUP" "Redis dump is missing (${file})" + done + fi +} +check_elastic_backup() { + if is_installed elasticsearch; then + # You could change the default path in /etc/evocheck.cf + ELASTIC_BACKUP_PATH=${ELASTIC_BACKUP_PATH:-"/home/backup-elasticsearch"} + test -d "$ELASTIC_BACKUP_PATH" || failed "IS_ELASTIC_BACKUP" "Elastic snapshot is missing (${ELASTIC_BACKUP_PATH})" + fi +} +check_duplicate_fs_label() { + # Do it only if thereis blkid binary + BLKID_BIN=$(command -v blkid) + 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" + done + tmpOutput=$(sort < "$tmpFile" | uniq -d) + # If there is no duplicate, uniq will have no output + # So, if $tmpOutput is not null, there is a duplicate + if [ -n "$tmpOutput" ]; then + # shellcheck disable=SC2086 + labels=$(echo -n $tmpOutput | tr '\n' ' ') + failed "IS_DUPLICATE_FS_LABEL" "Duplicate labels: $labels" + fi + else + failed "IS_DUPLICATE_FS_LABEL" "blkid not found in ${PATH}" + fi +} +check_evolix_user() { + grep -q -E "^evolix:" /etc/passwd \ + && failed "IS_EVOLIX_USER" "evolix user should be deleted, used only for install" +} +check_old_home_dir() { + homeDir=${homeDir:-/home} + for dir in "$homeDir"/*; do + statResult=$(stat -c "%n has owner %u resolved as %U" "$dir" \ + | grep -Eve '.bak' -e '\.[0-9]{2}-[0-9]{2}-[0-9]{4}' \ + | grep "UNKNOWN") + # There is at least one dir matching + if [[ -n "$statResult" ]]; then + failed "IS_OLD_HOME_DIR" "$statResult" + test "${VERBOSE}" = 1 || break + fi + done +} +check_tmp_1777() { + actual=$(stat --format "%a" /tmp) + expected="1777" + test "$expected" = "$actual" || failed "IS_TMP_1777" "/tmp must be $expected" +} +check_root_0700() { + actual=$(stat --format "%a" /root) + expected="700" + test "$expected" = "$actual" || failed "IS_ROOT_0700" "/root must be $expected" +} +check_usrsharescripts() { + actual=$(stat --format "%a" /usr/share/scripts) + expected="700" + test "$expected" = "$actual" || failed "IS_USRSHARESCRIPTS" "/usr/share/scripts must be $expected" +} +check_sshpermitrootno() { + # shellcheck disable=SC2086 + if ! (sshd -T 2> /dev/null | grep -qi 'permitrootlogin no'); then + failed "IS_SSHPERMITROOTNO" "PermitRoot should be set to no" + fi +} +check_evomaintenanceusers() { + if [ -f /etc/sudoers.d/evolinux ]; then + sudoers="/etc/sudoers.d/evolinux" + else + sudoers="/etc/sudoers" + fi + # combine users from User_Alias and sudo group + users=$({ grep "^User_Alias *ADMIN" $sudoers | cut -d= -f2 | tr -d " "; grep "^sudo" /etc/group | cut -d: -f 4; } | tr "," "\n" | sort -u) + + for user in $users; do + user_home=$(getent passwd "$user" | cut -d: -f6) + if [ -n "$user_home" ] && [ -d "$user_home" ]; then + if ! grep -qs "^trap.*sudo.*evomaintenance.sh" "${user_home}"/.*profile; then + failed "IS_EVOMAINTENANCEUSERS" "${user} doesn't have an evomaintenance trap" + test "${VERBOSE}" = 1 || break + fi + fi + done +} +check_evomaintenanceconf() { + f=/etc/evomaintenance.cf + if [ -e "$f" ]; then + perms=$(stat -c "%a" $f) + test "$perms" = "600" || failed "IS_EVOMAINTENANCECONF" "Wrong permissions on \`$f' ($perms instead of 600)" + + { grep "^export PGPASSWORD" $f | grep -qv "your-passwd" \ + && grep "^PGDB" $f | grep -qv "your-db" \ + && grep "^PGTABLE" $f | grep -qv "your-table" \ + && grep "^PGHOST" $f | grep -qv "your-pg-host" \ + && grep "^FROM" $f | grep -qv "jdoe@example.com" \ + && grep "^FULLFROM" $f | grep -qv "John Doe " \ + && grep "^URGENCYFROM" $f | grep -qv "mama.doe@example.com" \ + && grep "^URGENCYTEL" $f | grep -qv "06.00.00.00.00" \ + && grep "^REALM" $f | grep -qv "example.com" + } || failed "IS_EVOMAINTENANCECONF" "evomaintenance is not correctly configured" + else + failed "IS_EVOMAINTENANCECONF" "Configuration file \`$f' is missing" + fi +} +check_privatekeyworldreadable() { + # a simple globbing fails if directory is empty + if [ -n "$(ls -A /etc/ssl/private/)" ]; then + for f in /etc/ssl/private/*; do + perms=$(stat -L -c "%a" "$f") + if [ "${perms: -1}" != 0 ]; then + failed "IS_PRIVKEYWOLRDREADABLE" "$f is world-readable" + test "${VERBOSE}" = 1 || break + fi + done + fi +} +check_evobackup_incs() { + if is_installed bkctld; then + bkctld_cron_file=${bkctld_cron_file:-/etc/cron.d/bkctld} + if [ -f "${bkctld_cron_file}" ]; then + root_crontab=$(grep -v "^#" "${bkctld_cron_file}") + echo "${root_crontab}" | grep -q "bkctld inc" || failed "IS_EVOBACKUP_INCS" "\`bkctld inc' is missing in ${bkctld_cron_file}" + echo "${root_crontab}" | grep -qE "(check-incs.sh|bkctld check-incs)" || failed "IS_EVOBACKUP_INCS" "\`check-incs.sh' is missing in ${bkctld_cron_file}" + else + failed "IS_EVOBACKUP_INCS" "Crontab \`${bkctld_cron_file}' is missing" + fi + fi +} + +check_osprober() { + if is_installed os-prober qemu-kvm; then + failed "IS_OSPROBER" \ + "Removal of os-prober package is recommended as it can cause serious issue on KVM server" + fi +} + +check_apt_valid_until() { + aptvalidFile="/etc/apt/apt.conf.d/99no-check-valid-until" + aptvalidText="Acquire::Check-Valid-Until no;" + if grep -qs "archive.debian.org" /etc/apt/sources.list /etc/apt/sources.list.d/*; then + if ! grep -qs "$aptvalidText" /etc/apt/apt.conf.d/*; then + failed "IS_APT_VALID_UNTIL" \ + "As you use archive.mirror.org you need ${aptvalidFile}: ${aptvalidText}" + fi + fi +} + +check_chrooted_binary_uptodate() { + # list of processes to check + process_list="sshd" + for process_name in ${process_list}; do + # what is the binary path? + original_bin=$(command -v "${process_name}") + for pid in $(pgrep ${process_name}); do + process_bin=$(realpath "/proc/${pid}/exe") + # Is the process chrooted? + real_root=$(realpath "/proc/${pid}/root") + if [ "${real_root}" != "/" ]; then + chrooted_md5=$(md5sum "${process_bin}" | cut -f 1 -d ' ') + original_md5=$(md5sum "${original_bin}" | cut -f 1 -d ' ') + # compare md5 checksums + if [ "$original_md5" != "$chrooted_md5" ]; then + failed "IS_CHROOTED_BINARY_UPTODATE" "${process_bin} (${pid}) is different than ${original_bin}." + test "${VERBOSE}" = 1 || break + fi + fi + done + done +} + +check_lxc_container_resolv_conf() { + if is_installed lxc; then + container_list=$(lxc-ls) + current_resolvers=$(grep nameserver /etc/resolv.conf | sed 's/nameserver//g' ) + + for container in $container_list; do + if [ -f "/var/lib/lxc/${container}/rootfs/etc/resolv.conf" ]; then + + while read -r resolver; do + if ! grep -qE "^nameserver\s+${resolver}" "/var/lib/lxc/${container}/rootfs/etc/resolv.conf"; then + failed "IS_LXC_CONTAINER_RESOLV_CONF" "resolv.conf miss-match beween host and container : missing nameserver ${resolver} in container ${container} resolv.conf" + fi + done <<< "${current_resolvers}" + + else + failed "IS_LXC_CONTAINER_RESOLV_CONF" "resolv.conf missing in container ${container}" + fi + done + fi +} +download_versions() { + local file + file=${1:-} + + ## The file is supposed to list programs : each on a line, then its latest version number + ## Examples: + # evoacme 21.06 + # evomaintenance 0.6.4 + + versions_url="https://upgrades.evolix.org/versions-${DEBIAN_RELEASE}" + + # fetch timeout, in seconds + timeout=10 + + if command -v curl > /dev/null; then + curl --max-time ${timeout} --fail --silent --output "${versions_file}" "${versions_url}" + elif command -v wget > /dev/null; then + wget --timeout=${timeout} --quiet "${versions_url}" -O "${versions_file}" + elif command -v GET; then + GET -t ${timeout}s "${versions_url}" > "${versions_file}" + else + failed "IS_CHECK_VERSIONS" "failed to find curl, wget or GET" + fi + test "$?" -eq 0 || failed "IS_CHECK_VERSIONS" "failed to download ${versions_url} to ${versions_file}" +} +get_command() { + local program + program=${1:-} + + case "${program}" in + ## Special cases where the program name is different than the command name + evocheck) echo "${0}" ;; + evomaintenance) command -v "evomaintenance.sh" ;; + 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}" ;; + esac +} +get_version() { + local program + local command + program=${1:-} + command=${2:-} + + case "${program}" in + ## Special case if `command --version => 'command` is not the standard way to get the version + # my_command) + # /path/to/my_command --get-version + # ;; + + add-vm) + grep '^VERSION=' "${command}" | head -1 | cut -d '=' -f 2 + ;; + minifirewall) + ${command} version | 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 + ${command} --version 2> /dev/null | head -1 | cut -d ' ' -f 3 + else + grep '^VERSION=' "${command}" | head -1 | cut -d '=' -f 2 + fi + ;; + + ## General case to get the version + *) ${command} --version 2> /dev/null | head -1 | cut -d ' ' -f 3 ;; + esac +} +check_version() { + local program + local expected_version + program=${1:-} + expected_version=${2:-} + + command=$(get_command "${program}") + if [ -n "${command}" ]; then + # shellcheck disable=SC2086 + 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_CHECK_VERSIONS" "failed to lookup actual version of ${program}" + elif dpkg --compare-versions "${actual_version}" lt "${expected_version}"; then + 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_CHECK_VERSIONS" "${program} version ${actual_version} is newer than expected version ${expected_version}, you should update your index." + else + : # Version check OK + fi + fi +} +add_to_path() { + local new_path + new_path=${1:-} + + echo "$PATH" | grep -qF "${new_path}" || export PATH="${PATH}:${new_path}" +} +check_versions() { + 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" + + grep -v '^ *#' < "${versions_file}" | while IFS= read -r line; do + local program + local version + program=$(echo "${line}" | cut -d ' ' -f 1) + version=$(echo "${line}" | cut -d ' ' -f 2) + + if [ -n "${program}" ]; then + if [ -n "${version}" ]; then + check_version "${program}" "${version}" + else + failed "IS_CHECK_VERSIONS" "failed to lookup expected version for ${program}" + fi + fi + done +} + +main() { + # Default return code : 0 = no error + RC=0 + # 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}" + + test "${IS_TMP_1777:=1}" = 1 && check_tmp_1777 + test "${IS_ROOT_0700:=1}" = 1 && check_root_0700 + test "${IS_USRSHARESCRIPTS:=1}" = 1 && check_usrsharescripts + test "${IS_SSHPERMITROOTNO:=1}" = 1 && check_sshpermitrootno + test "${IS_EVOMAINTENANCEUSERS:=1}" = 1 && check_evomaintenanceusers + # Verification de la configuration d'evomaintenance + test "${IS_EVOMAINTENANCECONF:=1}" = 1 && check_evomaintenanceconf + test "${IS_PRIVKEYWOLRDREADABLE:=1}" = 1 && check_privatekeyworldreadable + + test "${IS_LSBRELEASE:=1}" = 1 && check_lsbrelease + test "${IS_DPKGWARNING:=1}" = 1 && check_dpkgwarning + test "${IS_NRPEPOSTFIX:=1}" = 1 && check_nrpepostfix + test "${IS_MODSECURITY:=1}" = 1 && check_modsecurity + test "${IS_CUSTOMSUDOERS:=1}" = 1 && check_customsudoers + test "${IS_VARTMPFS:=1}" = 1 && check_vartmpfs + test "${IS_SERVEURBASE:=1}" = 1 && check_serveurbase + test "${IS_LOGROTATECONF:=1}" = 1 && check_logrotateconf + test "${IS_SYSLOGCONF:=1}" = 1 && check_syslogconf + test "${IS_DEBIANSECURITY:=1}" = 1 && check_debiansecurity + test "${IS_APTITUDEONLY:=1}" = 1 && check_aptitudeonly + test "${IS_APTICRON:=0}" = 1 && check_apticron + test "${IS_USRRO:=1}" = 1 && check_usrro + test "${IS_TMPNOEXEC:=1}" = 1 && check_tmpnoexec + test "${IS_MOUNT_FSTAB:=1}" = 1 && check_mountfstab + test "${IS_LISTCHANGESCONF:=1}" = 1 && check_listchangesconf + test "${IS_CUSTOMCRONTAB:=1}" = 1 && check_customcrontab + test "${IS_SSHALLOWUSERS:=1}" = 1 && check_sshallowusers + test "${IS_DISKPERF:=0}" = 1 && check_diskperf + test "${IS_TMOUTPROFILE:=1}" = 1 && check_tmoutprofile + test "${IS_ALERT5BOOT:=1}" = 1 && check_alert5boot + test "${IS_ALERT5MINIFW:=1}" = 1 && check_alert5minifw + test "${IS_ALERT5MINIFW:=1}" = 1 && test "${IS_MINIFW:=1}" = 1 && check_minifw + test "${IS_NRPEPERMS:=1}" = 1 && check_nrpeperms + test "${IS_MINIFWPERMS:=1}" = 1 && check_minifwperms + test "${IS_NRPEDISKS:=0}" = 1 && check_nrpedisks + test "${IS_NRPEPID:=1}" = 1 && check_nrpepid + test "${IS_GRSECPROCS:=1}" = 1 && check_grsecprocs + test "${IS_APACHEMUNIN:=1}" = 1 && check_apachemunin + test "${IS_MYSQLUTILS:=1}" = 1 && check_mysqlutils + test "${IS_RAIDSOFT:=1}" = 1 && check_raidsoft + test "${IS_AWSTATSLOGFORMAT:=1}" = 1 && check_awstatslogformat + test "${IS_MUNINLOGROTATE:=1}" = 1 && check_muninlogrotate + test "${IS_SQUID:=1}" = 1 && check_squid + test "${IS_EVOMAINTENANCE_FW:=1}" = 1 && check_evomaintenance_fw + test "${IS_MODDEFLATE:=1}" = 1 && check_moddeflate + test "${IS_LOG2MAILRUNNING:=1}" = 1 && check_log2mailrunning + test "${IS_LOG2MAILAPACHE:=1}" = 1 && check_log2mailapache + test "${IS_LOG2MAILMYSQL:=1}" = 1 && check_log2mailmysql + test "${IS_LOG2MAILSQUID:=1}" = 1 && check_log2mailsquid + test "${IS_BINDCHROOT:=1}" = 1 && check_bindchroot + test "${IS_NETWORK_INTERFACES:=1}" = 1 && check_network_interfaces + test "${IS_AUTOIF:=1}" = 1 && check_autoif + test "${IS_INTERFACESGW:=1}" = 1 && check_interfacesgw + test "${IS_EVOBACKUP:=1}" = 1 && check_evobackup + test "${IS_EVOBACKUP_EXCLUDE_MOUNT:=1}" = 1 && check_evobackup_exclude_mount + test "${IS_USERLOGROTATE:=1}" = 1 && check_userlogrotate + test "${IS_APACHECTL:=1}" = 1 && check_apachectl + test "${IS_APACHESYMLINK:=1}" = 1 && check_apachesymlink + test "${IS_APACHEIPINALLOW:=1}" = 1 && check_apacheipinallow + test "${IS_MUNINAPACHECONF:=1}" = 1 && check_muninapacheconf + test "${IS_PHPMYADMINAPACHECONF:=1}" = 1 && check_phpmyadminapacheconf + test "${IS_KERNELUPTODATE:=1}" = 1 && check_kerneluptodate + test "${IS_UPTIME:=1}" = 1 && check_uptime + test "${IS_MUNINRUNNING:=1}" = 1 && check_muninrunning + test "${IS_BACKUPUPTODATE:=1}" = 1 && check_backupuptodate + test "${IS_ETCGIT:=1}" = 1 && check_etcgit + test "${IS_GITPERMS:=1}" = 1 && check_gitperms + test "${IS_NOTUPGRADED:=1}" = 1 && check_notupgraded + test "${IS_TUNE2FS_M5:=1}" = 1 && check_tune2fs_m5 + test "${IS_BROADCOMFIRMWARE:=1}" = 1 && check_broadcomfirmware + test "${IS_HARDWARERAIDTOOL:=1}" = 1 && check_hardwareraidtool + test "${IS_SQL_BACKUP:=1}" = 1 && check_sql_backup + test "${IS_POSTGRES_BACKUP:=1}" = 1 && check_postgres_backup + test "${IS_MONGO_BACKUP:=1}" = 1 && check_mongo_backup + test "${IS_LDAP_BACKUP:=1}" = 1 && check_ldap_backup + test "${IS_REDIS_BACKUP:=1}" = 1 && check_redis_backup + test "${IS_ELASTIC_BACKUP:=1}" = 1 && check_elastic_backup + test "${IS_DUPLICATE_FS_LABEL:=1}" = 1 && check_duplicate_fs_label + test "${IS_EVOLIX_USER:=1}" = 1 && check_evolix_user + test "${IS_OLD_HOME_DIR:=0}" = 1 && check_old_home_dir + test "${IS_EVOBACKUP_INCS:=1}" = 1 && check_evobackup_incs + test "${IS_OSPROBER:=1}" = 1 && check_osprober + test "${IS_APT_VALID_UNTIL:=1}" = 1 && check_apt_valid_until + test "${IS_CHROOTED_BINARY_UPTODATE:=1}" = 1 && check_chrooted_binary_uptodate + test "${IS_CHECK_VERSIONS:=1}" = 1 && check_versions + + 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 +readonly PROGNAME + +# shellcheck disable=SC2124 +ARGS=$@ +readonly ARGS + +# Disable LANG* +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 + +# Parse options +# based on https://gist.github.com/deshion/10d3cb5f88a21671e17a +while :; do + case $1 in + -h|-\?|--help) + show_help + exit 0 + ;; + --version) + show_version + exit 0 + ;; + --cron) + IS_KERNELUPTODATE=0 + IS_UPTIME=0 + IS_MELTDOWN_SPECTRE=0 + IS_CHECK_VERSIONS=0 + IS_NETWORKING_SERVICE=0 + ;; + -v|--verbose) + VERBOSE=1 + ;; + -q|--quiet) + QUIET=1 + VERBOSE=0 + ;; + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + if [ "${QUIET}" != 1 ]; then + printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2 + fi + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift +done + +# shellcheck disable=SC2086 +main ${ARGS} diff --git a/evocheck/tasks/cron.yml b/evocheck/tasks/cron.yml index ecf1e1d0..4e9c249e 100644 --- a/evocheck/tasks/cron.yml +++ b/evocheck/tasks/cron.yml @@ -1,8 +1,8 @@ --- - name: Check if cron is installed - shell: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'" - args: + ansible.builtin.shell: + cmd: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'" executable: /bin/bash failed_when: False changed_when: False @@ -10,11 +10,11 @@ register: is_cron_installed - name: evocheck crontab is updated - template: + ansible.builtin.template: src: crontab.j2 dest: /etc/cron.d/evocheck mode: "0644" owner: root group: root - force: yes + force: true when: is_cron_installed.rc == 0 diff --git a/evocheck/tasks/exec.yml b/evocheck/tasks/exec.yml index 306cf019..d5aa9320 100644 --- a/evocheck/tasks/exec.yml +++ b/evocheck/tasks/exec.yml @@ -1,6 +1,7 @@ --- - name: run evocheck - command: "{{ evocheck_bin_dir }}/evocheck.sh" + ansible.builtin.command: + cmd: "{{ evocheck_bin_dir }}/evocheck.sh" register: evocheck_run changed_when: False failed_when: False @@ -8,7 +9,7 @@ tags: - evocheck-exec -- debug: +- ansible.builtin.debug: var: evocheck_run.stdout_lines when: evocheck_run.stdout | length > 0 tags: diff --git a/evocheck/tasks/install.yml b/evocheck/tasks/install.yml index 7d4a0e6a..d1c1daf0 100644 --- a/evocheck/tasks/install.yml +++ b/evocheck/tasks/install.yml @@ -1,12 +1,12 @@ --- -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - when: evocheck_bin_dir is search ("/usr") + when: evocheck_bin_dir is search("/usr") tags: - evocheck - name: Scripts dir is present - file: + ansible.builtin.file: path: "{{ evocheck_bin_dir }}" state: directory owner: root @@ -15,20 +15,35 @@ tags: - evocheck +- name: Script for Debian 7 and earlier + ansible.builtin.set_fact: + evocheck_script_src: evocheck.wheezy.sh + when: ansible_distribution_major_version is version('7', '<=') + +- name: Script for Debian 8 + ansible.builtin.set_fact: + evocheck_script_src: evocheck.jessie.sh + when: ansible_distribution_major_version is version('8', '=') + +- name: Script for Debian 9 and later + ansible.builtin.set_fact: + evocheck_script_src: evocheck.sh + when: ansible_distribution_major_version is version('9', '>=') + - name: Copy evocheck.sh - copy: - src: evocheck.sh + ansible.builtin.copy: + src: "{{ evocheck_script_src }}" dest: "{{ evocheck_bin_dir }}/evocheck.sh" mode: "0700" owner: root - force: yes + force: true tags: - evocheck - name: Copy evocheck.cf - copy: + ansible.builtin.copy: src: evocheck.cf dest: /etc/evocheck.cf - force: no + force: false tags: - evocheck diff --git a/evocheck/tasks/main.yml b/evocheck/tasks/main.yml index 2032740b..ad47a24e 100644 --- a/evocheck/tasks/main.yml +++ b/evocheck/tasks/main.yml @@ -1,13 +1,6 @@ --- -- name: Package install is not supported anymore - fail: - msg: Package install is not supported anymore - when: - - evocheck_force_install is defined - - evocheck_force_install == "package" +- ansible.builtin.include: install.yml -- include: install.yml - -- include: cron.yml +- ansible.builtin.include: cron.yml when: evocheck_update_crontab | bool diff --git a/evolinux-base/defaults/main.yml b/evolinux-base/defaults/main.yml index 9debc8ab..497a3d2b 100644 --- a/evolinux-base/defaults/main.yml +++ b/evolinux-base/defaults/main.yml @@ -21,6 +21,8 @@ evolinux_apt_public_sources: True evolinux_apt_upgrade: True evolinux_apt_remove_aptitude: True +apt_keyring_dir: "{{ ansible_distribution_major_version is version('12', '<') | ternary('/etc/apt/trusted.gpg.d', '/etc/apt/keyrings') }}" + # etc-evolinux evolinux_etcevolinux_include: True @@ -48,6 +50,7 @@ evolinux_internal_fqdn: "{{ evolinux_internal_hostname }}.{{ evolinux_intern evolinux_kernel_include: True +evolinux_kernel_cloud_auto: True evolinux_kernel_reboot_after_panic: True evolinux_kernel_disable_tcp_timestamps: True evolinux_kernel_customize_swappiness: True @@ -77,7 +80,6 @@ evolinux_packages_include: True evolinux_packages_system: True evolinux_packages_diagnostic: True -evolinux_packages_hardware: True evolinux_packages_hardware_raid: True evolinux_packages_common: True evolinux_packages_stretch: True @@ -108,6 +110,7 @@ evolinux_system_profile: True evolinux_system_dirmode_adduser: True evolinux_system_restrict_securetty: False evolinux_system_set_timeout: True +evolinux_system_timeout: 36000 evolinux_system_cron_verboselog: True evolinux_system_cron_umask: True evolinux_system_cron_random: True @@ -223,4 +226,7 @@ evolinux_generateldif_include: True evolinux_cron_checkhpraid_frequency: daily # Motd -evolinux_motd_include: True \ No newline at end of file +evolinux_motd_include: True + +# Utils +evolinux_utils_include: True \ No newline at end of file diff --git a/evolinux-base/files/backup-server-state.sh b/evolinux-base/files/backup-server-state.sh deleted file mode 100644 index 3c42695d..00000000 --- a/evolinux-base/files/backup-server-state.sh +++ /dev/null @@ -1,1042 +0,0 @@ -#!/bin/sh - -PROGNAME="backup-server-state" - -VERSION="22.03.4" -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) - nft_bin=$(command -v nft) - - if [ -n "${nft_bin}" ]; then - debug "* nft found, skip iptables" - else - if [ -n "${iptables_bin}" ]; then - last_result=$({ ${iptables_bin} -L -n -v; ${iptables_bin} -t filter -L -n -v; } >> "${backup_dir}/iptables-v.txt") - last_rc=$? - - if [ ${last_rc} -eq 0 ]; then - debug "* iptables -v OK" - else - debug "* iptables -v ERROR" - debug "${last_result}" - rc=10 - fi - - last_result=$({ ${iptables_bin} -L -n; ${iptables_bin} -t filter -L -n; } >> "${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 - fi -} - -backup_sysctl() { - debug "Backup sysctl values" - - sysctl_bin=$(command -v sysctl) - - if [ -n "${sysctl_bin}" ]; then - last_result=$(${sysctl_bin} -a --ignore 2>/dev/null | 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 - # Look for local MySQL or MariaDB process - if pgrep mysqld > /dev/null || pgrep mariadbd > /dev/null; 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 "* no mysqld or mariadbd process is running" - 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/dir-check.sh b/evolinux-base/files/dir-check.sh new file mode 100644 index 00000000..d127632d --- /dev/null +++ b/evolinux-base/files/dir-check.sh @@ -0,0 +1,335 @@ +#!/bin/sh + +PROGNAME="dir-check" +REPOSITORY="https://gitea.evolix.org/evolix/ansible-roles" + +VERSION="22.06.1" +readonly VERSION + +show_version() { + cat <, + Jérémy Lecour + +${REPOSITORY} + +${PROGNAME} comes with ABSOLUTELY NO WARRANTY. This is free software, +and you are welcome to redistribute it under certain conditions. +See the GNU Affero General Public License v3.0 for details. +END +} +show_help() { + cat <> "${log_file}" + else + log_line "${level}" "${msg}" >&2 + fi + fi +} +log_info() { + level="INFO" + msg=$1 + if ! is_quiet; then + if is_log_file; then + log_line "${level}" "${msg}" >> "${log_file}" + else + log_line "${level}" "${msg}" >&2 + fi + fi +} +log_warning() { + level="WARNING" + msg=$1 + if ! is_quiet; then + if is_log_file; then + log_line "${level}" "${msg}" >> "${log_file}" + else + log_line "${level}" "${msg}" >&2 + fi + fi +} +log_error() { + level="ERROR" + msg=$1 + if ! is_quiet; then + if is_log_file; then + log_line "${level}" "${msg}" >> "${log_file}" + if tty -s; then + printf "%s\n" "${msg}" >&2 + fi + else + log_line "${level}" "${msg}" >&2 + fi + fi +} +log_fatal() { + level="FATAL" + msg=$1 + if is_log_file; then + log_line "${level}" "${msg}" >> "${log_file}" + if tty -s; then + printf "%s\n" "${msg}" >&2 + fi + else + log_line "${level}" "${msg}" >&2 + fi +} + +data_command() { + echo "du --bytes" +} +list_files_with_size() { + path=$1 + # shellcheck disable=SC2014,SC2046 + find "${path}" -type f -exec $(data_command) {} \; | sort -k2 +} +prepare_data() { + list_files_with_size "${final_dir}" > "${data_file}" + "${checksum_bin}" "${data_file}" > "${checksum_file}" +} +check_data() { + if [ -f "${checksum_file}" ]; then + # subshell to scope the commands to "parent_dir" + "${checksum_bin}" --status --check "${checksum_file}" + last_rc=$? + if [ ${last_rc} -eq 0 ]; then + log_debug "Verification succeeded with checksum file \`${checksum_file}' (inside \`${parent_dir}')." + else + log_error "Verification failed with checksum file \`${checksum_file}' (inside \`${parent_dir}')." + exit 1 + fi + else + log_warning "Couldn't find checksum file \`${checksum_file}' (inside \`${parent_dir}'). Skip verification." + fi + if [ -f "${data_file}" ]; then + while read -r data_line; do + expected_size=$(echo "${data_line}" | cut -f1) + file=$(echo "${data_line}" | cut -f2) + + if [ -f "${file}" ]; then + actual_size=$($(data_command) "${file}" | cut -f1) + + if [ "${actual_size}" = "${expected_size}" ]; then + log_debug "File \`${file}' has a consistent size of ${actual_size}." + else + log_error "File \`${file}' has an actual size of ${actual_size} instead of ${expected_size}." + rc=1 + fi + else + log_error "Couldn't find file \`${file}'." + rc=1 + fi + done < "${data_file}" + if [ ${rc} -eq 0 ]; then + log_info "Directory \`${final_dir}' is consistent with data stored in \`${data_file}' (inside \`${parent_dir}')." + else + log_error "Directory \`${final_dir}' is not consistent with data stored in \`${data_file}' (inside \`${parent_dir}')." + fi + else + log_fatal "Couldn't find data file \`${data_file}' (inside \`${parent_dir}')." + exit 1 + fi +} + +main() { + if [ -z "${dir}" ]; then + log_fatal "dir option is empty" + exit 1 + elif [ -e "${dir}" ] && [ ! -d "${dir}" ]; then + log_fatal "Directory \`${dir}' exists but is not a directory" + exit 1 + fi + + checksum_cmd="sha256sum" + checksum_bin=$(command -v ${checksum_cmd}) + if [ -z "${checksum_bin}" ]; then + log_fatal "Couldn't find \`${checksum_cmd}'.\nUse 'apt install ${checksum_cmd}'." + exit 1 + fi + + parent_dir=$(dirname "${dir}") + final_dir=$(basename "${dir}") + + data_file="${final_dir}.${PROGNAME}.db" + checksum_file="${data_file}.${checksum_cmd}" + + cwd=${PWD} + cd "${parent_dir}" || log_error "Impossible to change to \`${parent_dir}'" + + if [ -z "${action}" ]; then + log_fatal "Missing --check or --prepare option." + echo "" >&2 + show_help >&2 + exit 1 + fi + + case ${action} in + check) + check_data + ;; + prepare) + prepare_data + ;; + *) + log_fatal "Unknown action \`${action}'." + rc=1 + ;; + esac + + if [ -d "${cwd}" ]; then + cd "${cwd}" || log_error "Impossible to change back to \`${cwd}'" + else + log_error "Previous working directory \`${cwd}' is not a directory." + fi +} + +# Declare variables + +verbose="" +quiet="" +action="" +dir="" +rc=0 + +# 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 + ;; + + -d|--dir) + # with value separated by space + if [ -n "$2" ]; then + dir="$2" + shift + else + log_fatal 'ERROR: "--dir" requires a non-empty option argument.' + fi + ;; + --dir=?*) + # with value speparated by = + dir=${1#*=} + ;; + --dir=) + # without value + log_fatal '"--dir" requires a non-empty option argument.' + ;; + + -l|--log) + # with value separated by space + if [ -n "$2" ]; then + log_file="$2" + shift + else + log_fatal 'ERROR: "--log" requires a non-empty option argument.' + fi + ;; + --log=?*) + # with value speparated by = + log_file=${1#*=} + ;; + --log=) + # without value + log_fatal '"--log" requires a non-empty option argument.' + ;; + + --prepare) + action="prepare" + ;; + + --check) + action="check" + ;; + + -v|--verbose) + verbose=1 + ;; + + --quiet) + quiet=1 + verbose=0 + ;; + + --) + # End of all options. + shift + break + ;; + -?*|[[:alnum:]]*) + # ignore unknown options + if tty -s; then + printf 'Unknown option : %s\n' "$1" >&2 + echo "" >&2 + show_usage >&2 + exit 1 + else + log_fatal "Unknown option : $1" + fi + ;; + *) + # Default case: If no more options then break out of the loop. + break + ;; + esac + + shift +done + +# Default values + +verbose=${verbose:-0} +quiet=${quiet:-0} +action=${action:-} +log_file=${log_file:-} + +set -u + +main + +exit ${rc} \ No newline at end of file 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/update-evobackup-canary b/evolinux-base/files/update-evobackup-canary new file mode 100644 index 00000000..868c3be6 --- /dev/null +++ b/evolinux-base/files/update-evobackup-canary @@ -0,0 +1,129 @@ +#!/bin/sh + +PROGNAME="update-evobackup-canary" +REPOSITORY="https://gitea.evolix.org/evolix/evobackup" + +VERSION="22.06" +readonly VERSION + +# base functions + +show_version() { + cat <, + Jérémy Lecour , + 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 <> "${canary_file}" +} + +# 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 + ;; + + -w|--who) + # with value separated by space + if [ -n "$2" ]; then + who=$2 + shift + else + printf 'ERROR: "-w|--who" requires a non-empty option argument.\n' >&2 + exit 1 + fi + ;; + --who=?*) + # with value speparated by = + who=${1#*=} + ;; + --who=) + # without value + printf 'ERROR: "--who" requires a non-empty option argument.\n' >&2 + exit 1 + ;; + + -f|--file) + # with value separated by space + if [ -n "$2" ]; then + canary_file=$2 + shift + else + printf 'ERROR: "-f|--file" requires a non-empty option argument.\n' >&2 + exit 1 + fi + ;; + --file=?*) + # with value speparated by = + canary_file=${1#*=} + ;; + --file=) + # without value + printf 'ERROR: "--file" requires a non-empty option argument.\n' >&2 + exit 1 + ;; + + --) + # 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 + +export LC_ALL=C + +set -u + +main diff --git a/evolinux-base/handlers/main.yml b/evolinux-base/handlers/main.yml index 80b7378e..1c6df437 100644 --- a/evolinux-base/handlers/main.yml +++ b/evolinux-base/handlers/main.yml @@ -1,74 +1,81 @@ --- - name: dpkg-reconfigure-debconf - command: dpkg-reconfigure --frontend noninteractive debconf + ansible.builtin.command: + cmd: dpkg-reconfigure --frontend noninteractive debconf - name: dpkg-reconfigure-locales - command: dpkg-reconfigure --frontend noninteractive locales + ansible.builtin.command: + cmd: dpkg-reconfigure --frontend noninteractive locales - name: dpkg-reconfigure-apt - command: dpkg-reconfigure --frontend noninteractive apt-listchanges + ansible.builtin.command: + cmd: dpkg-reconfigure --frontend noninteractive apt-listchanges # - name: debconf-set-selections # command: debconf-set-selections /root/debconf-preseed - name: apt update - apt: + ansible.builtin.apt: update_cache: yes - name: restart rsyslog - service: + ansible.builtin.service: name: rsyslog state: restarted - name: remount /home - command: mount -o remount /home - args: - warn: no + ansible.builtin.command: + cmd: mount -o remount /home - name: remount /var - command: mount -o remount /var - args: - warn: no + ansible.builtin.command: + cmd: mount -o remount /var - name: restart nginx - service: + ansible.builtin.service: name: nginx state: restarted - name: reload nginx - service: + ansible.builtin.service: name: nginx state: reloaded - name: restart apache - service: + ansible.builtin.service: name: apache2 state: restarted - name: reload apache - service: + ansible.builtin.service: name: apache2 state: reloaded - name: restart cron - service: + ansible.builtin.service: name: cron state: restarted - name: newaliases - command: newaliases + ansible.builtin.command: + cmd: newaliases changed_when: False - name: reload sshd - service: + ansible.builtin.service: name: ssh state: reloaded - name: reload postfix - service: + ansible.builtin.service: name: postfix state: reloaded +- name: restart log2mail + ansible.builtin.service: + name: log2mail + state: restarted + diff --git a/evolinux-base/tasks/default_www.yml b/evolinux-base/tasks/default_www.yml index 84580b54..673ac397 100644 --- a/evolinux-base/tasks/default_www.yml +++ b/evolinux-base/tasks/default_www.yml @@ -1,13 +1,13 @@ --- - name: /var/www is present - file: + ansible.builtin.file: path: /var/www state: directory mode: "0755" when: evolinux_default_www_files | bool - name: images are copied - copy: + ansible.builtin.copy: src: default_www/img dest: /var/www/ mode: "0644" @@ -16,11 +16,11 @@ when: evolinux_default_www_files | bool - name: index is copied - template: + ansible.builtin.template: src: default_www/index.html.j2 dest: /var/www/index.html mode: "0644" - force: no + force: false when: evolinux_default_www_files | bool # SSL cert @@ -28,21 +28,23 @@ - name: Default certificate is present block: - name: Create private key and csr for default site ({{ ansible_fqdn }}) - command: openssl req -newkey rsa:2048 -sha256 -nodes -keyout /etc/ssl/private/{{ ansible_fqdn }}.key -out /etc/ssl/{{ ansible_fqdn }}.csr -batch -subj "/CN={{ ansible_fqdn }}" + ansible.builtin.command: + cmd: openssl req -newkey rsa:2048 -sha256 -nodes -keyout /etc/ssl/private/{{ ansible_fqdn }}.key -out /etc/ssl/{{ ansible_fqdn }}.csr -batch -subj "/CN={{ ansible_fqdn }}" args: creates: "/etc/ssl/private/{{ ansible_fqdn }}.key" - name: Adjust rights on private key - file: + ansible.builtin.file: path: /etc/ssl/private/{{ ansible_fqdn }}.key owner: root group: ssl-cert mode: "0640" - name: Create certificate for default site - command: openssl x509 -req -days 3650 -sha256 -in /etc/ssl/{{ ansible_fqdn }}.csr -signkey /etc/ssl/private/{{ ansible_fqdn }}.key -out /etc/ssl/certs/{{ ansible_fqdn }}.crt + ansible.builtin.command: + cmd: openssl x509 -req -days 3650 -sha256 -in /etc/ssl/{{ ansible_fqdn }}.csr -signkey /etc/ssl/private/{{ ansible_fqdn }}.key -out /etc/ssl/certs/{{ ansible_fqdn }}.crt args: creates: "/etc/ssl/certs/{{ ansible_fqdn }}.crt" when: evolinux_default_www_ssl_cert | bool -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers diff --git a/evolinux-base/tasks/dump-server-state.yml b/evolinux-base/tasks/dump-server-state.yml new file mode 100644 index 00000000..65ff2f45 --- /dev/null +++ b/evolinux-base/tasks/dump-server-state.yml @@ -0,0 +1,15 @@ +- name: dump-server-state script is present + ansible.builtin.copy: + 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 + ansible.builtin.file: + src: /usr/local/sbin/dump-server-state + dest: /usr/local/sbin/backup-server-state + state: link + force: true diff --git a/evolinux-base/tasks/etc-evolinux.yml b/evolinux-base/tasks/etc-evolinux.yml index 56b0a976..5ee3c238 100644 --- a/evolinux-base/tasks/etc-evolinux.yml +++ b/evolinux-base/tasks/etc-evolinux.yml @@ -2,12 +2,12 @@ ### This is taken care of by the evolinux-todo role # - name: /etc/evolinux exists -# file: +# ansible.builtin.file: # dest: /etc/evolinux # owner: root # group: root # mode: "0700" # state: directory -- include_role: +- ansible.builtin.include_role: name: evolix/evolinux-todo diff --git a/evolinux-base/tasks/fstab.yml b/evolinux-base/tasks/fstab.yml index a3933844..a99ba692 100644 --- a/evolinux-base/tasks/fstab.yml +++ b/evolinux-base/tasks/fstab.yml @@ -4,69 +4,70 @@ # TODO: try to use the custom mount_uuid module for a different approach - name: Fetch fstab content - command: "grep -v '^#' /etc/fstab" + ansible.builtin.command: + cmd: "grep -v '^#' /etc/fstab" check_mode: no register: fstab_content failed_when: False changed_when: False - name: /home partition is customized - replace: + ansible.builtin.replace: dest: /etc/fstab regexp: '([^#]\s+/home\s+\S+\s+)([a-z,]+)(\s+)' replace: '\1{{ evolinux_fstab_home_options | mandatory }}\3' notify: remount /home when: - - fstab_content.stdout | regex_search('\s/home\s') - - evolinux_fstab_home | bool + - fstab_content.stdout | regex_search('\s/home\s') + - evolinux_fstab_home | bool - name: /tmp partition is customized - replace: + ansible.builtin.replace: dest: /etc/fstab regexp: '([^#]\s+/tmp\s+\S+\s+)([a-z,]+)(\s+)' replace: '\1{{ evolinux_fstab_tmp_options | mandatory }}\3' when: - - fstab_content.stdout | regex_search('\s/tmp\s') - - evolinux_fstab_tmp | bool + - fstab_content.stdout | regex_search('\s/tmp\s') + - evolinux_fstab_tmp | bool - name: /usr partition is customized - replace: + ansible.builtin.replace: dest: /etc/fstab regexp: '([^#]\s+/usr\s+\S+\s+)([a-z,]+)(\s+)' replace: '\1{{ evolinux_fstab_usr_options | mandatory }}\3' when: - - fstab_content.stdout | regex_search('\s/usr\s') - - evolinux_fstab_usr | bool + - fstab_content.stdout | regex_search('\s/usr\s') + - evolinux_fstab_usr | bool - name: /var partition is customized - replace: + ansible.builtin.replace: dest: /etc/fstab regexp: '([^#]\s+/var\s+\S+\s+)([a-z,]+)(\s+)' replace: '\1{{ evolinux_fstab_var_options | mandatory }}\3' notify: remount /var when: - - fstab_content.stdout | regex_search('\s/var\s') - - evolinux_fstab_var | bool + - fstab_content.stdout | regex_search('\s/var\s') + - evolinux_fstab_var | bool - name: /var/tmp is created - mount: + ansible.posix.mount: src: tmpfs name: /var/tmp fstype: tmpfs opts: "{{ evolinux_fstab_var_tmp_options | mandatory }}" state: mounted when: - - evolinux_fstab_var_tmp | bool + - evolinux_fstab_var_tmp | bool - name: /dev/shm is created (Debian 10 and later) - mount: + ansible.posix.mount: src: tmpfs name: /dev/shm fstype: tmpfs opts: "{{ evolinux_fstab_dev_shm_options | mandatory }}" state: mounted when: - - evolinux_fstab_dev_shm | bool - - ansible_distribution_major_version is version('10', '>=') + - evolinux_fstab_dev_shm | bool + - ansible_distribution_major_version is version('10', '>=') -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers diff --git a/evolinux-base/tasks/hardware.dell.yml b/evolinux-base/tasks/hardware.dell.yml new file mode 100644 index 00000000..532b3f58 --- /dev/null +++ b/evolinux-base/tasks/hardware.dell.yml @@ -0,0 +1,109 @@ +--- + +## H745: Broadcom / LSI MegaRAID Tri-Mode SAS3516 (rev 01) +# This is OK + +## H750: Broadcom / LSI MegaRAID 12GSAS/PCIe Secure SAS39xx +# This is still incompatible with Debian + +- name: Check if PERC HBA11 device is present + ansible.builtin.shell: + cmd: "lspci | grep -qE 'MegaRAID.*SAS39xx'" + check_mode: no + register: perc_hba11_search + failed_when: False + changed_when: False + tags: + - packages + +- name: MegaCLI SAS package must not be installed if PERC HBA11 is present + block: + - name: Disable harware RAID tasks + ansible.builtin.set_fact: + evolinux_packages_hardware_raid: False + + - name: blacklist mageclisas-status package + ansible.builtin.blockinfile: + dest: /etc/apt/preferences.d/0-blacklist + create: yes + marker: "## {mark} MEGACLISAS-STATUS BLACKLIST" + block: | + # DO NOT INSTALL THESE PACKAGES ON THIS SERVER + Package: megacli megaclisas-status + Pin: version * + Pin-Priority: -100 + + - name: Remove MegaCLI packages + ansible.builtin.apt: + name: + - megacli + - megaclisas-status + state: absent + when: perc_hba11_search.rc == 0 + +- name: MegaCLI SAS package is present + block: + - name: HWRaid GPG key is installed + ansible.builtin.copy: + src: hwraid.le-vert.net.asc + dest: "{{ apt_keyring_dir }}/hwraid.le-vert.net.asc" + force: true + mode: "0644" + owner: root + group: root + tags: + - packages + when: ansible_distribution_major_version is version('9', '>=') + + - name: Add HW tool repository (Debian <12) + ansible.builtin.apt_repository: + repo: 'deb [signed-by={{ apt_keyring_dir }}/hwraid.le-vert.net.asc] http://hwraid.le-vert.net/debian {{ ansible_distribution_release }} main' + state: present + update_cache: yes + tags: + - packages + when: + - ansible_distribution_major_version is version('12', '<') + + - name: Add HW tool repository (Debian >=12) + ansible.builtin.template: + src: hardware/hwraid.le-vert.net.sources.j2 + dest: /etc/apt/sources.list.d/hwraid.le-vert.net.sources + tags: + - packages + register: hwraid_sources + when: ansible_distribution_major_version is version('12', '>=') + + - name: Update APT cache + ansible.builtin.apt: + update_cache: yes + when: hwraid_sources is changed + + - name: Install packages for DELL/LSI hardware + ansible.builtin.apt: + name: + - megacli + - megaclisas-status + allow_unauthenticated: yes + tags: + - packages + + - name: Configure packages for DELL/LSI hardware + ansible.builtin.template: + src: hardware/megaclisas-statusd.j2 + dest: /etc/default/megaclisas-statusd + mode: "0755" + tags: + - config + + - name: megaclisas-statusd is enabled and started + ansible.builtin.systemd: + name: megaclisas-statusd + enabled: true + state: restarted + tags: + - packages + - config + when: + - "'MegaRAID' in raidmodel.stdout" + diff --git a/evolinux-base/tasks/hardware.hp.yml b/evolinux-base/tasks/hardware.hp.yml new file mode 100644 index 00000000..a22eeb70 --- /dev/null +++ b/evolinux-base/tasks/hardware.hp.yml @@ -0,0 +1,87 @@ +--- + +- name: HPE GPG key is installed + ansible.builtin.copy: + src: hpePublicKey2048_key1.asc + dest: "{{ apt_keyring_dir }}/hpePublicKey2048_key1.asc" + force: true + mode: "0644" + owner: root + group: root + tags: + - packages + +- name: Add HPE repository (Debian <12) + ansible.builtin.apt_repository: + repo: 'deb [signed-by={{ apt_keyring_dir }}/hpePublicKey2048_key1.asc] https://downloads.linux.hpe.com/SDR/repo/mcp {{ ansible_distribution_release }}/current non-free' + state: present + tags: + - packages + when: + - ansible_distribution_major_version is version('12', '<') + +- name: Add HPE repository (Debian >=12) + ansible.builtin.template: + src: hardware/hp.sources.j2 + dest: /etc/apt/sources.list.d/hp.sources + tags: + - packages + when: + - ansible_distribution_major_version is version('12', '>=') + +- name: Install HPE Smart Storage Administrator (ssacli) + ansible.builtin.apt: + name: ssacli + tags: + - packages + +# NOTE: check_hpraid cron use check_hpraid from nagios-nrpe role +# So, if nagios-nrpe role is not installed it will not work +- name: Install and configure check_hpraid cron (HP gen >=10) + block: + - name: check_hpraid cron is present (HP gen >=10) + ansible.builtin.copy: + src: check_hpraid.cron.sh + dest: /etc/cron.{{ evolinux_cron_checkhpraid_frequency | mandatory }}/check_hpraid + mode: "0755" + tags: + - config + when: + - "'Adaptec Smart Storage PQI' in raidmodel.stdout" + +- name: Install and configure cciss-vol-status (HP gen <10) + block: + - name: Install cciss-vol-status (HP gen <10) + ansible.builtin.apt: + name: cciss-vol-status + state: present + tags: + - packages + + - name: cciss-vol-statusd init script is present (HP gen <10) + ansible.builtin.template: + src: hardware/cciss-vol-statusd.j2 + dest: /etc/init.d/cciss-vol-statusd + mode: "0755" + tags: + - packages + + - name: Configure cciss-vol-statusd (HP gen <10) + ansible.builtin.lineinfile: + dest: /etc/default/cciss-vol-statusd + line: 'MAILTO="{{ raid_alert_email or general_alert_email | mandatory }}"' + regexp: 'MAILTO=' + create: yes + tags: + - config + + - name: Enable cciss-vol-status in systemd (HP gen <10) + ansible.builtin.systemd: + name: cciss-vol-statusd + enabled: true + state: restarted + tags: + - packages + - config + when: + - "'Hewlett-Packard Company Smart Array' in raidmodel.stdout" diff --git a/evolinux-base/tasks/hardware.yml b/evolinux-base/tasks/hardware.yml index 2e68cc36..30badf70 100644 --- a/evolinux-base/tasks/hardware.yml +++ b/evolinux-base/tasks/hardware.yml @@ -1,15 +1,24 @@ --- - name: Install pciutils - apt: + ansible.builtin.apt: name: pciutils state: present tags: - packages +- name: firmware-non-free components are installed (Debian 12+) + ansible.builtin.replace: + dest: /etc/apt/sources.list.d/system.sources + regexp: '^(Components: ((?!\bfirmware-non-free\b).)*)$' + replace: '\1 firmware-non-free' + when: + - ansible_distribution_major_version is version('12', '>=') + ## Broadcom NetXtreme II - name: Check if Broadcom NetXtreme II device is present - shell: "lspci | grep -q 'NetXtreme II'" + ansible.builtin.shell: + cmd: "lspci | grep -q 'NetXtreme II'" check_mode: no register: broadcom_netextreme_search failed_when: False @@ -17,23 +26,21 @@ tags: - packages -# TODO: add the "non-free" part to the existing sources -# instead of adding a new source - -- name: Add non-free repo for Broadcom NetXtreme II - include_role: - name: evolix/apt - tasks_from: basics.yml - vars: - apt_basics_components: "main contrib non-free" +- name: Add non-free repo for Broadcom NetXtreme II (Debian <12) + ansible.builtin.replace: + dest: /etc/apt/sources.list + regexp: '^(main ((?!\bnon-free\b).)*)$' + replace: '\1 non-free' tags: - packages - when: broadcom_netextreme_search.rc == 0 + when: + - broadcom_netextreme_search.rc == 0 + - ansible_distribution_major_version is version('12', '<') +## Baremetal servers -## Dedicated hardware - name: Install some additionnals tools when it dedicated hardware - apt: + ansible.builtin.apt: name: - libipc-run-perl - freeipmi @@ -43,14 +50,13 @@ state: present tags: - packages - when: ansible_virtualization_role == "host" ## RAID # Dell and others: MegaRAID SAS # HP gen <10: Hewlett-Packard Company Smart Array # HP gen >=10: Adaptec Smart Storage PQI - name: Detect if RAID is installed - shell: + ansible.builtin.shell: cmd: "lspci -q | grep -e 'RAID bus controller' -e 'Serial Attached SCSI controller'" executable: /bin/bash check_mode: no @@ -60,160 +66,16 @@ tags: - packages -- name: Look for legacy apt keyring - stat: - path: /etc/apt/trusted.gpg - register: _trusted_gpg_keyring - tags: - - packages - -- name: HPE Smart Storage Administrator (ssacli) is present - block: - - name: HPE GPG embedded key is absent - apt_key: - id: "26C2B797" - keyring: /etc/apt/trusted.gpg - state: absent - when: _trusted_gpg_keyring.stat.exists - tags: - - packages - - - name: HPE GPG key is installed - copy: - src: hpePublicKey2048_key1.asc - dest: /etc/apt/trusted.gpg.d/hpePublicKey2048_key1.asc - force: yes - mode: "0644" - owner: root - group: root - tags: - - packages - - - name: Add HPE repository - apt_repository: - repo: 'deb https://downloads.linux.hpe.com/SDR/repo/mcp {{ ansible_distribution_release }}/current non-free' - state: present - tags: - - packages - - - name: Install HPE Smart Storage Administrator (ssacli) - apt: - name: ssacli - tags: - - packages +- name: "HP" + ansible.builtin.import_tasks: hardware.hp.yml when: - - "'Hewlett-Packard Company Smart Array' in raidmodel.stdout" - - "'Adaptec Smart Storage PQI' in raidmodel.stdout" + - "'Hewlett-Packard Company Smart Array' in raidmodel.stdout or 'Adaptec Smart Storage PQI' in raidmodel.stdout" - evolinux_packages_hardware_raid | bool -# NOTE: check_hpraid cron use check_hpraid from nagios-nrpe role -# So, if nagios-nrpe role is not installed it will not work -- name: Install and configure check_hpraid cron (HP gen >=10) - block: - - name: check_hpraid cron is present (HP gen >=10) - copy: - src: check_hpraid.cron.sh - dest: /etc/cron.{{ evolinux_cron_checkhpraid_frequency | mandatory }}/check_hpraid - mode: "0755" - tags: - - config - when: "'Adaptec Smart Storage PQI' in raidmodel.stdout" - -- name: Install and configure cciss-vol-status (HP gen <10) - block: - - name: Install cciss-vol-status (HP gen <10) - apt: - name: cciss-vol-status - state: present - tags: - - packages - - - name: cciss-vol-statusd init script is present (HP gen <10) - template: - src: hardware/cciss-vol-statusd.j2 - dest: /etc/init.d/cciss-vol-statusd - mode: "0755" - tags: - - packages - - - name: Configure cciss-vol-statusd (HP gen <10) - lineinfile: - dest: /etc/default/cciss-vol-statusd - line: 'MAILTO="{{ raid_alert_email or general_alert_email | mandatory }}"' - regexp: 'MAILTO=' - create: yes - tags: - - config - - - name: Enable cciss-vol-status in systemd (HP gen <10) - service: - name: cciss-vol-statusd - enabled: true - state: restarted - tags: - - packages - - config - when: - - "'Hewlett-Packard Company Smart Array' in raidmodel.stdout" - - evolinux_packages_hardware_raid | bool - -- name: MegaCLI SAS package is present - block: - - name: HWRaid embedded GPG key is absent - apt_key: - id: "23B3D3B4" - keyring: /etc/apt/trusted.gpg - state: absent - tags: - - packages - when: _trusted_gpg_keyring.stat.exists - - - name: HWRaid GPG key is installed - copy: - src: hwraid.le-vert.net.asc - dest: /etc/apt/trusted.gpg.d/hwraid.le-vert.net.asc - force: yes - mode: "0644" - owner: root - group: root - tags: - - packages - when: ansible_distribution_major_version is version('9', '>=') - - - name: Add HW tool repository - apt_repository: - repo: 'deb http://hwraid.le-vert.net/debian {{ ansible_distribution_release }} main' - state: present - tags: - - packages - - - name: Install packages for DELL/LSI hardware - apt: - name: - - megacli - - megaclisas-status - allow_unauthenticated: yes - tags: - - packages - - - name: Configure packages for DELL/LSI hardware - template: - src: hardware/megaclisas-statusd.j2 - dest: /etc/default/megaclisas-statusd - mode: "0755" - tags: - - config - - - name: Enable DELL/LSI hardware in systemd - service: - name: megaclisas-statusd - enabled: true - state: restarted - tags: - - packages - - config +- name: "Dell" + ansible.builtin.import_tasks: hardware.dell.yml when: - "'MegaRAID' in raidmodel.stdout" - evolinux_packages_hardware_raid | bool -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers diff --git a/evolinux-base/tasks/hostname.yml b/evolinux-base/tasks/hostname.yml index 2b9cfa93..a361ee44 100644 --- a/evolinux-base/tasks/hostname.yml +++ b/evolinux-base/tasks/hostname.yml @@ -1,23 +1,29 @@ --- - name: dbus is installed - apt: + ansible.builtin.apt: name: dbus state: present +- name: dbus is enabled and started + ansible.builtin.systemd: + name: dbus + state: started + enabled: true + - name: Set hostname "{{ evolinux_hostname }}" - hostname: + ansible.builtin.hostname: name: "{{ evolinux_hostname }}" when: evolinux_hostname_hosts | bool - name: Set right localhost line in /etc/hosts - replace: + ansible.builtin.replace: dest: /etc/hosts regexp: '^127.0.0.1(\s+)localhost.*$' replace: '127.0.0.1\1localhost.localdomain localhost' when: evolinux_hostname_hosts | bool - name: Set ip+fqdn+hostname in /etc/hosts - lineinfile: + ansible.builtin.lineinfile: dest: /etc/hosts regexp: '^{{ ansible_default_ipv4.address }}\s+' line: "{{ ansible_default_ipv4.address }} {{ [evolinux_fqdn, evolinux_internal_fqdn] | unique | join(' ') }} {{ [evolinux_hostname, evolinux_internal_hostname] | unique | join(' ') }}" @@ -25,34 +31,34 @@ when: evolinux_hostname_hosts | bool - name: 127.0.1.1 is removed - lineinfile: + ansible.builtin.lineinfile: dest: /etc/hosts regexp: '^127.0.1.1\s+' state: absent when: evolinux_hostname_hosts | bool - name: /etc/mailname is up-to-date - copy: + ansible.builtin.copy: dest: /etc/mailname content: "{{ evolinux_fqdn }}\n" - force: yes + force: true when: evolinux_hostname_mailname | bool # Override facts - name: Override ansible_hostname fact - set_fact: + ansible.builtin.set_fact: ansible_hostname: "{{ evolinux_hostname }}" when: ansible_hostname != evolinux_hostname - name: Override ansible_domain fact - set_fact: + ansible.builtin.set_fact: ansible_domain: "{{ evolinux_domain }}" when: ansible_domain != evolinux_domain - name: Override ansible_fqdn fact - set_fact: + ansible.builtin.set_fact: ansible_fqdn: "{{ evolinux_fqdn }}" when: ansible_fqdn != evolinux_fqdn -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers diff --git a/evolinux-base/tasks/htop.yml b/evolinux-base/tasks/htop.yml deleted file mode 100644 index eeb59beb..00000000 --- a/evolinux-base/tasks/htop.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: Deploy htop configuration - copy: - src: htoprc - dest: /etc/htoprc - mode: "0644" diff --git a/evolinux-base/tasks/kernel.yml b/evolinux-base/tasks/kernel.yml index 6ddeb57f..da3abf57 100644 --- a/evolinux-base/tasks/kernel.yml +++ b/evolinux-base/tasks/kernel.yml @@ -1,7 +1,25 @@ --- +- name: "Use Cloud kernel on virtual servers" + ansible.builtin.apt: + name: "linux-image-cloud-amd64" + state: present + when: + - ansible_machine == "x86_64" + - ansible_virtualization_role == "guest" + - evolinux_kernel_cloud_auto | bool + +- name: "Remove non-Cloud kernel on virtual servers" + ansible.builtin.apt: + name: "linux-image-amd64" + state: absent + when: + - ansible_machine == "x86_64" + - ansible_virtualization_role == "guest" + - evolinux_kernel_cloud_auto | bool + - name: Reboot after panic - sysctl: + ansible.posix.sysctl: name: "{{ item.name }}" value: "{{ item.value }}" sysctl_file: "{{ evolinux_kernel_sysctl_path }}" @@ -13,7 +31,7 @@ when: evolinux_kernel_reboot_after_panic | bool - name: Don't reboot after panic - sysctl: + ansible.posix.sysctl: name: "{{ item }}" sysctl_file: "{{ evolinux_kernel_sysctl_path }}" state: absent @@ -24,7 +42,7 @@ when: not evolinux_kernel_reboot_after_panic | bool - name: Disable net.ipv4.tcp_timestamps - sysctl: + ansible.posix.sysctl: name: net.ipv4.tcp_timestamps value: '0' sysctl_file: "{{ evolinux_kernel_sysctl_path }}" @@ -33,7 +51,7 @@ when: evolinux_kernel_disable_tcp_timestamps | bool - name: Customize the swappiness - sysctl: + ansible.posix.sysctl: name: vm.swappiness value: "{{ evolinux_kernel_swappiness }}" sysctl_file: "{{ evolinux_kernel_sysctl_path }}" @@ -42,7 +60,7 @@ when: evolinux_kernel_customize_swappiness | bool - name: Patch for TCP stack vulnerability CVE-2016-5696 - sysctl: + ansible.posix.sysctl: name: net.ipv4.tcp_challenge_ack_limit value: "1073741823" sysctl_file: "{{ evolinux_kernel_sysctl_path }}" @@ -51,7 +69,7 @@ when: evolinux_kernel_cve20165696 | bool - name: Patch for TCP stack vulnerability CVE-2018-5391 (FragmentSmack) - sysctl: + ansible.posix.sysctl: name: "{{ item.name }}" value: "{{ item.value }}" sysctl_file: "{{ evolinux_kernel_sysctl_path }}" @@ -63,4 +81,4 @@ - { name: "net.ipv4.ipfrag_high_thresh", value: "262144" } - { name: "net.ipv6.ip6frag_high_thresh", value: "262144" } -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers diff --git a/evolinux-base/tasks/log2mail.yml b/evolinux-base/tasks/log2mail.yml index e6f624c1..9a1f3314 100644 --- a/evolinux-base/tasks/log2mail.yml +++ b/evolinux-base/tasks/log2mail.yml @@ -1,18 +1,35 @@ --- - name: Deploy log2mail systemd unit - copy: + ansible.builtin.copy: src: log2mail.service dest: /etc/systemd/system/log2mail.service mode: "0644" - name: Remove log2mail sysvinit service - file: + ansible.builtin.file: path: /etc/init.d/log2mail state: absent - name: Enable and start log2mail service - systemd: + ansible.builtin.systemd: name: log2mail daemon-reload: yes state: started enabled: yes + +- name: log2mail config is present + ansible.builtin.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/logs.yml b/evolinux-base/tasks/logs.yml index 8298486e..a6dd97ad 100644 --- a/evolinux-base/tasks/logs.yml +++ b/evolinux-base/tasks/logs.yml @@ -3,7 +3,7 @@ # TODO: voir comment faire des backups initiaux des fichiers - name: Copy rsyslog.conf - copy: + ansible.builtin.copy: src: logs/rsyslog.conf dest: /etc/rsyslog.conf mode: "0644" @@ -11,7 +11,8 @@ when: evolinux_logs_rsyslog_conf | bool - name: Disable logrotate default conf - command: mv /etc/logrotate.d/rsyslog /etc/logrotate.d/rsyslog.disabled + ansible.builtin.command: + cmd: mv /etc/logrotate.d/rsyslog /etc/logrotate.d/rsyslog.disabled args: removes: /etc/logrotate.d/rsyslog creates: /etc/logrotate.d/rsyslog.disabled @@ -19,33 +20,33 @@ when: evolinux_logs_disable_logrotate_rsyslog | bool - name: Copy many logrotate files - copy: + ansible.builtin.copy: src: logs/logrotate.d/ dest: /etc/logrotate.d/ when: evolinux_logs_logrotate_confs | bool - name: Copy rsyslog logrotate file - template: + ansible.builtin.template: src: logs/zsyslog.j2 dest: /etc/logrotate.d/zsyslog when: evolinux_logs_logrotate_confs | bool - name: Configure logrotate.conf default rotate value - replace: + ansible.builtin.replace: dest: /etc/logrotate.conf regexp: "rotate [0-9]+" replace: "rotate 12" when: evolinux_logs_default_rotate | bool - name: Enable logrotate.conf dateext option - lineinfile: + ansible.builtin.lineinfile: dest: /etc/logrotate.conf line: "dateext" regexp: "^#?\\s*dateext" when: evolinux_logs_default_dateext | bool - name: Enable logrotate.conf dateformat option - lineinfile: + ansible.builtin.lineinfile: dest: /etc/logrotate.conf line: "dateformat {{ evolinux_logrotate_dateformat | mandatory }}" regexp: "^#?\\s*dateformat.*" @@ -53,11 +54,11 @@ when: evolinux_logs_default_dateext | bool - name: Disable logrotate.conf dateyesterday option - lineinfile: + ansible.builtin.lineinfile: dest: /etc/logrotate.conf line: "# dateyesterday" regexp: "^\\s*dateyesterday" insertafter: 'dateext' when: evolinux_logs_default_dateext | bool -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers diff --git a/evolinux-base/tasks/main.yml b/evolinux-base/tasks/main.yml index dba5e97b..fc9f5b87 100644 --- a/evolinux-base/tasks/main.yml +++ b/evolinux-base/tasks/main.yml @@ -1,64 +1,72 @@ --- - name: "System compatibility checks" - assert: + ansible.builtin.assert: that: - ansible_distribution == "Debian" - ansible_distribution_major_version is version('8', '>=') msg: only compatible with Debian >= 8 - name: Apt configuration - include_role: + ansible.builtin.include_role: name: evolix/apt vars: 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' }}" + apt_basics_components: "{{ (ansible_virtualization_role == 'host') | ternary('main contrib non-free', 'main') }}" when: evolinux_apt_include | bool - name: /etc versioning with Git - include_role: + ansible.builtin.include_role: name: evolix/etc-git when: evolinux_etcgit_include | bool - name: /etc/evolinux base - include: etc-evolinux.yml + ansible.builtin.import_tasks: etc-evolinux.yml when: evolinux_etcevolinux_include | bool - name: Hostname - include: hostname.yml + ansible.builtin.import_tasks: hostname.yml when: evolinux_hostname_include | bool - name: Kernel tuning - include: kernel.yml + ansible.builtin.import_tasks: kernel.yml when: evolinux_kernel_include | bool - name: Fstab configuration - include: fstab.yml + ansible.builtin.import_tasks: fstab.yml when: evolinux_fstab_include | bool - name: Packages - include: packages.yml + ansible.builtin.import_tasks: packages.yml when: evolinux_packages_include | bool - name: System settings - include: system.yml + ansible.builtin.import_tasks: system.yml when: evolinux_system_include | bool - name: Minifirewall - include_role: + ansible.builtin.include_role: name: evolix/minifirewall when: evolinux_minifirewall_include | bool - name: Evomaintenance - include_role: + ansible.builtin.include_role: name: evolix/evomaintenance when: evolinux_evomaintenance_include | bool -- name: SSH configuration - include: ssh.yml - when: evolinux_ssh_include | bool +- name: SSH configuration (single file) + ansible.builtin.import_tasks: ssh.single-file.yml + when: + - ansible_distribution_major_version is version('12', '<') + - evolinux_ssh_include | bool + +- name: SSH configuration (included-files) + ansible.builtin.import_tasks: ssh.included-files.yml + when: + - ansible_distribution_major_version is version('12', '>=') + - evolinux_ssh_include | bool ### disabled because of a memory leak # - name: Create evolinux users @@ -67,72 +75,71 @@ # when: evolinux_users_include - name: Root user configuration - include: root.yml + ansible.builtin.import_tasks: root.yml when: evolinux_root_include | bool - name: Postfix - include: postfix.yml + ansible.builtin.import_tasks: postfix.yml when: evolinux_postfix_include | bool - name: Logs management - include: logs.yml + ansible.builtin.import_tasks: logs.yml when: evolinux_logs_include | bool - name: Default index page - include: default_www.yml + ansible.builtin.import_tasks: default_www.yml when: evolinux_default_www_include | bool - name: Hardware drivers and tools - include: hardware.yml - when: evolinux_hardware_include | bool + ansible.builtin.import_tasks: hardware.yml + when: + - evolinux_hardware_include | bool + - ansible_virtualization_role == "host" - name: Customize for Online.net - include: provider_online.yml + ansible.builtin.import_tasks: provider_online.yml when: evolinux_provider_online_include | bool - name: Customize for Orange FCE - include: provider_orange_fce.yml + ansible.builtin.import_tasks: provider_orange_fce.yml when: evolinux_provider_orange_fce_include | bool - name: Override Log2mail service - include: log2mail.yml + ansible.builtin.import_tasks: log2mail.yml when: evolinux_log2mail_include | bool -- include: motd.yml +- ansible.builtin.import_tasks: motd.yml when: evolinux_motd_include | bool -- include: utils.yml +- ansible.builtin.import_tasks: utils.yml + when: evolinux_utils_include | bool - name: Munin - include_role: + ansible.builtin.include_role: name: evolix/munin when: evolinux_munin_include | bool - name: Nagios/NRPE - include_role: + ansible.builtin.include_role: name: evolix/nagios-nrpe when: evolinux_nagios_nrpe_include | bool - name: fail2ban - include_role: + ansible.builtin.include_role: name: evolix/fail2ban when: evolinux_fail2ban_include | bool - name: Evocheck - include_role: + ansible.builtin.include_role: name: evolix/evocheck when: evolinux_evocheck_include | bool - name: Listupgrade - include_role: + ansible.builtin.include_role: name: evolix/listupgrade when: evolinux_listupgrade_include | bool - name: Generate ldif script - include_role: + ansible.builtin.include_role: name: evolix/generate-ldif when: evolinux_generateldif_include | bool - -- include: top.yml - -- include: htop.yml diff --git a/evolinux-base/tasks/motd.yml b/evolinux-base/tasks/motd.yml index 70079463..0d0b7157 100644 --- a/evolinux-base/tasks/motd.yml +++ b/evolinux-base/tasks/motd.yml @@ -1,6 +1,6 @@ --- - name: Deploy custom motd - template: + ansible.builtin.template: src: "{{ item }}" dest: /etc/motd force: True diff --git a/evolinux-base/tasks/packages.yml b/evolinux-base/tasks/packages.yml index f4eafc6c..f8af347a 100644 --- a/evolinux-base/tasks/packages.yml +++ b/evolinux-base/tasks/packages.yml @@ -1,7 +1,7 @@ --- - name: Install/Update system tools - apt: + ansible.builtin.apt: name: - locales - sudo @@ -20,7 +20,7 @@ when: evolinux_packages_system | bool - name: Install/Update diagnostic tools - apt: + ansible.builtin.apt: name: - strace - htop @@ -39,15 +39,15 @@ when: evolinux_packages_diagnostic | bool - name: Install/Update hardware tools - apt: + ansible.builtin.apt: name: - hdparm - smartmontools - lm-sensors - when: evolinux_packages_hardware | bool + when: ansible_virtualization_role == "host" - name: Install/Update common tools - apt: + ansible.builtin.apt: name: - vim - screen @@ -55,7 +55,6 @@ - mutt - tree - git - - subversion - rsync - bc - pinentry-curses @@ -63,21 +62,21 @@ when: evolinux_packages_common | bool - name: Be sure that openntpd package is absent/purged - apt: + ansible.builtin.apt: name: openntpd state: absent purge: True when: evolinux_packages_purge_openntpd | bool - name: the chrony package is absent - apt: + ansible.builtin.apt: name: chrony purge: True state: absent when: evolinux_packages_purge_chrony | bool - name: Be sure locate/mlocate is absent/purged - apt: + ansible.builtin.apt: name: - locate - mlocate @@ -86,20 +85,20 @@ when: evolinux_packages_purge_locate | bool - name: Install/Update serveur-base meta-package - apt: + ansible.builtin.apt: name: serveur-base allow_unauthenticated: yes when: evolinux_packages_serveur_base | bool - name: Install/Update packages for Stretch and later - apt: + ansible.builtin.apt: name: net-tools when: - evolinux_packages_stretch | bool - ansible_distribution_major_version is version('9', '>=') - name: Install/Update packages for Buster and later - apt: + ansible.builtin.apt: name: - spectre-meltdown-checker - binutils @@ -108,14 +107,14 @@ - ansible_distribution_major_version is version('10', '>=') - name: Customize logcheck recipient - lineinfile: + ansible.builtin.lineinfile: dest: /etc/logcheck/logcheck.conf regexp: '^SENDMAILTO=".*"$' line: 'SENDMAILTO="{{ logcheck_alert_email or general_alert_email | mandatory }}"' when: evolinux_packages_logcheck_recipient | bool - name: Deleting rpcbind and nfs-common - apt: + ansible.builtin.apt: name: - rpcbind - nfs-common @@ -126,7 +125,7 @@ # TODO: use ini_file when Ansible > 2.1 (no_extra_spaces: yes) - name: Configure Listchanges on Jessie - lineinfile: + ansible.builtin.lineinfile: dest: /etc/apt/listchanges.conf regexp: '^{{ item.option }}\s*=' line: "{{ item.option }}={{ item.value }}" @@ -139,7 +138,7 @@ - ansible_distribution_release == "jessie" - name: apt-listchanges is absent on Stretch and later - apt: + ansible.builtin.apt: name: apt-listchanges state: absent when: @@ -147,4 +146,4 @@ - ansible_distribution_major_version is version('9', '>=') - evolinux_packages_delete_aptlistchanges -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers diff --git a/evolinux-base/tasks/postfix.yml b/evolinux-base/tasks/postfix.yml index 6a46548b..d9dba3e2 100644 --- a/evolinux-base/tasks/postfix.yml +++ b/evolinux-base/tasks/postfix.yml @@ -1,18 +1,18 @@ --- - name: Postfix packages are installed - apt: + ansible.builtin.apt: name: - postfix - mailgraph state: present - when: evolinux_postfix_packages | bool tags: - packages - postfix + when: evolinux_postfix_packages | bool - name: configure postfix myhostname - lineinfile: + ansible.builtin.lineinfile: dest: /etc/postfix/main.cf state: present line: "myhostname = {{ evolinux_fqdn }}" @@ -22,18 +22,18 @@ - postfix - name: configure postfix mynetworks - lineinfile: + ansible.builtin.lineinfile: dest: /etc/postfix/main.cf state: present - line: "mydestination = {{ [evolinux_fqdn, evolinux_internal_fqdn] | unique | join(' ') }} localhost.localdomain localhost" + line: "mydestination = {{ [evolinux_fqdn, evolinux_internal_fqdn] | unique | join(' ') }} localhost.localdomain localhost localhost.$mydomain" regexp: '^mydestination' notify: reload postfix tags: - postfix - name: fetch users list - shell: "set -o pipefail && getent passwd | cut -d':' -f 1 | grep -v root" - args: + ansible.builtin.shell: + cmd: "set -o pipefail && getent passwd | cut -d':' -f 1 | grep -v root" executable: /bin/bash check_mode: no register: non_root_users_list @@ -42,18 +42,18 @@ - postfix - name: each user is aliased to root - lineinfile: + ansible.builtin.lineinfile: dest: /etc/aliases regexp: "^{{ item }}:.*" line: "{{ item }}: root" loop: "{{ non_root_users_list.stdout_lines }}" notify: newaliases - when: evolinux_postfix_users_alias_root | bool tags: - postfix + when: evolinux_postfix_users_alias_root | bool - name: additional users address aliased to root - lineinfile: + ansible.builtin.lineinfile: dest: /etc/aliases regexp: "^{{ item }}:.*" line: "{{ item }}: root" @@ -65,24 +65,24 @@ - error - bounce notify: newaliases - when: evolinux_postfix_mailer_alias_root | bool tags: - postfix + when: evolinux_postfix_mailer_alias_root | bool - name: root alias is configured - lineinfile: + ansible.builtin.lineinfile: dest: /etc/aliases regexp: "^root:" line: "root: {{ postfix_alias_email or general_alert_email | mandatory }}" notify: newaliases - when: evolinux_postfix_root_alias | bool tags: - postfix + when: evolinux_postfix_root_alias | bool -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers - name: exim4 is absent - apt: + ansible.builtin.apt: name: - exim4 - exim4-base @@ -90,9 +90,9 @@ - exim4-daemon-light purge: yes state: absent - when: evolinux_postfix_purge_exim | bool tags: - packages - postfix + when: evolinux_postfix_purge_exim | bool -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers diff --git a/evolinux-base/tasks/provider_online.yml b/evolinux-base/tasks/provider_online.yml index 8174d15c..5696e504 100644 --- a/evolinux-base/tasks/provider_online.yml +++ b/evolinux-base/tasks/provider_online.yml @@ -1,8 +1,8 @@ -- debug: +- ansible.builtin.debug: msg: "Online DNS servers fails sometimes! Please change them in /etc/resolv.conf." - name: custom NTP server for Online servers - set_fact: + ansible.builtin.set_fact: nagios_nrpe_default_ntp_server: "ntp.online.net" -# - meta: flush_handlers +# - ansible.builtin.meta: flush_handlers diff --git a/evolinux-base/tasks/provider_orange_fce.yml b/evolinux-base/tasks/provider_orange_fce.yml index 4b9a26c7..c861ccd1 100644 --- a/evolinux-base/tasks/provider_orange_fce.yml +++ b/evolinux-base/tasks/provider_orange_fce.yml @@ -1,5 +1,5 @@ - name: Customize kernel for Orange FCE - sysctl: + ansible.posix.sysctl: name: "{{ item.name }}" value: "{{ item.value }}" sysctl_file: /etc/sysctl.d/evolinux_fce.conf @@ -10,7 +10,7 @@ - { name: net.ipv4.tcp_keepalive_intvl, value: 60 } - { name: net.ipv6.conf.all.disable_ipv6, value: 1 } -- debug: +- ansible.builtin.debug: msg: "Orange DNS servers suck! Please change them in /etc/resolv.conf." -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers diff --git a/evolinux-base/tasks/provider_vmware.yml b/evolinux-base/tasks/provider_vmware.yml new file mode 100644 index 00000000..04daa219 --- /dev/null +++ b/evolinux-base/tasks/provider_vmware.yml @@ -0,0 +1,18 @@ +--- +- name: Check if the virtual machine on VMWare Host + ansible.builtin.shell: + cmd: "dmidecode | grep -q 'VMware'" + check_mode: no + register: vmware_provider + failed_when: False + changed_when: False + tags: + - packages + +- name: OpenVM Tools are installed for vmware + ansible.builtin.apt: + state: present + name: open-vm-tools + tags: + - packages + when: vmware_provider.rc == 0 diff --git a/evolinux-base/tasks/root.yml b/evolinux-base/tasks/root.yml index df50d977..98cd3b3d 100644 --- a/evolinux-base/tasks/root.yml +++ b/evolinux-base/tasks/root.yml @@ -1,14 +1,14 @@ --- - name: chmod 700 /root - file: + ansible.builtin.file: path: /root state: directory mode: "0700" when: evolinux_root_chmod | bool - name: "Customize root's bashrc..." - lineinfile: + ansible.builtin.lineinfile: dest: /root/.bashrc line: "{{ item }}" create: yes @@ -24,34 +24,35 @@ ## .bash_history should be append-only - name: Create .bash_history if missing - copy: + ansible.builtin.copy: content: "" dest: "/root/.bash_history" - force: no + force: false when: evolinux_root_bash_history | bool - name: Set umask in /root/.profile - lineinfile: + ansible.builtin.lineinfile: dest: "/root/.profile" line: "umask 0077" regexp: "umask [0-9]+" when: evolinux_root_umask | bool - name: "/usr/share/scripts is present in root's PATH" - lineinfile: + ansible.builtin.lineinfile: dest: "/root/.profile" line: "PATH=\"${PATH}:/usr/share/scripts\"" when: ansible_distribution_major_version is version('10', '>=') - name: Custom git config for root - copy: + ansible.builtin.copy: src: root/gitconfig dest: "/root/.gitconfig" - force: no + force: false when: evolinux_root_gitconfig | bool - name: Is .bash_history append-only - shell: lsattr /root/.bash_history | grep -E "^.*a.* " + ansible.builtin.shell: + cmd: lsattr /root/.bash_history | grep -E "^.*a.* " check_mode: no register: bash_history_append_only failed_when: "'Inappropriate ioctl' in bash_history_append_only.stderr" @@ -59,14 +60,15 @@ changed_when: False - name: Set .bash_history append-only - command: chattr +a /root/.bash_history + ansible.builtin.command: + cmd: chattr +a /root/.bash_history when: - - evolinux_root_bash_history_appendonly | bool - - bash_history_append_only.rc != 0 - - "'Inappropriate ioctl' not in bash_history_append_only.stderr" + - evolinux_root_bash_history_appendonly | bool + - bash_history_append_only.rc != 0 + - "'Inappropriate ioctl' not in bash_history_append_only.stderr" - name: Setting vim as selected-editor - lineinfile: + ansible.builtin.lineinfile: dest: /root/.selected_editor regexp: '^SELECTED_EDITOR=' line: "SELECTED_EDITOR=\"/usr/bin/vim.basic\"" @@ -74,7 +76,7 @@ when: evolinux_root_vim_default | bool - name: Setting vim root configuration - lineinfile: + ansible.builtin.lineinfile: dest: /root/.vimrc line: "{{ item }}" create: yes @@ -89,9 +91,9 @@ when: evolinux_root_vim_conf | bool - name: disable SSH access for root - replace: + ansible.builtin.replace: dest: /etc/ssh/sshd_config - regexp: '^PermitRootLogin (yes|without-password|prohibit-password)' + regexp: '^#?PermitRootLogin (yes|without-password|prohibit-password)' replace: "PermitRootLogin no" validate: '/usr/sbin/sshd -t -f %s' notify: reload sshd @@ -99,7 +101,7 @@ ### Disabled : it seems useless and too dangerous for now # - name: remove root from AllowUsers directive -# replace: +# ansible.builtin.replace: # dest: /etc/ssh/sshd_config # regexp: '^(AllowUsers ((?!root(?:@\S+)?).)*)(\sroot(?:@\S+)?|root(?:@\S+)?\s)(.*)$' # replace: '\1\4' @@ -107,4 +109,4 @@ # notify: reload sshd # when: evolinux_root_disable_ssh -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers diff --git a/evolinux-base/tasks/ssh.included-files.yml b/evolinux-base/tasks/ssh.included-files.yml new file mode 100644 index 00000000..952b661f --- /dev/null +++ b/evolinux-base/tasks/ssh.included-files.yml @@ -0,0 +1,104 @@ +--- +# This is a copy of ssh.single-file.yml +# It needs to be changed when we move to a included-files configuration + + +- ansible.builtin.debug: + msg: "Warning: empty 'evolinux_ssh_password_auth_addresses' variable, tasks will be skipped!" + when: evolinux_ssh_password_auth_addresses == [] + +# From 'man sshd_config' : +# « If all of the criteria on the Match line are satisfied, the keywords +# on the following lines override those set in the global section of the config +# file, until either another Match line or the end of the file. +# If a keyword appears in multiple Match blocks that are satisfied, +# only the first instance of the keyword is applied. » +# +# We want to allow any user from a list of IP addresses to login with password, +# but users of the "{{ evolinux_internal_group }}" group can't login with password from other IP addresses + +- name: "Security directives for Evolinux (Debian 10 or later)" + ansible.builtin.blockinfile: + dest: /etc/ssh/sshd_config + marker: "# {mark} EVOLINUX PASSWORD RESTRICTIONS" + block: | + Match Address {{ evolinux_ssh_password_auth_addresses | join(',') }} + PasswordAuthentication yes + Match Group {{ evolinux_internal_group }} + PasswordAuthentication no + insertafter: EOF + validate: '/usr/sbin/sshd -t -f %s' + notify: reload sshd + when: + - evolinux_ssh_password_auth_addresses != [] + - ansible_distribution_major_version is version('10', '>=') + +- name: Security directives for Evolinux (Jessie/Stretch) + ansible.builtin.blockinfile: + dest: /etc/ssh/sshd_config + marker: "# {mark} EVOLINUX PASSWORD RESTRICTIONS BY ADDRESS" + block: | + Match Address {{ evolinux_ssh_password_auth_addresses | join(',') }} + PasswordAuthentication yes + insertafter: EOF + validate: '/usr/sbin/sshd -t -f %s' + notify: reload sshd + when: + - evolinux_ssh_password_auth_addresses != [] + - ansible_distribution_major_version is version('10', '<') + +# We disable AcceptEnv because it can be a security issue, but also because we +# do not want clients to push their environment variables like LANG. +- name: disable AcceptEnv in ssh config + ansible.builtin.replace: + dest: /etc/ssh/sshd_config + regexp: '^AcceptEnv' + replace: "#AcceptEnv" + notify: reload sshd + when: evolinux_ssh_disable_acceptenv | bool + +- name: Set log level to verbose (for Debian >= 9) + ansible.builtin.replace: + dest: /etc/ssh/sshd_config + regexp: '^#?LogLevel [A-Z]+' + replace: "LogLevel VERBOSE" + notify: reload sshd + when: ansible_distribution_major_version is version('9', '>=') + +- name: "Get current user" + ansible.builtin.command: + cmd: logname + changed_when: False + register: logname + check_mode: no + when: evolinux_ssh_allow_current_user | bool + +# we must double-escape caracters, because python +- name: verify AllowUsers directive + ansible.builtin.command: + cmd: "grep -E '^AllowUsers' /etc/ssh/sshd_config" + failed_when: False + changed_when: False + register: grep_allowusers_ssh + check_mode: no + when: evolinux_ssh_allow_current_user | bool + +- name: "Add AllowUsers sshd directive for current user" + ansible.builtin.lineinfile: + dest: /etc/ssh/sshd_config + line: "\nAllowUsers {{ logname.stdout }}" + insertafter: 'Subsystem' + validate: '/usr/sbin/sshd -t -f %s' + notify: reload sshd + when: evolinux_ssh_allow_current_user and grep_allowusers_ssh.rc != 0 + +- name: "Modify AllowUsers sshd directive for current user" + ansible.builtin.replace: + dest: /etc/ssh/sshd_config + regexp: '^(AllowUsers ((?!{{ logname.stdout }}).)*)$' + replace: '\1 {{ logname.stdout }}' + validate: '/usr/sbin/sshd -t -f %s' + notify: reload sshd + when: evolinux_ssh_allow_current_user and grep_allowusers_ssh.rc == 0 + +- ansible.builtin.meta: flush_handlers diff --git a/evolinux-base/tasks/ssh.single-file.yml b/evolinux-base/tasks/ssh.single-file.yml new file mode 100644 index 00000000..e76d792f --- /dev/null +++ b/evolinux-base/tasks/ssh.single-file.yml @@ -0,0 +1,100 @@ +--- +- ansible.builtin.debug: + msg: "Warning: empty 'evolinux_ssh_password_auth_addresses' variable, tasks will be skipped!" + when: evolinux_ssh_password_auth_addresses == [] + +# From 'man sshd_config' : +# « If all of the criteria on the Match line are satisfied, the keywords +# on the following lines override those set in the global section of the config +# file, until either another Match line or the end of the file. +# If a keyword appears in multiple Match blocks that are satisfied, +# only the first instance of the keyword is applied. » +# +# We want to allow any user from a list of IP addresses to login with password, +# but users of the "{{ evolinux_internal_group }}" group can't login with password from other IP addresses + +- name: "Security directives for Evolinux (Debian 10 or later)" + ansible.builtin.blockinfile: + dest: /etc/ssh/sshd_config + marker: "# {mark} EVOLINUX PASSWORD RESTRICTIONS" + block: | + Match Address {{ evolinux_ssh_password_auth_addresses | join(',') }} + PasswordAuthentication yes + Match Group {{ evolinux_internal_group }} + PasswordAuthentication no + insertafter: EOF + validate: '/usr/sbin/sshd -t -f %s' + notify: reload sshd + when: + - evolinux_ssh_password_auth_addresses != [] + - ansible_distribution_major_version is version('10', '>=') + +- name: Security directives for Evolinux (Jessie/Stretch) + ansible.builtin.blockinfile: + dest: /etc/ssh/sshd_config + marker: "# {mark} EVOLINUX PASSWORD RESTRICTIONS BY ADDRESS" + block: | + Match Address {{ evolinux_ssh_password_auth_addresses | join(',') }} + PasswordAuthentication yes + insertafter: EOF + validate: '/usr/sbin/sshd -t -f %s' + notify: reload sshd + when: + - evolinux_ssh_password_auth_addresses != [] + - ansible_distribution_major_version is version('10', '<') + +# We disable AcceptEnv because it can be a security issue, but also because we +# do not want clients to push their environment variables like LANG. +- name: disable AcceptEnv in ssh config + ansible.builtin.replace: + dest: /etc/ssh/sshd_config + regexp: '^AcceptEnv' + replace: "#AcceptEnv" + notify: reload sshd + when: evolinux_ssh_disable_acceptenv | bool + +- name: Set log level to verbose (for Debian >= 9) + ansible.builtin.replace: + dest: /etc/ssh/sshd_config + regexp: '^#?LogLevel [A-Z]+' + replace: "LogLevel VERBOSE" + notify: reload sshd + when: ansible_distribution_major_version is version('9', '>=') + +- name: "Get current user" + ansible.builtin.command: + cmd: logname + changed_when: False + register: logname + check_mode: no + when: evolinux_ssh_allow_current_user | bool + +# we must double-escape caracters, because python +- name: verify AllowUsers directive + ansible.builtin.command: + cmd: "grep -E '^AllowUsers' /etc/ssh/sshd_config" + failed_when: False + changed_when: False + register: grep_allowusers_ssh + check_mode: no + when: evolinux_ssh_allow_current_user | bool + +- name: "Add AllowUsers sshd directive for current user" + ansible.builtin.lineinfile: + dest: /etc/ssh/sshd_config + line: "\nAllowUsers {{ logname.stdout }}" + insertafter: 'Subsystem' + validate: '/usr/sbin/sshd -t -f %s' + notify: reload sshd + when: evolinux_ssh_allow_current_user and grep_allowusers_ssh.rc != 0 + +- name: "Modify AllowUsers sshd directive for current user" + ansible.builtin.replace: + dest: /etc/ssh/sshd_config + regexp: '^(AllowUsers ((?!{{ logname.stdout }}).)*)$' + replace: '\1 {{ logname.stdout }}' + validate: '/usr/sbin/sshd -t -f %s' + notify: reload sshd + when: evolinux_ssh_allow_current_user and grep_allowusers_ssh.rc == 0 + +- ansible.builtin.meta: flush_handlers diff --git a/evolinux-base/tasks/ssh.yml b/evolinux-base/tasks/ssh.yml index e063d164..2e15ec83 100644 --- a/evolinux-base/tasks/ssh.yml +++ b/evolinux-base/tasks/ssh.yml @@ -1,98 +1,13 @@ --- -- debug: - msg: "Warning: empty 'evolinux_ssh_password_auth_addresses' variable, tasks will be skipped!" - when: evolinux_ssh_password_auth_addresses == [] -# From 'man sshd_config' : -# « If all of the criteria on the Match line are satisfied, the keywords -# on the following lines override those set in the global section of the config -# file, until either another Match line or the end of the file. -# If a keyword appears in multiple Match blocks that are satisfied, -# only the first instance of the keyword is applied. » -# -# We want to allow any user from a list of IP addresses to login with password, -# but users of the "{{ evolinux_internal_group }}" group can't login with password from other IP addresses +# Backward compatibility task file -- name: "Security directives for Evolinux (Debian 10 or later)" - blockinfile: - dest: /etc/ssh/sshd_config - marker: "# {mark} EVOLINUX PASSWORD RESTRICTIONS" - block: | - Match Address {{ evolinux_ssh_password_auth_addresses | join(',') }} - PasswordAuthentication yes - Match Group {{ evolinux_internal_group }} - PasswordAuthentication no - insertafter: EOF - validate: '/usr/sbin/sshd -t -f %s' - notify: reload sshd +- name: SSH configuration (Debian <12) + ansible.builtin.import_tasks: ssh.single-file.yml when: - - evolinux_ssh_password_auth_addresses != [] - - ansible_distribution_major_version is version('10', '>=') + - ansible_distribution_major_version is version('12', '<') -- name: Security directives for Evolinux (Jessie/Stretch) - blockinfile: - dest: /etc/ssh/sshd_config - marker: "# {mark} EVOLINUX PASSWORD RESTRICTIONS BY ADDRESS" - block: | - Match Address {{ evolinux_ssh_password_auth_addresses | join(',') }} - PasswordAuthentication yes - insertafter: EOF - validate: '/usr/sbin/sshd -t -f %s' - notify: reload sshd +- name: SSH configuration (Debian >=12) + ansible.builtin.import_tasks: ssh.included-files.yml when: - - evolinux_ssh_password_auth_addresses != [] - - ansible_distribution_major_version is version('10', '<') - -# We disable AcceptEnv because it can be a security issue, but also because we -# do not want clients to push their environment variables like LANG. -- name: disable AcceptEnv in ssh config - replace: - dest: /etc/ssh/sshd_config - regexp: '^AcceptEnv' - replace: "#AcceptEnv" - notify: reload sshd - when: evolinux_ssh_disable_acceptenv | bool - -- name: Set log level to verbose (for Debian >= 9) - replace: - dest: /etc/ssh/sshd_config - regexp: '^#?LogLevel [A-Z]+' - replace: "LogLevel VERBOSE" - notify: reload sshd - when: ansible_distribution_major_version is version('9', '>=') - -- name: "Get current user" - command: logname - changed_when: False - register: logname - check_mode: no - when: evolinux_ssh_allow_current_user | bool - -# we must double-escape caracters, because python -- name: verify AllowUsers directive - command: "grep -E '^AllowUsers' /etc/ssh/sshd_config" - failed_when: False - changed_when: False - register: grep_allowusers_ssh - check_mode: no - when: evolinux_ssh_allow_current_user | bool - -- name: "Add AllowUsers sshd directive for current user" - lineinfile: - dest: /etc/ssh/sshd_config - line: "\nAllowUsers {{ logname.stdout }}" - insertafter: 'Subsystem' - validate: '/usr/sbin/sshd -t -f %s' - notify: reload sshd - when: evolinux_ssh_allow_current_user and grep_allowusers_ssh.rc != 0 - -- name: "Modify AllowUsers sshd directive for current user" - replace: - dest: /etc/ssh/sshd_config - regexp: '^(AllowUsers ((?!{{ logname.stdout }}).)*)$' - replace: '\1 {{ logname.stdout }}' - validate: '/usr/sbin/sshd -t -f %s' - notify: reload sshd - when: evolinux_ssh_allow_current_user and grep_allowusers_ssh.rc == 0 - -- meta: flush_handlers + - ansible_distribution_major_version is version('12', '>=') diff --git a/evolinux-base/tasks/system.yml b/evolinux-base/tasks/system.yml index e5363fed..8f3d7b03 100644 --- a/evolinux-base/tasks/system.yml +++ b/evolinux-base/tasks/system.yml @@ -1,14 +1,14 @@ --- - name: /tmp must be world-writable - file: + ansible.builtin.file: path: /tmp state: directory mode: "u=rwx,g=rwx,o=rwxt" when: evolinux_system_chmod_tmp | bool - name: Setting default locales - lineinfile: + ansible.builtin.lineinfile: dest: /etc/locale.gen line: "{{ item }}" create: yes @@ -21,11 +21,12 @@ when: evolinux_system_locales | bool - name: Reconfigure locales - command: /usr/sbin/locale-gen + ansible.builtin.command: + cmd: /usr/sbin/locale-gen when: evolinux_system_locales and default_locales is changed - name: Setting default timezone - timezone: + community.general.timezone: name: "{{ evolinux_system_timezone | mandatory }}" notify: restart cron when: evolinux_system_set_timezone | bool @@ -33,24 +34,24 @@ # TODO : find a way to force the console-data configuration # non-interactively (like tzdata ↑) -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - name: Ensure automagic vim conf is disabled - lineinfile: + ansible.builtin.lineinfile: dest: /etc/vim/vimrc regexp: 'let g:skip_defaults_vim =' line: 'let g:skip_defaults_vim = 1' when: evolinux_system_vim_skip_defaults | bool - name: Setting vim as default editor - alternatives: + community.general.alternatives: name: editor path: /usr/bin/vim.basic when: evolinux_system_vim_default_editor | bool - name: Add "umask 027" to /etc/profile.d/evolinux.sh - lineinfile: + ansible.builtin.lineinfile: dest: /etc/profile.d/evolinux.sh line: "umask 027" create: yes @@ -58,7 +59,7 @@ when: evolinux_system_profile | bool - name: Set /etc/adduser.conf DIR_MODE to 0700 - replace: + ansible.builtin.replace: dest: /etc/adduser.conf regexp: "^DIR_MODE=0755$" replace: "DIR_MODE=0700" @@ -67,7 +68,7 @@ # TODO: trouver comment ne pas faire ça sur Xen Dom-U - name: Deactivating login on all tty except tty2 - lineinfile: + ansible.builtin.lineinfile: dest: /etc/securetty line: "tty2" create: yes @@ -75,9 +76,10 @@ when: evolinux_system_restrict_securetty | bool - name: Setting TMOUT to disconnect inactive users - lineinfile: + ansible.builtin.lineinfile: dest: /etc/profile.d/evolinux.sh - line: "export TMOUT=36000" + line: "export TMOUT={{ evolinux_system_timeout }}" + regexp: "^export TMOUT=" create: yes state: present when: evolinux_system_set_timeout | bool @@ -85,8 +87,8 @@ #- name: Customizing /etc/fstab - name: Check if cron is installed - shell: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'" - args: + ansible.builtin.shell: + cmd: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'" executable: /bin/bash check_mode: no failed_when: False @@ -94,7 +96,7 @@ register: is_cron_installed - name: Set verbose logging for cron deamon - lineinfile: + ansible.builtin.lineinfile: dest: /etc/default/cron line: "EXTRA_OPTS='-L 15'" create: yes @@ -104,7 +106,7 @@ - evolinux_system_cron_verboselog | bool - name: Modify default umask for cron deamon - lineinfile: + ansible.builtin.lineinfile: dest: /etc/default/cron line: "umask 022" create: yes @@ -114,7 +116,7 @@ - evolinux_system_cron_umask | bool - name: Randomize periodic crontabs - replace: + ansible.builtin.replace: dest: /etc/crontab regexp: "{{ item.regexp }}" replace: "{{ item.replace }}" @@ -127,23 +129,23 @@ - is_cron_installed.rc == 0 - evolinux_system_cron_random | bool -- include_role: +- ansible.builtin.include_role: name: evolix/ntpd ## alert5 - name: Install alert5 init script (jessie/stretch) - template: + ansible.builtin.template: src: system/alert5.sysvinit.j2 dest: /etc/init.d/alert5 - force: no + force: false mode: "0755" when: - evolinux_system_alert5_init | bool - ansible_distribution_release == "jessie" or ansible_distribution_release == "stretch" - name: Enable alert5 init script (jessie/stretch) - service: + ansible.builtin.service: name: alert5 enabled: yes when: @@ -154,27 +156,27 @@ - name: Install alert5 init script (buster and later) - template: + ansible.builtin.template: src: system/alert5.sh.j2 dest: /usr/share/scripts/alert5.sh - force: no + force: false mode: "0755" when: - evolinux_system_alert5_init | bool - ansible_distribution_major_version is version('10', '>=') - name: Install alert5 service (buster and later) - copy: + ansible.builtin.copy: src: alert5.service dest: /etc/systemd/system/alert5.service - force: yes + force: true mode: "0644" when: - evolinux_system_alert5_init | bool - ansible_distribution_major_version is version('10', '>=') - name: Enable alert5 init script (buster and later) - systemd: + ansible.builtin.systemd: name: alert5 daemon_reload: yes enabled: yes @@ -187,14 +189,15 @@ ## network interfaces - name: "Is there an \"allow-hotplug\" interface ?" - command: grep allow-hotplug /etc/network/interfaces + ansible.builtin.command: + cmd: grep allow-hotplug /etc/network/interfaces failed_when: False changed_when: False check_mode: no register: grep_hotplug_eni - name: "Network interfaces must be \"auto\" and not \"allow-hotplug\"" - replace: + ansible.builtin.replace: dest: /etc/network/interfaces regexp: "allow-hotplug" replace: "auto" @@ -202,6 +205,4 @@ - evolinux_system_eni_auto | bool - grep_hotplug_eni.rc == 0 -## /sbin/deny - -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers diff --git a/evolinux-base/tasks/top.yml b/evolinux-base/tasks/top.yml deleted file mode 100644 index 64fdf6b6..00000000 --- a/evolinux-base/tasks/top.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -- name: Deploy top configuration file - copy: - # The config format is unredable; ATM it only add the SWAP column - src: topdefaultrc - dest: /etc/topdefaultrc - mode: "0644" diff --git a/evolinux-base/tasks/utils.yml b/evolinux-base/tasks/utils.yml index a5ff56fa..a1c4d646 100644 --- a/evolinux-base/tasks/utils.yml +++ b/evolinux-base/tasks/utils.yml @@ -1,22 +1,42 @@ --- -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr -- name: backup-server-state script is present - copy: - src: "backup-server-state.sh" - dest: /usr/local/sbin/backup-server-state - force: True - owner: root - group: root - mode: "0750" +- ansible.builtin.include_tasks: + file: dump-server-state.yml - name: "/sbin/deny script is present" - copy: + ansible.builtin.copy: src: deny.sh dest: /sbin/deny mode: "0700" owner: root group: root - force: no \ No newline at end of file + force: false + +- name: update-evobackup-canary script is present + ansible.builtin.copy: + src: update-evobackup-canary + dest: /usr/local/bin/update-evobackup-canary + force: True + owner: root + group: root + mode: "0755" + +# TODO: delete when this has been run once on all our servers +- name: update-evobackup-canary is removed from sbin + ansible.builtin.file: + path: /usr/local/sbin/update-evobackup-canary + state: absent + +- name: Deploy htop configuration + ansible.builtin.copy: + src: htoprc + dest: /etc/htoprc + mode: "0644" + +- name: Deploy top configuration file + ansible.builtin.file: + path: /etc/topdefaultrc + state: absent diff --git a/evolinux-base/templates/hardware/hp.sources.j2 b/evolinux-base/templates/hardware/hp.sources.j2 new file mode 100644 index 00000000..04ccbc9d --- /dev/null +++ b/evolinux-base/templates/hardware/hp.sources.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +Types: deb +URIs: https://downloads.linux.hpe.com/SDR/repo/mcp +Suites: {{ ansible_distribution_release }}/current +Components: non-free +Signed-by: {{ apt_keyring_dir }}/hpePublicKey2048_key1.asc +Enabled: yes \ No newline at end of file diff --git a/evolinux-base/templates/hardware/hwraid.le-vert.net.sources.j2 b/evolinux-base/templates/hardware/hwraid.le-vert.net.sources.j2 new file mode 100644 index 00000000..9d424a5b --- /dev/null +++ b/evolinux-base/templates/hardware/hwraid.le-vert.net.sources.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +Types: deb +URIs: http://hwraid.le-vert.net/debian +Suites: {{ ansible_distribution_release }} +Components: main +Signed-by: {{ apt_keyring_dir }}/hwraid.le-vert.net.asc] +Enabled: yes diff --git a/evolinux-todo/tasks/cat.yml b/evolinux-todo/tasks/cat.yml index 58e3ba4c..e1d4faf8 100644 --- a/evolinux-todo/tasks/cat.yml +++ b/evolinux-todo/tasks/cat.yml @@ -1,13 +1,14 @@ --- - name: cat /etc/evolinux/todo.txt - command: "cat /etc/evolinux/todo.txt" + ansible.builtin.command: + cmd: "cat /etc/evolinux/todo.txt" register: evolinux_todo changed_when: False failed_when: False check_mode: no - name: "Content of /etc/evolinux/todo.txt" - debug: + ansible.builtin.debug: var: evolinux_todo.stdout_lines when: evolinux_todo.stdout | length > 0 diff --git a/evolinux-todo/tasks/main.yml b/evolinux-todo/tasks/main.yml index bd098c72..9b0d9c80 100644 --- a/evolinux-todo/tasks/main.yml +++ b/evolinux-todo/tasks/main.yml @@ -1,14 +1,16 @@ --- - name: /etc/evolinux is present - file: + ansible.builtin.file: dest: /etc/evolinux mode: "0700" state: directory + when: ansible_distribution == "Debian" - name: /etc/evolinux/todo.txt is present - copy: + ansible.builtin.copy: src: todo.defaults.txt dest: /etc/evolinux/todo.txt mode: "0640" - force: no + force: false + when: ansible_distribution == "Debian" diff --git a/evolinux-users/README.md b/evolinux-users/README.md index c0f6e9ef..9c7beab4 100644 --- a/evolinux-users/README.md +++ b/evolinux-users/README.md @@ -19,6 +19,7 @@ evolinux_users: groups: "baz" password_hash: 'sdfgsdfgsdfgsdfg' ssh_key: 'ssh-rsa AZERTYXYZ' + create: always bar: name: bar uid: 1002 @@ -30,6 +31,7 @@ evolinux_users: ssh_keys: - 'ssh-rsa QWERTYUIOP' - 'ssh-ed25519 QWERTYUIOP' + create: on_demand ``` * `evolinux_sudo_group`: which group to use for sudo (default: `evolinux-sudo`) diff --git a/evolinux-users/defaults/main.yml b/evolinux-users/defaults/main.yml index 8ff94551..658e4a31 100644 --- a/evolinux-users/defaults/main.yml +++ b/evolinux-users/defaults/main.yml @@ -6,3 +6,6 @@ evolinux_ssh_group: "evolinux-ssh" evolinux_internal_group: "" evolinux_root_disable_ssh: True + +# Defines which groups of users are created +evolinux_users_create: always \ No newline at end of file diff --git a/evolinux-users/handlers/main.yml b/evolinux-users/handlers/main.yml index a94909a5..039ab7c2 100644 --- a/evolinux-users/handlers/main.yml +++ b/evolinux-users/handlers/main.yml @@ -1,9 +1,10 @@ --- - name: reload sshd - service: + ansible.builtin.service: name: sshd state: reloaded - name: newaliases - command: newaliases + ansible.builtin.command: + cmd: newaliases changed_when: False diff --git a/evolinux-users/tasks/main.yml b/evolinux-users/tasks/main.yml index 1b838e01..f0fd703a 100644 --- a/evolinux-users/tasks/main.yml +++ b/evolinux-users/tasks/main.yml @@ -1,26 +1,28 @@ --- - name: "System compatibility checks" - assert: + ansible.builtin.assert: that: - ansible_distribution == "Debian" - ansible_distribution_major_version is version('8', '>=') msg: only compatible with Debian >= 8 -- debug: +- ansible.builtin.debug: msg: "Warning: empty 'evolinux_users' variable, tasks will be skipped!" when: evolinux_users | length == 0 - name: Create user accounts - include: user.yml + ansible.builtin.include: user.yml vars: user: "{{ item.value }}" loop: "{{ evolinux_users | dict2items }}" - when: evolinux_users | length > 0 + when: + - user.create == evolinux_users_create + - evolinux_users | length > 0 - name: Configure sudo - include: sudo.yml + ansible.builtin.include: sudo.yml - name: Configure SSH - include: ssh.yml + ansible.builtin.include: ssh.yml when: evolinux_users | length > 0 diff --git a/evolinux-users/tasks/ssh.yml b/evolinux-users/tasks/ssh.yml index ac2fdf12..9110911f 100644 --- a/evolinux-users/tasks/ssh.yml +++ b/evolinux-users/tasks/ssh.yml @@ -1,64 +1,67 @@ --- - name: verify AllowGroups directive - command: "grep -E '^AllowGroups' /etc/ssh/sshd_config" + ansible.builtin.command: + cmd: "grep -E '^AllowGroups' /etc/ssh/sshd_config" changed_when: False failed_when: False check_mode: no register: grep_allowgroups_ssh -- debug: +- ansible.builtin.debug: var: grep_allowgroups_ssh verbosity: 1 - name: verify AllowUsers directive - command: "grep -E '^AllowUsers' /etc/ssh/sshd_config" + ansible.builtin.command: + cmd: "grep -E '^AllowUsers' /etc/ssh/sshd_config" changed_when: False failed_when: False check_mode: no register: grep_allowusers_ssh -- debug: +- ansible.builtin.debug: var: grep_allowusers_ssh verbosity: 1 -- assert: +- ansible.builtin.assert: that: "not (grep_allowusers_ssh.rc == 0 and grep_allowgroups_ssh.rc == 0)" msg: "We can't deal with AllowUsers and AllowGroups at the same time" -- set_fact: +- ansible.builtin.set_fact: # If "AllowGroups is present" or "AllowUsers is absent and Debian 10+", ssh_allowgroups: "{{ (grep_allowgroups_ssh.rc == 0) or (grep_allowusers_ssh.rc != 0 and (ansible_distribution_major_version is version('10', '>='))) }}" # If "AllowGroups is absent" and "AllowUsers is absent or Debian <10" ssh_allowusers: "{{ (grep_allowusers_ssh.rc == 0) or (grep_allowgroups_ssh.rc != 0 and (ansible_distribution_major_version is version('10', '<'))) }}" -- debug: +- ansible.builtin.debug: var: ssh_allowgroups verbosity: 1 -- debug: +- ansible.builtin.debug: var: ssh_allowusers verbosity: 1 -- include: ssh_allowgroups.yml +- ansible.builtin.include: ssh_allowgroups.yml when: - ssh_allowgroups - not ssh_allowusers -- include: ssh_allowusers.yml +- ansible.builtin.include: ssh_allowusers.yml vars: user: "{{ item.value }}" loop: "{{ evolinux_users | dict2items }}" when: + - user.create == evolinux_users_create - ssh_allowusers - not ssh_allowgroups - name: disable root login - replace: + ansible.builtin.replace: dest: /etc/ssh/sshd_config - regexp: '^PermitRootLogin (yes|without-password|prohibit-password)' + regexp: '^#PermitRootLogin (yes|without-password|prohibit-password)' replace: "PermitRootLogin no" notify: reload sshd when: evolinux_root_disable_ssh | bool -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers diff --git a/evolinux-users/tasks/ssh_allowgroups.yml b/evolinux-users/tasks/ssh_allowgroups.yml index a4e4ee54..2dac1f80 100644 --- a/evolinux-users/tasks/ssh_allowgroups.yml +++ b/evolinux-users/tasks/ssh_allowgroups.yml @@ -3,14 +3,15 @@ # this check must be repeated for each user # even if it's been done before - name: verify AllowGroups directive - command: "grep -E '^AllowGroups' /etc/ssh/sshd_config" + ansible.builtin.command: + cmd: "grep -E '^AllowGroups' /etc/ssh/sshd_config" changed_when: False failed_when: False check_mode: no register: grep_allowgroups_ssh - name: "Add AllowGroups sshd directive with '{{ evolinux_ssh_group }}'" - lineinfile: + ansible.builtin.lineinfile: dest: /etc/ssh/sshd_config line: "\nAllowGroups {{ evolinux_ssh_group }}" insertafter: 'Subsystem' @@ -19,7 +20,7 @@ when: grep_allowgroups_ssh.rc != 0 - name: "Append '{{ evolinux_ssh_group }}' to AllowGroups sshd directive" - replace: + ansible.builtin.replace: dest: /etc/ssh/sshd_config regexp: '^(AllowGroups ((?!\b{{ evolinux_ssh_group }}\b).)*)$' replace: '\1 {{ evolinux_ssh_group }}' diff --git a/evolinux-users/tasks/ssh_allowusers.yml b/evolinux-users/tasks/ssh_allowusers.yml index 1aa31f3c..00827a46 100644 --- a/evolinux-users/tasks/ssh_allowusers.yml +++ b/evolinux-users/tasks/ssh_allowusers.yml @@ -3,14 +3,15 @@ # this check must be repeated for each user # even if it's been done before - name: verify AllowUsers directive - command: "grep -E '^AllowUsers' /etc/ssh/sshd_config" + ansible.builtin.command: + cmd: "grep -E '^AllowUsers' /etc/ssh/sshd_config" changed_when: False failed_when: False check_mode: no register: grep_allowusers_ssh - name: "Add AllowUsers sshd directive with '{{ user.name }}'" - lineinfile: + ansible.builtin.lineinfile: dest: /etc/ssh/sshd_config line: "\nAllowUsers {{ user.name }}" insertafter: 'Subsystem' @@ -19,7 +20,7 @@ when: grep_allowusers_ssh.rc != 0 - name: "Append '{{ user.name }}' to AllowUsers sshd directive" - replace: + ansible.builtin.replace: dest: /etc/ssh/sshd_config regexp: '^(AllowUsers ((?!\b{{ user.name }}\b).)*)$' replace: '\1 {{ user.name }}' @@ -28,14 +29,15 @@ when: grep_allowusers_ssh.rc == 0 - name: "verify Match User directive" - command: "grep -E '^Match User' /etc/ssh/sshd_config" + ansible.builtin.command: + cmd: "grep -E '^Match User' /etc/ssh/sshd_config" changed_when: False failed_when: False check_mode: no register: grep_matchuser_ssh - name: "Add Match User sshd directive with '{{ user.name }}'" - lineinfile: + ansible.builtin.lineinfile: dest: /etc/ssh/sshd_config line: "\nMatch User {{ user.name }}\n PasswordAuthentication no" insertafter: "# END EVOLINUX PASSWORD RESTRICTIONS BY ADDRESS" @@ -44,7 +46,7 @@ when: grep_matchuser_ssh.rc != 0 - name: "Append '{{ user.name }}' to Match User's sshd directive" - replace: + ansible.builtin.replace: dest: /etc/ssh/sshd_config regexp: '^(Match User ((?!{{ user.name }}).)*)$' replace: '\1,{{ user.name }}' diff --git a/evolinux-users/tasks/sudo.yml b/evolinux-users/tasks/sudo.yml index 4056e7ad..b3089aab 100644 --- a/evolinux-users/tasks/sudo.yml +++ b/evolinux-users/tasks/sudo.yml @@ -1,23 +1,27 @@ --- -- include: sudo_jessie.yml +- ansible.builtin.include: sudo_jessie.yml vars: user: "{{ item.value }}" loop: "{{ evolinux_users | dict2items }}" when: - evolinux_users | length > 0 + - user.create == evolinux_users_create - ansible_distribution_release == "jessie" - block: - - include: sudo_stretch_common.yml + - ansible.builtin.include: sudo_common.yml - - include: sudo_stretch_user.yml + - ansible.builtin.include: sudo_user.yml vars: user: "{{ item.value }}" loop: "{{ evolinux_users | dict2items }}" + when: + - evolinux_users | length > 0 + - user.create == evolinux_users_create when: - ansible_distribution_major_version is defined - ansible_distribution_major_version is version('9', '>=') -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers diff --git a/evolinux-users/tasks/sudo_stretch_common.yml b/evolinux-users/tasks/sudo_common.yml similarity index 80% rename from evolinux-users/tasks/sudo_stretch_common.yml rename to evolinux-users/tasks/sudo_common.yml index fb8f9ac7..0f463756 100644 --- a/evolinux-users/tasks/sudo_stretch_common.yml +++ b/evolinux-users/tasks/sudo_common.yml @@ -1,7 +1,7 @@ --- - name: "/etc/sudoers.d presence and permissions" - file: + ansible.builtin.file: path: /etc/sudoers.d owner: root group: root @@ -9,15 +9,16 @@ state: directory - name: "Verify 'evolinux' sudoers file presence (Debian 9 or later)" - template: - src: sudoers_stretch.j2 + ansible.builtin.template: + src: sudoers.j2 dest: /etc/sudoers.d/evolinux - force: no + force: false mode: "0440" validate: '/usr/sbin/visudo -cf %s' register: copy_sudoers_evolinux - name: "Create '{{ evolinux_sudo_group }}' group (Debian 9 or later)" - group: + + ansible.builtin.group: name: "{{ evolinux_sudo_group }}" system: yes diff --git a/evolinux-users/tasks/sudo_jessie.yml b/evolinux-users/tasks/sudo_jessie.yml index d3f70198..2cc50500 100644 --- a/evolinux-users/tasks/sudo_jessie.yml +++ b/evolinux-users/tasks/sudo_jessie.yml @@ -1,16 +1,16 @@ --- - name: "Verify Evolinux sudoers file presence (jessie)" - template: + ansible.builtin.template: src: sudoers_jessie.j2 dest: /etc/sudoers.d/evolinux - force: no + force: false mode: "0440" validate: '/usr/sbin/visudo -cf %s' register: copy_sudoers_evolinux - name: "Add user in sudoers file for '{{ user.name }}' (jessie)" - replace: + ansible.builtin.replace: dest: /etc/sudoers.d/evolinux regexp: '^(User_Alias\s+ADMINS\s+=((?!{{ user.name }}).)*)$' replace: '\1,{{ user.name }}' diff --git a/evolinux-users/tasks/sudo_stretch_user.yml b/evolinux-users/tasks/sudo_user.yml similarity index 85% rename from evolinux-users/tasks/sudo_stretch_user.yml rename to evolinux-users/tasks/sudo_user.yml index 97f1f77d..40830535 100644 --- a/evolinux-users/tasks/sudo_stretch_user.yml +++ b/evolinux-users/tasks/sudo_user.yml @@ -1,13 +1,13 @@ --- - name: "Add user to '{{ evolinux_sudo_group }}' group (Debian 9 or later)" - user: + ansible.builtin.user: name: '{{ user.name }}' groups: "{{ evolinux_sudo_group }}" append: yes - name: "Add user to 'adm' group (Debian 9 or later)" - user: + ansible.builtin.user: name: '{{ user.name }}' groups: "adm" append: yes diff --git a/evolinux-users/tasks/user.yml b/evolinux-users/tasks/user.yml index 0f8bd480..5bba2e0e 100644 --- a/evolinux-users/tasks/user.yml +++ b/evolinux-users/tasks/user.yml @@ -2,23 +2,25 @@ # Unix account -- fail: +- ansible.builtin.fail: msg: "You must provide a value for the 'user.name ' variable." when: (user.name is not defined) or (user.name | length == 0) -- fail: +- ansible.builtin.fail: msg: "You must provide a value for the 'user.uid ' variable." when: (user.uid is not defined) or (user.uid | string | length == 0) - name: "Test if '{{ user.name }}' exists" - command: 'id -u "{{ user.name }}"' + ansible.builtin.command: + cmd: 'id -u "{{ user.name }}"' register: get_id_from_login failed_when: False changed_when: False check_mode: no - name: "Test if uid '{{ user.uid }}' exists" - command: 'id -un -- "{{ user.uid }}"' + ansible.builtin.command: + cmd: 'id -un -- "{{ user.uid }}"' register: get_login_from_id failed_when: False changed_when: False @@ -28,7 +30,7 @@ # the uid already exists # and the user associated with this uid is not the desired user - name: "Fail if uid already exists for another user" - fail: + ansible.builtin.fail: msg: "Uid '{{ user.uid }}' is already used by '{{ get_login_from_id.stdout }}'. You must change uid for '{{ user.name }}'" when: - get_login_from_id.rc == 0 @@ -38,7 +40,7 @@ # the user doesn't already exist and the uid isn't already used # or the user exists with the defined uid - name: "Unix account for '{{ user.name }}' is present (with uid '{{ user.uid }}')" - user: + ansible.builtin.user: state: present uid: '{{ user.uid }}' name: '{{ user.name }}' @@ -53,7 +55,7 @@ # the user doesn't already exist but the defined uid is already used # or another user already exists with a the same uid - name: "Unix account for '{{ user.name }}' is present (with random uid)" - user: + ansible.builtin.user: state: present name: '{{ user.name }}' comment: '{{ user.fullname }}' @@ -64,12 +66,12 @@ - (get_id_from_login.rc != 0 and get_login_from_id.rc == 0) or (get_id_from_login.rc == 0 and get_login_from_id.stdout != user.name) - name: Is /etc/aliases present? - stat: + ansible.builtin.stat: path: /etc/aliases register: etc_aliases - name: Set mail alias - lineinfile: + ansible.builtin.lineinfile: state: present dest: /etc/aliases line: '{{ user.name }}: root' @@ -82,13 +84,14 @@ ## Group for SSH authorizations - name: "Unix group '{{ evolinux_ssh_group }}' is present (Debian 10 or later)" - group: + + ansible.builtin.group: name: "{{ evolinux_ssh_group }}" state: present when: ansible_distribution_major_version is version('10', '>=') - name: "Unix user '{{ user.name }}' belongs to group '{{ evolinux_ssh_group }}' (Debian 10 or later)" - user: + ansible.builtin.user: name: '{{ user.name }}' groups: "{{ evolinux_ssh_group }}" append: yes @@ -97,7 +100,8 @@ ## Optional group for all evolinux users - name: "Unix group '{{ evolinux_internal_group }}' is present (Debian 9 or later)" - group: + + ansible.builtin.group: name: "{{ evolinux_internal_group }}" state: present when: @@ -106,7 +110,7 @@ - ansible_distribution_major_version is version('9', '>=') - name: "Unix user '{{ user.name }}' belongs to group '{{ evolinux_internal_group }}' (Debian 9 or later)" - user: + ansible.builtin.user: name: '{{ user.name }}' groups: "{{ evolinux_internal_group }}" append: yes @@ -118,7 +122,8 @@ ## Optional secondary groups, defined per user - name: "Secondary Unix groups are present" - group: + + ansible.builtin.group: name: "{{ group }}" loop: "{{ user.groups }}" loop_control: @@ -128,7 +133,7 @@ - user.groups | length > 0 - name: "Unix user '{{ user.name }}' belongs to secondary groups" - user: + ansible.builtin.user: name: '{{ user.name }}' groups: "{{ user.groups | join(',') }}" append: yes @@ -139,7 +144,7 @@ # Permissions on home directory - name: "Home directory for '{{ user.name }}' is not accessible by group and other users" - file: + ansible.builtin.file: name: '/home/{{ user.name }}' mode: "0700" state: directory @@ -147,7 +152,8 @@ # Evomaintenance - name: Search profile for presence of evomaintenance - command: 'grep -q "trap.*sudo.*evomaintenance.sh" /home/{{ user.name }}/.profile' + ansible.builtin.command: + cmd: 'grep -q "trap.*sudo.*evomaintenance.sh" /home/{{ user.name }}/.profile' changed_when: False failed_when: False check_mode: no @@ -155,7 +161,7 @@ ## Don't add the trap if it is present or commented - name: "User '{{ user.name }}' has its shell trap for evomaintenance" - lineinfile: + ansible.builtin.lineinfile: state: present dest: '/home/{{ user.name }}/.profile' insertafter: EOF @@ -165,7 +171,7 @@ # SSH keys - name: "SSH directory for '{{ user.name }}' is present" - file: + ansible.builtin.file: dest: '/home/{{ user.name }}/.ssh/' state: directory mode: "0700" @@ -173,7 +179,7 @@ group: '{{ user.name }}' - name: "SSH public key for '{{ user.name }}' is present" - authorized_key: + ansible.posix.authorized_key: user: "{{ user.name }}" key: "{{ user.ssh_key }}" state: present @@ -182,7 +188,7 @@ - user.ssh_key | length > 0 - name: "SSH public keys for '{{ user.name }}' are present" - authorized_key: + ansible.posix.authorized_key: user: "{{ user.name }}" key: "{{ ssk_key }}" state: present @@ -193,4 +199,4 @@ - user.ssh_keys is defined - user.ssh_keys | length > 0 -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers diff --git a/evolinux-users/templates/sudoers_stretch.j2 b/evolinux-users/templates/sudoers.j2 similarity index 83% rename from evolinux-users/templates/sudoers_stretch.j2 rename to evolinux-users/templates/sudoers.j2 index 4a522e1b..29a22da7 100644 --- a/evolinux-users/templates/sudoers_stretch.j2 +++ b/evolinux-users/templates/sudoers.j2 @@ -1,6 +1,6 @@ Defaults umask=0077 -Cmnd_Alias MAINT = /usr/share/scripts/evomaintenance.sh, /usr/share/scripts/listupgrade.sh, /usr/bin/apt, /bin/mount +Cmnd_Alias MAINT = /usr/share/scripts/evomaintenance.sh, /usr/share/scripts/listupgrade.sh nagios ALL = NOPASSWD: /usr/lib/nagios/plugins/check_procs nagios ALL = NOPASSWD: /usr/local/lib/nagios/plugins/check_minifirewall @@ -12,7 +12,9 @@ nagios ALL = NOPASSWD: /usr/local/lib/nagios/plugins/check_phpfpm_multi nagios ALL = NOPASSWD: /usr/local/lib/nagios/plugins/check_phpfpm_multi /var/lib/lxc/php70/rootfs/etc/php/7.0/fpm/pool.d/ nagios ALL = NOPASSWD: /usr/local/lib/nagios/plugins/check_phpfpm_multi /var/lib/lxc/php73/rootfs/etc/php/7.3/fpm/pool.d/ nagios ALL = NOPASSWD: /usr/local/lib/nagios/plugins/check_phpfpm_multi /var/lib/lxc/php74/rootfs/etc/php/7.4/fpm/pool.d/ -nagios ALL = NOPASSWD: /usr/local/lib/nagios/plugins/check_phpfpm_multi /var/lib/lxc/php74/rootfs/etc/php/8.0/fpm/pool.d/ +nagios ALL = NOPASSWD: /usr/local/lib/nagios/plugins/check_phpfpm_multi /var/lib/lxc/php80/rootfs/etc/php/8.0/fpm/pool.d/ +nagios ALL = NOPASSWD: /usr/local/lib/nagios/plugins/check_phpfpm_multi /var/lib/lxc/php81/rootfs/etc/php/8.1/fpm/pool.d/ +nagios ALL = NOPASSWD: /usr/local/lib/nagios/plugins/check_phpfpm_multi /var/lib/lxc/php82/rootfs/etc/php/8.2/fpm/pool.d/ nagios ALL = NOPASSWD: /usr/sbin/megaclisas-status --nagios nagios ALL = NOPASSWD: /usr/lib/nagios/plugins/check_ipmi_sensor nagios ALL = NOPASSWD: /sbin/dmsetup status --noflush diff --git a/evolinux-users/templates/sudoers_jessie.j2 b/evolinux-users/templates/sudoers_jessie.j2 index c0703c49..6bc3e57b 100644 --- a/evolinux-users/templates/sudoers_jessie.j2 +++ b/evolinux-users/templates/sudoers_jessie.j2 @@ -1,6 +1,6 @@ Defaults umask=0077 -Cmnd_Alias MAINT = /usr/share/scripts/evomaintenance.sh, /usr/share/scripts/listupgrade.sh, /usr/bin/apt, /bin/mount +Cmnd_Alias MAINT = /usr/share/scripts/evomaintenance.sh, /usr/share/scripts/listupgrade.sh User_Alias ADMINS = {{ user.name }} nagios ALL = NOPASSWD: /usr/lib/nagios/plugins/check_procs diff --git a/evomaintenance/files/evomaintenance.sh b/evomaintenance/files/evomaintenance.sh index 3903f2ef..bce0e562 100644 --- a/evomaintenance/files/evomaintenance.sh +++ b/evomaintenance/files/evomaintenance.sh @@ -7,7 +7,7 @@ # Copyright 2007-2022 Evolix , Gregory Colpart , # Jérémy Lecour and others. -VERSION="22.01" +VERSION="22.07" show_version() { cat <\S*),.* -ignoreregex = +[Definition] +failregex = (?: pop3-login|imap-login): .*(?:Authentication failure|Internal login failure|Aborted login \(auth failed|Aborted login \(tried to use disabled|Disconnected \(auth failed|Aborted login \(\d+ authentication attempts).*rip=,.* +ignoreregex = diff --git a/fail2ban/handlers/main.yml b/fail2ban/handlers/main.yml index 85f32698..49db2f25 100644 --- a/fail2ban/handlers/main.yml +++ b/fail2ban/handlers/main.yml @@ -1,10 +1,10 @@ --- - name: restart fail2ban - service: + ansible.builtin.service: name: fail2ban state: restarted - name: restart munin-node - service: + ansible.builtin.service: name: munin-node state: restarted diff --git a/fail2ban/tasks/fix-dbpurgeage.yml b/fail2ban/tasks/fix-dbpurgeage.yml new file mode 100644 index 00000000..c24335cd --- /dev/null +++ b/fail2ban/tasks/fix-dbpurgeage.yml @@ -0,0 +1,31 @@ +--- +- name: Sqlite needed + ansible.builtin.apt: + name: + - sqlite3 + state: present + +- name: Register bantime from default config from package + ansible.builtin.shell: + cmd: "grep -R -E 'dbpurgeage[[:blank:]]*=[[:blank:]]*[0-9]+' /etc/fail2ban/fail2ban.conf |awk '{print $3}'|head -n1" + register: dbpurgeage + changed_when: False + check_mode: false + +- name: + ansible.builtin.set_fact: + fail2ban_dbpurgeage_default : "{{ dbpurgeage.stdout }}" + when: dbpurgeage.stdout | regex_search("^\\d+\w+$") + +- name: + ansible.builtin.set_fact: + fail2ban_dbpurgeage_default : "{{ dbpurgeage.stdout }} second" + when: dbpurgeage.stdout | regex_search("^\\d+$") + +- name: Add crontab + ansible.builtin.template: + src: fail2ban_dbpurge.j2 + dest: /etc/cron.daily/fail2ban_dbpurge + mode: 0700 + owner: root + group: root diff --git a/fail2ban/tasks/ip_whitelist.yml b/fail2ban/tasks/ip_whitelist.yml index f899e618..02cdb3c9 100644 --- a/fail2ban/tasks/ip_whitelist.yml +++ b/fail2ban/tasks/ip_whitelist.yml @@ -1,10 +1,10 @@ --- -- set_fact: +- ansible.builtin.set_fact: fail2ban_ignore_ips: "{{ ['127.0.0.1/8'] | union(fail2ban_default_ignore_ips) | union(fail2ban_additional_ignore_ips) | unique }}" - name: Update ignoreips lists - ini_file: + community.general.ini_file: dest: /etc/fail2ban/jail.local section: "DEFAULT" option: "ignoreip" diff --git a/fail2ban/tasks/main.yml b/fail2ban/tasks/main.yml index 30c795c9..1629a02a 100644 --- a/fail2ban/tasks/main.yml +++ b/fail2ban/tasks/main.yml @@ -3,7 +3,7 @@ # or we risk being jailed by fail2ban - name: Prepare fail2ban hierarchy - file: + ansible.builtin.file: path: "{{ item }}" state: directory owner: root @@ -12,43 +12,33 @@ loop: - "/etc/fail2ban" - "/etc/fail2ban/filter.d" + - "/etc/fail2ban/fail2ban.d" tags: - fail2ban -- set_fact: +- ansible.builtin.set_fact: fail2ban_ignore_ips: "{{ ['127.0.0.1/8'] | union(fail2ban_default_ignore_ips) | union(fail2ban_additional_ignore_ips) | unique }}" tags: - fail2ban - name: local jail is installed - template: + ansible.builtin.template: src: jail.local.j2 dest: /etc/fail2ban/jail.local mode: "0644" - force: no + force: "{{ fail2ban_override_jaillocal }}" notify: restart fail2ban tags: - fail2ban - name: Include ignoredips update task - include: ip_whitelist.yml + ansible.builtin.include: ip_whitelist.yml when: fail2ban_force_update_ignore_ips | bool tags: - fail2ban -- name: Disable SSH filter - ini_file: - dest: /etc/fail2ban/jail.local - section: sshd - option: enabled - value: false - notify: restart fail2ban - when: fail2ban_disable_ssh | bool - tags: - - fail2ban - - name: custom filters are installed - copy: + ansible.builtin.copy: src: "{{ item }}" dest: /etc/fail2ban/filter.d/ mode: "0644" @@ -62,8 +52,8 @@ tags: - fail2ban -- name: package is installed - apt: +- name: package fail2ban is installed + ansible.builtin.apt: name: fail2ban state: present tags: @@ -71,7 +61,7 @@ - packages - name: is Munin present ? - stat: + ansible.builtin.stat: path: /etc/munin/plugins check_mode: no register: etc_munin_plugins @@ -80,7 +70,7 @@ - munin - name: is fail2ban Munin plugin available ? - stat: + ansible.builtin.stat: path: /usr/share/munin/plugins/fail2ban check_mode: no register: fail2ban_munin_plugin @@ -89,7 +79,7 @@ - munin - name: Enable Munin plugins - file: + ansible.builtin.file: src: "/usr/share/munin/plugins/fail2ban" dest: "/etc/munin/plugins/fail2ban" state: link @@ -100,3 +90,24 @@ tags: - fail2ban - munin + +- name: "Extend dbpurgeage if recidive jail is enabled" + ansible.builtin.blockinfile: + dest: /etc/fail2ban/fail2ban.d/recidive_dbpurgeage + marker: "# ANSIBLE MANAGED" + block: | + [DEFAULT] + dbpurgeage = {{ fail2ban_recidive_bantime }} + insertafter: EOF + create: yes + mode: "0644" + notify: restart fail2ban + when: + - fail2ban_recidive + +- name: Fix dbpurgeage for stretch and buster + ansible.builtin.include: fix-dbpurgeage.yml + when: + - ansible_distribution_release == "stretch" or ansible_distribution_release == "buster" + tags: + - fail2ban diff --git a/fail2ban/templates/fail2ban_dbpurge.j2 b/fail2ban/templates/fail2ban_dbpurge.j2 new file mode 100644 index 00000000..44c20f4c --- /dev/null +++ b/fail2ban/templates/fail2ban_dbpurge.j2 @@ -0,0 +1,13 @@ +#!/bin/sh +# Juin - Decembre 2022 : #64088 +# Purge pour Stretch et Buster + +/usr/bin/ionice -c3 /usr/bin/sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 "DELETE FROM bans WHERE datetime('now', '-{{ fail2ban_dbpurgeage_default }}') > datetime(timeofban, 'unixepoch');" + +place_dispo=$( df -h /var/lib/fail2ban/fail2ban.sqlite3 --output="avail" -h --block-size=1 |tail -n1 ) +place_pris=$( echo $(("$(stat --format %s /var/lib/fail2ban/fail2ban.sqlite3 ) * 2" )) ) + +if [ $place_pris -lt $place_dispo ] +then + /usr/bin/ionice -c3 /usr/bin/sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 "VACUUM;" +fi diff --git a/fail2ban/templates/jail.local.j2 b/fail2ban/templates/jail.local.j2 index 7e097e4f..6713c92e 100644 --- a/fail2ban/templates/jail.local.j2 +++ b/fail2ban/templates/jail.local.j2 @@ -1,61 +1,65 @@ # EvoLinux Fail2Ban config. +{% if fail2ban_override_jaillocal %} +# WARNING : THIS FILE IS (PROBABLY) ANSIBLE MANAGED AS IT WAS OVERWRITTEN BY ANSIBLE +{% endif %} + [DEFAULT] # "ignoreip" can be an IP address, a CIDR mask or a DNS host ignoreip = {{ ['127.0.0.1/8'] | union(fail2ban_ignore_ips) | unique | join(' ') }} -bantime = 600 -maxretry = 3 - -# "backend" specifies the backend used to get files modification. Available -# options are "gamin", "polling" and "auto". -# yoh: For some reason Debian shipped python-gamin didn't work as expected -# This issue left ToDo, so polling is default backend for now -backend = auto +bantime = {{ fail2ban_default_bantime }} +maxretry = {{ fail2ban_default_maxretry }} destemail = {{ fail2ban_alert_email or general_alert_email | mandatory }} # ACTIONS - banaction = iptables-multiport -mta = sendmail -protocol = tcp -chain = INPUT -action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] - %(mta)s-whois-lines[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"] +action = %({{ fail2ban_default_action }})s -action = %(action_mwl)s [sshd] +enabled = {{ fail2ban_sshd }} port = ssh,2222,22222 -logpath = %(sshd_log)s -backend = %(sshd_backend)s -maxretry = 10 -{% if fail2ban_wordpress %} +maxretry = {{ fail2ban_sshd_maxretry }} +findtime = {{ fail2ban_sshd_findtime }} +bantime = {{ fail2ban_sshd_bantime }} + +[recidive] +enabled = {{ fail2ban_recidive }} + +maxretry = {{ fail2ban_recidive_maxretry }} +findtime = {{ fail2ban_recidive_findtime }} +bantime = {{ fail2ban_recidive_bantime }} + + +# Evolix custom jails + [wordpress-hard] -enabled = true -port = http,https +enabled = {{ fail2ban_wordpress_hard }} +port = http, https filter = wordpress-hard logpath = /var/log/auth.log -maxretry = 1 -findtime = 300 +maxretry = {{ fail2ban_wordpress_hard_maxretry }} +findtime = {{ fail2ban_wordpress_hard_findtime }} +bantime = {{ fail2ban_wordpress_hard_bantime }} [wordpress-soft] -enabled = true -port = http,https +enabled = {{ fail2ban_wordpress_soft }} +port = http, https filter = wordpress-soft logpath = /var/log/auth.log -maxretry = 5 -findtime = 300 -{% endif %} +maxretry = {{ fail2ban_wordpress_soft_maxretry }} +findtime = {{ fail2ban_wordpress_soft_findtime }} +bantime = {{ fail2ban_wordpress_soft_bantime }} -{% if fail2ban_roundcube %} [roundcube] -enabled = true -port = http,https +enabled = {{ fail2ban_roundcube }} +port = http, https filter = roundcube logpath = /var/lib/roundcube/logs/errors -maxretry = 5 -{% endif %} +maxretry = {{ fail2ban_roundcube_maxretry }} +findtime = {{ fail2ban_roundcube_findtime }} +bantime = {{ fail2ban_roundcube_bantime }} diff --git a/fail2ban/tests/test.yml b/fail2ban/tests/test.yml index 67c6e10c..687b1a35 100644 --- a/fail2ban/tests/test.yml +++ b/fail2ban/tests/test.yml @@ -1,4 +1,6 @@ --- -- hosts: test-kitchen +- hosts: all + become: true +# gather_facts: no roles: - role: fail2ban diff --git a/filebeat/defaults/main.yml b/filebeat/defaults/main.yml index deed1508..6538aab5 100644 --- a/filebeat/defaults/main.yml +++ b/filebeat/defaults/main.yml @@ -22,3 +22,5 @@ filebeat_use_config_template: False filebeat_update_config: True filebeat_force_config: True filebeat_upgrade_package: False + +apt_keyring_dir: "{{ ansible_distribution_major_version is version('12', '<') | ternary('/etc/apt/trusted.gpg.d', '/etc/apt/keyrings') }}" \ No newline at end of file diff --git a/filebeat/handlers/main.yml b/filebeat/handlers/main.yml index 3ad08a63..8456ee33 100644 --- a/filebeat/handlers/main.yml +++ b/filebeat/handlers/main.yml @@ -1,7 +1,7 @@ --- - name: restart filebeat - systemd: + ansible.builtin.systemd: name: filebeat state: restarted when: not ansible_check_mode diff --git a/filebeat/tasks/apt_sources.yml b/filebeat/tasks/apt_sources.yml new file mode 100644 index 00000000..e525ba4b --- /dev/null +++ b/filebeat/tasks/apt_sources.yml @@ -0,0 +1,36 @@ +--- +- name: APT https transport is enabled (Debian <10) + ansible.builtin.apt: + name: apt-transport-https + state: present + when: ansible_distribution_major_version is version('10', '<') + +- name: Elastic GPG key is installed + ansible.builtin.copy: + src: elastic.asc + dest: "{{ apt_keyring_dir }}/elastic.asc" + force: true + mode: "0644" + owner: root + group: root + +- name: Add Elastic repository (Debian <12) + ansible.builtin.apt_repository: + repo: "deb [signed-by={{ apt_keyring_dir }}/elastic.asc] https://artifacts.elastic.co/packages/{{ elastic_stack_version | mandatory }}/apt stable main" + filename: elastic + state: present + update_cache: yes + when: ansible_distribution_major_version is version('12', '<') + +- name: Add Elastic repository (Debian >=12) + ansible.builtin.template: + src: elastic.sources.j2 + dest: /etc/apt/sources.list.d/elastic.sources + state: present + register: elastic_sources + when: ansible_distribution_major_version is version('12', '>=') + +- name: Update APT cache + ansible.builtin.apt: + update_cache: yes + when: elastic_sources is changed \ No newline at end of file diff --git a/filebeat/tasks/main.yml b/filebeat/tasks/main.yml index dd326cc8..9714183d 100644 --- a/filebeat/tasks/main.yml +++ b/filebeat/tasks/main.yml @@ -1,55 +1,14 @@ --- - -- name: APT https transport is enabled - apt: - name: apt-transport-https - state: present - tags: - - filebeat - - packages - -- name: Look for legacy apt keyring - stat: - path: /etc/apt/trusted.gpg - register: _trusted_gpg_keyring - tags: - - filebeat - - packages - -- name: Elastic embedded GPG key is absent - apt_key: - id: "D88E42B4" - keyring: /etc/apt/trusted.gpg - state: absent - when: _trusted_gpg_keyring.stat.exists - tags: - - filebeat - - packages - -- name: Elastic GPG key is installed - copy: - src: elastic.asc - dest: /etc/apt/trusted.gpg.d/elastic.asc - force: yes - mode: "0644" - owner: root - group: root - tags: - - filebeat - - packages - -- name: Elastic sources list is available - apt_repository: - repo: "deb https://artifacts.elastic.co/packages/{{ elastic_stack_version | mandatory }}/apt stable main" - filename: elastic - state: present - update_cache: yes - tags: - - filebeat - - packages +- name: APT sources + ansible.builtin.include_tasks: apt_sources.yml + args: + apply: + tags: + - filebeat + - packages - name: Filebeat is installed - apt: + ansible.builtin.apt: name: filebeat state: "{% if filebeat_upgrade_package %}latest{% else %}present{% endif %}" notify: restart filebeat @@ -58,35 +17,36 @@ - packages - name: Filebeat service is enabled - systemd: + ansible.builtin.systemd: name: filebeat enabled: yes notify: restart filebeat when: not ansible_check_mode - name: is logstash-plugin available? - stat: + ansible.builtin.stat: path: /usr/share/logstash/bin/logstash-plugin check_mode: no register: logstash_plugin - name: is logstash-input-beats installed? - command: grep logstash-input-beats /usr/share/logstash/Gemfile + ansible.builtin.command: + cmd: grep logstash-input-beats /usr/share/logstash/Gemfile check_mode: no register: logstash_plugin_installed - failed_when: false - changed_when: false + failed_when: False + changed_when: False when: - filebeat_logstash_plugin | bool - logstash_plugin.stat.exists - name: Logstash plugin is installed block: - - include_role: + - ansible.builtin.include_role: name: evolix/remount-usr - name: logstash-plugin install logstash-input-beats - command: /usr/share/logstash/bin/logstash-plugin install logstash-input-beats + ansible.builtin.command: /usr/share/logstash/bin/logstash-plugin install logstash-input-beats when: - filebeat_logstash_plugin | bool - logstash_plugin.stat.exists @@ -95,7 +55,7 @@ # When we don't use a config template (default) - block: - name: cloud_metadata processor is disabled - replace: + ansible.builtin.replace: dest: /etc/filebeat/filebeat.yml regexp: '^(\s+)(- add_cloud_metadata:)' replace: '\1# \2' @@ -103,7 +63,7 @@ when: not (filebeat_processors_cloud_metadata | bool) - name: cloud_metadata processor is disabled - lineinfile: + ansible.builtin.lineinfile: dest: /etc/filebeat/filebeat.yml line: " - add_cloud_metadata: ~" insert_after: '^processors:' @@ -111,7 +71,7 @@ when: filebeat_processors_cloud_metadata | bool - name: Filebeat knows where to find Elasticsearch - lineinfile: + ansible.builtin.lineinfile: dest: /etc/filebeat/filebeat.yml regexp: '^ hosts: .*' line: " hosts: [\"{{ filebeat_elasticsearch_hosts | join('\", \"') }}\"]" @@ -120,7 +80,7 @@ when: filebeat_elasticsearch_hosts | length > 0 - name: Filebeat protocol for Elasticsearch - lineinfile: + ansible.builtin.lineinfile: dest: /etc/filebeat/filebeat.yml regexp: '^ #?protocol: .*' line: " protocol: \"{{ filebeat_elasticsearch_protocol }}\"" @@ -129,7 +89,7 @@ when: filebeat_elasticsearch_protocol == "http" or filebeat_elasticsearch_protocol == "https" - name: Filebeat auth/username for Elasticsearch are configured - lineinfile: + ansible.builtin.lineinfile: dest: /etc/filebeat/filebeat.yml regexp: '{{ item.regexp }}' line: '{{ item.line }}' @@ -146,7 +106,7 @@ - not ansible_check_mode - name: Filebeat api_key for Elasticsearch are configured - lineinfile: + ansible.builtin.lineinfile: dest: /etc/filebeat/filebeat.yml regexp: '^ #?api_key: .*' line: ' api_key: "{{ filebeat_elasticsearch_auth_api_key }}"' @@ -157,7 +117,7 @@ # When we use a config template - block: - name: Configuration is up-to-date - template: + ansible.builtin.template: src: "{{ item }}" dest: /etc/filebeat/filebeat.yml force: "{{ filebeat_force_config }}" diff --git a/filebeat/templates/elastic.sources.j2 b/filebeat/templates/elastic.sources.j2 new file mode 100644 index 00000000..93df736d --- /dev/null +++ b/filebeat/templates/elastic.sources.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +Types: deb +URIs: https://artifacts.elastic.co/packages/{{ elastic_stack_version | mandatory }}/apt +Suites: stable +Components: main +Signed-by: {{ apt_keyring_dir }}/elastic.asc +Enabled: yes \ No newline at end of file diff --git a/fluentd/defaults/main.yml b/fluentd/defaults/main.yml index 86475f51..18d9b0c7 100644 --- a/fluentd/defaults/main.yml +++ b/fluentd/defaults/main.yml @@ -10,3 +10,5 @@ fluentd_host_port: fluentd_flush_interval: fluentd_heartbeat_type: + +apt_keyring_dir: "{{ ansible_distribution_major_version is version('12', '<') | ternary('/etc/apt/trusted.gpg.d', '/etc/apt/keyrings') }}" \ No newline at end of file diff --git a/fluentd/files/fluentd.asc b/fluentd/files/treasuredata.asc similarity index 100% rename from fluentd/files/fluentd.asc rename to fluentd/files/treasuredata.asc diff --git a/fluentd/handlers/main.yml b/fluentd/handlers/main.yml index 2468cef3..e87c76ab 100644 --- a/fluentd/handlers/main.yml +++ b/fluentd/handlers/main.yml @@ -1,10 +1,10 @@ --- - name: restart fluentd - systemd: + ansible.builtin.systemd: name: td-agent state: restarted - name: restart nagios-nrpe-server - service: + ansible.builtin.service: name: nagios-nrpe-server state: restarted diff --git a/fluentd/tasks/main.yml b/fluentd/tasks/main.yml index 282accf2..9f350bf4 100644 --- a/fluentd/tasks/main.yml +++ b/fluentd/tasks/main.yml @@ -1,28 +1,10 @@ --- -- name: Look for legacy apt keyring - stat: - path: /etc/apt/trusted.gpg - register: _trusted_gpg_keyring - tags: - - packages - - fluentd - -- name: Fluentd embedded GPG key is absent - apt_key: - id: "AB97ACBE" - keyring: /etc/apt/trusted.gpg - state: absent - when: _trusted_gpg_keyring.stat.exists - tags: - - packages - - fluentd - - name: Add Fluentd GPG key - copy: - src: fluentd.asc - dest: /etc/apt/trusted.gpg.d/fluentd.asc - force: yes + ansible.builtin.copy: + src: treasuredata.asc + dest: "{{ apt_keyring_dir }}/treasuredata.asc" + force: true mode: "0644" owner: root group: root @@ -30,18 +12,35 @@ - packages - fluentd -- name: Fluentd sources list is available - apt_repository: - repo: "deb http://packages.treasuredata.com/3/debian/{{ ansible_distribution_release }}/ {{ ansible_distribution_release }} contrib" +- name: Add Treasuredata repository (Debian <12) + ansible.builtin.apt_repository: + repo: "deb [signed-by={{ apt_keyring_dir }}/treasuredata.asc] http://packages.treasuredata.com/3/debian/{{ ansible_distribution_release }}/ {{ ansible_distribution_release }} contrib" filename: treasuredata - update_cache: yes state: present + update_cache: yes tags: - packages - fluentd + when: ansible_distribution_major_version is version('12', '<') + +- name: Add Treasuredata repository (Debian >=12) + ansible.builtin.template: + src: treasuredata.sources.j2 + dest: /etc/apt/sources.list.d/treasuredata.sources + state: present + register: treasuredata_sources + tags: + - packages + - fluentd + when: ansible_distribution_major_version is version('12', '>=') + +- name: Update APT cache + ansible.builtin.apt: + update_cache: yes + when: treasuredata_sources is changed - name: Fluentd is installed. - apt: + ansible.builtin.apt: name: td-agent state: present tags: @@ -49,7 +48,7 @@ - packages - name: Fluentd is configured. - template: + ansible.builtin.template: src: td-agent.conf.j2 dest: "{{ fluentd_conf_path }}" mode: "0644" @@ -58,7 +57,7 @@ - fluentd - name: Fluentd is running and enabled on boot. - systemd: + ansible.builtin.systemd: name: td-agent enabled: yes state: started @@ -66,7 +65,7 @@ - fluentd - name: NRPE check is configured - lineinfile: + ansible.builtin.lineinfile: path: /etc/nagios/nrpe.d/evolix.cfg line: 'command[check_fluentd]=/usr/lib/nagios/plugins/check_tcp -p {{ fluentd_port }}' notify: "restart nagios-nrpe-server" diff --git a/fluentd/templates/treasuredata.sources.j2 b/fluentd/templates/treasuredata.sources.j2 new file mode 100644 index 00000000..38dc3eb7 --- /dev/null +++ b/fluentd/templates/treasuredata.sources.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +Types: deb +URIs: http://packages.treasuredata.com/3/debian/{{ ansible_distribution_release }}/ +Suites: {{ ansible_distribution_release }} +Components: contrib +Signed-by: {{ apt_keyring_dir }}/treasuredata.asc +Enabled: yes \ No newline at end of file diff --git a/generate-ldif/tasks/exec.yml b/generate-ldif/tasks/exec.yml index 213560a5..0c25758a 100644 --- a/generate-ldif/tasks/exec.yml +++ b/generate-ldif/tasks/exec.yml @@ -1,6 +1,7 @@ --- - name: run generateldif - command: '{{ general_scripts_dir }}/generateldif.sh' + ansible.builtin.command: + cmd: '{{ general_scripts_dir }}/generateldif.sh' register: generateldif_run changed_when: False failed_when: False @@ -8,7 +9,7 @@ tags: - generateldif-exec -- debug: +- ansible.builtin.debug: var: generateldif_run.stdout_lines verbosity: 1 tags: diff --git a/generate-ldif/tasks/main.yml b/generate-ldif/tasks/main.yml index 019f5a83..29acb2fc 100644 --- a/generate-ldif/tasks/main.yml +++ b/generate-ldif/tasks/main.yml @@ -1,10 +1,10 @@ --- -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr when: general_scripts_dir is search("/usr") - name: "copy generateldif.sh" - template: + ansible.builtin.template: src: templates/generateldif.sh.j2 dest: '{{ general_scripts_dir }}/generateldif.sh' owner: root diff --git a/generate-ldif/templates/generateldif.sh.j2 b/generate-ldif/templates/generateldif.sh.j2 index 86bfc0eb..e306f075 100755 --- a/generate-ldif/templates/generateldif.sh.j2 +++ b/generate-ldif/templates/generateldif.sh.j2 @@ -40,27 +40,27 @@ if [ "$type" = "kvm" ]; then HardwareMark="KVM" HardwareModel="Virtual Machine" - cpuMark=$(lscpu | grep Vendor | tr -s '\t' ' ' | cut -d' ' -f3) - cpuModel="Virtual $(lscpu | grep "Model name" | tr -s '\t' ' ' | cut -d' ' -f3-), $(nproc) vCPU" - cpuFreq="$(lscpu | grep "CPU MHz" | tr -s '\t' ' ' | cut -d' ' -f3-)MHz" + cpuMark=$(lscpu | grep "Vendor ID:" | head -n1 | tr -s '\t' ' ' | cut -d' ' -f3) + cpuModel="Virtual $(lscpu | grep "Model name" | head -n1 | tr -s '\t' ' ' | cut -d' ' -f3-), $(nproc) vCPU" + cpuFreq="$(lscpu | grep "GHz" | head -n1 | tr -s '\t' ' ' | cut -d'@' -f2 | tr -d ' ')" elif [ "$type" = "vmware" ]; then ComputerType="VM" HardwareMark="VMWare" HardwareModel="Virtual Machine" - cpuMark=$(lscpu | grep Vendor | tr -s '\t' ' ' | cut -d' ' -f3) - cpuModel="Virtual $(lscpu | grep "Model name" | tr -s '\t' ' ' | cut -d' ' -f3-), $(nproc) vCPU" - cpuFreq="$(lscpu | grep "CPU MHz" | tr -s '\t' ' ' | cut -d' ' -f3-)MHz" + cpuMark=$(lscpu | grep "Vendor ID:" | head -n1 | tr -s '\t' ' ' | cut -d' ' -f3) + cpuModel="Virtual $(lscpu | grep "Model name" | head -n1 | tr -s '\t' ' ' | cut -d' ' -f3-), $(nproc) vCPU" + cpuFreq="$(lscpu | grep "GHz" | head -n1 | tr -s '\t' ' ' | cut -d'@' -f2 | tr -d ' ')" elif [ "$type" = "virtualbox" ]; then ComputerType="VM" HardwareMark="VirtualBox" HardwareModel="Virtual Machine" - cpuMark=$(lscpu | grep Vendor | tr -s '\t' ' ' | cut -d' ' -f3) - cpuModel="Virtual $(lscpu | grep "Model name" | tr -s '\t' ' ' | cut -d' ' -f3-), $(nproc) vCPU" - cpuFreq="$(lscpu | grep "CPU MHz" | tr -s '\t' ' ' | cut -d' ' -f3-)MHz" + cpuMark=$(lscpu | grep "Vendor ID:" | head -n1 | tr -s '\t' ' ' | cut -d' ' -f3) + cpuModel="Virtual $(lscpu | grep "Model name" | head -n1 | tr -s '\t' ' ' | cut -d' ' -f3-), $(nproc) vCPU" + cpuFreq="$(lscpu | grep "GHz" | head -n1 | tr -s '\t' ' ' | cut -d'@' -f2 | tr -d ' ')" else ComputerType="Baremetal" HardwareModel=$(dmidecode -s system-product-name | grep -v '^#') @@ -307,10 +307,10 @@ for net in $(ls /sys/class/net); do hw=$(cat ${path}/address) # In some cases some devices does not have a vendor or device, skip it test -f ${path}/device/vendor || continue - vendor_id=$(cat ${path}/device/vendor) + vendor_id=$(cat ${path}/device/vendor | sed -E 's/^0x//g') test -f ${path}/device/device || continue - dev_id=$(cat ${path}/device/device) - [ "${dev_id}" = "0x0001" ] && dev_id="0x1000" + dev_id=$(cat ${path}/device/device | sed -E 's/^0x//g') + [ "${dev_id}" = "0001" ] && dev_id="1000" dev=$(lspci -d "${vendor_id}:${dev_id}" -vm) vendor=$(echo "${dev}" | grep -E "^Vendor" | cut -d':' -f2 | xargs) model=$(echo "${dev}" | grep -E "^Vendor" -A1 | grep -E "^Device" | cut -d':' -f2 | xargs) @@ -408,12 +408,8 @@ EOT fi # MariaDB -if is_pkg_installed mariadb-server-10.3; then - mariadb_version=$(get_pkg_version mariadb-server-10.3) -elif is_pkg_installed mariadb-server-10.1; then - mariadb_version=$(get_pkg_version mariadb-server-10.1) -elif is_pkg_installed mariadb-server-10.0; then - mariadb_version=$(get_pkg_version mariadb-server-10.0) +if is_pkg_installed mariadb-server; then + mariadb_version=$(get_pkg_version mariadb-server) fi if [ -n "${mariadb_version}" ]; then cat <> "${ldif_file}" @@ -488,8 +484,8 @@ EOT fi # Dovecot -if is_pkg_installed dovecot-common; then - dovecot_version=$(get_pkg_version dovecot-common) +if is_pkg_installed dovecot-core; then + dovecot_version=$(get_pkg_version dovecot-core) fi if [ -n "${dovecot_version}" ]; then cat <> "${ldif_file}" diff --git a/graylog/README.md b/graylog/README.md new file mode 100644 index 00000000..1ad4e712 --- /dev/null +++ b/graylog/README.md @@ -0,0 +1,18 @@ +# Graylog + +Installation and basic configuration of Graylog. + +## Tasks + +Everything is in the `tasks/main.yml` file. + +## Available variables + +Main variables are : + +* `graylog_version`: the Graylog version to install (default: `5.0`), +* `graylog_listen_ip`: the listen IP for Graylog (default: `"127.0.0.1"`), +* `graylog_listen_port`: the listen port for Graylog (default: `9000`), +* `graylog_custom_datadir`: the Graylog data directory (default: `""`, the empty string). + +The full list of variables (with default values) can be found in `defaults/main.yml`. diff --git a/graylog/defaults/main.yml b/graylog/defaults/main.yml new file mode 100644 index 00000000..26ed02ea --- /dev/null +++ b/graylog/defaults/main.yml @@ -0,0 +1,5 @@ +--- +graylog_version: "5.0" +graylog_listen_ip: "127.0.0.1" +graylog_listen_port: 9000 +graylog_custom_datadir: "" diff --git a/graylog/tasks/main.yml b/graylog/tasks/main.yml new file mode 100644 index 00000000..66e1b5c3 --- /dev/null +++ b/graylog/tasks/main.yml @@ -0,0 +1,100 @@ +--- + +- name: Dependencies are installed + ansible.builtin.apt: + name: + - apt-transport-https + - openjdk-11-jre-headless + - uuid-runtime + - pwgen + - dirmngr + - gnupg + - wget + update_cache: yes + +- name: Elasticsearch is configured + ansible.builtin.lineinfile: + dest: '/etc/elasticsearch/elasticsearch.yml' + line: 'action.auto_create_index: false' + register: es_config + +- name: Elasticsearch is restarted + ansible.builtin.systemd: + name: elasticsearch + state: restarted + when: es_config is changed + +- name: Graylog repository is installed + ansible.builtin.apt: + deb: 'https://packages.graylog2.org/repo/packages/graylog-{{ graylog_version }}-repository_latest.deb' + +- name: Graylog is installed + ansible.builtin.apt: + name: + - graylog-server + update_cache: yes + +- name: Graylog password_secret is set + ansible.builtin.replace: + dest: '/etc/graylog/server/server.conf' + regexp: '^(password_secret =)$' + replace: '\1 {{ lookup("ansible.builtin.password", "/dev/null chars=ascii_lowercase,digits length=96") }}' + +- name: Graylog root_password_sha2 is set + ansible.builtin.replace: + dest: '/etc/graylog/server/server.conf' + regexp: '^(root_password_sha2 =)$' + replace: '\1 {{ graylog_root_password_sha2 }}' + when: graylog_root_password_sha2 is defined + +- name: Graylog http_bind_address is set + ansible.builtin.lineinfile: + dest: '/etc/graylog/server/server.conf' + line: 'http_bind_address = {{ graylog_listen_ip }}:{{ graylog_listen_port }}' + +- block: + - name: "Is {{ graylog_custom_datadir }} present ?" + ansible.builtin.stat: + path: "{{ graylog_custom_datadir }}" + check_mode: no + register: graylog_custom_datadir_test + + - name: "read the real datadir" + ansible.builtin.command: + cmd: readlink -f /var/lib/graylog-server + changed_when: False + check_mode: no + register: graylog_current_real_datadir_test + when: graylog_custom_datadir is defined and graylog_custom_datadir | length > 0 + +- block: + - name: Graylog is stopped + ansible.builtin.service: + name: graylog-server + state: stopped + + - name: Move Graylog datadir to {{ graylog_custom_datadir }} + ansible.builtin.command: + cmd: mv {{ graylog_current_real_datadir_test.stdout }} {{ graylog_custom_datadir }} + args: + creates: "{{ graylog_custom_datadir }}" + + - name: Symlink {{ graylog_custom_datadir }} to /var/lib/graylog-server + ansible.builtin.file: + src: "{{ graylog_custom_datadir }}" + dest: '/var/lib/graylog-server' + state: link + when: + - graylog_custom_datadir | length > 0 + - graylog_custom_datadir != graylog_current_real_datadir_test.stdout + - not graylog_custom_datadir_test.stat.exists + +- name: Graylog is started + ansible.builtin.service: + name: graylog-server + state: started + +- name: Graylog is enabled + ansible.builtin.service: + name: graylog-server + enabled: yes diff --git a/haproxy/defaults/main.yml b/haproxy/defaults/main.yml index 0745f1a9..50f6bb48 100644 --- a/haproxy/defaults/main.yml +++ b/haproxy/defaults/main.yml @@ -35,3 +35,5 @@ haproxy_deny_ips: [] haproxy_backports_packages_stretch: haproxy libssl1.0.0 haproxy_backports_packages_buster: haproxy haproxy_backports_packages_bullseye: haproxy + +haproxy_allow_ip_nonlocal_bind: Null \ No newline at end of file diff --git a/haproxy/handlers/main.yml b/haproxy/handlers/main.yml index 9cf3b9cb..a20031f1 100644 --- a/haproxy/handlers/main.yml +++ b/haproxy/handlers/main.yml @@ -1,15 +1,15 @@ --- - name: reload haproxy - service: + ansible.builtin.service: name: haproxy state: reloaded - name: restart haproxy - service: + ansible.builtin.service: name: haproxy state: restarted - name: restart munin-node - service: + ansible.builtin.service: name: munin-node state: restarted diff --git a/haproxy/tasks/main.yml b/haproxy/tasks/main.yml index d29e3cbc..12fdd224 100644 --- a/haproxy/tasks/main.yml +++ b/haproxy/tasks/main.yml @@ -1,6 +1,6 @@ --- - name: ssl-cert package is installed - apt: + ansible.builtin.apt: name: ssl-cert state: present tags: @@ -8,7 +8,7 @@ - packages - name: HAProxy SSL directory is present - file: + ansible.builtin.file: path: /etc/haproxy/ssl owner: root group: root @@ -19,7 +19,8 @@ - ssl - name: Self-signed certificate is present in HAProxy ssl directory - shell: "cat /etc/ssl/certs/ssl-cert-snakeoil.pem /etc/ssl/private/ssl-cert-snakeoil.key > /etc/haproxy/ssl/ssl-cert-snakeoil.pem" + ansible.builtin.shell: + cmd: "cat /etc/ssl/certs/ssl-cert-snakeoil.pem /etc/ssl/private/ssl-cert-snakeoil.key > /etc/haproxy/ssl/ssl-cert-snakeoil.pem" args: creates: /etc/haproxy/ssl/ssl-cert-snakeoil.pem notify: reload haproxy @@ -28,7 +29,7 @@ - ssl - name: HAProxy stats_access_ips are present - blockinfile: + ansible.builtin.blockinfile: dest: /etc/haproxy/stats_access_ips create: yes block: | @@ -42,7 +43,7 @@ - update-config - name: HAProxy stats_admin_ips are present - blockinfile: + ansible.builtin.blockinfile: dest: /etc/haproxy/stats_admin_ips create: yes block: | @@ -56,7 +57,7 @@ - update-config - name: HAProxy maintenance_ips are present - blockinfile: + ansible.builtin.blockinfile: dest: /etc/haproxy/maintenance_ips create: yes block: | @@ -70,7 +71,7 @@ - update-config - name: HAProxy deny_ips are present - blockinfile: + ansible.builtin.blockinfile: dest: /etc/haproxy/deny_ips create: yes block: | @@ -83,11 +84,11 @@ - config - update-config -- include: packages_backports.yml +- ansible.builtin.include: packages_backports.yml when: haproxy_backports | bool - name: Install HAProxy package - apt: + ansible.builtin.apt: name: haproxy state: present tags: @@ -95,7 +96,7 @@ - packages - name: Copy HAProxy configuration - template: + ansible.builtin.template: src: "{{ item }}" dest: /etc/haproxy/haproxy.cfg force: "{{ haproxy_force_config }}" @@ -115,7 +116,7 @@ - update-config - name: Rotate logs with dateext - lineinfile: + ansible.builtin.lineinfile: dest: /etc/logrotate.d/haproxy line: ' dateext' regexp: '^\s*#*\s*(no)?dateext' @@ -125,7 +126,7 @@ - logrotate - name: Rotate logs with nodelaycompress - lineinfile: + ansible.builtin.lineinfile: dest: /etc/logrotate.d/haproxy line: ' nodelaycompress' regexp: '^\s*#*\s*(no)?delaycompress' @@ -134,4 +135,17 @@ - haproxy - logrotate -- include: munin.yml +- name: Set net.ipv4.ip_nonlocal_bind + ansible.posix.sysctl: + name: net.ipv4.ip_nonlocal_bind + value: "{{ haproxy_allow_ip_nonlocal_bind | ternary('1','0') }}" + sysctl_file: "{{ evolinux_kernel_sysctl_path | default('/etc/sysctl.d/evolinux.conf') }}" + state: present + reload: yes + tags: + - haproxy + when: + - haproxy_allow_ip_nonlocal_bind is defined + - haproxy_allow_ip_nonlocal_bind is not none + +- ansible.builtin.include: munin.yml diff --git a/haproxy/tasks/munin.yml b/haproxy/tasks/munin.yml index 1f65dbe3..f7a35e56 100644 --- a/haproxy/tasks/munin.yml +++ b/haproxy/tasks/munin.yml @@ -1,6 +1,6 @@ --- - name: Install Munin plugin and dependencies - apt: + ansible.builtin.apt: name: - munin-plugins-extra - liblwp-useragent-determined-perl @@ -9,17 +9,17 @@ - haproxy - name: Enable Munin Haproxy plugins - file: + ansible.builtin.file: src: /usr/share/munin/plugins/haproxy_ng dest: /etc/munin/plugins/haproxy_ng - force: yes + force: true state: link notify: restart munin-node tags: - haproxy - name: Copy Munin Haproxy config - template: + ansible.builtin.template: src: munin.conf.j2 dest: /etc/munin/plugin-conf.d/haproxy mode: "0644" diff --git a/haproxy/tasks/packages_backports.yml b/haproxy/tasks/packages_backports.yml index eab4fbca..2a5a855c 100644 --- a/haproxy/tasks/packages_backports.yml +++ b/haproxy/tasks/packages_backports.yml @@ -1,29 +1,29 @@ --- -- include_role: +- ansible.builtin.include_role: name: evolix/apt tasks_from: backports.yml tags: - haproxy - packages -- set_fact: +- ansible.builtin.set_fact: haproxy_backports_packages: "{{ haproxy_backports_packages_stretch }}" when: ansible_distribution_release == 'stretch' -- set_fact: +- ansible.builtin.set_fact: haproxy_backports_packages: "{{ haproxy_backports_packages_buster }}" when: ansible_distribution_release == 'buster' -- set_fact: +- ansible.builtin.set_fact: haproxy_backports_packages: "{{ haproxy_backports_packages_bullseye }}" when: ansible_distribution_release == 'bullseye' - name: Prefer HAProxy package from backports - template: + ansible.builtin.template: src: haproxy_apt_preferences.j2 dest: /etc/apt/preferences.d/999-haproxy - force: yes + force: true mode: "0640" register: haproxy_apt_preferences tags: @@ -31,7 +31,7 @@ - packages - name: update apt - apt: + ansible.builtin.apt: update_cache: yes when: haproxy_apt_preferences is changed tags: diff --git a/haproxy/templates/haproxy.default.cfg.j2 b/haproxy/templates/haproxy.default.cfg.j2 index e33d111a..0f13e54d 100644 --- a/haproxy/templates/haproxy.default.cfg.j2 +++ b/haproxy/templates/haproxy.default.cfg.j2 @@ -63,7 +63,7 @@ listen stats acl stats_users http_auth(stats_users) stats http-request auth realm "HAProxy admin" if !stats_access_ips !stats_users {% else %} - stats http-request deny if !stats_access_ips + stats http-request deny if !stats_access_ips !stats_admin_ips {% endif %} http-request set-log-level silent diff --git a/haproxy/templates/munin.conf.j2 b/haproxy/templates/munin.conf.j2 index 24042f66..149896b2 100644 --- a/haproxy/templates/munin.conf.j2 +++ b/haproxy/templates/munin.conf.j2 @@ -1,4 +1,4 @@ [haproxy_*] {% if haproxy_stats_internal_enable %} -env.url http://{{ haproxy_stats_internal_host }}:{{ haproxy_stats_internal_port }}/;csv;norefresh +env.url http://{{ haproxy_stats_internal_host }}:{{ haproxy_stats_internal_port }}{{ haproxy_stats_path }};csv;norefresh {% endif %} diff --git a/java/defaults/main.yml b/java/defaults/main.yml index 89f5cdac..b28fd4a5 100644 --- a/java/defaults/main.yml +++ b/java/defaults/main.yml @@ -1,4 +1,4 @@ --- java_alternative: 'openjdk' -java_version: 8 +java_version: Null java_default_alternative: True diff --git a/java/tasks/main.yml b/java/tasks/main.yml index f899bf1c..d07ce5eb 100644 --- a/java/tasks/main.yml +++ b/java/tasks/main.yml @@ -3,8 +3,8 @@ # msg: "This role support only java 8 for now !" # when: java_version != 8 -- include: openjdk.yml +- ansible.builtin.include: openjdk.yml when: java_alternative == 'openjdk' -- include: oracle.yml +- ansible.builtin.include: oracle.yml when: java_alternative == 'oracle' diff --git a/java/tasks/openjdk.yml b/java/tasks/openjdk.yml index b41db0a7..e0d947db 100644 --- a/java/tasks/openjdk.yml +++ b/java/tasks/openjdk.yml @@ -1,31 +1,44 @@ --- - name: Decide which Debian release to use - set_fact: + ansible.builtin.set_fact: java_apt_release: '{% if ansible_distribution_release == "jessie" %}jessie-backports{% else %}{{ ansible_distribution_release }}{% endif %}' tags: - java - name: Install jessie-backports - include_role: + ansible.builtin.include_role: name: evolix/apt tasks_from: backports.yml when: ansible_distribution_release == "jessie" tags: - java -- name: Install openjdk package - apt: - name: "openjdk-{{ java_version}}-jre-headless" +- name: Install default openjdk package + ansible.builtin.apt: + name: "default-jre-headless" default_release: "{{ java_apt_release }}" state: present tags: - java - packages + when: java_version is none -- name: This openjdk version is the default alternative - alternatives: - name: java - path: "{{ java_bin_path[java_version] }}" - when: java_default_alternative | bool +- name: Install specific openjdk package + ansible.builtin.apt: + name: "openjdk-{{ java_version }}-jre-headless" + default_release: "{{ java_apt_release }}" + state: present tags: - java + - packages + when: java_version is not none + +- name: This openjdk version is the default alternative + community.general.alternatives: + name: java + path: "{{ java_bin_path[java_version] }}" + tags: + - java + when: + - java_default_alternative | bool + - java_version is not none diff --git a/java/tasks/oracle.yml b/java/tasks/oracle.yml index 0b057695..3c4b5b11 100644 --- a/java/tasks/oracle.yml +++ b/java/tasks/oracle.yml @@ -1,6 +1,6 @@ --- - name: Install dependencies for build java package - apt: + ansible.builtin.apt: name: - java-package - build-essential @@ -9,7 +9,7 @@ - java - name: Create jvm dir - file: + ansible.builtin.file: path: "{{ item }}" state: directory mode: "0777" @@ -21,7 +21,7 @@ - java - name: Get Oracle jre archive - get_url: + ansible.builtin.get_url: url: 'https://download.oracle.com/otn-pub/java/jdk/8u192-b12/750e1c8617c5452694857ad95c3ee230/server-jre-8u192-linux-x64.tar.gz' dest: '/srv/java-package/src/' checksum: 'sha256:3d811a5ec65dc6fc261f488757bae86ecfe285a79992363b016f60cdb4dbe7e6' @@ -31,25 +31,26 @@ - java - name: Make Debian package from Oracle JDK archive - shell: "yes | TMPDIR=/srv/java-package/tmp make-jpkg /srv/java-package/src/server-jre-8u192-linux-x64.tar.gz" + ansible.builtin.shell: + cmd: "yes | TMPDIR=/srv/java-package/tmp make-jpkg /srv/java-package/src/server-jre-8u192-linux-x64.tar.gz" args: chdir: /srv/java-package creates: /srv/java-package/oracle-java8-server-jre_8u192_amd64.deb - become: False + become: false tags: - java -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - name: Install java package - apt: + ansible.builtin.apt: deb: /srv/java-package/oracle-java8-server-jre_8u192_amd64.deb tags: - java - name: This openjdk version is the default alternative - alternatives: + community.general.alternatives: name: java path: "/usr/lib/jvm/oracle-java{{ java_version }}-server-jre-amd64/bin/java" when: java_default_alternative | bool diff --git a/jenkins/defaults/main.yml b/jenkins/defaults/main.yml new file mode 100644 index 00000000..bf1296d7 --- /dev/null +++ b/jenkins/defaults/main.yml @@ -0,0 +1,3 @@ +--- + +apt_keyring_dir: "{{ ansible_distribution_major_version is version('12', '<') | ternary('/etc/apt/trusted.gpg.d', '/etc/apt/keyrings') }}" \ No newline at end of file diff --git a/jenkins/handlers/main.yml b/jenkins/handlers/main.yml index b7d269cf..a38d1b47 100644 --- a/jenkins/handlers/main.yml +++ b/jenkins/handlers/main.yml @@ -1,15 +1,15 @@ --- - name: reload squid - service: + ansible.builtin.service: name: squid state: reloaded - name: reload squid3 - service: + ansible.builtin.service: name: squid3 state: reloaded - name: Restart Jenkins - service: + ansible.builtin.service: name: jenkins state: restarted diff --git a/jenkins/tasks/main.yml b/jenkins/tasks/main.yml index 8ed3d38c..835d3a3e 100644 --- a/jenkins/tasks/main.yml +++ b/jenkins/tasks/main.yml @@ -5,39 +5,42 @@ # http://mirrors.jenkins.io/.* # http://jenkins.mirror.isppower.de/.* -- name: Look for legacy apt keyring - stat: - path: /etc/apt/trusted.gpg - register: _trusted_gpg_keyring - -- name: Jenkins embedded GPG key is absent - apt_key: - id: "D50582E6" - keyring: /etc/apt/trusted.gpg - state: absent - when: _trusted_gpg_keyring.stat.exists - - name: Add Jenkins GPG key - copy: + ansible.builtin.copy: src: jenkins.asc - dest: /etc/apt/trusted.gpg.d/jenkins.asc - force: yes + dest: "{{ apt_keyring_dir }}/jenkins.asc" + force: true mode: "0644" owner: root group: root -- name: Add jenkins APT repository - apt_repository: - repo: deb http://pkg.jenkins-ci.org/debian-stable binary/ +- name: Add Jenkins APT repository (Debian <12) + ansible.builtin.apt_repository: + repo: deb [signed-by={{ apt_keyring_dir }}/jenkins.asc] http://pkg.jenkins-ci.org/debian-stable binary/ filename: jenkins update_cache: yes + when: ansible_distribution_major_version is version('12', '<') + +- name: Add Jenkins repository (Debian >=12) + ansible.builtin.template: + src: jenkins.sources.j2 + dest: /etc/apt/sources.list.d/jenkins.sources + state: present + register: jenkins_sources + when: ansible_distribution_major_version is version('12', '>=') + +- name: Update APT cache + ansible.builtin.apt: + update_cache: yes + when: jenkins_sources is changed - name: Install Jenkins - apt: + ansible.builtin.apt: name: jenkins + state: present - name: Change Jenkins port - replace: + ansible.builtin.replace: name: /etc/default/jenkins regexp: "^HTTP_PORT=.*$" replace: "HTTP_PORT=8081" diff --git a/jenkins/templates/jenkins.sources.j2 b/jenkins/templates/jenkins.sources.j2 new file mode 100644 index 00000000..c3578a03 --- /dev/null +++ b/jenkins/templates/jenkins.sources.j2 @@ -0,0 +1,7 @@ +# {{ ansible_managed }} + +Types: deb +URIs: http://pkg.jenkins-ci.org/debian-stable +Suites: binary/ +Signed-by: {{ apt_keyring_dir }}/jenkins.asc +Enabled: yes \ No newline at end of file diff --git a/keepalived/files/check_keepalived b/keepalived/files/check_keepalived index e518e99e..a457551d 100644 --- a/keepalived/files/check_keepalived +++ b/keepalived/files/check_keepalived @@ -18,35 +18,38 @@ MASTER='true' # checking if there are alive keepalived processes so we can trust the content of the notify 'state' file KEEPALIVENUM=`ps uax|grep '/usr/sbin/keepalived'|grep -v grep|wc -l|tr -d "\n"` -if [ $KEEPALIVENUM -gt 0 ]; then +if [ ${KEEPALIVENUM} -gt 0 ]; then KEEPALIVESTATE=`cat /var/run/keepalive.state` - if [ "$MASTER" == "true" ]; then + if [ "${MASTER}" == "true" ]; then - if [[ $KEEPALIVESTATE == *"MASTER"* ]];then - echo $KEEPALIVESTATE + if [[ ${KEEPALIVESTATE} == *"MASTER"* ]];then + echo "OK - ${KEEPALIVESTATE}" exit 0 fi - if [[ $KEEPALIVESTATE == *"BACKUP"* ]];then - echo $KEEPALIVESTATE - exit 2 + if [[ ${KEEPALIVESTATE} == *"BACKUP"* ]];then + echo "WARNING - ${KEEPALIVESTATE}" + exit 1 fi else - if [[ $KEEPALIVESTATE == *"BACKUP"* ]];then - echo $KEEPALIVESTATE + if [[ ${KEEPALIVESTATE} == *"BACKUP"* ]];then + echo "OK - ${KEEPALIVESTATE}" exit 0 fi - if [[ $KEEPALIVESTATE == *"MASTER"* ]];then - echo $KEEPALIVESTATE - exit 2 + if [[ ${KEEPALIVESTATE} == *"MASTER"* ]];then + echo "WARNING - ${KEEPALIVESTATE}" + exit 1 fi fi +else + echo "CRITICAL - keepalived is not running" + exit 2 fi echo "Keepalived is in UNKNOWN state" diff --git a/keepalived/files/notify.sh b/keepalived/files/notify.sh index b99c0489..7844a341 100644 --- a/keepalived/files/notify.sh +++ b/keepalived/files/notify.sh @@ -1,2 +1,3 @@ #!/bin/bash echo $1 $2 is in $3 state > /var/run/keepalive.state +chmod og+r /var/run/keepalive.state diff --git a/keepalived/handlers/main.yml b/keepalived/handlers/main.yml index 252fe515..7c9235d2 100644 --- a/keepalived/handlers/main.yml +++ b/keepalived/handlers/main.yml @@ -1,10 +1,10 @@ --- - name: restart keepalived - systemd: + ansible.builtin.systemd: name: keepalived state: restarted - name: restart nagios-nrpe-server - service: + ansible.builtin.service: name: nagios-nrpe-server state: restarted diff --git a/keepalived/tasks/main.yml b/keepalived/tasks/main.yml index e468da58..30f9557a 100644 --- a/keepalived/tasks/main.yml +++ b/keepalived/tasks/main.yml @@ -1,39 +1,39 @@ --- - name: install Keepalived service - apt: + ansible.builtin.apt: pkg: keepalived state: present tags: - keepalived - name: Add notify.sh script for NRPE check - file: + ansible.builtin.file: src: notify.sh dest: /etc/keepalived/notify.sh mode: "0755" owner: root group: root - force: yes + force: true notify: restart keepalived tags: - keepalived - nrpe - name: check_keepalived is installed - file: + ansible.builtin.file: src: check_keepalived dest: /usr/local/lib/nagios/plugins/check_keepalived mode: "0755" owner: root group: root - force: yes + force: true tags: - keepalived - nrpe - name: Use check_keepalived for NRPE - lineinfile: + ansible.builtin.lineinfile: dest: /etc/nagios/nrpe.d/evolix.cfg regexp: 'command\[check_keepalived\]' replace: 'command[check_keepalived]=/usr/local/lib/nagios/plugins/check_keepalived' @@ -43,16 +43,16 @@ - nrpe - name: generate Keepalived configuration - template: + ansible.builtin.template: src: keepalived.conf.j2 dest: /etc/keepalived/keepalived.conf - mode: 0644 + mode: "0644" notify: restart keepalived tags: - keepalived - name: enable and restart Keepalived service - systemd: + ansible.builtin.systemd: name: keepalived daemon_reload: yes state: started diff --git a/kibana/defaults/main.yml b/kibana/defaults/main.yml index 7107398c..900e579c 100644 --- a/kibana/defaults/main.yml +++ b/kibana/defaults/main.yml @@ -9,3 +9,5 @@ kibana_proxy_nginx: False kibana_proxy_domain: "kibana.{{ ansible_fqdn }}" kibana_proxy_ssl_cert: "/etc/ssl/certs/{{ ansible_fqdn }}.crt" kibana_proxy_ssl_key: "/etc/ssl/private/{{ ansible_fqdn }}.key" + +apt_keyring_dir: "{{ ansible_distribution_major_version is version('12', '<') | ternary('/etc/apt/trusted.gpg.d', '/etc/apt/keyrings') }}" \ No newline at end of file diff --git a/kibana/handlers/main.yml b/kibana/handlers/main.yml index cbccd8e0..90467e19 100644 --- a/kibana/handlers/main.yml +++ b/kibana/handlers/main.yml @@ -1,6 +1,6 @@ --- - name: restart kibana - systemd: + ansible.builtin.systemd: name: kibana state: restarted diff --git a/kibana/tasks/apt_sources.yml b/kibana/tasks/apt_sources.yml new file mode 100644 index 00000000..e525ba4b --- /dev/null +++ b/kibana/tasks/apt_sources.yml @@ -0,0 +1,36 @@ +--- +- name: APT https transport is enabled (Debian <10) + ansible.builtin.apt: + name: apt-transport-https + state: present + when: ansible_distribution_major_version is version('10', '<') + +- name: Elastic GPG key is installed + ansible.builtin.copy: + src: elastic.asc + dest: "{{ apt_keyring_dir }}/elastic.asc" + force: true + mode: "0644" + owner: root + group: root + +- name: Add Elastic repository (Debian <12) + ansible.builtin.apt_repository: + repo: "deb [signed-by={{ apt_keyring_dir }}/elastic.asc] https://artifacts.elastic.co/packages/{{ elastic_stack_version | mandatory }}/apt stable main" + filename: elastic + state: present + update_cache: yes + when: ansible_distribution_major_version is version('12', '<') + +- name: Add Elastic repository (Debian >=12) + ansible.builtin.template: + src: elastic.sources.j2 + dest: /etc/apt/sources.list.d/elastic.sources + state: present + register: elastic_sources + when: ansible_distribution_major_version is version('12', '>=') + +- name: Update APT cache + ansible.builtin.apt: + update_cache: yes + when: elastic_sources is changed \ No newline at end of file diff --git a/kibana/tasks/main.yml b/kibana/tasks/main.yml index d0694094..bcfb852a 100644 --- a/kibana/tasks/main.yml +++ b/kibana/tasks/main.yml @@ -1,63 +1,23 @@ --- +- name: APT sources + ansible.builtin.import_tasks: apt_sources.yml + args: + apply: + tags: + - kibana + - packages -- name: APT https transport is enabled - apt: - name: apt-transport-https - state: present - tags: - - kibana - - packages - -- name: Look for legacy apt keyring - stat: - path: /etc/apt/trusted.gpg - register: _trusted_gpg_keyring - tags: - - kibana - - packages - -- name: Elastic embedded GPG key is absent - apt_key: - id: "D88E42B4" - keyring: /etc/apt/trusted.gpg - state: absent - when: _trusted_gpg_keyring.stat.exists - tags: - - kibana - - packages - -- name: Elastic GPG key is installed - copy: - src: elastic.asc - dest: /etc/apt/trusted.gpg.d/elastic.asc - force: yes - mode: "0644" - owner: root - group: root - tags: - - kibana - - packages - -- name: Elastic sources list is available - apt_repository: - repo: "deb https://artifacts.elastic.co/packages/{{ elastic_stack_version | mandatory }}/apt stable main" - filename: elastic +- name: Kibana is installed + ansible.builtin.apt: + name: kibana state: present update_cache: yes tags: - kibana - packages -- name: Kibana is installed - apt: - name: kibana - state: present - tags: - - kibana - - packages - - name: kibana server host configuration - lineinfile: + ansible.builtin.lineinfile: dest: /etc/kibana/kibana.yml line: "server.host: \"{{ kibana_server_host }}\"" regexp: '^server.host:' @@ -67,7 +27,7 @@ - kibana - name: kibana server basepath configuration - lineinfile: + ansible.builtin.lineinfile: dest: /etc/kibana/kibana.yml line: "server.basePath: \"{{ kibana_server_basepath }}\"" regexp: '^server.basePath:' @@ -77,7 +37,7 @@ - kibana - name: kibana log destination is present - file: + ansible.builtin.file: dest: /var/log/kibana owner: kibana group: kibana @@ -87,7 +47,7 @@ - kibana - name: kibana log messages go to custom file - lineinfile: + ansible.builtin.lineinfile: dest: /etc/kibana/kibana.yml line: "logging.dest: \"/var/log/kibana/kibana.log\"" regexp: '^logging.dest:' @@ -97,7 +57,7 @@ - kibana - name: Kibana service is enabled and started - systemd: + ansible.builtin.systemd: name: kibana enabled: yes state: started @@ -105,7 +65,7 @@ - kibana - name: Logrotate configuration is enabled - copy: + ansible.builtin.copy: src: logrotate dest: /etc/logrotate.d/kibana mode: "0644" @@ -116,8 +76,6 @@ # - name: Get mount options for /usr partition # shell: "mount | grep 'on /usr type'" -# args: -# warn: no # register: mount # changed_when: False # failed_when: False @@ -136,7 +94,7 @@ # - optimize # - data -- include: proxy_nginx.yml +- ansible.builtin.include: proxy_nginx.yml when: kibana_proxy_nginx | bool tags: - kibana diff --git a/kibana/tasks/proxy_nginx.yml b/kibana/tasks/proxy_nginx.yml index 5849fdd6..e3b672b8 100644 --- a/kibana/tasks/proxy_nginx.yml +++ b/kibana/tasks/proxy_nginx.yml @@ -1,16 +1,16 @@ --- - name: Example proxy for Kibana with Nginx (with SSL) - template: + ansible.builtin.template: src: nginx_proxy_kibana_ssl.j2 dest: /etc/nginx/sites-available/kibana_ssl.conf - force: no + force: false - name: Example proxy for Kibana with Nginx (without SSL) - template: + ansible.builtin.template: src: nginx_proxy_kibana_nossl.j2 dest: /etc/nginx/sites-available/kibana_nossl.conf - force: no + force: false # - name: Kibana host in Nginx is enabled # file: diff --git a/kibana/templates/elastic.sources.j2 b/kibana/templates/elastic.sources.j2 new file mode 100644 index 00000000..93df736d --- /dev/null +++ b/kibana/templates/elastic.sources.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +Types: deb +URIs: https://artifacts.elastic.co/packages/{{ elastic_stack_version | mandatory }}/apt +Suites: stable +Components: main +Signed-by: {{ apt_keyring_dir }}/elastic.asc +Enabled: yes \ No newline at end of file diff --git a/kvm-host/files/add-vm.sh b/kvm-host/files/add-vm.sh index 51b5c737..78acfe1c 100755 --- a/kvm-host/files/add-vm.sh +++ b/kvm-host/files/add-vm.sh @@ -219,7 +219,7 @@ ${drbdadm} -- --overwrite-data-of-peer primary "${vmName}" if ! isDryRun; then sleep 5 - drbd-overview | tail -4 + drbdadm status | tail -4 drbdDiskPath="/dev/drbd/by-res/${vmName}/0" if ! [ -b "${drbdDiskPath}" ]; then diff --git a/kvm-host/files/kvmstats.sh b/kvm-host/files/kvmstats.sh index 0dcfb4e8..0258b322 100755 --- a/kvm-host/files/kvmstats.sh +++ b/kvm-host/files/kvmstats.sh @@ -42,25 +42,34 @@ error () { main() { for VM in $(virsh list --name --all | sed '/^$/d' | sort) do - echo "$VM" - - # cpu - virsh vcpucount --current "$VM" - - # mem - # libvirt stores memory in KiB, POW must be lowered by 1 - virsh dommemstat "$VM" 2>/dev/null | awk 'BEGIN{ret=1}$1~/^actual$/{print $2 / '$((POW / 1024))';ret=0}END{exit ret}' || - virsh dumpxml "$VM" | awk -F'[<>]' '$2~/^memory unit/{print $3/'$((POW / 1024))'}' - - # disk - for BLK in $(virsh domblklist "$VM" | sed '1,2d;/-$/d;/^$/d' | awk '{print $1}') - do - virsh domblkinfo "$VM" "$BLK" 2>/dev/null - done | awk '/Physical:/ { size += $2 } END { print int(size / '${POW}') }' - - # state - virsh domstate "$VM" | grep -q '^running$' && echo yes || echo no - done | xargs -n5 | { + printf '%s ' "${VM}" + virsh domstats "${VM}" | awk ' +BEGIN { + FS = "=" +} +/vcpu\.current/ { + vcpu = $2 +} +/balloon\.current/ { + mem = $2 +} +/balloon\.maximum/ { + if (!mem) + mem = $2 +} +/block\.[0-9]+\.physical/ { + disksize += $2 +} +/state\.state/ { + if ($2 == 1) + running = "yes" + else + running = "no" +} +END { + print vcpu, mem / 1024 ^ 2, disksize / 1024 ^ 3, running +}' + done | { echo vm vcpu ram disk running awk '{ print } /yes$/ { vcpu += $2; ram += $3; disk += $4; running++ } END { print "TOTAL(running)", vcpu, ram, disk, running }' test "$SHOW_AVAIL" && { @@ -72,7 +81,19 @@ main() { column -t ;; 'html') - awk 'BEGIN{print "\n"}{printf "";for(i=1;i<=NF;i++)printf "", $i;print ""}END{print "
    %s
    \n"}' + awk ' +BEGIN { + print "\n" +} +{ + printf "" + for(i = 1; i <= NF; i++) + printf "", $i + print "" +} +END { + print "
    %s
    \n" +}' ;; 'csv') tr ' ' ',' diff --git a/kvm-host/handlers/main.yml b/kvm-host/handlers/main.yml index 0b7f394e..5ca5295a 100644 --- a/kvm-host/handlers/main.yml +++ b/kvm-host/handlers/main.yml @@ -1,5 +1,5 @@ --- - name: restart munin-node - service: + ansible.builtin.service: name: munin-node state: restarted diff --git a/kvm-host/tasks/images.yml b/kvm-host/tasks/images.yml index b9ec57a8..9e8a7670 100644 --- a/kvm-host/tasks/images.yml +++ b/kvm-host/tasks/images.yml @@ -3,13 +3,13 @@ - name: Set images path when customized block: - name: "Is {{ kvm_custom_libvirt_images_path }} present ?" - stat: + ansible.builtin.stat: path: "{{ kvm_custom_libvirt_images_path }}" check_mode: no register: kvm_custom_libvirt_images_path_test - name: "read the real datadir" - command: readlink -f /var/lib/libvirt/images + ansible.builtin.command: readlink -f /var/lib/libvirt/images changed_when: False check_mode: no register: kvm_libvirt_images_current_real_path_test @@ -18,19 +18,19 @@ - name: Images directory is moved to custom path block: - name: "Move libvirt images to {{ kvm_custom_libvirt_images_path }}" - command: mv /var/lib/libvirt/images {{ kvm_custom_libvirt_images_path }} + ansible.builtin.command: mv /var/lib/libvirt/images {{ kvm_custom_libvirt_images_path }} args: creates: "{{ kvm_custom_libvirt_images_path }}" - name: Fix owner/group/permissions - file: + ansible.builtin.file: path: "{{ kvm_custom_libvirt_images_path }}" owner: root group: libvirt mode: "02775" - name: "Symlink {{ kvm_custom_libvirt_images_path }} to /var/lib/libvirt/images" - file: + ansible.builtin.file: src: "{{ kvm_custom_libvirt_images_path }}" dest: '/var/lib/libvirt/images' state: link diff --git a/kvm-host/tasks/main.yml b/kvm-host/tasks/main.yml index a2f6953c..c6004b7b 100644 --- a/kvm-host/tasks/main.yml +++ b/kvm-host/tasks/main.yml @@ -1,16 +1,16 @@ --- -- include_role: +- ansible.builtin.include_role: name: evolix/drbd when: kvm_install_drbd ## TODO: check why it's disabled -- include: ssh.yml +- ansible.builtin.include: ssh.yml -- include: packages.yml +- ansible.builtin.include: packages.yml -- include: munin.yml +- ansible.builtin.include: munin.yml -- include: images.yml +- ansible.builtin.include: images.yml -- include: tools.yml +- ansible.builtin.include: tools.yml diff --git a/kvm-host/tasks/munin.yml b/kvm-host/tasks/munin.yml index d0bf1b0a..8cd45cb5 100644 --- a/kvm-host/tasks/munin.yml +++ b/kvm-host/tasks/munin.yml @@ -1,46 +1,48 @@ --- -- include_role: +- ansible.builtin.include_role: name: remount-usr - name: Create local munin directory - file: + ansible.builtin.file: name: /usr/local/share/munin/ state: directory mode: "0755" - name: Create plugin directory - file: + ansible.builtin.file: name: /usr/local/share/munin/plugins/ state: directory mode: "0755" - name: Get Munin plugins - get_url: + ansible.builtin.get_url: url: "https://raw.githubusercontent.com/munin-monitoring/contrib/master/plugins/libvirt/{{ item }}" dest: "/usr/local/share/munin/plugins/" mode: "0755" - force: no + force: false loop: - kvm_cpu - kvm_io - kvm_mem notify: restart munin-node -- name: Enable redis munin plugin - file: - src: "/usr/local/share/munin/plugins/{{item}}" - dest: "/etc/munin/plugins/{{item}}" +- name: Enable Munin plugins + ansible.builtin.file: + src: "/usr/local/share/munin/plugins/{{ plugin_name }}" + dest: "/etc/munin/plugins/{{ plugin_name }}" state: link - force: yes + force: true loop: - kvm_cpu - kvm_io - kvm_mem + loop_control: + loop_var: plugin_name notify: restart munin-node - name: Copy Munin plugins conf - copy: + ansible.builtin.copy: src: files/munin-plugins dest: "/etc/munin/plugin-conf.d/kvm" mode: "0644" diff --git a/kvm-host/tasks/packages.yml b/kvm-host/tasks/packages.yml index 1b58b324..12e7897e 100644 --- a/kvm-host/tasks/packages.yml +++ b/kvm-host/tasks/packages.yml @@ -1,7 +1,7 @@ --- - name: Install packages for kvm/libvirt - apt: + ansible.builtin.apt: name: - qemu-kvm - netcat-openbsd @@ -14,7 +14,7 @@ state: present - name: Install packages for kvmstats - apt: + ansible.builtin.apt: name: - dialog - html-xml-utils diff --git a/kvm-host/tasks/ssh.yml b/kvm-host/tasks/ssh.yml index 3c097abc..9ce09eb7 100644 --- a/kvm-host/tasks/ssh.yml +++ b/kvm-host/tasks/ssh.yml @@ -1,18 +1,19 @@ --- - name: Generate root ssh_key - user: + ansible.builtin.user: name: root generate_ssh_key: yes ssh_key_bits: 2048 - name: Fetch ssh public keys - command: cat /root/.ssh/id_rsa.pub + ansible.builtin.command: + cmd: cat /root/.ssh/id_rsa.pub register: ssh_keys check_mode: no - changed_when: false + changed_when: False - name: Print ssh public keys - debug: + ansible.builtin.debug: msg: "{{ ssh_keys.stdout }}" #- name: Autorize other kvm ssh key @@ -28,7 +29,7 @@ # when: item[1] != inventory_hostname - name: Crontab for sync libvirt xml file - cron: + ansible.builtin.cron: name: "sync libvirt xml on {{ kvm_pair }}" state: present special_time: "hourly" @@ -42,7 +43,7 @@ tags: crontab - name: Crontab for sync list of running vm - cron: + ansible.builtin.cron: name: "sync list of libvirt running vm on {{ kvm_pair }}" state: present special_time: "daily" diff --git a/kvm-host/tasks/tools.yml b/kvm-host/tasks/tools.yml index 83845a31..fccf9307 100644 --- a/kvm-host/tasks/tools.yml +++ b/kvm-host/tasks/tools.yml @@ -1,44 +1,44 @@ --- - name: remove old package - apt: + ansible.builtin.apt: name: kvm-tools purge: yes state: absent -- include_role: +- ansible.builtin.include_role: name: remount-usr - when: kvm_scripts_dir is search ("/usr") + when: kvm_scripts_dir is search("/usr") - name: add-vm script is present - copy: + ansible.builtin.copy: src: add-vm.sh dest: "{{ kvm_scripts_dir }}/add-vm" mode: "0700" owner: root group: root - force: yes + force: true - name: migrate-vm script is present - copy: + ansible.builtin.copy: src: migrate-vm.sh dest: "{{ kvm_scripts_dir }}/migrate-vm" mode: "0700" owner: root group: root - force: yes + force: true - name: kvmstats script is present - copy: + ansible.builtin.copy: src: kvmstats.sh dest: "{{ kvm_scripts_dir }}/kvmstats" mode: "0700" owner: root group: root - force: yes + force: true - name: kvmstats cron is present - template: + ansible.builtin.template: src: kvmstats.cron.j2 dest: "/etc/cron.hourly/kvmstats" mode: "0755" @@ -46,7 +46,7 @@ group: root - name: entry for kvmstats in web page is present - lineinfile: + ansible.builtin.lineinfile: dest: /var/www/index.html insertbefore: '' line: '
  • kvmstats
  • ' @@ -55,13 +55,13 @@ # backward compatibility - name: remove old migrate-vm script - file: + ansible.builtin.file: path: /usr/share/scripts/migrate-vm state: absent when: "'/usr/share/scripts' not in kvm_scripts_dir" - name: remove old kvmstats script - file: + ansible.builtin.file: path: /usr/share/scripts/kvmstats state: absent when: "'/usr/share/scripts' not in kvm_scripts_dir" \ No newline at end of file diff --git a/ldap/handlers/main.yml b/ldap/handlers/main.yml index 2105f4b5..5735515b 100644 --- a/ldap/handlers/main.yml +++ b/ldap/handlers/main.yml @@ -1,5 +1,5 @@ --- - name: restart slapd - service: + ansible.builtin.service: name: slapd state: restarted diff --git a/ldap/tasks/init.yml b/ldap/tasks/init.yml index 16be0842..0ab85f18 100644 --- a/ldap/tasks/init.yml +++ b/ldap/tasks/init.yml @@ -1,32 +1,35 @@ --- - name: upload ldap initial config - template: + ansible.builtin.template: src: config_ldapvi.j2 dest: /root/evolinux_ldap_config.ldapvi mode: "0640" - name: upload ldap initial entries - template: + ansible.builtin.template: src: first-entries.ldif.j2 dest: /root/evolinux_ldap_first-entries.ldif mode: "0640" - name: inject config - command: ldapvi -Y EXTERNAL -h ldapi:// --ldapmodify /root/evolinux_ldap_config.ldapvi + ansible.builtin.command: + cmd: ldapvi -Y EXTERNAL -h ldapi:// --ldapmodify /root/evolinux_ldap_config.ldapvi environment: TERM: xterm - name: inject first entries - command: slapadd -l /root/evolinux_ldap_first-entries.ldif + ansible.builtin.command: + cmd: slapadd -l /root/evolinux_ldap_first-entries.ldif - name: upload custom schema - copy: + ansible.builtin.copy: src: "{{ ldap_schema }}" dest: "/root/{{ ldap_schema }}" mode: "0640" when: ldap_schema is defined - name: inject custom schema - command: "ldapadd -Y EXTERNAL -H ldapi:/// -f /root/{{ ldap_schema }}" + ansible.builtin.command: + cmd: "ldapadd -Y EXTERNAL -H ldapi:/// -f /root/{{ ldap_schema }}" when: ldap_schema is defined \ No newline at end of file diff --git a/ldap/tasks/ldapvirc.yml b/ldap/tasks/ldapvirc.yml index f44249d6..568ad60a 100644 --- a/ldap/tasks/ldapvirc.yml +++ b/ldap/tasks/ldapvirc.yml @@ -1,13 +1,13 @@ --- - name: "Is /root/.ldapvirc present ?" - stat: + ansible.builtin.stat: path: /root/.ldapvirc check_mode: no register: root_ldapvirc_path - name: Warning when ldapvirc file is present and ldap_admin_password is given - debug: + ansible.builtin.debug: msg: "WARNING: an LDAP admin password is given, but an ldapvirc file already exists. It will not be updated." when: - ldap_admin_password | length > 0 @@ -15,13 +15,14 @@ # Generate ldap password if none is given and ldapvirc is absent - name: apg package is installed - apt: + ansible.builtin.apt: name: apg state: present when: not root_ldapvirc_path.stat.exists - name: create a password for cn=admin - command: "apg -n 1 -m 16 -M lcN" + ansible.builtin.command: + cmd: "apg -n 1 -m 16 -M lcN" register: new_ldap_admin_password changed_when: False when: @@ -30,20 +31,21 @@ # Use the generated password or the one found in the file - name: overwrite ldap_admin_password - set_fact: + ansible.builtin.set_fact: ldap_admin_password: "{{ new_ldap_admin_password.stdout }}" when: - ldap_admin_password | length == 0 - not root_ldapvirc_path.stat.exists - name: hash password for cn=admin - command: "slappasswd -s {{ ldap_admin_password }}" + ansible.builtin.command: + cmd: "slappasswd -s {{ ldap_admin_password }}" register: ldap_admin_password_ssha changed_when: False when: not root_ldapvirc_path.stat.exists - name: create ldapvirc config - template: + ansible.builtin.template: src: ldapvirc.j2 dest: /root/.ldapvirc mode: "0640" @@ -51,12 +53,13 @@ # Read ldap password when none is given and ldapvirc is present - name: read ldap admin password from ldapvirc file - shell: "grep -E '^password: .+$' /root/.ldapvirc | awk '{print $2}'" + ansible.builtin.shell: + cmd: "grep -E '^password: .+$' /root/.ldapvirc | awk '{print $2}'" changed_when: False check_mode: no register: new_ldap_admin_password # Use the password found in the file - name: overwrite ldap_admin_password - set_fact: + ansible.builtin.set_fact: ldap_admin_password: "{{ new_ldap_admin_password.stdout }}" diff --git a/ldap/tasks/main.yml b/ldap/tasks/main.yml index 9bfb6517..ca89b997 100644 --- a/ldap/tasks/main.yml +++ b/ldap/tasks/main.yml @@ -1,5 +1,5 @@ - name: LDAP packages are installed - apt: + ansible.builtin.apt: name: - slapd - ldap-utils @@ -9,18 +9,18 @@ update_cache: yes - name: change slapd listen ip:port - lineinfile: + ansible.builtin.lineinfile: dest: /etc/default/slapd regexp: 'SLAPD_SERVICES=.*' line: "SLAPD_SERVICES=\"{{ ldap_listen }}\"" notify: restart slapd - name: ldapvirc file - include: ldapvirc.yml + ansible.builtin.include: ldapvirc.yml - name: nagios config file for LDAP - include: nagios.yml + ansible.builtin.include: nagios.yml - name: initialize database - include: init.yml + ansible.builtin.include: init.yml when: not root_ldapvirc_path.stat.exists \ No newline at end of file diff --git a/ldap/tasks/nagios.yml b/ldap/tasks/nagios.yml index 0c92f7b3..58120baa 100644 --- a/ldap/tasks/nagios.yml +++ b/ldap/tasks/nagios.yml @@ -1,13 +1,13 @@ --- - name: "Is /etc/nagios/monitoring-plugins.ini present ?" - stat: + ansible.builtin.stat: path: /etc/nagios/monitoring-plugins.ini check_mode: no register: nagios_monitoring_plugins_path - name: Warning when nagios config is present and ldap_nagios_password is given - debug: + ansible.builtin.debug: msg: "WARNING: an LDAP nagios password is given, but a nagios config already exists. It will not be updated." when: - ldap_nagios_password | length > 0 @@ -15,7 +15,7 @@ # Generate ldap password if none is given and nagios config is absent - name: apg package is installed - apt: + ansible.builtin.apt: name: apg state: present when: @@ -23,7 +23,8 @@ - not nagios_monitoring_plugins_path.stat.exists - name: create a password for cn=admin - command: "apg -n 1 -m 16 -M lcN" + ansible.builtin.command: + cmd: "apg -n 1 -m 16 -M lcN" register: new_ldap_nagios_password changed_when: False when: @@ -32,14 +33,14 @@ # Use the generated password or the one found in the file - name: overwrite ldap_nagios_password (from apg) - set_fact: + ansible.builtin.set_fact: ldap_nagios_password: "{{ new_ldap_nagios_password.stdout }}" when: - ldap_nagios_password | length == 0 - not nagios_monitoring_plugins_path.stat.exists - name: set params for NRPE check - ini_file: + community.general.ini_file: dest: /etc/nagios/monitoring-plugins.ini owner: root group: nagios @@ -57,7 +58,7 @@ # Read ldap password when none is given and nagios config is present # We can't parse a remote file, so we have to fetch it first - name: Fetch /etc/nagios/monitoring-plugins.ini - fetch: + ansible.builtin.fetch: src: /etc/nagios/monitoring-plugins.ini dest: /tmp/{{ inventory_hostname }}/ flat: yes @@ -65,10 +66,11 @@ # Then web can parse it with the 'ini' lookup # and set the variable - name: overwrite ldap_nagios_password (from file) - set_fact: + ansible.builtin.set_fact: ldap_nagios_password: "{{ lookup('ini', 'pass section=check_ldap file=/tmp/{{ inventory_hostname }}/monitoring-plugins.ini') }}" - name: hash password for cn=nagios - command: "slappasswd -s {{ ldap_nagios_password }}" + ansible.builtin.command: + cmd: "slappasswd -s {{ ldap_nagios_password }}" register: ldap_nagios_password_ssha changed_when: False \ No newline at end of file diff --git a/listupgrade/files/listupgrade.sh b/listupgrade/files/listupgrade.sh index 74a673aa..e6518f43 100644 --- a/listupgrade/files/listupgrade.sh +++ b/listupgrade/files/listupgrade.sh @@ -9,13 +9,13 @@ # - 60 : current release is not in the $r_releases list # - 70 : at least an upgradable package is not in the $r_packages list -VERSION="21.06.3" +VERSION="23.03.3" show_version() { cat <, +Copyright 2018-2023 Evolix , Gregory Colpart , Romain Dessort , Ludovic Poujol , @@ -84,6 +84,7 @@ Subject: Prochain creneau pour mise a jour de votre serveur ${hostname} X-Debian-Release: ${local_release} X-Packages: ${packagesParsable} X-Date: ${date} +X-Listupgrade-Version: ${VERSION} Bonjour, @@ -100,15 +101,15 @@ semaine prochaine. Voici la listes de packages qui seront mis à jour : -$(cat "${packages}") +$(sort -h "${packages}" | uniq) Liste des packages dont la mise-à-jour a été manuellement suspendue : -$(cat "${packagesHold}") +$(sort -h "${packagesHold}" | uniq) Liste des services qui seront redémarrés : -$(cat "${servicesToRestart}") +$(sort -h "${servicesToRestart}" | uniq) N'hésitez pas à nous faire toute remarque sur ce créneau d'intervention le plus tôt possible. @@ -181,6 +182,28 @@ main() { fi local_release=$(cut -f 1 -d . >"${servicesToRestart}" elif echo "${pkg}" | grep -q "^mariadb-server"; then echo "MariaDB" >>"${servicesToRestart}" - elif echo "${pkg}" | grep -qE "^postgresql-[[:digit:]]+\.[[:digit:]]+$"; then + elif echo "${pkg}" | grep -qE "^postgresql-[[:digit:]]+(\.[[:digit:]]+)?$"; then echo "PostgreSQL" >>"${servicesToRestart}" elif echo "${pkg}" | grep -qE "^tomcat[[:digit:]]+$"; then echo "Tomcat" >>"${servicesToRestart}" diff --git a/listupgrade/tasks/main.yml b/listupgrade/tasks/main.yml index fc02dfeb..dec4881d 100644 --- a/listupgrade/tasks/main.yml +++ b/listupgrade/tasks/main.yml @@ -1,10 +1,10 @@ --- -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - name: Scripts dir is present - file: + ansible.builtin.file: path: "/usr/share/scripts" state: directory owner: root @@ -12,16 +12,16 @@ mode: "0700" - name: Copy listupgrade script - copy: + ansible.builtin.copy: src: listupgrade.sh dest: "/usr/share/scripts/listupgrade.sh" mode: "0700" owner: root group: root - force: yes + force: true - name: Create /etc/evolinux - file: + ansible.builtin.file: path: /etc/evolinux state: directory owner: root @@ -29,16 +29,16 @@ mode: "0700" - name: Copy listupgrade config - template: + ansible.builtin.template: src: listupgrade.cnf.j2 dest: /etc/evolinux/listupgrade.cnf mode: "0600" owner: root group: root - force: no + force: false - name: Cron.d is present - file: + ansible.builtin.file: path: "/etc/cron.d" state: directory mode: "0755" @@ -46,11 +46,11 @@ group: root - name: Enable listupgrade cron - cron: - name: "lisupgrade.sh" + ansible.builtin.cron: + name: "listupgrade.sh" cron_file: "listupgrade" user: root - job: "/usr/share/scripts/listupgrade.sh --cron {{ listupgrade_cron_force | bool | ternary('--force','') }}" + job: "/usr/share/scripts/listupgrade.sh --cron{{ listupgrade_cron_force | bool | ternary(' --force','') }}" minute: "{{ listupgrade_cron_minute }}" hour: "{{ listupgrade_cron_hour }}" weekday: "{{ listupgrade_cron_weekday }}" @@ -59,7 +59,7 @@ state: "{{ listupgrade_cron_enabled | bool | ternary('present','absent') }}" - name: old-kernel-autoremoval script is present - copy: + ansible.builtin.copy: src: old-kernel-autoremoval.sh dest: /usr/share/scripts/old-kernel-autoremoval.sh mode: "0755" diff --git a/logstash/defaults/main.yml b/logstash/defaults/main.yml index 7cc40e49..b42fc347 100644 --- a/logstash/defaults/main.yml +++ b/logstash/defaults/main.yml @@ -7,4 +7,6 @@ logstash_log_rotate_days: 365 logstash_custom_tmpdir: Null logstash_default_tmpdir: /var/lib/logstash/tmp logstash_log_syslog_enabled: True -logstash_config_force: True \ No newline at end of file +logstash_config_force: True + +apt_keyring_dir: "{{ ansible_distribution_major_version is version('12', '<') | ternary('/etc/apt/trusted.gpg.d', '/etc/apt/keyrings') }}" \ No newline at end of file diff --git a/logstash/handlers/main.yml b/logstash/handlers/main.yml index d21d4de3..b38c949e 100644 --- a/logstash/handlers/main.yml +++ b/logstash/handlers/main.yml @@ -1,10 +1,11 @@ --- - name: restart logstash - systemd: + ansible.builtin.systemd: name: logstash state: restarted daemon_reload: yes - name: reload systemd - command: systemctl daemon-reload \ No newline at end of file + ansible.builtin.systemd: + daemon-reload: yes \ No newline at end of file diff --git a/logstash/tasks/apt_sources.yml b/logstash/tasks/apt_sources.yml new file mode 100644 index 00000000..e525ba4b --- /dev/null +++ b/logstash/tasks/apt_sources.yml @@ -0,0 +1,36 @@ +--- +- name: APT https transport is enabled (Debian <10) + ansible.builtin.apt: + name: apt-transport-https + state: present + when: ansible_distribution_major_version is version('10', '<') + +- name: Elastic GPG key is installed + ansible.builtin.copy: + src: elastic.asc + dest: "{{ apt_keyring_dir }}/elastic.asc" + force: true + mode: "0644" + owner: root + group: root + +- name: Add Elastic repository (Debian <12) + ansible.builtin.apt_repository: + repo: "deb [signed-by={{ apt_keyring_dir }}/elastic.asc] https://artifacts.elastic.co/packages/{{ elastic_stack_version | mandatory }}/apt stable main" + filename: elastic + state: present + update_cache: yes + when: ansible_distribution_major_version is version('12', '<') + +- name: Add Elastic repository (Debian >=12) + ansible.builtin.template: + src: elastic.sources.j2 + dest: /etc/apt/sources.list.d/elastic.sources + state: present + register: elastic_sources + when: ansible_distribution_major_version is version('12', '>=') + +- name: Update APT cache + ansible.builtin.apt: + update_cache: yes + when: elastic_sources is changed \ No newline at end of file diff --git a/logstash/tasks/logs.yml b/logstash/tasks/logs.yml index b09ebaf2..8262ce29 100644 --- a/logstash/tasks/logs.yml +++ b/logstash/tasks/logs.yml @@ -1,7 +1,7 @@ --- - name: Check if cron is installed - shell: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'" - args: + ansible.builtin.shell: + cmd: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'" executable: /bin/bash check_mode: no failed_when: False @@ -9,7 +9,7 @@ register: is_cron_installed - name: "log rotation script" - template: + ansible.builtin.template: src: rotate_logstash_logs.j2 dest: /etc/cron.daily/rotate_logstash_logs owner: root @@ -18,12 +18,12 @@ when: is_cron_installed.rc == 0 - name: "Create a system config directory for systemd overrides" - file: + ansible.builtin.file: path: /etc/systemd/system/logstash.service.d state: directory - name: "disable syslog" - ini_file: + community.general.ini_file: path: /etc/systemd/system/logstash.service.d/override.conf section: Service option: "{{ item.option }}" diff --git a/logstash/tasks/main.yml b/logstash/tasks/main.yml index 856ceba1..4f3b8da7 100644 --- a/logstash/tasks/main.yml +++ b/logstash/tasks/main.yml @@ -1,55 +1,14 @@ --- - -- name: APT https transport is enabled - apt: - name: apt-transport-https - state: present - tags: - - logstash - - packages - -- name: Look for legacy apt keyring - stat: - path: /etc/apt/trusted.gpg - register: _trusted_gpg_keyring - tags: - - logstash - - packages - -- name: Elastic embedded GPG key is absent - apt_key: - id: "D88E42B4" - keyring: /etc/apt/trusted.gpg - state: absent - when: _trusted_gpg_keyring.stat.exists - tags: - - logstash - - packages - -- name: Elastic GPG key is installed - copy: - src: elastic.asc - dest: /etc/apt/trusted.gpg.d/elastic.asc - force: yes - mode: "0644" - owner: root - group: root - tags: - - logstash - - packages - -- name: Elastic sources list is available - apt_repository: - repo: "deb https://artifacts.elastic.co/packages/{{ elastic_stack_version | mandatory }}/apt stable main" - filename: elastic - state: present - update_cache: yes - tags: - - logstash - - packages +- name: APT sources + ansible.builtin.import_tasks: apt_sources.yml + args: + apply: + tags: + - logstash + - packages - name: Logstash is installed - apt: + ansible.builtin.apt: name: logstash state: present tags: @@ -57,14 +16,14 @@ - packages - name: Logstash service is enabled - systemd: + ansible.builtin.systemd: name: logstash enabled: yes tags: - logstash - name: JVM Heap size (min) is set - lineinfile: + ansible.builtin.lineinfile: dest: /etc/logstash/jvm.options regexp: "^-Xms" line: "-Xms{{ logstash_jvm_xms }}" @@ -73,7 +32,7 @@ - config - name: JVM Heap size (max) is set - lineinfile: + ansible.builtin.lineinfile: dest: /etc/logstash/jvm.options regexp: "^-Xmx" line: "-Xmx{{ logstash_jvm_xmx }}" @@ -82,7 +41,7 @@ - config - name: Add a configuration - template: + ansible.builtin.template: src: "{{ item }}" dest: /etc/logstash/conf.d/logstash.conf owner: logstash @@ -101,10 +60,10 @@ - logstash - config -- debug: +- ansible.builtin.debug: var: logstash_template verbosity: 1 -- include: logs.yml +- ansible.builtin.include: logs.yml -- include: tmpdir.yml +- ansible.builtin.include: tmpdir.yml diff --git a/logstash/tasks/tmpdir.yml b/logstash/tasks/tmpdir.yml index e41b1205..ab054d34 100644 --- a/logstash/tasks/tmpdir.yml +++ b/logstash/tasks/tmpdir.yml @@ -1,18 +1,19 @@ --- - name: Check if /tmp is noexec - shell: "cat /etc/fstab | grep -E \" +/tmp\" | grep noexec" + ansible.builtin.shell: + cmd: "cat /etc/fstab | grep -E \" +/tmp\" | grep noexec" register: fstab_tmp_noexec failed_when: False changed_when: False check_mode: no - block: - - set_fact: + - ansible.builtin.set_fact: _logstash_custom_tmpdir: "{{ logstash_custom_tmpdir | default(logstash_default_tmpdir, True) | mandatory }}" - name: "Create {{ _logstash_custom_tmpdir }}" - file: + ansible.builtin.file: path: "{{ _logstash_custom_tmpdir }}" owner: logstash group: logstash @@ -22,7 +23,7 @@ - logstash - name: change JVM tmpdir - lineinfile: + ansible.builtin.lineinfile: dest: /etc/logstash/jvm.options line: "-Djava.io.tmpdir={{ _logstash_custom_tmpdir }}" regexp: "^-Djava.io.tmpdir=" diff --git a/logstash/templates/elastic.sources.j2 b/logstash/templates/elastic.sources.j2 new file mode 100644 index 00000000..93df736d --- /dev/null +++ b/logstash/templates/elastic.sources.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +Types: deb +URIs: https://artifacts.elastic.co/packages/{{ elastic_stack_version | mandatory }}/apt +Suites: stable +Components: main +Signed-by: {{ apt_keyring_dir }}/elastic.asc +Enabled: yes \ No newline at end of file diff --git a/lxc-php/defaults/main.yml b/lxc-php/defaults/main.yml index 415d1c9e..17af05cf 100644 --- a/lxc-php/defaults/main.yml +++ b/lxc-php/defaults/main.yml @@ -21,3 +21,15 @@ lxc_php_container_releases: php74: "bullseye" php80: "bullseye" php81: "bullseye" + php82: "bullseye" + +lxc_php_services: + php56: 'php5-fpm.service' + php70: 'php7.0-fpm.service' + php73: 'php7.3-fpm.service' + php74: 'php7.4-fpm.service' + php80: 'php8.0-fpm.service' + php81: 'php8.1-fpm.service' + php82: 'php8.2-fpm.service' + +apt_keyring_dir: "{{ ansible_distribution_major_version is version('12', '<') | ternary('/etc/apt/trusted.gpg.d', '/etc/apt/keyrings') }}" diff --git a/lxc-php/files/pub_evolix.asc b/lxc-php/files/pub_evolix.asc new file mode 100644 index 00000000..4a21bdfe --- /dev/null +++ b/lxc-php/files/pub_evolix.asc @@ -0,0 +1,87 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGOsRdcBEADDPJ8Tsqr5Z4crmQlNQM32hfufe7gTUrXo0cAL8clt92y1QX3N +YyMv0Re4+Ugo7JZd4jsF2Q1twJMxsX5rA12xDnHHcZRSc/E0DIYvPnfLzEHkwseN +OK4f9lI+xo06k+B3KQQKMeI/RjVaN6AiSply9ZGaZVeGGqd4es4PsU1VQMTWdclV +Bn54HBWUnL5dPStPMnNkt0bMQYIqc5733Yby3qMiUKcql2bl9TYBw8SaJXvClsLw +ERqit6FjljUOEeWtB4WZFpjhc/aqcxGcUTPHRrNTlNF0HCvk8JicEu4/lr99pwy7 +7z6SRql++WGMSG06E4MBtUt+wWAmDDHNj3fdZPnoCaDFp7vxy/FEARB2aygTtu11 +mLk4XOKheqU/WibWxoXRzyUCuclJ247Fh+YPxkYVG1dnDwpWGbYuRmzUapGLv4ma +dnKsQN0KhXzUqkSoybBgV208dGOP7BqdY6TVnyU0v/7XDeUqFEwnllRKMSYLilV3 +huTifiCFTK45HACM/x2yckx8dyAuYg6cJaAR1yn1iaTexoyYPG9ZFifvMB6ranEm +vkmQq1e8/7xiNSQsh5F3Ybl5hh4GVLwsR6esfZsHG0Ve+CitsmcZgWnr0JJ2PZOk ++XHxMwo7Gb0/KVH9XGeoXk+eiNNW/kdcgBMkGkU3nWooVHDm7Dy54I5CzQARAQAB +tC9Fdm9saXggUHVibGljIFJlcG9zaXRvcnkgPGVxdWlwZStwdWJAZXZvbGl4LmZy +PokCVAQTAQoAPhYhBP+vfRvzUK1F+rMpCUaPWta4YwY9BQJjrEXXAhsDBQkHhM4A +BQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEEaPWta4YwY9V6oP/iYfZceiA1Sy +x9t/7CL3EReuvpdZtZYf2KklBfxEFtzkERV/KKMMpf8mKoGD6BA+ryUc7b4a8npq +yvKbSKDHGZW6gAbq8hneW71vRuNfPNqtfO98JbJO694nqX9sIYU2xQn0UIh0G6N7 +D2bOcaicn8AgV/8cQZfgN9yRM4VhCoWZwhLqgROUqMYfDn3szamfkPcFiw10ToVt +c2PIFdqj2soKO9OrF5Ct/pztSGy1f+orDFiJ0AtRlqqRk9z18VB893qspfyd6y9N +q7IrQbYsiP+D8DcXYWZA1KURsI4LVQwsudNXokvGkYdnZitVgXI2lIaY7odDou5F +btZsCIEa45m7Vmvu0Wvtu/90EFbu9iwbOVrNpC7lLnfJpDObVXMiY1r0rQVuweEZ +ZbBcv1NUa3R0SPsPLPKf7L6dCx8gCpZjDVJLsgBeeSEV7XFQiYDbl8THasNTKCOa +C6v4h00mg0H6GhZvGMx+lcx8TzW6l3XXRoptHl4vkdE5usLFjy8/JWG3yJ7e2W3D +jVbPQ0UKJAnkGn1t+UJB1GP9O4annks0nPfcomjZzaDweIL8zSLPy5R9DGNgYLjp +5h/baLoNAOkaKssZrusq/P+BM2tdr3i/N6TK+dbrffz3hNgzSFFYVg51DspV7XWo +JKGqhqCgQpkms+NPJiKr4NDs6DdXn0IKuQINBGOsRdcBEAC9i5qcrYLTfeGrWPo3 +Zok3jikNk181HC3HR7Wu8a5whCe/88GgJDY00sU2zZEF9hN/4Vtqq9FICVXUcs+F +5j+Gcb/sqAgwXuwk8LKuhbtR2cnz6I0GCsqNPuj+5uM7MXQlVWeIN5Z6zA/Jw++o +aENZHO6cnuep2KDNPUZzjmTHAa4+qXRL5cRXEOmMB1vtA8mm/43c7wicJ7MrZpba +mqzmiQPsQ2qfmCABfx8BwBgXCVON4sgtzCa+rYOPScsDtv0pv6uG+h/GJp4MdKBp +g3BfShQEAmOwwy3Pt2vo9Rw2s0uJJ9AM2O6tJ3x93YkUP5qj3Etr/eTcgVUiVvSs +h2Rrz2FLen3GMAcqUUDPViCy9nEWRAo7iWQgAKgr8WjeGerOmtsYPyjIQE47eX5M +Gomx0LVCGigYfkSAFIYzm5I+depmn1qTUyizfklvPr0bA/8Cs4zbqx6Pf6Rk5wvb +sJ4envk3dzQRNTH1Vt7Yoktyx1+VX0HFVEaPTQ3JlFORaHYwQQ97LaOZ0VmztE0A +5+CIFFdqp/0H7zGPol+LsPgqnzZZEQ2XFYPOy7/gB17zI2eWNWPAQmOdrUM/v12A +etnLEthZyALcjjBpJEVIHFnuaabYp+mdotycjDkBNSh+P+8H/UsMSrNVhheKQLB8 +smzwFcSrAcnQbtiCjFWANTWyKQARAQABiQI8BBgBCgAmFiEE/699G/NQrUX6sykJ +Ro9a1rhjBj0FAmOsRdcCGwwFCQeEzgAACgkQRo9a1rhjBj0FZw//fNhJdx55ACvX +mpa8wz6eZOvzhr5GWSW5/Qie9nRjInPPI3bJ/jU0S/4ENqFBD9RSvY5F+0xCU67F +V2R3a3FFcB81HLIcUrkN0GH6fLcex0Js+grq/U117e2umdfGMKQG0UFJ+XonhtlT +foBcBjXPFr2NUaJB2SPo/RPQ3U+N3wMSm0ZbB/Xvxi5qMEb971dfObvsXTkQZvn7 +b0TvccfHhyzs2IM8pZO3PamTwA5e16/2QqisRX4CeL0a/q3Yxfw4R8RPCrz/l0k5 +FPdbdXaQuk5s+CiV+Nse7yFGoEoSlLpJM2BpueBsIg92joyOstZRm+tuCb5QefWI +7yFPfJU6xG1CMDqIGjXNU1tzSIoReGUBCNrE9UgzBQPPVD0jNM1WdW6HWSVR7jBb ++dvAeJNzQjJYlvKLQ383mAiVcwmCWBUp+R/kBPlLMGEpLlspti5fkmEc8xvtCaHc +fCLVWd0r2lUFUz+W53r8IXaRcxLtFinz7SHZPrlhaVwErdtlo+5X3kq39Mc4KCmF +bevT+qxlgzHXof+WGTYoc9IHkhDrvZ/TWeAUnBPvVn88dsBRtOC9f5wSCK4r9SfR +Dnf0lAsLWMpNtt812W8sA82RGXRUBwonZKa7YoGNKSa2vPJcUgmpIiHNtoLWpNa+ +7pYGN7bV51zyQ1ERaLU5TBC9sPE70p25Ag0EY6xJaQEQAKsxFCb4Vxe8VuUEAKp/ +RSRNGX/v9KqXVwbnf3kTYq9FMoplZBeqj4LQ22BqRzZ74ywoyfvHHtvkAtCbmrlc +8iLQEmicLug3Ibk97qm1lvvHnK9fqFOWh+Tx/omlaiSzEfAFbLEjNcplmq1ooqmX +fkI9zcefLZHtUFx6Clw3rwp79d/V5XJDM+2jwB47HfIhrW6jEubUuaXIHNR/GSSd +gTYuw55g9K97LhONX6ZvSBhjp4pOeUUbtFuG1fRkjPiObsB54fJ2R32yfm4jV53/ +YgG/Ih/o97tKV+ishQIrr85SB3XiLFlGhQuu/0a/+/vfGVTbJOzrQrE+OCWt9Xm1 +4b91MiVSSzXy6TGzPvpNXYR2PQZzVwvz7UctCikaE4gGB0lSH0LemDD0LZIZUwBL +1G9mlwFTkMYK0+iMyHFOKeAlUnSSpO6hFYr4GHOxAMGTjHqqEJZ3lBi9SBPc7AEK +3NcEp4etuiLOeaSBtqmUs+y7g8yMTrnyWPVxa0l5q4OUitbb2qvWYbaD3O22xYyj +9BlqzpG9uO6/d8HefDK8XMNCHlmwFoJj3HJlHJg7oN029vYsXEwBIhFyolAPzIvB +jpLKcebq9DJSObs1nHjAyVUpL4ZzRmujFcJYDYSixiqaWc/1aGTgUZQ/JDXcODiC +LgFu1vLTRf6hwKSb/vnZP5OtABEBAAGJBHIEGAEKACYWIQT/r30b81CtRfqzKQlG +j1rWuGMGPQUCY6xJaQIbAgUJA8JnAAJACRBGj1rWuGMGPcF0IAQZAQoAHRYhBA7H +BbTwXPF0hLMgRYefxhvnjx3ABQJjrElpAAoJEIefxhvnjx3ANpUQAIFLkLcx2z3M +jV0SgoAYertib9T/OOy/rsfeQjE6DFk6IArrHolZPA9g/PpTPuRwK165n5xw483q +BMyssUT9IK7SZxt0gbKpvZ0HFSCwSp5wdSJZymwB4AOcgRBU5rwC/9fFxYihgIym +Ig7TH9aWW4hDbEuGJDrKbhK+DpIL7lK3A5WUZk9ltGOpCcFctV3YnVgbMIwX5gO6 +lZ5Zi6NHJEB3HauVZJ59NIPJ/f0xe5GMte/LXckyijs9ei4WOFOjstiW64EWkOBH +El0tj+LUxLznCP2szdXjkDN1P6/NDrY1Nid6/ECOfkh4xO/VHhkdSRAlhdP9FHiV +sy3KUUoPH5B805z1MyOI7UYUD/8CK0juIXcbw7isbVUmLf/VV8jEDmq3WWDj8YZp +IStn2AvQeo3VWGWUfkf3v7UthKandIUTIGc5isD+i6KvzzbggyyZWNtvb3/1wMrz +DUKGlFi/IjMhhElJ0oF3YGsBwz2V2UKP7pPIYo+f5zthc7SbmO9yxAQebEOc3prM +G/Br8JOZ90w1dy6CeIYxkM4YEhhG1K8CzD3ZTTI7vh8mwRc92A6HI2NFyxeYJCr0 +IsUcFQpCyXMtcLRN75DGLIjIKdYrYJuwSiUgcH5FtgkuxMYfJEX9UX8rV7HAxUvs +UdIyHLl7k+khGlZa0/W6uCioFNiygnBEp7oP/iSj4Q2Xh5yKI6Jjw/IsfRcsiaac +lHc7uF0caYGMkqRNHiX17d5EtaidTbiqQii1W9slSPXmUuUcKfD1xUfLng7TbZVm +AdEbpHCT+q037cGCYFpHPMvw3OYhhGzYeh3+1oN9t3ZvyGlvAhkrtssDQB+gxX8r +adCpihziFLjm+6IvCLYHEh3gILVFbbhdYDDUduFFjf/snlJW7j8OVc7Cxa7FbPdf +SHLT9VESzf7oiwkP5/ijGmHiEQoJd9EWYkGGz+LZAXemBwe5ZnPPWVZvDEQRMe8v +2V8pa37vyReaK//O8xxGg3NzGTn9otwVr/4Ti9OxrSzmDWpd967oZ42IZSeSY2bz +kOaV8z4C8AIgIA7vWOS83Hncbrgf2nMCXmRjf0KTMm1P7Z0BQDWpxK9lP0nRpVAg +2T3/OjJ9KcAsTz02NFC3/kOUz//NcfDP747HsQB0sltIty140B7CfcWk0a0eKSad +OxGUehskjyKhO6v3dYF+8oR9p98Q8/Rh8r7evYy2mfhgJd7a9Cchn7612Y6k1SLf +nmPGYu3s0lf/k6GoHLfXXQIJDgWeua4ZBr6cgpGONLSvWBeCVaqnk8nhbNIiSBHk +jnrcX8xAtoPLgqg0+yi7rZ3NAauZcQE6UaNB+xjJxDOIpgVLUWtFyAG4MDeIh6GH +oA9QflpnDubMnCve +=ZCml +-----END PGP PUBLIC KEY BLOCK----- diff --git a/lxc-php/handlers/main.yml b/lxc-php/handlers/main.yml index a757a2d0..b703933b 100644 --- a/lxc-php/handlers/main.yml +++ b/lxc-php/handlers/main.yml @@ -1,41 +1,62 @@ --- + +- name: Reload PHP-FPM + community.general.lxc_container: + name: "{{ lxc_php_version }}" + container_command: "systemctl reload {{ lxc_php_services[lxc_php_version] }}" + +- name: Restart PHP-FPM + community.general.lxc_container: + name: "{{ lxc_php_version }}" + container_command: "systemctl restart {{ lxc_php_services[lxc_php_version] }}" + +- name: Reload php82-fpm + community.general.lxc_container: + name: "{{ lxc_php_version }}" + container_command: "systemctl reload php8.2-fpm" + - name: Reload php81-fpm - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" container_command: "systemctl reload php8.1-fpm" - name: Reload php80-fpm - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" container_command: "systemctl reload php8.0-fpm" - name: Reload php74-fpm - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" container_command: "systemctl reload php7.4-fpm" - name: Reload php73-fpm - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" container_command: "systemctl reload php7.3-fpm" - name: Reload php70-fpm - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" container_command: "systemctl reload php7.0-fpm" - name: Reload php56-fpm - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" container_command: "systemctl reload php5-fpm" - name: Restart opensmtpd - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" container_command: "systemctl restart opensmtpd" +- name: Daemon reload + community.general.lxc_container: + name: "{{ lxc_php_version }}" + container_command: "systemctl daemon-reload" + - name: Restart container - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" state: restarted diff --git a/lxc-php/meta/main.yml b/lxc-php/meta/main.yml index 58c2298c..f0f9bb70 100644 --- a/lxc-php/meta/main.yml +++ b/lxc-php/meta/main.yml @@ -27,8 +27,4 @@ galaxy_info: allow_duplicates: yes -dependencies: - - { role: evolix/lxc, - lxc_containers: [ { name: "{{ lxc_php_version }}", release: "{{ lxc_php_container_releases[lxc_php_version] }}" } ], - when: lxc_php_version is defined - } +dependencies: [] diff --git a/lxc-php/tasks/mail_opensmtpd.yml b/lxc-php/tasks/mail_opensmtpd.yml index 25dec9ea..35d0e75b 100644 --- a/lxc-php/tasks/mail_opensmtpd.yml +++ b/lxc-php/tasks/mail_opensmtpd.yml @@ -1,23 +1,23 @@ --- - name: "{{ lxc_php_version }} - Install opensmtpd" - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" container_command: "DEBIAN_FRONTEND=noninteractive apt install --no-install-recommends -y opensmtpd" - name: "{{ lxc_php_version }} - Configure opensmtpd (in the container)" - template: + ansible.builtin.template: src: smtpd.conf.j2 - dest: "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/smtpd.conf" + dest: "{{ lxc_rootfs }}/etc/smtpd.conf" mode: "0644" notify: "Restart opensmtpd" when: lxc_php_container_releases[lxc_php_version] in ["jessie", "stretch", "buster"] - name: "{{ lxc_php_version }} - Configure opensmtpd (in the container)" - template: + ansible.builtin.template: src: smtpd.conf.bullseye.j2 - dest: "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/smtpd.conf" + dest: "{{ lxc_rootfs }}/etc/smtpd.conf" mode: "0644" notify: "Restart opensmtpd" when: not lxc_php_container_releases[lxc_php_version] in ["jessie", "stretch", "buster"] diff --git a/lxc-php/tasks/mail_ssmtp.yml b/lxc-php/tasks/mail_ssmtp.yml index 95055044..b57d5d77 100644 --- a/lxc-php/tasks/mail_ssmtp.yml +++ b/lxc-php/tasks/mail_ssmtp.yml @@ -1,12 +1,12 @@ --- - name: "{{ lxc_php_version }} - Install ssmtp" - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" container_command: "DEBIAN_FRONTEND=noninteractive apt install --no-install-recommends -y ssmtp " - name: "{{ lxc_php_version }} - Configure ssmtp" - template: + ansible.builtin.template: src: ssmtp.conf.j2 - dest: "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/ssmtp/ssmtp.conf" + dest: "{{ lxc_rootfs }}/etc/ssmtp/ssmtp.conf" mode: "0644" diff --git a/lxc-php/tasks/main.yml b/lxc-php/tasks/main.yml index c6d85fbe..035bfe15 100644 --- a/lxc-php/tasks/main.yml +++ b/lxc-php/tasks/main.yml @@ -1,30 +1,47 @@ --- - name: "Ensure that lxc_php_version is defined" - fail: + ansible.builtin.fail: msg: Please configure var lxc_php_version when: lxc_php_version is none -- name: "Update APT cache in container {{lxc_php_version}}" - lxc_container: + +- ansible.builtin.include_role: + name: evolix/lxc + vars: + lxc_containers: + - { name: "{{ lxc_php_version }}", release: "{{ lxc_php_container_releases[lxc_php_version] }}" } + when: lxc_php_version is defined + +- name: set LXC rootfs + ansible.builtin.set_fact: + lxc_rootfs: "/var/lib/lxc/{{ lxc_php_version }}/rootfs" + +- name: "Update APT cache in container {{ lxc_php_version }}" + community.general.lxc_container: name: "{{ lxc_php_version }}" container_command: "apt-get update" -- include: "php56.yml" +- ansible.builtin.import_tasks: "php56.yml" when: lxc_php_version == "php56" -- include: "php70.yml" +- ansible.builtin.import_tasks: "php70.yml" when: lxc_php_version == "php70" -- include: "php73.yml" +- ansible.builtin.import_tasks: "php73.yml" when: lxc_php_version == "php73" -- include: "php74.yml" +- ansible.builtin.import_tasks: "php74.yml" when: lxc_php_version == "php74" -- include: "php80.yml" +- ansible.builtin.import_tasks: "php80.yml" when: lxc_php_version == "php80" -- include: "php81.yml" +- ansible.builtin.import_tasks: "php81.yml" when: lxc_php_version == "php81" -- include: "misc.yml" +- ansible.builtin.import_tasks: "php82.yml" + when: lxc_php_version == "php82" + +- ansible.builtin.import_tasks: "umask.yml" + +- ansible.builtin.import_tasks: "misc.yml" diff --git a/lxc-php/tasks/misc.yml b/lxc-php/tasks/misc.yml index c5aa5245..248aa8e2 100644 --- a/lxc-php/tasks/misc.yml +++ b/lxc-php/tasks/misc.yml @@ -1,30 +1,30 @@ --- - name: "{{ lxc_php_version }} - Configure timezone for the container" - copy: + ansible.builtin.copy: remote_src: yes src: "/etc/timezone" - dest: "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/timezone" + dest: "{{ lxc_rootfs }}/etc/timezone" - name: "{{ lxc_php_version }} - Ensure container's root directory is 755" - file: - path: "/var/lib/lxc/{{ lxc_php_version }}/rootfs" + ansible.builtin.file: + path: "{{ lxc_rootfs }}" state: directory mode: '0755' - name: "{{ lxc_php_version }} - Configure mailname for the container" - copy: + ansible.builtin.copy: content: "{{ evolinux_hostname }}.{{ evolinux_domain }}\n" - dest: "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/mailname" + dest: "{{ lxc_rootfs }}/etc/mailname" notify: "Restart opensmtpd" - name: "{{ lxc_php_version }} - Install misc packages" - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" container_command: "DEBIAN_FRONTEND=noninteractive apt install -y cron logrotate git zip unzip" - name: "{{ lxc_php_version }} - Add MySQL socket to container default mounts" - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" container_config: - "lxc.mount.entry = /run/mysqld {{ php_conf_mysql_socket_dir | replace('/', '', 1) }} none bind,create=dir 0 0" diff --git a/lxc-php/tasks/php56.yml b/lxc-php/tasks/php56.yml index ece7dc8d..d210d80b 100644 --- a/lxc-php/tasks/php56.yml +++ b/lxc-php/tasks/php56.yml @@ -1,20 +1,20 @@ --- - name: "{{ lxc_php_version }} - Install PHP packages" - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php5-fpm php5-cli php5-gd php5-imap php5-ldap php5-mcrypt php5-mysql php5-pgsql php5-sqlite php-gettext php5-intl php5-curl php5-ssh2 libphp-phpmailer" - name: "{{ lxc_php_version }} - Copy evolinux PHP configuration" - template: + ansible.builtin.template: src: z-evolinux-defaults.ini.j2 dest: "{{ line_item }}" mode: "0644" notify: "Reload {{ lxc_php_version }}-fpm" loop: - - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php5/fpm/conf.d/z-evolinux-defaults.ini" - - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php5/cli/conf.d/z-evolinux-defaults.ini" + - "{{ lxc_rootfs }}/etc/php5/fpm/conf.d/z-evolinux-defaults.ini" + - "{{ lxc_rootfs }}/etc/php5/cli/conf.d/z-evolinux-defaults.ini" loop_control: loop_var: line_item -- include: "mail_ssmtp.yml" +- ansible.builtin.include: "mail_ssmtp.yml" diff --git a/lxc-php/tasks/php70.yml b/lxc-php/tasks/php70.yml index 2291b386..52c96883 100644 --- a/lxc-php/tasks/php70.yml +++ b/lxc-php/tasks/php70.yml @@ -1,20 +1,20 @@ --- - name: "{{ lxc_php_version }} - Install PHP packages" - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mcrypt php-mysql php-pgsql php-sqlite3 php-gettext php-curl php-ssh2 php-zip php-mbstring composer libphp-phpmailer" - name: "{{ lxc_php_version }} - Copy evolinux PHP configuration" - template: + ansible.builtin.template: src: z-evolinux-defaults.ini.j2 dest: "{{ line_item }}" mode: "0644" notify: "Reload {{ lxc_php_version }}-fpm" loop: - - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php/7.0/fpm/conf.d/z-evolinux-defaults.ini" - - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php/7.0/cli/conf.d/z-evolinux-defaults.ini" + - "{{ lxc_rootfs }}/etc/php/7.0/fpm/conf.d/z-evolinux-defaults.ini" + - "{{ lxc_rootfs }}/etc/php/7.0/cli/conf.d/z-evolinux-defaults.ini" loop_control: loop_var: line_item -- include: "mail_opensmtpd.yml" +- ansible.builtin.include: "mail_opensmtpd.yml" diff --git a/lxc-php/tasks/php73.yml b/lxc-php/tasks/php73.yml index d7fd7937..ade67b97 100644 --- a/lxc-php/tasks/php73.yml +++ b/lxc-php/tasks/php73.yml @@ -1,20 +1,20 @@ --- - name: "{{ lxc_php_version }} - Install PHP packages" - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mysql php-pgsql php-sqlite3 php-gettext php-curl php-ssh2 php-zip php-mbstring php-zip composer libphp-phpmailer" - name: "{{ lxc_php_version }} - Copy evolinux PHP configuration" - template: + ansible.builtin.template: src: z-evolinux-defaults.ini.j2 dest: "{{ line_item }}" mode: "0644" notify: "Reload {{ lxc_php_version }}-fpm" loop: - - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php/7.3/fpm/conf.d/z-evolinux-defaults.ini" - - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php/7.3/cli/conf.d/z-evolinux-defaults.ini" + - "{{ lxc_rootfs }}/etc/php/7.3/fpm/conf.d/z-evolinux-defaults.ini" + - "{{ lxc_rootfs }}/etc/php/7.3/cli/conf.d/z-evolinux-defaults.ini" loop_control: loop_var: line_item -- include: "mail_opensmtpd.yml" +- ansible.builtin.include: "mail_opensmtpd.yml" diff --git a/lxc-php/tasks/php74.yml b/lxc-php/tasks/php74.yml index eaae77fd..f1dd021a 100644 --- a/lxc-php/tasks/php74.yml +++ b/lxc-php/tasks/php74.yml @@ -1,26 +1,26 @@ --- - name: "{{ lxc_php_version }} - Install PHP packages" - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" - container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mysql php-pgsql php-sqlite3 php-curl php-zip php-mbstring php-zip composer libphp-phpmailer" + container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mysql php-pgsql php-sqlite3 php-curl php-zip php-mbstring php-xml php-zip composer libphp-phpmailer" - name: "{{ lxc_php_version }} - fix bullseye repository" - replace: - dest: "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/apt/sources.list" + ansible.builtin.replace: + dest: "{{ lxc_rootfs }}/etc/apt/sources.list" regexp: 'bullseye/updates' replace: 'bullseye-security' - name: "{{ lxc_php_version }} - Copy evolinux PHP configuration" - template: + ansible.builtin.template: src: z-evolinux-defaults.ini.j2 dest: "{{ line_item }}" mode: "0644" notify: "Reload {{ lxc_php_version }}-fpm" loop: - - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php/7.4/fpm/conf.d/z-evolinux-defaults.ini" - - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php/7.4/cli/conf.d/z-evolinux-defaults.ini" + - "{{ lxc_rootfs }}/etc/php/7.4/fpm/conf.d/z-evolinux-defaults.ini" + - "{{ lxc_rootfs }}/etc/php/7.4/cli/conf.d/z-evolinux-defaults.ini" loop_control: loop_var: line_item -- include: "mail_opensmtpd.yml" +- ansible.builtin.include: "mail_opensmtpd.yml" diff --git a/lxc-php/tasks/php80.yml b/lxc-php/tasks/php80.yml index 4f725f0b..043c0174 100644 --- a/lxc-php/tasks/php80.yml +++ b/lxc-php/tasks/php80.yml @@ -1,63 +1,68 @@ --- +- name: set APT keyring + ansible.builtin.set_fact: + lxc_apt_keyring_dir: /etc/apt/trusted.gpg.d + + - name: "{{ lxc_php_version }} - Install dependency packages" - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" - container_command: "DEBIAN_FRONTEND=noninteractive apt install -y wget apt-transport-https gnupg" + container_command: "DEBIAN_FRONTEND=noninteractive apt install -y wget gnupg" - name: "{{ lxc_php_version }} - fix bullseye repository" - replace: - dest: "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/apt/sources.list" + ansible.builtin.replace: + dest: "{{ lxc_rootfs }}/etc/apt/sources.list" regexp: 'bullseye/updates' replace: 'bullseye-security' - name: "{{ lxc_php_version }} - Add sury repo" - lineinfile: - dest: "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/apt/sources.list.d/sury.list" + ansible.builtin.lineinfile: + dest: "{{ lxc_rootfs }}/etc/apt/sources.list.d/sury.list" line: "{{ item }}" state: present create: yes mode: "0644" loop: - - "deb https://packages.sury.org/php/ bullseye main" - - "deb http://pub.evolix.net/ bullseye-php80/" + - "deb [signed-by={{ lxc_apt_keyring_dir }}/sury.gpg] https://packages.sury.org/php/ bullseye main" + - "deb [signed-by={{ lxc_apt_keyring_dir }}/pub_evolix.asc] http://pub.evolix.org/evolix bullseye-php80 main" - name: copy pub.evolix.net GPG key - copy: - src: reg.asc - dest: /var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/apt/trusted.gpg.d/reg.asc + ansible.builtin.copy: + src: pub_evolix.asc + dest: "{{ lxc_rootfs }}{{ lxc_apt_keyring_dir }}/pub_evolix.asc" mode: "0644" owner: root group: root - name: copy packages.sury.org GPG Key - copy: + ansible.builtin.copy: src: sury.gpg - dest: /var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/apt/trusted.gpg.d/sury.gpg + dest: "{{ lxc_rootfs }}{{ lxc_apt_keyring_dir }}/sury.gpg" mode: "0644" owner: root group: root - name: "{{ lxc_php_version }} - Update APT cache" - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" container_command: "DEBIAN_FRONTEND=noninteractive apt update" - name: "{{ lxc_php_version }} - Install PHP packages" - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" - container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mysql php-pgsql php-sqlite3 php-curl php-zip php-mbstring php-zip composer libphp-phpmailer" + container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mysql php-pgsql php-sqlite3 php-curl php-zip php-mbstring php-xml php-zip composer libphp-phpmailer" - name: "{{ lxc_php_version }} - Copy evolinux PHP configuration" - template: + ansible.builtin.template: src: z-evolinux-defaults.ini.j2 dest: "{{ line_item }}" mode: "0644" notify: "Reload {{ lxc_php_version }}-fpm" loop: - - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php/8.0/fpm/conf.d/z-evolinux-defaults.ini" - - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php/8.0/cli/conf.d/z-evolinux-defaults.ini" + - "{{ lxc_rootfs }}/etc/php/8.0/fpm/conf.d/z-evolinux-defaults.ini" + - "{{ lxc_rootfs }}/etc/php/8.0/cli/conf.d/z-evolinux-defaults.ini" loop_control: loop_var: line_item -- include: "mail_opensmtpd.yml" +- ansible.builtin.include: "mail_opensmtpd.yml" diff --git a/lxc-php/tasks/php81.yml b/lxc-php/tasks/php81.yml index f4498dd2..a1e9c71b 100644 --- a/lxc-php/tasks/php81.yml +++ b/lxc-php/tasks/php81.yml @@ -1,63 +1,67 @@ --- +- name: set APT keyring + ansible.builtin.set_fact: + lxc_apt_keyring_dir: /etc/apt/trusted.gpg.d + - name: "{{ lxc_php_version }} - Install dependency packages" - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" - container_command: "DEBIAN_FRONTEND=noninteractive apt install -y wget apt-transport-https gnupg" + container_command: "DEBIAN_FRONTEND=noninteractive apt install -y wget gnupg" - name: "{{ lxc_php_version }} - fix bullseye repository" - replace: - dest: "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/apt/sources.list" + ansible.builtin.replace: + dest: "{{ lxc_rootfs }}/etc/apt/sources.list" regexp: 'bullseye/updates' replace: 'bullseye-security' - name: "{{ lxc_php_version }} - Add sury repo" - lineinfile: - dest: "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/apt/sources.list.d/sury.list" + ansible.builtin.lineinfile: + dest: "{{ lxc_rootfs }}/etc/apt/sources.list.d/sury.list" line: "{{ item }}" state: present create: yes mode: "0644" loop: - - "deb https://packages.sury.org/php/ bullseye main" - - "deb http://pub.evolix.net/ bullseye-php81/" + - "deb [signed-by={{ lxc_apt_keyring_dir }}/sury.gpg] https://packages.sury.org/php/ bullseye main" + - "deb [signed-by={{ lxc_apt_keyring_dir }}/pub_evolix.asc] http://pub.evolix.org/evolix bullseye-php81 main" - name: copy pub.evolix.net GPG key - copy: - src: reg.asc - dest: /var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/apt/trusted.gpg.d/reg.asc + ansible.builtin.copy: + src: pub_evolix.asc + dest: "{{ lxc_rootfs }}{{ lxc_apt_keyring_dir }}/pub_evolix.asc" mode: "0644" owner: root group: root - name: copy packages.sury.org GPG Key - copy: + ansible.builtin.copy: src: sury.gpg - dest: /var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/apt/trusted.gpg.d/sury.gpg + dest: "{{ lxc_rootfs }}{{ lxc_apt_keyring_dir }}/sury.gpg" mode: "0644" owner: root group: root - name: "{{ lxc_php_version }} - Update APT cache" - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" container_command: "DEBIAN_FRONTEND=noninteractive apt update" - name: "{{ lxc_php_version }} - Install PHP packages" - lxc_container: + community.general.lxc_container: name: "{{ lxc_php_version }}" - container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mysql php-pgsql php-sqlite3 php-curl php-zip php-mbstring php-zip composer libphp-phpmailer" + container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mysql php-pgsql php-sqlite3 php-curl php-zip php-mbstring php-xml php-zip composer libphp-phpmailer" - name: "{{ lxc_php_version }} - Copy evolinux PHP configuration" - template: + ansible.builtin.template: src: z-evolinux-defaults.ini.j2 dest: "{{ line_item }}" mode: "0644" notify: "Reload {{ lxc_php_version }}-fpm" loop: - - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php/8.1/fpm/conf.d/z-evolinux-defaults.ini" - - "/var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/php/8.1/cli/conf.d/z-evolinux-defaults.ini" + - "{{ lxc_rootfs }}/etc/php/8.1/fpm/conf.d/z-evolinux-defaults.ini" + - "{{ lxc_rootfs }}/etc/php/8.1/cli/conf.d/z-evolinux-defaults.ini" loop_control: loop_var: line_item -- include: "mail_opensmtpd.yml" +- ansible.builtin.include: "mail_opensmtpd.yml" diff --git a/lxc-php/tasks/php82.yml b/lxc-php/tasks/php82.yml new file mode 100644 index 00000000..1fb81851 --- /dev/null +++ b/lxc-php/tasks/php82.yml @@ -0,0 +1,67 @@ +--- + +- name: set APT keyring + ansible.builtin.set_fact: + lxc_apt_keyring_dir: /etc/apt/trusted.gpg.d + +- name: "{{ lxc_php_version }} - Install dependency packages" + community.general.lxc_container: + name: "{{ lxc_php_version }}" + container_command: "DEBIAN_FRONTEND=noninteractive apt install -y wget apt-transport-https gnupg" + +- name: "{{ lxc_php_version }} - fix bullseye repository" + ansible.builtin.replace: + dest: "{{ lxc_rootfs }}/etc/apt/sources.list" + regexp: 'bullseye/updates' + replace: 'bullseye-security' + +- name: "{{ lxc_php_version }} - Add sury repo" + ansible.builtin.lineinfile: + dest: "{{ lxc_rootfs }}/etc/apt/sources.list.d/sury.list" + line: "{{ item }}" + state: present + create: yes + mode: "0644" + loop: + - "deb [signed-by={{ apt_keyring_dir }}/sury.gpg] https://packages.sury.org/php/ bullseye main" + - "deb [signed-by={{ apt_keyring_dir }}/pub_evolix.asc] http://pub.evolix.org/evolix bullseye-php82 main" + +- name: copy pub.evolix.net GPG key + ansible.builtin.copy: + src: pub_evolix.asc + dest: /var/lib/lxc/{{ lxc_php_version }}/rootfs{{ apt_keyring_dir }}/pub_evolix.asc + mode: "0644" + owner: root + group: root + +- name: copy packages.sury.org GPG Key + ansible.builtin.copy: + src: sury.gpg + dest: /var/lib/lxc/{{ lxc_php_version }}/rootfs{{ apt_keyring_dir }}/sury.gpg + mode: "0644" + owner: root + group: root + +- name: "{{ lxc_php_version }} - Update APT cache" + community.general.lxc_container: + name: "{{ lxc_php_version }}" + container_command: "DEBIAN_FRONTEND=noninteractive apt update" + +- name: "{{ lxc_php_version }} - Install PHP packages" + community.general.lxc_container: + name: "{{ lxc_php_version }}" + container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mysql php-pgsql php-sqlite3 php-curl php-zip php-mbstring php-xml php-zip composer libphp-phpmailer" + +- name: "{{ lxc_php_version }} - Copy evolinux PHP configuration" + ansible.builtin.template: + src: z-evolinux-defaults.ini.j2 + dest: "{{ line_item }}" + mode: "0644" + notify: "Reload {{ lxc_php_version }}-fpm" + loop: + - "{{ lxc_rootfs }}/etc/php/8.2/fpm/conf.d/z-evolinux-defaults.ini" + - "{{ lxc_rootfs }}/etc/php/8.2/cli/conf.d/z-evolinux-defaults.ini" + loop_control: + loop_var: line_item + +- ansible.builtin.include: "mail_opensmtpd.yml" diff --git a/lxc-php/tasks/umask.yml b/lxc-php/tasks/umask.yml new file mode 100644 index 00000000..254fd75e --- /dev/null +++ b/lxc-php/tasks/umask.yml @@ -0,0 +1,28 @@ +# Ajoute UMask=0007 à l'unité systemd PHP-FPM du conteneur LXC +# dans /etc/systemd/system/phpX.X-fpm.service.d/evolinux.conf +--- + +- name: "Crée des répertoires (si absents) pour surcharger la config des services PHP dans les conteneurs LXC." + ansible.builtin.file: + path: "{{ lxc_rootfs }}/etc/systemd/system/{{ lxc_php_services[lxc_php_version] }}.d" + state: directory + register: systemd_path + +- name: "[Service] est présent dans la surchage des services PHP-FPM des conteneurs LXC." + ansible.builtin.lineinfile: + path: "{{ systemd_path.path }}/evolinux.conf" + regex: "\\[Service\\]" + line: "[Service]" + create: yes + +- name: "UMask=0007 est présent dans la surchage des services PHP-FPM des conteneurs LXC." + ansible.builtin.lineinfile: + path: "{{ systemd_path.path }}/evolinux.conf" + regex: "^UMask=" + line: "UMask=0007" + insertafter: "\\[Service\\]" + when: not ansible_check_mode + notify: + - "Daemon reload" + - "Restart PHP-FPM" + diff --git a/lxc-php/templates/mailname.j2 b/lxc-php/templates/mailname.j2 index e374dd45..ff7139b8 100644 --- a/lxc-php/templates/mailname.j2 +++ b/lxc-php/templates/mailname.j2 @@ -1 +1 @@ -{{ansible_fqdn}} +{{ ansible_fqdn }} diff --git a/lxc-solr/defaults/main.yml b/lxc-solr/defaults/main.yml index ad2a3e23..18144daa 100644 --- a/lxc-solr/defaults/main.yml +++ b/lxc-solr/defaults/main.yml @@ -14,5 +14,9 @@ # release: stretch # solr_version: 8.4.1 # solr_port: 8985 +# - name: solr9 +# release: bullseye +# solr_version: 9.0.0 +# solr_port: 8985 lxc_containers: [] diff --git a/lxc-solr/tasks/main.yml b/lxc-solr/tasks/main.yml index d629bbf6..fdfd1208 100644 --- a/lxc-solr/tasks/main.yml +++ b/lxc-solr/tasks/main.yml @@ -1,14 +1,18 @@ --- - name: LXC configuration - include_role: + ansible.builtin.include_role: name: evolix/lxc - name: Ensure containers root directory is 755 - file: + ansible.builtin.file: path: "/var/lib/lxc/{{ item.name }}/rootfs" state: directory mode: '0755' loop: "{{ lxc_containers }}" -- include: "solr.yml name={{item.name}} solr_version={{item.solr_version}} solr_port={{item.solr_port}}" +- ansible.builtin.include: solr.yml + args: + name: "{{ item.name }}" + solr_version: "{{ item.solr_version }}" + solr_port: "{{ item.solr_port }}" loop: "{{ lxc_containers }}" diff --git a/lxc-solr/tasks/solr.yml b/lxc-solr/tasks/solr.yml index 4cf521ae..7eafb696 100644 --- a/lxc-solr/tasks/solr.yml +++ b/lxc-solr/tasks/solr.yml @@ -1,42 +1,44 @@ --- -- name: Install openjdk-8-jre-headless and lsof packages - command: "lxc-attach -n {{name}} -- apt-get install -y openjdk-8-jre-headless lsof" + +- name: "Set values for Solr < 9.0.0" + ansible.builtin.set_fact: + tarball_url: https://archive.apache.org/dist/lucene/solr/{{ solr_version }}/solr-{{ solr_version }}.tgz + tarball_path: /var/lib/lxc/{{ name }}/rootfs/root/solr-{{ solr_version }}.tgz + start_command: "/etc/init.d/solr start" + stop_command: "/etc/init.d/solr stop" + when: "solr_version is version('9.0.0', '<')" + +- name: "Set values for Solr >= 9.0.0" + ansible.builtin.set_fact: + tarball_url: https://archive.apache.org/dist/solr/solr/{{ solr_version }}/solr-{{ solr_version }}.tgz + tarball_path: /var/lib/lxc/{{ name }}/rootfs/root/solr-{{ solr_version }}.tgz + start_command: "systemctl start solr" + stop_command: "systemctl stop solr" + when: "solr_version is version('9.0.0', '>=')" + +- name: Install java and lsof packages + ansible.builtin.command: + cmd: "lxc-attach -n {{ name }} -- apt-get install -y default-jre-headless lsof" - name: "Download Solr {{ solr_version }}" - get_url: - url: "https://archive.apache.org/dist/lucene/solr/{{ solr_version }}/solr-{{ solr_version }}.tgz" - dest: "/var/lib/lxc/{{ name }}/rootfs/root/solr-{{ solr_version }}.tgz" + ansible.builtin.get_url: + url: "{{ tarball_url }}" + dest: "{{ tarball_path }}" mode: '0644' - name: "Extract solr-{{ solr_version }}.tgz" - unarchive: - src: /var/lib/lxc/{{ name }}/rootfs/root/solr-{{ solr_version }}.tgz + ansible.builtin.unarchive: + src: "{{ tarball_path }}" dest: /var/lib/lxc/{{ name }}/rootfs/root/ remote_src: yes -- name: "Install Solr {{ solr_version }}" - command: "lxc-attach -n {{name}} -- /root/solr-{{ solr_version }}/bin/install_solr_service.sh /root/solr-{{ solr_version }}.tgz" - -- name: "Stop Solr" - command: "lxc-attach -n {{name}} -- /etc/init.d/solr stop" - ignore_errors: True - - name: "Make sure /home/solr exists" - file: - path: /home/solr + ansible.builtin.file: + path: /home/solr/{{ name }} + recurse: yes state: directory mode: '0755' -- name: "Move Solr data directory to /home/solr/{{name}}" - command: "lxc-attach -n {{name}} -- mv /var/solr /home/solr/{{name}}" - -- name: "Create a symbolic link to /home/solr/{{name}}" - command: "lxc-attach -n {{name}} -- ln -s /home/solr/{{name}} /var/solr" - -- name: "Set Solr port to {{ solr_port }}" - lineinfile: - dest: /var/lib/lxc/{{ name }}/rootfs/etc/default/solr.in.sh - line: "SOLR_PORT={{ solr_port }}" - -- name: "Start Solr" - command: "lxc-attach -n {{name}} -- /etc/init.d/solr start" +- name: "Install Solr {{ solr_version }}" + ansible.builtin.command: + cmd: "lxc-attach -n {{ name }} -- /root/solr-{{ solr_version }}/bin/install_solr_service.sh /root/solr-{{ solr_version }}.tgz -d /home/solr/{{ name }} -p {{ solr_port }}" diff --git a/lxc/defaults/main.yml b/lxc/defaults/main.yml index e7e1c1ff..d17e78a0 100644 --- a/lxc/defaults/main.yml +++ b/lxc/defaults/main.yml @@ -15,4 +15,6 @@ lxc_mount_part: "/home" # release: jessie # - name: php70 # release: stretch +# - name: php81 +# release: bullseye lxc_containers: [] diff --git a/lxc/tasks/create-container.yml b/lxc/tasks/create-container.yml index ad4f35d6..3b70cdde 100644 --- a/lxc/tasks/create-container.yml +++ b/lxc/tasks/create-container.yml @@ -1,12 +1,13 @@ --- - name: "Check if container {{ name }} exists" - command: "lxc-ls {{ name }}" - changed_when: false + ansible.builtin.command: + cmd: "lxc-ls {{ name }}" + changed_when: False check_mode: no register: container_exists - name: "Create container {{ name }}" - lxc_container: + community.general.lxc_container: name: "{{ name }}" container_log: true template: debian @@ -15,44 +16,56 @@ when: container_exists.stdout_lines | length == 0 - name: "Disable network configuration inside container {{ name }}" - replace: + ansible.builtin.replace: name: "/var/lib/lxc/{{ name }}/rootfs/etc/default/networking" regexp: "^#CONFIGURE_INTERFACES=yes" replace: CONFIGURE_INTERFACES=no when: lxc_network_type == "none" - name: "Disable interface shut down on halt inside container {{ name }} (Jessie container)" - lineinfile: + ansible.builtin.lineinfile: name: "/var/lib/lxc/{{ name }}/rootfs/etc/default/halt" line: "NETDOWN=no" when: lxc_network_type == "none" and release == "jessie" - name: "Make the container {{ name }} poweroff on SIGPWR sent by lxc-stop (Jessie container)" - file: + ansible.builtin.file: src: /lib/systemd/system/poweroff.target dest: "/var/lib/lxc/{{ name }}/rootfs/etc/systemd/system/sigpwr.target" state: link when: release == 'jessie' - name: "Configure the DNS resolvers in the container {{ name }}" - copy: + ansible.builtin.copy: remote_src: yes src: /etc/resolv.conf dest: "/var/lib/lxc/{{ name }}/rootfs/etc/" - name: "Add hostname in /etc/hosts for container {{ name }}" - lineinfile: + ansible.builtin.lineinfile: name: "/var/lib/lxc/{{ name }}/rootfs/etc/hosts" line: "127.0.0.1 {{ name }}" - name: "Fix permission on /dev for container {{ name }}" - lineinfile: + ansible.builtin.lineinfile: name: "/var/lib/lxc/{{ name }}/rootfs/etc/rc.local" line: "chmod 755 /dev" insertbefore: "^exit 0$" when: release == 'jessie' - name: "Ensure that {{ name }} container is running" - lxc_container: + community.general.lxc_container: name: "{{ name }}" state: started + +- name: "Ensure /etc/profile.d exists in container" + ansible.builtin.file: + path: "/var/lib/lxc/{{ name }}/rootfs/etc/profile.d" + mode: '0755' + state: directory + +- name: "Copy host /etc/profile.d/evolinux into container" + ansible.builtin.copy: + src: "/etc/profile.d/evolinux.sh" + remote_src: true + dest: "/var/lib/lxc/{{ name }}/rootfs/etc/profile.d/evolinux.sh" diff --git a/lxc/tasks/main.yml b/lxc/tasks/main.yml index 70f5dc2b..d0f9f144 100644 --- a/lxc/tasks/main.yml +++ b/lxc/tasks/main.yml @@ -1,56 +1,67 @@ --- - name: Install lxc tools - apt: + ansible.builtin.apt: name: - lxc - debootstrap - xz-utils - name: python-lxc is installed (Debian <= 10) - apt: + ansible.builtin.apt: name: python-lxc state: present when: ansible_python_version is version('3', '<') - name: python3-lxc is installed (Debian >= 10) - apt: + ansible.builtin.apt: name: python3-lxc state: present when: ansible_python_version is version('3', '>=') - name: Install additional packages (Debian >= 10) - apt: + ansible.builtin.apt: name: - apparmor - lxc-templates when: ansible_distribution_major_version is version('10', '>=') - name: Copy LXC default containers configuration - template: + ansible.builtin.template: src: default.conf dest: /etc/lxc/ - name: Check if root has subuids - command: grep '^root:100000:10000$' /etc/subuid - failed_when: false - changed_when: false + ansible.builtin.command: + cmd: grep '^root:100000:10000$' /etc/subuid + failed_when: False + changed_when: False register: root_subuids when: lxc_unprivilegied_containers | bool - name: Add subuid and subgid ranges to root - command: usermod -v 100000-199999 -w 100000-109999 root + ansible.builtin.command: + cmd: usermod -v 100000-199999 -w 100000-109999 root when: - lxc_unprivilegied_containers | bool - root_subuids.rc != 0 -- name: Check if /var has not mount options or nosuid or nodev or noexec - shell: findmnt | grep -E "/var[^/]" | grep -e nodev -e noexec -e nosuid - register: check_var - changed_when: false - failed_when: "check_var.rc == 0" +- name: Get filesystem options + ansible.builtin.command: + cmd: findmnt --noheadings --target /var/lib/lxc --output OPTIONS + changed_when: False + check_mode: no + register: check_fs_options + +- name: Check if options are correct + ansible.builtin.assert: + that: + - "'nodev' not in check_fs_options.stdout" + - "'noexec' not in check_fs_options.stdout" + - "'nosuid' not in check_fs_options.stdout" + msg: "LXC directory is in a filesystem with incompatible options" - name: Create containers - include: create-container.yml + ansible.builtin.include: create-container.yml vars: name: "{{ item.name }}" release: "{{ item.release }}" diff --git a/memcached/files/check_memcached_instances.sh b/memcached/files/check_memcached_instances.sh new file mode 100644 index 00000000..e97352f7 --- /dev/null +++ b/memcached/files/check_memcached_instances.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# {{ ansible_managed }} + +set -u + +return=0 +nb_crit=0 +nb_warn=0 +nb_ok=0 +nb_unchk=0 +output="" + +vendored_check=/usr/local/lib/nagios/plugins/check_memcached.pl + +if [ -x $vendored_check ]; then + check_bin=$vendored_check +else + echo "UNCHK - can't find check_memcached" + exit 3 +fi + +check_server() { + name=$1 + conf_file=$2 + + host=$(config_var "-l" "${conf_file}") + port=$(config_var "-p" "${conf_file}") + + cmd="${check_bin} -H ${host} -p ${port}" + + result=$($cmd) + ret="${?}" + if [ "${ret}" -ge 2 ]; then + nb_crit=$((nb_crit + 1)) + printf -v output "%s%s\n" "${output}" "${result}" + [ "${return}" -le 2 ] && return=2 + elif [ "${ret}" -ge 1 ]; then + nb_warn=$((nb_warn + 1)) + printf -v output "%s%s\n" "${output}" "${result}" + [ "${return}" -le 1 ] && return=1 + else + nb_ok=$((nb_ok + 1)) + printf -v output "%s%s\n" "${output}" "${result}" + [ "${return}" -le 0 ] && return=0 + fi +} +config_var() { + variable=$1 + file=$2 + test -f "${file}" && grep -E "^${variable}\s+.+$" "${file}" | awk '{ print $2 }' | sed -e "s/^[\"']//" -e "s/[\"']$//" +} + +# default instance +if systemctl is-enabled -q memcached; then + check_server "default" "/etc/memcached.conf" +fi + +# additional instances +conf_files=$(ls -1 /etc/memcached_*.conf 2> /dev/null) +for conf_file in ${conf_files}; do + name=$(basename "${conf_file}" | sed '{s|memcached_||;s|\.conf||}') + if systemctl is-enabled -q "memcached@${name}.service"; then + check_server "${name}" "${conf_file}" + else + nb_unchk=$((nb_unchk + 1)) + output="${output}UNCHK - ${name} (unit is disabled or missing)\n" + fi +done + +[ "${return}" -ge 0 ] && header="OK" +[ "${return}" -ge 1 ] && header="WARNING" +[ "${return}" -ge 2 ] && header="CRITICAL" + +printf "%s - %s UNCHK / %s CRIT / %s WARN / %s OK\n\n" "${header}" "${nb_unchk}" "${nb_crit}" "${nb_warn}" "${nb_ok}" + +printf "%s" "${output}" | grep -E "CRITICAL" +printf "%s" "${output}" | grep -E "WARNING" +printf "%s" "${output}" | grep -E "OK" +printf "%s" "${output}" | grep -E "UNCHK" + +exit "${return}" diff --git a/memcached/handlers/main.yml b/memcached/handlers/main.yml index 136c39d7..20dbe61e 100644 --- a/memcached/handlers/main.yml +++ b/memcached/handlers/main.yml @@ -1,15 +1,15 @@ --- - name: restart memcached - service: + ansible.builtin.service: name: memcached state: restarted - name: restart munin-node - service: + ansible.builtin.service: name: munin-node state: restarted - name: restart nagios-nrpe-server - service: + ansible.builtin.service: name: nagios-nrpe-server state: restarted diff --git a/memcached/tasks/instance-default.yml b/memcached/tasks/instance-default.yml new file mode 100644 index 00000000..8a0630a4 --- /dev/null +++ b/memcached/tasks/instance-default.yml @@ -0,0 +1,17 @@ + +- name: Memcached is configured. + ansible.builtin.template: + src: memcached.conf.j2 + dest: /etc/memcached.conf + mode: "0644" + notify: restart memcached + tags: + - memcached + +- name: Memcached is running and enabled on boot. + ansible.builtin.service: + name: memcached + enabled: yes + state: started + tags: + - memcached diff --git a/memcached/tasks/instance-multi.yml b/memcached/tasks/instance-multi.yml new file mode 100644 index 00000000..873b0b15 --- /dev/null +++ b/memcached/tasks/instance-multi.yml @@ -0,0 +1,41 @@ +--- + +- name: Add systemd unit template + ansible.builtin.copy: + src: memcached@.service + dest: /etc/systemd/system/memcached@.service + tags: + - memcached + +- name: Disable default memcached systemd unit + ansible.builtin.systemd: + name: memcached + enabled: false + state: stopped + tags: + - memcached + +- name: Make sure memcached.conf is absent + ansible.builtin.file: + path: /etc/memcached.conf + state: absent + tags: + - memcached + +- name: "Create a configuration file for instance ({{ memcached_instance_name }})" + ansible.builtin.template: + src: memcached.conf.j2 + dest: /etc/memcached_{{ memcached_instance_name }}.conf + mode: "0644" + tags: + - memcached + +- name: "Enable and start the memcached instance ({{ memcached_instance_name }})" + ansible.builtin.systemd: + name: memcached@{{ memcached_instance_name }} + enabled: yes + state: started + daemon_reload: yes + masked: no + tags: + - memcached diff --git a/memcached/tasks/main.yml b/memcached/tasks/main.yml index 0159f8d6..96060d4a 100644 --- a/memcached/tasks/main.yml +++ b/memcached/tasks/main.yml @@ -1,74 +1,16 @@ -- name: ensure packages are installed - apt: +- name: Ensure memcached is installed + ansible.builtin.apt: name: memcached state: present tags: - memcached -- name: Memcached is configured. - template: - src: memcached.conf.j2 - dest: /etc/memcached.conf - mode: "0644" - notify: restart memcached - tags: - - memcached - when: memcached_instance_name | length == 0 +- ansible.builtin.include: instance-default.yml + when: memcached_instance_name is undefined -- name: Memcached is running and enabled on boot. - service: - name: memcached - enabled: yes - state: started - tags: - - memcached - when: memcached_instance_name | length == 0 +- ansible.builtin.include: instance-multi.yml + when: memcached_instance_name is defined -- name: Add systemd template - copy: - src: memcached@.service - dest: /etc/systemd/system/memcached@.service - tags: - - memcached - when: memcached_instance_name | length > 0 +- ansible.builtin.include: munin.yml -- name: Delete default memcached systemd configuration file - systemd: - name: memcached - enabled: false - state: stopped - tags: - - memcached - when: memcached_instance_name | length > 0 - -- name: Make sure memcached.conf is absent - file: - path: /etc/memcached.conf - state: absent - tags: - - memcached - when: memcached_instance_name | length > 0 - -- name: Create a configuration file - template: - src: memcached.conf.j2 - dest: /etc/memcached_{{ memcached_instance_name }}.conf - mode: "0644" - tags: - - memcached - when: memcached_instance_name | length > 0 - -- name: Enable and start the memcached instance - systemd: - name: memcached@{{ memcached_instance_name }} - enabled: yes - state: started - daemon_reload: yes - masked: no - tags: - - memcached - when: memcached_instance_name | length > 0 - -- include: munin.yml - -- include: nrpe.yml +- ansible.builtin.include: nrpe.yml diff --git a/memcached/tasks/munin.yml b/memcached/tasks/munin.yml index 6e2f6d6f..b25b9275 100644 --- a/memcached/tasks/munin.yml +++ b/memcached/tasks/munin.yml @@ -1,11 +1,11 @@ --- - name: Choose packages (Oracle) - set_fact: + ansible.builtin.set_fact: multi: "multi_" - when: memcached_instance_name | length > 0 + when: memcached_instance_name is defined - name: is Munin present ? - stat: + ansible.builtin.stat: path: /etc/munin/plugin-conf.d/munin-node check_mode: no register: munin_node_plugins_config @@ -15,14 +15,14 @@ - block: - name: Install munin-plugins-extra and libcache-memcached-perl for Munin - apt: + ansible.builtin.apt: name: - 'munin-plugins-extra' - 'libcache-memcached-perl' state: present - name: Enable core Munin plugins - file: + ansible.builtin.file: src: '/usr/share/munin/plugins/memcached_' dest: /etc/munin/plugins/{{ multi }}{{ item }} state: link diff --git a/memcached/tasks/nrpe.yml b/memcached/tasks/nrpe.yml index 21070aec..aba43da6 100644 --- a/memcached/tasks/nrpe.yml +++ b/memcached/tasks/nrpe.yml @@ -1,30 +1,49 @@ --- -- include_role: - name: evolix/remount-usr - name: Is nrpe present ? - stat: + ansible.builtin.stat: path: /etc/nagios/nrpe.d/evolix.cfg register: nrpe_evolix_config - block: - name: Install dependencies - apt: - name: libcache-memcached-perl + ansible.builtin.apt: + name: + - libcache-memcached-perl + - libmemcached11 + + - ansible.builtin.include_role: + name: evolix/remount-usr - name: Copy Nagios check for memcached - copy: + ansible.builtin.copy: src: check_memcached.pl dest: /usr/local/lib/nagios/plugins/ mode: "0755" - # TODO: install a "multi-instances" check if the memcached_instance_name variable is not null + - name: install check_memcached_instances + ansible.builtin.copy: + src: check_memcached_instances.sh + dest: /usr/local/lib/nagios/plugins/check_memcached_instances + force: true + mode: "0755" + owner: root + group: root - - name: Add NRPE check - lineinfile: + - name: Add NRPE check (single instance) + ansible.builtin.lineinfile: name: /etc/nagios/nrpe.d/evolix.cfg regexp: '^command\[check_memcached\]=' line: 'command[check_memcached]=/usr/local/lib/nagios/plugins/check_memcached.pl -H 127.0.0.1 -p {{ memcached_port }}' notify: restart nagios-nrpe-server + when: memcached_instance_name is undefined + + - name: Add NRPE check (multi instance) + ansible.builtin.lineinfile: + name: /etc/nagios/nrpe.d/evolix.cfg + regexp: '^command\[check_memcached\]=' + line: 'command[check_memcached]=/usr/local/lib/nagios/plugins/check_memcached_instances' + notify: restart nagios-nrpe-server + when: memcached_instance_name is defined when: nrpe_evolix_config.stat.exists diff --git a/memcached/tasks/phpmemcachedadmin.yml b/memcached/tasks/phpmemcachedadmin.yml index 0a8e4417..1e49ae9e 100644 --- a/memcached/tasks/phpmemcachedadmin.yml +++ b/memcached/tasks/phpmemcachedadmin.yml @@ -1,6 +1,6 @@ --- - name: Create phpMemcachedAdmin root dir - file: + ansible.builtin.file: path: /var/www/phpmemcachedadmin/ state: directory mode: "0755" @@ -8,7 +8,7 @@ - memcached - name: Install phpMemcachedAdmin - unarchive: + ansible.builtin.unarchive: src: 'https://github.com/elijaa/phpmemcachedadmin/archive/1.3.0.tar.gz' dest: /var/www/phpmemcachedadmin/ remote_src: True @@ -18,7 +18,7 @@ - memcached - name: Copy phpMemcachedAdmin config - template: + ansible.builtin.template: src: Memcache.php.j2 dest: /var/www/phpmemcachedadmin/Config/Memcache.php mode: "0755" diff --git a/metricbeat/defaults/main.yml b/metricbeat/defaults/main.yml index 780a4ffd..f6eb2a3e 100644 --- a/metricbeat/defaults/main.yml +++ b/metricbeat/defaults/main.yml @@ -28,3 +28,5 @@ metricbeat_tags: Null # metricbeat_fields: # - "env: staging" metricbeat_fields: Null + +apt_keyring_dir: "{{ ansible_distribution_major_version is version('12', '<') | ternary('/etc/apt/trusted.gpg.d', '/etc/apt/keyrings') }}" \ No newline at end of file diff --git a/metricbeat/handlers/main.yml b/metricbeat/handlers/main.yml index cd83ab5d..949eac26 100644 --- a/metricbeat/handlers/main.yml +++ b/metricbeat/handlers/main.yml @@ -1,6 +1,6 @@ --- - name: restart metricbeat - systemd: + ansible.builtin.systemd: name: metricbeat state: restarted diff --git a/metricbeat/tasks/apt_sources.yml b/metricbeat/tasks/apt_sources.yml new file mode 100644 index 00000000..e525ba4b --- /dev/null +++ b/metricbeat/tasks/apt_sources.yml @@ -0,0 +1,36 @@ +--- +- name: APT https transport is enabled (Debian <10) + ansible.builtin.apt: + name: apt-transport-https + state: present + when: ansible_distribution_major_version is version('10', '<') + +- name: Elastic GPG key is installed + ansible.builtin.copy: + src: elastic.asc + dest: "{{ apt_keyring_dir }}/elastic.asc" + force: true + mode: "0644" + owner: root + group: root + +- name: Add Elastic repository (Debian <12) + ansible.builtin.apt_repository: + repo: "deb [signed-by={{ apt_keyring_dir }}/elastic.asc] https://artifacts.elastic.co/packages/{{ elastic_stack_version | mandatory }}/apt stable main" + filename: elastic + state: present + update_cache: yes + when: ansible_distribution_major_version is version('12', '<') + +- name: Add Elastic repository (Debian >=12) + ansible.builtin.template: + src: elastic.sources.j2 + dest: /etc/apt/sources.list.d/elastic.sources + state: present + register: elastic_sources + when: ansible_distribution_major_version is version('12', '>=') + +- name: Update APT cache + ansible.builtin.apt: + update_cache: yes + when: elastic_sources is changed \ No newline at end of file diff --git a/metricbeat/tasks/main.yml b/metricbeat/tasks/main.yml index 8a009f7f..16cc4865 100644 --- a/metricbeat/tasks/main.yml +++ b/metricbeat/tasks/main.yml @@ -1,55 +1,14 @@ --- - -- name: APT https transport is enabled - apt: - name: apt-transport-https - state: present - tags: - - metricbeat - - packages - -- name: Look for legacy apt keyring - stat: - path: /etc/apt/trusted.gpg - register: _trusted_gpg_keyring - tags: - - metricbeat - - packages - -- name: Elastic embedded GPG key is absent - apt_key: - id: "D88E42B4" - keyring: /etc/apt/trusted.gpg - state: absent - when: _trusted_gpg_keyring.stat.exists - tags: - - metricbeat - - packages - -- name: Elastic GPG key is installed - copy: - src: elastic.asc - dest: /etc/apt/trusted.gpg.d/elastic.asc - force: yes - mode: "0644" - owner: root - group: root - tags: - - metricbeat - - packages - -- name: Elastic sources list is available - apt_repository: - repo: "deb https://artifacts.elastic.co/packages/{{ elastic_stack_version | mandatory }}/apt stable main" - filename: elastic - state: present - update_cache: yes - tags: - - metricbeat - - packages +- name: APT sources + ansible.builtin.import_tasks: apt_sources.yml + args: + apply: + tags: + - metricbeat + - packages - name: Metricbeat is installed - apt: + ansible.builtin.apt: name: metricbeat state: "{% if metribeat_upgrade_package %}latest{% else %}present{% endif %}" notify: restart metricbeat @@ -58,7 +17,7 @@ - packages - name: Metricbeat service is enabled - systemd: + ansible.builtin.systemd: name: metricbeat enabled: yes notify: restart metricbeat @@ -66,7 +25,7 @@ # When we don't use a config template (default) - block: - name: Metricbeat knows where to find Elasticsearch - lineinfile: + ansible.builtin.lineinfile: dest: /etc/metricbeat/metricbeat.yml regexp: '^ hosts: .*' line: " hosts: [\"{{ metricbeat_elasticsearch_hosts | join('\", \"') }}\"]" @@ -75,7 +34,7 @@ when: metricbeat_elasticsearch_hosts | length > 0 - name: Metricbeat protocol for Elasticsearch - lineinfile: + ansible.builtin.lineinfile: dest: /etc/metricbeat/metricbeat.yml regexp: '^ #?protocol: .*' line: " protocol: \"{{ metricbeat_elasticsearch_protocol }}\"" @@ -84,7 +43,7 @@ when: metricbeat_elasticsearch_protocol == "http" or metricbeat_elasticsearch_protocol == "https" - name: Metricbeat auth/username for Elasticsearch are configured - lineinfile: + ansible.builtin.lineinfile: dest: /etc/metricbeat/metricbeat.yml regexp: '{{ item.regexp }}' line: '{{ item.line }}' @@ -98,7 +57,7 @@ - metricbeat_elasticsearch_auth_password | length > 0 - name: Metricbeat api_key for Elasticsearch are configured - lineinfile: + ansible.builtin.lineinfile: dest: /etc/metricbeat/metricbeat.yml regexp: '^ #?api_key: .*' line: ' api_key: "{{ metricbeat_elasticsearch_auth_api_key }}"' @@ -107,7 +66,7 @@ when: metricbeat_elasticsearch_auth_api_key | length > 0 - name: disable cloud_metadata - replace: + ansible.builtin.replace: dest: /etc/metricbeat/metricbeat.yml regexp: '^(\s+)(- add_cloud_metadata:)' replace: '\1# \2' @@ -115,7 +74,7 @@ when: not (metricbeat_processors_cloud_metadata | bool) - name: cloud_metadata processor is disabled - lineinfile: + ansible.builtin.lineinfile: dest: /etc/metricbeat/metricbeat.yml line: " - add_cloud_metadata: ~" insert_after: '^processors:' @@ -126,7 +85,7 @@ # When we use a config template - block: - name: Configuration is up-to-date - template: + ansible.builtin.template: src: "{{ item }}" dest: /etc/metricbeat/metricbeat.yml force: "{{ metricbeat_force_config }}" diff --git a/metricbeat/templates/elastic.sources.j2 b/metricbeat/templates/elastic.sources.j2 new file mode 100644 index 00000000..93df736d --- /dev/null +++ b/metricbeat/templates/elastic.sources.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +Types: deb +URIs: https://artifacts.elastic.co/packages/{{ elastic_stack_version | mandatory }}/apt +Suites: stable +Components: main +Signed-by: {{ apt_keyring_dir }}/elastic.asc +Enabled: yes \ No newline at end of file diff --git a/minifirewall/defaults/main.yml b/minifirewall/defaults/main.yml index 51d169cb..18d7d5b3 100644 --- a/minifirewall/defaults/main.yml +++ b/minifirewall/defaults/main.yml @@ -1,9 +1,14 @@ --- -# Deprecated variable -# minifirewall_main_file: /etc/default/minifirewall +# possible values: Null (default), modern or legacy +minifirewall_install_mode: Null -minifirewall_tail_file: zzz-tail +# 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 @@ -14,17 +19,17 @@ minifirewall_force_upgrade_config: False # Update specific values in configuration minifirewall_update_config: True -minifirewall_git_url: "https://forge.evolix.org/minifirewall.git" -minifirewall_checkout_path: "/tmp/minifirewall" 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] @@ -47,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/check_minifirewall b/minifirewall/files/check_minifirewall index 17943994..bfd5bfc7 100644 --- a/minifirewall/files/check_minifirewall +++ b/minifirewall/files/check_minifirewall @@ -1,5 +1,11 @@ #!/bin/sh +set -u + +return=0 +summary="" +details="" + is_alert5_enabled() { # It's not very clear how to reliably detect if a SysVinit script # wrapped in a systemd unit is enabled or not. @@ -10,69 +16,128 @@ is_alert5_enabled() { if test -f /etc/init.d/alert5; then test -f /etc/rc2.d/S*alert5 else - systemctl is-enabled alert5 -q + systemctl is-enabled alert5 | grep -q "^enabled$" fi } is_minifirewall_enabled() { # TODO: instead of nested conditionals, we could loop with many possible paths # and grep the first found, or error if none is found - if test -f /etc/rc2.d/S*alert5; then - grep -q "^/etc/init.d/minifirewall" /etc/rc2.d/S*alert5 + if [ -f /etc/systemd/system/minifirewall.service ]; then + systemctl is-enabled minifirewall 2>&1 > /dev/null else - if test -f /usr/share/scripts/alert5.sh; then - grep -q "^/etc/init.d/minifirewall" /usr/share/scripts/alert5.sh + if test -f /etc/rc2.d/S*alert5; then + grep -q "^/etc/init.d/minifirewall" /etc/rc2.d/S*alert5 else - return_critical "No Alert5 scripts has been found." + if test -f /usr/share/scripts/alert5.sh; then + grep -q "^/etc/init.d/minifirewall" /usr/share/scripts/alert5.sh + else + return_critical "No Alert5 scripts has been found." + fi fi fi } is_minifirewall_started() { - if test -x /usr/share/scripts/minifirewall_status; then - /usr/share/scripts/minifirewall_status > /dev/null + if [ -f /etc/systemd/system/minifirewall.service ]; then + systemctl is-active minifirewall 2>&1 > /dev/null else - /sbin/iptables -L -n | grep -q -E "^(DROP\s+udp|ACCEPT\s+icmp)\s+--\s+0\.0\.0\.0\/0\s+0\.0\.0\.0\/0\s*$" + if test -x /usr/share/scripts/minifirewall_status; then + /usr/share/scripts/minifirewall_status > /dev/null + else + /sbin/iptables -L -n | grep -q -E "^(DROP\s+(udp|17)|ACCEPT\s+(icmp|1))\s+--\s+0\.0\.0\.0\/0\s+0\.0\.0\.0\/0\s*$" + fi fi } -return_critical() { - echo "CRITICAL: $1" - exit 2 +summary_critical() { + summary="CRITICAL: $1" + [ "${return}" -le 2 ] && return=2 } - -return_warning() { - echo "WARNING: $1" - exit 1 +summary_warning() { + summary="WARNING: $1" + [ "${return}" -le 1 ] && return=1 } - -return_ok() { - echo "OK: $1" - exit 0 +summary_ok() { + summary="OK: $1" + [ "${return}" -le 0 ] && return=0 +} +append_details() { + if [ -z "${details}" ]; then + details="${1}\n" + else + details="${details}$1\n" + fi } main() { if is_alert5_enabled; then - if is_minifirewall_enabled; then + append_details "alert5 is enabled" + + if is_minifirewall_enabled; then + append_details "minifirewall is enabled" + if is_minifirewall_started; then - return_ok "Minifirewall is started." + append_details "minifirewall is started" + + check_result=$(/etc/init.d/minifirewall check-active-config) + check_rc=$? + + if [ ${check_rc} -eq 0 ]; then + append_details "configuration is up-to-date" + summary_ok "minifirewall is started and configuration is up-to-date" + else + if echo "${check_result}" | grep --quiet --regexp 'usage'; then + append_details "minifirewall is too old to check active configuration" + else + case "${check_rc}" in + 1) + summary_warning "minifirewall is started, but unknown configuration state" + ;; + 2) + summary_critical "minifirewall is started, but configuration is outdated" + append_details "configuration is outdated" + ;; + *) + summary_unchk "minifirewall is started, but unknown configuration state" + ;; + esac + append_details "=> run '/etc/init.d/minifirewall check-active-config' for details" + fi + fi else - return_critical "Minifirewall is not started." + summary_critical "minifirewall is stopped, but enabled in alert5 or systemd" fi else + append_details "minifirewall is disabled" + if is_minifirewall_started; then - return_warning "Minifirewall is started, but disabled in alert5." + append_details "minifirewall is started" + summary_warning "minifirewall is started, but disabled in alert5 or systemd" else - return_ok "Minifirewall is not started, but disabled in alert5." + append_details "minifirewall is stopped" + summary_ok "minifirewall is stopped, but disabled in alert5 or systemd" fi fi else + append_details "alert5 is disabled" + if is_minifirewall_started; then - return_warning "Minifirewall is started, but Alert5 script is not enabled." + append_details "minifirewall is started" + summary_warning "minifirewall is started, but alert5 is disabled" else - return_ok "Minifirewall is not started and Alert5 script is not enabled." + append_details "minifirewall is stopped" + summary_ok "minifirewall is stopped and alert5 is disabled" fi fi + + [ "${return}" -ge 0 ] && header="OK" + [ "${return}" -ge 1 ] && header="WARNING" + [ "${return}" -ge 2 ] && header="CRITICAL" + + printf "%s\n\n%s\n" "${summary}" "${details}" + + exit "${return}" } main diff --git a/minifirewall/files/minifirewall b/minifirewall/files/minifirewall index 9e8ff67f..2272a4b0 100755 --- a/minifirewall/files/minifirewall +++ b/minifirewall/files/minifirewall @@ -1,10 +1,11 @@ #!/bin/sh +# shellcheck disable=SC2059 -# minifirewall is shellscripts for easy firewalling on a standalone server -# we used netfilter/iptables http://netfilter.org/ designed for recent Linux kernel +# 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 +# Copyright (c) 2007-2023 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 @@ -28,14 +29,19 @@ # Description: Firewall designed for standalone server ### END INIT INFO -VERSION="22.03.3" +VERSION="23.07" -NAME="minifirewall" +PROGNAME="minifirewall" # shellcheck disable=SC2034 DESC="Firewall designed for standalone server" set -u +if [ "$(id -u)" -ne "0" ] ; then + echo "${PROGNAME} must be run as root." >&2 + exit 1 +fi + # Variables configuration ######################### @@ -97,6 +103,74 @@ 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' + +ACTIVE_CONFIG='/var/run/minifirewall_active_config' +ACTIVE_CONFIG_DIFF="${ACTIVE_CONFIG}.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) + + # shellcheck disable=SC2086 + 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. + +remove_colors() { + sed -r 's/\x1B\[(;?[0-9]{1,3})+[mGK]//g' +} +syslog_info() { + if [ -x "${LOGGER_BIN}" ]; then + ${LOGGER_BIN} -t "${PROGNAME}" -p daemon.info "$1" + fi +} +syslog_error() { + if [ -x "${LOGGER_BIN}" ]; then + ${LOGGER_BIN} -t "${PROGNAME}" -p daemon.error "$1" + fi +} +sort_values() { + echo "$*" | tr ' ' '\n' | sort -h +} is_ipv6_enabled() { test "${IPV6}" != "off" } @@ -124,37 +198,40 @@ chain_exists() { } source_file_or_error() { file=$1 - echo "...sourcing '${file}\`" + 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 - echo "${file} returns standard or error output (see below). Stopping." >&2 + 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 "${tmpfile}" + rm -f "${tmpfile}" } source_configuration() { if ! test -f ${config_file}; then - echo "${config_file} does not exist" >&2 + 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 - echo "${old_config_file} is deprecated. Rename it to ${config_file}" >&2 + printf "${YELLOW}%s is deprecated and ignored. Rename it to %s${RESET}\n" "${old_config_file}" "${config_file}" >&2 fi exit 1 fi - if grep -e "iptables" -e "ip6tables" "${config_file}" | grep -qvE "^#"; then + # 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 ########################### - echo "Legacy config detected" + printf "${YELLOW}legacy config detected${RESET}\n" LEGACY_CONFIG='on' # Non-backward compatible mode @@ -176,29 +253,182 @@ source_configuration() { # and not interfere with the configuration step. tmp_config_file=$(mktemp --tmpdir=/tmp minifirewall.XXX) - grep -E "^\s*[_a-zA-Z0-9]+=" "${config_file}" > "${tmp_config_file}" + # get only variable assignments + grep -E "^\s*\w+=" "${config_file}" > "${tmp_config_file}" source_file_or_error "${tmp_config_file}" - rm "${tmp_config_file}" + rm -f "${tmp_config_file}" else source_file_or_error "${config_file}" fi } +include_files() { + if [ -d "${includes_dir}" ]; then + find ${includes_dir} -type f -readable -not -name '*.*' | sort -h + else + echo "" + 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 + for include_file in $(include_files); do source_file_or_error "${include_file}" done fi } +filter_config_file() { + # Remove lines with: + # * empty or only whitespaces + # * comments + grep --extended-regexp --invert-match -e "^(\s*#)" -e "^\s*$" "${1}" +} +save_active_configuration() { + dest_file=${1} + rm -f "${dest_file}" + + echo "# ${config_file}" >> "${dest_file}" + filter_config_file "${config_file}" >> "${dest_file}" + + found_include_files=$(include_files) + if [ -n "${found_include_files}" ]; then + for include_file in ${found_include_files}; do + echo "# ${include_file}" >> "${dest_file}" + filter_config_file "${include_file}" >> "${dest_file}" + done + fi +} +check_active_configuration() { + # NRPE-compatible return codes + # 0: OK + # 1: WARNING + # 2: CRITICAL + # 3: UNKNOWN + rc=0 + + if [ -f "${ACTIVE_CONFIG}" ]; then + cmp_bin=$(command -v cmp) + diff_bin=$(command -v diff) + + if [ -z "${cmp_bin}" ]; then + printf "${YELLOW}WARNING: Skipped active configuration check (Can't find cmp(1) command)${RESET}\n" + rc=1 + elif [ -z "${diff_bin}" ]; then + printf "${YELLOW}WARNING: Skipped active configuration check (Can't find diff(1) command)${RESET}\n" + rc=1 + else + rm -f "${ACTIVE_CONFIG_DIFF}" + + tmp_config_file=$(mktemp --tmpdir=/tmp minifirewall.XXX) + save_active_configuration "${tmp_config_file}" + + cmp_result=$(cmp "${ACTIVE_CONFIG}" "${tmp_config_file}" 2>&1) + cmp_rc=$? + + if [ ${cmp_rc} -eq 0 ]; then + # echo " config has not changed since latest start" + printf "${GREEN}OK: Active configuration is up-to-date.${RESET}\n" + rc=0 + elif [ ${cmp_rc} -eq 1 ]; then + diff -u "${ACTIVE_CONFIG}" "${tmp_config_file}" > "${ACTIVE_CONFIG_DIFF}" + + printf "${RED}CRITICAL: Active configuration is not up-to-date (minifirewall not restarted after config change?), check %s${RESET}\n" "${ACTIVE_CONFIG_DIFF}" + rc=2 + else + printf "${RED}CRITICAL: Error while comparing rules:${RESET}\n" + printf "${cmp_result}\n" + rc=2 + fi + + rm -f "${tmp_config_file}" + fi + else + printf "${YELLOW}WARNING: Skipped active configuration check (missing file ${ACTIVE_CONFIG})${RESET}\n" + rc=1 + fi + exit ${rc} +} +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 (without colors) + mkdir -p "$(dirname "${STATE_FILE_CURRENT}")" + status_without_numbers | remove_colors > "${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}" 2>&1) + 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 (without colors) + mkdir -p "$(dirname "${STATE_FILE_LATEST}")" + status_without_numbers | remove_colors > "${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}" 2>&1) + 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() { - echo "Start IPTables rules..." + syslog_info "starting" + printf "${BOLD}${PROGNAME} starting${RESET}\n" # Stop and warn if error! set -e - trap 'echo "ERROR in minifirewall configuration (fix it now!) or script manipulation (fix yourself)." ' INT TERM EXIT + trap 'printf "${RED}${PROGNAME} failed : an error occured during startup.${RESET}\n"; syslog_error "failed" ' INT TERM EXIT # sysctl network security settings ################################## @@ -222,31 +452,39 @@ start() { 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 + # Apparently not applicable to IPv6 else - echo "Invalid SYSCTL_ICMP_ECHO_IGNORE_BROADCASTS value '${SYSCTL_ICMP_ECHO_IGNORE_BROADCASTS}', must be '0' or '1'." >&2 + 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 + # Apparently not applicable to IPv6 else - echo "Invalid SYSCTL_ICMP_IGNORE_BOGUS_ERROR_RESPONSES value '${SYSCTL_ICMP_IGNORE_BOGUS_ERROR_RESPONSES}', must be '0' or '1'." >&2 + 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}" + echo "${SYSCTL_ACCEPT_SOURCE_ROUTE}" > "${proc_sys_file}" done + if is_ipv6_enabled; then + for proc_sys_file in /proc/sys/net/ipv6/conf/*/accept_source_route; do + echo "${SYSCTL_ACCEPT_SOURCE_ROUTE}" > "${proc_sys_file}" + done + fi else - echo "Invalid SYSCTL_ACCEPT_SOURCE_ROUTE value '${SYSCTL_ACCEPT_SOURCE_ROUTE}', must be '0' or '1'." >&2 + 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 + # Apparently not applicable to IPv6 else - echo "Invalid SYSCTL_TCP_SYNCOOKIES value '${SYSCTL_TCP_SYNCOOKIES}', must be '0' or '1'." >&2 + printf "${RED}ERROR: invalid %s value '%s', must be '0' or '1'.\n" "SYSCTL_TCP_SYNCOOKIES" "${SYSCTL_TCP_SYNCOOKIES}" >&2 exit 1 fi @@ -257,8 +495,13 @@ start() { for proc_sys_file in /proc/sys/net/ipv4/conf/*/send_redirects; do echo "${SYSCTL_ICMP_REDIRECTS}" > "${proc_sys_file}" done + if is_ipv6_enabled; then + for proc_sys_file in /proc/sys/net/ipv6/conf/*/accept_redirects; do + echo "${SYSCTL_ICMP_REDIRECTS}" > "${proc_sys_file}" + done + fi else - echo "Invalid SYSCTL_ICMP_REDIRECTS value '${SYSCTL_ICMP_REDIRECTS}', must be '0' or '1'." >&2 + printf "${RED}ERROR: invalid %s value '%s', must be '0' or '1'.\n" "SYSCTL_ICMP_REDIRECTS" "${SYSCTL_ICMP_REDIRECTS}" >&2 exit 1 fi @@ -266,8 +509,9 @@ start() { for proc_sys_file in /proc/sys/net/ipv4/conf/*/rp_filter; do echo "${SYSCTL_RP_FILTER}" > "${proc_sys_file}" done + # Apparently not applicable to IPv6 else - echo "Invalid SYSCTL_RP_FILTER value '${SYSCTL_RP_FILTER}', must be '0' or '1'." >&2 + printf "${RED}ERROR: invalid %s value '%s', must be '0' or '1'.\n" "SYSCTL_RP_FILTER" "${SYSCTL_RP_FILTER}" >&2 exit 1 fi @@ -275,8 +519,9 @@ start() { for proc_sys_file in /proc/sys/net/ipv4/conf/*/log_martians; do echo "${SYSCTL_LOG_MARTIANS}" > "${proc_sys_file}" done + # Apparently not applicable to IPv6 else - echo "Invalid SYSCTL_LOG_MARTIANS value '${SYSCTL_LOG_MARTIANS}', must be '0' or '1'." >&2 + printf "${RED}ERROR: invalid %s value '%s', must be '0' or '1'.\n" "SYSCTL_LOG_MARTIANS" "${SYSCTL_LOG_MARTIANS}" >&2 exit 1 fi @@ -303,6 +548,31 @@ start() { # * 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 @@ -667,7 +937,7 @@ start() { ${IPT} -A INPUT -p tcp --sport "${server_port}" --dport 1024:65535 -s "${server_ip}" -m state --state ESTABLISHED,RELATED -j ACCEPT fi else - echo "Unrecognized syntax for BACKUPSERVERS '${server}\`. Use space-separated IP:PORT tuples." >&2 + printf "${RED}ERROR: unrecognized syntax for BACKUPSERVERS '%s\`. Use space-separated IP:PORT tuples.${RESET}\n" "${server}" >&2 exit 1 fi done @@ -678,6 +948,10 @@ start() { ${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 ################# @@ -714,17 +988,32 @@ start() { ${IPT6} -A OUTPUT -p udp -j DROP fi - if is_legacy_config; then - source_file_or_error "${config_file}" - fi + # Finish + ######################## trap - INT TERM EXIT - echo "...starting IPTables rules is now finish : OK" + syslog_info "started" + printf "${GREEN}${BOLD}${PROGNAME} started${RESET}\n" + + # No need to exit on error anymore + set +e + + # save active configuration + save_active_configuration "${ACTIVE_CONFIG}" + + report_state_changes } stop() { - echo "Flush all rules and accept everything..." + syslog_info "stopping" + printf "${BOLD}${PROGNAME} stopping${RESET}\n" + + printf "${BLUE}flushing all rules and accepting everything${RESET}\n" + + # Save previous state (without colors) + mkdir -p "$(dirname "${STATE_FILE_PREVIOUS}")" + status_without_numbers | remove_colors > "${STATE_FILE_PREVIOUS}" # Delete all rules ${IPT} -F INPUT @@ -799,19 +1088,45 @@ stop() { ${IPT6} -X NEEDRESTRICT fi - echo "...flushing IPTables rules is now finish : OK" + rm -f "${STATE_FILE_LATEST}" "${STATE_FILE_CURRENT}" "${ACTIVE_CONFIG}" + + syslog_info "stopped" + printf "${GREEN}${BOLD}${PROGNAME} stopped${RESET}\n" } status() { - ${IPT} -L -n -v --line-numbers - ${IPT} -t nat -L -n -v --line-numbers - ${IPT} -t mangle -L -n -v --line-numbers - ${IPT6} -L -n -v --line-numbers - ${IPT6} -t mangle -L -n -v --line-numbers + 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() { - echo "Reset all IPTables counters..." + syslog_info "resetting" + printf "${BOLD}${PROGNAME} resetting${RESET}\n" ${IPT} -Z if is_ipv6_enabled; then @@ -825,37 +1140,98 @@ reset() { ${IPT6} -t mangle -Z fi - echo "...reseting IPTables counters is now finish : OK" + syslog_info "reset" + printf "${GREEN}${BOLD}${PROGNAME} reset${RESET}\n" } +show_version() { + cat <. + +${PROGNAME} 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 +} +show_help() { + cat < 0 + +- name: Stat minifirewall config file (after) + ansible.builtin.stat: + path: "{{ minifirewall_main_file }}" + register: minifirewall_after + +- name: Schedule minifirewall restart (legacy) + ansible.builtin.command: + cmd: /bin/true + notify: "restart minifirewall (legacy)" + when: + - minifirewall_install_mode == 'legacy' + - minifirewall_restart_if_needed | bool + - minifirewall_is_running.rc == 0 + - minifirewall_before.stat.checksum != minifirewall_after.stat.checksum or minifirewall_upgrade_script is changed or minifirewall_upgrade_config is changed + + +- ansible.builtin.debug: + var: minifirewall_init_restart + verbosity: 2 diff --git a/minifirewall/tasks/config.yml b/minifirewall/tasks/config.yml index c0afd2b1..2d4da100 100644 --- a/minifirewall/tasks/config.yml +++ b/minifirewall/tasks/config.yml @@ -1,58 +1,58 @@ --- -- debug: +- ansible.builtin.debug: var: minifirewall_trusted_ips verbosity: 1 -- debug: +- ansible.builtin.debug: var: minifirewall_privilegied_ips verbosity: 1 - name: Stat minifirewall config file (before) - stat: + ansible.builtin.stat: path: "/etc/default/minifirewall" register: minifirewall_before - name: Check if minifirewall is running - shell: + ansible.builtin.shell: cmd: /sbin/iptables -L -n | grep -E "^(DROP\s+udp|ACCEPT\s+icmp)\s+--\s+0\.0\.0\.0\/0\s+0\.0\.0\.0\/0\s*$" changed_when: False failed_when: False check_mode: no register: minifirewall_is_running -- debug: +- ansible.builtin.debug: var: minifirewall_is_running verbosity: 1 - name: Begin marker for IP addresses - lineinfile: + ansible.builtin.lineinfile: dest: "/etc/default/minifirewall" line: "# BEGIN ANSIBLE MANAGED BLOCK FOR IPS" insertbefore: '^# Main interface' create: no - name: End marker for IP addresses - lineinfile: + ansible.builtin.lineinfile: dest: "/etc/default/minifirewall" create: no line: "# END ANSIBLE MANAGED BLOCK FOR IPS" insertafter: '^PRIVILEGIEDIPS=' - name: Verify that at least 1 trusted IP is provided - assert: + ansible.builtin.assert: that: minifirewall_trusted_ips | length > 0 msg: You must provide at least 1 trusted IP -- debug: +- ansible.builtin.debug: 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: +- ansible.builtin.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: + ansible.builtin.blockinfile: dest: "/etc/default/minifirewall" marker: "# {mark} ANSIBLE MANAGED BLOCK FOR IPS" block: | @@ -70,7 +70,7 @@ # 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' + DOCKER='{{ minifirewall_docker }}' # Trusted IPv4 local network # ...will be often IP/32 if you don't trust anything @@ -86,21 +86,21 @@ register: minifirewall_config_ips - name: Begin marker for ports - lineinfile: + ansible.builtin.lineinfile: dest: "/etc/default/minifirewall" line: "# BEGIN ANSIBLE MANAGED BLOCK FOR PORTS" insertbefore: '^# Protected services' create: no - name: End marker for ports - lineinfile: + ansible.builtin.lineinfile: dest: "/etc/default/minifirewall" line: "# END ANSIBLE MANAGED BLOCK FOR PORTS" insertafter: '^SERVICESUDP3=' create: no - name: Configure ports - blockinfile: + ansible.builtin.blockinfile: dest: "/etc/default/minifirewall" marker: "# {mark} ANSIBLE MANAGED BLOCK FOR PORTS" block: | @@ -124,94 +124,174 @@ register: minifirewall_config_ports - name: Configure DNSSERVEURS - lineinfile: + ansible.builtin.lineinfile: 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: + ansible.builtin.lineinfile: 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: + ansible.builtin.lineinfile: 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: + ansible.builtin.lineinfile: 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: + ansible.builtin.lineinfile: 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: + ansible.builtin.lineinfile: 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: + ansible.builtin.lineinfile: 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: + ansible.builtin.lineinfile: dest: "/etc/default/minifirewall" line: "NTPOK='{{ minifirewall_ntp_ok | join(' ') }}'" - regexp: "NTPOK='.*'" + regexp: "NTPOK=('|\").*('|\")" create: no when: minifirewall_ntp_ok is not none +- name: Configure PROXY + ansible.builtin.lineinfile: + dest: "/etc/default/minifirewall" + line: "PROXY='{{ minifirewall_proxy }}'" + regexp: "PROXY=('|\").*('|\")" + create: no + when: minifirewall_proxy is not none + +- name: Configure PROXYPORT + ansible.builtin.lineinfile: + 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 + ansible.builtin.lineinfile: + dest: "/etc/default/minifirewall" + line: "PROXYBYPASS=\"{{ minifirewall_proxybypass | join(' ') }}\"" + regexp: "PROXYBYPASS=('|\").*('|\")" + create: no + when: minifirewall_proxybypass is not none + +- name: Configure BACKUPSERVERS + ansible.builtin.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 + ansible.builtin.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 + ansible.builtin.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 + ansible.builtin.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 + ansible.builtin.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 + ansible.builtin.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 + ansible.builtin.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 + ansible.builtin.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: + ansible.builtin.stat: 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" +- name: Schedule minifirewall restart (modern) + ansible.builtin.command: + cmd: /bin/true + notify: "restart minifirewall (modern)" when: + - minifirewall_install_mode != 'legacy' - minifirewall_restart_if_needed | bool - minifirewall_is_running.rc == 0 - - minifirewall_before.stat.checksum != minifirewall_after.stat.checksum + - minifirewall_before.stat.checksum != minifirewall_after.stat.checksum or minifirewall_upgrade_script is changed or minifirewall_upgrade_config is changed -- name: restart minifirewall (noop) - meta: noop - register: minifirewall_init_restart - failed_when: False - changed_when: False - when: not (minifirewall_restart_if_needed | bool) - -- debug: +- ansible.builtin.debug: var: minifirewall_init_restart verbosity: 2 diff --git a/minifirewall/tasks/install.legacy.yml b/minifirewall/tasks/install.legacy.yml new file mode 100644 index 00000000..7d03efff --- /dev/null +++ b/minifirewall/tasks/install.legacy.yml @@ -0,0 +1,24 @@ +--- + +- name: dependencies are satisfied + ansible.builtin.apt: + name: iptables + state: present + +- name: init script is copied + ansible.builtin.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 + ansible.builtin.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 5eeed116..1a507d31 100644 --- a/minifirewall/tasks/install.yml +++ b/minifirewall/tasks/install.yml @@ -1,30 +1,32 @@ --- - name: dependencies are satisfied - apt: + ansible.builtin.apt: name: iptables state: present - name: init script is copied - copy: + ansible.builtin.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: + ansible.builtin.copy: src: minifirewall.conf 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: + ansible.builtin.file: path: /etc/minifirewall.d/ state: directory owner: root @@ -32,30 +34,10 @@ mode: "0700" - name: examples for includes are present - copy: + ansible.builtin.copy: src: "minifirewall.d/" dest: "/etc/minifirewall.d/" force: "no" mode: "0600" owner: root group: root - -- 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 diff --git a/minifirewall/tasks/main.yml b/minifirewall/tasks/main.yml index 5f442eb1..5457d60c 100644 --- a/minifirewall/tasks/main.yml +++ b/minifirewall/tasks/main.yml @@ -1,28 +1,182 @@ --- -- name: Compose minifirewall_restart_handler_name variable - set_fact: - minifirewall_restart_handler_name: "{{ minifirewall_restart_if_needed | bool | ternary('restart minifirewall', 'restart minifirewall (noop)') }}" +# Legacy or modern mode? ############################################## -- name: Fail if minifirewall_main_file is defined - fail: - msg: "Variable minifirewall_main_file is deprecated and not configurable anymore." - when: minifirewall_main_file is defined +- name: Check minifirewall + ansible.builtin.stat: + path: /etc/init.d/minifirewall + register: _minifirewall_check + tags: + - always -- include: install.yml - -- include: config.yml - when: minifirewall_update_config | bool - -- 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 + ansible.builtin.shell: + cmd: "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 + tags: + - always + +- name: Set install mode to legacy if needed + ansible.builtin.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 + tags: + - always + +- name: Set install mode to modern if not legacy + ansible.builtin.set_fact: + minifirewall_install_mode: modern + when: minifirewall_install_mode != 'legacy' + tags: + - always + +- name: Debug install mode + ansible.builtin.debug: + var: minifirewall_install_mode + verbosity: 1 + tags: + - always + +- name: 'Set minifirewall_restart_handler_name to "noop"' + ansible.builtin.set_fact: + minifirewall_restart_handler_name: "restart minifirewall (noop)" + when: + - not (minifirewall_restart_if_needed | bool) + tags: + - always + +- name: 'Set minifirewall_restart_handler_name to "legacy"' + ansible.builtin.set_fact: + minifirewall_restart_handler_name: "restart minifirewall (legacy)" + when: + - minifirewall_restart_if_needed | bool + - minifirewall_install_mode == 'legacy' + tags: + - always + +- name: 'Set minifirewall_restart_handler_name to "modern"' + ansible.builtin.set_fact: + minifirewall_restart_handler_name: "restart minifirewall (modern)" + when: + - minifirewall_restart_if_needed | bool + - minifirewall_install_mode != 'legacy' + tags: + - always + +####################################################################### + +- name: Fail if minifirewall_main_file is defined (legacy mode) + ansible.builtin.fail: + msg: "Variable minifirewall_main_file is deprecated and not configurable anymore." + when: + - minifirewall_install_mode != 'legacy' + - minifirewall_main_file is defined + tags: + - always + +- name: Install tasks (modern mode) + ansible.builtin.import_tasks: install.yml + when: minifirewall_install_mode != 'legacy' + +- name: Install tasks (legacy mode) + ansible.builtin.import_tasks: install.legacy.yml + when: minifirewall_install_mode == 'legacy' + +- name: Debug minifirewall_update_config + ansible.builtin.debug: + var: minifirewall_update_config | bool + verbosity: 1 + tags: + - always + +- name: Config tasks (modern mode) + ansible.builtin.include_tasks: config.yml + when: + - minifirewall_install_mode != 'legacy' + - minifirewall_update_config | bool + tags: + - manage + +- name: Config tasks (legacy mode) + ansible.builtin.include_tasks: config.legacy.yml + args: + apply: + tags: + - manage + when: + - minifirewall_install_mode == 'legacy' + - minifirewall_update_config | bool + +- name: Utils tasks + ansible.builtin.include_tasks: utils.yml + +- name: NRPE tasks + ansible.builtin.include_tasks: nrpe.yml + +- name: Activation tasks + ansible.builtin.include_tasks: activate.yml + +- name: Debug minifirewall_tail_included + ansible.builtin.debug: + var: minifirewall_tail_included | bool + verbosity: 1 + tags: + - always + +- name: Tail tasks (modern mode) + ansible.builtin.include_tasks: tail.yml + args: + apply: + tags: + - manage + when: + - minifirewall_install_mode != 'legacy' + - minifirewall_tail_included | bool + +- name: Tail tasks (legacy mode) + ansible.builtin.include_tasks: tail.legacy.yml + args: + apply: + tags: + - manage + when: + - minifirewall_install_mode == 'legacy' + - minifirewall_tail_included | bool + +# Restart? + +- name: Debug minifirewall_restart_force + ansible.builtin.debug: + var: minifirewall_restart_force | bool + verbosity: 1 + tags: + - always + +- name: Force restart minifirewall (legacy) + ansible.builtin.command: + cmd: /bin/true + notify: "restart minifirewall (legacy)" + tags: + - always + when: + - minifirewall_install_mode == 'legacy' + - minifirewall_restart_force | bool + +- name: Force restart minifirewall (modern) + ansible.builtin.command: + cmd: /bin/true + notify: "restart minifirewall (modern)" + tags: + - always + when: + - minifirewall_install_mode != 'legacy' + - minifirewall_restart_force | bool \ No newline at end of file diff --git a/minifirewall/tasks/nrpe.yml b/minifirewall/tasks/nrpe.yml index 2e9674f7..691dd454 100644 --- a/minifirewall/tasks/nrpe.yml +++ b/minifirewall/tasks/nrpe.yml @@ -1,10 +1,10 @@ --- -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - name: /usr/share/scripts exists - file: + ansible.builtin.file: dest: /usr/share/scripts mode: "0700" owner: root @@ -12,7 +12,7 @@ state: directory - name: minifirewall_status is installed - copy: + ansible.builtin.copy: src: minifirewall_status dest: /usr/share/scripts/minifirewall_status force: "{{ minifirewall_force_update_nrpe_scripts | bool }}" @@ -21,7 +21,7 @@ group: root - name: /usr/local/lib/nagios/plugins/ exists - file: + ansible.builtin.file: dest: "{{ nagios_plugins_directory }}" mode: "02755" owner: root @@ -29,7 +29,7 @@ state: directory - name: check_minifirewall is installed - copy: + ansible.builtin.copy: src: check_minifirewall dest: "{{ nagios_plugins_directory }}/check_minifirewall" force: "{{ minifirewall_force_update_nrpe_scripts | bool }}" @@ -38,12 +38,12 @@ group: staff - name: Is NRPE installed? - stat: + ansible.builtin.stat: path: /etc/nagios/nrpe.d/evolix.cfg register: nrpe_evolix_cfg - name: check_minifirewall is available for NRPE - lineinfile: + ansible.builtin.lineinfile: dest: /etc/nagios/nrpe.d/evolix.cfg regexp: 'command\[check_minifirewall\]' line: 'command[check_minifirewall]=sudo {{ nagios_plugins_directory }}/check_minifirewall' @@ -51,12 +51,12 @@ when: nrpe_evolix_cfg.stat.exists - name: Is evolinux sudoers installed? - stat: + ansible.builtin.stat: path: /etc/sudoers.d/evolinux register: sudoers_evolinux - name: sudo without password for nagios - lineinfile: + ansible.builtin.lineinfile: dest: /etc/sudoers.d/evolinux regexp: 'check_minifirewall' line: 'nagios ALL = NOPASSWD: {{ nagios_plugins_directory }}/check_minifirewall' diff --git a/minifirewall/tasks/tail.legacy.yml b/minifirewall/tasks/tail.legacy.yml new file mode 100644 index 00000000..d78d2090 --- /dev/null +++ b/minifirewall/tasks/tail.legacy.yml @@ -0,0 +1,63 @@ +--- + +- name: Stat minifirewall config file (before) + ansible.builtin.stat: + path: "/etc/default/minifirewall" + register: minifirewall_before + +- name: Check if minifirewall is running + ansible.builtin.shell: + cmd: /sbin/iptables -L -n | grep -E "^(DROP\s+udp|ACCEPT\s+icmp)\s+--\s+0\.0\.0\.0\/0\s+0\.0\.0\.0\/0\s*$" + changed_when: False + failed_when: False + check_mode: no + register: minifirewall_is_running + +- ansible.builtin.debug: + var: minifirewall_is_running + verbosity: 1 + +- name: Add some rules at the end of minifirewall file + ansible.builtin.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 + +- ansible.builtin.debug: + var: minifirewall_tail_template + verbosity: 1 + +- name: source minifirewall.tail at the end of the main file + ansible.builtin.blockinfile: + dest: "{{ minifirewall_main_file }}" + marker: "# {mark} ANSIBLE MANAGED EXTERNAL RULES" + block: ". {{ minifirewall_tail_file }}" + insertbefore: EOF + register: minifirewall_tail_source + +- ansible.builtin.debug: + var: minifirewall_tail_source + verbosity: 1 + +- name: Schedule minifirewall restart (legacy) + ansible.builtin.command: + cmd: /bin/true + notify: "restart minifirewall (legacy)" + when: + - minifirewall_install_mode == 'legacy' + - minifirewall_restart_if_needed | bool + - minifirewall_is_running.rc == 0 + - minifirewall_tail_template is changed + +- ansible.builtin.debug: + var: minifirewall_init_restart + verbosity: 1 diff --git a/minifirewall/tasks/tail.yml b/minifirewall/tasks/tail.yml index 199b4c7a..a3911f4a 100644 --- a/minifirewall/tasks/tail.yml +++ b/minifirewall/tasks/tail.yml @@ -1,9 +1,28 @@ --- + +- name: Stat minifirewall config file (before) + ansible.builtin.stat: + path: "/etc/default/minifirewall" + register: minifirewall_before + +- name: Check if minifirewall is running + ansible.builtin.shell: + cmd: /sbin/iptables -L -n | grep -E "^(DROP\s+udp|ACCEPT\s+icmp)\s+--\s+0\.0\.0\.0\/0\s+0\.0\.0\.0\/0\s*$" + changed_when: False + failed_when: False + check_mode: no + register: minifirewall_is_running + +- ansible.builtin.debug: + var: minifirewall_is_running + verbosity: 1 + - name: Add some rules at the end of minifirewall file - template: + ansible.builtin.template: src: "{{ item }}" - dest: "/etc/minifirewall.d/{{ minifirewall_tail_file }}" + dest: "{{ minifirewall_tail_file }}" force: "{{ minifirewall_tail_force | bool }}" + follow: yes loop: "{{ query('first_found', templates) }}" vars: templates: @@ -13,29 +32,20 @@ - "templates/minifirewall.default.tail.j2" register: minifirewall_tail_template -- debug: +- ansible.builtin.debug: var: minifirewall_tail_template 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" +- name: Schedule minifirewall restart (modern) + ansible.builtin.command: + cmd: /bin/true + notify: "restart minifirewall (modern)" when: - - minifirewall_tail_template is changed + - minifirewall_install_mode != 'legacy' - minifirewall_restart_if_needed | bool + - minifirewall_is_running.rc == 0 + - minifirewall_tail_template is changed -- name: restart minifirewall (noop) - meta: noop - register: minifirewall_init_restart - failed_when: False - changed_when: False - when: not (minifirewall_restart_if_needed | bool) - -- debug: +- ansible.builtin.debug: var: minifirewall_init_restart verbosity: 1 diff --git a/minifirewall/tasks/utils.yml b/minifirewall/tasks/utils.yml new file mode 100644 index 00000000..14ea7aac --- /dev/null +++ b/minifirewall/tasks/utils.yml @@ -0,0 +1,21 @@ +--- + +- ansible.builtin.include_role: + name: evolix/remount-usr + +- name: /usr/share/scripts exists + ansible.builtin.file: + dest: /usr/share/scripts + mode: "0700" + owner: root + group: root + state: directory + +- name: blacklist-countries.sh is copied + ansible.builtin.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.legacy.j2 b/minifirewall/templates/minifirewall.legacy.j2 new file mode 100644 index 00000000..13b5130d --- /dev/null +++ b/minifirewall/templates/minifirewall.legacy.j2 @@ -0,0 +1,492 @@ +#!/bin/sh + +# minifirewall is shellscripts for easy firewalling on a standalone server +# we used netfilter/iptables http://netfilter.org/ designed for recent Linux kernel +# See https://gitea.evolix.org/evolix/minifirewall + +# Copyright (c) 2007-2020 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: minfirewall +# 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 + +DESC="minifirewall" +NAME="minifirewall" + + +# Variables configuration +######################### + +# iptables paths +IPT=/sbin/iptables +IPT6=/sbin/ip6tables + +# 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' + +chain_exists() +{ + local chain_name="$1" ; shift + [ $# -eq 1 ] && local intable="--table $1" + iptables $intable -nL "$chain_name" >/dev/null 2>&1 +} + +# Configuration +oldconfigfile="/etc/firewall.rc" +configfile="{{ minifirewall_main_file }}" + +IPV6=$(grep "IPV6=" {{ minifirewall_main_file }} | awk -F '=' -F "'" '{print $2}') +DOCKER=$(grep "DOCKER=" {{ minifirewall_main_file }} | awk -F '=' -F "'" '{print $2}') +INT=$(grep "INT=" {{ minifirewall_main_file }} | awk -F '=' -F "'" '{print $2}') + +case "$1" in + start) + + echo "Start IPTables rules..." + +# Stop and warn if error! +set -e +trap 'echo "ERROR in minifirewall configuration (fix it now!) or script manipulation (fix yourself)." ' INT TERM EXIT + + +# sysctl network security settings +################################## + +# Don't answer to broadcast pings +echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts + +# Ignore bogus ICMP responses +echo 1 > /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses + +# Disable Source Routing +for i in /proc/sys/net/ipv4/conf/*/accept_source_route; do +echo 0 > $i +done + +# Enable TCP SYN cookies to avoid TCP-SYN-FLOOD attacks +# cf http://cr.yp.to/syncookies.html +echo 1 > /proc/sys/net/ipv4/tcp_syncookies + +# Disable ICMP redirects +for i in /proc/sys/net/ipv4/conf/*/accept_redirects; do +echo 0 > $i +done + +for i in /proc/sys/net/ipv4/conf/*/send_redirects; do +echo 0 > $i +done + +# Enable Reverse Path filtering : verify if responses use same network interface +for i in /proc/sys/net/ipv4/conf/*/rp_filter; do +echo 1 > $i +done + +# log des paquets avec adresse incoherente +for i in /proc/sys/net/ipv4/conf/*/log_martians; do +echo 1 > $i +done + +# 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 test -f $oldconfigfile; then + echo "$oldconfigfile is deprecated, rename to $configfile" >&2 + exit 1 +fi + +if ! test -f $configfile; then + echo "$configfile does not exist" >&2 + exit 1 +fi + +tmpfile=`mktemp` +. $configfile 2>$tmpfile >&2 +if [ -s $tmpfile ]; then + echo "$configfile returns standard or error output (see below). Stopping." >&2 + cat $tmpfile + exit 1 +fi +rm $tmpfile + +# Trusted ip addresses +$IPT -N ONLYTRUSTED +$IPT -A ONLYTRUSTED -j LOG_DROP +for x in $TRUSTEDIPS + do + $IPT -I ONLYTRUSTED -s $x -j ACCEPT + done + +# Privilegied ip addresses +# (trusted ip addresses *are* privilegied) +$IPT -N ONLYPRIVILEGIED +$IPT -A ONLYPRIVILEGIED -j ONLYTRUSTED +for x in $PRIVILEGIEDIPS + do + $IPT -I ONLYPRIVILEGIED -s $x -j ACCEPT + done + +# Chain for restrictions (blacklist IPs/ranges) +$IPT -N NEEDRESTRICT + +# We allow all on loopback interface +$IPT -A INPUT -i lo -j ACCEPT +[ "$IPV6" != "off" ] && $IPT6 -A INPUT -i lo -j ACCEPT +# if OUTPUTDROP +$IPT -A OUTPUT -o lo -j ACCEPT +[ "$IPV6" != "off" ] && $IPT6 -A OUTPUT -o lo -j ACCEPT + +# 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 +$IPT -A INPUT -s $LOOPBACK ! -i lo -j DROP + + +if [ "$DOCKER" = "on" ]; then + + $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) +$IPT -A INPUT -s $INTLAN -j ACCEPT + +# Enable protection chain for sensible services +for x in $SERVICESTCP1p + do + $IPT -A INPUT -p tcp --dport $x -j NEEDRESTRICT + done + +for x in $SERVICESUDP1p + do + $IPT -A INPUT -p udp --dport $x -j NEEDRESTRICT + done + +# Public service +for x in $SERVICESTCP1 + do + $IPT -A INPUT -p tcp --dport $x -j ACCEPT + [ "$IPV6" != "off" ] && $IPT6 -A INPUT -p tcp --dport $x -j ACCEPT + done + +for x in $SERVICESUDP1 + do + $IPT -A INPUT -p udp --dport $x -j ACCEPT + [ "$IPV6" != "off" ] && $IPT6 -A INPUT -p udp --dport $x -j ACCEPT + done + +# Privilegied services +for x in $SERVICESTCP2 + do + $IPT -A INPUT -p tcp --dport $x -j ONLYPRIVILEGIED + done + +for x in $SERVICESUDP2 + do + $IPT -A INPUT -p udp --dport $x -j ONLYPRIVILEGIED + done + +# Private services +for x in $SERVICESTCP3 + do + $IPT -A INPUT -p tcp --dport $x -j ONLYTRUSTED + done + +for x in $SERVICESUDP3 + do + $IPT -A INPUT -p udp --dport $x -j ONLYTRUSTED + done + + +if [ "$DOCKER" = "on" ]; then + + # 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 + $IPT -I MINIFW-DOCKER-PRIVILEGED -p tcp -s "$srcip" --dport "$dstport" -j RETURN + done + + for srcip in $TRUSTEDIPS + do + $IPT -I MINIFW-DOCKER-PRIVILEGED -p tcp -s "$srcip" --dport "$dstport" -j RETURN + done + done + + for dstport in $SERVICESUDP2 + do + for srcip in $PRIVILEGIEDIPS + do + $IPT -I MINIFW-DOCKER-PRIVILEGED -p udp -s "$srcip" --dport "$dstport" -j RETURN + done + + for srcip in $TRUSTEDIPS + do + $IPT -I MINIFW-DOCKER-PRIVILEGED -p udp -s "$srcip" --dport "$dstport" -j RETURN + done + done + + # Trusted services (accessible from trusted IPs) + for dstport in $SERVICESTCP3 + do + for srcip in $TRUSTEDIPS + do + $IPT -I MINIFW-DOCKER-TRUSTED -p tcp -s "$srcip" --dport "$dstport" -j RETURN + done + done + + for dstport in $SERVICESUDP3 + do + for srcip in $TRUSTEDIPS + do + $IPT -I MINIFW-DOCKER-TRUSTED -p udp -s "$srcip" --dport "$dstport" -j RETURN + done + done +fi + +# External services +################### + +# DNS authorizations +for x in $DNSSERVEURS + do + $IPT -A INPUT -p tcp ! --syn --sport 53 --dport $PORTSUSER -s $x -j ACCEPT + $IPT -A INPUT -p udp --sport 53 --dport $PORTSUSER -s $x -m state --state ESTABLISHED,RELATED -j ACCEPT + $IPT -A OUTPUT -o $INT -p udp -d $x --dport 53 --match state --state NEW -j ACCEPT + done + +# HTTP (TCP/80) authorizations +for x in $HTTPSITES + do + $IPT -A INPUT -p tcp ! --syn --sport 80 --dport $PORTSUSER -s $x -j ACCEPT + done + +# HTTPS (TCP/443) authorizations +for x in $HTTPSSITES + do + $IPT -A INPUT -p tcp ! --syn --sport 443 --dport $PORTSUSER -s $x -j ACCEPT + done + +# FTP (so complex protocol...) authorizations +for x in $FTPSITES + do + # requests on Control connection + $IPT -A INPUT -p tcp ! --syn --sport 21 --dport $PORTSUSER -s $x -j ACCEPT + # FTP port-mode on Data Connection + $IPT -A INPUT -p tcp --sport 20 --dport $PORTSUSER -s $x -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 $x -j ACCEPT + done + +# SSH authorizations +for x in $SSHOK + do + $IPT -A INPUT -p tcp ! --syn --sport 22 -s $x -j ACCEPT + done + +# SMTP authorizations +for x in $SMTPOK + do + $IPT -A INPUT -p tcp ! --syn --sport 25 --dport $PORTSUSER -s $x -j ACCEPT + done + +# secure SMTP (TCP/465 et TCP/587) authorizations +for x in $SMTPSECUREOK + do + $IPT -A INPUT -p tcp ! --syn --sport 465 --dport $PORTSUSER -s $x -j ACCEPT + $IPT -A INPUT -p tcp ! --syn --sport 587 --dport $PORTSUSER -s $x -j ACCEPT + done + +# NTP authorizations +for x in $NTPOK + do + $IPT -A INPUT -p udp --sport 123 -s $x -j ACCEPT + $IPT -A OUTPUT -o $INT -p udp -d $x --dport 123 --match state --state NEW -j ACCEPT + done + +# Always allow ICMP +$IPT -A INPUT -p icmp -j ACCEPT +[ "$IPV6" != "off" ] && $IPT6 -A INPUT -p icmpv6 -j ACCEPT + + +# IPTables policy +################# + +# by default DROP INPUT packets +$IPT -P INPUT DROP +[ "$IPV6" != "off" ] && $IPT6 -P INPUT DROP + +# by default, no FORWARING (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 +[ "$IPV6" != "off" ] && $IPT6 -P OUTPUT ACCEPT +$IPT -A OUTPUT -o $INT -p udp --dport 33434:33523 --match state --state NEW -j ACCEPT +$IPT -A OUTPUT -p udp --match state --state ESTABLISHED,RELATED -j ACCEPT +$IPT -A OUTPUT -p udp -j DROP +[ "$IPV6" != "off" ] && $IPT6 -A OUTPUT -o $INT -p udp --dport 33434:33523 --match state --state NEW -j ACCEPT +[ "$IPV6" != "off" ] && $IPT6 -A OUTPUT -p udp --match state --state ESTABLISHED,RELATED -j ACCEPT +[ "$IPV6" != "off" ] && $IPT6 -A OUTPUT -p udp -j DROP + +trap - INT TERM EXIT + + echo "...starting IPTables rules is now finish : OK" + ;; + + stop) + + echo "Flush all rules and accept everything..." + + # Delete all rules + $IPT -F INPUT + $IPT -F OUTPUT + $IPT -F LOG_DROP + $IPT -F LOG_ACCEPT + $IPT -F ONLYTRUSTED + $IPT -F ONLYPRIVILEGIED + $IPT -F NEEDRESTRICT + [ "$DOCKER" = "off" ] && $IPT -t nat -F + $IPT -t mangle -F + [ "$IPV6" != "off" ] && $IPT6 -F INPUT + [ "$IPV6" != "off" ] && $IPT6 -F OUTPUT + + if [ "$DOCKER" = "on" ]; then + $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 + + fi + + # Accept all + $IPT -P INPUT ACCEPT + $IPT -P OUTPUT ACCEPT + [ "$IPV6" != "off" ] && $IPT6 -P INPUT ACCEPT + [ "$IPV6" != "off" ] && $IPT6 -P OUTPUT ACCEPT + #$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 + + echo "...flushing IPTables rules is now finish : OK" + ;; + + status) + + $IPT -L -n -v --line-numbers + $IPT -t nat -L -n -v --line-numbers + $IPT -t mangle -L -n -v --line-numbers + $IPT6 -L -n -v --line-numbers + $IPT6 -t mangle -L -n -v --line-numbers + ;; + + reset) + + echo "Reset all IPTables counters..." + + $IPT -Z + $IPT -t nat -Z + $IPT -t mangle -Z + [ "$IPV6" != "off" ] && $IPT6 -Z + [ "$IPV6" != "off" ] && $IPT6 -t mangle -Z + + echo "...reseting IPTables counters is now finish : OK" + ;; + + restart) + + $0 stop + $0 start + ;; + + *) + + echo "Usage: $0 {start|stop|restart|status|reset|squid}" + exit 1 +esac + +exit 0 diff --git a/minifirewall/tests/test.yml b/minifirewall/tests/test.yml index 43dd567f..a7168a68 100644 --- a/minifirewall/tests/test.yml +++ b/minifirewall/tests/test.yml @@ -3,7 +3,7 @@ vars: - minifirewall_trusted_ips: ["{{ ansible_default_ipv4.address }}/24"] pre_tasks: - - apt: + - ansible.builtin.apt: name: git roles: - role: minifirewall diff --git a/mongodb/defaults/main.yml b/mongodb/defaults/main.yml index c118f588..667d68d5 100644 --- a/mongodb/defaults/main.yml +++ b/mongodb/defaults/main.yml @@ -7,4 +7,6 @@ mongodb_bind: 127.0.0.1 # otherwise it can disable important settings, like authorization :/ mongodb_force_config: False -mongodb_version: 4.4 \ No newline at end of file +mongodb_version: 4.4 + +apt_keyring_dir: "{{ ansible_distribution_major_version is version('12', '<') | ternary('/etc/apt/trusted.gpg.d', '/etc/apt/keyrings') }}" \ No newline at end of file diff --git a/mongodb/handlers/main.yml b/mongodb/handlers/main.yml index 15f70437..7b793cdf 100644 --- a/mongodb/handlers/main.yml +++ b/mongodb/handlers/main.yml @@ -1,16 +1,16 @@ --- # handlers file for mongodb - name: restart mongod - service: + ansible.builtin.service: name: mongod state: restarted - name: restart mongodb - service: + ansible.builtin.service: name: mongodb state: restarted - name: restart munin-node - systemd: + ansible.builtin.systemd: name: munin-node state: restarted diff --git a/mongodb/tasks/main.yml b/mongodb/tasks/main.yml index 3054ccfe..e8bf2cfc 100644 --- a/mongodb/tasks/main.yml +++ b/mongodb/tasks/main.yml @@ -1,13 +1,16 @@ --- -- include: main_jessie.yml +- ansible.builtin.import_tasks: main_jessie.yml when: ansible_distribution_release == "jessie" -- include: main_stretch.yml +- ansible.builtin.import_tasks: main_stretch.yml when: ansible_distribution_release == "stretch" -- include: main_buster.yml +- ansible.builtin.import_tasks: main_buster.yml when: ansible_distribution_release == "buster" -- include: main_bullseye.yml - when: ansible_distribution_major_version is version('11', '>=') +- ansible.builtin.import_tasks: main_bullseye.yml + when: ansible_distribution_release == "bullseye" + +- ansible.builtin.import_tasks: main_bookworm.yml + when: ansible_distribution_release == "bookworm" diff --git a/mongodb/tasks/main_bookworm.yml b/mongodb/tasks/main_bookworm.yml new file mode 100644 index 00000000..93989230 --- /dev/null +++ b/mongodb/tasks/main_bookworm.yml @@ -0,0 +1,103 @@ +--- + +- ansible.builtin.fail: + msg: MongoDB is not compatible with Debian 12 (Bookworm) + when: + - ansible_distribution_release == "bookworm" + +# - fail: +# msg: MongoDB version <5 are not compatible with Debian 12 (Bookworm) +# when: +# - ansible_distribution_release == "bookworm" +# - mongodb_version is version('5.0', '<') + +- name: Add MongoDB repository + ansible.builtin.template: + src: mongodb.sources.j2 + dest: /etc/apt/sources.list.d/mongodb.sources + state: present + register: mongodb_sources + +- name: Update APT cache + ansible.builtin.apt: + update_cache: yes + when: mongodb_sources is changed + +- name: Install packages + ansible.builtin.apt: + name: mongodb-org + state: present + register: _mongodb_install_package + +- name: MongoDB service in enabled and started + ansible.builtin.systemd: + name: mongod + enabled: yes + state: started + when: _mongodb_install_package is changed + +- name: install dependency for monitoring + ansible.builtin.apt: + name: python3-pymongo + state: present + +- name: Custom configuration + ansible.builtin.template: + src: mongodb_bullseye.conf.j2 + dest: "/etc/mongod.conf" + force: "{{ mongodb_force_config | bool | ternary('yes', 'no') }}" + notify: restart mongod + +- name: Configure logrotate + ansible.builtin.template: + src: logrotate_bullseye.j2 + dest: /etc/logrotate.d/mongodb + force: true + backup: no + +- ansible.builtin.include_role: + name: evolix/remount-usr + +- name: Create plugin directory + ansible.builtin.file: + name: /usr/local/share/munin/ + state: directory + mode: "0755" + +- name: Create plugin directory + ansible.builtin.file: + name: /usr/local/share/munin/plugins/ + state: directory + mode: "0755" + +- name: Munin plugins are present + ansible.builtin.copy: + src: "munin/{{ item }}" + dest: '/usr/local/share/munin/plugins/{{ item }}' + force: true + loop: + - mongo_btree + - mongo_collections + - mongo_conn + - mongo_docs + - mongo_lock + - mongo_mem + - mongo_ops + - mongo_page_faults + notify: restart munin-node + +- name: Enable core Munin plugins + ansible.builtin.file: + src: '/usr/local/share/munin/plugins/{{ item }}' + dest: /etc/munin/plugins/{{ item }} + state: link + loop: + - mongo_btree + - mongo_collections + - mongo_conn + - mongo_docs + - mongo_lock + - mongo_mem + - mongo_ops + - mongo_page_faults + notify: restart munin-node diff --git a/mongodb/tasks/main_bullseye.yml b/mongodb/tasks/main_bullseye.yml index e31ffed3..0cfebf20 100644 --- a/mongodb/tasks/main_bullseye.yml +++ b/mongodb/tasks/main_bullseye.yml @@ -1,88 +1,79 @@ --- -- fail: - msg: Not compatible with Debian 11 (Bullseye) +- ansible.builtin.fail: + msg: MongoDB versions <4.2 are not compatible with Debian 11 (Bullseye) when: - - ansible_distribution_release == "bullseye" - - mongodb_version is version_compare('5.0', '<=') - - -- name: MongoDB embedded GPG key is absent - apt_key: - id: "B8612B5D" - keyring: /etc/apt/trusted.gpg - state: absent - when: _trusted_gpg_keyring.stat.exists + - ansible_distribution_release == "bullseye" + - mongodb_version is version('5.2', '<') - name: Add MongoDB GPG key - copy: - src: "server-{{mongodb_version}}.asc" - dest: "/etc/apt/trusted.gpg.d/mongodb-server-{{mongodb_version}}.asc" - force: yes + ansible.builtin.copy: + src: "server-{{ mongodb_version }}.asc" + dest: "{{ apt_keyring_dir }}/mongodb-server-{{ mongodb_version }}.asc" + force: true mode: "0644" owner: root group: root -- name: enable APT sources list - apt_repository: - repo: "deb http://repo.mongodb.org/apt/debian buster/mongodb-org/{{mongodb_version}} main" +- name: Add MongoDB repository + ansible.builtin.apt_repository: + repo: "deb [signed-by={{ apt_keyring_dir }}/mongodb-server-{{ mongodb_version }}.asc] http://repo.mongodb.org/apt/debian bullseye/mongodb-org/{{ mongodb_version }} main" state: present - filename: "mongodb-org-{{mongodb_version}}" - update_cache: yes + filename: "mongodb-org-{{ mongodb_version }}" - name: Install packages - apt: + ansible.builtin.apt: name: mongodb-org update_cache: yes state: present register: _mongodb_install_package - name: MongoDB service in enabled and started - systemd: + ansible.builtin.systemd: name: mongod enabled: yes state: started when: _mongodb_install_package is changed - name: install dependency for monitoring - apt: - name: python-pymongo + ansible.builtin.apt: + name: python3-pymongo state: present - name: Custom configuration - template: - src: mongodb_buster.conf.j2 + ansible.builtin.template: + src: mongodb_bullseye.conf.j2 dest: "/etc/mongod.conf" force: "{{ mongodb_force_config | bool | ternary('yes', 'no') }}" notify: restart mongod - name: Configure logrotate - template: - src: logrotate_buster.j2 + ansible.builtin.template: + src: logrotate_bullseye.j2 dest: /etc/logrotate.d/mongodb - force: yes + force: true backup: no -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - name: Create plugin directory - file: + ansible.builtin.file: name: /usr/local/share/munin/ state: directory mode: "0755" - name: Create plugin directory - file: + ansible.builtin.file: name: /usr/local/share/munin/plugins/ state: directory mode: "0755" - name: Munin plugins are present - copy: + ansible.builtin.copy: src: "munin/{{ item }}" dest: '/usr/local/share/munin/plugins/{{ item }}' - force: yes + force: true loop: - mongo_btree - mongo_collections @@ -95,7 +86,7 @@ notify: restart munin-node - name: Enable core Munin plugins - file: + ansible.builtin.file: src: '/usr/local/share/munin/plugins/{{ item }}' dest: /etc/munin/plugins/{{ item }} state: link diff --git a/mongodb/tasks/main_buster.yml b/mongodb/tasks/main_buster.yml index cf5ce2ae..7d47ed25 100644 --- a/mongodb/tasks/main_buster.yml +++ b/mongodb/tasks/main_buster.yml @@ -1,86 +1,93 @@ --- - name: Look for legacy apt keyring - stat: + ansible.builtin.stat: path: /etc/apt/trusted.gpg register: _trusted_gpg_keyring - name: MongoDB embedded GPG key is absent - apt_key: + ansible.builtin.apt_key: id: "B8612B5D" keyring: /etc/apt/trusted.gpg state: absent when: _trusted_gpg_keyring.stat.exists - name: Add MongoDB GPG key - copy: - src: "server-{{mongodb_version}}.asc" - dest: "/etc/apt/trusted.gpg.d/mongodb-server-{{mongodb_version}}.asc" - force: yes + ansible.builtin.copy: + src: "server-{{ mongodb_version }}.asc" + dest: "{{ apt_keyring_dir }}/mongodb-server-{{ mongodb_version }}.asc" + force: true mode: "0644" owner: root group: root -- name: enable APT sources list - apt_repository: - repo: "deb http://repo.mongodb.org/apt/debian buster/mongodb-org/{{mongodb_version}} main" +- name: Enable APT sources list + ansible.builtin.apt_repository: + repo: "deb [signed-by={{ apt_keyring_dir }}/mongodb-server-{{ mongodb_version }}.asc] http://repo.mongodb.org/apt/debian buster/mongodb-org/{{ mongodb_version }} main" state: present - filename: "mongodb-org-{{mongodb_version}}" + filename: "mongodb-org-{{ mongodb_version }}" + update_cache: yes + +- name: Disable unsigned APT sources list + ansible.builtin.apt_repository: + repo: "deb http://repo.mongodb.org/apt/debian buster/mongodb-org/{{ mongodb_version }} main" + state: absent + filename: "mongodb-org-{{ mongodb_version }}" update_cache: yes - name: Install packages - apt: + ansible.builtin.apt: name: mongodb-org update_cache: yes state: present register: _mongodb_install_package - name: MongoDB service in enabled and started - systemd: + ansible.builtin.systemd: name: mongod enabled: yes state: started when: _mongodb_install_package is changed - name: install dependency for monitoring - apt: + ansible.builtin.apt: name: python-pymongo state: present - name: Custom configuration - template: + ansible.builtin.template: src: mongodb_buster.conf.j2 dest: "/etc/mongod.conf" force: "{{ mongodb_force_config | bool | ternary('yes', 'no') }}" notify: restart mongod - name: Configure logrotate - template: + ansible.builtin.template: src: logrotate_buster.j2 dest: /etc/logrotate.d/mongodb - force: yes + force: true backup: no -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - name: Create plugin directory - file: + ansible.builtin.file: name: /usr/local/share/munin/ state: directory mode: "0755" - name: Create plugin directory - file: + ansible.builtin.file: name: /usr/local/share/munin/plugins/ state: directory mode: "0755" - name: Munin plugins are present - copy: + ansible.builtin.copy: src: "munin/{{ item }}" dest: '/usr/local/share/munin/plugins/{{ item }}' - force: yes + force: true loop: - mongo_btree - mongo_collections @@ -93,7 +100,7 @@ notify: restart munin-node - name: Enable core Munin plugins - file: + ansible.builtin.file: src: '/usr/local/share/munin/plugins/{{ item }}' dest: /etc/munin/plugins/{{ item }} state: link diff --git a/mongodb/tasks/main_jessie.yml b/mongodb/tasks/main_jessie.yml index db69c7c7..50767d68 100644 --- a/mongodb/tasks/main_jessie.yml +++ b/mongodb/tasks/main_jessie.yml @@ -1,38 +1,61 @@ --- -- name: MongoDB public GPG Key - apt_key: - # url: https://www.mongodb.org/static/pgp/server-3.4.asc - data: "{{ lookup('file', 'server-3.4.asc') }}" +- name: Look for legacy apt keyring + ansible.builtin.stat: + path: /etc/apt/trusted.gpg + register: _trusted_gpg_keyring -- name: enable APT sources list - apt_repository: - repo: deb http://repo.mongodb.org/apt/debian jessie/mongodb-org/3.4 main +- name: MongoDB embedded GPG key is absent + ansible.builtin.apt_key: + id: "B8612B5D" + keyring: /etc/apt/trusted.gpg + state: absent + when: _trusted_gpg_keyring.stat.exists + +- name: Add MongoDB GPG key + ansible.builtin.copy: + src: "server-{{ mongodb_version }}.asc" + dest: "/etc/apt/trusted.gpg.d/mongodb-server-{{ mongodb_version }}.asc" + force: true + mode: "0644" + owner: root + group: root + +- name: Enable APT sources list + ansible.builtin.apt_repository: + repo: "deb http://repo.mongodb.org/apt/debian jessie/mongodb-org/{{ mongodb_version }} main" state: present - filename: mongodb + filename: "mongodb-org-{{ mongodb_version }}" + update_cache: yes + +- name: Disable APT sources list + ansible.builtin.apt_repository: + repo: "deb http://repo.mongodb.org/apt/debian jessie/mongodb-org/{{ mongodb_version }} main" + state: absent + filename: "mongodb-org-{{ mongodb_version }}" update_cache: yes - name: Install packages - apt: + ansible.builtin.apt: name: mongodb-org allow_unauthenticated: yes state: present - name: install dependency for monitoring - apt: + ansible.builtin.apt: name: python-pymongo state: present - name: Custom configuration - template: + ansible.builtin.template: src: mongod_jessie.conf.j2 dest: "/etc/mongod.conf" force: "{{ mongodb_force_config | bool | ternary('yes', 'no') }}" notify: restart mongod - name: Configure logrotate - template: + ansible.builtin.template: src: logrotate_jessie.j2 dest: /etc/logrotate.d/mongodb - force: yes + force: true backup: no diff --git a/mongodb/tasks/main_stretch.yml b/mongodb/tasks/main_stretch.yml index fe44e259..5b9a84c0 100644 --- a/mongodb/tasks/main_stretch.yml +++ b/mongodb/tasks/main_stretch.yml @@ -1,38 +1,39 @@ --- - name: Install packages - apt: + ansible.builtin.apt: name: - mongodb - mongo-tools state: present - name: install dependency for monitoring - apt: + ansible.builtin.apt: name: python-pymongo state: present - name: Custom configuration - template: + ansible.builtin.template: src: mongodb_stretch.conf.j2 dest: "/etc/mongodb.conf" force: "{{ mongodb_force_config | bool | ternary('yes', 'no') }}" notify: restart mongodb - name: enable service - service: + ansible.builtin.service: name: mongodb enabled: yes - name: Configure logrotate - template: + ansible.builtin.template: src: logrotate_stretch.j2 dest: /etc/logrotate.d/mongodb-server - force: yes + force: true backup: no - name: disable previous logrotate - command: mv /etc/logrotate.d/mongodb /etc/logrotate.d/mongodb.disabled + ansible.builtin.command: + cmd: mv /etc/logrotate.d/mongodb /etc/logrotate.d/mongodb.disabled args: removes: /etc/logrotate.d/mongodb creates: /etc/logrotate.d/mongodb.disabled diff --git a/mongodb/templates/mongodb.sources.j2 b/mongodb/templates/mongodb.sources.j2 new file mode 100644 index 00000000..ab55d938 --- /dev/null +++ b/mongodb/templates/mongodb.sources.j2 @@ -0,0 +1,8 @@ +# {{ansible_managed }} + +Types: deb +URIs: http://repo.mongodb.org/apt/debian +Suites: bookworm/mongodb-org/{{ mongodb_version }} +Components: main +Signed-by: {{ apt_keyring_dir }}/mongodb-server-{{ mongodb_version }}.asc +Enabled: yes \ No newline at end of file diff --git a/monit/handlers/main.yml b/monit/handlers/main.yml index d7900061..51beff76 100644 --- a/monit/handlers/main.yml +++ b/monit/handlers/main.yml @@ -1,11 +1,11 @@ --- - name: reload monit - service: + ansible.builtin.service: name: monit state: reloaded - name: restart monit - service: + ansible.builtin.service: name: monit state: restarted diff --git a/monit/tasks/main.yml b/monit/tasks/main.yml index fcdd0b4c..65deb5f9 100644 --- a/monit/tasks/main.yml +++ b/monit/tasks/main.yml @@ -1,7 +1,7 @@ --- - name: monit is installed - apt: + ansible.builtin.apt: name: monit state: present tags: @@ -9,11 +9,11 @@ - packages - name: custom config is installed - template: + ansible.builtin.template: src: evolinux-defaults.conf.j2 dest: /etc/monit/conf.d/z-evolinux-defaults.conf mode: "0640" - force: yes + force: true notify: restart monit tags: - monit 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/handlers/main.yml b/munin/handlers/main.yml index 8654181d..76782bf8 100644 --- a/munin/handlers/main.yml +++ b/munin/handlers/main.yml @@ -1,15 +1,15 @@ --- - name: restart munin-node - service: + ansible.builtin.service: name: munin-node state: restarted - name: restart munin_node - service: + ansible.builtin.service: name: munin_node state: restarted - name: systemd daemon-reload - systemd: + ansible.builtin.systemd: daemon_reload: yes \ No newline at end of file diff --git a/munin/tasks/main.yml b/munin/tasks/main.yml index d7cf8e2a..53aad7d0 100644 --- a/munin/tasks/main.yml +++ b/munin/tasks/main.yml @@ -1,27 +1,33 @@ --- -- name: Ensure that Munin is installed - apt: +- name: Ensure that Munin (and useful dependencies) is installed + ansible.builtin.apt: name: - munin - munin-node - munin-plugins-core - munin-plugins-extra + - gawk state: present tags: - munin - packages +- name: Ensure /usr is still writable + ansible.builtin.include_role: + name: evolix/remount-usr + - block: - name: Replace localdomain in Munin config - replace: + ansible.builtin.replace: dest: /etc/munin/munin.conf regexp: 'localhost.localdomain' replace: '{{ ansible_fqdn }}' notify: restart munin-node - name: Rename the localdomain data dir - shell: "mv /var/lib/munin/localdomain /var/lib/munin/{{ ansible_domain }} && rename \"s/localhost.localdomain/{{ ansible_fqdn }}/\" /var/lib/munin/{{ ansible_domain }}/*" + ansible.builtin.shell: + cmd: "mv /var/lib/munin/localdomain /var/lib/munin/{{ ansible_domain }} && rename \"s/localhost.localdomain/{{ ansible_fqdn }}/\" /var/lib/munin/{{ ansible_domain }}/*" args: creates: /var/lib/munin/{{ ansible_domain }} removes: /var/lib/munin/localdomain @@ -31,8 +37,20 @@ tags: - munin +- ansible.builtin.include_role: + name: evolix/remount-usr + +- name: Install some Munin plugins (disabled) + ansible.builtin.copy: + src: 'plugins/{{ item }}' + dest: '/usr/share/munin/plugins/{{ item }}' + loop: + - dhcp_pool + tags: + - munin + - name: Ensure some Munin plugins are disabled - file: + ansible.builtin.file: path: '/etc/munin/plugins/{{ item }}' state: absent loop: @@ -48,7 +66,7 @@ - munin - name: Ensure some Munin plugins are enabled - file: + ansible.builtin.file: src: "/usr/share/munin/plugins/{{ item }}" dest: "/etc/munin/plugins/{{ item }}" state: link @@ -63,18 +81,34 @@ tags: - munin -- name: Enable sensors plugin unless VM detected - file: +- name: Enable sensors_ plugin on dedicated hardware + ansible.builtin.file: src: /usr/share/munin/plugins/sensors_ - dest: /etc/munin/plugins/sensors_temp + dest: "/etc/munin/plugins/sensors_{{ item }}" state: link - when: ansible_virtualization_role != "guest" + with_items: + - fan + - temp + when: ansible_virtualization_role == "host" notify: restart munin-node tags: - munin +- name: Enable ipmi_ plugin on dedicated hardware + ansible.builtin.file: + src: /usr/share/munin/plugins/ipmi_ + dest: "/etc/munin/plugins/ipmi_{{ item }}" + state: link + when: ansible_virtualization_role == "host" + notify: restart munin-node + with_items: + - fans + - temp + - power + - volts + - name: adjustments for grsec kernel - blockinfile: + ansible.builtin.blockinfile: dest: /etc/munin/plugin-conf.d/munin-node marker: "# {mark} ANSIBLE MANAGED GRSECURITY CUSTOMIZATIONS" block: | @@ -90,13 +124,13 @@ when: ansible_kernel is search("-grs-") - name: Create override directory for munin-node unit - file: + ansible.builtin.file: name: /etc/systemd/system/munin-node.service.d/ state: directory mode: "0755" - name: Override is present for protected home - ini_file: + community.general.ini_file: dest: "/etc/systemd/system/munin-node.service.d/override.conf" section: "Service" option: "ProtectHome" diff --git a/mysql-oracle/files/mysqltuner.cron.sh b/mysql-oracle/files/mysqltuner.cron.sh index 5424aa90..ada4a0f8 100644 --- a/mysql-oracle/files/mysqltuner.cron.sh +++ b/mysql-oracle/files/mysqltuner.cron.sh @@ -44,7 +44,7 @@ Bien à vous, -- Rapport automatique Evolix EOT - mutt -x -e 'set send_charset="utf-8"' -H $template \ + mutt -x -e 'set send_charset="utf-8"' -e "set crypt_use_gpgme=no" -H $template \ -a /var/www/mysqlreport_${instance}.html < $body done chmod 644 /var/www/mysqlreport*html diff --git a/mysql-oracle/handlers/main.yml b/mysql-oracle/handlers/main.yml index c89d562a..eef49ef5 100644 --- a/mysql-oracle/handlers/main.yml +++ b/mysql-oracle/handlers/main.yml @@ -1,28 +1,29 @@ --- - name: restart munin-node - service: + ansible.builtin.service: name: munin-node state: restarted - name: restart nagios-nrpe-server - service: + ansible.builtin.service: name: nagios-nrpe-server state: restarted - name: restart mysql - service: + ansible.builtin.service: name: mysql state: restarted - name: restart mysql (noop) - meta: noop + ansible.builtin.meta: noop failed_when: False changed_when: False - name: reload systemd - systemd: + ansible.builtin.systemd: name: mysql daemon_reload: yes - name: Restart minifirewall - command: /etc/init.d/minifirewall restart + ansible.builtin.command: + cmd: /etc/init.d/minifirewall restart diff --git a/mysql-oracle/tasks/config.yml b/mysql-oracle/tasks/config.yml index 16590a59..0a3370ab 100644 --- a/mysql-oracle/tasks/config.yml +++ b/mysql-oracle/tasks/config.yml @@ -1,26 +1,26 @@ --- -- set_fact: +- ansible.builtin.set_fact: mysql_config_directory: "/etc/mysql/mysql.conf.d" - name: "Copy MySQL defaults config file" - copy: + ansible.builtin.copy: src: evolinux-defaults.cnf dest: "{{ mysql_config_directory }}/z-evolinux-defaults.cnf" owner: root group: root mode: "0644" - force: yes + force: true tags: - mysql - name: "Copy MySQL custom config file" - template: + ansible.builtin.template: src: evolinux-custom.cnf.j2 dest: "{{ mysql_config_directory }}/zzz-evolinux-custom.cnf" owner: root group: root mode: "0644" - force: no + force: false tags: - mysql diff --git a/mysql-oracle/tasks/datadir.yml b/mysql-oracle/tasks/datadir.yml index c375f5d5..d28d6440 100644 --- a/mysql-oracle/tasks/datadir.yml +++ b/mysql-oracle/tasks/datadir.yml @@ -2,13 +2,14 @@ - block: - name: "Is {{ mysql_custom_datadir }} present ?" - stat: + ansible.builtin.stat: path: "{{ mysql_custom_datadir }}" check_mode: no register: mysql_custom_datadir_test - name: "read the real datadir" - command: readlink -f /var/lib/mysql + ansible.builtin.command: + cmd: readlink -f /var/lib/mysql changed_when: False check_mode: no register: mysql_current_real_datadir_test @@ -18,23 +19,24 @@ - block: - name: MySQL is stopped - service: + ansible.builtin.service: name: mysql state: stopped - name: Move MySQL datadir to {{ mysql_custom_datadir }} - command: mv {{ mysql_current_real_datadir_test.stdout }} {{ mysql_custom_datadir }} + ansible.builtin.command: + cmd: mv {{ mysql_current_real_datadir_test.stdout }} {{ mysql_custom_datadir }} args: creates: "{{ mysql_custom_datadir }}" - name: Symlink {{ mysql_custom_datadir }} to /var/lib/mysql - file: + ansible.builtin.file: src: "{{ mysql_custom_datadir }}" dest: '/var/lib/mysql' state: link - name: MySQL is started - service: + ansible.builtin.service: name: mysql state: started tags: diff --git a/mysql-oracle/tasks/log2mail.yml b/mysql-oracle/tasks/log2mail.yml index 568b6649..4eee01c8 100644 --- a/mysql-oracle/tasks/log2mail.yml +++ b/mysql-oracle/tasks/log2mail.yml @@ -1,7 +1,7 @@ --- - name: Is log2mail present ? - stat: + ansible.builtin.stat: path: /etc/log2mail/config check_mode: no register: log2mail_config_dir @@ -10,7 +10,7 @@ - log2mail - name: Copy log2mail config - template: + ansible.builtin.template: src: log2mail.j2 dest: /etc/log2mail/config/mysql.conf owner: log2mail diff --git a/mysql-oracle/tasks/main.yml b/mysql-oracle/tasks/main.yml index 2e2f09bf..1e928681 100644 --- a/mysql-oracle/tasks/main.yml +++ b/mysql-oracle/tasks/main.yml @@ -1,22 +1,22 @@ --- -- set_fact: +- ansible.builtin.set_fact: mysql_restart_handler_name: "{{ mysql_restart_if_needed | bool | ternary('restart mysql', 'restart mysql (noop)') }}" -- include: packages.yml +- ansible.builtin.include: packages.yml -- include: users.yml +- ansible.builtin.include: users.yml -- include: config.yml +- ansible.builtin.include: config.yml -- include: datadir.yml +- ansible.builtin.include: datadir.yml -- include: tmpdir.yml +- ansible.builtin.include: tmpdir.yml -- include: nrpe.yml +- ansible.builtin.include: nrpe.yml -- include: munin.yml +- ansible.builtin.include: munin.yml -- include: log2mail.yml +- ansible.builtin.include: log2mail.yml -- include: utils.yml +- ansible.builtin.include: utils.yml diff --git a/mysql-oracle/tasks/munin.yml b/mysql-oracle/tasks/munin.yml index b9e633b0..bed33556 100644 --- a/mysql-oracle/tasks/munin.yml +++ b/mysql-oracle/tasks/munin.yml @@ -1,7 +1,7 @@ --- - name: is Munin present ? - stat: + ansible.builtin.stat: path: /etc/munin/plugin-conf.d/munin-node check_mode: no register: munin_node_plugins_config @@ -11,14 +11,14 @@ - block: - name: Install perl libraries for Munin - apt: + ansible.builtin.apt: name: - libdbd-mysql-perl - libcache-cache-perl state: present - name: Enable core Munin plugins - file: + ansible.builtin.file: src: '/usr/share/munin/plugins/{{ item }}' dest: /etc/munin/plugins/{{ item }} state: link @@ -30,7 +30,7 @@ notify: restart munin-node - name: Enable contributed Munin plugins - file: + ansible.builtin.file: src: /usr/share/munin/plugins/mysql_ dest: '/etc/munin/plugins/mysql_{{ item }}' state: link diff --git a/mysql-oracle/tasks/nrpe.yml b/mysql-oracle/tasks/nrpe.yml index c3457699..cce8e4b7 100644 --- a/mysql-oracle/tasks/nrpe.yml +++ b/mysql-oracle/tasks/nrpe.yml @@ -1,7 +1,7 @@ --- - name: is NRPE present ? - stat: + ansible.builtin.stat: path: /etc/nagios/nrpe.d/evolix.cfg check_mode: no register: nrpe_evolix_config @@ -10,7 +10,7 @@ - nrpe - name: NRPE user exists for MySQL ? - stat: + ansible.builtin.stat: path: ~nagios/.my.cnf check_mode: no register: nrpe_my_cnf @@ -20,13 +20,14 @@ - block: - name: Create a password for NRPE - command: "apg -n 1 -m 16 -M lcN" + ansible.builtin.command: + cmd: "apg -n 1 -m 16 -M lcN" register: mysql_nrpe_password check_mode: no changed_when: False - name: Create nrpe user - mysql_user: + community.mysql.mysql_user: name: nrpe password: '{{ mysql_nrpe_password.stdout }}' priv: "*.*:REPLICATION CLIENT" @@ -36,7 +37,7 @@ register: create_nrpe_user - name: Store credentials in nagios home - ini_file: + community.general.ini_file: dest: "~nagios/.my.cnf" owner: nagios group: nagios diff --git a/mysql-oracle/tasks/packages.yml b/mysql-oracle/tasks/packages.yml index 5bf8848e..ede629f5 100644 --- a/mysql-oracle/tasks/packages.yml +++ b/mysql-oracle/tasks/packages.yml @@ -1,43 +1,43 @@ --- -- set_fact: +- ansible.builtin.set_fact: mysql_apt_config_package: mysql-apt-config_0.8.9-1_all.deb - name: Set default MySQL version to 5.7 - debconf: + ansible.builtin.debconf: name: mysql-apt-config question: mysql-apt-config/enable-repo value: mysql-5.7 vtype: select - name: MySQL APT config package is available - copy: + ansible.builtin.copy: src: "{{ mysql_apt_config_package }}" dest: "/root/{{ mysql_apt_config_package }}" -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - name: MySQL APT config package is installed - apt: + ansible.builtin.apt: deb: "/root/{{ mysql_apt_config_package }}" state: present register: mysql_apt_config_deb - name: Open firewall for MySQL.com repository - replace: + ansible.builtin.replace: name: /etc/default/minifirewall regexp: "^(HTTPSITES='((?!(repo\\.mysql\\.com|0\\.0\\.0\\.0)).)*)'$" replace: "\\1 repo.mysql.com'" notify: Restart minifirewall -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - name: /usr/share/mysql exists - file: + ansible.builtin.file: dest: /usr/share/mysql/ mode: "0755" owner: root @@ -45,31 +45,31 @@ state: directory - name: mysql-systemd-start scripts is installed - copy: + ansible.builtin.copy: src: debian/mysql-systemd-start dest: /usr/share/mysql/mysql-systemd-start mode: "0755" owner: root group: root - force: yes + force: true - name: systemd unit is installed - copy: + ansible.builtin.copy: src: debian/mysql-server-5.7.mysql.service dest: /etc/systemd/system/mysql.service mode: "0644" owner: root group: root - force: yes + force: true register: mysql_systemd_unit - name: APT cache is up-to-date - apt: + ansible.builtin.apt: update_cache: yes when: mysql_apt_config_deb is changed - name: Install MySQL packages - apt: + ansible.builtin.apt: name: - mysql-server - mysql-client @@ -80,7 +80,7 @@ - packages - name: Install MySQL dev packages - apt: + ansible.builtin.apt: name: libmysqlclient20 update_cache: yes state: present @@ -90,7 +90,7 @@ when: mysql_install_libclient | bool - name: MySQL is started - systemd: + ansible.builtin.systemd: name: mysql daemon_reload: yes state: started @@ -99,7 +99,7 @@ - services - name: apg package is installed - apt: + ansible.builtin.apt: name: apg state: present tags: diff --git a/mysql-oracle/tasks/tmpdir.yml b/mysql-oracle/tasks/tmpdir.yml index 790a9f2e..d293ea82 100644 --- a/mysql-oracle/tasks/tmpdir.yml +++ b/mysql-oracle/tasks/tmpdir.yml @@ -2,7 +2,7 @@ - block: - name: "Create {{ mysql_custom_tmpdir }}" - file: + ansible.builtin.file: path: "{{ mysql_custom_tmpdir }}" owner: mysql group: mysql @@ -12,7 +12,7 @@ - mysql - name: Configure tmpdir - ini_file: + community.general.ini_file: dest: "{{ mysql_config_directory }}/zzz-evolinux-custom.cnf" section: mysqld option: tmpdir diff --git a/mysql-oracle/tasks/users.yml b/mysql-oracle/tasks/users.yml index d0c444e5..62923f27 100644 --- a/mysql-oracle/tasks/users.yml +++ b/mysql-oracle/tasks/users.yml @@ -1,7 +1,7 @@ --- - name: Python2 dependencies for Ansible are installed - apt: + ansible.builtin.apt: name: - python-mysqldb - python-pymysql @@ -11,7 +11,7 @@ when: ansible_python_version is version('3', '<') - name: Python3 dependencies for Ansible are installed - apt: + ansible.builtin.apt: name: - python3-mysqldb - python3-pymysql @@ -21,14 +21,15 @@ when: ansible_python_version is version('3', '>=') - name: create a password for mysqladmin - command: "apg -n 1 -m 16 -M lcN" + ansible.builtin.command: + cmd: "apg -n 1 -m 16 -M lcN" register: mysql_admin_password changed_when: False tags: - mysql - name: there is a mysqladmin user - mysql_user: + community.mysql.mysql_user: name: mysqladmin password: '{{ mysql_admin_password.stdout }}' priv: "*.*:ALL,GRANT" @@ -41,7 +42,7 @@ - mysql - name: mysqladmin is the default user - ini_file: + community.general.ini_file: dest: /root/.my.cnf mode: "0600" section: client @@ -57,14 +58,15 @@ - name: create a password for debian-sys-maint - command: "apg -n 1 -m 16 -M lcN" + ansible.builtin.command: + cmd: "apg -n 1 -m 16 -M lcN" register: mysql_debian_password changed_when: False tags: - mysql - name: there is a debian-sys-maint user - mysql_user: + community.mysql.mysql_user: name: debian-sys-maint password: '{{ mysql_debian_password.stdout }}' priv: "*.*:ALL,GRANT" @@ -76,7 +78,7 @@ - mysql - name: store debian-sys-maint user credentials - ini_file: + community.general.ini_file: dest: /etc/mysql/debian.cnf mode: "0600" section: "{{ item[0] }}" @@ -94,7 +96,7 @@ - mysql - name: remove root user - mysql_user: + community.mysql.mysql_user: name: root host_all: yes config_file: "/root/.my.cnf" diff --git a/mysql-oracle/tasks/utils.yml b/mysql-oracle/tasks/utils.yml index e0520cee..2504eaa2 100644 --- a/mysql-oracle/tasks/utils.yml +++ b/mysql-oracle/tasks/utils.yml @@ -1,14 +1,14 @@ --- -- set_fact: +- ansible.builtin.set_fact: _mysql_scripts_dir: "{{ mysql_scripts_dir | default(general_scripts_dir, True) | mandatory }}" -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - when: _mysql_scripts_dir is search ("/usr") + when: _mysql_scripts_dir is search("/usr") - name: Scripts directory exists - file: + ansible.builtin.file: dest: "{{ _mysql_scripts_dir }}" mode: "0700" state: directory @@ -18,7 +18,7 @@ # mytop - name: "mytop is installed (Debian 9)" - apt: + ansible.builtin.apt: name: mytop state: present tags: @@ -33,7 +33,7 @@ # when: ansible_distribution_major_version is version('9', '>=') - name: "mytop dependencies are installed (Buster)" - apt: + ansible.builtin.apt: name: - libconfig-inifiles-perl - libdbd-mysql-perl @@ -47,7 +47,7 @@ when: ansible_distribution_release == "stretch" - name: "Install dependencies for mytop (Debian 10)" - apt: + ansible.builtin.apt: name: - mariadb-client-10.3 - libconfig-inifiles-perl @@ -55,34 +55,35 @@ when: ansible_distribution_release == "buster" - name: "Install dependencies for mytop (Debian 11 or later)" - apt: + ansible.builtin.apt: name: - mariadb-client-10.5 - libconfig-inifiles-perl - libterm-readkey-perl when: ansible_distribution_major_version is version('11', '>=') -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr tags: - mytop - mysql - name: "mytop is installed (Debian 9 or later)" - copy: + ansible.builtin.copy: src: mytop dest: /usr/local/bin/mytop mode: "0755" owner: root group: staff - force: yes + force: true tags: - mytop - mysql when: ansible_distribution_major_version is version('9', '>=') - name: Read debian-sys-maint password - shell: 'cat /etc/mysql/debian.cnf | grep -m1 "password = .*" | cut -d" " -f3' + ansible.builtin.shell: + cmd: 'cat /etc/mysql/debian.cnf | grep -m1 "password = .*" | cut -d" " -f3' register: mysql_debian_password changed_when: False check_mode: no @@ -91,29 +92,29 @@ - mysql - name: mytop configuration is copied - template: + ansible.builtin.template: src: mytop-config.j2 dest: /root/.mytop mode: "0600" - force: yes + force: true tags: - mytop - mysql # mysqltuner -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr tags: - mysql - when: _mysql_scripts_dir is search ("/usr") + when: _mysql_scripts_dir is search("/usr") - name: mysqltuner is installed # copy: # src: mysqltuner.pl # dest: "{{ _mysql_scripts_dir }}/mysqltuner.pl" # mode: "0700" - apt: + ansible.builtin.apt: name: mysqltuner state: present tags: @@ -121,21 +122,21 @@ - mysqltuner - name: aha is installed - apt: + ansible.builtin.apt: name: aha tags: - mysql # automatic optimizations -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr tags: - mysql - when: _mysql_scripts_dir is search ("/usr") + when: _mysql_scripts_dir is search("/usr") - name: mysql-optimize.sh is installed - copy: + ansible.builtin.copy: src: mysql-optimize.sh dest: "{{ _mysql_scripts_dir }}/mysql-optimize.sh" mode: "0700" @@ -143,7 +144,7 @@ - mysql - name: "Cron dir for optimize is present" - file: + ansible.builtin.file: path: "/etc/cron.{{ mysql_cron_optimize_frequency | mandatory }}" state: directory mode: "0755" @@ -153,7 +154,7 @@ - mysql - name: "Enable cron to optimize MySQL" - file: + ansible.builtin.file: src: "{{ _mysql_scripts_dir }}/mysql-optimize.sh" dest: /etc/cron.{{ mysql_cron_optimize_frequency | mandatory }}/mysql-optimize.sh state: link @@ -162,7 +163,7 @@ - mysql - name: "Disable cron to optimize MySQL" - file: + ansible.builtin.file: dest: /etc/cron.{{ mysql_cron_optimize_frequency | mandatory }}/mysql-optimize.sh state: absent when: not (mysql_cron_optimize | bool) @@ -170,7 +171,7 @@ - mysql - name: "Cron dir for mysqltuner is present" - file: + ansible.builtin.file: path: "/etc/cron.{{ mysql_cron_mysqltuner_frequency | mandatory }}" state: directory mode: "0755" @@ -181,7 +182,7 @@ - mysqltuner - name: "Enable mysqltuner in cron" - copy: + ansible.builtin.copy: src: mysqltuner.cron.sh dest: /etc/cron.{{ mysql_cron_mysqltuner_frequency | mandatory }}/mysqltuner.sh mode: "0755" @@ -191,7 +192,7 @@ - mysqltuner - name: "Disable mysqltuner in cron" - file: + ansible.builtin.file: dest: /etc/cron.{{ mysql_cron_mysqltuner_frequency | mandatory }}/mysqltuner.sh state: absent when: not (mysql_cron_mysqltuner | bool) @@ -201,12 +202,12 @@ # my-add.sh -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - when: _mysql_scripts_dir is search ("/usr") + when: _mysql_scripts_dir is search("/usr") - name: Install my-add.sh - copy: + ansible.builtin.copy: src: my-add.sh dest: "{{ _mysql_scripts_dir }}/my-add.sh" mode: "0700" diff --git a/mysql/defaults/main.yml b/mysql/defaults/main.yml index 80f526c6..af43f495 100644 --- a/mysql/defaults/main.yml +++ b/mysql/defaults/main.yml @@ -50,8 +50,10 @@ mysql_restart_if_needed: True mysql_performance_schema: True +mysql_skip_enabled: False + # replication variables: -mysql_replication: false +mysql_replication: False mysql_log_bin: null mysql_binlog_format: mixed mysql_server_id: null diff --git a/mysql/files/evomariabackup.sh b/mysql/files/evomariabackup.sh index 0e3de84b..df8a3884 100644 --- a/mysql/files/evomariabackup.sh +++ b/mysql/files/evomariabackup.sh @@ -1,12 +1,12 @@ #!/bin/sh -VERSION="21.11" +VERSION="22.06.1" show_version() { cat <, +Copyright 2004-2022 Evolix , Éric Morino , Jérémy Lecour and others. @@ -20,24 +20,33 @@ show_help() { cat < "${lock_file}" - log_debug "Lock file '${lock_file}' has been created" + log_debug "Lock file \`${lock_file}' has been created" else - log_fatal "Failed to acquire lock file '${lock_file}'. Abort." + log_fatal "Failed to acquire lock file \`${lock_file}'. Abort." exit 1 fi } @@ -210,30 +229,30 @@ check_backup_dir() { if [ -d "${backup_dir:?}" ]; then if [ "$(ls -A "${backup_dir:?}")" ]; then if is_mariabackup_directory "${backup_dir:?}"; then - log_debug "The backup directory ${backup_dir:?} is not empty but looks like a mariabackup target. Let's clear it." + log_debug "The backup directory \`${backup_dir:?}' is not empty but looks like a mariabackup target. Let's clear it." rm -rf "${backup_dir:?}" else - log_fatal "The backup directory ${backup_dir:?} is not empty and doesn't look like a mariabackup target. Please verify and clear the directory if you are sure." + log_fatal "The backup directory \`${backup_dir:?}' is not empty and doesn't look like a mariabackup target. Please verify and clear the directory if you are sure." exit 1 fi else - log_debug "The backup directory ${backup_dir:?} exists but is empty. Let's proceed." + log_debug "The backup directory \`${backup_dir:?}' exists but is empty. Let's proceed." fi else - log_debug "The backup directory ${backup_dir:?} doesn't exist. Let's proceed." + log_debug "The backup directory \`${backup_dir:?}' doesn't exist. Let's proceed." fi mkdir -p "${backup_dir:?}" } check_compress_dir() { if [ -d "${compress_dir:?}" ]; then - log_debug "The compress_dir directory ${compress_dir:?} exists. Let's proceed." + log_debug "The compress_dir directory \`${compress_dir:?}' exists. Let's proceed." else - log_debug "The compress_dir directory ${compress_dir:?} doesn't exist. Let's proceed." + log_debug "The compress_dir directory \`${compress_dir:?}' doesn't exist. Let's proceed." fi mkdir -p "${compress_dir:?}" } -backup() { +backup_phase() { if [ -z "${backup_dir}" ]; then log_fatal "backup-dir option is empty" else @@ -242,16 +261,15 @@ backup() { mariabackup_bin=$(command -v mariabackup) if [ -z "${mariabackup_bin}" ]; then - log_fatal "Couldn't find mariabackup.\nUse 'apt install mariadb-backup'." + log_fatal "Couldn't find mariabackup.\nYou can install it with 'apt install mariadb-backup'." exit 1 fi backup_command="${mariabackup_bin} --backup --slave-info --target-dir=${backup_dir:?}" - if ! is_quiet; then - log_debug "${backup_command}" log_info "BEGIN mariabackup backup phase" + log_debug "${backup_command}" fi if is_quiet || ! is_verbose ; then @@ -277,8 +295,8 @@ backup() { prepare_command="${mariabackup_bin} --prepare --target-dir=${backup_dir:?}" if ! is_quiet; then - log_debug "${prepare_command}" log_info "BEGIN mariabackup prepare phase" + log_debug "${prepare_command}" fi if is_quiet || ! is_verbose ; then @@ -301,14 +319,44 @@ backup() { log_info "END mariabackup prepare phase" fi } -compress() { +mtree_phase() { + if [ -z "${backup_dir}" ]; then + log_fatal "backup-dir option is empty" + exit 1 + elif [ -e "${backup_dir}" ] && [ ! -d "${backup_dir}" ]; then + log_fatal "backup directory \`${backup_dir}' exists but is not a directory" + exit 1 + fi + + if [ -z "${mtree_file}" ]; then + mtree_file="${backup_dir}.mtree" + fi + + mtree_cmd="mtree" + mtree_bin=$(command -v ${mtree_cmd}) + if [ -z "${mtree_bin}" ]; then + log_fatal "Couldn't find ${mtree_cmd}.\nYou can install it with 'apt install mtree-netbsd'." + exit 1 + fi + + backup_parent_dir=$(dirname "${backup_dir}") + backup_final_dir=$(basename "${backup_dir}") + + log_info "BEGIN mtree phase" + log_debug "Store mtree specification of \`${backup_dir}' to \`${mtree_file}' using \`${mtree_bin}'" + + "${mtree_bin}" -x -c -p "${backup_dir}" > "${mtree_file}" + + log_info "END mtree phase" +} +compress_phase() { compress_dir=$(dirname "${compress_file}") if [ -z "${backup_dir}" ]; then log_fatal "backup-dir option is empty" exit 1 elif [ -e "${backup_dir}" ] && [ ! -d "${backup_dir}" ]; then - log_fatal "backup directory '${backup_dir}' exists but is not a directory" + log_fatal "backup directory \`${backup_dir}' exists but is not a directory" exit 1 fi if [ -z "${compress_file}" ]; then @@ -327,13 +375,13 @@ compress() { elif [ -n "${gzip_bin}" ]; then compress_program="${gzip_bin} -6" else - log_fatal "Couldn't find pigz nor gzip.\nUse 'apt install pigz' or 'apt install gzip'." + log_fatal "Couldn't find pigz nor gzip.\nYou can install it with 'apt install pigz' or 'apt install gzip'." exit 1 fi if ! is_quiet; then - log_debug "Compression of ${backup_dir} to ${compress_file} using \`${compress_program}'" log_info "BEGIN compression phase" + log_debug "Compression of \`${backup_dir}' to \`${compress_file}' using \`${compress_program}'" fi if is_quiet || ! is_verbose ; then tar --use-compress-program="${compress_program}" -cf "${compress_file}" "${backup_dir}" >/dev/null 2>&1 @@ -349,24 +397,69 @@ compress() { fi if [ ${tar_rc} -ne 0 ]; then - log_fatal "An error occured while compressing ${backup_dir} to ${compress_file}" + log_fatal "An error occured while compressing \`${backup_dir}' to \`${compress_file}'" exit 1 elif ! is_quiet; then log_info "END compression phase" fi } +post_backup_hook_phase() { + if [ -x "${post_backup_hook}" ]; then + + if ! is_quiet; then + log_debug "Execution of \`${post_backup_hook}'" + log_info "BEGIN hook phase" + fi + + ( + export BACKUP_DIR="${backup_dir}" + if is_log_file; then + export LOG_FILE="${log_file}" + fi + "${post_backup_hook}" + ) + hook_rc=$? + + if [ ${hook_rc} -ne 0 ]; then + log_fatal "An error occured while executing post backup hook \`${post_backup_hook}'" + exit 1 + elif ! is_quiet; then + log_info "END hook phase" + fi + else + log_fatal "Post backup hook \`${post_backup_hook}' is missing or not executable" + exit 1 + fi +} + main() { + if ! is_quiet; then + log_info "BEGIN evomariabackup" + fi + kill_or_clean_lockfile "${lock_file}" # shellcheck disable=SC2064 trap "rm -f ${lock_file};" 0 new_lock_file "${lock_file}" if [ "${do_backup}" = "1" ] && [ -n "${backup_dir}" ]; then - backup "${backup_dir}" + backup_phase + fi + + if [ "${do_mtree}" = "1" ] && [ -n "${backup_dir}" ]; then + mtree_phase fi if [ "${do_compress}" = "1" ] && [ -n "${compress_file}" ]; then - compress "${backup_dir}" "${compress_file}" + compress_phase + fi + + if [ -n "${post_backup_hook}" ]; then + post_backup_hook_phase + fi + + if ! is_quiet; then + log_info "END evomariabackup" fi } @@ -377,11 +470,14 @@ log_file="" verbose="" quiet="" max_age="" -max_age="" +force_unlock="" do_backup="" backup_dir="" +do_mtree="" +mtree_file="" do_compress="" compress_file="" +post_backup_hook="" # Parse options # based on https://gist.github.com/deshion/10d3cb5f88a21671e17a @@ -472,6 +568,38 @@ while :; do log_fatal '"--compress-file" requires a non-empty option argument.' ;; + --mtree) + do_mtree=1 + ;; + + --no-mtree) + do_mtree=0 + ;; + + --mtree-file) + # with value separated by space + if [ -n "$2" ]; then + mtree_file="$2" + if [ -z "${do_mtree}" ]; then + do_mtree=1 + fi + shift + else + log_fatal '"--mtree-file" requires a non-empty option argument.' + fi + ;; + --mtree-file=?*) + # with value speparated by = + mtree_file=${1#*=} + if [ -z "${do_mtree}" ]; then + do_mtree=1 + fi + ;; + --mtree-file=) + # without value + log_fatal '"--mtree-file" requires a non-empty option argument.' + ;; + --lock-file) # with value separated by space if [ -n "$2" ]; then @@ -490,6 +618,10 @@ while :; do log_fatal '"--lock-file" requires a non-empty option argument.' ;; + --force-unlock) + force_unlock=1 + ;; + --log-file) # with value separated by space if [ -n "$2" ]; then @@ -508,6 +640,24 @@ while :; do log_fatal '"--log-file" requires a non-empty option argument.' ;; + --post-backup-hook) + # with value separated by space + if [ -n "$2" ]; then + post_backup_hook="$2" + shift + else + log_fatal '"--post-backup-hook" requires a non-empty option argument.' + fi + ;; + --post-backup-hook=?*) + # with value speparated by = + post_backup_hook=${1#*=} + ;; + --post-backup-hook=) + # without value + log_fatal '"--post-backup-hook" requires a non-empty option argument.' + ;; + -v|--verbose) verbose=1 ;; @@ -527,7 +677,7 @@ while :; do if tty -s; then printf 'Unknown option : %s\n' "$1" >&2 echo "" >&2 - show_usage >&2 + show_help >&2 exit 1 else log_fatal 'Unknown option : %s\n' "$1" >&2 @@ -547,8 +697,19 @@ done lock_file="${lock_file:-/run/lock/evomariabackup.lock}" verbose=${verbose:-0} quiet=${quiet:-0} -max_age="${max_age:-86400}" -do_backup="${do_backup:-1}" -do_compress="${do_compress:-0}" +max_age="${max_age:-1d}" +force_unlock=${force_unlock:-0} +# Enable backup phase if not disabled and backup_dir is set +if [ -z "${do_backup}" ] && [ -n "${backup_dir}" ]; then + do_backup="1" +fi +# Enable mtree phase if not disabled and mtree_file is set +if [ -z "${do_mtree}" ] && [ -n "${mtree_file}" ]; then + do_mtree="1" +fi +# Enable compress phase if not disabled and compress_file is set +if [ -z "${do_compress}" ] && [ -n "${compress_file}" ]; then + do_compress="1" +fi main \ No newline at end of file diff --git a/mysql/files/mysql_skip.sh b/mysql/files/mysql_skip.sh new file mode 100644 index 00000000..ca72a9fc --- /dev/null +++ b/mysql/files/mysql_skip.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +# File containing error messages to skip (one per line). +error_messages="/etc/mysql_skip.conf" + +# Sleep interval between 2 check. +sleep_interval="1" + +# Exit when Seconds_Behind_Master reached 0. +exit_when_uptodate="false" + +# Options to pass to mysql. +#mysql_opt="-P 3307" + +# File to log skipped queries to (leave empty for no logs). +log_file="/var/log/mysql_skip.log" + +mysql_skip_error() { + error="$1" + + mysql ${mysql_opt} -e 'SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1; START SLAVE;' + + printf 'Skipping: %s\n' "$error" + [ -n "$log_file" ] && printf '%s Skipping: %s\n' "$(date --iso-8601=seconds)" "$error" >>"$log_file" +} + +while true; do + slave_status="$(mysql ${mysql_opt} -e 'SHOW SLAVE STATUS\G')" + seconds_behind_master=$(echo "${slave_status}" |grep 'Seconds_Behind_Master: ' |awk -F ' ' '{print $2}') + last_SQL_error="$(echo "${slave_status}" |grep 'Last_SQL_Error: ' |sed 's/^.\+Last_SQL_Error: //')" + + if [ "${seconds_behind_master}" = "0" ]; then + #printf 'Replication is up to date!\n' + if [ "${exit_when_uptodate}" = "true" ]; then + exit 0 + fi + + elif [ -z "$last_SQL_error" ]; then + sleep ${sleep_interval} + + elif echo "$last_SQL_error" |grep -q -f $error_messages; then + mysql_skip_error "${last_SQL_error}" + + fi + sleep 1 +done diff --git a/mysql/files/mysqltuner.cron.sh b/mysql/files/mysqltuner.cron.sh index 5424aa90..ada4a0f8 100644 --- a/mysql/files/mysqltuner.cron.sh +++ b/mysql/files/mysqltuner.cron.sh @@ -44,7 +44,7 @@ Bien à vous, -- Rapport automatique Evolix EOT - mutt -x -e 'set send_charset="utf-8"' -H $template \ + mutt -x -e 'set send_charset="utf-8"' -e "set crypt_use_gpgme=no" -H $template \ -a /var/www/mysqlreport_${instance}.html < $body done chmod 644 /var/www/mysqlreport*html diff --git a/mysql/handlers/main.yml b/mysql/handlers/main.yml index 80afafe5..01ffeccd 100644 --- a/mysql/handlers/main.yml +++ b/mysql/handlers/main.yml @@ -1,25 +1,25 @@ --- - name: restart munin-node - service: + ansible.builtin.service: name: munin-node state: restarted - name: restart mysql - service: + ansible.builtin.service: name: mysql state: restarted - name: restart mysql (noop) - meta: noop + ansible.builtin.meta: noop failed_when: False changed_when: False - name: reload systemd - systemd: + ansible.builtin.systemd: name: mysql daemon_reload: yes - name: 'restart xinetd' - service: + ansible.builtin.service: name: 'xinetd' state: 'restarted' diff --git a/mysql/tasks/config_jessie.yml b/mysql/tasks/config_jessie.yml index a5dd4d77..3d8c494d 100644 --- a/mysql/tasks/config_jessie.yml +++ b/mysql/tasks/config_jessie.yml @@ -1,21 +1,21 @@ --- -- set_fact: +- ansible.builtin.set_fact: mysql_config_directory: /etc/mysql/conf.d - name: "Copy MySQL defaults config file (jessie)" - copy: + ansible.builtin.copy: src: evolinux-defaults.cnf dest: "{{ mysql_config_directory }}/{{ mysql_evolinux_defaults_file }}" owner: root group: root mode: "0644" - force: yes + force: true tags: - mysql - name: "Copy MySQL custom config file (jessie)" - template: + ansible.builtin.template: src: evolinux-custom.cnf.j2 dest: "{{ mysql_config_directory }}/{{ mysql_evolinux_custom_file }}" owner: root diff --git a/mysql/tasks/config_stretch.yml b/mysql/tasks/config_stretch.yml index cfbeedfe..57346fb5 100644 --- a/mysql/tasks/config_stretch.yml +++ b/mysql/tasks/config_stretch.yml @@ -1,44 +1,46 @@ --- -- set_fact: +- ansible.builtin.set_fact: mysql_config_directory: /etc/mysql/mariadb.conf.d - name: "Copy MySQL defaults config file (Debian 9 or later)" - copy: + ansible.builtin.copy: src: evolinux-defaults.cnf dest: "{{ mysql_config_directory }}/{{ mysql_evolinux_defaults_file }}" owner: root group: root mode: "0644" - force: yes + force: true + notify: "{{ mysql_restart_handler_name }}" tags: - mysql - name: "Copy MySQL custom config file (Debian 9 or later)" - template: + ansible.builtin.template: src: evolinux-custom.cnf.j2 dest: "{{ mysql_config_directory }}/{{ mysql_evolinux_custom_file }}" owner: root group: root mode: "0644" force: "{{ mysql_force_custom_config }}" + notify: "{{ mysql_restart_handler_name }}" tags: - mysql - name: "Create a system config directory for systemd overrides (Debian 9 or later)" - file: + ansible.builtin.file: path: /etc/systemd/system/mariadb.service.d state: directory - name: "Override MariaDB systemd unit (Debian 9 or later)" - template: + ansible.builtin.template: src: mariadb.systemd.j2 dest: /etc/systemd/system/mariadb.service.d/evolinux.conf - force: yes + force: true register: mariadb_systemd_override - name: reload systemd and restart MariaDB - systemd: + ansible.builtin.systemd: name: mysql daemon_reload: yes notify: "{{ mysql_restart_handler_name }}" diff --git a/mysql/tasks/datadir.yml b/mysql/tasks/datadir.yml index c375f5d5..d28d6440 100644 --- a/mysql/tasks/datadir.yml +++ b/mysql/tasks/datadir.yml @@ -2,13 +2,14 @@ - block: - name: "Is {{ mysql_custom_datadir }} present ?" - stat: + ansible.builtin.stat: path: "{{ mysql_custom_datadir }}" check_mode: no register: mysql_custom_datadir_test - name: "read the real datadir" - command: readlink -f /var/lib/mysql + ansible.builtin.command: + cmd: readlink -f /var/lib/mysql changed_when: False check_mode: no register: mysql_current_real_datadir_test @@ -18,23 +19,24 @@ - block: - name: MySQL is stopped - service: + ansible.builtin.service: name: mysql state: stopped - name: Move MySQL datadir to {{ mysql_custom_datadir }} - command: mv {{ mysql_current_real_datadir_test.stdout }} {{ mysql_custom_datadir }} + ansible.builtin.command: + cmd: mv {{ mysql_current_real_datadir_test.stdout }} {{ mysql_custom_datadir }} args: creates: "{{ mysql_custom_datadir }}" - name: Symlink {{ mysql_custom_datadir }} to /var/lib/mysql - file: + ansible.builtin.file: src: "{{ mysql_custom_datadir }}" dest: '/var/lib/mysql' state: link - name: MySQL is started - service: + ansible.builtin.service: name: mysql state: started tags: diff --git a/mysql/tasks/log2mail.yml b/mysql/tasks/log2mail.yml index 568b6649..4eee01c8 100644 --- a/mysql/tasks/log2mail.yml +++ b/mysql/tasks/log2mail.yml @@ -1,7 +1,7 @@ --- - name: Is log2mail present ? - stat: + ansible.builtin.stat: path: /etc/log2mail/config check_mode: no register: log2mail_config_dir @@ -10,7 +10,7 @@ - log2mail - name: Copy log2mail config - template: + ansible.builtin.template: src: log2mail.j2 dest: /etc/log2mail/config/mysql.conf owner: log2mail diff --git a/mysql/tasks/logdir.yml b/mysql/tasks/logdir.yml index bd6ecab2..10d2f70e 100644 --- a/mysql/tasks/logdir.yml +++ b/mysql/tasks/logdir.yml @@ -2,13 +2,14 @@ - block: - name: "Is {{ mysql_custom_logdir }} present ?" - stat: + ansible.builtin.stat: path: "{{ mysql_custom_logdir }}" check_mode: no register: mysql_custom_logdir_test - name: "read the real logdir" - command: readlink -f /var/log/mysql + ansible.builtin.command: + cmd: readlink -f /var/log/mysql changed_when: False check_mode: no register: mysql_current_real_logdir_test @@ -18,23 +19,24 @@ - block: - name: MySQL is stopped - service: + ansible.builtin.service: name: mysql state: stopped - name: Move MySQL logdir to {{ mysql_custom_logdir }} - command: mv {{ mysql_current_real_logdir_test.stdout }} {{ mysql_custom_logdir }} + ansible.builtin.command: + cmd: mv {{ mysql_current_real_logdir_test.stdout }} {{ mysql_custom_logdir }} args: creates: "{{ mysql_custom_logdir }}" - name: Symlink {{ mysql_custom_logdir }} to /var/log/mysql - file: + ansible.builtin.file: src: "{{ mysql_custom_logdir }}" dest: '/var/log/mysql' state: link - name: MySQL is started - service: + ansible.builtin.service: name: mysql state: started tags: diff --git a/mysql/tasks/main.yml b/mysql/tasks/main.yml index a7c38808..73493588 100644 --- a/mysql/tasks/main.yml +++ b/mysql/tasks/main.yml @@ -1,47 +1,52 @@ --- - name: Set if MySQL should be restart (if needed) or not at all - set_fact: + ansible.builtin.set_fact: mysql_restart_handler_name: "{{ mysql_restart_if_needed | bool | ternary('restart mysql', 'restart mysql (noop)') }}" -- include: packages_stretch.yml +- ansible.builtin.include_tasks: packages_stretch.yml when: ansible_distribution_major_version is version('9', '>=') -- include: packages_jessie.yml +- ansible.builtin.include_tasks: packages_jessie.yml when: ansible_distribution_release == "jessie" -## There is nothing to do with users on Debian 11 - yet we need a /root/.my.cnf for compatibility -- include: users_bullseye.yml - when: ansible_distribution_release == "bullseye" -- include: users_buster.yml +## There is nothing to do with users on Debian 11+ - yet we need a /root/.my.cnf for compatibility +- ansible.builtin.include_tasks: users_bullseye.yml + when: ansible_distribution_major_version is version('11', '>=') + +- ansible.builtin.include_tasks: users_buster.yml when: ansible_distribution_release == "buster" -- include: users_stretch.yml +- ansible.builtin.include_tasks: users_stretch.yml when: ansible_distribution_release == "stretch" -- include: users_jessie.yml +- ansible.builtin.include_tasks: users_jessie.yml when: ansible_distribution_release == "jessie" -- include: config_stretch.yml + +- ansible.builtin.include_tasks: config_stretch.yml when: ansible_distribution_major_version is version('9', '>=') -- include: config_jessie.yml +- ansible.builtin.include_tasks: config_jessie.yml when: ansible_distribution_release == "jessie" -- include: replication.yml + +- ansible.builtin.include_tasks: replication.yml when: mysql_replication | bool -- include: datadir.yml +- ansible.builtin.include_tasks: datadir.yml -- include: logdir.yml +- ansible.builtin.include_tasks: logdir.yml -- include: tmpdir.yml +- ansible.builtin.include_tasks: tmpdir.yml -- include: nrpe.yml +- ansible.builtin.include_tasks: nrpe.yml -- include: munin.yml +- ansible.builtin.include_tasks: munin.yml -- include: log2mail.yml +- ansible.builtin.include_tasks: log2mail.yml -- include: utils.yml +- ansible.builtin.include_tasks: utils.yml + +- ansible.builtin.include_tasks: mysql_skip.yml diff --git a/mysql/tasks/munin.yml b/mysql/tasks/munin.yml index 9ee8f95f..9b4e9617 100644 --- a/mysql/tasks/munin.yml +++ b/mysql/tasks/munin.yml @@ -1,7 +1,7 @@ --- - name: is Munin present ? - stat: + ansible.builtin.stat: path: /etc/munin/plugin-conf.d/munin-node check_mode: no register: munin_node_plugins_config @@ -11,7 +11,7 @@ - block: - name: "Install perl libraries for Munin (Debian < 11)" - apt: + ansible.builtin.apt: name: - libdbd-mysql-perl - libcache-cache-perl @@ -19,14 +19,14 @@ when: ansible_distribution_major_version is version('11', '<') - name: "Install perl libraries for Munin (Debian >= 11)" - apt: + ansible.builtin.apt: name: - libcache-cache-perl - libdbd-mariadb-perl when: ansible_distribution_major_version is version('11', '>=') - name: Enable core Munin plugins - file: + ansible.builtin.file: src: '/usr/share/munin/plugins/{{ item }}' dest: /etc/munin/plugins/{{ item }} state: link @@ -38,7 +38,7 @@ notify: restart munin-node - name: Enable contributed Munin plugins - file: + ansible.builtin.file: src: /usr/share/munin/plugins/mysql_ dest: '/etc/munin/plugins/mysql_{{ item }}' state: link @@ -66,13 +66,42 @@ - replication notify: restart munin-node - - name: verify Munin configuration for mysql - replace: + - name: verify Munin configuration for mysql < Debian 11 + ansible.builtin.replace: dest: /etc/munin/plugin-conf.d/munin-node after: '\[mysql\*\]' regexp: '^env.mysqluser (.+)$' replace: 'env.mysqluser debian-sys-maint' notify: restart munin-node + when: ansible_distribution_major_version is version_compare('11', '<') + + - name: set Munin env.mysqluser option for mysql >= Debian 11 + ansible.builtin.replace: + dest: /etc/munin/plugin-conf.d/munin-node + after: '\[mysql\*\]' + regexp: '^env.mysqluser (.+)$' + replace: 'env.mysqluser root' + notify: restart munin-node + when: ansible_distribution_major_version is version_compare('11', '>=') + + - name: set Munin env.mysqlopts option for mysql >= Debian 11 + ansible.builtin.replace: + dest: /etc/munin/plugin-conf.d/munin-node + after: '\[mysql\*\]' + regexp: '^env.mysqlopts (.+)$' + replace: 'env.mysqlopts --defaults-file=/root/.my.cnf' + notify: restart munin-node + when: ansible_distribution_major_version is version_compare('11', '>=') + + - name: set Munin env.mysqlconnection option for mysql >= Debian 11 + ansible.builtin.replace: + dest: /etc/munin/plugin-conf.d/munin-node + after: '\[mysql\*\]' + regexp: '^env.mysqlconnection (.+)$' + replace: 'env.mysqlconnection DBI:mysql:mysql;mysql_read_default_file=/root/.my.cnf' + notify: restart munin-node + when: ansible_distribution_major_version is version_compare('11', '>=') + when: munin_node_plugins_config.stat.exists tags: diff --git a/mysql/tasks/mysql_skip.yml b/mysql/tasks/mysql_skip.yml new file mode 100644 index 00000000..e98567a7 --- /dev/null +++ b/mysql/tasks/mysql_skip.yml @@ -0,0 +1,54 @@ +--- + +- name: "Copy script mysql_skip.sh into /usr/local/bin/" + ansible.builtin.copy: + src: mysql_skip.sh + dest: "/usr/local/bin/mysql_skip.sh" + owner: root + group: root + mode: "0700" + force: true + tags: + - mysql_skip + +- name: "Copy config file for mysql_skip.sh" + ansible.builtin.template: + src: mysql_skip.conf.j2 + dest: "/etc/mysql_skip.conf" + owner: root + group: root + mode: "0600" + tags: + - mysql_skip + +- name: "Create log file for mysql_skip.sh" + ansible.builtin.file: + path: "/var/log/mysql_skip.log" + state: touch + owner: root + group: adm + mode: "0640" + tags: + - mysql_skip + +- name: "Copy logrotate file for mysql_skip.sh" + ansible.builtin.template: + src: mysql_skip.logrotate.j2 + dest: "/etc/logrotate.d/mysql_skip" + owner: root + group: root + mode: "0600" + tags: + - mysql_skip + +- name: "Copy mysql_skip.sh systemd unit" + ansible.builtin.template: + src: mysql_skip.systemd.j2 + dest: /etc/systemd/system/mysql_skip.service + force: true + +- name: "Start or stop systemd unit" + ansible.builtin.systemd: + name: mysql_skip + daemon_reload: yes + state: "{{ mysql_skip_enabled | bool | ternary('started', 'stopped') }}" \ No newline at end of file diff --git a/mysql/tasks/nrpe.yml b/mysql/tasks/nrpe.yml index c3457699..cce8e4b7 100644 --- a/mysql/tasks/nrpe.yml +++ b/mysql/tasks/nrpe.yml @@ -1,7 +1,7 @@ --- - name: is NRPE present ? - stat: + ansible.builtin.stat: path: /etc/nagios/nrpe.d/evolix.cfg check_mode: no register: nrpe_evolix_config @@ -10,7 +10,7 @@ - nrpe - name: NRPE user exists for MySQL ? - stat: + ansible.builtin.stat: path: ~nagios/.my.cnf check_mode: no register: nrpe_my_cnf @@ -20,13 +20,14 @@ - block: - name: Create a password for NRPE - command: "apg -n 1 -m 16 -M lcN" + ansible.builtin.command: + cmd: "apg -n 1 -m 16 -M lcN" register: mysql_nrpe_password check_mode: no changed_when: False - name: Create nrpe user - mysql_user: + community.mysql.mysql_user: name: nrpe password: '{{ mysql_nrpe_password.stdout }}' priv: "*.*:REPLICATION CLIENT" @@ -36,7 +37,7 @@ register: create_nrpe_user - name: Store credentials in nagios home - ini_file: + community.general.ini_file: dest: "~nagios/.my.cnf" owner: nagios group: nagios diff --git a/mysql/tasks/packages_jessie.yml b/mysql/tasks/packages_jessie.yml index 652eace7..942c1006 100644 --- a/mysql/tasks/packages_jessie.yml +++ b/mysql/tasks/packages_jessie.yml @@ -1,7 +1,7 @@ --- - name: Choose packages (Oracle) - set_fact: + ansible.builtin.set_fact: mysql_packages: "{{ mysql_packages_oracle }}" when: mysql_variant == "oracle" tags: @@ -9,7 +9,7 @@ - packages - name: Choose packages (MariaDB) - set_fact: + ansible.builtin.set_fact: mysql_packages: "{{ mysql_packages_mariadb }}" when: mysql_variant == "mariadb" tags: @@ -17,7 +17,7 @@ - packages - name: Install MySQL packages - apt: + ansible.builtin.apt: name: "{{ mysql_packages }}" update_cache: yes state: present @@ -26,7 +26,7 @@ - packages - name: Install MySQL dev packages - apt: + ansible.builtin.apt: name: libmysqlclient-dev update_cache: yes state: present @@ -36,7 +36,7 @@ when: mysql_install_libclient | bool - name: MySQL is started - service: + ansible.builtin.service: name: mysql state: started tags: @@ -44,7 +44,7 @@ - services - name: apg package is installed - apt: + ansible.builtin.apt: name: apg state: present tags: @@ -52,7 +52,7 @@ - packages - name: Python dependencies for Ansible are installed - apt: + ansible.builtin.apt: name: python-mysqldb state: present tags: diff --git a/mysql/tasks/packages_stretch.yml b/mysql/tasks/packages_stretch.yml index 880f5050..acd98d2e 100644 --- a/mysql/tasks/packages_stretch.yml +++ b/mysql/tasks/packages_stretch.yml @@ -1,44 +1,57 @@ --- - name: Install MySQL packages - apt: + ansible.builtin.apt: name: - mariadb-server - mariadb-client update_cache: yes state: present tags: - - mysql - - packages + - mysql + - packages + +- name: Default log directory is present + ansible.builtin.file: + path: /var/log/mysql + owner: mysql + group: adm + mode: "2750" + state: directory + notify: restart mysql + tags: + - mysql + - packages + when: ansible_distribution_major_version is version('12', '>=') - name: Install MySQL dev packages - apt: + ansible.builtin.apt: name: default-libmysqlclient-dev update_cache: yes state: present tags: - - mysql - - packages + - mysql + - packages when: mysql_install_libclient | bool - name: MySQL is started - service: + ansible.builtin.service: name: mysql state: started tags: - - mysql - - services + - mysql + - services - name: apg package is installed - apt: + ansible.builtin.apt: name: apg state: present tags: - - mysql - - packages + - mysql + - packages - name: Python2 dependencies for Ansible are installed - apt: + ansible.builtin.apt: name: - python-mysqldb - python-pymysql @@ -49,7 +62,7 @@ when: ansible_python_version is version('3', '<') - name: Python3 dependencies for Ansible are installed - apt: + ansible.builtin.apt: name: - python3-mysqldb - python3-pymysql diff --git a/mysql/tasks/replication.yml b/mysql/tasks/replication.yml index f447d099..4ca491da 100644 --- a/mysql/tasks/replication.yml +++ b/mysql/tasks/replication.yml @@ -1,14 +1,14 @@ --- - name: 'Copy MySQL configuration for replication' - template: + ansible.builtin.template: src: 'replication.cnf.j2' dest: "{{ mysql_config_directory }}/zzzz-replication.cnf" mode: "0644" notify: 'restart mysql' - name: 'Create repl user' - mysql_user: + community.mysql.mysql_user: name: 'repl' host: '%' encrypted: true @@ -20,22 +20,22 @@ when: mysql_repl_password | length > 0 - name: 'Install xinetd' - apt: + ansible.builtin.apt: name: 'xinetd' - name: 'Add xinetd configuration for MySQL HAProxy check' - copy: + ansible.builtin.copy: src: 'xinetd/mysqlchk' dest: '/etc/xinetd.d/' mode: '0644' notify: 'restart xinetd' # /!\ Warning, this is a temporary hack -- include_role: +- ansible.builtin.include_role: name: remount-usr - name: 'Copy mysqlchk script' - copy: + ansible.builtin.copy: src: 'xinetd/mysqlchk.sh' dest: '/usr/share/scripts/' mode: '0755' diff --git a/mysql/tasks/tmpdir.yml b/mysql/tasks/tmpdir.yml index 79a3ac5e..ecd9e279 100644 --- a/mysql/tasks/tmpdir.yml +++ b/mysql/tasks/tmpdir.yml @@ -2,7 +2,7 @@ - block: - name: "Create {{ mysql_custom_tmpdir }}" - file: + ansible.builtin.file: path: "{{ mysql_custom_tmpdir }}" owner: mysql group: mysql @@ -12,7 +12,7 @@ - mysql - name: Configure tmpdir - ini_file: + community.general.ini_file: dest: "{{ mysql_config_directory }}/{{ mysql_evolinux_custom_file }}" section: mysqld option: tmpdir diff --git a/mysql/tasks/users_bullseye.yml b/mysql/tasks/users_bullseye.yml index 1bdc9084..d2b6c04d 100644 --- a/mysql/tasks/users_bullseye.yml +++ b/mysql/tasks/users_bullseye.yml @@ -1,7 +1,7 @@ --- - name: Populate the .my.cnf of root with default user - ini_file: + community.general.ini_file: dest: /root/.my.cnf mode: "0600" section: client diff --git a/mysql/tasks/users_buster.yml b/mysql/tasks/users_buster.yml index dc7cec85..490a7ccc 100644 --- a/mysql/tasks/users_buster.yml +++ b/mysql/tasks/users_buster.yml @@ -1,7 +1,8 @@ --- - name: create a password for mysqladmin - command: "apg -n 1 -m 16 -M lcN" + ansible.builtin.command: + cmd: "apg -n 1 -m 16 -M lcN" register: mysql_admin_password changed_when: False check_mode: False @@ -9,7 +10,7 @@ - mysql - name: there is a mysqladmin user - mysql_user: + community.mysql.mysql_user: name: mysqladmin password: '{{ mysql_admin_password.stdout }}' priv: "*.*:ALL,GRANT" @@ -21,7 +22,7 @@ - mysql - name: mysqladmin is the default user - ini_file: + community.general.ini_file: dest: /root/.my.cnf mode: "0600" section: client @@ -36,7 +37,8 @@ - mysql - name: create a password for debian-sys-maint - command: "apg -n 1 -m 16 -M lcN" + ansible.builtin.command: + cmd: "apg -n 1 -m 16 -M lcN" register: mysql_debian_password changed_when: False check_mode: False @@ -44,7 +46,7 @@ - mysql - name: there is a debian-sys-maint user - mysql_user: + community.mysql.mysql_user: name: debian-sys-maint password: '{{ mysql_debian_password.stdout }}' priv: "*.*:ALL,GRANT" @@ -56,7 +58,7 @@ - mysql - name: store debian-sys-maint user credentials - ini_file: + community.general.ini_file: dest: /etc/mysql/debian.cnf mode: "0600" section: "{{ item[0] }}" @@ -74,7 +76,7 @@ - mysql - name: root user is absent - mysql_user: + community.mysql.mysql_user: name: root host_all: yes config_file: "/root/.my.cnf" diff --git a/mysql/tasks/users_jessie.yml b/mysql/tasks/users_jessie.yml index e2b066b1..1bde42c9 100644 --- a/mysql/tasks/users_jessie.yml +++ b/mysql/tasks/users_jessie.yml @@ -1,12 +1,13 @@ --- - name: "Abort if MariaDB on Debian 8" - fail: + ansible.builtin.fail: msg: "We can't create other users with 'debian-sys-maint' on Debian 8 with MariaDB.\nWe must give it the GRANT privilege before continuing." when: mysql_variant == "mariadb" - name: create a password for mysqladmin - command: "apg -n 1 -m 16 -M lcN" + ansible.builtin.command: + cmd: "apg -n 1 -m 16 -M lcN" register: mysql_admin_password changed_when: False check_mode: no @@ -14,7 +15,7 @@ - mysql - name: there is a mysqladmin user - mysql_user: + community.mysql.mysql_user: name: mysqladmin password: '{{ mysql_admin_password.stdout }}' priv: "*.*:ALL,GRANT" @@ -26,7 +27,7 @@ - mysql - name: mysqladmin is the default user - ini_file: + community.general.ini_file: dest: /root/.my.cnf mode: "0600" section: client @@ -41,7 +42,7 @@ - mysql - name: root user is absent - mysql_user: + community.mysql.mysql_user: name: root host_all: yes config_file: "/root/.my.cnf" diff --git a/mysql/tasks/users_stretch.yml b/mysql/tasks/users_stretch.yml index dc7cec85..490a7ccc 100644 --- a/mysql/tasks/users_stretch.yml +++ b/mysql/tasks/users_stretch.yml @@ -1,7 +1,8 @@ --- - name: create a password for mysqladmin - command: "apg -n 1 -m 16 -M lcN" + ansible.builtin.command: + cmd: "apg -n 1 -m 16 -M lcN" register: mysql_admin_password changed_when: False check_mode: False @@ -9,7 +10,7 @@ - mysql - name: there is a mysqladmin user - mysql_user: + community.mysql.mysql_user: name: mysqladmin password: '{{ mysql_admin_password.stdout }}' priv: "*.*:ALL,GRANT" @@ -21,7 +22,7 @@ - mysql - name: mysqladmin is the default user - ini_file: + community.general.ini_file: dest: /root/.my.cnf mode: "0600" section: client @@ -36,7 +37,8 @@ - mysql - name: create a password for debian-sys-maint - command: "apg -n 1 -m 16 -M lcN" + ansible.builtin.command: + cmd: "apg -n 1 -m 16 -M lcN" register: mysql_debian_password changed_when: False check_mode: False @@ -44,7 +46,7 @@ - mysql - name: there is a debian-sys-maint user - mysql_user: + community.mysql.mysql_user: name: debian-sys-maint password: '{{ mysql_debian_password.stdout }}' priv: "*.*:ALL,GRANT" @@ -56,7 +58,7 @@ - mysql - name: store debian-sys-maint user credentials - ini_file: + community.general.ini_file: dest: /etc/mysql/debian.cnf mode: "0600" section: "{{ item[0] }}" @@ -74,7 +76,7 @@ - mysql - name: root user is absent - mysql_user: + community.mysql.mysql_user: name: root host_all: yes config_file: "/root/.my.cnf" diff --git a/mysql/tasks/utils.yml b/mysql/tasks/utils.yml index e55b6361..9d81514a 100644 --- a/mysql/tasks/utils.yml +++ b/mysql/tasks/utils.yml @@ -1,14 +1,14 @@ --- -- set_fact: +- ansible.builtin.set_fact: _mysql_scripts_dir: "{{ mysql_scripts_dir | default(general_scripts_dir, True) | mandatory }}" -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - when: _mysql_scripts_dir is search ("/usr") + when: _mysql_scripts_dir is search("/usr") - name: Ensure scripts directory exists - file: + ansible.builtin.file: dest: "{{ _mysql_scripts_dir }}" mode: "0700" state: directory @@ -17,8 +17,8 @@ # mytop -- name: "Install mytop (Debian 9)" - apt: +- name: "Install mytop (Debian 8)" + ansible.builtin.apt: name: mytop state: present tags: @@ -28,7 +28,7 @@ when: ansible_distribution_release == "jessie" - name: "Install dependencies for mytop (Debian 9)" - apt: + ansible.builtin.apt: name: - mariadb-client-10.1 - libconfig-inifiles-perl @@ -36,24 +36,34 @@ when: ansible_distribution_release == "stretch" - name: "Install dependencies for mytop (Debian 10)" - apt: + ansible.builtin.apt: name: - mariadb-client-10.3 - libconfig-inifiles-perl - libterm-readkey-perl when: ansible_distribution_release == "buster" -- name: "Install dependencies for mytop (Debian 11 or later)" - apt: +- name: "Install dependencies for mytop (Debian 11)" + ansible.builtin.apt: name: - mariadb-client-10.5 - libconfig-inifiles-perl - libterm-readkey-perl - libdbd-mariadb-perl - when: ansible_distribution_major_version is version('11', '>=') + when: ansible_distribution_release == "bullseye" + +- name: "Install dependencies for mytop (Debian 12 or later)" + ansible.builtin.apt: + name: + - mariadb-client + - libconfig-inifiles-perl + - libterm-readkey-perl + - libdbd-mariadb-perl + when: ansible_distribution_major_version is version('12', '>=') - name: Read debian-sys-maint password (Debian < 11) - shell: 'cat /etc/mysql/debian.cnf | grep -m1 "password = .*" | cut -d" " -f3' + ansible.builtin.shell: + cmd: 'cat /etc/mysql/debian.cnf | grep -m1 "password = .*" | cut -d" " -f3' register: mysql_debian_password changed_when: False check_mode: no @@ -62,22 +72,22 @@ when: ansible_distribution_major_version is version('11', '<') - name: Configure mytop (Debian < 11) - template: + ansible.builtin.template: src: mytop.j2 dest: /root/.mytop mode: "0600" - force: yes + force: true tags: - mytop - mysql when: ansible_distribution_major_version is version('11', '<') - name: Configure mytop (Debian >= 11) - template: + ansible.builtin.template: src: mytop.bullseye.j2 dest: /root/.mytop mode: "0600" - force: yes + force: true tags: - mytop - mysql @@ -85,16 +95,16 @@ # mysqltuner -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - when: _mysql_scripts_dir is search ("/usr") + when: _mysql_scripts_dir is search("/usr") - name: Install mysqltuner # copy: # src: mysqltuner.pl # dest: "{{ _mysql_scripts_dir }}/mysqltuner.pl" # mode: "0700" - apt: + ansible.builtin.apt: name: mysqltuner state: present tags: @@ -102,7 +112,7 @@ - mysqltuner - name: Install aha - apt: + ansible.builtin.apt: name: aha tags: - mysql @@ -110,7 +120,7 @@ # Percona Toolkit - name: "Install percona-toolkit (Debian 9 or later)" - apt: + ansible.builtin.apt: name: percona-toolkit state: present tags: @@ -121,12 +131,12 @@ # automatic optimizations -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - when: _mysql_scripts_dir is search ("/usr") + when: _mysql_scripts_dir is search("/usr") - name: Optimize script for MySQL - copy: + ansible.builtin.copy: src: mysql-optimize.sh dest: "{{ _mysql_scripts_dir }}/mysql-optimize.sh" mode: "0700" @@ -134,7 +144,7 @@ - mysql - name: "Cron dir for optimize is present" - file: + ansible.builtin.file: path: "/etc/cron.{{ mysql_cron_optimize_frequency | mandatory }}" state: directory mode: "0755" @@ -142,7 +152,7 @@ group: root - name: "Enable cron to optimize MySQL" - file: + ansible.builtin.file: src: "{{ _mysql_scripts_dir }}/mysql-optimize.sh" dest: /etc/cron.{{ mysql_cron_optimize_frequency | mandatory }}/mysql-optimize.sh state: link @@ -151,7 +161,7 @@ - mysql - name: "Disable cron to optimize MySQL" - file: + ansible.builtin.file: dest: /etc/cron.{{ mysql_cron_optimize_frequency | mandatory }}/mysql-optimize.sh state: absent when: not (mysql_cron_optimize | bool) @@ -159,7 +169,7 @@ - mysql - name: "Cron dir for mysqltuner is present" - file: + ansible.builtin.file: path: "/etc/cron.{{ mysql_cron_mysqltuner_frequency | mandatory }}" state: directory mode: "0755" @@ -167,7 +177,7 @@ group: root - name: "Enable mysqltuner in cron" - copy: + ansible.builtin.copy: src: mysqltuner.cron.sh dest: /etc/cron.{{ mysql_cron_mysqltuner_frequency | mandatory }}/mysqltuner.sh mode: "0755" @@ -176,7 +186,7 @@ - mysql - name: "Disable mysqltuner in cron" - file: + ansible.builtin.file: dest: /etc/cron.{{ mysql_cron_mysqltuner_frequency | mandatory }}/mysqltuner.sh state: absent when: not (mysql_cron_mysqltuner | bool) @@ -185,12 +195,12 @@ # my-add.sh -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - when: _mysql_scripts_dir is search ("/usr") + when: _mysql_scripts_dir is search("/usr") - name: Install my-add.sh - copy: + ansible.builtin.copy: src: my-add.sh dest: "{{ _mysql_scripts_dir }}/my-add.sh" mode: "0700" @@ -199,44 +209,44 @@ - mysql - name: Install apg - apt: + ansible.builtin.apt: name: apg tags: - mysql - packages - name: "Install save_mysql_processlist.sh" - copy: + ansible.builtin.copy: src: save_mysql_processlist.sh dest: "{{ _mysql_scripts_dir }}/save_mysql_processlist.sh" mode: "0755" - force: no + force: false tags: - mysql - name: "Install mysql_connections" - copy: + ansible.builtin.copy: src: mysql_connections.sh dest: "{{ _mysql_scripts_dir }}/mysql_connections" mode: "0755" - force: no + force: false tags: - mysql - name: "Install mysql-queries-killer.sh" - copy: + ansible.builtin.copy: src: mysql-queries-killer.sh dest: "{{ _mysql_scripts_dir }}/mysql-queries-killer.sh" mode: "0755" - force: no + force: false tags: - mysql - name: "Install evomariabackup" - copy: + ansible.builtin.copy: src: evomariabackup.sh dest: "{{ _mysql_scripts_dir }}/evomariabackup" mode: "0755" - force: no + force: false tags: - mysql \ No newline at end of file diff --git a/mysql/templates/mysql_skip.conf.j2 b/mysql/templates/mysql_skip.conf.j2 new file mode 100644 index 00000000..3c8ef5fc --- /dev/null +++ b/mysql/templates/mysql_skip.conf.j2 @@ -0,0 +1 @@ +## Put your matched patern here ## diff --git a/mysql/templates/mysql_skip.logrotate.j2 b/mysql/templates/mysql_skip.logrotate.j2 new file mode 100644 index 00000000..4a75b3ea --- /dev/null +++ b/mysql/templates/mysql_skip.logrotate.j2 @@ -0,0 +1,10 @@ +/var/log/mysql_skip.log { + missingok + notifempty + monthly + rotate 12 + compress + create 640 root adm + dateext + dateformat -%Y%m%d%H +} \ No newline at end of file diff --git a/mysql/templates/mysql_skip.systemd.j2 b/mysql/templates/mysql_skip.systemd.j2 new file mode 100644 index 00000000..afe44700 --- /dev/null +++ b/mysql/templates/mysql_skip.systemd.j2 @@ -0,0 +1,16 @@ +[Unit] +Description=Script for skip define mysql replication errors + +[Service] +ExecStart=/usr/local/bin/mysql_skip.sh +Type=simple +User=root +Group=root +PIDFile=/run/mysql_skip.pid +ExecStop=/bin/kill -- $MAINPID +KillMode=process +Restart=on-failure +RestartSec=5s + +[Install] +WantedBy=multi-user.target diff --git a/mysql/templates/replication.cnf.j2 b/mysql/templates/replication.cnf.j2 index 030f2470..460f0833 100644 --- a/mysql/templates/replication.cnf.j2 +++ b/mysql/templates/replication.cnf.j2 @@ -1,4 +1,4 @@ -# {{ansible_managed}} +# {{ ansible_managed }} [mysqld] {% if mysql_log_bin %} diff --git a/nagios-nrpe/files/alerts_switch b/nagios-nrpe/files/alerts_switch new file mode 100644 index 00000000..3c5a1417 --- /dev/null +++ b/nagios-nrpe/files/alerts_switch @@ -0,0 +1,83 @@ +#!/bin/bash + +# https://forge.evolix.org/projects/evolix-private/repository +# +# You should not alter this file. +# If you need to, create and customize a copy. + +set -e + +readonly PROGNAME=$(basename $0) +readonly PROGDIR=$(readlink -m $(dirname $0)) +readonly ARGS="$@" + +usage() { + echo "$PROGNAME action prefix" +} + +disable_alerts () { + disabled_file="$1_disabled" + enabled_file="$1_enabled" + + if [ -e "${enabled_file}" ]; then + mv "${enabled_file}" "${disabled_file}" + else + touch "${disabled_file}" + chmod 0644 "${disabled_file}" + fi +} + +enable_alerts () { + disabled_file="$1_disabled" + enabled_file="$1_enabled" + + if [ -e "${disabled_file}" ]; then + mv "${disabled_file}" "${enabled_file}" + else + touch "${enabled_file}" + chmod 0644 "${enabled_file}" + fi +} + +now () { + date --iso-8601=seconds +} + +log_disable () { + echo "$(now) - alerts disabled by $(logname || echo unknown)" >> $1 +} + +log_enable () { + echo "$(now) - alerts enabled by $(logname || echo unknown)" >> $1 +} + +main () { + local action=$1 + local prefix=$2 + + local base_dir="/var/lib/misc" + mkdir -p "${base_dir}" + + local file_path="${base_dir}/${prefix}_alerts" + local log_file="/var/log/${prefix}_alerts.log" + + case "$action" in + enable) + enable_alerts ${file_path} + log_enable ${log_file} + ;; + disable) + disable_alerts ${file_path} + log_disable ${log_file} + ;; + help) + usage + ;; + *) + >&2 echo "Unknown action '$action'" + exit 1 + ;; + esac +} + +main $ARGS diff --git a/nagios-nrpe/files/alerts_wrapper b/nagios-nrpe/files/alerts_wrapper new file mode 100644 index 00000000..d4524fdd --- /dev/null +++ b/nagios-nrpe/files/alerts_wrapper @@ -0,0 +1,217 @@ +#!/bin/bash + +# https://forge.evolix.org/projects/evolix-private/repository +# +# You should not alter this file. +# If you need to, create and customize a copy. + +VERSION="21.04" +readonly VERSION + +# base functions + +show_version() { + cat <, + Jérémy Lecour + and others. + +alerts_wrapper 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 < "${check_stdout}" + check_rc=$? + readonly check_rc + + delay=0 + + if [ -e "${alerts_disabled_file}" ]; then + delay=$(delay_from_alerts_disabled_file) + + if [ "${delay}" -le "0" ]; then + enable_check + fi + fi + + if [ -e "${alerts_disabled_file}" ]; then + formatted_last_change=$(date --date "@$(stat -c %Z "${alerts_disabled_file}")" +'%c') + readonly formatted_last_change + + echo "ALERTS DISABLED for ${check_name} (since ${formatted_last_change}, delay: ${delay} sec) - $(cat "${check_stdout}")" + if [ ${check_rc} = 0 ]; then + # Nagios OK + exit 0 + else + # Nagios WARNING + exit 1 + fi + else + cat "${check_stdout}" + exit ${check_rc} + fi +} + +# Default: 1 day before re-enabling the check +wrapper_limit_default="1d" +readonly wrapper_limit_default + +if [[ "${1}" =~ -.* ]]; then + # 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 + ;; + + --limit) + # with value separated by space + if [ -n "$2" ]; then + wrapper_limit=$2 + shift + else + printf 'ERROR: "--limit" requires a non-empty option argument.\n' >&2 + exit 1 + fi + ;; + --limit=?*) + # with value speparated by = + wrapper_limit=${1#*=} + ;; + --limit=) + # without value + printf 'ERROR: "--limit" requires a non-empty option argument.\n' >&2 + exit 1 + ;; + + --name) + # with value separated by space + if [ -n "$2" ]; then + check_name=$2 + shift + else + printf 'ERROR: "--name" requires a non-empty option argument.\n' >&2 + exit 1 + fi + ;; + --name=?*) + # with value speparated by = + check_name=${1#*=} + ;; + --name=) + # without value + printf 'ERROR: "--name" requires a non-empty option argument.\n' >&2 + exit 1 + ;; + + --) + # 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 + # The rest is the command + check_command="$*" +else + # no option is passed (backward compatibility with previous version) + # treat the first argument as check_name and the rest as the command + check_name="${1}" + shift + check_command="$*" +fi + +# Default values or errors +if [ -z "${wrapper_limit}" ]; then + wrapper_limit="${wrapper_limit_default}" +fi +if [ -z "${check_name}" ]; then + printf 'ERROR: You must specify a check name, with --name.\n' >&2 + exit 1 +fi +if [ -z "${check_command}" ]; then + printf 'ERROR: You must specify a command to execute.\n' >&2 + exit 1 +fi + +readonly check_name +readonly check_command +readonly wrapper_limit +alerts_disabled_file="/var/lib/misc/${check_name}_alerts_disabled" +readonly alerts_disabled_file + +check_file="/var/lib/misc/${check_name}_alerts_disabled" +readonly check_file + +check_stdout=$(mktemp --tmpdir=/tmp "${check_name}_stdout.XXXX") +readonly check_stdout + +# shellcheck disable=SC2064 +trap "rm ${check_stdout}" EXIT + +main diff --git a/nagios-nrpe/files/check-local b/nagios-nrpe/files/check-local new file mode 100755 index 00000000..40587425 --- /dev/null +++ b/nagios-nrpe/files/check-local @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +if ! test -f /usr/lib/nagios/plugins/check_nrpe; then + echo '/usr/lib/nagios/plugins/check_nrpe is missing, please install nagios-nrpe-plugin package.' + exit 1 +fi + +if [ -r /etc/nagios/nrpe.d/evolix.cfg ]; then + command=$(grep "check_$1" /etc/nagios/nrpe.d/evolix.cfg | grep -v '^[[:blank:]]*#' | tail -n1 | cut -d'=' -f2-) + echo "Command:" + echo " $command" +fi + +echo "NRPE daemon output:" +/usr/lib/nagios/plugins/check_nrpe -H 127.0.0.1 -c "check_$1" + + diff --git a/nagios-nrpe/files/check-local_completion b/nagios-nrpe/files/check-local_completion new file mode 100644 index 00000000..174ae061 --- /dev/null +++ b/nagios-nrpe/files/check-local_completion @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +_check_local_dynamic_completion() { + local cur; + cur=${COMP_WORDS[COMP_CWORD]}; + COMPREPLY=(); + COMPREPLY=( $( compgen -W '$(grep check_ /etc/nagios/nrpe.d/evolix.cfg | grep -vE "^[[:blank:]]*#" | awk -F"[\\\[\\\]=_]" "{print \$3}")' -- $cur ) ); +} + +complete -F _check_local_dynamic_completion check-local + + diff --git a/nagios-nrpe/files/check_async b/nagios-nrpe/files/check_async index 5ff8ad24..2a54f920 100644 --- a/nagios-nrpe/files/check_async +++ b/nagios-nrpe/files/check_async @@ -59,9 +59,9 @@ delay_from_check_file() { enable_check() { if [ "$(id -u)" -eq "0" ] ; then - /usr/share/scripts/alerts_switch enable "${check_name}" + /usr/local/bin/alerts_switch enable "${check_name}" else - sudo /usr/share/scripts/alerts_switch enable "${check_name}" + sudo /usr/local/bin/alerts_switch enable "${check_name}" fi } diff --git a/nagios-nrpe/files/plugins/check_ceph_df b/nagios-nrpe/files/plugins/check_ceph_df new file mode 100755 index 00000000..0f798aa1 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_ceph_df @@ -0,0 +1,232 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2013 SWITCH http://www.switch.ch +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from __future__ import print_function +import argparse +import os +import subprocess +import sys + +__version__ = '1.7.1' + +# default ceph values +CEPH_COMMAND = '/usr/bin/ceph' + +# nagios exit code +STATUS_OK = 0 +STATUS_WARNING = 1 +STATUS_ERROR = 2 +STATUS_UNKNOWN = 3 + +def main(): + + # parse args + parser = argparse.ArgumentParser(description="'ceph df' nagios plugin.") + parser.add_argument('-e','--exe', help='ceph executable [%s]' % CEPH_COMMAND) + parser.add_argument('-c','--conf', help='alternative ceph conf file') + parser.add_argument('-m','--monaddress', help='ceph monitor address[:port]') + parser.add_argument('-i','--id', help='ceph client id') + parser.add_argument('-n','--name', help='ceph client name') + parser.add_argument('-k','--keyring', help='ceph client keyring file') + parser.add_argument('-p','--pool', help='ceph pool name') + parser.add_argument('-d','--detail', help="show pool details on warn and critical", action='store_true') + parser.add_argument('-W','--warn', help="warn above this percent RAW USED", type=float) + parser.add_argument('-C','--critical', help="critical alert above this percent RAW USED", type=float) + parser.add_argument('-V','--version', help='show version and exit', action='store_true') + args = parser.parse_args() + + # validate args + ceph_exec = args.exe if args.exe else CEPH_COMMAND + if not os.path.exists(ceph_exec): + print("ERROR: ceph executable '%s' doesn't exist" % ceph_exec) + return STATUS_UNKNOWN + + if args.version: + print('version %s' % __version__) + return STATUS_OK + + if args.conf and not os.path.exists(args.conf): + print("ERROR: ceph conf file '%s' doesn't exist" % args.conf) + return STATUS_UNKNOWN + + if args.keyring and not os.path.exists(args.keyring): + print("ERROR: keyring file '%s' doesn't exist" % args.keyring) + return STATUS_UNKNOWN + + if not args.warn or not args.critical or args.warn > args.critical: + print("ERROR: warn and critical level must be set and critical must be greater than warn") + return STATUS_UNKNOWN + + # build command + ceph_df = [ceph_exec] + if args.monaddress: + ceph_df.append('-m') + ceph_df.append(args.monaddress) + if args.conf: + ceph_df.append('-c') + ceph_df.append(args.conf) + if args.id: + ceph_df.append('--id') + ceph_df.append(args.id) + if args.name: + ceph_df.append('--name') + ceph_df.append(args.name) + if args.keyring: + ceph_df.append('--keyring') + ceph_df.append(args.keyring) + ceph_df.append('df') + + #print ceph_df + + # exec command + p = subprocess.Popen(ceph_df,stdout=subprocess.PIPE,stderr=subprocess.PIPE) + output, err = p.communicate() + + # parse output + # print "DEBUG: output:", output + # print "DEBUG: err:", err + if output: + output = output.decode('utf-8') + # parse output + # if detail switch was not set only show global values and compare to warning and critical + # otherwise show space for pools too + result=output.splitlines() + # values for GLOBAL are in 3rd line of output + globalline = result[2] + globalvals = globalline.split() + # Luminous vs Minic output (27.3TiB vs 27.3 TiB) + if len(globalvals) == 7: + gv = [] + gv.append("{}{}".format(globalvals[0], globalvals[1])) + gv.append("{}{}".format(globalvals[2], globalvals[3])) + gv.append("{}{}".format(globalvals[4], globalvals[5])) + gv.append(globalvals[6]) + globalvals = gv + #print "XXX: globalvals: {} {}".format(len(globalvals), globalvals) + # Nautilus output + if len(globalvals) == 10: + gv = [] + gv.append("{}{}".format(globalvals[1], globalvals[2])) + gv.append("{}{}".format(globalvals[3], globalvals[4])) + gv.append("{}{}".format(globalvals[5], globalvals[6])) + gv.append(globalvals[9]) + globalvals = gv + #print "XXX: globalvals: {} {}".format(len(globalvals), globalvals) + + # prepare pool values + # pool output starts in line 4 with the bare word POOLS: followed by the output + poollines = result[3:] + + if args.pool: + for line in poollines: + if args.pool in line: + poolvals = line.split() + # Luminous vs Minic output (27.3TiB vs 27.3 TiB) + if len(poolvals) == 8: + pv = [] + pv.append(poolvals[0]) # NAME + pv.append(poolvals[1]) # ID + pv.append("{}{}".format(poolvals[2], poolvals[3])) # USED 27.3 TiB + pv.append(poolvals[4]) # %USED + pv.append("{}{}".format(poolvals[5], poolvals[6])) # MAX AVAIL 27.3 TiB + # pv.append(poolvals[7]) # OBJECTS + poolvals = pv + #print "XXX: poolvals: {} {}".format(len(poolvals), poolvals) + # Nautilus output + if len(poolvals) == 10: + pv = [] + pv.append(poolvals[0]) # NAME + pv.append(poolvals[1]) # ID + pv.append("{}{}".format(poolvals[2], poolvals[3])) # USED 27.3 TiB + pv.append(poolvals[7]) # %USED + pv.append("{}{}".format(poolvals[8], poolvals[9])) # MAX AVAIL 27.3 TiB + # pv.append(poolvals[7]) # OBJECTS, not used + poolvals = pv + #print "XXX: poolvals: {} {}".format(len(poolvals), poolvals) + # Octopus >= v15.2.8 (pgs added to ceph-df) + if len(poolvals) == 11: + pv = [] + pv.append(poolvals[0]) # NAME + pv.append(poolvals[1]) # ID + #pv.append(poolvals[2]) # PGS, not used + pv.append("{}{}".format(poolvals[3], poolvals[4])) # USED 27.3 TiB + pv.append(poolvals[8]) # %USED + pv.append("{}{}".format(poolvals[9], poolvals[10])) # MAX AVAIL 27.3 TiB + # pv.append(poolvals[7]) # OBJECTS, not used + poolvals = pv + #print "XXX: poolvals: {} {}".format(len(poolvals), poolvals) + + + pool_used = poolvals[2] + pool_usage_percent = float(poolvals[3]) + pool_available_space = poolvals[4] + # pool_objects = float(poolvals[5]) # not used + + if pool_usage_percent > args.critical: + print('CRITICAL: %s%% usage in Pool \'%s\' is above %s%% (%s used) | Usage=%s%%;%s;%s;;' % (pool_usage_percent, args.pool, args.critical, pool_used, pool_usage_percent, args.warn, args.critical)) + return STATUS_ERROR + if pool_usage_percent > args.warn: + print('WARNING: %s%% usage in Pool \'%s\' is above %s%% (%s used) | Usage=%s%%;%s;%s;;' % (pool_usage_percent, args.pool, args.warn, pool_used, pool_usage_percent, args.warn, args.critical)) + return STATUS_WARNING + else: + print('%s%% usage in Pool \'%s\' | Usage=%s%%;%s;%s;;' % (pool_usage_percent, args.pool, pool_usage_percent, args.warn, args.critical)) + return STATUS_OK + else: + # print 'DEBUG:', globalvals + # finally 4th element contains percentual value + # print 'DEBUG USAGE:', globalvals[3] + global_usage_percent = float(globalvals[3]) + global_available_space = globalvals[1] + global_total_space = globalvals[0] + # print 'DEBUG WARNLEVEL:', args.warn + # print 'DEBUG CRITICALLEVEL:', args.critical + if global_usage_percent > args.critical: + if args.detail: + poollines.insert(0, '\n') + poolout = '\n '.join(poollines) + else: + poolout = '' + print('CRITICAL: global RAW usage of %s%% is above %s%% (%s of %s free)%s | Usage=%s%%;%s;%s;;' % (global_usage_percent, args.critical, global_available_space, global_total_space, poolout, global_usage_percent, args.warn, args.critical)) + return STATUS_ERROR + elif global_usage_percent > args.warn: + if args.detail: + poollines.insert(0, '\n') + poolout = '\n '.join(poollines) + else: + poolout = '' + print('WARNING: global RAW usage of %s%% is above %s%% (%s of %s free)%s | Usage=%s%%;%s;%s;;' % (global_usage_percent, args.warn, global_available_space, global_total_space, poolout, global_usage_percent, args.warn, args.critical)) + return STATUS_WARNING + else: + print('RAW usage %s%% | Usage=%s%%;%s;%s;;' % (global_usage_percent, global_usage_percent, args.warn, args.critical)) + return STATUS_OK + + #for + elif err: + # read only first line of error + one_line = err.split('\n')[0] + if '-1 ' in one_line: + idx = one_line.rfind('-1 ') + print('ERROR: %s: %s' % (ceph_exec, one_line[idx+len('-1 '):])) + else: + print(one_line) + + return STATUS_UNKNOWN + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/nagios-nrpe/files/plugins/check_ceph_health b/nagios-nrpe/files/plugins/check_ceph_health new file mode 100755 index 00000000..ede44914 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_ceph_health @@ -0,0 +1,200 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2013-2016 SWITCH http://www.switch.ch +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from __future__ import print_function +import argparse +import os +import subprocess +import sys +import re +import json + +__version__ = '1.7.0' + +# default ceph values +CEPH_ADM_COMMAND = '/usr/sbin/cephadm' +CEPH_COMMAND = '/usr/bin/ceph' + +# nagios exit code +STATUS_OK = 0 +STATUS_WARNING = 1 +STATUS_ERROR = 2 +STATUS_UNKNOWN = 3 + + +def main(): + + # parse args + parser = argparse.ArgumentParser(description="'ceph health' nagios plugin.") + parser.add_argument('-e','--exe', help='ceph executable [%s]' % CEPH_COMMAND) + parser.add_argument('-A','--admexe', help='cephadm executable [%s]' % CEPH_ADM_COMMAND) + parser.add_argument('--cluster', help='ceph cluster name') + parser.add_argument('-c','--conf', help='alternative ceph conf file') + parser.add_argument('-m','--monaddress', help='ceph monitor address[:port]') + parser.add_argument('-i','--id', help='ceph client id') + parser.add_argument('-n','--name', help='ceph client name') + parser.add_argument('-k','--keyring', help='ceph client keyring file') + parser.add_argument('--check', help='regexp of which check(s) to check (luminous+) ' + "Can be inverted, e.g. '^((?!(PG_DEGRADED|OBJECT_MISPLACED)$).)*$'") + parser.add_argument('-w','--whitelist', help='whitelist regexp for ceph health warnings') + parser.add_argument('-d','--detail', help="exec 'ceph health detail'", action='store_true') + parser.add_argument('-V','--version', help='show version and exit', action='store_true') + parser.add_argument('-a','--cephadm', help='uses cephadm to execute the command', action='store_true') + parser.add_argument('-s','--skip-muted', help='skip muted checks', action='store_true') + args = parser.parse_args() + + # validate args + cephadm_exec = args.admexe if args.admexe else CEPH_ADM_COMMAND + ceph_exec = args.exe if args.exe else CEPH_COMMAND + + if args.cephadm: + if not os.path.exists(cephadm_exec): + print("ERROR: cephadm executable '%s' doesn't exist" % cephadm_exec) + return STATUS_UNKNOWN + else: + if not os.path.exists(ceph_exec): + print("ERROR: ceph executable '%s' doesn't exist" % ceph_exec) + return STATUS_UNKNOWN + + if args.version: + print('version %s' % __version__) + return STATUS_OK + + if args.conf and not os.path.exists(args.conf): + print("ERROR: ceph conf file '%s' doesn't exist" % args.conf) + return STATUS_UNKNOWN + + if args.keyring and not os.path.exists(args.keyring): + print("ERROR: keyring file '%s' doesn't exist" % args.keyring) + return STATUS_UNKNOWN + + # build command + ceph_health = [ceph_exec] + + if args.cephadm: + # Prepend the command with the cephadm binary and the shell command + ceph_health = [cephadm_exec, 'shell'] + ceph_health + + if args.monaddress: + ceph_health.append('-m') + ceph_health.append(args.monaddress) + if args.cluster: + ceph_health.append('--cluster') + ceph_health.append(args.cluster) + if args.conf: + ceph_health.append('-c') + ceph_health.append(args.conf) + if args.id: + ceph_health.append('--id') + ceph_health.append(args.id) + if args.name: + ceph_health.append('--name') + ceph_health.append(args.name) + if args.keyring: + ceph_health.append('--keyring') + ceph_health.append(args.keyring) + ceph_health.append('health') + if args.detail: + ceph_health.append('detail') + + ceph_health.append('--format') + ceph_health.append('json') + #print(ceph_health) + + # exec command + p = subprocess.Popen(ceph_health,stdout=subprocess.PIPE,stderr=subprocess.PIPE) + output, err = p.communicate() + try: + output = json.loads(output) + except ValueError: + output = dict() + + # parse output + # print "output:", output + #print "err:", err + if output: + ret = STATUS_OK + msg = "" + extended = [] + if 'checks' in output: + #luminous + for check,status in output['checks'].items(): + # skip check if not selected + if args.check and not re.search(args.check, check): + continue + + if args.skip_muted and ('muted' in status and status['muted']): + continue + + check_detail = "%s( %s )" % (check, status['summary']['message']) + + if status["severity"] == "HEALTH_ERR": + extended.append(msg) + msg = "CRITICAL: %s" % check_detail + ret = STATUS_ERROR + continue + + if args.whitelist and re.search(args.whitelist,status['summary']['message']): + continue + + check_msg = "WARNING: %s" % check_detail + if not msg: + msg = check_msg + ret = STATUS_WARNING + else: + extended.append(check_msg) + else: + #pre-luminous + for status in output["summary"]: + if status != "HEALTH_OK": + if status == "HEALTH_ERROR": + msg = "CRITICAL: %s" % status['summary'] + ret = STATUS_ERROR + continue + + if args.whitelist and re.search(args.whitelist,status['summary']): + continue + + if not msg: + msg = "WARNING: %s" % status['summary'] + ret = STATUS_WARNING + else: + extended.append("WARNING: %s" % status['summary']) + + if msg: + print(msg) + else: + print("HEALTH OK") + if extended: print('\n'.join(extended)) + return ret + + + elif err: + # read only first line of error + one_line = err.split('\n')[0] + if '-1 ' in one_line: + idx = one_line.rfind('-1 ') + print('ERROR: %s: %s' % (ceph_exec, one_line[idx+len('-1 '):])) + else: + print(one_line) + + return STATUS_UNKNOWN + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/nagios-nrpe/files/plugins/check_ceph_mds b/nagios-nrpe/files/plugins/check_ceph_mds new file mode 100755 index 00000000..4e654c05 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_ceph_mds @@ -0,0 +1,188 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2013 Catalyst IT http://www.catalyst.net.nz +# Copyright (c) 2015 SWITCH http://www.switch.ch +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from __future__ import print_function +import argparse +import socket +import os +import re +import subprocess +import sys +import json + +__version__ = '1.6.0' + +# default ceph values +CEPH_EXEC = '/usr/bin/ceph' +CEPH_COMMAND = 'mds stat -f json' + +# nagios exit code +STATUS_OK = 0 +STATUS_WARNING = 1 +STATUS_ERROR = 2 +STATUS_UNKNOWN = 3 + +def main(): + # parse args + parser = argparse.ArgumentParser(description="'ceph mds stat' nagios plugin.") + parser.add_argument('-e','--exe', help='ceph executable [%s]' % CEPH_EXEC) + parser.add_argument('-c','--conf', help='alternative ceph conf file') + parser.add_argument('-m','--monaddress', help='ceph monitor to use for queries (address[:port])') + parser.add_argument('-i','--id', help='ceph client id') + parser.add_argument('-k','--keyring', help='ceph client keyring file') + parser.add_argument('-V','--version', help='show version and exit', action='store_true') + parser.add_argument('-n','--name', help='mds daemon name', required=True) + parser.add_argument('-f','--filesystem', help='mds filesystem name', required=True) + args = parser.parse_args() + + if args.version: + print('version %s' % __version__) + return STATUS_OK + + # validate args + ceph_exec = args.exe if args.exe else CEPH_EXEC + if not os.path.exists(ceph_exec): + print("MDS ERROR: ceph executable '%s' doesn't exist" % ceph_exec) + return STATUS_UNKNOWN + + if args.conf and not os.path.exists(args.conf): + print("MDS ERROR: ceph conf file '%s' doesn't exist" % args.conf) + return STATUS_UNKNOWN + + if args.keyring and not os.path.exists(args.keyring): + print("MDS ERROR: keyring file '%s' doesn't exist" % args.keyring) + return STATUS_UNKNOWN + + # build command + ceph_cmd = [ceph_exec] + if args.monaddress: + ceph_cmd.append('-m') + ceph_cmd.append(args.monaddress) + if args.conf: + ceph_cmd.append('-c') + ceph_cmd.append(args.conf) + if args.id: + ceph_cmd.append('--id') + ceph_cmd.append(args.id) + if args.keyring: + ceph_cmd.append('--keyring') + ceph_cmd.append(args.keyring) + ceph_cmd.extend(CEPH_COMMAND.split(' ')) + + # exec command + p = subprocess.Popen(ceph_cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) + output, err = p.communicate() + + if p.returncode != 0 or not output: + print("MDS ERROR: %s" % err) + return STATUS_ERROR + + # load json output and parse + mds_stat = None + try: + mds_stat = json.loads(output) + except Exception as e: + print("MDS ERROR: could not parse '%s' output: %s: %s" % (CEPH_COMMAND,output,e)) + return STATUS_UNKNOWN + + return check_target_mds(mds_stat, args.filesystem, args.name) + +def check_target_mds(mds_stat, fs_name, name): + # find mds from standby list + standby_mdss = _get_standby_mds(mds_stat) + for mds in standby_mdss: + if mds.get_name() == name: + print("MDS OK: %s" % (mds)) + return STATUS_OK + + # find mds from active list + active_mdss = _get_active_mds(mds_stat, fs_name) + + if active_mdss: + for mds in active_mdss: + if mds.get_name() != name: + continue + # target mds in active list + print("MDS %s: %s" % ("WARN" if mds.is_laggy() else "OK", mds)) + return STATUS_WARNING if mds.is_laggy() else STATUS_OK + + # mds not found + print("MDS ERROR: MDS '%s' is not found (offline?)" % (name)) + return STATUS_ERROR + else: + # fs not found in map, perhaps user input error + print("MDS ERROR: FS '%s' is not found in fsmap" % (fs_name)) + return STATUS_ERROR + +def _get_standby_mds(mds_stat): + mds_array = [] + for mds in mds_stat['fsmap']['standbys']: + name = mds['name'] + state = mds['state'] + laggy_since = mds['laggy_since'] if 'laggy_since' in mds else None + mds_array.append(MDS(name, state)) + + return mds_array + +def _get_active_mds(mds_stat, fs_name): + mds_fs = mds_stat['fsmap']['filesystems'] + + # find filesystem in stat + for i in range(len(mds_fs)): + mdsmap = mds_fs[i]['mdsmap'] + if mdsmap['fs_name'] != fs_name: + continue + # put mds to array + mds_array = [] + infos = mds_stat['fsmap']['filesystems'][i]['mdsmap']['info'] + for gid in infos: + name = infos[gid]['name'] + state = infos[gid]['state'] + laggy_since = infos[gid]['laggy_since'] if 'laggy_since' in infos[gid] else None + mds_array.append(MDS(name, state, laggy_since)) + + return mds_array + + # no fs found + return None + +class MDS(object): + def __init__(self, name, state, laggy_since=None): + self.name = name + self.state = state + self.laggy_since = laggy_since + + def get_name(self): + return self.name + + def get_state(self): + return self.state + + def is_laggy(self): + return self.laggy_since is not None + + def __str__(self): + msg = "MDS '%s' is %s" % (self.name, self.state) + if self.laggy_since is not None: + msg += " (laggy or crashed)" + return msg + +# main +if __name__ == "__main__": + sys.exit(main()) diff --git a/nagios-nrpe/files/plugins/check_ceph_mgr b/nagios-nrpe/files/plugins/check_ceph_mgr new file mode 100755 index 00000000..019e4a3f --- /dev/null +++ b/nagios-nrpe/files/plugins/check_ceph_mgr @@ -0,0 +1,188 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018 SWITCH http://www.switch.ch +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from __future__ import print_function +import argparse +import os +import subprocess +import sys +import json + +__version__ = '1.0.0' + +# default ceph values +CEPH_EXEC = '/usr/bin/ceph' +CEPH_COMMAND = 'mgr dump -f json' + +CEPH_MGR_DUMP_EXAMPLE = ''' +$ ceph --version +ceph version 12.2.7 (3ec878d1e53e1aeb47a9f619c49d9e7c0aa384d5) luminous (stable) +$ ceph mgr dump -f json|jq . +{ + "epoch": 165, + "active_gid": 248001409, + "active_name": "zhdk0013", + "active_addr": "10.10.10.9:6800/810408", + "available": true, + "standbys": [ + { + "gid": 247991934, + "name": "zhdk0009", + "available_modules": [ + "balancer", + "dashboard", + "influx", + "localpool", + "prometheus", + "restful", + "selftest", + "status", + "zabbix" + ] + }, + { + "gid": 248011196, + "name": "zhdk0025", + "available_modules": [ + "balancer", + "dashboard", + "influx", + "localpool", + "prometheus", + "restful", + "selftest", + "status", + "zabbix" + ] + } + ], + "modules": [ + "balancer", + "restful", + "status" + ], + "available_modules": [ + "balancer", + "dashboard", + "influx", + "localpool", + "prometheus", + "restful", + "selftest", + "status", + "zabbix" + ], + "services": {} +} +''' + +# nagios exit code +STATUS_OK = 0 +STATUS_WARNING = 1 +STATUS_ERROR = 2 +STATUS_UNKNOWN = 3 + + +def main(): + # parse args + parser = argparse.ArgumentParser(description="'ceph mgr dump' nagios plugin.") + parser.add_argument('-e', '--exe', help='ceph executable [%s]' % CEPH_EXEC) + parser.add_argument('-c', '--conf', help='alternative ceph conf file') + parser.add_argument('-m', '--monaddress', help='ceph monitor to use for queries (address[:port])') + parser.add_argument('-i', '--id', help='ceph client id') + parser.add_argument('-n', '--name', help='ceph client name') + parser.add_argument('-k', '--keyring', help='ceph client keyring file') + parser.add_argument('-V', '--version', help='show version and exit', action='store_true') + args = parser.parse_args() + + if args.version: + print("version {}".format(__version__)) + return STATUS_OK + + # validate args + ceph_exec = args.exe if args.exe else CEPH_EXEC + if not os.path.exists(ceph_exec): + print("MGR ERROR: ceph executable '{}' doesn't exist".format(ceph_exec)) + return STATUS_UNKNOWN + + if args.conf and not os.path.exists(args.conf): + print("MGR ERROR: ceph conf file '{}' doesn't exist".format(args.conf)) + return STATUS_UNKNOWN + + if args.keyring and not os.path.exists(args.keyring): + print("MGR ERROR: keyring file '{}' doesn't exist".format(args.keyring)) + return STATUS_UNKNOWN + + # build command + ceph_cmd = [ceph_exec] + if args.monaddress: + ceph_cmd.append('-m') + ceph_cmd.append(args.monaddress) + if args.conf: + ceph_cmd.append('-c') + ceph_cmd.append(args.conf) + if args.id: + ceph_cmd.append('--id') + ceph_cmd.append(args.id) + if args.name: + ceph_cmd.append('--name') + ceph_cmd.append(args.name) + if args.keyring: + ceph_cmd.append('--keyring') + ceph_cmd.append(args.keyring) + ceph_cmd.extend(CEPH_COMMAND.split(' ')) + + # exec command + p = subprocess.Popen(ceph_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output, err = p.communicate() + + if p.returncode != 0 or not output: + print("MGR ERROR: {}".format(err)) + return STATUS_UNKNOWN + + # load json output and parse + mgr_dump = None + try: + mgr_dump = json.loads(output) + except Exception as e: + print("MGR ERROR: could not parse '{}' output: {}: {}".format(ceph_cmd, output, e)) + return STATUS_UNKNOWN + + # check active + if 'active_name' not in mgr_dump: + print("MGR CRITICAL: not active mgr found") + print("JSON: {}".format(json.dumps(mgr_dump))) + return STATUS_ERROR + + active_mgr_name = mgr_dump['active_name'] + # check standby + standby_mgr_names = [] + for standby_mgr in mgr_dump['standbys']: + standby_mgr_names.append(standby_mgr['name']) + + if len(standby_mgr_names) <= 0: + print("MGR WARN: active: {} but no standbys".format(active_mgr_name)) + return STATUS_WARNING + else: + print("MGR OK: active: {}, standbys: {}".format(active_mgr_name, + ", ".join(standby_mgr_names))) + return STATUS_OK + +# main +if __name__ == "__main__": + sys.exit(main()) diff --git a/nagios-nrpe/files/plugins/check_ceph_mon b/nagios-nrpe/files/plugins/check_ceph_mon new file mode 100755 index 00000000..db417676 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_ceph_mon @@ -0,0 +1,163 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2013 Catalyst IT http://www.catalyst.net.nz +# Copyright (c) 2015 SWITCH http://www.switch.ch +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from __future__ import print_function +import argparse +import socket +import os +import re +import subprocess +import sys +import json + +__version__ = '1.5.0' + +# default ceph values +CEPH_EXEC = '/usr/bin/ceph' +CEPH_COMMAND = 'quorum_status' + +# nagios exit code +STATUS_OK = 0 +STATUS_WARNING = 1 +STATUS_ERROR = 2 +STATUS_UNKNOWN = 3 + +## +# ceph quorum_status output example +## +ceph_quorum_status_output_example = '''{ + "quorum_leader_name" : "s0001", + "monmap" : { + "mons" : [ + { + "name" : "s0001", + "addr" : "[2001:620:5ca1:8000::1001]:6789/0", + "rank" : 0 + }, + { + "name" : "s0003", + "addr" : "[2001:620:5ca1:8000::1003]:6789/0", + "rank" : 1 + } + ], + "created" : "2014-12-15 08:28:35.153650", + "epoch" : 2, + "modified" : "2014-12-15 08:28:40.371878", + "fsid" : "22348d2b-b69d-46cc-9a79-ca93cd6bae84" + }, + "quorum_names" : [ + "s0001", + "s0003" + ], + "quorum" : [ + 0, + 1 + ], + "election_epoch" : 24 +}''' + +def main(): + + # parse args + parser = argparse.ArgumentParser(description="'ceph quorum_status' nagios plugin.") + parser.add_argument('-e','--exe', help='ceph executable [%s]' % CEPH_EXEC) + parser.add_argument('-c','--conf', help='alternative ceph conf file') + parser.add_argument('-m','--monaddress', help='ceph monitor to use for queries (address[:port])') + parser.add_argument('-i','--id', help='ceph client id') + parser.add_argument('-k','--keyring', help='ceph client keyring file') + parser.add_argument('-V','--version', help='show version and exit', action='store_true') + parser.add_argument('-I','--monid', help='mon ID to be checked for availability') + args = parser.parse_args() + + if args.version: + print('version %s' % __version__) + return STATUS_OK + + # validate args + ceph_exec = args.exe if args.exe else CEPH_EXEC + if not os.path.exists(ceph_exec): + print("MON ERROR: ceph executable '%s' doesn't exist" % ceph_exec) + return STATUS_UNKNOWN + + if args.conf and not os.path.exists(args.conf): + print("MON ERROR: ceph conf file '%s' doesn't exist" % args.conf) + return STATUS_UNKNOWN + + if args.keyring and not os.path.exists(args.keyring): + print("MON ERROR: keyring file '%s' doesn't exist" % args.keyring) + return STATUS_UNKNOWN + + if not args.monid: + print("MON ERROR: no MON ID given, use -I/--monid parameter") + return STATUS_UNKNOWN + + # build command + ceph_cmd = [ceph_exec] + if args.monaddress: + ceph_cmd.append('-m') + ceph_cmd.append(args.monaddress) + if args.conf: + ceph_cmd.append('-c') + ceph_cmd.append(args.conf) + if args.id: + ceph_cmd.append('--id') + ceph_cmd.append(args.id) + if args.keyring: + ceph_cmd.append('--keyring') + ceph_cmd.append(args.keyring) + ceph_cmd.append(CEPH_COMMAND) + + # exec command + p = subprocess.Popen(ceph_cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) + output, err = p.communicate() + + if p.returncode != 0 or not output: + print("MON ERROR: %s" % err) + return STATUS_ERROR + + # load json output and parse + quorum_status = False + try: + quorum_status = json.loads(output) + except Exception as e: + print("MON ERROR: could not parse '%s' output: %s: %s" % (CEPH_COMMAND,output,e)) + return STATUS_UNKNOWN + + #print "XXX: quorum_status['quorum_names']:", quorum_status['quorum_names'] + + # do our checks + is_monitor = False + for mon in quorum_status['monmap']['mons']: + if mon['name'] == args.monid: + is_monitor = True + if not is_monitor: + print("MON WARN: mon '%s' is not in monmap: %s" % (args.monid,quorum_status['monmap']['mons'])) + return STATUS_WARNING + + in_quorum = args.monid in quorum_status['quorum_names'] + if in_quorum: + print("MON OK") + return STATUS_OK + else: + print("MON WARN: no MON '%s' found in quorum" % args.monid) + return STATUS_WARNING + +# main +if __name__ == "__main__": + sys.exit(main()) diff --git a/nagios-nrpe/files/plugins/check_ceph_osd b/nagios-nrpe/files/plugins/check_ceph_osd new file mode 100755 index 00000000..88a37488 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_ceph_osd @@ -0,0 +1,154 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2013 Catalyst IT http://www.catalyst.net.nz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# 1.5.2 (2019-06-16) Martin Seener: fixed regex to work with Ceph Nautilus (14.2.x) + +from __future__ import print_function +import argparse +import os +import re +import subprocess +import sys +import socket + +__version__ = '1.5.2' + +# default ceph values +CEPH_COMMAND = '/usr/bin/ceph' + +# nagios exit code +STATUS_OK = 0 +STATUS_WARNING = 1 +STATUS_ERROR = 2 +STATUS_UNKNOWN = 3 + +def main(): + + # parse args + parser = argparse.ArgumentParser(description="'ceph osd' nagios plugin.") + parser.add_argument('-e','--exe', help='ceph executable [%s]' % CEPH_COMMAND) + parser.add_argument('-c','--conf', help='alternative ceph conf file') + parser.add_argument('-m','--monaddress', help='ceph monitor address[:port]') + parser.add_argument('-i','--id', help='ceph client id') + parser.add_argument('-k','--keyring', help='ceph client keyring file') + parser.add_argument('-V','--version', help='show version and exit', action='store_true') + parser.add_argument('-H','--host', help='osd host', required=True) + parser.add_argument('-I','--osdid', help='osd id', required=False) + parser.add_argument('-C','--crit', help='Number of failed OSDs to trigger critical (default=2)',type=int,default=2, required=False) + parser.add_argument('-o','--out', help='check osds that are set OUT', default=False, action='store_true', required=False) + args = parser.parse_args() + + # validate args + ceph_exec = args.exe if args.exe else CEPH_COMMAND + if not os.path.exists(ceph_exec): + print("OSD ERROR: ceph executable '%s' doesn't exist" % ceph_exec) + return STATUS_UNKNOWN + + if args.version: + print('version %s' % __version__) + return STATUS_OK + + if args.conf and not os.path.exists(args.conf): + print("OSD ERROR: ceph conf file '%s' doesn't exist" % args.conf) + return STATUS_UNKNOWN + + if args.keyring and not os.path.exists(args.keyring): + print("OSD ERROR: keyring file '%s' doesn't exist" % args.keyring) + return STATUS_UNKNOWN + + if not args.osdid: + args.osdid = '[^ ]*' + + if not args.host: + print("OSD ERROR: no OSD hostname given") + return STATUS_UNKNOWN + + try: + addrinfo = socket.getaddrinfo(args.host, None, 0, socket.SOCK_STREAM) + args.host = addrinfo[0][-1][0] + if addrinfo[0][0] == socket.AF_INET6: + args.host = "[%s]" % args.host + except: + print('OSD ERROR: could not resolve %s' % args.host) + return STATUS_UNKNOWN + + + # build command + ceph_cmd = [ceph_exec] + if args.monaddress: + ceph_cmd.append('-m') + ceph_cmd.append(args.monaddress) + if args.conf: + ceph_cmd.append('-c') + ceph_cmd.append(args.conf) + if args.id: + ceph_cmd.append('--id') + ceph_cmd.append(args.id) + if args.keyring: + ceph_cmd.append('--keyring') + ceph_cmd.append(args.keyring) + ceph_cmd.append('osd') + ceph_cmd.append('dump') + + # exec command + p = subprocess.Popen(ceph_cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) + output, err = p.communicate() + output = output.decode('utf8') + + if err or not output: + print("OSD ERROR: %s" % err) + return STATUS_ERROR + + # escape IPv4 host address + osd_host = args.host.replace('.', '\.') + # escape IPv6 host address + osd_host = osd_host.replace('[', '\[') + osd_host = osd_host.replace(']', '\]') + up = re.findall(r"^(osd\.%s) up.*%s:" % (args.osdid, osd_host), output, re.MULTILINE) + if args.out: + down = re.findall(r"^(osd\.%s) down.*%s:" % (args.osdid, osd_host), output, re.MULTILINE) + down_in = re.findall(r"^(osd\.%s) down[ ]+in.*%s:" % (args.osdid, osd_host), output, re.MULTILINE) + down_out = re.findall(r"^(osd\.%s) down[ ]+out.*%s:" % (args.osdid, osd_host), output, re.MULTILINE) + else: + down = re.findall(r"^(osd\.%s) down[ ]+in.*%s:" % (args.osdid, osd_host), output, re.MULTILINE) + down_in = down + down_out = re.findall(r"^(osd\.%s) down[ ]+out.*%s:" % (args.osdid, osd_host), output, re.MULTILINE) + + if down: + print("OSD %s: Down OSD%s on %s: %s" % ('CRITICAL' if len(down)>=args.crit else 'WARNING' ,'s' if len(down)>1 else '', args.host, " ".join(down))) + print("Up OSDs: " + " ".join(up)) + print("Down+In OSDs: " + " ".join(down_in)) + print("Down+Out OSDs: " + " ".join(down_out)) + print("| 'osd_up'=%d 'osd_down_in'=%d;;%d 'osd_down_out'=%d;;%d" % (len(up), len(down_in), args.crit, len(down_out), args.crit)) + if len(down)>=args.crit: + return STATUS_ERROR + else: + return STATUS_WARNING + + if up: + print("OSD OK") + print("Up OSDs: " + " ".join(up)) + print("Down+In OSDs: " + " ".join(down_in)) + print("Down+Out OSDs: " + " ".join(down_out)) + print("| 'osd_up'=%d 'osd_down_in'=%d;;%d 'osd_down_out'=%d;;%d" % (len(up), len(down_in), args.crit, len(down_out), args.crit)) + return STATUS_OK + + print("OSD WARN: no OSD.%s found on host %s" % (args.osdid, args.host)) + return STATUS_WARNING + +if __name__ == "__main__": + sys.exit(main()) diff --git a/nagios-nrpe/files/plugins/check_ceph_osd_db b/nagios-nrpe/files/plugins/check_ceph_osd_db new file mode 100755 index 00000000..6a01836b --- /dev/null +++ b/nagios-nrpe/files/plugins/check_ceph_osd_db @@ -0,0 +1,152 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2020 Binero AB https://binero.com +# Copyright (c) 2013 Catalyst IT http://www.catalyst.net.nz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import os +import re +import subprocess +import sys +import socket +import json + + +CEPH_COMMAND = '/usr/bin/ceph' + +STATUS_OK = 0 +STATUS_CRITICAL = 2 +STATUS_UNKNOWN = 3 + + +def main(): + parser = argparse.ArgumentParser(description="'ceph osd' nagios plugin.") + + parser.add_argument('-e','--exe', help='ceph executable [%s]' % CEPH_COMMAND) + parser.add_argument('-c','--conf', help='alternative ceph conf file') + parser.add_argument('-m','--monaddress', help='ceph monitor address[:port]') + parser.add_argument('-i','--id', help='ceph client id') + parser.add_argument('-k','--keyring', help='ceph client keyring file') + parser.add_argument('-H','--host', help='osd host', required=True) + parser.add_argument('-C','--critical', help='critical threshold', default=60) + + args = parser.parse_args() + + ceph_exec = args.exe if args.exe else CEPH_COMMAND + if not os.path.exists(ceph_exec): + print("UNKNOWN: ceph executable '%s' doesn't exist" % ceph_exec) + return STATUS_UNKNOWN + + if args.conf and not os.path.exists(args.conf): + print("UNKNOWN: ceph conf file '%s' doesn't exist" % args.conf) + return STATUS_UNKNOWN + + if args.keyring and not os.path.exists(args.keyring): + print("UNKNOWN: keyring file '%s' doesn't exist" % args.keyring) + return STATUS_UNKNOWN + + if not args.host: + print("UNKNOWN: no OSD hostname given") + return STATUS_UNKNOWN + + try: + addrinfo = socket.getaddrinfo(args.host, None, 0, socket.SOCK_STREAM) + args.host = addrinfo[0][-1][0] + if addrinfo[0][0] == socket.AF_INET6: + args.host = "[%s]" % args.host + except Exception: + print('UNKNOWN: could not resolve %s' % args.host) + return STATUS_UNKNOWN + + ceph_cmd = [ceph_exec] + if args.monaddress: + ceph_cmd.append('-m') + ceph_cmd.append(args.monaddress) + if args.conf: + ceph_cmd.append('-c') + ceph_cmd.append(args.conf) + if args.id: + ceph_cmd.append('--id') + ceph_cmd.append(args.id) + if args.keyring: + ceph_cmd.append('--keyring') + ceph_cmd.append(args.keyring) + + ceph_cmd.append('osd') + ceph_cmd.append('dump') + + p = subprocess.Popen(ceph_cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) + output, err = p.communicate() + + if err or not output: + print("CRITICAL: %s" % err) + return STATUS_CRITICAL + + # escape IPv4 host address + osd_host = args.host.replace('.', '\.') + # escape IPv6 host address + osd_host = osd_host.replace('[', '\[') + osd_host = osd_host.replace(']', '\]') + + osds_up = re.findall(r"^(osd\.[^ ]*) up.*%s:" % (osd_host), output, re.MULTILINE) + + final_status = STATUS_OK + lines = [] + + for osd in osds_up: + daemon_ceph_cmd = [ceph_exec, '--format', 'json'] + daemon_ceph_cmd.append('daemon') + daemon_ceph_cmd.append(osd) + daemon_ceph_cmd.append('perf') + daemon_ceph_cmd.append('dump') + + p = subprocess.Popen(daemon_ceph_cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) + output, err = p.communicate() + + if err or not output: + print("CRITICAL: %s" % err) + return STATUS_CRITICAL + + try: + data = json.loads(output) + except Exception: + print("CRITICAL: failed to load json") + return STATUS_CRITICAL + + bluefs = data.get('bluefs', None) + + if not bluefs: + continue + + db_total_bytes = bluefs.get('db_total_bytes') + db_used_bytes = bluefs.get('db_used_bytes') + perc = (float(db_used_bytes) / float(db_total_bytes) * 100) + + if perc >= args.critical and final_status == STATUS_OK: + final_status = STATUS_CRITICAL + + lines.append("%s=%.2f%%" % (osd, perc)) + + if final_status == STATUS_OK: + print("OK: %s" % (' '.join(lines))) + else: + print("CRITICAL: %s" % (' '.join(lines))) + + return final_status + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/nagios-nrpe/files/plugins/check_ceph_osd_df b/nagios-nrpe/files/plugins/check_ceph_osd_df new file mode 100755 index 00000000..fb1c2806 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_ceph_osd_df @@ -0,0 +1,153 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# check_ceph_osd_df - Check OSD DF output +# Copyright (c) 2020 noris network AG https://www.noris.de +# +# This plugin will not output perfdata as there is likely a lot of output +# which should be gathered using other tools. +# +# Parts based on code from check_ceph_df which is +# Copyright (c) 2013 SWITCH http://www.switch.ch +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from __future__ import print_function +import argparse +import os +import subprocess +import sys +import json +from operator import itemgetter + +# Semver +__version__ = '1.0.0' + +# default ceph values +CEPH_COMMAND = '/usr/bin/ceph' + +# nagios exit code +STATUS_OK = 0 +STATUS_WARNING = 1 +STATUS_ERROR = 2 +STATUS_UNKNOWN = 3 + +def main(): + + # parse args + parser = argparse.ArgumentParser(description="'ceph osd df' nagios plugin.") + parser.add_argument('-e','--exe', help='ceph executable [%s]' % CEPH_COMMAND) + parser.add_argument('-c','--conf', help='alternative ceph conf file') + parser.add_argument('-m','--monaddress', help='ceph monitor address[:port]') + parser.add_argument('-i','--id', help='ceph client id') + parser.add_argument('-n','--name', help='ceph client name') + parser.add_argument('-k','--keyring', help='ceph client keyring file') + parser.add_argument('-W','--warn', help="warn above this percent USED", type=float) + parser.add_argument('-C','--critical', help="critical alert above this percent USED", type=float) + parser.add_argument('-V','--version', help='show version and exit', action='store_true') + args = parser.parse_args() + + # validate args + ceph_exec = args.exe if args.exe else CEPH_COMMAND + if not os.path.exists(ceph_exec): + print("ERROR: ceph executable '%s' doesn't exist" % ceph_exec) + return STATUS_UNKNOWN + + if args.version: + print('version %s' % __version__) + return STATUS_OK + + if args.conf and not os.path.exists(args.conf): + print("ERROR: ceph conf file '%s' doesn't exist" % args.conf) + return STATUS_UNKNOWN + + if args.keyring and not os.path.exists(args.keyring): + print("ERROR: keyring file '%s' doesn't exist" % args.keyring) + return STATUS_UNKNOWN + + if not args.warn or not args.critical or args.warn > args.critical: + print("ERROR: warn and critical level must be set and critical must be greater than warn") + return STATUS_UNKNOWN + + # build command + ceph_osd_df = [ceph_exec] + if args.monaddress: + ceph_osd_df.append('-m') + ceph_osd_df.append(args.monaddress) + if args.conf: + ceph_osd_df.append('-c') + ceph_osd_df.append(args.conf) + if args.id: + ceph_osd_df.append('--id') + ceph_osd_df.append(args.id) + if args.name: + ceph_osd_df.append('--name') + ceph_osd_df.append(args.name) + if args.keyring: + ceph_osd_df.append('--keyring') + ceph_osd_df.append(args.keyring) + ceph_osd_df.append('osd') + ceph_osd_df.append('df') + ceph_osd_df.append('--format=json') + + # exec command + p = subprocess.Popen(ceph_osd_df,stdout=subprocess.PIPE,stderr=subprocess.PIPE) + output, err = p.communicate() + + # parse output + # print "DEBUG: output:", output + # print "DEBUG: err:", err + if output: + # parse output + try: + result = json.loads(output) + check_return_value = STATUS_OK + nodes_sorted = sorted(result["nodes"], key=itemgetter('utilization','id')) + + warn_crit_osds = [] + + for node in reversed(nodes_sorted): + if node["utilization"] >= args.warn and check_return_value is not STATUS_ERROR: + check_return_value = STATUS_WARNING + warn_crit_osds.append("{}={:04.2f}".format(node["name"], node["utilization"])) + + if node["utilization"] >= args.critical: + check_return_value = STATUS_ERROR + warn_crit_osds.append("{}={:04.2f}".format(node["name"], node["utilization"])) + + if check_return_value == STATUS_OK: + print("OK: All OSDs within limits") + return STATUS_OK + elif check_return_value == STATUS_WARNING: + print("WARNING: OSD usage above warn threshold: {:.4054}".format(", ".join(warn_crit_osds))) + return STATUS_WARNING + elif check_return_value == STATUS_ERROR: + print("CRITICAL: OSD usage above critical or warn threshold: {:.4041}".format(", ".join(warn_crit_osds))) + return STATUS_ERROR + except: + print("ERROR: {}".format(sys.exc_info()[0])) + return STATUS_UNKNOWN + elif err: + # read only first line of error + one_line = err.split('\n')[0] + if '-1 ' in one_line: + idx = one_line.rfind('-1 ') + print('ERROR: %s: %s' % (ceph_exec, one_line[idx+len('-1 '):])) + else: + print(one_line) + + return STATUS_UNKNOWN + +if __name__ == "__main__": + sys.exit(main()) diff --git a/nagios-nrpe/files/plugins/check_ceph_rgw b/nagios-nrpe/files/plugins/check_ceph_rgw new file mode 100755 index 00000000..39773f79 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_ceph_rgw @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2014 Catalyst IT http://www.catalyst.net.nz +# Copyright (c) 2015 SWITCH http://www.switch.ch +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from __future__ import print_function +import argparse +import os +import re +import subprocess +import sys +import json + +__version__ = '1.5.1' + +# default ceph values +RGW_COMMAND = '/usr/bin/radosgw-admin' + +# nagios exit code +STATUS_OK = 0 +STATUS_WARNING = 1 +STATUS_ERROR = 2 +STATUS_UNKNOWN = 3 + +def main(): + + # parse args + parser = argparse.ArgumentParser(description="'radosgw-admin bucket stats' nagios plugin.") + parser.add_argument('-d','--detail', help='output perf data for all buckets', action='store_true') + parser.add_argument('-B','--byte', help='output perf data in Byte instead of KB', action='store_true') + parser.add_argument('-e','--exe', help='radosgw-admin executable [%s]' % RGW_COMMAND) + parser.add_argument('-c','--conf', help='alternative ceph conf file') + parser.add_argument('-i','--id', help='ceph client id') + parser.add_argument('-n','--name', help='ceph client name (type.id)') + parser.add_argument('-V','--version', help='show version and exit', action='store_true') + args = parser.parse_args() + + # validate args + rgw_exec = args.exe if args.exe else RGW_COMMAND + if not os.path.exists(rgw_exec): + print("RGW ERROR: radosgw-admin executable '%s' doesn't exist" % rgw_exec) + return STATUS_UNKNOWN + + if args.version: + print('version %s' % __version__) + return STATUS_OK + + if args.conf and not os.path.exists(args.conf): + print("RGW ERROR: ceph conf file '%s' doesn't exist" % args.conf) + return STATUS_UNKNOWN + + # build command + rgw_cmd = [rgw_exec] + if args.conf: + rgw_cmd.append('-c') + rgw_cmd.append(args.conf) + if args.id: + rgw_cmd.append('--id') + rgw_cmd.append(args.id) + if args.name: + rgw_cmd.append('-n') + rgw_cmd.append(args.name) + rgw_cmd.append('bucket') + rgw_cmd.append('stats') + + # exec command + p = subprocess.Popen(rgw_cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE) + output, err = p.communicate() + + if p.returncode != 0 or not output: + print("RGW ERROR: %s :: %s" % (output, err)) + return STATUS_ERROR + + bucket_stats = json.loads(output) + #print bucket_stats + + buckets = [] + for i in bucket_stats: + if type(i) is dict: + bucket_name = i['bucket'] + usage_dict = i['usage'] + if usage_dict and 'rgw.main' in usage_dict: + bucket_usage_kb = usage_dict['rgw.main']['size_kb_actual'] + else: + bucket_usage_kb = 0 + buckets.append((bucket_name, bucket_usage_kb)) + buckets_total_kb = sum([b[1] for b in buckets]) + + if args.byte: + status = "RGW OK: {} buckets, {} KB total | /={}B ".format(len(buckets),buckets_total_kb,buckets_total_kb*1024) + else: + status = "RGW OK: {} buckets, {} KB total | /={}KB ".format(len(buckets),buckets_total_kb,buckets_total_kb) + #print buckets + if buckets and args.detail: + if args.byte: + status = status + " ".join(["{}={}B".format(b[0],b[1]*1024) for b in buckets]) + else: + status = status + " ".join(["{}={}KB".format(b[0],b[1]) for b in buckets]) + + print(status) + return STATUS_OK + +if __name__ == "__main__": + sys.exit(main()) diff --git a/nagios-nrpe/files/plugins/check_ceph_rgw_api b/nagios-nrpe/files/plugins/check_ceph_rgw_api new file mode 100755 index 00000000..1235f98d --- /dev/null +++ b/nagios-nrpe/files/plugins/check_ceph_rgw_api @@ -0,0 +1,116 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2014 Catalyst IT http://www.catalyst.net.nz +# Copyright (c) 2015 SWITCH http://www.switch.ch +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function +import requests +import warnings +import json +import argparse +import sys +from awsauth import S3Auth + +__version__ = '1.7.2' + +# nagios exit code +STATUS_OK = 0 +STATUS_WARNING = 1 +STATUS_CRITICAL = 2 +STATUS_UNKNOWN = 3 + +def main(): + + # parse args + parser = argparse.ArgumentParser(description="'radosgw api bucket stats' nagios plugin.") + parser.add_argument('-H', '--host', help="Server URL for the radosgw api (example: http://objects.dreamhost.com/)", required=True) + parser.add_argument('-k', '--insecure', help="Allow insecure server connections when using SSL", action="store_false") + parser.add_argument('-e', '--admin_entry', help="The entry point for an admin request URL [default is '%(default)s']", default="admin") + parser.add_argument('-a', '--access_key', help="S3 access key", required=True) + parser.add_argument('-s', '--secret_key', help="S3 secret key", required=True) + parser.add_argument('-d', '--detail', help="output perf data for all buckets", action="store_true") + parser.add_argument('-b', '--byte', help="output perf data in Byte instead of KB", action="store_true") + parser.add_argument('-v', '--version', help='show version and exit', action="store_true") + args = parser.parse_args() + + if args.version: + print("version {0}".format(__version__)) + return STATUS_OK + + # helpers for default schema + if not args.host.startswith("http"): + args.host = "http://{0}".format(args.host) + # and for request_uri + if not args.host.endswith("/"): + args.host = "{0}/".format(args.host) + + url = "{0}{1}/bucket?format=json&stats=True".format(args.host, + args.admin_entry) + + try: + # Inversion of condition, when '--insecure' is defined we disable + # requests warning about certificate hostname mismatch. + if not args.insecure: + warnings.filterwarnings('ignore', message='Unverified HTTPS request') + + response = requests.get(url, verify=args.insecure, + auth=S3Auth(args.access_key, args.secret_key, + args.host)) + + if response.status_code == requests.codes.ok: + bucket_stats = response.json() + else: + # no usage caps or wrong admin entry + print("RGW ERROR [{0}]: {1}".format(response.status_code, + response.content.decode('utf-8'))) + return STATUS_WARNING + +# DNS, connection errors, etc + except requests.exceptions.RequestException as e: + print("RGW ERROR: {0}".format(e)) + return STATUS_UNKNOWN + + #print(bucket_stats) + buckets = [] + for i in bucket_stats: + if type(i) is dict: + bucket_name = i['bucket'] + usage_dict = i['usage'] + if usage_dict and 'rgw.main' in usage_dict: + bucket_usage_kb = usage_dict['rgw.main']['size_kb_actual'] + else: + bucket_usage_kb = 0 + buckets.append((bucket_name, bucket_usage_kb)) + buckets_total_kb = sum([b[1] for b in buckets]) + + status = "RGW OK: {0} buckets, {1} KB total | /={2}{3} " + + if args.byte: + status = status.format(len(buckets), buckets_total_kb, buckets_total_kb*1024, "B") + else: + status = status.format(len(buckets), buckets_total_kb, buckets_total_kb, "KB") + #print(buckets) + if buckets and args.detail: + if args.byte: + status = status + " ".join(["{}={}B".format(b[0], b[1]*1024) for b in buckets]) + else: + status = status + " ".join(["{}={}KB".format(b[0], b[1]) for b in buckets]) + + print(status) + return STATUS_OK + +if __name__ == "__main__": + sys.exit(main()) 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_haproxy_stats b/nagios-nrpe/files/plugins/check_haproxy_stats index fc51938f..d08c4103 100755 --- a/nagios-nrpe/files/plugins/check_haproxy_stats +++ b/nagios-nrpe/files/plugins/check_haproxy_stats @@ -5,6 +5,7 @@ # Copyright (C) 2012, Giacomo Montagner # 2015, Yann Fertat, Romain Dessort, Jeff Palmer, # Christophe Drevet-Droguet +# 2022, Jérémy Lecour # # This program is free software; you can redistribute it and/or modify it # under the same terms as Perl 5.10.1. @@ -15,7 +16,7 @@ # warranty of merchantability or fitness for a particular purpose. # -our $VERSION = "1.2.0"; +our $VERSION = "1.3.1"; open(STDERR, ">&STDOUT"); @@ -29,6 +30,8 @@ open(STDERR, ">&STDOUT"); # 1.1.0 - support for HTTP interface # 1.1.1 - drop perl 5.10 requirement # 1.2.0 - add an option for ignore NOLB +# 1.3.0 - add an option for ignore DRAIN +# 1.3.1 - support DRAIN/MAINT when set by agent use strict; use warnings; @@ -64,6 +67,8 @@ DESCRIPTION Assume servers in MAINT state to be ok. -n, --ignore-nolb Assume servers in NOLB state to be ok. + --ignore-drain + Assume servers in DRAIN state to be ok. -p, --proxy Check only named proxies, not every one. Use comma to separate proxies in list. @@ -132,6 +137,7 @@ my $pass = ''; my $dump; my $ignore_maint; my $ignore_nolb; +my $ignore_drain; my $proxy; my $no_proxy; my $help; @@ -143,7 +149,8 @@ GetOptions ( "d|dump" => \$dump, "h|help" => \$help, "m|ignore-maint" => \$ignore_maint, - "n|ignore-nolb" => \$ignore_nolb, + "n|ignore-nolb" => \$ignore_nolb, + "ignore-drain" => \$ignore_drain, "p|proxy=s" => \$proxy, "P|no-proxy=s" => \$no_proxy, "s|sock|socket=s" => \$sock, @@ -267,8 +274,9 @@ foreach (@hastats) { # Check of servers } else { if ($data[$status] ne 'UP') { - next if ($ignore_maint && $data[$status] eq 'MAINT'); + next if ($ignore_maint && ($data[$status] eq 'MAINT' || $data[$status] eq 'MAINT (agent)')); next if ($ignore_nolb && $data[$status] eq 'NOLB'); + next if ($ignore_drain && ($data[$status] eq 'DRAIN' || $data[$status] eq 'DRAIN (agent)')); next if $data[$status] eq 'no check'; # Ignore server if no check is configured to be run next if $data[$svname] eq 'sock-1'; $exitcode = 2; diff --git a/nagios-nrpe/files/plugins/check_mongodb b/nagios-nrpe/files/plugins/check_mongodb old mode 100755 new mode 100644 index bc6278ac..cce3a76d --- a/nagios-nrpe/files/plugins/check_mongodb +++ b/nagios-nrpe/files/plugins/check_mongodb @@ -17,24 +17,29 @@ # - Dag Stockstad # - @Andor on github # - Steven Richards - Captainkrtek on github -# - Max Vernimmen +# - Max Vernimmen - @mvernimmen-CG / @mvernimmen on github +# - Kris Nova - @kris@nivenly.com github.com/kris-nova +# - Jan Kantert - firstname@lastname.net # # USAGE # # See the README.md # +from __future__ import print_function +from __future__ import division import sys import time import optparse -import textwrap import re import os +import numbers +import socket try: import pymongo -except ImportError, e: - print e +except ImportError as e: + print(e) sys.exit(2) # As of pymongo v 1.9 the SON API is part of the BSON package, therefore attempt @@ -78,37 +83,35 @@ def performance_data(perf_data, params): def numeric_type(param): - if ((type(param) == float or type(param) == int or param == None)): - return True - return False + return param is None or isinstance(param, numbers.Real) def check_levels(param, warning, critical, message, ok=[]): if (numeric_type(critical) and numeric_type(warning)): if param >= critical: - print "CRITICAL - " + message + print("CRITICAL - " + message) sys.exit(2) elif param >= warning: - print "WARNING - " + message + print("WARNING - " + message) sys.exit(1) else: - print "OK - " + message + print("OK - " + message) sys.exit(0) else: if param in critical: - print "CRITICAL - " + message + print("CRITICAL - " + message) sys.exit(2) if param in warning: - print "WARNING - " + message + print("WARNING - " + message) sys.exit(1) if param in ok: - print "OK - " + message + print("OK - " + message) sys.exit(0) # unexpected param value - print "CRITICAL - Unexpected value : %d" % param + "; " + message + print("CRITICAL - Unexpected value : %d" % param + "; " + message) return 2 @@ -120,21 +123,32 @@ def get_server_status(con): data = con.admin.command(son.SON([('serverStatus', 1)])) return data +def split_host_port(string): + if not string.rsplit(':', 1)[-1].isdigit(): + return (string, None) + string = string.rsplit(':', 1) + host = string[0] # 1st index is always host + port = int(string[1]) + return (host, port) + def main(argv): p = optparse.OptionParser(conflict_handler="resolve", description="This Nagios plugin checks the health of mongodb.") p.add_option('-H', '--host', action='store', type='string', dest='host', default='127.0.0.1', help='The hostname you want to connect to') - p.add_option('-P', '--port', action='store', type='int', dest='port', default=27017, help='The port mongodb is runnung on') + p.add_option('-h', '--host-to-check', action='store', type='string', dest='host_to_check', default=None, help='The hostname you want to check (if this is different from the host you are connecting)') + p.add_option('--rdns-lookup', action='store_true', dest='rdns_lookup', default=False, help='RDNS(PTR) lookup on given host/host-to-check, to convert ip-address to fqdn') + p.add_option('-P', '--port', action='store', type='int', dest='port', default=27017, help='The port mongodb is running on') + p.add_option('--port-to-check', action='store', type='int', dest='port_to_check', default=None, help='The port you want to check (if this is different from the port you are connecting)') p.add_option('-u', '--user', action='store', type='string', dest='user', default=None, help='The username you want to login as') p.add_option('-p', '--pass', action='store', type='string', dest='passwd', default=None, help='The password you want to use for that user') - p.add_option('-W', '--warning', action='store', dest='warning', default=None, help='The warning threshold we want to set') - p.add_option('-C', '--critical', action='store', dest='critical', default=None, help='The critical threshold we want to set') + p.add_option('-W', '--warning', action='store', dest='warning', default=None, help='The warning threshold you want to set') + p.add_option('-C', '--critical', action='store', dest='critical', default=None, help='The critical threshold you want to set') p.add_option('-A', '--action', action='store', type='choice', dest='action', default='connect', help='The action you want to take', choices=['connect', 'connections', 'replication_lag', 'replication_lag_percent', 'replset_state', 'memory', 'memory_mapped', 'lock', - 'flushing', 'last_flush_time', 'index_miss_ratio', 'databases', 'collections', 'database_size', 'database_indexes', 'collection_indexes', 'collection_size', - 'queues', 'oplog', 'journal_commits_in_wl', 'write_data_files', 'journaled', 'opcounters', 'current_lock', 'replica_primary', 'page_faults', - 'asserts', 'queries_per_second', 'page_faults', 'chunks_balance', 'connect_primary', 'collection_state', 'row_count', 'replset_quorum']) + 'flushing', 'last_flush_time', 'index_miss_ratio', 'databases', 'collections', 'database_size', 'database_indexes', 'collection_documents', 'collection_indexes', 'collection_size', + 'collection_storageSize', 'queues', 'oplog', 'journal_commits_in_wl', 'write_data_files', 'journaled', 'opcounters', 'current_lock', 'replica_primary', + 'page_faults', 'asserts', 'queries_per_second', 'page_faults', 'chunks_balance', 'connect_primary', 'collection_state', 'row_count', 'replset_quorum']) p.add_option('--max-lag', action='store_true', dest='max_lag', default=False, help='Get max replication lag (for replication_lag action only)') p.add_option('--mapped-memory', action='store_true', dest='mapped_memory', default=False, help='Get mapped memory instead of resident (if resident memory can not be read)') p.add_option('-D', '--perf-data', action='store_true', dest='perf_data', default=False, help='Enable output of Nagios performance data') @@ -145,12 +159,28 @@ def main(argv): p.add_option('-q', '--querytype', action='store', dest='query_type', default='query', help='The query type to check [query|insert|update|delete|getmore|command] from queries_per_second') p.add_option('-c', '--collection', action='store', dest='collection', default='admin', help='Specify the collection to check') p.add_option('-T', '--time', action='store', type='int', dest='sample_time', default=1, help='Time used to sample number of pages faults') + p.add_option('-M', '--mongoversion', action='store', type='choice', dest='mongo_version', default='2', help='The MongoDB version you are talking with, either 2 or 3', + choices=['2','3']) + p.add_option('-a', '--authdb', action='store', type='string', dest='authdb', default='admin', help='The database you want to authenticate against') + p.add_option('--insecure', action='store_true', dest='insecure', default=False, help="Don't verify SSL/TLS certificates") + p.add_option('--ssl-ca-cert-file', action='store', type='string', dest='ssl_ca_cert_file', default=None, help='Path to Certificate Authority file for SSL') + p.add_option('-f', '--ssl-cert-file', action='store', type='string', dest='cert_file', default=None, help='Path to PEM encoded key and cert for client authentication') + p.add_option('-m','--auth-mechanism', action='store', type='choice', dest='auth_mechanism', default=None, help='Auth mechanism used for auth with mongodb', + choices=['MONGODB-X509','SCRAM-SHA-256','SCRAM-SHA-1']) + p.add_option('--disable_retry_writes', dest='retry_writes_disabled', default=False, action='callback', callback=optional_arg(True), help='Disable retryWrites feature') options, arguments = p.parse_args() host = options.host + host_to_check = options.host_to_check if options.host_to_check else options.host + rdns_lookup = options.rdns_lookup + if (rdns_lookup): + host_to_check = socket.getnameinfo((host_to_check, 0), 0)[0] port = options.port + port_to_check = options.port_to_check if options.port_to_check else options.port user = options.user passwd = options.passwd + authdb = options.authdb + query_type = options.query_type collection = options.collection sample_time = options.sample_time @@ -164,9 +194,15 @@ def main(argv): action = options.action perf_data = options.perf_data max_lag = options.max_lag + mongo_version = options.mongo_version database = options.database ssl = options.ssl replicaset = options.replicaset + insecure = options.insecure + ssl_ca_cert_file = options.ssl_ca_cert_file + cert_file = options.cert_file + auth_mechanism = options.auth_mechanism + retry_writes_disabled = options.retry_writes_disabled if action == 'replica_primary' and replicaset is None: return "replicaset must be passed in when using replica_primary check" @@ -177,31 +213,35 @@ def main(argv): # moving the login up here and passing in the connection # start = time.time() - err, con = mongo_connect(host, port, ssl, user, passwd, replicaset) + err, con = mongo_connect(host, port, ssl, user, passwd, replicaset, authdb, insecure, ssl_ca_cert_file, cert_file, auth_mechanism, retry_writes_disabled=retry_writes_disabled) + if err != 0: + return err + + # Autodetect mongo-version and force pymongo to let us know if it can connect or not. + err, mongo_version = check_version(con) if err != 0: return err conn_time = time.time() - start - conn_time = round(conn_time, 0) if action == "connections": return check_connections(con, warning, critical, perf_data) elif action == "replication_lag": - return check_rep_lag(con, host, port, warning, critical, False, perf_data, max_lag, user, passwd) + return check_rep_lag(con, host_to_check, port_to_check, rdns_lookup, warning, critical, False, perf_data, max_lag, ssl, user, passwd, replicaset, authdb, insecure, ssl_ca_cert_file, cert_file, auth_mechanism, retry_writes_disabled=retry_writes_disabled) elif action == "replication_lag_percent": - return check_rep_lag(con, host, port, warning, critical, True, perf_data, max_lag, user, passwd) + return check_rep_lag(con, host_to_check, port_to_check, rdns_lookup, warning, critical, True, perf_data, max_lag, ssl, user, passwd, replicaset, authdb, insecure, ssl_ca_cert_file, cert_file, auth_mechanism, retry_writes_disabled=retry_writes_disabled) elif action == "replset_state": return check_replset_state(con, perf_data, warning, critical) elif action == "memory": - return check_memory(con, warning, critical, perf_data, options.mapped_memory) + return check_memory(con, warning, critical, perf_data, options.mapped_memory, host) elif action == "memory_mapped": return check_memory_mapped(con, warning, critical, perf_data) elif action == "queues": return check_queues(con, warning, critical, perf_data) elif action == "lock": - return check_lock(con, warning, critical, perf_data) + return check_lock(con, warning, critical, perf_data, mongo_version) elif action == "current_lock": - return check_current_lock(con, host, warning, critical, perf_data) + return check_current_lock(con, host, port, warning, critical, perf_data) elif action == "flushing": return check_flushing(con, warning, critical, True, perf_data) elif action == "last_flush_time": @@ -223,22 +263,26 @@ def main(argv): return check_database_size(con, database, warning, critical, perf_data) elif action == "database_indexes": return check_database_indexes(con, database, warning, critical, perf_data) + elif action == "collection_documents": + return check_collection_documents(con, database, collection, warning, critical, perf_data) elif action == "collection_indexes": return check_collection_indexes(con, database, collection, warning, critical, perf_data) elif action == "collection_size": return check_collection_size(con, database, collection, warning, critical, perf_data) + elif action == "collection_storageSize": + return check_collection_storageSize(con, database, collection, warning, critical, perf_data) elif action == "journaled": return check_journaled(con, warning, critical, perf_data) elif action == "write_data_files": return check_write_to_datafiles(con, warning, critical, perf_data) elif action == "opcounters": - return check_opcounters(con, host, warning, critical, perf_data) + return check_opcounters(con, host, port, warning, critical, perf_data) elif action == "asserts": - return check_asserts(con, host, warning, critical, perf_data) + return check_asserts(con, host, port, warning, critical, perf_data) elif action == "replica_primary": - return check_replica_primary(con, host, warning, critical, perf_data, replicaset) + return check_replica_primary(con, host, warning, critical, perf_data, replicaset, mongo_version) elif action == "queries_per_second": - return check_queries_per_second(con, query_type, warning, critical, perf_data) + return check_queries_per_second(con, query_type, warning, critical, perf_data, mongo_version) elif action == "page_faults": check_page_faults(con, sample_time, warning, critical, perf_data) elif action == "chunks_balance": @@ -255,30 +299,73 @@ def main(argv): return check_connect(host, port, warning, critical, perf_data, user, passwd, conn_time) -def mongo_connect(host=None, port=None, ssl=False, user=None, passwd=None, replica=None): +def mongo_connect(host=None, port=None, ssl=False, user=None, passwd=None, replica=None, authdb="admin", insecure=False, ssl_ca_cert_file=None, ssl_cert=None, auth_mechanism=None, retry_writes_disabled=False): + from pymongo.errors import ConnectionFailure + from pymongo.errors import PyMongoError + import ssl as SSL + + con_args = dict() + + if ssl: + if insecure: + con_args['ssl_cert_reqs'] = SSL.CERT_NONE + else: + con_args['ssl_cert_reqs'] = SSL.CERT_REQUIRED + con_args['ssl'] = ssl + if ssl_ca_cert_file: + con_args['ssl_ca_certs'] = ssl_ca_cert_file + if ssl_cert: + con_args['ssl_certfile'] = ssl_cert + + if retry_writes_disabled: + con_args['retryWrites'] = False + try: # ssl connection for pymongo > 2.3 if pymongo.version >= "2.3": if replica is None: - con = pymongo.MongoClient(host, port) + con = pymongo.MongoClient(host, port, **con_args) else: - con = pymongo.Connection(host, port, read_preference=pymongo.ReadPreference.SECONDARY, ssl=ssl, replicaSet=replica, network_timeout=10) + con = pymongo.MongoClient(host, port, read_preference=pymongo.ReadPreference.SECONDARY, replicaSet=replica, **con_args) else: if replica is None: con = pymongo.Connection(host, port, slave_okay=True, network_timeout=10) else: con = pymongo.Connection(host, port, slave_okay=True, network_timeout=10) - #con = pymongo.Connection(host, port, slave_okay=True, replicaSet=replica, network_timeout=10) + + # we must authenticate the connection, otherwise we won't be able to perform certain operations + if ssl_cert and ssl_ca_cert_file and user and auth_mechanism == 'SCRAM-SHA-256': + con.the_database.authenticate(user, mechanism='SCRAM-SHA-256') + elif ssl_cert and ssl_ca_cert_file and user and auth_mechanism == 'SCRAM-SHA-1': + con.the_database.authenticate(user, mechanism='SCRAM-SHA-1') + elif ssl_cert and ssl_ca_cert_file and user and auth_mechanism == 'MONGODB-X509': + con.the_database.authenticate(user, mechanism='MONGODB-X509') + + try: + result = con.admin.command("ismaster") + except ConnectionFailure: + print("CRITICAL - Connection to Mongo server on %s:%s has failed" % (host, port) ) + sys.exit(2) + + if 'arbiterOnly' in result and result['arbiterOnly'] == True: + print("OK - State: 7 (Arbiter on port %s)" % (port)) + sys.exit(0) if user and passwd: - db = con["admin"] - if not db.authenticate(user, passwd): + db = con[authdb] + try: + db.authenticate(user, password=passwd) + except PyMongoError: sys.exit("Username/Password incorrect") - except Exception, e: + + # Ping to check that the server is responding. + con.admin.command("ping") + + except Exception as e: if isinstance(e, pymongo.errors.AutoReconnect) and str(e).find(" is an arbiter") != -1: # We got a pymongo AutoReconnect exception that tells us we connected to an Arbiter Server # This means: Arbiter is reachable and can answer requests/votes - this is all we need to know from an arbiter - print "OK - State: 7 (Arbiter)" + print("OK - State: 7 (Arbiter)") sys.exit(0) return exit_with_general_critical(e), None return 0, con @@ -288,7 +375,7 @@ def exit_with_general_warning(e): if isinstance(e, SystemExit): return e else: - print "WARNING - General MongoDB warning:", e + print("WARNING - General MongoDB warning:", e) return 1 @@ -296,19 +383,27 @@ def exit_with_general_critical(e): if isinstance(e, SystemExit): return e else: - print "CRITICAL - General MongoDB Error:", e + print("CRITICAL - General MongoDB Error:", e) return 2 def set_read_preference(db): - if pymongo.version >= "2.1": + if pymongo.version >= "2.2": + pymongo.read_preferences.Secondary + else: db.read_preference = pymongo.ReadPreference.SECONDARY +def check_version(con): + try: + server_info = con.server_info() + except Exception as e: + return exit_with_general_critical(e), None + return 0, int(server_info['version'].split('.')[0].strip()) def check_connect(host, port, warning, critical, perf_data, user, passwd, conn_time): warning = warning or 3 critical = critical or 6 - message = "Connection took %i seconds" % conn_time + message = "Connection took %.3f seconds" % conn_time message += performance_data(perf_data, [(conn_time, "connection_time", warning, critical)]) return check_levels(conn_time, warning, critical, message) @@ -330,13 +425,17 @@ def check_connections(con, warning, critical, perf_data): (available, "available_connections")]) return check_levels(used_percent, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) -def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_lag, user, passwd): +def check_rep_lag(con, host, port, rdns_lookup, warning, critical, percent, perf_data, max_lag, ssl=False, user=None, passwd=None, replicaset=None, authdb="admin", insecure=None, ssl_ca_cert_file=None, cert_file=None, auth_mechanism=None, retry_writes_disabled=False): # Get mongo to tell us replica set member name when connecting locally if "127.0.0.1" == host: + if not "me" in list(con.admin.command("ismaster","1").keys()): + print("UNKNOWN - This is not replicated MongoDB") + return 3 + host = con.admin.command("ismaster","1")["me"].split(':')[0] if percent: @@ -348,16 +447,15 @@ def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_la rs_status = {} slaveDelays = {} try: - set_read_preference(con.admin) + #set_read_preference(con.admin) # Get replica set status try: rs_status = con.admin.command("replSetGetStatus") - except pymongo.errors.OperationFailure, e: - if e.code == None and str(e).find('failed: not running with --replSet"'): - print "OK - Not running with replSet" - return 0 - + except pymongo.errors.OperationFailure as e: + if ((e.code == None and str(e).find('failed: not running with --replSet"')) or (e.code == 76 and str(e).find('not running with --replSet"'))): + print("UNKNOWN - Not running with replSet") + return 3 serverVersion = tuple(con.server_info()['version'].split('.')) if serverVersion >= tuple("2.0.0".split(".")): # @@ -377,24 +475,32 @@ def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_la for member in rs_status["members"]: if member["stateStr"] == "PRIMARY": primary_node = member - if member["name"].split(':')[0] == host and int(member["name"].split(':')[1]) == port: + + # if rdns_lookup is true then lookup both values back to their rdns value so we can compare hostname vs fqdn + if rdns_lookup: + member_host, member_port = split_host_port(member.get('name')) + member_host = "{0}:{1}".format(socket.getnameinfo((member_host, 0), 0)[0], member_port) + if member_host == "{0}:{1}".format(socket.getnameinfo((host, 0), 0)[0], port): + host_node = member + # Exact match + elif member.get('name') == "{0}:{1}".format(host, port): host_node = member # Check if we're in the middle of an election and don't have a primary if primary_node is None: - print "WARNING - No primary defined. In an election?" + print("WARNING - No primary defined. In an election?") return 1 # Check if we failed to find the current host # below should never happen if host_node is None: - print "CRITICAL - Unable to find host '" + host + "' in replica set." + print("CRITICAL - Unable to find host '" + host + "' in replica set.") return 2 # Is the specified host the primary? if host_node["stateStr"] == "PRIMARY": if max_lag == False: - print "OK - This is the primary." + print("OK - This is the primary.") return 0 else: #get the maximal replication lag @@ -407,7 +513,7 @@ def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_la data = data + member['name'] + " lag=%d;" % replicationLag maximal_lag = max(maximal_lag, replicationLag) if percent: - err, con = mongo_connect(primary_node['name'].split(':')[0], int(primary_node['name'].split(':')[1]), False, user, passwd) + err, con = mongo_connect(split_host_port(primary_node['name'])[0], int(split_host_port(primary_node['name'])[1]), ssl, user, passwd, replicaset, authdb, insecure, ssl_ca_cert_file, cert_file, auth_mechanism, retry_writes_disabled=retry_writes_disabled) if err != 0: return err primary_timediff = replication_get_time_diff(con) @@ -419,8 +525,8 @@ def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_la message += performance_data(perf_data, [(maximal_lag, "replication_lag", warning, critical)]) return check_levels(maximal_lag, warning, critical, message) elif host_node["stateStr"] == "ARBITER": - print "OK - This is an arbiter" - return 0 + print("UNKNOWN - This is an arbiter") + return 3 # Find the difference in optime between current node and PRIMARY @@ -439,7 +545,7 @@ def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_la lag = float(optime_lag.seconds + optime_lag.days * 24 * 3600) if percent: - err, con = mongo_connect(primary_node['name'].split(':')[0], int(primary_node['name'].split(':')[1]), False, user, passwd) + err, con = mongo_connect(split_host_port(primary_node['name'])[0], int(split_host_port(primary_node['name'])[1]), ssl, user, passwd, replicaset, authdb, insecure, ssl_ca_cert_file, cert_file, auth_mechanism, retry_writes_disabled=retry_writes_disabled) if err != 0: return err primary_timediff = replication_get_time_diff(con) @@ -471,19 +577,19 @@ def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_la # Check if we're in the middle of an election and don't have a primary if primary_node is None: - print "WARNING - No primary defined. In an election?" + print("WARNING - No primary defined. In an election?") sys.exit(1) # Is the specified host the primary? if host_node["stateStr"] == "PRIMARY": - print "OK - This is the primary." + print("OK - This is the primary.") sys.exit(0) # Find the difference in optime between current node and PRIMARY optime_lag = abs(primary_node[1] - host_node["optimeDate"]) lag = optime_lag.seconds if percent: - err, con = mongo_connect(primary_node['name'].split(':')[0], int(primary_node['name'].split(':')[1])) + err, con = mongo_connect(split_host_port(primary_node['name'])[0], int(split_host_port(primary_node['name'])[1]), ssl, user, passwd, replicaset, authdb, insecure, ssl_ca_cert_file, cert_file, auth_mechanism, retry_writes_disabled=retry_writes_disabled) if err != 0: return err primary_timediff = replication_get_time_diff(con) @@ -495,26 +601,34 @@ def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_la message += performance_data(perf_data, [(lag, "replication_lag", warning, critical)]) return check_levels(lag, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) - -def check_memory(con, warning, critical, perf_data, mapped_memory): - # - # These thresholds are basically meaningless, and must be customized to your system's ram - # - - # Get the total system merory and calculate based on that how much memory used by Mongodb is ok or not. +# +# Check the memory usage of mongo. Alerting on this may be hard to get right +# because it'll try to get as much memory as it can. And that's probably +# a good thing. +# +def check_memory(con, warning, critical, perf_data, mapped_memory, host): + # Get the total system memory of this system (This is totally bogus if you + # are running this command remotely) and calculate based on that how much + # memory used by Mongodb is ok or not. meminfo = open('/proc/meminfo').read() matched = re.search(r'^MemTotal:\s+(\d+)', meminfo) - if matched: + if matched: mem_total_kB = int(matched.groups()[0]) - # Old way - #critical = critical or 16 - # The new way. if using >80% then warn, if >90% then critical level - warning = warning or (mem_total_kB * 0.8) / 1024.0 / 1024.0 - critical = critical or (mem_total_kB * 0.9) / 1024.0 / 1024.0 + if host != "127.0.0.1" and not warning: + # Running remotely and value was not set by user, use hardcoded value + warning = 12 + else: + # running locally or user provided value + warning = warning or (mem_total_kB * 0.8) / 1024.0 / 1024.0 + + if host != "127.0.0.1" and not critical: + critical = 16 + else: + critical = critical or (mem_total_kB * 0.9) / 1024.0 / 1024.0 # debugging #print "mem total: {0}kb, warn: {1}GB, crit: {2}GB".format(mem_total_kB,warning, critical) @@ -522,7 +636,7 @@ def check_memory(con, warning, critical, perf_data, mapped_memory): try: data = get_server_status(con) if not data['mem']['supported'] and not mapped_memory: - print "OK - Platform not supported for memory info" + print("OK - Platform not supported for memory info") return 0 # # convert to gigs @@ -559,7 +673,7 @@ def check_memory(con, warning, critical, perf_data, mapped_memory): else: return check_levels(mem_resident, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -572,7 +686,7 @@ def check_memory_mapped(con, warning, critical, perf_data): try: data = get_server_status(con) if not data['mem']['supported']: - print "OK - Platform not supported for memory info" + print("OK - Platform not supported for memory info") return 0 # # convert to gigs @@ -589,38 +703,45 @@ def check_memory_mapped(con, warning, critical, perf_data): message += " %.2fGB mappedWithJournal" % mem_mapped_journal except: mem_mapped_journal = 0 - message += performance_data(perf_data, [("%.2f" % mem_mapped, "memory_mapped"), ("%.2f" % mem_mapped_journal, "mappedWithJournal")]) + message += performance_data(perf_data, [("%.2f" % mem_mapped, "memory_mapped", warning, critical), ("%.2f" % mem_mapped_journal, "mappedWithJournal")]) if not mem_mapped == -1: return check_levels(mem_mapped, warning, critical, message) else: - print "OK - Server does not provide mem.mapped info" + print("OK - Server does not provide mem.mapped info") return 0 - except Exception, e: + except Exception as e: return exit_with_general_critical(e) -def check_lock(con, warning, critical, perf_data): +# +# Return the percentage of the time there was a global Lock +# +def check_lock(con, warning, critical, perf_data, mongo_version): warning = warning or 10 critical = critical or 30 - try: - data = get_server_status(con) - # - # calculate percentage - # - lockTime = data['globalLock']['lockTime'] - totalTime = data['globalLock']['totalTime'] - if lockTime > totalTime: - lock_percentage = 0.00 - else: - lock_percentage = float(lockTime) / float(totalTime) * 100 - message = "Lock Percentage: %.2f%%" % lock_percentage - message += performance_data(perf_data, [("%.2f" % lock_percentage, "lock_percentage", warning, critical)]) - return check_levels(lock_percentage, warning, critical, message) - - except Exception, e: - return exit_with_general_critical(e) + if mongo_version == 2: + try: + data = get_server_status(con) + lockTime = data['globalLock']['lockTime'] + totalTime = data['globalLock']['totalTime'] + # + # calculate percentage + # + if lockTime > totalTime: + lock_percentage = 0.00 + else: + lock_percentage = float(lockTime) / float(totalTime) * 100 + message = "Lock Percentage: %.2f%%" % lock_percentage + message += performance_data(perf_data, [("%.2f" % lock_percentage, "lock_percentage", warning, critical)]) + return check_levels(lock_percentage, warning, critical, message) + except Exception as e: + print("Couldn't get globalLock lockTime info from mongo, are you sure you're not using version 3? See the -M option.") + return exit_with_general_critical(e) + else: + print("OK - MongoDB version 3 doesn't report on global locks") + return 0 def check_flushing(con, warning, critical, avg, perf_data): @@ -632,19 +753,24 @@ def check_flushing(con, warning, critical, avg, perf_data): critical = critical or 15000 try: data = get_server_status(con) - if avg: - flush_time = float(data['backgroundFlushing']['average_ms']) - stat_type = "Average" - else: - flush_time = float(data['backgroundFlushing']['last_ms']) - stat_type = "Last" + try: + data['backgroundFlushing'] + if avg: + flush_time = float(data['backgroundFlushing']['average_ms']) + stat_type = "Average" + else: + flush_time = float(data['backgroundFlushing']['last_ms']) + stat_type = "Last" - message = "%s Flush Time: %.2fms" % (stat_type, flush_time) - message += performance_data(perf_data, [("%.2fms" % flush_time, "%s_flush_time" % stat_type.lower(), warning, critical)]) + message = "%s Flush Time: %.2fms" % (stat_type, flush_time) + message += performance_data(perf_data, [("%.2fms" % flush_time, "%s_flush_time" % stat_type.lower(), warning, critical)]) - return check_levels(flush_time, warning, critical, message) + return check_levels(flush_time, warning, critical, message) + except Exception: + print("OK - flushing stats not available for this storage engine") + return 0 - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -655,6 +781,7 @@ def index_miss_ratio(con, warning, critical, perf_data): data = get_server_status(con) try: + data['indexCounters'] serverVersion = tuple(con.server_info()['version'].split('.')) if serverVersion >= tuple("2.4.0".split(".")): miss_ratio = float(data['indexCounters']['missRatio']) @@ -662,19 +789,24 @@ def index_miss_ratio(con, warning, critical, perf_data): miss_ratio = float(data['indexCounters']['btree']['missRatio']) except KeyError: not_supported_msg = "not supported on this platform" - if data['indexCounters'].has_key('note'): - print "OK - MongoDB says: " + not_supported_msg + try: + data['indexCounters'] + if 'note' in data['indexCounters']: + print("OK - MongoDB says: " + not_supported_msg) + return 0 + else: + print("WARNING - Can't get counter from MongoDB") + return 1 + except Exception: + print("OK - MongoDB says: " + not_supported_msg) return 0 - else: - print "WARNING - Can't get counter from MongoDB" - return 1 message = "Miss Ratio: %.2f" % miss_ratio message += performance_data(perf_data, [("%.2f" % miss_ratio, "index_miss_ratio", warning, critical)]) return check_levels(miss_ratio, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) def check_replset_quorum(con, perf_data): @@ -698,7 +830,7 @@ def check_replset_quorum(con, perf_data): message = "Cluster is not quorate and cannot operate" return check_levels(state, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -713,44 +845,63 @@ def check_replset_state(con, perf_data, warning="", critical=""): except: critical = [8, 4, -1] - ok = range(-1, 8) # should include the range of all posiible values + ok = list(range(-1, 8)) # should include the range of all posiible values try: + worst_state = -2 + message = "" try: try: set_read_preference(con.admin) data = con.admin.command(pymongo.son_manipulator.SON([('replSetGetStatus', 1)])) except: data = con.admin.command(son.SON([('replSetGetStatus', 1)])) - state = int(data['myState']) - except pymongo.errors.OperationFailure, e: - if e.code == None and str(e).find('failed: not running with --replSet"'): - state = -1 + members = data['members'] + my_state = int(data['myState']) + worst_state = my_state + for member in members: + their_state = int(member['state']) + message += " %s: %i (%s)" % (member['name'], their_state, state_text(their_state)) + if state_is_worse(their_state, worst_state, warning, critical): + worst_state = their_state + message += performance_data(perf_data, [(my_state, "state")]) - if state == 8: - message = "State: %i (Down)" % state - elif state == 4: - message = "State: %i (Fatal error)" % state - elif state == 0: - message = "State: %i (Starting up, phase1)" % state - elif state == 3: - message = "State: %i (Recovering)" % state - elif state == 5: - message = "State: %i (Starting up, phase2)" % state - elif state == 1: - message = "State: %i (Primary)" % state - elif state == 2: - message = "State: %i (Secondary)" % state - elif state == 7: - message = "State: %i (Arbiter)" % state - elif state == -1: - message = "Not running with replSet" - else: - message = "State: %i (Unknown state)" % state - message += performance_data(perf_data, [(state, "state")]) - return check_levels(state, warning, critical, message, ok) - except Exception, e: + except pymongo.errors.OperationFailure as e: + if ((e.code == None and str(e).find('failed: not running with --replSet"')) or (e.code == 76 and str(e).find('not running with --replSet"'))): + worst_state = -1 + + return check_levels(worst_state, warning, critical, message, ok) + except Exception as e: return exit_with_general_critical(e) +def state_is_worse(state, worst_state, warning, critical): + if worst_state in critical: + return False + if worst_state in warning: + return state in critical + return (state in warning) or (state in critical) + +def state_text(state): + if state == 8: + return "Down" + elif state == 4: + return "Fatal error" + elif state == 0: + return "Starting up, phase1" + elif state == 3: + return "Recovering" + elif state == 5: + return "Starting up, phase2" + elif state == 1: + return "Primary" + elif state == 2: + return "Secondary" + elif state == 7: + return "Arbiter" + elif state == -1: + return "Not running with replSet" + else: + return "Unknown state" + def check_databases(con, warning, critical, perf_data=None): try: @@ -764,7 +915,7 @@ def check_databases(con, warning, critical, perf_data=None): message = "Number of DBs: %.0f" % count message += performance_data(perf_data, [(count, "databases", warning, critical, message)]) return check_levels(count, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -786,7 +937,7 @@ def check_collections(con, warning, critical, perf_data=None): message += performance_data(perf_data, [(count, "collections", warning, critical, message)]) return check_levels(count, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -823,21 +974,21 @@ def check_database_size(con, database, warning, critical, perf_data): try: set_read_preference(con.admin) data = con[database].command('dbstats') - storage_size = data['storageSize'] / 1024 / 1024 + storage_size = data['storageSize'] // 1024 // 1024 if perf_data: perfdata += " | database_size=%i;%i;%i" % (storage_size, warning, critical) #perfdata += " database=%s" %(database) if storage_size >= critical: - print "CRITICAL - Database size: %.0f MB, Database: %s%s" % (storage_size, database, perfdata) + print("CRITICAL - Database size: %.0f MB, Database: %s%s" % (storage_size, database, perfdata)) return 2 elif storage_size >= warning: - print "WARNING - Database size: %.0f MB, Database: %s%s" % (storage_size, database, perfdata) + print("WARNING - Database size: %.0f MB, Database: %s%s" % (storage_size, database, perfdata)) return 1 else: - print "OK - Database size: %.0f MB, Database: %s%s" % (storage_size, database, perfdata) + print("OK - Database size: %.0f MB, Database: %s%s" % (storage_size, database, perfdata)) return 0 - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -851,20 +1002,42 @@ def check_database_indexes(con, database, warning, critical, perf_data): try: set_read_preference(con.admin) data = con[database].command('dbstats') - index_size = data['indexSize'] / 1024 / 1024 + index_size = data['indexSize'] / 1024 // 1024 if perf_data: perfdata += " | database_indexes=%i;%i;%i" % (index_size, warning, critical) if index_size >= critical: - print "CRITICAL - %s indexSize: %.0f MB %s" % (database, index_size, perfdata) + print("CRITICAL - %s indexSize: %.0f MB %s" % (database, index_size, perfdata)) return 2 elif index_size >= warning: - print "WARNING - %s indexSize: %.0f MB %s" % (database, index_size, perfdata) + print("WARNING - %s indexSize: %.0f MB %s" % (database, index_size, perfdata)) return 1 else: - print "OK - %s indexSize: %.0f MB %s" % (database, index_size, perfdata) + print("OK - %s indexSize: %.0f MB %s" % (database, index_size, perfdata)) return 0 - except Exception, e: + except Exception as e: + return exit_with_general_critical(e) + + +def check_collection_documents(con, database, collection, warning, critical, perf_data): + perfdata = "" + try: + set_read_preference(con.admin) + data = con[database].command('collstats', collection) + documents = data['count'] + if perf_data: + perfdata += " | collection_documents=%i;%i;%i" % (documents, warning, critical) + + if documents >= critical: + print("CRITICAL - %s.%s documents: %s %s" % (database, collection, documents, perfdata)) + return 2 + elif documents >= warning: + print("WARNING - %s.%s documents: %s %s" % (database, collection, documents, perfdata)) + return 1 + else: + print("OK - %s.%s documents: %s %s" % (database, collection, documents, perfdata)) + return 0 + except Exception as e: return exit_with_general_critical(e) @@ -883,15 +1056,15 @@ def check_collection_indexes(con, database, collection, warning, critical, perf_ perfdata += " | collection_indexes=%i;%i;%i" % (total_index_size, warning, critical) if total_index_size >= critical: - print "CRITICAL - %s.%s totalIndexSize: %.0f MB %s" % (database, collection, total_index_size, perfdata) + print("CRITICAL - %s.%s totalIndexSize: %.0f MB %s" % (database, collection, total_index_size, perfdata)) return 2 elif total_index_size >= warning: - print "WARNING - %s.%s totalIndexSize: %.0f MB %s" % (database, collection, total_index_size, perfdata) + print("WARNING - %s.%s totalIndexSize: %.0f MB %s" % (database, collection, total_index_size, perfdata)) return 1 else: - print "OK - %s.%s totalIndexSize: %.0f MB %s" % (database, collection, total_index_size, perfdata) + print("OK - %s.%s totalIndexSize: %.0f MB %s" % (database, collection, total_index_size, perfdata)) return 0 - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -908,7 +1081,7 @@ def check_queues(con, warning, critical, perf_data): message += performance_data(perf_data, [(total_queues, "total_queues", warning, critical), (readers_queues, "readers_queues"), (writers_queues, "writers_queues")]) return check_levels(total_queues, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) def check_collection_size(con, database, collection, warning, critical, perf_data): @@ -923,18 +1096,43 @@ def check_collection_size(con, database, collection, warning, critical, perf_dat perfdata += " | collection_size=%i;%i;%i" % (size, warning, critical) if size >= critical: - print "CRITICAL - %s.%s size: %.0f MB %s" % (database, collection, size, perfdata) + print("CRITICAL - %s.%s size: %.0f MB %s" % (database, collection, size, perfdata)) return 2 elif size >= warning: - print "WARNING - %s.%s size: %.0f MB %s" % (database, collection, size, perfdata) + print("WARNING - %s.%s size: %.0f MB %s" % (database, collection, size, perfdata)) return 1 else: - print "OK - %s.%s size: %.0f MB %s" % (database, collection, size, perfdata) + print("OK - %s.%s size: %.0f MB %s" % (database, collection, size, perfdata)) return 0 - except Exception, e: + except Exception as e: return exit_with_general_critical(e) -def check_queries_per_second(con, query_type, warning, critical, perf_data): + +def check_collection_storageSize(con, database, collection, warning, critical, perf_data): + warning = warning or 100 + critical = critical or 1000 + perfdata = "" + try: + set_read_preference(con.admin) + data = con[database].command('collstats', collection) + storageSize = data['storageSize'] / 1024 / 1024 + if perf_data: + perfdata += " | collection_storageSize=%i;%i;%i" % (storageSize, warning, critical) + + if storageSize >= critical: + print("CRITICAL - %s.%s storageSize: %.0f MB %s" % (database, collection, storageSize, perfdata)) + return 2 + elif storageSize >= warning: + print("WARNING - %s.%s storageSize: %.0f MB %s" % (database, collection, storageSize, perfdata)) + return 1 + else: + print("OK - %s.%s storageSize: %.0f MB %s" % (database, collection, storageSize, perfdata)) + return 0 + except Exception as e: + return exit_with_general_critical(e) + + +def check_queries_per_second(con, query_type, warning, critical, perf_data, mongo_version): warning = warning or 250 critical = critical or 500 @@ -955,10 +1153,17 @@ def check_queries_per_second(con, query_type, warning, critical, perf_data): diff_query = num - last_count['data'][query_type]['count'] diff_ts = ts - last_count['data'][query_type]['ts'] + if diff_ts == 0: + message = "diff_query = " + str(diff_query) + " diff_ts = " + str(diff_ts) + return check_levels(0, warning, critical, message) + query_per_sec = float(diff_query) / float(diff_ts) # update the count now - db.nagios_check.update({u'_id': last_count['_id']}, {'$set': {"data.%s" % query_type: {'count': num, 'ts': int(time.time())}}}) + if mongo_version == 2: + db.nagios_check.update({u'_id': last_count['_id']}, {'$set': {"data.%s" % query_type: {'count': num, 'ts': int(time.time())}}}) + else: + db.nagios_check.update_one({u'_id': last_count['_id']}, {'$set': {"data.%s" % query_type: {'count': num, 'ts': int(time.time())}}}) message = "Queries / Sec: %f" % query_per_sec message += performance_data(perf_data, [(query_per_sec, "%s_per_sec" % query_type, warning, critical, message)]) @@ -967,17 +1172,24 @@ def check_queries_per_second(con, query_type, warning, critical, perf_data): # since it is the first run insert it query_per_sec = 0 message = "First run of check.. no data" - db.nagios_check.update({u'_id': last_count['_id']}, {'$set': {"data.%s" % query_type: {'count': num, 'ts': int(time.time())}}}) + if mongo_version == 2: + db.nagios_check.update({u'_id': last_count['_id']}, {'$set': {"data.%s" % query_type: {'count': num, 'ts': int(time.time())}}}) + else: + db.nagios_check.update_one({u'_id': last_count['_id']}, {'$set': {"data.%s" % query_type: {'count': num, 'ts': int(time.time())}}}) + except TypeError: # # since it is the first run insert it query_per_sec = 0 message = "First run of check.. no data" - db.nagios_check.insert({'check': 'query_counts', 'data': {query_type: {'count': num, 'ts': int(time.time())}}}) + if mongo_version == 2: + db.nagios_check.insert({'check': 'query_counts', 'data': {query_type: {'count': num, 'ts': int(time.time())}}}) + else: + db.nagios_check.insert_one({'check': 'query_counts', 'data': {query_type: {'count': num, 'ts': int(time.time())}}}) return check_levels(query_per_sec, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -1024,7 +1236,7 @@ def check_oplog(con, warning, critical, perf_data): message += performance_data(perf_data, [("%.2f" % hours_in_oplog, 'oplog_time', warning, critical), ("%.2f " % approx_level, 'oplog_time_100_percent_used')]) return check_levels(-approx_level, -warning, -critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -1042,7 +1254,7 @@ Under very high write situations it is normal for this value to be nonzero. """ message += performance_data(perf_data, [(j_commits_in_wl, "j_commits_in_wl", warning, critical)]) return check_levels(j_commits_in_wl, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -1058,7 +1270,7 @@ def check_journaled(con, warning, critical, perf_data): message += performance_data(perf_data, [("%.2f" % journaled, "journaled", warning, critical)]) return check_levels(journaled, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -1075,11 +1287,11 @@ than the amount physically written to disk.""" message += performance_data(perf_data, [("%.2f" % writes, "write_to_data_files", warning, critical)]) return check_levels(writes, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) -def get_opcounters(data, opcounters_name, host): +def get_opcounters(data, opcounters_name, host, port): try: insert = data[opcounters_name]['insert'] query = data[opcounters_name]['query'] @@ -1087,21 +1299,21 @@ def get_opcounters(data, opcounters_name, host): delete = data[opcounters_name]['delete'] getmore = data[opcounters_name]['getmore'] command = data[opcounters_name]['command'] - except KeyError, e: + except KeyError as e: return 0, [0] * 100 total_commands = insert + query + update + delete + getmore + command new_vals = [total_commands, insert, query, update, delete, getmore, command] - return maintain_delta(new_vals, host, opcounters_name) + return maintain_delta(new_vals, host, port, opcounters_name) -def check_opcounters(con, host, warning, critical, perf_data): +def check_opcounters(con, host, port, warning, critical, perf_data): """ A function to get all opcounters delta per minute. In case of a replication - gets the opcounters+opcountersRepl""" warning = warning or 10000 critical = critical or 15000 data = get_server_status(con) - err1, delta_opcounters = get_opcounters(data, 'opcounters', host) - err2, delta_opcounters_repl = get_opcounters(data, 'opcountersRepl', host) + err1, delta_opcounters = get_opcounters(data, 'opcounters', host, port) + err2, delta_opcounters_repl = get_opcounters(data, 'opcountersRepl', host, port) if err1 == 0 and err2 == 0: delta = [(x + y) for x, y in zip(delta_opcounters, delta_opcounters_repl)] delta[0] = delta_opcounters[0] # only the time delta shouldn't be summarized @@ -1109,14 +1321,14 @@ def check_opcounters(con, host, warning, critical, perf_data): message = "Test succeeded , old values missing" message = "Opcounters: total=%d,insert=%d,query=%d,update=%d,delete=%d,getmore=%d,command=%d" % tuple(per_minute_delta) message += performance_data(perf_data, ([(per_minute_delta[0], "total", warning, critical), (per_minute_delta[1], "insert"), - (per_minute_delta[2], "query"), (per_minute_delta[3], "update"), (per_minute_delta[5], "delete"), + (per_minute_delta[2], "query"), (per_minute_delta[3], "update"), (per_minute_delta[4], "delete"), (per_minute_delta[5], "getmore"), (per_minute_delta[6], "command")])) return check_levels(per_minute_delta[0], warning, critical, message) else: return exit_with_general_critical("problem reading data from temp file") -def check_current_lock(con, host, warning, critical, perf_data): +def check_current_lock(con, host, port, warning, critical, perf_data): """ A function to get current lock percentage and not a global one, as check_lock function does""" warning = warning or 10 critical = critical or 30 @@ -1125,7 +1337,7 @@ def check_current_lock(con, host, warning, critical, perf_data): lockTime = float(data['globalLock']['lockTime']) totalTime = float(data['globalLock']['totalTime']) - err, delta = maintain_delta([totalTime, lockTime], host, "locktime") + err, delta = maintain_delta([totalTime, lockTime], host, port, "locktime") if err == 0: lock_percentage = delta[2] / delta[1] * 100 # lockTime/totalTime*100 message = "Current Lock Percentage: %.2f%%" % lock_percentage @@ -1135,7 +1347,7 @@ def check_current_lock(con, host, warning, critical, perf_data): return exit_with_general_warning("problem reading data from temp file") -def check_page_faults(con, host, warning, critical, perf_data): +def check_page_faults(con, host, port, warning, critical, perf_data): """ A function to get page_faults per second from the system""" warning = warning or 10 critical = critical or 30 @@ -1147,7 +1359,7 @@ def check_page_faults(con, host, warning, critical, perf_data): # page_faults unsupported on the underlaying system return exit_with_general_critical("page_faults unsupported on the underlaying system") - err, delta = maintain_delta([page_faults], host, "page_faults") + err, delta = maintain_delta([page_faults], host, port, "page_faults") if err == 0: page_faults_ps = delta[1] / delta[0] message = "Page faults : %.2f ps" % page_faults_ps @@ -1157,7 +1369,7 @@ def check_page_faults(con, host, warning, critical, perf_data): return exit_with_general_warning("problem reading data from temp file") -def check_asserts(con, host, warning, critical, perf_data): +def check_asserts(con, host, port, warning, critical, perf_data): """ A function to get asserts from the system""" warning = warning or 1 critical = critical or 10 @@ -1172,7 +1384,7 @@ def check_asserts(con, host, warning, critical, perf_data): user = asserts['user'] rollovers = asserts['rollovers'] - err, delta = maintain_delta([regular, warning_asserts, msg, user, rollovers], host, "asserts") + err, delta = maintain_delta([regular, warning_asserts, msg, user, rollovers], host, port, "asserts") if err == 0: if delta[5] != 0: @@ -1206,7 +1418,7 @@ def get_stored_primary_server_name(db): return stored_primary_server -def check_replica_primary(con, host, warning, critical, perf_data, replicaset): +def check_replica_primary(con, host, warning, critical, perf_data, replicaset, mongo_version): """ A function to check if the primary server of a replica set has changed """ if warning is None and critical is None: warning = 1 @@ -1229,7 +1441,10 @@ def check_replica_primary(con, host, warning, critical, perf_data, replicaset): saved_primary = "None" if current_primary != saved_primary: last_primary_server_record = {"server": current_primary} - db.last_primary_server.update({"_id": "last_primary"}, {"$set": last_primary_server_record}, upsert=True, safe=True) + if mongo_version == 2: + db.last_primary_server.update({"_id": "last_primary"}, {"$set": last_primary_server_record}, upsert=True) + else: + db.last_primary_server.update_one({"_id": "last_primary"}, {"$set": last_primary_server_record}, upsert=True) message = "Primary server has changed from %s to %s" % (saved_primary, current_primary) primary_status = 1 return check_levels(primary_status, warning, critical, message) @@ -1251,9 +1466,9 @@ def check_page_faults(con, sample_time, warning, critical, perf_data): try: #on linux servers only - page_faults = (int(data2['extra_info']['page_faults']) - int(data1['extra_info']['page_faults'])) / sample_time + page_faults = (int(data2['extra_info']['page_faults']) - int(data1['extra_info']['page_faults'])) // sample_time except KeyError: - print "WARNING - Can't get extra_info.page_faults counter from MongoDB" + print("WARNING - Can't get extra_info.page_faults counter from MongoDB") sys.exit(1) message = "Page Faults: %i" % (page_faults) @@ -1261,7 +1476,7 @@ def check_page_faults(con, sample_time, warning, critical, perf_data): message += performance_data(perf_data, [(page_faults, "page_faults", warning, critical)]) check_levels(page_faults, warning, critical, message) - except Exception, e: + except Exception as e: exit_with_general_critical(e) @@ -1277,35 +1492,35 @@ def chunks_balance(con, database, collection, warning, critical): shards = col.distinct("shard") except: - print "WARNING - Can't get chunks infos from MongoDB" + print("WARNING - Can't get chunks infos from MongoDB") sys.exit(1) if nscount == 0: - print "WARNING - Namespace %s is not sharded" % (nsfilter) + print("WARNING - Namespace %s is not sharded" % (nsfilter)) sys.exit(1) - avgchunksnb = nscount / len(shards) - warningnb = avgchunksnb * warning / 100 - criticalnb = avgchunksnb * critical / 100 + avgchunksnb = nscount // len(shards) + warningnb = avgchunksnb * warning // 100 + criticalnb = avgchunksnb * critical // 100 for shard in shards: delta = abs(avgchunksnb - col.find({"ns": nsfilter, "shard": shard}).count()) message = "Namespace: %s, Shard name: %s, Chunk delta: %i" % (nsfilter, shard, delta) if delta >= criticalnb and delta > 0: - print "CRITICAL - Chunks not well balanced " + message + print("CRITICAL - Chunks not well balanced " + message) sys.exit(2) elif delta >= warningnb and delta > 0: - print "WARNING - Chunks not well balanced " + message + print("WARNING - Chunks not well balanced " + message) sys.exit(1) - print "OK - Chunks well balanced across shards" + print("OK - Chunks well balanced across shards") sys.exit(0) - except Exception, e: + except Exception as e: exit_with_general_critical(e) - print "OK - Chunks well balanced across shards" + print("OK - Chunks well balanced across shards") sys.exit(0) @@ -1321,7 +1536,7 @@ def check_connect_primary(con, warning, critical, perf_data): data = con.admin.command(son.SON([('isMaster', 1)])) if data['ismaster'] == True: - print "OK - This server is primary" + print("OK - This server is primary") return 0 phost = data['primary'].split(':')[0] @@ -1339,17 +1554,17 @@ def check_connect_primary(con, warning, critical, perf_data): return check_levels(pconn_time, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) def check_collection_state(con, database, collection): try: con[database][collection].find_one() - print "OK - Collection %s.%s is reachable " % (database, collection) + print("OK - Collection %s.%s is reachable " % (database, collection)) return 0 - except Exception, e: + except Exception as e: return exit_with_general_critical(e) @@ -1361,14 +1576,18 @@ def check_row_count(con, database, collection, warning, critical, perf_data): return check_levels(count, warning, critical, message) - except Exception, e: + except Exception as e: return exit_with_general_critical(e) -def build_file_name(host, action): +def build_file_name(host, port, action): #done this way so it will work when run independently and from shell module_name = re.match('(.*//*)*(.*)\..*', __file__).group(2) - return "/tmp/" + module_name + "_data/" + host + "-" + action + ".data" + + if (port == 27017): + return "/tmp/" + module_name + "_data/" + host + "-" + action + ".data" + else: + return "/tmp/" + module_name + "_data/" + host + "-" + str(port) + "-" + action + ".data" def ensure_dir(f): @@ -1381,7 +1600,7 @@ def write_values(file_name, string): f = None try: f = open(file_name, 'w') - except IOError, e: + except IOError as e: #try creating if (e.errno == 2): ensure_dir(file_name) @@ -1400,11 +1619,11 @@ def read_values(file_name): data = f.read() f.close() return 0, data - except IOError, e: + except IOError as e: if (e.errno == 2): #no previous data return 1, '' - except Exception, e: + except Exception as e: return 2, None @@ -1420,8 +1639,8 @@ def calc_delta(old, new): return 0, delta -def maintain_delta(new_vals, host, action): - file_name = build_file_name(host, action) +def maintain_delta(new_vals, host, port, action): + file_name = build_file_name(host, port, action) err, data = read_values(file_name) old_vals = data.split(';') new_vals = [str(int(time.time()))] + new_vals @@ -1442,8 +1661,8 @@ def replication_get_time_diff(con): col = 'oplog.$main' firstc = local[col].find().sort("$natural", 1).limit(1) lastc = local[col].find().sort("$natural", -1).limit(1) - first = firstc.next() - last = lastc.next() + first = next(firstc) + last = next(lastc) tfirst = first["ts"] tlast = last["ts"] delta = tlast.time - tfirst.time diff --git a/nagios-nrpe/files/plugins/check_phpfpm_multi b/nagios-nrpe/files/plugins/check_phpfpm_multi index 865c31d3..b02fc7e2 100644 --- a/nagios-nrpe/files/plugins/check_phpfpm_multi +++ b/nagios-nrpe/files/plugins/check_phpfpm_multi @@ -56,20 +56,20 @@ for pool_file in $POOL_FILES; do if [ "${ret}" -ge 2 ]; then nb_crit=$((nb_crit + 1)) - output="${output}${result}\n" [ "${return}" -le 2 ] && return=2 elif [ "${ret}" -ge 1 ]; then nb_warn=$((nb_warn + 1)) - output="${output}${result}\n" [ "${return}" -le 1 ] && return=1 else nb_ok=$((nb_ok + 1)) - output="${output}$(echo "$result" | cut -d '|' -f1)\n" [ "${return}" -le 0 ] && return=0 fi + result_status=$(echo ${result} | awk -F' - ' '{ print $1}') + result_content=$(echo ${result} | awk -F' - ' '{ print $2}') + output="${output}${result_status} - ${pool_file} - ${result_content}\n" -done; +done [ "${return}" -ge 0 ] && header="OK" diff --git a/nagios-nrpe/files/plugins/check_readwrite b/nagios-nrpe/files/plugins/check_readwrite new file mode 100755 index 00000000..578d9740 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_readwrite @@ -0,0 +1,20 @@ +#!/bin/sh +# +# Verify mounted filesystems are readable and writable. + +filesystems=$* + +exit_code=0 +for filesystem in $filesystems; do + if findmnt --options ro --noheadings "${filesystem}"; then + exit_code=2 + fi +done + +if [ $exit_code != 0 ]; then + echo "CRITICAL - Above filesystems aren't monted in read and write mode" +else + echo "OK - All fine" +fi + +exit "${exit_code}" diff --git a/nagios-nrpe/files/plugins/check_redis_sentinel_sync b/nagios-nrpe/files/plugins/check_redis_sentinel_sync new file mode 100755 index 00000000..e8f217aa --- /dev/null +++ b/nagios-nrpe/files/plugins/check_redis_sentinel_sync @@ -0,0 +1,46 @@ +#!/bin/sh +# +# Verify the synchroniation of Redis Sentinel slaves. + +output=$(mktemp --tmpdir $(basename "$0").XXXXXXXXXX) +critical_count=0 +ok_count=0 + +trap "rm -f $output" EXIT + +input=$(redis-cli -p 6380 sentinel slaves redis | sed 'N;s/\n/=/') + +#while read -r line; do +for line in $input; do + case "$line" in + name=*) name=${line#name=} ;; + master-link-status=*) status=${line#master-link-status=} ;; + esac + if [ -n "$name" ] && [ -n "$status" ]; then + if [ "$status" = ok ]; then + echo "OK - $name" >> "$output" + ok_count=$(( ok_count + 1)) + else + echo "CRITICAL - $name" >> "$output" + critical_count=$(( critical_count + 1)) + fi + unset name status + 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 Redis Sentinel slave%s are in sync\n\n" \ + "$ok_count" "$total_count" "$plural" + cat "$output" + exit 0 +else + printf "CRITICAL - %d/%d Redis Sentinal slave%s aren't in sync\n\n" \ + "$critical_count" "$total_count" "$plural" + cat "$output" + exit 2 +fi diff --git a/nagios-nrpe/files/plugins/check_ssl_local b/nagios-nrpe/files/plugins/check_ssl_local new file mode 100755 index 00000000..860ed676 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_ssl_local @@ -0,0 +1,69 @@ +#!/bin/bash + +# Check permettant de monitorer une liste de certificats se trouvant dans +# /etc/nagios/ssl_local.cfg +# +# Développé par Will (2022) +# + +certs_list_path="/etc/nagios/check_ssl_local_list.cfg" + +# Dates in seconds +_10_days="864000" +_15_days="1296000" + +critical=0 +warning=0 + + +if [[ ! -f "$certs_list_path" ]]; then + touch "$certs_list_path" +fi + +certs_list=$(cat "$certs_list_path" | sed -E 's/(.*)#.*/\1/g' | grep -v -E '^$') + +for cert_path in $certs_list; do + + if [ ! -f "$cert_path" ] && [ ! -d "$cert_path" ]; then + >&2 echo "Warning: path '$cert_path' is not a file or a directory." + warning=1 + continue + fi + + enddate=$(openssl x509 -noout -enddate -in "$cert_path" | cut -d'=' -f2) + + # Check cert expiré (critique) + if ! openssl x509 -checkend 0 -in "$cert_path" &> /dev/null; then + critical=1 + >&2 echo "Critical: Cert '$cert_path' has expired on $enddate." + continue + fi + + # Check cert expire < 10 jours (critique) + if ! openssl x509 -checkend "$_10_days" -in "$cert_path" &> /dev/null; then + critical=1 + >&2 echo "Critical: Cert '$cert_path' will expire on $enddate." + continue + fi + + # Check cert expire < 15 jours (warning) + if ! openssl x509 -checkend "$_15_days" -in "$cert_path" &> /dev/null; then + warning=1 + >&2 echo "Warning: Cert '$cert_path' will expire on $enddate." + continue + fi + + # Cert expire > 15 jours (OK) + echo "Cert '$cert_path' OK." + +done + +if [ $critical -eq 1 ]; then + exit 2 +elif [ $warning -eq 1 ]; then + exit 1 +else + exit 0 +fi + + diff --git a/nagios-nrpe/files/plugins/check_vrrpd b/nagios-nrpe/files/plugins/check_vrrpd index 9390aa6e..a82c379b 100755 --- a/nagios-nrpe/files/plugins/check_vrrpd +++ b/nagios-nrpe/files/plugins/check_vrrpd @@ -1,94 +1,190 @@ #!/bin/bash +# shellcheck disable=SC2207,SC2009,SC2076 -# README -# -# Variable to adjust : is_master and vrrpd_processes. -# vrrpd_processes is the number of vrrpd processes that should run on the server. -# is_master defines whether the vrrpd group should be master (1) or backup (0). -# -# If some instances have to be master and some other have to be backup, -# then the value of is_master is 2 and the states has to be precised in arguments. -# e.g. : ./check_vrrpd master backup master -# The order is defined by the output order of `ps auwx | grep vrrp` +usage() { +cat << EOL + Usage : -RC=0 -IFS=' -' + $0 --master X,Y --backup Z -is_master=2 # 1 if master ; 0 if backup ; 2 if mixed master and backup, in this case, it has to be precised in arguments -vrrpd_processes=3 # number of vrrpd processes that should be running -is_vrrpd_running=$(sudo /usr/lib/nagios//plugins/check_procs -C vrrpd -c $vrrpd_processes:$vrrpd_processes) -rc_is_vrrpd_running=$? -IP_vrrpd=($(for i in $(ps auwx | grep vrrpd | grep -v grep | grep -v check); do echo $i | awk '{print $--NF}'; done)) -INT_vrrpd=($(for i in $(ps auwx | grep vrrpd | grep -v grep | grep -v check); do echo $i | awk '{print $13}'; done)) -ID_vrrpd=($(for i in $(ps auwx | grep vrrpd | grep -v grep | grep -v check); do echo $i | awk '{print $19}'; done)) + -m|--master ID_MASTER # VRRP ID that should be master, separated by a comma "," + -b|--backup ID_BACKUP # VRRP ID that should be backup, separated by a comma "," + [--vrrpd] # Check for vrrpd daemon (default) + [--uvrrpd] # Check for uvrrpd daemon +EOL +} -if [[ $rc_is_vrrpd_running -ne 0 ]]; then - echo $is_vrrpd_running instead of $vrrpd_processes +unset ID_master +unset ID_backup +vrrpd_option="unset" +uvrrpd_option="unset" +unset critical_output +critical_state="unset" +unset warning_output +warning_state="unset" +unset ok_output +ok_state="unset" +exit_code=0 +used_daemon="vrrpd" +IFS=" +" + +# If no argument then show usage +if [ "$#" -eq 0 ]; then + usage exit 2 fi -for i in $(seq 0 $((${#ID_vrrpd[*]}-1))); do - ifconfig vrrp_${ID_vrrpd[$i]}_${INT_vrrpd[$i]} >/dev/null 2>&1 - # If has interface - if [[ $? -eq 0 ]]; then - # If has to be master : OK - if [[ $is_master -eq 1 ]]; then - echo OK - ${IP_vrrpd[$i]} exists and is master - # If has to be backup : KO - elif [[ $is_master -eq 0 ]]; then - echo CRITICAL - ${IP_vrrpd[$i]} exists whereas it should be backup - RC=2 - # We retrieve the state it should be from args - elif [[ $is_master -eq 2 ]]; then - arg=$(($i+1)) - state=${!arg} - # If has to be master : OK - if [[ $state = master ]]; then - echo OK - ${IP_vrrpd[$i]} exists and is master - # If has to be backup : KO - elif [[ $state = backup ]]; then - echo CRITICAL - ${IP_vrrpd[$i]} exists whereas it should be backup - RC=2 +while :; do + case $1 in + -h|-\?|--help) # Call a "usage" function to display a synopsis, then exit. + usage + exit + ;; + -m|--master) # Takes an option argument, ensuring it has been specified. + if [ -n "$2" ]; then + ID_master=($(echo "$2" | tr "," "\n")) # Make an array with values separated by "," + shift else - echo "CRITICAL - The arguments have to be master or backup. Exiting" + printf 'ERROR: "--master" requires a non-empty option argument.\n' >&2 exit 2 fi - # Unknown - else - RC=3 - fi - # If hasn't interface - elif [[ $? -ne 0 ]]; then - # If has to be master : KO - if [[ $is_master -eq 1 ]]; then - echo CRITICAL - ${IP_vrrpd[$i]} does not exist whereas it should be master - RC=2 - # If has to be backup : OK - elif [[ $is_master -eq 0 ]]; then - echo OK - ${IP_vrrpd[$i]} is backup - # We retrieve the state it should be from args - elif [[ $is_master -eq 2 ]]; then - arg=$(($i+1)) - state=${!arg} - # If has to be master : KO - if [[ $state = master ]]; then - echo CRITICAL - ${IP_vrrpd[$i]} does not exist whereas it should be master - RC=2 - # If has to be backup : OK - elif [[ $state = backup ]]; then - echo OK - ${IP_vrrpd[$i]} is backup + ;; + -b|--backup) # Takes an option argument, ensuring it has been specified. + if [ -n "$2" ]; then + ID_backup=($(echo "$2" | tr "," "\n")) # Make an array with values separated by "," + shift else - echo "CRITICAL - The arguments have to be master or backup. Exiting" + printf 'ERROR: "--backup" requires a non-empty option argument.\n' >&2 exit 2 fi - # Unknown - else - RC=3 - fi - # Unknown - else - RC=3 - fi + ;; + --vrrpd) + used_daemon="vrrpd" + vrrpd_option="set" + ;; + --uvrrpd) + used_daemon="uvrrpd" + uvrrpd_option="set" + ;; + -?*) + printf 'WARNING: Unknown option (ignored): %s\n' "$1" >&2 + ;; + *) # Default case: If no more options then break out of the loop. + break + esac + shift done -exit $RC + +# Make sure that each given ID is given once only +all_ID=("${ID_master[@]}" "${ID_backup[@]}") +uniqueNum=$(printf '%s\n' "${all_ID[@]}"|awk '!($0 in seen){seen[$0];c++} END {print c}') +if [ "$uniqueNum" != ${#all_ID[@]} ]; then + echo "ERROR : At least one VRRP ID is given multiple times" + exit 2 +fi + +# Make sure --vrrpd and --uvrrpd are not both set +if [ $vrrpd_option = "set" ] && [ $uvrrpd_option = "set" ]; then + echo "ERROR : You cannot set both parameters --vrrpd and --uvrrpd" + exit 2 +fi + +# Make sure no sysclt parameter "rp_filter" is set to 1 +if grep -q 1 /proc/sys/net/ipv4/conf/*/rp_filter; then + critical_output="${critical_output}CRITICAL - rp_filter is set to 1 at least for one interface\n" + critical_state="set" +fi + +vrrpd_processes_number=$((${#ID_master[@]}+${#ID_backup[@]})) # Number of vrrpd processes that should be running = length of arrays ID_master + ID_backup +regex_ipv4="((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])" +regex_ipv6="(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))" +vrrpd_processes=$(ps auwx | grep "$used_daemon" | grep -v -e grep -e check) +ID_running_vrrpd=($(for i in ${vrrpd_processes}; do echo "$i" | grep -Eo -- "-v [0-9]+" | awk '{print $2}'; done)) + +# Check the number of running vrrpd processes in comparison to the number of ID given +if ! sudo /usr/lib/nagios/plugins/check_procs -C "$used_daemon" -c $vrrpd_processes_number:$vrrpd_processes_number >/dev/null; then + critical_output="${critical_output}CRITICAL : $vrrpd_processes_number VRRP ID are given but $(ps auwx | grep "$used_daemon" | grep -v -e grep -e check -c) $used_daemon processes are running\n" + if pgrep uvrrp >/dev/null && [ $uvrrpd_option = "unset" ]; then + critical_output="${critical_output}It seems that uvrrpd is running. Use parameter --uvrrpd\n" + fi + critical_state="set" +fi + +IFS=" " + +# For each ID_master, make sure a process exist +if [ ${#ID_master[@]} -ne 0 ]; then + for i in "${ID_master[@]}"; do + # If array contains the current ID, then a process exist, and we have to make sure the corresponding interface exists + if [[ " ${ID_running_vrrpd[*]} " =~ " $i " ]]; then + vrrpd_current_proccess=$(echo "$vrrpd_processes" | grep -E -- "-v $i ") + INT_current_vrrpd=$(echo "$vrrpd_current_proccess" | grep -Eo -- "-i \S+" | awk '{print $2}') + IP_current_vrrpd=$(echo "$vrrpd_current_proccess" | grep -Eo "${regex_ipv4}|${regex_ipv6}") + if [ "$used_daemon" = "vrrpd" ]; then + int_name="vrrp_${i}_${INT_current_vrrpd}" + elif [ "$used_daemon" = "uvrrpd" ]; then + int_name="${INT_current_vrrpd}_${i}" + fi + if /sbin/ifconfig "$int_name" 2> /dev/null | grep -q "$IP_current_vrrpd"; then + ok_output="${ok_output}OK - ID $i has a process and $IP_current_vrrpd is master\n" + ok_state="set" + else + warning_output="${warning_output}WARNING - The IP $IP_current_vrrpd for ID $i is backup while it should be master\n" + warning_state="set" + fi + else + critical_output="${critical_output}CRITICAL - No process is running for VRRP ID $i\n" + critical_state="set" + fi + done +fi + +# For each ID_backup, make sure a process exist +if [ ${#ID_backup[@]} -ne 0 ]; then + for i in "${ID_backup[@]}"; do + # If array contains the current ID, then a process exist, and we have to make sure the corresponding interface does not exist + if [[ " ${ID_running_vrrpd[*]} " =~ " $i " ]]; then + vrrpd_current_proccess=$(echo "$vrrpd_processes" | grep -E -- "-v $i ") + INT_current_vrrpd=$(echo "$vrrpd_current_proccess" | grep -Eo -- "-i \S+" | awk '{print $2}') + IP_current_vrrpd=$(echo "$vrrpd_current_proccess" | grep -Eo "${regex_ipv4}|${regex_ipv6}") + if [ "$used_daemon" = "vrrpd" ]; then + int_name="vrrp_${i}_${INT_current_vrrpd}" + elif [ "$used_daemon" = "uvrrpd" ]; then + int_name="${INT_current_vrrpd}_${i}" + fi + if ! /sbin/ifconfig "$int_name" 2> /dev/null | grep -q "$IP_current_vrrpd"; then + ok_output="${ok_output}OK - ID $i has a process and $IP_current_vrrpd is backup\n" + ok_state="set" + else + warning_output="${warning_output}WARNING - The IP $IP_current_vrrpd for ID $i is master while it should be backup\n" + warning_state="set" + fi + else + critical_output="${critical_output}CRITICAL - No process is running for VRRP ID $i\n" + critical_state="set" + fi + done +fi + +# Make $exit_code the highest set +if [ "$critical_state" = "set" ]; then + exit_code=2 +elif [ "$warning_state" = "set" ]; then + exit_code=1 +elif [ "$ok_state" = "set" ]; then + exit_code=0 +fi + +# Echo most critical output first, least last +if [ -n "$critical_output" ]; then + echo -e "$critical_output" | grep -v "^$" +fi +if [ -n "$warning_output" ]; then + echo -e "$warning_output" | grep -v "^$" +fi +if [ -n "$ok_output" ]; then + echo -e "$ok_output" | grep -v "^$" +fi + +exit $exit_code diff --git a/nagios-nrpe/handlers/main.yml b/nagios-nrpe/handlers/main.yml index 25ab29ad..b4b24b09 100644 --- a/nagios-nrpe/handlers/main.yml +++ b/nagios-nrpe/handlers/main.yml @@ -1,11 +1,11 @@ --- - name: restart nagios-nrpe-server - service: + ansible.builtin.service: name: nagios-nrpe-server state: restarted - name: restart nrpe - service: + ansible.builtin.service: name: nrpe state: restarted diff --git a/nagios-nrpe/tasks/check-local.yml b/nagios-nrpe/tasks/check-local.yml new file mode 100644 index 00000000..1b696292 --- /dev/null +++ b/nagios-nrpe/tasks/check-local.yml @@ -0,0 +1,24 @@ +--- +# Install check-local utilitary + +- name: Package nagios-nrpe-plugin is intalled + ansible.builtin.apt: + name: nagios-nrpe-plugin + +- name: "Remount /usr if needed" + ansible.builtin.include_role: + name: remount-usr + +- name: Utilitary check-local is installed + ansible.builtin.copy: + src: check-local + dest: /usr/local/bin/check-local + mode: "0755" + +- name: Completion for utilitary check-local is installed + ansible.builtin.copy: + src: check-local_completion + dest: /etc/bash_completion.d/check-local + mode: "0755" + + diff --git a/nagios-nrpe/tasks/main.yml b/nagios-nrpe/tasks/main.yml index 77770020..aa36f9c9 100644 --- a/nagios-nrpe/tasks/main.yml +++ b/nagios-nrpe/tasks/main.yml @@ -1,6 +1,6 @@ --- - name: base nrpe & plugins packages are installed - apt: + ansible.builtin.apt: name: - nagios-nrpe-server - monitoring-plugins @@ -14,7 +14,7 @@ - name: custom plugin dependencies packages are installed - apt: + ansible.builtin.apt: name: - libfcgi-client-perl state: present @@ -25,18 +25,18 @@ - nagios-plugins - name: custom configuration is present - template: + ansible.builtin.template: src: evolix.cfg.j2 dest: /etc/nagios/nrpe.d/evolix.cfg group: nagios mode: "0640" - force: no + force: false notify: restart nagios-nrpe-server tags: - nagios-nrpe - name: update allowed_hosts lists - lineinfile: + ansible.builtin.lineinfile: dest: /etc/nagios/nrpe.d/evolix.cfg line: "allowed_hosts={{ nagios_nrpe_allowed_hosts | join(',') }}" regexp: '^allowed_hosts=' @@ -47,7 +47,7 @@ - nagios-nrpe - name: Nagios config is secured - file: + ansible.builtin.file: dest: /etc/nagios/ mode: "0750" group: nagios @@ -56,15 +56,15 @@ tags: - nagios-nrpe -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - when: nagios_plugins_directory is search ("/usr") + when: nagios_plugins_directory is search("/usr") tags: - nagios-nrpe - nagios-plugins - name: Nagios plugins are installed - copy: + ansible.builtin.copy: src: plugins/ dest: "{{ nagios_plugins_directory }}/" mode: "0755" @@ -74,7 +74,7 @@ - nagios-plugins - name: Nagios lib is secured - file: + ansible.builtin.file: dest: /usr/local/lib/nagios/ mode: "0755" group: nagios @@ -83,3 +83,7 @@ notify: restart nagios-nrpe-server tags: - nagios-nrpe + +- ansible.builtin.include_tasks: wrapper.yml + +- ansible.builtin.include_tasks: check-local.yml diff --git a/nagios-nrpe/tasks/wrapper.yml b/nagios-nrpe/tasks/wrapper.yml new file mode 100644 index 00000000..4eb98350 --- /dev/null +++ b/nagios-nrpe/tasks/wrapper.yml @@ -0,0 +1,43 @@ +--- + + +- name: "Remount /usr if needed" + ansible.builtin.include_role: + name: remount-usr + +- name: check if old script is present + ansible.builtin.stat: + path: /usr/share/scripts/alerts_switch + register: old_alerts_switch + +- name: alerts_switch is at the right place + ansible.builtin.command: + cmd: "mv /usr/share/scripts/alerts_switch /usr/local/bin/alerts_switch" + args: + creates: /usr/local/bin/alerts_switch + when: old_alerts_switch.stat.exists + +- name: "copy alerts_switch" + ansible.builtin.copy: + src: alerts_switch + dest: /usr/local/bin/alerts_switch + owner: root + group: root + mode: "0750" + force: true + +- name: "symlink for backward compatibility" + ansible.builtin.file: + src: /usr/local/bin/alerts_switch + dest: /usr/share/scripts/alerts_switch + state: link + when: old_alerts_switch.stat.exists + +- name: "copy alerts_wrapper" + ansible.builtin.copy: + src: alerts_wrapper + dest: "{{ nagios_plugins_directory }}/alerts_wrapper" + owner: root + group: staff + mode: "0755" + force: true \ No newline at end of file diff --git a/nagios-nrpe/templates/evolix.cfg.j2 b/nagios-nrpe/templates/evolix.cfg.j2 index 80eaaaf2..c0f97ea7 100644 --- a/nagios-nrpe/templates/evolix.cfg.j2 +++ b/nagios-nrpe/templates/evolix.cfg.j2 @@ -9,7 +9,7 @@ allowed_hosts={{ nagios_nrpe_allowed_hosts | join(',') }} # System checks command[check_load]=/usr/lib/nagios/plugins/check_load --percpu --warning=0.7,0.6,0.5 --critical=0.9,0.8,0.7 command[check_swap]=/usr/lib/nagios/plugins/check_swap -a -w 30% -c 20% -command[check_disk1]=/usr/lib/nagios/plugins/check_disk -x /lib/init/rw -x /dev -x /dev/shm -x /sys/kernel/debug/tracing -w 10% -c 3% -W 10% -K 3% -C -w 5% -c 2% -W 5% -K 2% -p /home +command[check_disk1]=/usr/lib/nagios/plugins/check_disk -e -w 10% -c 3% -W 10% -K 3% -C -w 5% -c 2% -W 5% -K 2% -p /home -x /lib/init/rw -x /dev -x /dev/shm -x /run -I '^/run/' -I '^/sys/' command[check_zombie_procs]=sudo /usr/lib/nagios/plugins/check_procs -w 5 -c 10 -s Z command[check_total_procs]=sudo /usr/lib/nagios/plugins/check_procs -w 400 -c 600 command[check_users]=/usr/lib/nagios/plugins/check_users -w 5 -c 10 @@ -34,7 +34,7 @@ command[check_pop]=/usr/lib/nagios/plugins/check_pop -H localhost command[check_pops]=/usr/lib/nagios/plugins/check_pop -S -H localhost -p 995 command[check_ftp]=/usr/lib/nagios/plugins/check_ftp -H localhost command[check_http]=/usr/lib/nagios/plugins/check_http -e 301 -I 127.0.0.1 -H localhost -command[check_https]=/usr/lib/nagios/plugins/check_http -e 403 -I 127.0.0.1 -S -p 443 --sni -H ssl.evolix.net +command[check_https]=/usr/lib/nagios/plugins/check_http -e 401,403 -I 127.0.0.1 -S -p 443 --sni -H ssl.evolix.net command[check_bind]=/usr/lib/nagios/plugins/check_dig -l evolix.net -H localhost command[check_unbound]=/usr/lib/nagios/plugins/check_dig -l evolix.net -H localhost command[check_smb]=/usr/lib/nagios/plugins/check_tcp -H 127.0.0.1 -p 445 @@ -48,15 +48,17 @@ command[check_redis]=/usr/lib/nagios/plugins/check_tcp -p 6379 command[check_clamd]=/usr/lib/nagios/plugins/check_clamd -H /var/run/clamav/clamd.ctl -v command[check_clamav_db]=/usr/lib/nagios/plugins/check_file_age -w 86400 -c 172800 -f /var/lib/clamav/evolix.ndb command[check_ssl]=/usr/lib/nagios/plugins/check_http -f follow -I 127.0.0.1 -S -p 443 -H ssl.evolix.net -C 15,5 +command[check_ssl_local]={{ nagios_plugins_directory }}/check_ssl_local command[check_elasticsearch]=/usr/lib/nagios/plugins/check_http -I 127.0.0.1 -u /_cat/health?h=st -p 9200 -r 'red' --invert-regex command[check_memcached]=/usr/lib/nagios/plugins/check_tcp -H 127.0.0.1 -p 11211 -command[check_opendkim]=/usr/lib/nagios/plugins/check_tcp -H 127.0.0.1 -p 54321 +command[check_opendkim]=/usr/lib/nagios/plugins/check_tcp -H 127.0.0.1 -p 8891 command[check_bkctld_setup]=sudo /usr/sbin/bkctld check-setup command[check_bkctld_jails]=sudo /usr/sbin/bkctld check-jails # "check_bkctld" is here as backward compatibility, but is replaced by "check_bkctld_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 @@ -70,7 +72,7 @@ command[check_mongodb_connect]={{ nagios_plugins_directory }}/check_mongodb -H l command[check_glusterfs]={{ nagios_plugins_directory }}/check_glusterfs -v all -n 0 command[check_supervisord_status]={{ nagios_plugins_directory }}/check_supervisord command[check_varnish]={{ nagios_plugins_directory }}/check_varnish_health -i 127.0.0.1 -p 6082 -s /etc/varnish/secret -w 2 -c 4 -command[check_haproxy]=sudo {{ nagios_plugins_directory }}/check_haproxy_stats -s /run/haproxy/admin.sock -w 80 -c 90 --ignore-maint --ignore-nolb +command[check_haproxy]=sudo {{ nagios_plugins_directory }}/check_haproxy_stats -s /run/haproxy/admin.sock -w 80 -c 90 --ignore-maint --ignore-nolb --ignore-drain command[check_minifirewall]=sudo {{ nagios_plugins_directory }}/check_minifirewall command[check_redis_instances]={{ nagios_plugins_directory }}/check_redis_instances command[check_hpraid]={{ nagios_plugins_directory }}/check_hpraid @@ -81,8 +83,10 @@ command[check_php-fpm73]=sudo {{ nagios_plugins_directory }}/check_phpfpm_multi command[check_php-fpm74]=sudo {{ nagios_plugins_directory }}/check_phpfpm_multi /var/lib/lxc/php74/rootfs/etc/php/7.4/fpm/pool.d/ command[check_php-fpm80]=sudo {{ nagios_plugins_directory }}/check_phpfpm_multi /var/lib/lxc/php80/rootfs/etc/php/8.0/fpm/pool.d/ 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_php-fpm82]=sudo {{ nagios_plugins_directory }}/check_phpfpm_multi /var/lib/lxc/php82/rootfs/etc/php/8.2/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/nameserver/tasks/main.yml b/nameserver/tasks/main.yml index 420e65af..16b06bbd 100644 --- a/nameserver/tasks/main.yml +++ b/nameserver/tasks/main.yml @@ -1,14 +1,15 @@ --- - name: Get actual nameserver - shell: grep nameserver /etc/resolv.conf | awk '{ print $2 }' + ansible.builtin.shell: + cmd: grep nameserver /etc/resolv.conf | awk '{ print $2 }' register: grep_nameserver check_mode: no - changed_when: false + changed_when: False tags: - nameserver - name: Set nameserver - lineinfile: + ansible.builtin.lineinfile: dest: /etc/resolv.conf line: "nameserver {{ item }}" state: present @@ -17,7 +18,7 @@ - nameserver - name: Unset others nameserver - lineinfile: + ansible.builtin.lineinfile: dest: /etc/resolv.conf line: "nameserver {{ item }}" state: absent diff --git a/networkd-to-ifconfig/tasks/main.yml b/networkd-to-ifconfig/tasks/main.yml index d1ac0ac4..ff192645 100644 --- a/networkd-to-ifconfig/tasks/main.yml +++ b/networkd-to-ifconfig/tasks/main.yml @@ -1,11 +1,11 @@ --- - name: Check state of /etc/network/interfaces - stat: + ansible.builtin.stat: path: /etc/network/interfaces register: interfaces_file -- debug: +- ansible.builtin.debug: msg: A /etc/network/interfaces file already exists, nothing is done. when: - interfaces_file.stat.exists @@ -13,29 +13,29 @@ - block: - name: "Look for systemd network config" - stat: + ansible.builtin.stat: path: /etc/systemd/network/50-default.network register: systemd_network_file - name: Set interface name - set_fact: + ansible.builtin.set_fact: eni_interface_name: "{{ ansible_default_ipv4.interface }}" - - include: set_facts_from_systemd.yml + - ansible.builtin.include: set_facts_from_systemd.yml when: systemd_network_file.stat.exists - - include: set_facts_from_ansible.yml + - ansible.builtin.include: set_facts_from_ansible.yml when: not systemd_network_file.stat.exists - name: Check config (IPv4) - assert: + ansible.builtin.assert: that: - eni_ipv4_address | ipv4 - eni_ipv4_gateway | ipv4 msg: "IPv4 configuration is invalid" - name: Check config (IPV6) - assert: + ansible.builtin.assert: that: - eni_ipv6_address | ipv6 - eni_ipv6_gateway | ipv6 @@ -43,7 +43,7 @@ when: (eni_ipv6_address | length > 0) or (eni_ipv6_gateway | length > 0) - name: "A new /etc/network/interfaces is generated" - template: + ansible.builtin.template: src: interfaces.j2 dest: /etc/network/interfaces mode: "0644" @@ -51,18 +51,18 @@ group: root - name: "Systemd 'networkd' unit is stopped and disabled" - systemd: + ansible.builtin.systemd: name: systemd-networkd.service enabled: False state: stopped - name: "Systemd 'networking' unit is restarted (it often results in error)" - systemd: + ansible.builtin.systemd: name: networking enabled: True state: restarted ignore_errors: True - - debug: + - ansible.builtin.debug: msg: You should verify your configuration, then reboot the server. when: (force_update_eni_file | bool) or (not interfaces_file.stat.exists) diff --git a/networkd-to-ifconfig/tasks/set_facts_from_ansible.yml b/networkd-to-ifconfig/tasks/set_facts_from_ansible.yml index 5f6f4011..b358801d 100644 --- a/networkd-to-ifconfig/tasks/set_facts_from_ansible.yml +++ b/networkd-to-ifconfig/tasks/set_facts_from_ansible.yml @@ -1,13 +1,13 @@ --- - name: Prepare variables (IPv4) - set_fact: + ansible.builtin.set_fact: eni_ipv4_address: "{{ ansible_default_ipv4.address | ipv4 }}" eni_ipv4_gateway: "{{ ansible_default_ipv4.gateway | ipv4 }}" when: ansible_default_ipv4 | length > 0 - name: Prepare variables (IPv6) - set_fact: + ansible.builtin.set_fact: eni_ipv6_address: "{{ ansible_default_ipv6.address | ipv6 | first }}" eni_ipv6_gateway: "{{ ansible_default_ipv6.gateway | ipv6 | first }}" when: ansible_default_ipv6 | length > 0 diff --git a/networkd-to-ifconfig/tasks/set_facts_from_systemd.yml b/networkd-to-ifconfig/tasks/set_facts_from_systemd.yml index d21012fd..66dc648c 100644 --- a/networkd-to-ifconfig/tasks/set_facts_from_systemd.yml +++ b/networkd-to-ifconfig/tasks/set_facts_from_systemd.yml @@ -1,17 +1,19 @@ --- - name: "Parse addresses" - shell: "grep Address= /etc/systemd/network/50-default.network | cut -d'=' -f2" + ansible.builtin.shell: + cmd: "grep Address= /etc/systemd/network/50-default.network | cut -d'=' -f2" register: network_address_grep check_mode: no - name: "Parse gateways" - shell: "grep Gateway= /etc/systemd/network/50-default.network | cut -d'=' -f2" + ansible.builtin.shell: + cmd: "grep Gateway= /etc/systemd/network/50-default.network | cut -d'=' -f2" register: network_gateway_grep check_mode: no - name: Prepare variables - set_fact: + ansible.builtin.set_fact: eni_ipv4_address: "{{ network_address_grep.stdout_lines | ipv4 | first }}" eni_ipv4_gateway: "{{ network_gateway_grep.stdout_lines | ipv4 | first }}" eni_ipv6_address: "{{ network_address_grep.stdout_lines | ipv6 | first }}" diff --git a/newrelic/defaults/main.yml b/newrelic/defaults/main.yml index cddbcb0b..3205e53b 100644 --- a/newrelic/defaults/main.yml +++ b/newrelic/defaults/main.yml @@ -5,3 +5,5 @@ newrelic_php: False newrelic_license: "" newrelic_appname: "" + +apt_keyring_dir: "{{ ansible_distribution_major_version is version('12', '<') | ternary('/etc/apt/trusted.gpg.d', '/etc/apt/keyrings') }}" \ No newline at end of file diff --git a/newrelic/handlers/main.yml b/newrelic/handlers/main.yml index 4ad78be9..ffa52956 100644 --- a/newrelic/handlers/main.yml +++ b/newrelic/handlers/main.yml @@ -1,20 +1,20 @@ --- - name: reload squid3 - service: + ansible.builtin.service: name: squid3 state: reloaded - name: reload squid - service: + ansible.builtin.service: name: squid state: reloaded - name: apt update - apt: + ansible.builtin.apt: update_cache: yes - name: restart newrelic-sysmond - systemd: + ansible.builtin.systemd: name: newrelic-sysmond state: restarted diff --git a/newrelic/tasks/main.yml b/newrelic/tasks/main.yml index a4e8f2b3..e2c49021 100644 --- a/newrelic/tasks/main.yml +++ b/newrelic/tasks/main.yml @@ -1,9 +1,9 @@ --- -- include: sources.yml +- ansible.builtin.include: sources.yml -- include: php.yml +- ansible.builtin.include: php.yml when: newrelic_php | bool -- include: sysmond.yml +- ansible.builtin.include: sysmond.yml when: newrelic_sysmond | bool diff --git a/newrelic/tasks/php.yml b/newrelic/tasks/php.yml index c41dbac9..5afe937d 100644 --- a/newrelic/tasks/php.yml +++ b/newrelic/tasks/php.yml @@ -1,7 +1,7 @@ --- - name: Pre-seed package configuration with app name - debconf: + ansible.builtin.debconf: name: newrelic-php5 question: "newrelic-php5/application-name" value: "{{ newrelic_appname }}" @@ -9,7 +9,7 @@ when: newrelic_appname | length > 0 - name: Pre-seed package configuration with license - debconf: + ansible.builtin.debconf: name: newrelic-php5 question: "newrelic-php5/license-key" value: "{{ newrelic_license }}" @@ -17,26 +17,27 @@ when: newrelic_license | length > 0 - name: list newrelic config files - shell: "find /etc/php* -type f -name newrelic.ini" - changed_when: false + ansible.builtin.shell: + cmd: "find /etc/php* -type f -name newrelic.ini" + changed_when: False check_mode: no register: find_newrelic_ini - name: Disable AWS detection - lineinfile: + ansible.builtin.lineinfile: dest: "{{ item }}" regexp: '^;?newrelic.daemon.utilization.detect_aws' line: 'newrelic.daemon.utilization.detect_aws = false' loop: "{{ find_newrelic_ini.stdout_lines }}" - name: Disable Docker detection - lineinfile: + ansible.builtin.lineinfile: dest: "{{ item }}" regexp: '^;?newrelic.daemon.utilization.detect_docker' line: 'newrelic.daemon.utilization.detect_docker = false' loop: "{{ find_newrelic_ini.stdout_lines }}" - name: Install package for PHP - apt: + ansible.builtin.apt: name: newrelic-php5 state: present diff --git a/newrelic/tasks/sources.yml b/newrelic/tasks/sources.yml index c27de24d..3f745db9 100644 --- a/newrelic/tasks/sources.yml +++ b/newrelic/tasks/sources.yml @@ -1,29 +1,31 @@ --- -- name: Look for legacy apt keyring - stat: - path: /etc/apt/trusted.gpg - register: _trusted_gpg_keyring - -- name: NewRelic embedded GPG key is absent - apt_key: - id: "548C16BF" - keyring: /etc/apt/trusted.gpg - state: absent - when: _trusted_gpg_keyring.stat.exists - - name: Add NewRelic GPG key - copy: + ansible.builtin.copy: src: newrelic.asc - dest: /etc/apt/trusted.gpg.d/newrelic.asc - force: yes + dest: "{{ apt_keyring_dir }}/newrelic.asc" + force: true mode: "0644" owner: root group: root -- name: Install NewRelic repository - apt_repository: - repo: "deb http://apt.newrelic.com/debian/ newrelic non-free" +- name: Install NewRelic repository (Debian <12) + ansible.builtin.apt_repository: + repo: "deb [signed-by={{ apt_keyring_dir }}/newrelic.asc] http://apt.newrelic.com/debian/ newrelic non-free" state: present filename: newrelic update_cache: yes + when: ansible_distribution_major_version is version('12', '<') + +- name: Add NewRelic repository (Debian >=12) + ansible.builtin.template: + src: newrelic.sources.j2 + dest: /etc/apt/sources.list.d/newrelic.sources + state: present + register: newrelic_sources + when: ansible_distribution_major_version is version('12', '>=') + +- name: Update APT cache + ansible.builtin.apt: + update_cache: yes + when: newrelic_sources is changed \ No newline at end of file diff --git a/newrelic/tasks/sysmond.yml b/newrelic/tasks/sysmond.yml index e5c5bab9..a6f7fdf6 100644 --- a/newrelic/tasks/sysmond.yml +++ b/newrelic/tasks/sysmond.yml @@ -1,11 +1,11 @@ --- - name: Install system monitor daemon - apt: + ansible.builtin.apt: name: newrelic-sysmond - name: Set license key for newrelic-sysmond - replace: + ansible.builtin.replace: dest: /etc/newrelic/nrsysmond.cfg regexp: "license_key=REPLACE_WITH_REAL_KEY" replace: "license_key={{ newrelic_license }}" diff --git a/newrelic/templates/newrelic.sources.j2 b/newrelic/templates/newrelic.sources.j2 new file mode 100644 index 00000000..85145fc0 --- /dev/null +++ b/newrelic/templates/newrelic.sources.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +Types: deb +URIs: http://apt.newrelic.com/debian/ +Suites: newrelic +Components: non-free +Signed-by: {{ apt_keyring_dir }}/newrelic.asc +Enabled: yes \ No newline at end of file diff --git a/nginx/handlers/main.yml b/nginx/handlers/main.yml index 494d40f4..bdd5f477 100644 --- a/nginx/handlers/main.yml +++ b/nginx/handlers/main.yml @@ -1,15 +1,15 @@ --- - name: restart nginx - service: + ansible.builtin.service: name: nginx state: restarted - name: reload nginx - service: + ansible.builtin.service: name: nginx state: reloaded - name: restart munin - service: + ansible.builtin.service: name: munin-node state: restarted diff --git a/nginx/tasks/ip_whitelist.yml b/nginx/tasks/ip_whitelist.yml index 2667d1d3..fc4fd2d2 100644 --- a/nginx/tasks/ip_whitelist.yml +++ b/nginx/tasks/ip_whitelist.yml @@ -1,7 +1,7 @@ --- - name: add IP addresses to private IP whitelist - lineinfile: + ansible.builtin.lineinfile: dest: /etc/nginx/snippets/ipaddr_whitelist line: "allow {{ item }};" state: present @@ -12,7 +12,7 @@ - ips - name: remove IP addresses from private IP whitelist - lineinfile: + ansible.builtin.lineinfile: dest: /etc/nginx/snippets/ipaddr_whitelist line: "allow {{ item }};" state: absent diff --git a/nginx/tasks/logrotate.yml b/nginx/tasks/logrotate.yml index c987c2f7..9081ef41 100644 --- a/nginx/tasks/logrotate.yml +++ b/nginx/tasks/logrotate.yml @@ -1,10 +1,10 @@ --- - name: Logrotate is configured for Nginx - copy: + ansible.builtin.copy: src: logrotate_nginx dest: /etc/logrotate.d/nginx - force: no + force: false tags: - nginx - logrotate diff --git a/nginx/tasks/main.yml b/nginx/tasks/main.yml index b3f1c313..1284a6a1 100644 --- a/nginx/tasks/main.yml +++ b/nginx/tasks/main.yml @@ -1,16 +1,16 @@ --- -- debug: +- ansible.builtin.debug: msg: "Nginx minimal mode has been removed, falling back to normal mode." when: not nginx_minimal | bool -- debug: +- ansible.builtin.debug: msg: "Nginx minimal mode has been set, using minimal mode." when: nginx_minimal | bool -- include: packages.yml +- ansible.builtin.include: packages.yml -- include: server_status_read.yml +- ansible.builtin.include: server_status_read.yml tags: - nginx @@ -18,7 +18,7 @@ # without touching the main file - name: customize worker_connections - lineinfile: + ansible.builtin.lineinfile: dest: /etc/nginx/nginx.conf regexp: '^(\s*worker_connections)\s+.+;' line: ' worker_connections 1024;' @@ -27,7 +27,7 @@ - nginx - name: use epoll - lineinfile: + ansible.builtin.lineinfile: dest: /etc/nginx/nginx.conf regexp: '^(\s*use)\s+.+;' line: ' use epoll;' @@ -36,11 +36,11 @@ - nginx - name: Install Nginx http configuration - copy: + ansible.builtin.copy: src: nginx/evolinux-defaults.conf dest: /etc/nginx/conf.d/z-evolinux-defaults.conf mode: "0640" - # force: yes + # force: true notify: reload nginx tags: - nginx @@ -50,51 +50,51 @@ # and not too loose for private_htpasswd - name: Copy ipaddr_whitelist - copy: + ansible.builtin.copy: src: nginx/snippets/ipaddr_whitelist dest: /etc/nginx/snippets/ipaddr_whitelist owner: www-data group: www-data directory_mode: "0640" mode: "0640" - force: no + force: false notify: reload nginx tags: - nginx - ips - name: Include IP address whitelist task - include: ip_whitelist.yml + ansible.builtin.include: ip_whitelist.yml -- name: Copy evolinux_server_custom - copy: +- name: Copy evolinux_server_custom + ansible.builtin.copy: src: nginx/snippets/evolinux_server_custom dest: /etc/nginx/snippets/evolinux_server_custom owner: www-data group: www-data directory_mode: "0640" mode: "0640" - force: no + force: false notify: reload nginx tags: - nginx - ips - name: Copy private_htpasswd - copy: + ansible.builtin.copy: src: nginx/snippets/private_htpasswd dest: /etc/nginx/snippets/private_htpasswd owner: www-data group: www-data directory_mode: "0640" mode: "0640" - force: no + force: false notify: reload nginx tags: - nginx - name: add user:pwd to private htpasswd - lineinfile: + ansible.builtin.lineinfile: dest: /etc/nginx/snippets/private_htpasswd line: "{{ item }}" state: present @@ -104,7 +104,7 @@ - nginx - name: remove user:pwd from private htpasswd - lineinfile: + ansible.builtin.lineinfile: dest: /etc/nginx/snippets/private_htpasswd line: "{{ item }}" state: absent @@ -114,7 +114,7 @@ - nginx - name: nginx vhost is installed - template: + ansible.builtin.template: src: "{{ nginx_default_template_regular }}" dest: /etc/nginx/sites-available/evolinux-default.conf mode: "0640" @@ -124,22 +124,22 @@ - nginx - name: default vhost is enabled - file: + ansible.builtin.file: src: /etc/nginx/sites-available/evolinux-default.conf dest: /etc/nginx/sites-enabled/default state: link - force: yes + force: true notify: reload nginx when: nginx_evolinux_default_enabled | bool tags: - nginx -- include: server_status_write.yml +- ansible.builtin.include: server_status_write.yml tags: - nginx - name: Verify that the service is enabled and started - service: + ansible.builtin.service: name: nginx enabled: yes state: started @@ -147,7 +147,7 @@ - nginx - name: Check if Munin is installed - stat: + ansible.builtin.stat: path: /etc/munin/plugin-conf.d/munin-node check_mode: no register: stat_munin_node @@ -155,16 +155,16 @@ - nginx - munin -- include: munin_vhost.yml +- ansible.builtin.include: munin_vhost.yml when: stat_munin_node.stat.exists tags: - nginx - munin -- include: munin_graphs.yml +- ansible.builtin.include: munin_graphs.yml when: stat_munin_node.stat.exists tags: - nginx - munin -- include: logrotate.yml +- ansible.builtin.include: logrotate.yml diff --git a/nginx/tasks/munin_graphs.yml b/nginx/tasks/munin_graphs.yml index 5958c856..f2a6e4b5 100644 --- a/nginx/tasks/munin_graphs.yml +++ b/nginx/tasks/munin_graphs.yml @@ -1,14 +1,14 @@ --- - name: Munin config for Nginx is present - template: + ansible.builtin.template: src: munin/evolinux.nginx dest: /etc/munin/plugin-conf.d/ mode: "0644" notify: restart munin - name: Munin plugins for Nginx are installed - file: + ansible.builtin.file: src: '/usr/share/munin/plugins/{{ item }}' dest: '/etc/munin/plugins/{{ item }}' state: link diff --git a/nginx/tasks/munin_vhost.yml b/nginx/tasks/munin_vhost.yml index ff9f8423..98cc8672 100644 --- a/nginx/tasks/munin_vhost.yml +++ b/nginx/tasks/munin_vhost.yml @@ -1,13 +1,13 @@ --- - name: Add munin to hosts - lineinfile: + ansible.builtin.lineinfile: dest: /etc/hosts regexp: 'munin$' line: '127.0.0.1 munin' insertafter: EOF - name: Packages for Munin CGI are installed - apt: + ansible.builtin.apt: name: - liblwp-useragent-determined-perl - libcgi-fast-perl @@ -15,26 +15,24 @@ state: present - name: Owner for munin-cgi is set to www-data:munin - shell: "chown --verbose www-data:munin /var/log/munin/munin-cgi-*" + ansible.builtin.shell: + cmd: "chown --verbose www-data:munin /var/log/munin/munin-cgi-*" register: command_result changed_when: "'changed' in command_result.stdout" - args: - warn: no - name: Mode for munin-cgi is set to 660 - shell: "chmod --verbose 660 /var/log/munin/munin-cgi-*" + ansible.builtin.shell: + cmd: "chmod --verbose 660 /var/log/munin/munin-cgi-*" register: command_result changed_when: "'changed' in command_result.stdout" - args: - warn: no - name: Systemd unit for Munin-fcgi is installed - copy: + ansible.builtin.copy: src: systemd/spawn-fcgi-munin-graph.service dest: /etc/systemd/system/spawn-fcgi-munin-graph.service - name: Systemd unit for Munin-fcgi is started - systemd: + ansible.builtin.systemd: name: spawn-fcgi-munin-graph daemon_reload: yes enabled: yes diff --git a/nginx/tasks/packages.yml b/nginx/tasks/packages.yml index f9a500c0..fd9febcf 100644 --- a/nginx/tasks/packages.yml +++ b/nginx/tasks/packages.yml @@ -1,27 +1,18 @@ -- set_fact: +- ansible.builtin.set_fact: nginx_default_package_name: nginx-light when: nginx_minimal | bool -- include: packages_backports.yml +- ansible.builtin.include: packages_backports.yml when: nginx_backports | bool # TODO: install "nginx" + only necessary modules, instead of "nginx-full" - name: Nginx is installed - apt: + ansible.builtin.apt: name: "{{ nginx_package_name | default(nginx_default_package_name) }}" state: present tags: - nginx - packages - -- name: Service is running as configured. - service: - name: nginx - state: "{{ nginx_service_state }}" - enabled: "{{ nginx_service_enabled }}" - tags: - - nginx - - packages diff --git a/nginx/tasks/packages_backports.yml b/nginx/tasks/packages_backports.yml index 820d8713..88402bb9 100644 --- a/nginx/tasks/packages_backports.yml +++ b/nginx/tasks/packages_backports.yml @@ -1,7 +1,7 @@ --- - name: Backports repository is configured - include_role: + ansible.builtin.include_role: name: evolix/apt tasks_from: backports.yml tags: @@ -9,10 +9,10 @@ - packages - name: Prefer Nginx packages from backports - template: + ansible.builtin.template: src: apt/nginx_preferences dest: /etc/apt/preferences.d/999-nginx - force: yes + force: true mode: "0640" register: nginx_apt_preferences tags: @@ -20,7 +20,7 @@ - packages - name: APT cache is updated - apt: + ansible.builtin.apt: update_cache: yes when: nginx_apt_preferences is changed tags: diff --git a/nginx/tasks/server_status_read.yml b/nginx/tasks/server_status_read.yml index 652bc154..d6cecbe3 100644 --- a/nginx/tasks/server_status_read.yml +++ b/nginx/tasks/server_status_read.yml @@ -1,7 +1,7 @@ --- - name: "server status dirname exists '{{ nginx_serverstatus_suffix_file | dirname }}'" - file: + ansible.builtin.file: dest: "{{ nginx_serverstatus_suffix_file | dirname }}" mode: "0700" owner: root @@ -9,28 +9,30 @@ state: directory - name: set nginx serverstatus suffix if provided - copy: + ansible.builtin.copy: dest: "{{ nginx_serverstatus_suffix_file }}" # The last character "\u000A" is a line feed (LF), it's better to keep it content: "{{ nginx_serverstatus_suffix }}\u000A" - force: yes + force: true when: nginx_serverstatus_suffix | length > 0 - name: generate random string for server-status suffix - shell: "apg -a 1 -M N -n 1 > {{ nginx_serverstatus_suffix_file }}" + ansible.builtin.shell: + cmd: "apg -a 1 -M N -n 1 > {{ nginx_serverstatus_suffix_file }}" args: creates: "{{ nginx_serverstatus_suffix_file }}" - name: read nginx server status suffix - command: "tail -n 1 {{ nginx_serverstatus_suffix_file }}" + ansible.builtin.command: + cmd: "tail -n 1 {{ nginx_serverstatus_suffix_file }}" changed_when: False check_mode: no register: new_nginx_serverstatus_suffix - name: overwrite nginx_serverstatus_suffix - set_fact: + ansible.builtin.set_fact: nginx_serverstatus_suffix: "{{ new_nginx_serverstatus_suffix.stdout }}" -- debug: +- ansible.builtin.debug: var: nginx_serverstatus_suffix verbosity: 1 diff --git a/nginx/tasks/server_status_write.yml b/nginx/tasks/server_status_write.yml index beb56c67..dbed56cb 100644 --- a/nginx/tasks/server_status_write.yml +++ b/nginx/tasks/server_status_write.yml @@ -1,19 +1,19 @@ --- - name: replace server-status suffix in default site index - replace: + ansible.builtin.replace: dest: /var/www/index.html regexp: '__SERVERSTATUS_SUFFIX__' replace: "{{ nginx_serverstatus_suffix }}" - name: add server-status suffix in default site index if missing - replace: + ansible.builtin.replace: dest: /var/www/index.html regexp: '"/server-status-?"' replace: '"/server-status-{{ nginx_serverstatus_suffix }}"' - name: add server-status suffix in default VHost - replace: + ansible.builtin.replace: dest: /etc/nginx/sites-available/evolinux-default.conf regexp: 'location /server-status-? {' replace: 'location /server-status-{{ nginx_serverstatus_suffix }} {' diff --git a/nodejs/defaults/main.yml b/nodejs/defaults/main.yml index 8f36de49..a8adbb47 100644 --- a/nodejs/defaults/main.yml +++ b/nodejs/defaults/main.yml @@ -4,3 +4,5 @@ nodejs_apt_version: 'node_16.x' nodejs_install_yarn: False + +apt_keyring_dir: "{{ ansible_distribution_major_version is version('12', '<') | ternary('/etc/apt/trusted.gpg.d', '/etc/apt/keyrings') }}" \ No newline at end of file diff --git a/nodejs/files/yarn.asc b/nodejs/files/yarn.asc index e8d9cabb..03d9338a 100644 --- a/nodejs/files/yarn.asc +++ b/nodejs/files/yarn.asc @@ -158,86 +158,63 @@ V1L7FROM6fKydeSLJbx17SNjVdQnq1OsyqSO0catAFNptMHBsN+tiCI29gpGegao umV9cnND69aYvyPBgvdtmzPChjSmc6rzW1yXCJDm2qzwm/BcwJNXW5B3EUPxc0qS Wste9fUna0G4l/WMuaIzVkuTgXf1/r9HeQbjtxAztxH0d0VgdHAWPDkUYmztcZ4s d0PWkVa18qSrOvyhI96gCzdvMRLX17m1kPvP5PlPulvqizjDs8BScqeSzGgSbbQV -m5Tx4w2uF4/n3FBnABEBAAGJBEQEGAECAA8FAlwsRBECGwIFCQIKEgACKQkQFkaw -G4blAxDBXSAEGQECAAYFAlwsRBEACgkQI+cWZ4i2Ph6B0g//cPis3v2M6XvAbVoM -3GIMXnsVj1WAHuwA/ja7UfZJ9+kV/PiMLkAbW0fBj0/y0O3Ry12VVQGXhC+Vo4j6 -C8qwFP4OXa6EsxHXuvWMIztBaX1Kav613aXBtxp6tTrud0FFUh4sDc1RREb3tMr6 -y5cvFJgnrdWcX1gsl6ODcgWBGNc6ZX7H7j48hMR6KmNeZocW7p8W+BgDQJqXYwVN -L15qOHzVAh0dWsFLE9gwBTmDCY03x9arxSNDGCXyxt6E77LbNVIoSRlEbkvi6j33 -nEbuERICYl6CltXQCyiVKjheJcLMjbgv5+bLCv2zfeJ/WyOmOGKpHRu+lBV1Gvli -RxUblVlmjWPhYPBZXGyjII16Tqr+ilREcZFW+STccbrVct75JWLbxwlEmix+W1Hw -SRCR+KHx3Cur4ZPMOBlPsFilOOsNa7ROUB56t7zv21Ef3BeeaCd9c4kzNGN8d1ic -EqSXoWWPqgST0LZPtZyqWZVnWrHChVHfrioxhSnw8O3wY1A2GSahiCSvvjvOeEoJ -yU21ZMw6AVyHCh6v42oYadBfGgFwNo5OCMhNxNy/CcUrBSDqyLVTM5QlNsT75Ys7 -kHHnc+Jk+xx4JpiyNCz5LzcPhlwpqnJQcjJdY1hDhK75Ormj/NfCMeZ8g1aVPX4x -Eq8AMyZYhZ5/lmM+13Rdv8ZW6FK7HQ/+IAKzntxOjw0MzCXkksKdmIOZ2bLeOVI8 -aSLaUmoT5CLuoia9g7iFHlYrSY+01riRrAaPtYx0x8onfyVxL9dlW/Fv5+qc1fF5 -FxdhyIgdqgzm82TnXHu/haUxYmUvNrbsmmNl5UTTOf+YQHMccKFdYfZ2rCBtbN2n -iXG1tuz2+k83pozu4mJ1rOOLNAsQoY3yR6OODte1FyOgp7blwDhTIoQb8/UiJ7CM -BI3OPrfoXFAnhYoxeRSAN4UFu9/HIkqfaQgRPCZS1gNerWF6r6yz9AZWUZqjSJss -jBqXCtK9bGbTYBZk+pw3H9Nd0RJ2WJ9qPqmlmUr1wdqct0ChsJx1xAT86QrssicJ -/HFFmF45hlnGkHUBWLaVJt8YkLb/DqOIbVbwyCLQtJ80VQLEeupfmu5QNsTpntRY -NKf8cr00uc8vSYXYFRxa5H5oRT1eoFEEjDDvokNnHXfT+Hya44IjYpzaqvAgeDp6 -sYlOdtWIv/V3s+trxACwTkRN7zw3lLTbT8PK9szK0fYZ5KHG1/AKH+mbZ6qNc/25 -PNbAFRtttLGuEIC3HJ12IAp2JdjioeD2OnWLu4ZeCT2CKKFsleZPrSyCrn3gyZPm -fYvv5h2JbQNO6uweOrZENWX5SU43OBoplbuKJZsMP6p6NahuGnIeJLlv509JYAf/ -HN4ARyvvOpOJBFsEGAEIACYCGwIWIQRy7PRqVrStOckHu7cWRrAbhuUDEAUCYA3F -QQUJB6PoMAIpwV0gBBkBAgAGBQJcLEQRAAoJECPnFmeItj4egdIP/3D4rN79jOl7 -wG1aDNxiDF57FY9VgB7sAP42u1H2SffpFfz4jC5AG1tHwY9P8tDt0ctdlVUBl4Qv -laOI+gvKsBT+Dl2uhLMR17r1jCM7QWl9Smr+td2lwbcaerU67ndBRVIeLA3NUURG -97TK+suXLxSYJ63VnF9YLJejg3IFgRjXOmV+x+4+PITEeipjXmaHFu6fFvgYA0Ca -l2MFTS9eajh81QIdHVrBSxPYMAU5gwmNN8fWq8UjQxgl8sbehO+y2zVSKEkZRG5L -4uo995xG7hESAmJegpbV0AsolSo4XiXCzI24L+fmywr9s33if1sjpjhiqR0bvpQV -dRr5YkcVG5VZZo1j4WDwWVxsoyCNek6q/opURHGRVvkk3HG61XLe+SVi28cJRJos -fltR8EkQkfih8dwrq+GTzDgZT7BYpTjrDWu0TlAeere879tRH9wXnmgnfXOJMzRj -fHdYnBKkl6Flj6oEk9C2T7WcqlmVZ1qxwoVR364qMYUp8PDt8GNQNhkmoYgkr747 -znhKCclNtWTMOgFchwoer+NqGGnQXxoBcDaOTgjITcTcvwnFKwUg6si1UzOUJTbE -++WLO5Bx53PiZPsceCaYsjQs+S83D4ZcKapyUHIyXWNYQ4Su+Tq5o/zXwjHmfINW -lT1+MRKvADMmWIWef5ZjPtd0Xb/GVuhSCRAWRrAbhuUDEMTLEACyFHe0SPm4rMMA -E6dyadTJP8wRoI2epQciRqitIhANhmJ244WyqPWV3tDTgH/TaWPV7DerL6d2jOnw -mdfT5JeXkWrGf5Gxwz619UFx/S4VpPOQf4eJb1Z9WaOdQ87A9+BwwO8d+2XROhMm -iAetVo6jhvil0xR5t9HYg/uUSUu+tlHXlwPjdlYHUwUnt8HftoefWLXJj8ADHir1 -slw7jjFR/INE2dWqk6Lx2Ala+3yHN7/vpfOYvY4EyTvIeyLSoVn0fzUrsIv3HQSR -WogO3MykjkiMjNbhdH8CXbEiQ1MiFKsugyi0kY6HOIe3//+cZ4xXlQLsLRnV3xm9 -e/xGOte4M8o05JaUCrcsCmubOnqUIaZmDF9bITHI7bhkxLkvXopoxx4UodiL4PPG -OarAdRD2Y73eI7W6QhqZt8267tsLx4qe0q8/pCr7gX60E9hOSx2NszyS0FPME2CI -4vxVR+GxS8gzp5hFQ8OUaSC9a6eb4YI66bDhkRog0GrMagX3JJI2172blRyp8Fe7 -DAEUOb/xCcaKdv6waT+pqtrOaxDArDVRPVVqDlr1fY0lJis92ycBk4Gs8pAYiMEZ -lGUoh5MouBEPP7HtfZTMlsQm8J5hq3cJ+AxUPSbGTWUCql7hGpT4S97mpyATuLnW -qLZmBgDHhpHEmUQmONKSSpzSjjAS6LkCDQRcN/VvARAAoEHIkyjFDsfoCxA/b2qN -jz+l8OI2WhAMdqxReg7JN9R61qbetj9RYIcWswPSO84c0ioRUk+xJavEFh/6Lg00 -QKwJKPf0kd1Us6SfqklxGczOaWNLyiM7JthFRNMp0qVX6NjLqGoCNO+d/+nNk6s2 -x4rLECj/EROmE3ZQQEo5nBXmPlhXpVem23rGfXEQvXDNqFmvqrP+Befn/+aDpo89 -QIm3sE8G0LfgcajIdSfgLH+NJTvOVAtXXVXJPK39Njr1aBzWTbWhLS2bji7DwP7h -shdh7DE2rS623vlzvkkrms8oKkiRpKATdhQ8CEx+mhTFKCj6GtNqhwttCbf98N9G -piHD0has65YtgQQjk2pLR62rZf6czagRfKbFQzXjl2JxS/bsHVhTkhyJFqgDcHCS -Xe7K8uGTAE2AkakGhGyDJYqGVSl0w5IAU8dqDQMc0IpsVMbFk4nX4GgOwixwrzrg -Ch0jRi+EwUHJYZHBAyzNCkr++D25R0gwNhPMjSKe8Ks6G3hH3XP/ZVlceW/gPfxR -ixUTk/q7s3xPpPhLMREEpKS1aGcmYxEkrkVBDAzNYKdKP1MYwLn4lh4yNFXWlTCl -nDyI6UODTHwt8xDddtnT9u+U+xc6OJiYcCOstl+ovS9HmM/Kt9VTEX9cckEEL1IS -+9esQMr4b5X02Y1q9Q2uEucAEQEAAYkEWwQYAQgAJgIbAhYhBHLs9GpWtK05yQe7 -txZGsBuG5QMQBQJgDcVSBQkHmDbjAinBXSAEGQECAAYFAlw39W8ACgkQT3dnk2lH -W6p0eg/+K2JJu1RbTSLJPFYQhLcxX+5d2unkuNLIy3kArtZuB992E2Fw00okPGtu -PdSyk2ygh4DeYnwmabIWChi7LDp+YnqcI4GfMxNG6RsHs+A/77rLBST3BB1sejZp -pmKCQZDSC2pvYaZBpS80UvftCZ9RFdY+kTC22Btn/5ekiQOfIqhUH9CyGWS/YlGc -iomVIVn1hSPN8l4EpBCDtceRaephvzjQIZT3AxOfSlpwJviYjAOkSX4qWyIjC5Ke -5kfEOldUuBN1JGAm45tKlrz/LD/+VOc2IWpbkOIAVSldUgpRyiIJQAZ80trNxrJI -7ncaID8lAa7pBptJiL0KorRjk3c6Y7p830Nwe0J5e5+W1RzN4wlR8+9uuRyP8Mcw -z/Hz2jwMiv38Vk4tAOe4PYNZuDnpjZ28yCpF3UUgvzjarubFAcg2jd8SauCQFlmO -fvT+1qIMSeLmWBOdlzJTUpJRcZqnkEE4WtiMSlxyWVFvUwOmKSGi8CLoGW1Ksh9t -hQ9zKhvVUiVoKn4Z79HXr4pX6rnp+mweJ2dEZtlqD7HxjVTlCHn9fzClt/Nt0h72 -1fJbS587AC/ZMgg5GV+GKu6Mij0sPAowUJVCIwN9uK/GHICZEAoMSngP8xzKnhU5 -FD38vwBvsqbKxTtICrv2NuwnQ0WBBQ58w5mv2RCMr2W6iegSKIAJEBZGsBuG5QMQ -U8oQAMjiPEOFmgRcuhvhlzXT53d/1b8sfG4MV9c45xKE65L+kPoSGzvNWYumB2Kw -Qzf8tWu+6PmOljj1Ofyilqm3bblOasHWgDGPTSOcBaVhl8nZrS3o2fzZy7aQKYE3 -gQBZ6+jzhHQzrnQURpR+s/mdSO3+Gs+6kBmh9dkIQ8U1cfaAbZgy17BipPZkpwjr -ltTcDyJniQyEm7L6yV6MWt2TiFUA5IvyH+hTSKrLHnR7+lYDEo28wV8f8UcLrUpQ -joiCOWZeNCubaIxHHoGtCE+zkhSsuW9lGSX0rzQlmx1vclrYwyMKhlpDOqy8kzdI -Ws7VF3vCXRi6fWSA7apRtQQ7PbuZOOyYTaEkEuJ5CfWhFGy3eikiXilPk05ECZd3 -/uMB1dmPFKT+MbUDCA/b8amfkNTLg+RFNX+5isMLkrJ+8k13ueTp/PToGMIkYsbR -+HRm0HmrdqGFPl7o+0xXUT4wGbQD8QfK81lzH1QQhsu+12OsFt+jQC3IDYiXOUBk -zgkwMlt8C0vU0i/EElpqx/0n19iHv7XvPn5q0MdNBS5pW+DOho0D+z+NM9MWpYUu -ymC/28jo8Olju+9DZuZwEUEbptmltcA8UQ5r4FHx4m3sfCmCs1QUeb8TPNL0x8OA -XnADXbxMgGYTNX7YvdUw3a8M73stqnN9M8lUXln7ulOCee2z -=IgpF ------END PGP PUBLIC KEY BLOCK----- +m5Tx4w2uF4/n3FBnABEBAAGJBFsEGAEIACYCGwIWIQRy7PRqVrStOckHu7cWRrAb +huUDEAUCY897hAUJDUbR8wIpwV0gBBkBAgAGBQJcLEQRAAoJECPnFmeItj4egdIP +/3D4rN79jOl7wG1aDNxiDF57FY9VgB7sAP42u1H2SffpFfz4jC5AG1tHwY9P8tDt +0ctdlVUBl4QvlaOI+gvKsBT+Dl2uhLMR17r1jCM7QWl9Smr+td2lwbcaerU67ndB +RVIeLA3NUURG97TK+suXLxSYJ63VnF9YLJejg3IFgRjXOmV+x+4+PITEeipjXmaH +Fu6fFvgYA0Cal2MFTS9eajh81QIdHVrBSxPYMAU5gwmNN8fWq8UjQxgl8sbehO+y +2zVSKEkZRG5L4uo995xG7hESAmJegpbV0AsolSo4XiXCzI24L+fmywr9s33if1sj +pjhiqR0bvpQVdRr5YkcVG5VZZo1j4WDwWVxsoyCNek6q/opURHGRVvkk3HG61XLe ++SVi28cJRJosfltR8EkQkfih8dwrq+GTzDgZT7BYpTjrDWu0TlAeere879tRH9wX +nmgnfXOJMzRjfHdYnBKkl6Flj6oEk9C2T7WcqlmVZ1qxwoVR364qMYUp8PDt8GNQ +NhkmoYgkr747znhKCclNtWTMOgFchwoer+NqGGnQXxoBcDaOTgjITcTcvwnFKwUg +6si1UzOUJTbE++WLO5Bx53PiZPsceCaYsjQs+S83D4ZcKapyUHIyXWNYQ4Su+Tq5 +o/zXwjHmfINWlT1+MRKvADMmWIWef5ZjPtd0Xb/GVuhSCRAWRrAbhuUDEHSxD/9M +5il+6iZDsLMFQvsZJjRWnquPxRXBfyA3aiLJXsmMwWfSdEjS3JKq2hrOKVT3FgkN +CHBxhPREIPEhlE7EsGmdYvvzceYeM8LuK4DVMIjjpsIlxyS+h3iQNamoITbwuZyc +Hgv9FGVOElrtntqPY6BZWBdK1ZVAT3Q4hf1+o2UZ6o5gcmu6rR5wlgsqdGc5XCev +YVaJ7qQXvLhU0gzWyJ1p//d4DQUqrXW9+1bFg/gwPFn+ZBoO40/IovwoIdo1xX4p +KgH47aXFRHB53LhNtve422XDEuQnBTwNucvxAA91TmFt1BDVy1VCEwlDaKMS4Tuw +xrBEBKwsuBqelJPEcDzzt+yvc3jPoVrNrC5zLpAF3VPCUCkf21tbqYroFy/UfQls +O26iJhfPxoLEGtuCYt+DrpnR/1DteKqtett+Z1nJ9JEZAxk8QjdcpdMa5kBtC1hd +vb9f8ySSxv91RtzmyehIc7TBogwK+mydWMskTmNAl4ecGepfghPfA5JDW0NUm/Vv +/DAylze+BXzXPBeMXDAsHOcf4A8QVht9jX5a03QpPcFcXUYFjtItrjeDyzlSBp3K +8B9ECMy2+ke0U0jupNWlFxxzR15e+rEi450ilL/wKm7Va5VhQuNlXToIZJdQg/3e +n2jb+0Wye2SNCdPjF8663z+VwaZDVaDXqnT72wEJv7kCDQRcN/VvARAAoEHIkyjF +DsfoCxA/b2qNjz+l8OI2WhAMdqxReg7JN9R61qbetj9RYIcWswPSO84c0ioRUk+x +JavEFh/6Lg00QKwJKPf0kd1Us6SfqklxGczOaWNLyiM7JthFRNMp0qVX6NjLqGoC +NO+d/+nNk6s2x4rLECj/EROmE3ZQQEo5nBXmPlhXpVem23rGfXEQvXDNqFmvqrP+ +Befn/+aDpo89QIm3sE8G0LfgcajIdSfgLH+NJTvOVAtXXVXJPK39Njr1aBzWTbWh +LS2bji7DwP7hshdh7DE2rS623vlzvkkrms8oKkiRpKATdhQ8CEx+mhTFKCj6GtNq +hwttCbf98N9GpiHD0has65YtgQQjk2pLR62rZf6czagRfKbFQzXjl2JxS/bsHVhT +khyJFqgDcHCSXe7K8uGTAE2AkakGhGyDJYqGVSl0w5IAU8dqDQMc0IpsVMbFk4nX +4GgOwixwrzrgCh0jRi+EwUHJYZHBAyzNCkr++D25R0gwNhPMjSKe8Ks6G3hH3XP/ +ZVlceW/gPfxRixUTk/q7s3xPpPhLMREEpKS1aGcmYxEkrkVBDAzNYKdKP1MYwLn4 +lh4yNFXWlTClnDyI6UODTHwt8xDddtnT9u+U+xc6OJiYcCOstl+ovS9HmM/Kt9VT +EX9cckEEL1IS+9esQMr4b5X02Y1q9Q2uEucAEQEAAYkEWwQYAQgAJgIbAhYhBHLs +9GpWtK05yQe7txZGsBuG5QMQBQJjz3uOBQkNOyCfAinBXSAEGQECAAYFAlw39W8A +CgkQT3dnk2lHW6p0eg/+K2JJu1RbTSLJPFYQhLcxX+5d2unkuNLIy3kArtZuB992 +E2Fw00okPGtuPdSyk2ygh4DeYnwmabIWChi7LDp+YnqcI4GfMxNG6RsHs+A/77rL +BST3BB1sejZppmKCQZDSC2pvYaZBpS80UvftCZ9RFdY+kTC22Btn/5ekiQOfIqhU +H9CyGWS/YlGciomVIVn1hSPN8l4EpBCDtceRaephvzjQIZT3AxOfSlpwJviYjAOk +SX4qWyIjC5Ke5kfEOldUuBN1JGAm45tKlrz/LD/+VOc2IWpbkOIAVSldUgpRyiIJ +QAZ80trNxrJI7ncaID8lAa7pBptJiL0KorRjk3c6Y7p830Nwe0J5e5+W1RzN4wlR +8+9uuRyP8Mcwz/Hz2jwMiv38Vk4tAOe4PYNZuDnpjZ28yCpF3UUgvzjarubFAcg2 +jd8SauCQFlmOfvT+1qIMSeLmWBOdlzJTUpJRcZqnkEE4WtiMSlxyWVFvUwOmKSGi +8CLoGW1Ksh9thQ9zKhvVUiVoKn4Z79HXr4pX6rnp+mweJ2dEZtlqD7HxjVTlCHn9 +fzClt/Nt0h721fJbS587AC/ZMgg5GV+GKu6Mij0sPAowUJVCIwN9uK/GHICZEAoM +SngP8xzKnhU5FD38vwBvsqbKxTtICrv2NuwnQ0WBBQ58w5mv2RCMr2W6iegSKIAJ +EBZGsBuG5QMQ0SIQAMFN0FlUSP5TiKrTFMj79TcCLDeAvk8+h7nNj/dlgDpRl4kp +r+XO/a0VTwK8XVszNA43FDuT0WORPG73LYlgJi5gdLeWoXaEnW1f+ZyR2uc8/UNu +8nwv2dPLefLbhrWpkQbcriOt5FHL61Z8CqYa67vm2Lkr1yD+y3XFAuB2j3hbB1pF +xmc3wvkY+ZMA3fMb+ZbAlV9ylNn4MWzK2Z1hzC0G33Ym6z8SbqljvTn0ABS8BI0g +cJaPtSV7+rq+a/YOCBudSY1qBLCHGvpkByispqKjguS/95+37zcqEbTCTX9S5XmS +lsKFY08+6rq7yu8ptLkbg/RuXLzAvn6g56zFQlPeR+BIrKeCbWRu9hx4kSS6uN22 +MgYgv7l9ohNTzRxnugHnnerdyElDge50AQeFR43bdHEhvyumPLjaJ2WbSHtxRkLw +HcXOlx6lL/i2DJeLMaCshITV6TfvubVYG8djMUogWiXK0T74oocPSs00HDNs7OPy +9W44ZAFknGvoaTOEYxNgSI84yUf2304IhP+U9pYcRnJwJM4pOzcXZxPibrQf2Ex9 +XZXRkb9jkfYMvs0XBnCTUnSl5WVVlNHo2oUC2/mwuc321M6ucf7uDwN6FdPQVlJh +1qXVLvbNiyYug0lvwXsyfwu6IX+wl+kAP5NrRYuX8H+L0eauTGrRsld7OZ3H +=e4wy +-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file diff --git a/nodejs/tasks/main.yml b/nodejs/tasks/main.yml index 5ab49e70..f79f058c 100644 --- a/nodejs/tasks/main.yml +++ b/nodejs/tasks/main.yml @@ -1,38 +1,19 @@ --- -- name: APT https transport is enabled - apt: +- name: APT https transport is enabled (Debian <10) + ansible.builtin.apt: name: apt-transport-https state: present tags: - system - packages - nodejs - -- name: Look for legacy apt keyring - stat: - path: /etc/apt/trusted.gpg - register: _trusted_gpg_keyring - tags: - - system - - packages - - nodejs - -- name: NodeJS embedded GPG key is absent - apt_key: - id: "68576280" - keyring: /etc/apt/trusted.gpg - state: absent - when: _trusted_gpg_keyring.stat.exists - tags: - - system - - packages - - nodejs + when: ansible_distribution_major_version is version('10', '<') - name: NodeJS GPG key is installed - copy: + ansible.builtin.copy: src: nodesource.asc - dest: /etc/apt/trusted.gpg.d/nodesource.asc + dest: "{{ apt_keyring_dir }}/nodesource.asc" mode: "0644" owner: root group: root @@ -41,9 +22,9 @@ - packages - nodejs -- name: NodeJS sources list ({{ nodejs_apt_version }}) is available - apt_repository: - repo: "deb https://deb.nodesource.com/{{ nodejs_apt_version }} {{ ansible_distribution_release }} main" +- name: Add NodeJS repository (Debian <12) + ansible.builtin.apt_repository: + repo: "deb [signed-by={{ apt_keyring_dir }}/nodesource.asc] https://deb.nodesource.com/{{ nodejs_apt_version }} {{ ansible_distribution_release }} main" filename: nodesource update_cache: yes state: present @@ -51,15 +32,32 @@ - system - packages - nodejs + when: ansible_distribution_major_version is version('12', '<') + +- name: Add NodeJS repository (Debian >=12) + ansible.builtin.template: + src: nodesource.sources.j2 + dest: /etc/apt/sources.list.d/nodesource.sources + state: present + register: nodesource_sources + tags: + - system + - packages + - nodejs + when: ansible_distribution_major_version is version('12', '>=') + +- name: Update APT cache + ansible.builtin.apt: + update_cache: yes + when: nodesource_sources is changed - name: NodeJS is installed - apt: + ansible.builtin.apt: name: nodejs state: present - update_cache: yes tags: - packages - nodejs -- include: yarn.yml +- ansible.builtin.import_tasks: yarn.yml when: nodejs_install_yarn | bool diff --git a/nodejs/tasks/yarn.yml b/nodejs/tasks/yarn.yml index e3dfe1da..645f8f90 100644 --- a/nodejs/tasks/yarn.yml +++ b/nodejs/tasks/yarn.yml @@ -1,31 +1,9 @@ --- -- name: Look for legacy apt keyring - stat: - path: /etc/apt/trusted.gpg - register: _trusted_gpg_keyring - tags: - - system - - packages - - nodejs - - yarn - -- name: Yarn embedded GPG key is absent - apt_key: - id: "86E50310" - keyring: /etc/apt/trusted.gpg - state: absent - when: _trusted_gpg_keyring.stat.exists - tags: - - system - - packages - - nodejs - - yarn - - name: Yarn GPG key is installed - copy: + ansible.builtin.copy: src: yarn.asc - dest: /etc/apt/trusted.gpg.d/yarn.asc + dest: "{{ apt_keyring_dir }}/yarn.asc" mode: "0644" owner: root group: root @@ -35,20 +13,39 @@ - nodejs - yarn -- name: Yarn sources list is available - apt_repository: - repo: "deb https://dl.yarnpkg.com/debian/ stable main" +- name: Add Yarn repository (Debian <12) + ansible.builtin.apt_repository: + repo: "deb [signed-by={{ apt_keyring_dir }}/yarn.asc] https://dl.yarnpkg.com/debian/ stable main" filename: yarn - update_cache: yes state: present tags: - system - packages - nodejs - yarn + when: ansible_distribution_major_version is version('12', '<') + +- name: Add Yarn repository (Debian >=12) + ansible.builtin.template: + src: yarn.sources.j2 + dest: /etc/apt/sources.list.d/yarn.sources + state: present + update_cache: yes + register: yarn_sources + tags: + - system + - packages + - nodejs + - yarn + when: ansible_distribution_major_version is version('12', '>=') + +- name: Update APT cache + ansible.builtin.apt: + update_cache: yes + when: yarn_sources is changed - name: Yarn is installed - apt: + ansible.builtin.apt: name: yarn state: present tags: diff --git a/nodejs/templates/nodesource.sources.j2 b/nodejs/templates/nodesource.sources.j2 new file mode 100644 index 00000000..02a4653a --- /dev/null +++ b/nodejs/templates/nodesource.sources.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +Types: deb +URIs: https://deb.nodesource.com/{{ nodejs_apt_version }} +Suites: {{ ansible_distribution_release }} +Components: main +Signed-by: {{ apt_keyring_dir }}/nodesource.asc +Enabled: yes \ No newline at end of file diff --git a/nodejs/templates/yarn.sources.j2 b/nodejs/templates/yarn.sources.j2 new file mode 100644 index 00000000..cd98bc13 --- /dev/null +++ b/nodejs/templates/yarn.sources.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +Types: deb +URIs: https://dl.yarnpkg.com/debian/ +Suites: stable +Components: main +Signed-by: {{ apt_keyring_dir }}/yarn.asc +Enabled: yes diff --git a/ntpd/handlers/main.yml b/ntpd/handlers/main.yml index 333d30de..70b41926 100644 --- a/ntpd/handlers/main.yml +++ b/ntpd/handlers/main.yml @@ -1,5 +1,5 @@ --- - name: restart ntp - service: + ansible.builtin.service: name: ntp state: restarted diff --git a/ntpd/tasks/main.yml b/ntpd/tasks/main.yml index 2d66d765..ac5f8288 100644 --- a/ntpd/tasks/main.yml +++ b/ntpd/tasks/main.yml @@ -1,20 +1,20 @@ --- - name: Remove openntpd package - apt: + ansible.builtin.apt: name: openntpd state: absent tags: - ntp - name: Install ntp package - apt: + ansible.builtin.apt: name: ntp state: present tags: - ntp - name: Copy ntp config - template: + ansible.builtin.template: src: ntp.conf.j2 dest: /etc/ntp.conf mode: "0644" 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/opendkim/files/opendkim.conf b/opendkim/files/opendkim-evolix.conf similarity index 100% rename from opendkim/files/opendkim.conf rename to opendkim/files/opendkim-evolix.conf diff --git a/opendkim/handlers/main.yml b/opendkim/handlers/main.yml index ccf166a8..3cc7b05f 100644 --- a/opendkim/handlers/main.yml +++ b/opendkim/handlers/main.yml @@ -1,10 +1,10 @@ --- - name: reload opendkim - systemd: + ansible.builtin.systemd: name: opendkim state: reloaded - name: restart opendkim - systemd: + ansible.builtin.systemd: name: opendkim state: restarted diff --git a/opendkim/tasks/main.yml b/opendkim/tasks/main.yml index 94aa3dfd..a9c1bf49 100644 --- a/opendkim/tasks/main.yml +++ b/opendkim/tasks/main.yml @@ -1,6 +1,6 @@ --- - name: install OpenDKIM - apt: + ansible.builtin.apt: name: - opendkim - opendkim-tools @@ -11,7 +11,7 @@ - opendkim - name: Add user opendkim in ssl-cert group - user: + ansible.builtin.user: name: opendkim groups: ssl-cert state: present @@ -20,7 +20,7 @@ - opendkim - name: add 127.0.0.1 to TrustedHosts - lineinfile: + ansible.builtin.lineinfile: dest: '/etc/opendkim/TrustedHosts' line: '127.0.0.1' create: True @@ -32,7 +32,7 @@ - opendkim - name: create config files - file: + ansible.builtin.file: name: "/etc/opendkim/{{ item }}" state: touch owner: opendkim @@ -45,40 +45,50 @@ tags: - opendkim -- name: copy OpenDKIM config - copy: - src: opendkim.conf +- name: Add Include in opendkim.conf + ansible.builtin.lineinfile: dest: /etc/opendkim.conf + line: 'Include /etc/opendkim-evolix.conf' + state: present + create: no mode: "0644" - force: yes + tags: + - opendkim + +- name: copy OpenDKIM config + ansible.builtin.copy: + src: opendkim-evolix.conf + dest: /etc/opendkim-evolix.conf + mode: "0644" + force: true notify: restart opendkim tags: - opendkim - name: Set folder permissions to 0750 - file: + ansible.builtin.file: path: "/etc/opendkim/" owner: opendkim group: opendkim mode: "0750" - force: yes + force: true tags: - opendkim - name: ensure opendkim is started and enabled - systemd: + ansible.builtin.systemd: name: opendkim state: started enabled: True tags: - opendkim -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - name: deploy opendkim-add.sh script - copy: + ansible.builtin.copy: src: opendkim-add.sh dest: /usr/share/scripts/opendkim-add.sh mode: "0750" diff --git a/openvpn/README.md b/openvpn/README.md index 27b507d4..79ed6246 100644 --- a/openvpn/README.md +++ b/openvpn/README.md @@ -5,17 +5,27 @@ Install and configure OpenVPN, based on [our HowtoOpenVPN wiki](https://wiki.evo ## Tasks Everything is in the `tasks/main.yml` file. -Some manual actions are requested at the end of the playbook, to do before finishing the playbook. -Here is a copy of what is requested : +Here is what this role does : -* You have to manually create the CA on the server with `shellpki init server.example.com`. The command will ask you to create a password, and will ask you again to give the same one several times. -* You have to manually generate the CRL on the server with `openssl ca -gencrl -keyfile /etc/shellpki/cakey.key -cert /etc/shellpki/cacert.pem -out /etc/shellpki/crl.pem -config /etc/shellpki/openssl.cnf`. The previously created password will be asked. -* You have to manually create the server's certificate with `shellpki create server.example.com`. -* You have to adjust the config file `/etc/openvpn/server.conf` for the following parameters : `local` (to check), `cert` (to check), `key` (to add), `server` (to check), `push` (to complete if needed). -* Finally, you can (re)start the OpenVPN service with `systemctl restart openvpn@server.service` on Debian, or `rcctl restart openvpn` on OpenBSD. +* Installs and configures OpenVPN +* Installs and configures shellpki +* Authorizes users in shellpki group to use shellpki with sudo +* Configures NAT if minifirewall exists, for Debian only +* Allows connexion to UDP/1194 port publicly in minifirewall if it exists or in PacketFilter for OpenBSD +* Enables IPv4 forwarding with sysctl +* Configures NRPE to check OpenVPN +* Adds a cron to warn about certificates expiration +* Inits the CA and create the server's certificate -Then, you can use `shellpki` to generate client certificates. +NAT allows servers reached through OpenVPN to be reached by the public IP of the OpenVPN server. The public IP of the OpenVPN server must therefore be allowed on the end servers. + +Some manual actions are requested at the end of the playbook, to do before finishing the playbook : + +* You must check and adjust if necessary the configuration file "/etc/openvpn/server.conf", and then restart the OpenVPN service with "rcctl restart openvpn". +* You must take note of the generated CA password and store it in your password manager. + +Finally, you can use `shellpki` to generate client certificates. ## Variables @@ -23,6 +33,8 @@ 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 +By default, if the server IP is 192.0.2.42, then OpenVPN LAN will be 10.2.42.0/24 (last 2 digit of main IP of server set as 2nd and 3rd digit of OpenVPN LAN). -* See TODO tasks in tasks/*.yml +## Dependencies + +* 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/logrotate_openvpn b/openvpn/files/logrotate_openvpn new file mode 100644 index 00000000..e240faf6 --- /dev/null +++ b/openvpn/files/logrotate_openvpn @@ -0,0 +1,10 @@ +/var/log/openvpn.log +{ + weekly + rotate 52 + missingok + notifempty + delaycompress + compress + copytruncate +} diff --git a/openvpn/files/shellpki/cert-expirations.sh b/openvpn/files/shellpki/cert-expirations.sh new file mode 100644 index 00000000..b0cfc09a --- /dev/null +++ b/openvpn/files/shellpki/cert-expirations.sh @@ -0,0 +1,126 @@ +#!/bin/sh + +VERSION="22.12.1" + +show_version() { + cat <, + Jérémy Lecour , + Jérémy Dubois + and others. + +cert-expirations.sh 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 </dev/null | grep 'status' | cut -d' ' -f2) + + if [ "$carp" = "backup" ]; then + exit 0 + fi + fi +} + +check_ca_expiration() { + echo "CA certificate:" + openssl x509 -enddate -noout -in ${cacert_path} \ + | cut -d '=' -f 2 \ + | sed -e "s/^\(.*\)\ \(20..\).*/- \2 \1/" +} + +check_certs_expiration() { + # Syntax "cmd | { while read line; do var="foo"; done echo $var }" needed, otherwise $var is empty at the end of while loop + grep ^V ${index_path} \ + | 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 | { + while read -r line; do + + # Predicting expirations - OpenBSD case (date is not the same than in Linux) + if [ "${SYSTEM}" = "openbsd" ]; then + # Already expired if expiration date is before now + if [ "$(TZ=:Zulu date -jf "%Y %b %d %H:%M:%S" "$(echo "$line" | awk '{print $2,$3,$4,$5}')" +%s)" -le "$(date +%s)" ]; then + expired_certs="${expired_certs}$line\n" + # Expiring soon if expiration date is after now and before now + $somedays days + elif [ "$(TZ=:Zulu date -jf "%Y %b %d %H:%M:%S" "$(echo "$line" | awk '{print $2,$3,$4,$5}')" +%s)" -gt "$(date +%s)" ] && [ "$(TZ=:Zulu date -jf "%Y %b %d %H:%M:%S" "$(echo "$line" | awk '{print $2,$3,$4,$5}')" +%s)" -lt "$(($(date +%s) + somedays))" ]; then + expiring_soon_certs="${expiring_soon_certs}$line\n" + # Still valid for a time if expiration date is after now + $somedays days + elif [ "$(TZ=:Zulu date -jf "%Y %b %d %H:%M:%S" "$(echo "$line" | awk '{print $2,$3,$4,$5}')" +%s)" -ge "$(($(date +%s) + somedays))" ]; then + still_valid_certs="${still_valid_certs}$line\n" + fi + # Non OpenBSD cases + else + # Already expired if expiration date is before now + if [ "$(TZ=:Zulu date -d "$(echo "$line" | awk '{print $3,$4,$2,$5}')" +%s)" -le "$(date +%s)" ]; then + expired_certs="${expired_certs}$line\n" + # Expiring soon if expiration date is after now and before now + $somedays days + elif [ "$(TZ=:Zulu date -d "$(echo "$line" | awk '{print $3,$4,$2,$5}')" +%s)" -gt "$(date +%s)" ] && [ "$(TZ=:Zulu date -d "$(echo "$line" | awk '{print $3,$4,$2,$5}')" +%s)" -lt "$(($(date +%s) + somedays))" ]; then + expiring_soon_certs="${expiring_soon_certs}$line\n" + # Still valid for a time if expiration date is after now + $somedays days + elif [ "$(TZ=:Zulu date -d "$(echo "$line" | awk '{print $3,$4,$2,$5}')" +%s)" -ge "$(($(date +%s) + somedays))" ]; then + still_valid_certs="${still_valid_certs}$line\n" + fi + fi + done + + echo "Expired client certificates:" + echo "${expired_certs}" + echo "Valid client certificates expiring soon (in less than $((somedays / 60 / 60 / 24)) days):" + echo "${expiring_soon_certs}" + echo "Valid client certificates expiring later (in more than $((somedays / 60 / 60 / 24)) days):" + echo "${still_valid_certs}" + } +} + +main() { + SYSTEM=$(uname | tr '[:upper:]' '[:lower:]') + cacert_path="/etc/shellpki/cacert.pem" + index_path="/etc/shellpki/index.txt" + somedays="3456000" # 40 days currently + expired_certs="" + expiring_soon_certs="" + still_valid_certs="" + + case "$1" in + version|--version) + show_version + exit 0 + ;; + + help|--help) + show_usage + exit 0 + ;; + + "") + check_carp_state + echo "Warning : all times are in UTC !" + echo "" + check_ca_expiration + echo "" + check_certs_expiration + ;; + + *) + show_usage >&2 + exit 1 + ;; + esac +} + +main "$@" diff --git a/openvpn/files/shellpki/openssl.cnf b/openvpn/files/shellpki/openssl.cnf new file mode 100644 index 00000000..48ab9bd5 --- /dev/null +++ b/openvpn/files/shellpki/openssl.cnf @@ -0,0 +1,60 @@ +# VERSION="22.12.2" + +[ 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= 730 +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..ac1d263d --- /dev/null +++ b/openvpn/files/shellpki/shellpki @@ -0,0 +1,1113 @@ +#!/bin/sh +# +# shellpki is a wrapper around OpenSSL to manage a small PKI +# + +set -u + +VERSION="22.12.2" + +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}" pkey \ + -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 0751 "${CA_DIR}" + [ -d "${CRT_DIR}" ] || mkdir -m 0750 "${CRT_DIR}" + [ -f "${INDEX_FILE}" ] || touch "${INDEX_FILE}" + [ -f "${INDEX_FILE}.attr" ] || touch "${INDEX_FILE}.attr" + [ -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="-pass 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}" genpkey \ + -algorithm RSA \ + -out "${CA_KEY}" \ + ${passout_arg} \ + -aes256 \ + -pkeyopt "rsa_keygen_bits:${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}" genpkey \ + -algorithm RSA \ + -out "${OCSP_KEY}" \ + -pkeyopt "rsa_keygen_bits:${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 -pass file:${password_file}" + elif [ -n "${PASSWORD:-}" ]; then + pass_args="-aes256 -pass pass:${PASSWORD}" + fi + "${OPENSSL_BIN}" genpkey \ + -algorithm RSA \ + -out "${key_file}" \ + ${pass_args} \ + -pkeyopt "rsa_keygen_bits:${KEY_LENGTH}" \ + >/dev/null 2>&1 + # shellcheck disable=SC2181 + if [ "$?" -eq 0 ]; then + chmod 600 "${key_file}" + 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 "${CRT_DIR}" "${KEY_DIR}" "${CSR_DIR}" "${PKCS12_DIR}" "${OVPN_DIR}" "${TMP_DIR}" + chmod 600 "${INDEX_FILE}"* "${SERIAL}"* "${CA_KEY}" + chmod 640 "${CA_CERT}" + chmod 604 "${CRL}" + chmod 751 "${CA_DIR}" +} + +main "$@" diff --git a/openvpn/handlers/main.yml b/openvpn/handlers/main.yml index 44b0de93..cc74ea52 100644 --- a/openvpn/handlers/main.yml +++ b/openvpn/handlers/main.yml @@ -1,14 +1,15 @@ --- - name: restart nagios-nrpe-server - service: + ansible.builtin.service: name: nagios-nrpe-server state: restarted - name: restart nrpe - service: + ansible.builtin.service: name: nrpe state: restarted - name: reload packetfilter - command: pfctl -f /etc/pf.conf + ansible.builtin.command: + cmd: pfctl -f /etc/pf.conf diff --git a/openvpn/tasks/debian.yml b/openvpn/tasks/debian.yml index f94e19b6..173299b4 100644 --- a/openvpn/tasks/debian.yml +++ b/openvpn/tasks/debian.yml @@ -1,24 +1,27 @@ --- - name: Install OpenVPN - apt: + ansible.builtin.apt: name: openvpn - name: Delete unwanted OpenVPN folders - file: + ansible.builtin.file: state: absent dest: "/etc/openvpn/{{ item }}" with_items: - client - server -- name: Clone shellpki repo - git: - repo: "https://gitea.evolix.org/evolix/shellpki.git" - dest: /root/shellpki +- name: Create the _openvpn user + ansible.builtin.user: + name: _openvpn + system: yes + create_home: no + home: "/nonexistent" + shell: "/usr/sbin/nologin" - name: Create the shellpki user - user: + ansible.builtin.user: name: shellpki system: yes create_home: no @@ -26,45 +29,29 @@ shell: "/usr/sbin/nologin" - name: Create /etc/shellpki - file: + ansible.builtin.file: dest: "/etc/shellpki" mode: "0755" owner: shellpki group: shellpki state: directory -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - name: Copy shellpki files - copy: - src: "{{ item.source }}" + ansible.builtin.copy: + 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: "0750", owner: "root", group: "root" } - name: Add sudo rights - lineinfile: + ansible.builtin.lineinfile: dest: "/etc/sudoers.d/shellpki" regexp: '/usr/local/sbin/shellpki' line: "%shellpki ALL = (root) /usr/local/sbin/shellpki" @@ -75,7 +62,7 @@ validate: 'visudo -cf %s' - name: Deploy OpenVPN client config template - template: + ansible.builtin.template: src: "ovpn.conf.j2" dest: "/etc/shellpki/ovpn.conf" mode: "0600" @@ -83,23 +70,15 @@ group: shellpki - name: Generate dhparam - command: "openssl dhparam -out /etc/shellpki/dh2048.pem 2048" + community.crypto.openssl_dhparam: + path: /etc/shellpki/dh2048.pem + size: 2048 -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr -- name: Fix CRL rights in shellpki command - lineinfile: - dest: "/usr/local/sbin/shellpki" - regexp: '{{ item.regexp }}' - insertafter: "{{ item.insertafter }}" - line: "{{ item.line }}" - with_items: - - { regexp: '^ chmod 644 /etc/shellpki/crl.pem$', line: " chmod 644 /etc/shellpki/crl.pem", insertafter: '^ chmod 640 "\${CACERT}"$' } - - { regexp: '^ chmod 755 /etc/shellpki/$', line: " chmod 755 /etc/shellpki/", insertafter: '^ chmod 644 /etc/shellpki/crl.pem$' } - - name: Deploy OpenVPN server config - template: + ansible.builtin.template: src: "server.conf.j2" dest: "/etc/openvpn/server.conf" mode: "0600" @@ -107,21 +86,22 @@ group: root - name: Is minifirewall installed ? - stat: + ansible.builtin.stat: path: "/etc/default/minifirewall" check_mode: no - changed_when: false + changed_when: False register: minifirewall_config - name: Retrieve the default interface - shell: "grep '^INT=' /etc/default/minifirewall | cut -d\\' -f 2" + ansible.builtin.shell: + cmd: "grep '^INT=' /etc/default/minifirewall | cut -d\\' -f 2" check_mode: no - changed_when: false + changed_when: False register: minifirewall_int when: minifirewall_config.stat.exists - name: Add minifirewall rule in config file - lineinfile: + ansible.builtin.lineinfile: dest: "/etc/default/minifirewall" line: "{{ item }}" with_items: @@ -130,7 +110,7 @@ when: minifirewall_config.stat.exists - name: Activate minifirewall rule - iptables: + ansible.builtin.iptables: table: nat chain: POSTROUTING source: "{{ openvpn_lan }}/{{ openvpn_netmask_cidr }}" @@ -139,7 +119,7 @@ when: minifirewall_config.stat.exists - name: Add 1194/udp OpenVPN port to public services in minifirewall - replace: + ansible.builtin.replace: dest: "/etc/default/minifirewall" regexp: "^SERVICESUDP1='(.*)?'$" replace: "SERVICESUDP1='\\1 1194'" @@ -147,7 +127,7 @@ when: minifirewall_config.stat.exists - name: Activate minifirewall rule for IPv4 - iptables: + ansible.builtin.iptables: chain: INPUT protocol: udp destination_port: "1194" @@ -156,7 +136,7 @@ when: minifirewall_config.stat.exists - name: Activate minifirewall rule for IPv6 - iptables: + ansible.builtin.iptables: chain: INPUT protocol: udp destination_port: "1194" @@ -165,17 +145,23 @@ when: minifirewall_config.stat.exists - name: Enable forwarding - sysctl: + ansible.posix.sysctl: name: net.ipv4.ip_forward value: "1" sysctl_file: "/etc/sysctl.d/openvpn.conf" +- name: Configure logrotate for OpenVPN + ansible.builtin.copy: + src: logrotate_openvpn + dest: /etc/logrotate.d/openvpn + force: false + - name: Generate a password for the management interface - set_fact: + ansible.builtin.set_fact: management_pwd: "{{ lookup('password', '/dev/null length=15 chars=ascii_letters,digits') }}" - name: Set the management password - copy: + ansible.builtin.copy: dest: "/etc/openvpn/management-pwd" content: "{{ management_pwd }}" mode: "0600" @@ -183,27 +169,27 @@ group: root - name: Enable openvpn service - systemd: + ansible.builtin.systemd: name: "openvpn@server.service" enabled: yes - name: Is NRPE installed ? - stat: + ansible.builtin.stat: path: "/etc/nagios/nrpe.d/evolix.cfg" check_mode: no - changed_when: false + changed_when: False register: nrpe_evolix_config - name: Install NRPE check dependencies - apt: + ansible.builtin.apt: name: libnet-telnet-perl when: nrpe_evolix_config.stat.exists -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - name: Install OpenVPN NRPE check - copy: + ansible.builtin.copy: src: "files/check_openvpn_debian.pl" dest: "/usr/local/lib/nagios/plugins/check_openvpn" mode: "0755" @@ -212,18 +198,18 @@ when: nrpe_evolix_config.stat.exists - name: Configure NRPE OpenVPN check - lineinfile: + ansible.builtin.lineinfile: dest: "/etc/nagios/nrpe.d/evolix.cfg" regexp: '^command\[check_openvpn\]=' line: "command[check_openvpn]=/usr/local/lib/nagios/plugins/check_openvpn -H 127.0.0.1 -p 1195 -P {{ management_pwd }}" notify: restart nagios-nrpe-server when: nrpe_evolix_config.stat.exists -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - name: Install OpenVPN certificates NRPE check - copy: + ansible.builtin.copy: src: "files/check_openvpn_certificates.sh" dest: "/usr/local/lib/nagios/plugins/check_openvpn_certificates.sh" mode: "0755" @@ -232,7 +218,7 @@ when: nrpe_evolix_config.stat.exists - name: Add sudo rights for NRPE check - lineinfile: + ansible.builtin.lineinfile: dest: "/etc/sudoers.d/openvpn" regexp: 'check_openvpn_certificates.sh' line: "nagios ALL=NOPASSWD: /usr/local/lib/nagios/plugins/check_openvpn_certificates.sh" @@ -244,53 +230,70 @@ when: nrpe_evolix_config.stat.exists - name: Configure NRPE certificates check - lineinfile: + ansible.builtin.lineinfile: dest: "/etc/nagios/nrpe.d/evolix.cfg" regexp: '^command\[check_openvpn_certificates\]=' line: "command[check_openvpn_certificates]=sudo /usr/local/lib/nagios/plugins/check_openvpn_certificates.sh" 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: +- ansible.builtin.include_role: name: evolix/remount-usr -- name: Copy shellpki script - copy: - src: "/root/shellpki-dev/cert-expirations.sh" +- name: Copy script to check expirations + ansible.builtin.copy: + 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: + ansible.builtin.cron: name: "OpenVPN certificates expiration" special_time: monthly - job: '/usr/share/scripts/cert-expirations.sh | mail -E -s "PKI VPN {{ ansible_hostname }} : recapitulatif expirations" {{ client_email }}' + job: '/usr/share/scripts/cert-expirations.sh | mail -E -s "PKI OpenVPN {{ ansible_hostname }} : recapitulatif expirations" {{ client_email }}' -- name: Warn the user about command to execute manually - pause: +- name: Generate the CA password + ansible.builtin.set_fact: + ca_pwd: "{{ lookup('password', '/dev/null length=25 chars=ascii_letters,digits') }}" + check_mode: no + changed_when: no + +- name: Initialization of the CA + ansible.builtin.shell: + cmd: 'CA_PASSWORD="{{ ca_pwd }}" shellpki init --non-interactive {{ ansible_fqdn }}' + +- name: Creation of the server's certificate + ansible.builtin.shell: + cmd: 'CA_PASSWORD="{{ ca_pwd }}" shellpki create --days 3650 --non-interactive {{ ansible_fqdn }}' + +- name: Get the server key + ansible.builtin.shell: + cmd: 'ls -tr /etc/shellpki/private/ | tail -1' + register: ca_key + check_mode: no + changed_when: no + +- name: Configure the server key + ansible.builtin.replace: + path: /etc/openvpn/server.conf + regexp: 'key /etc/shellpki/private/TO_COMPLETE' + replace: 'key /etc/shellpki/private/{{ ca_key.stdout }}' + +- name: Restart OpenVPN + ansible.builtin.systemd: + name: "openvpn@server.service" + state: restarted + +- name: Warn the user about manual checks + ansible.builtin.pause: prompt: | /!\ WARNING /!\ - You have to manually create the CA on the server with "shellpki init {{ ansible_fqdn }}". The command will ask you to create a password, and will ask you again to give the same one several times. - You have to manually generate the CRL on the server with "openssl ca -gencrl -keyfile /etc/shellpki/cakey.key -cert /etc/shellpki/cacert.pem -out /etc/shellpki/crl.pem -config /etc/shellpki/openssl.cnf". The previously created password will be asked. - You have to manually create the server's certificate with "shellpki create {{ ansible_fqdn }}". - You have to adjust the config file "/etc/openvpn/server.conf" for the following parameters : local (to check), cert (to check), key (to add), server (to check), push (to complete if needed). - Finally, you can (re)start the OpenVPN service with "systemctl restart openvpn@server.service". - + You must check and adjust if necessary the configuration file "/etc/openvpn/server.conf", and then restart the OpenVPN service with "systemctl restart openvpn@server.service". + The "push" parameter may be needed to push a route to the client, so that the client can access that route through OpenVPN. + + Take note of the generated CA password and store it in your password manager : {{ ca_pwd }} + Press enter to exit when it's done. diff --git a/openvpn/tasks/main.yml b/openvpn/tasks/main.yml index 1e20772a..26a04ee7 100644 --- a/openvpn/tasks/main.yml +++ b/openvpn/tasks/main.yml @@ -1,15 +1,15 @@ --- - name: System compatibility checks - assert: + ansible.builtin.assert: that: "ansible_distribution == 'Debian' or ansible_distribution == 'OpenBSD'" msg: "Only compatible with Debian and OpenBSD" - name: Include Debian version - include: debian.yml + ansible.builtin.include: debian.yml when: ansible_distribution == "Debian" - name: Include OpenBSD version - include: openbsd.yml + ansible.builtin.include: openbsd.yml when: ansible_distribution == "OpenBSD" diff --git a/openvpn/tasks/openbsd.yml b/openvpn/tasks/openbsd.yml index 2edbec70..28781880 100644 --- a/openvpn/tasks/openbsd.yml +++ b/openvpn/tasks/openbsd.yml @@ -1,25 +1,20 @@ --- - name: Install OpenVPN - openbsd_pkg: + community.general.openbsd_pkg: name: openvpn-- when: ansible_distribution == 'OpenBSD' - name: Create /etc/openvpn - file: + ansible.builtin.file: dest: "/etc/openvpn" state: directory owner: root 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: + ansible.builtin.user: name: _shellpki system: yes create_home: no @@ -27,7 +22,7 @@ shell: "/sbin/nologin" - name: Create /etc/shellpki - file: + ansible.builtin.file: dest: "/etc/shellpki" state: directory owner: _shellpki @@ -35,38 +30,25 @@ mode: "0755" - name: Copy shellpki files - copy: - src: "{{ item.source }}" + ansible.builtin.copy: + 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: "0750", owner: "root", group: "wheel" } - name: Add sudo rights - lineinfile: + ansible.builtin.lineinfile: dest: "/etc/sudoers" regexp: '/usr/local/sbin/shellpki' line: "%_shellpki ALL = (root) /usr/local/sbin/shellpki" validate: 'visudo -cf %s' - name: Deploy OpenVPN client config template - template: + ansible.builtin.template: src: "ovpn.conf.j2" dest: "/etc/shellpki/ovpn.conf" mode: "0640" @@ -74,20 +56,12 @@ group: _shellpki - name: Generate dhparam - command: "openssl dhparam -out /etc/shellpki/dh2048.pem 2048" - -- name: Fix CRL rights in shellpki command - lineinfile: - dest: "/usr/local/sbin/shellpki" - regexp: '{{ item.regexp }}' - insertafter: "{{ item.insertafter }}" - line: "{{ item.line }}" - with_items: - - { regexp: '^ chmod 644 /etc/shellpki/crl.pem$', line: " chmod 644 /etc/shellpki/crl.pem", insertafter: '^ chmod 640 "\${CACERT}"$' } - - { regexp: '^ chmod 755 /etc/shellpki/$', line: " chmod 755 /etc/shellpki/", insertafter: '^ chmod 644 /etc/shellpki/crl.pem$' } + community.crypto.openssl_dhparam: + path: /etc/shellpki/dh2048.pem + size: 2048 - name: Deploy OpenVPN server config - template: + ansible.builtin.template: src: "server.conf.j2" dest: "/etc/openvpn/server.conf" mode: "0600" @@ -95,7 +69,7 @@ group: wheel - name: Configure PacketFilter - lineinfile: + ansible.builtin.lineinfile: dest: "/etc/pf.conf" line: "{{ item }}" validate: 'pfctl -nf %s' @@ -105,7 +79,7 @@ - "pass in quick on $ext_if proto udp from any to self port 1194" - name: Create a cron to rotate the logs - cron: + ansible.builtin.cron: name: "OpenVPN logs rotation" weekday: "6" hour: "4" @@ -113,11 +87,11 @@ job: "cp /var/log/openvpn.log /var/log/openvpn.log.$(date +\\%F) && echo \"$(date +\\%F' '\\%R) - logfile turned over via cron\" > /var/log/openvpn.log && gzip /var/log/openvpn.log.$(date +\\%F) && find /var/log/ -type f -name \"openvpn.log.*\" -mtime +365 -exec rm {} \\+" - name: Generate a password for the management interface - set_fact: + ansible.builtin.set_fact: management_pwd: "{{ lookup('password', '/dev/null length=15 chars=ascii_letters,digits') }}" - name: Set the management password - copy: + ansible.builtin.copy: dest: "/etc/openvpn/management-pwd" content: "{{ management_pwd }}" mode: "0600" @@ -125,30 +99,30 @@ group: wheel - name: Enable openvpn service - service: + ansible.builtin.service: name: openvpn enabled: yes - name: Set openvpn flags - lineinfile: + ansible.builtin.lineinfile: dest: /etc/rc.conf.local regexp: "^openvpn_flags=" line: "openvpn_flags=--daemon --config /etc/openvpn/server.conf" create: yes - name: Is NRPE installed ? - stat: + ansible.builtin.stat: path: "/etc/nrpe.d/evolix.cfg" check_mode: no register: nrpe_evolix_config - name: Install NRPE check dependencies - openbsd_pkg: + community.general.openbsd_pkg: name: p5-Net-Telnet when: nrpe_evolix_config.stat.exists - name: Install OpenVPN NRPE check - copy: + ansible.builtin.copy: src: "files/check_openvpn_openbsd.pl" dest: "/usr/local/libexec/nagios/plugins/check_openvpn.pl" mode: "0755" @@ -157,8 +131,8 @@ when: nrpe_evolix_config.stat.exists - name: Configure NRPE OpenVPN check - lineinfile: - dest: "/etc/nrpe.d/zzz_evolix.cfg" + ansible.builtin.lineinfile: + dest: "/etc/nrpe.d/evolix.cfg" regexp: '^command\[check_openvpn\]=' line: "command[check_openvpn]=/usr/local/libexec/nagios/plugins/check_openvpn.pl -H 127.0.0.1 -p 1195 -P {{ management_pwd }}" create: yes @@ -169,7 +143,7 @@ when: nrpe_evolix_config.stat.exists - name: Install OpenVPN certificates NRPE check - copy: + ansible.builtin.copy: src: "files/check_openvpn_certificates.sh" dest: "/usr/local/libexec/nagios/plugins/check_openvpn_certificates.sh" mode: "0755" @@ -178,7 +152,7 @@ when: nrpe_evolix_config.stat.exists - name: Add doas rights for NRPE check - lineinfile: + ansible.builtin.lineinfile: dest: "/etc/doas.conf" regexp: 'check_openvpn_certificates.sh' line: "permit nopass _nrpe as root cmd /usr/local/libexec/nagios/plugins/check_openvpn_certificates.sh" @@ -186,50 +160,67 @@ when: nrpe_evolix_config.stat.exists - name: Configure NRPE certificates check - lineinfile: + ansible.builtin.lineinfile: dest: "/etc/nrpe.d/evolix.cfg" regexp: '^command\[check_openvpn_certificates\]=' line: "command[check_openvpn_certificates]=doas /usr/local/libexec/nagios/plugins/check_openvpn_certificates.sh" 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" +- name: Copy script to check expirations + ansible.builtin.copy: + 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: + ansible.builtin.cron: name: "OpenVPN certificates expiration" special_time: monthly - job: '/usr/share/scripts/cert-expirations.sh | mail -E -s "PKI VPN {{ ansible_hostname }} : recapitulatif expirations" {{ client_email }}' + job: '/usr/share/scripts/cert-expirations.sh | mail -E -s "PKI OpenVPN {{ ansible_hostname }} : recapitulatif expirations" {{ client_email }}' -- name: Warn the user about command to execute manually - pause: +- name: Generate the CA password + ansible.builtin.set_fact: + ca_pwd: "{{ lookup('password', '/dev/null length=25 chars=ascii_letters,digits') }}" + check_mode: no + changed_when: no + +- name: Initialization of the CA + ansible.builtin.shell: + cmd: 'CA_PASSWORD="{{ ca_pwd }}" shellpki init --non-interactive {{ ansible_fqdn }}' + +- name: Creation of the server's certificate + ansible.builtin.shell: + cmd: 'CA_PASSWORD="{{ ca_pwd }}" shellpki create --days 3650 --non-interactive {{ ansible_fqdn }}' + +- name: Get the server key + ansible.builtin.shell: + cmd: 'ls -tr /etc/shellpki/private/ | tail -1' + register: ca_key + check_mode: no + changed_when: no + +- name: Configure the server key + ansible.builtin.replace: + path: /etc/openvpn/server.conf + regexp: 'key /etc/shellpki/private/TO_COMPLETE' + replace: 'key /etc/shellpki/private/{{ ca_key.stdout }}' + +- name: Restart OpenVPN + ansible.builtin.service: + name: openvpn + state: restarted + +- name: Warn the user about manual checks + ansible.builtin.pause: prompt: | /!\ WARNING /!\ - You have to manually create the CA on the server with "shellpki init {{ ansible_fqdn }}". The command will ask you to create a password, and will ask you again to give the same one several times. - You have to manually generate the CRL on the server with "openssl ca -gencrl -keyfile /etc/shellpki/cakey.key -cert /etc/shellpki/cacert.pem -out /etc/shellpki/crl.pem -config /etc/shellpki/openssl.cnf". The previously created password will be asked. - You have to manually create the server's certificate with "shellpki create {{ ansible_fqdn }}". - You have to adjust the config file "/etc/openvpn/server.conf" for the following parameters : local (to check), cert (to check), key (to add), server (to check), push (to complete if needed). - Finally, you can (re)start the OpenVPN service with "rcctl restart openvpn". - + You must check and adjust if necessary the configuration file "/etc/openvpn/server.conf", and then restart the OpenVPN service with "rcctl restart openvpn". + The "push" parameter may be needed to push a route to the client, so that the client can access that route through OpenVPN. + + Take note of the generated CA password and store it in your password manager : {{ ca_pwd }} + Press enter to exit when it's done. diff --git a/openvpn/templates/ovpn.conf.j2 b/openvpn/templates/ovpn.conf.j2 index d1b3c214..f65d43fd 100644 --- a/openvpn/templates/ovpn.conf.j2 +++ b/openvpn/templates/ovpn.conf.j2 @@ -9,5 +9,5 @@ nobind persist-key persist-tun -cipher AES-256-CBC +cipher AES-256-GCM diff --git a/openvpn/templates/server.conf.j2 b/openvpn/templates/server.conf.j2 index 23ce3e2b..a41b9b22 100644 --- a/openvpn/templates/server.conf.j2 +++ b/openvpn/templates/server.conf.j2 @@ -1,5 +1,5 @@ -user nobody -group nogroup +user _openvpn +group _openvpn local {{ ansible_default_ipv4.address }} port 1194 diff --git a/packweb-apache/files/userlogrotate b/packweb-apache/files/userlogrotate deleted file mode 100644 index 897c077b..00000000 --- a/packweb-apache/files/userlogrotate +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash - -DATE=`/bin/date +"%d-%m-%Y"` -HOMEPREFIX="/home" - -rotate () { - mv $1 $1.$DATE - gzip $1.$DATE - touch $1 - chown $2 $1 - chmod g+r $1 -} - -user_for() { - homedir=`echo $1 | sed "s#\($HOMEPREFIX/\([^/]\+\)\).*#\1#"` - stat -L -c '%G' $homedir -} - -for log in access.log access-*.log error.log; do - for i in `ls -1 -d $HOMEPREFIX/*/log/$log 2>/dev/null | grep -v \.bak\.`; do - USER=`user_for $i` - rotate $i root:$USER - done -done - -for log in production.log delayed_job.log development.log test.log; do - for i in `ls -1 -d $HOMEPREFIX/*/www/{,current/}log/$log 2>/dev/null | grep -v \.bak\.`; do - USER=`user_for $i` - rotate $i $USER:$USER - done -done - -test -x /usr/sbin/apache2ctl && if /etc/init.d/apache2 status > /dev/null ; then \ - /etc/init.d/apache2 reload > /dev/null; \ -fi; - -test -x /usr/sbin/nginx && invoke-rc.d nginx rotate >/dev/null 2>&1 - -# we want exit 0 -true diff --git a/packweb-apache/handlers/main.yml b/packweb-apache/handlers/main.yml index af4d94d2..f9170bc9 100644 --- a/packweb-apache/handlers/main.yml +++ b/packweb-apache/handlers/main.yml @@ -1,10 +1,10 @@ --- - name: restart apache - service: + ansible.builtin.service: name: apache2 state: restarted - name: reload apache - service: + ansible.builtin.service: name: apache2 state: reloaded diff --git a/packweb-apache/meta/main.yml b/packweb-apache/meta/main.yml index bbf086ce..47d29159 100644 --- a/packweb-apache/meta/main.yml +++ b/packweb-apache/meta/main.yml @@ -26,18 +26,5 @@ galaxy_info: allow_duplicates: true -dependencies: - - { role: evolix/apache } - - { role: evolix/php, php_apache_enable: True, when: packweb_apache_modphp } - - { role: evolix/php, php_fpm_enable: True, when: packweb_apache_fpm } - - { role: evolix/squid, squid_localproxy_enable: True } - - { role: evolix/mysql, when: packweb_mysql_variant == "debian" } - - { role: evolix/mysql-oracle, when: packweb_mysql_variant == "oracle" } - - { role: evolix/lxc-php, lxc_php_version: php56, lxc_php_create_mysql_link: True, when: "'php56' in packweb_multiphp_versions" } - - { role: evolix/lxc-php, lxc_php_version: php70, lxc_php_create_mysql_link: True, when: "'php70' in packweb_multiphp_versions" } - - { role: evolix/lxc-php, lxc_php_version: php73, lxc_php_create_mysql_link: True, when: "'php73' in packweb_multiphp_versions" } - - { role: evolix/lxc-php, lxc_php_version: php74, lxc_php_create_mysql_link: True, when: "'php74' in packweb_multiphp_versions" } - - { role: evolix/lxc-php, lxc_php_version: php80, lxc_php_create_mysql_link: True, when: "'php80' in packweb_multiphp_versions" } - - { role: evolix/lxc-php, lxc_php_version: php81, lxc_php_create_mysql_link: True, when: "'php81' in packweb_multiphp_versions" } - - { role: evolix/webapps/evoadmin-web, evoadmin_enable_vhost: "{{ packweb_enable_evoadmin_vhost }}", evoadmin_multiphp_versions: "{{ packweb_multiphp_versions }}" } - - { role: evolix/evoacme } +dependencies: [] + diff --git a/packweb-apache/tasks/apache.yml b/packweb-apache/tasks/apache.yml index 57b360ce..b7e05f71 100644 --- a/packweb-apache/tasks/apache.yml +++ b/packweb-apache/tasks/apache.yml @@ -1,14 +1,15 @@ --- - name: Check if Apache envvars have a PATH - command: "grep -E '^export PATH ' /etc/apache2/envvars" + ansible.builtin.command: + cmd: "grep -E '^export PATH ' /etc/apache2/envvars" failed_when: False changed_when: False register: envvar_grep_path check_mode: no - name: Add a PATH envvar for Apache - blockinfile: + ansible.builtin.blockinfile: dest: /etc/apache2/envvars marker: "## {mark} ANSIBLE MANAGED BLOCK FOR PATH" block: | @@ -17,7 +18,7 @@ when: envvar_grep_path.rc != 0 - name: Additional packages are installed - apt: + ansible.builtin.apt: name: - libapache2-mod-security2 - modsecurity-crs @@ -25,7 +26,7 @@ state: present - name: Additional modules are enabled - apache2_module: + community.general.apache2_module: name: '{{ item }}' state: present loop: @@ -33,27 +34,29 @@ - include - negotiation - alias + - log_forensic - name: Copy Apache settings for modules - copy: + ansible.builtin.copy: src: "evolinux-modsec.conf" dest: "/etc/apache2/conf-available/evolinux-modsec.conf" owner: root group: root mode: "0644" - force: no + force: false - name: Copy Apache settings for modules - template: + ansible.builtin.template: src: "evolinux-evasive.conf.j2" dest: "/etc/apache2/conf-available/evolinux-evasive.conf" owner: root group: root mode: "0644" - force: no + force: false - name: Ensure Apache modules configs are enabled - command: "a2enconf {{ item }}" + ansible.builtin.command: + cmd: "a2enconf {{ item }}" register: command_result changed_when: "'Enabling' in command_result.stderr" loop: diff --git a/packweb-apache/tasks/awstats.yml b/packweb-apache/tasks/awstats.yml index 5ea0fa57..f6e8b309 100644 --- a/packweb-apache/tasks/awstats.yml +++ b/packweb-apache/tasks/awstats.yml @@ -1,11 +1,11 @@ --- - name: Install awstats - apt: + ansible.builtin.apt: name: awstats state: present - name: Configure awstats - blockinfile: + ansible.builtin.blockinfile: dest: /etc/awstats/awstats.conf.local marker: "## {mark} ANSIBLE MANAGED BLOCK FOR PACKWEB" block: | @@ -24,31 +24,32 @@ mode: "0644" - name: Create conf-available/awstats-icon.conf file - copy: + ansible.builtin.copy: dest: /etc/apache2/conf-available/awstats-icon.conf content: | Alias /awstats-icon/ /usr/share/awstats/icon/ Require all granted - force: no + force: false mode: "0644" - name: Enable apache awstats-icon configuration - command: "a2enconf awstats-icon" + ansible.builtin.command: + cmd: "a2enconf awstats-icon" register: command_result changed_when: "'Enabling' in command_result.stderr" notify: reload apache - name: Create awstats cron - lineinfile: + ansible.builtin.lineinfile: dest: /etc/cron.d/awstats create: yes regexp: '-config=awstats' line: "10 */6 * * * root umask 033; [ -x /usr/lib/cgi-bin/awstats.pl -a -f /etc/awstats/awstats.conf -a -r /var/log/apache2/access.log ] && /usr/lib/cgi-bin/awstats.pl -config=awstats -update >/dev/null" - name: Comment default awstat cron's tasks - lineinfile: + ansible.builtin.lineinfile: dest: /etc/cron.d/awstats regexp: "(?i)^([^#]*update\\.sh.*)" line: '#\1' diff --git a/packweb-apache/tasks/dependencies.yml b/packweb-apache/tasks/dependencies.yml new file mode 100644 index 00000000..bf29b849 --- /dev/null +++ b/packweb-apache/tasks/dependencies.yml @@ -0,0 +1,87 @@ +--- + +- ansible.builtin.include_role: + name: evolix/apache + +- ansible.builtin.include_role: + name: evolix/php + vars: + php_apache_enable: True + when: packweb_apache_modphp + +- ansible.builtin.include_role: + name: evolix/php + vars: + php_fpm_enable: True + when: packweb_apache_fpm + +- ansible.builtin.include_role: + name: evolix/squid + vars: + squid_localproxy_enable: True + +- include_role: + name: evolix/mysql + when: packweb_mysql_variant == "debian" + +- ansible.builtin.include_role: + name: evolix/mysql-oracle + when: packweb_mysql_variant == "oracle" + +- ansible.builtin.include_role: + name: evolix/lxc-php + vars: + lxc_php_version: php56 + lxc_php_create_mysql_link: True + when: "'php56' in packweb_multiphp_versions" + +- ansible.builtin.include_role: + name: evolix/lxc-php + vars: + lxc_php_version: php70 + lxc_php_create_mysql_link: True + when: "'php70' in packweb_multiphp_versions" + +- ansible.builtin.include_role: + name: evolix/lxc-php + vars: + lxc_php_version: php73 + lxc_php_create_mysql_link: True + when: "'php73' in packweb_multiphp_versions" + +- ansible.builtin.include_role: + name: evolix/lxc-php + vars: + lxc_php_version: php74 + lxc_php_create_mysql_link: True + when: "'php74' in packweb_multiphp_versions" + +- ansible.builtin.include_role: + name: evolix/lxc-php + vars: + lxc_php_version: php80 + lxc_php_create_mysql_link: True + when: "'php80' in packweb_multiphp_versions" + +- ansible.builtin.include_role: + name: evolix/lxc-php + vars: + lxc_php_version: php81 + lxc_php_create_mysql_link: True + when: "'php81' in packweb_multiphp_versions" + +- ansible.builtin.include_role: + name: evolix/lxc-php + vars: + lxc_php_version: php82 + lxc_php_create_mysql_link: True + when: "'php82' in packweb_multiphp_versions" + +- ansible.builtin.include_role: + name: evolix/webapps/evoadmin-web + vars: + evoadmin_enable_vhost: "{{ packweb_enable_evoadmin_vhost }}" + evoadmin_multiphp_versions: "{{ packweb_multiphp_versions }}" + +- include_role: + name: evolix/evoacme diff --git a/packweb-apache/tasks/fhs_retrictions.yml b/packweb-apache/tasks/fhs_retrictions.yml index 7fa41478..6cb486d6 100644 --- a/packweb-apache/tasks/fhs_retrictions.yml +++ b/packweb-apache/tasks/fhs_retrictions.yml @@ -1,7 +1,8 @@ --- - name: Remove read permission on some folders (/, /etc, ...) - shell: "test -d {{ item }} && chmod --verbose o-r {{ item }}" + ansible.builtin.shell: + cmd: "test -d {{ item }} && chmod --verbose o-r {{ item }}" register: command_result changed_when: "'changed' in command_result.stdout" failed_when: False @@ -25,7 +26,8 @@ - /etc/default - name: Set 750 permission on some folders (/var/log/apt, /var/log/munin, ...) - shell: "test -d {{ item }} && chmod --verbose 750 {{ item }}" + ansible.builtin.shell: + cmd: "test -d {{ item }} && chmod --verbose 750 {{ item }}" register: command_result changed_when: "'changed' in command_result.stdout" failed_when: False @@ -41,13 +43,14 @@ - /var/log/installer - name: Change group to www-data for /etc/phpmyadmin/ - file: + ansible.builtin.file: dest: /etc/phpmyadmin/ group: www-data state: directory - name: Set u-s permission on some binaries (/bin/ping, /usr/bin/mtr, ...) - shell: "test -f {{ item }} && chmod --verbose u-s {{ item }}" + ansible.builtin.shell: + cmd: "test -f {{ item }} && chmod --verbose u-s {{ item }}" register: command_result changed_when: "'changed' in command_result.stdout" failed_when: False @@ -59,7 +62,8 @@ - /usr/bin/mtr - name: Set 640 permission on some files (/var/log/evolix.log, ...) - shell: "test -f {{ item }} && chmod --verbose 640 {{ item }}" + ansible.builtin.shell: + cmd: "test -f {{ item }} && chmod --verbose 640 {{ item }}" register: command_result changed_when: "'changed' in command_result.stdout" failed_when: False diff --git a/packweb-apache/tasks/main.yml b/packweb-apache/tasks/main.yml index 5e2f9e92..7843a642 100644 --- a/packweb-apache/tasks/main.yml +++ b/packweb-apache/tasks/main.yml @@ -1,43 +1,46 @@ --- -- fail: +- name: Dependencies are satisfied + ansible.builtin.include_tasks: dependencies.yml + +- ansible.builtin.fail: msg: only compatible with Debian >= 8 when: - ansible_distribution != "Debian" or ansible_distribution_major_version is version('8', '<') - name: Additional packages are installed - apt: + ansible.builtin.apt: name: - zip - unzip state: present - name: install info.php - copy: + ansible.builtin.copy: src: info.php dest: /var/www/info.php mode: "0644" - name: enable info.php link in default site index - lineinfile: + ansible.builtin.lineinfile: dest: /var/www/index.html line: '
  • Infos PHP
  • ' regexp: "Infos PHP" - name: install opcache.php - copy: + ansible.builtin.copy: src: opcache.php dest: /var/www/opcache.php mode: "0644" - name: enable opcache.php link in default site index - lineinfile: + ansible.builtin.lineinfile: dest: /var/www/index.html line: '
  • Infos OpCache PHP
  • ' regexp: "Infos OpCache PHP" - name: Add elements to user account template - file: + ansible.builtin.file: path: "/etc/skel/{{ item.path }}" state: "{{ item.state }}" mode: "{{ item.mode }}" @@ -47,7 +50,8 @@ - { path: www, mode: "0750", state: directory } - name: Apache log file (templates) are present - command: "touch /etc/skel/log/{{ item }}" + ansible.builtin.command: + cmd: "touch /etc/skel/log/{{ item }}" args: creates: "/etc/skel/log/{{ item }}" loop: @@ -55,47 +59,37 @@ - error.log - name: Apache log file (templates) have the proper permissions - file: + ansible.builtin.file: dest: "/etc/skel/log/{{ item }}" mode: "0644" loop: - access.log - error.log -- name: "Install userlogrotate (jessie)" - copy: - src: userlogrotate_jessie - dest: /etc/cron.weekly/userlogrotate - mode: "0755" - when: ansible_distribution_release == "jessie" - -- name: "Install userlogrotate (Debian 9 or later)" - copy: - src: userlogrotate - dest: /etc/cron.weekly/userlogrotate - mode: "0755" - when: ansible_distribution_major_version is version('9', '>=') +- ansible.builtin.include_role: + name: userlogrotate - name: Force DIR_MODE to 0750 in /etc/adduser.conf - lineinfile: + ansible.builtin.lineinfile: dest: /etc/adduser.conf regexp: '^DIR_MODE=' line: 'DIR_MODE=0750' -- include: apache.yml +- ansible.builtin.include: apache.yml -- include: phpmyadmin.yml +- ansible.builtin.include: phpmyadmin.yml -- include: awstats.yml +- ansible.builtin.include: awstats.yml -- include: fhs_retrictions.yml +- ansible.builtin.include: fhs_retrictions.yml when: packweb_fhs_retrictions | bool - name: Periodically cache ftp directory sizes for ftpadmin.sh - cron: + ansible.builtin.cron: name: "ProFTPd directory size caching" special_time: daily job: "/usr/share/scripts/evoadmin/stats.sh" -- include: multiphp.yml +- ansible.builtin.include: multiphp.yml when: packweb_multiphp_versions | length > 0 + diff --git a/packweb-apache/tasks/multiphp.yml b/packweb-apache/tasks/multiphp.yml index 01f0b130..b6719374 100644 --- a/packweb-apache/tasks/multiphp.yml +++ b/packweb-apache/tasks/multiphp.yml @@ -1,25 +1,25 @@ --- - name: Enable proxy_fcgi - apache2_module: + community.general.apache2_module: state: present name: proxy_fcgi notify: restart apache2 -- include_role: +- ansible.builtin.include_role: name: remount-usr - name: Copy phpContainer script - copy: + ansible.builtin.copy: src: phpContainer dest: /usr/local/bin/phpContainer - mode: 0755 + mode: "0755" # - name: Copy php shim to call phpContainer when the user is a web user # copy: # src: multiphp-shim # dest: /usr/local/bin/php -# mode: 0755 +# mode: "0755" # - name: Modify bashrc skel file # lineinfile: @@ -27,7 +27,7 @@ # line: "alias php='sudo /usr/local/bin/phpContainer'" - name: Add multiphp sudoers file - copy: + ansible.builtin.copy: src: multiphp-sudoers dest: /etc/sudoers.d/multiphp mode: "0600" diff --git a/packweb-apache/tasks/phpmyadmin.yml b/packweb-apache/tasks/phpmyadmin.yml index f83b0a5d..abb1c552 100644 --- a/packweb-apache/tasks/phpmyadmin.yml +++ b/packweb-apache/tasks/phpmyadmin.yml @@ -1,46 +1,47 @@ --- - name: Install apg - apt: + ansible.builtin.apt: name: apg # On Debian 10, we need to install the package from buster-backports - name: Enable backports (Debian 10) - include_role: + ansible.builtin.include_role: name: evolix/apt tasks_from: backports.yml when: ansible_distribution_major_version is version('10', '=') - name: Prefer phpMyAdmin package from backports (Debian 10) - template: + ansible.builtin.template: src: phpmyadmin_apt_preferences.j2 dest: /etc/apt/preferences.d/999-phpmyadmin - force: yes + force: true mode: "0644" when: ansible_distribution_major_version is version('10', '=') - name: Install phpmyadmin - apt: + ansible.builtin.apt: name: phpmyadmin update_cache: yes - name: Check if phpmyadmin default configuration is present - stat: + ansible.builtin.stat: path: /etc/apache2/conf-enabled/phpmyadmin.conf register: pma_default_config -- debug: +- ansible.builtin.debug: var: pma_default_config verbosity: 1 - name: Disable phpmyadmin default configuration - command: "a2disconf phpmyadmin" + ansible.builtin.command: + cmd: "a2disconf phpmyadmin" register: command_result changed_when: "'Disabling' in command_result.stderr" when: pma_default_config.stat.exists - name: "phpmyadmin suffix dirname '{{ packweb_phpmyadmin_suffix_file | dirname }}' exists" - file: + ansible.builtin.file: dest: "{{ packweb_phpmyadmin_suffix_file | dirname }}" mode: "0700" owner: root @@ -48,34 +49,36 @@ state: directory - name: set phpmyadmin suffix if provided - copy: + ansible.builtin.copy: dest: "{{ packweb_phpmyadmin_suffix_file }}" # The last character "\u000A" is a line feed (LF), it's better to keep it content: "{{ packweb_phpmyadmin_suffix }}\u000A" - force: yes + force: true when: packweb_phpmyadmin_suffix | length > 0 - name: generate random string for phpmyadmin suffix - shell: "apg -a 1 -M N -n 1 > {{ packweb_phpmyadmin_suffix_file }}" + ansible.builtin.shell: + cmd: "apg -a 1 -M N -n 1 > {{ packweb_phpmyadmin_suffix_file }}" args: creates: "{{ packweb_phpmyadmin_suffix_file }}" - name: read phpmyadmin suffix - command: "tail -n 1 {{ packweb_phpmyadmin_suffix_file }}" + ansible.builtin.command: + cmd: "tail -n 1 {{ packweb_phpmyadmin_suffix_file }}" changed_when: False check_mode: no register: new_packweb_phpmyadmin_suffix - name: overwrite packweb_phpmyadmin_suffix - set_fact: + ansible.builtin.set_fact: packweb_phpmyadmin_suffix: "{{ new_packweb_phpmyadmin_suffix.stdout }}" -- debug: +- ansible.builtin.debug: var: packweb_phpmyadmin_suffix verbosity: 1 - name: enable phpMyAdmin config - blockinfile: + ansible.builtin.blockinfile: dest: /etc/apache2/sites-available/000-evolinux-default.conf marker: "# {mark} phpMyAdmin section" block: | @@ -88,13 +91,13 @@ - name: enable phpmyadmin link in default site index - replace: + ansible.builtin.replace: dest: /var/www/index.html regexp: '' replace: '
  • Accès PhpMyAdmin
  • ' - name: replace phpmyadmin suffix in default site index - replace: + ansible.builtin.replace: dest: /var/www/index.html regexp: '__PHPMYADMIN_SUFFIX__' replace: "{{ packweb_phpmyadmin_suffix }}" diff --git a/percona/defaults/main.yml b/percona/defaults/main.yml index 46a86904..316eccc9 100644 --- a/percona/defaults/main.yml +++ b/percona/defaults/main.yml @@ -2,3 +2,5 @@ percona__install_xtrabackup: True percona__xtrabackup_package_name: percona-xtrabackup-24 + +apt_keyring_dir: "{{ ansible_distribution_major_version is version('12', '<') | ternary('/etc/apt/trusted.gpg.d', '/etc/apt/keyrings') }}" \ No newline at end of file diff --git a/percona/tasks/main.yml b/percona/tasks/main.yml index 27544252..069956d0 100644 --- a/percona/tasks/main.yml +++ b/percona/tasks/main.yml @@ -1,32 +1,32 @@ --- -- set_fact: +- ansible.builtin.set_fact: percona__apt_config_package_file: "percona-release_latest.{{ ansible_distribution_release }}_all.deb" - name: Look for legacy apt keyring - stat: + ansible.builtin.stat: path: /etc/apt/trusted.gpg register: _trusted_gpg_keyring - name: Percona embedded GPG key is absent - apt_key: + ansible.builtin.apt_key: id: "8507EFA5" keyring: /etc/apt/trusted.gpg state: absent when: _trusted_gpg_keyring.stat.exists - name: Add Percona GPG key - copy: + ansible.builtin.copy: src: percona.asc - dest: /etc/apt/trusted.gpg.d/percona.asc - force: yes + dest: "{{ apt_keyring_dir }}/percona.asc" + force: true mode: "0644" owner: root group: root - name: Check if percona-release is installed - shell: "set -o pipefail && dpkg -l percona-release 2>/dev/null | grep -q -E '^(i|h)i'" - args: + ansible.builtin.shell: + cmd: "set -o pipefail && dpkg -l percona-release 2>/dev/null | grep -q -E '^(i|h)i'" executable: /bin/bash check_mode: no failed_when: False @@ -34,7 +34,7 @@ register: percona__apt_config_package_installed - name: Percona APT config package is available - copy: + ansible.builtin.copy: src: "{{ percona__apt_config_package_file }}" dest: "/root/{{ percona__apt_config_package_file }}" when: not (percona__apt_config_package_installed | bool) @@ -43,23 +43,23 @@ # name: evolix/remount-usr - name: Percona APT config package is installed from deb file - apt: + ansible.builtin.apt: deb: "/root/{{ percona__apt_config_package_file }}" state: present register: percona__apt_config_deb when: not (percona__apt_config_package_installed | bool) - name: Percona APT config package is installed from repository - apt: + ansible.builtin.apt: name: percona-release state: latest register: percona__apt_config_deb when: percona__apt_config_package_installed | bool - name: APT cache is up-to-date - apt: + ansible.builtin.apt: update_cache: yes when: percona__apt_config_deb is changed -- include: xtrabackup.yml +- ansible.builtin.include: xtrabackup.yml when: percona__install_xtrabackup | bool diff --git a/percona/tasks/xtrabackup.yml b/percona/tasks/xtrabackup.yml index 7d4e29d1..6a68fbff 100644 --- a/percona/tasks/xtrabackup.yml +++ b/percona/tasks/xtrabackup.yml @@ -1,16 +1,17 @@ --- - name: Percona Tools is enabled - command: percona-release enable tools release + ansible.builtin.command: + cmd: percona-release enable tools release # changed_when: # register: percona__release_enable_tools - name: APT cache is up-to-date - apt: + ansible.builtin.apt: update_cache: yes # when: percona__release_enable_tools is changed - name: Percona XtraBackup package is installed - apt: + ansible.builtin.apt: name: "{{ percona__xtrabackup_package_name }}" state: present diff --git a/pgbouncer/README.md b/pgbouncer/README.md new file mode 100644 index 00000000..94cdeccf --- /dev/null +++ b/pgbouncer/README.md @@ -0,0 +1,57 @@ +# PgBouncer + +Installation and basic configuration of PgBouncer. + +## Tasks + +Everything is in the `tasks/main.yml` file. + +## Available variables + +Main variables are : + +* `pgbouncer_listen_addr`: the listen IP for PgBouncer (default: `127.0.0.1`), +* `pgbouncer_listen_port`: the listen post for PgBouncer (default: `6432`), +* `pgbouncer_databases`: the databases that clients of PgBouncer can connect to, +* `pgbouncer_account_list`: the accounts that clients of PgBouncer can connect to. +* `pgbouncer_auth_type`: the variable `auth_type` define by default to `scram-sha-256`, if you installed PgBouncer on PostgreSQL version inferior to 14, set this variable to `md5`. + +The variable `pgbouncer_databases` must have the `name`, `host` and `port` attributes. The variable can be defined like this: + +``` +pgbouncer_databases: + - { name: "db1", host: "192.168.3.14", port: "5432" } + - { name: "*", host: "192.168.2.71", port: "5432" } +``` + +The variable `pgbouncer_account_list` must have the `name` and `hash` attributes. The variable can be defined like this: + +``` +pgbouncer_account_list: + - { name: "account1", hash: "" } + - { name: "account2", hash: "" } +``` + +The value of `hash` can be obtained by running this command on the PostgreSQL server: + +``` +select passwd from pg_shadow where usename='account1'; +``` + +> These accounts must exist on the PostgreSQL server. + +The variables `pgbouncer_admin_users` and `pgbouncer_stats_users` list the SQL user can be access on pgbouncer console. This variables can be defines like this : + +``` +pgbouncer_admin_users: + - account1 + - account2 +``` + +``` +pgbouncer_stats_users: + - account1 + - account2 +``` + +The full list of variables (with default values) can be found in `defaults/main.yml`. diff --git a/pgbouncer/defaults/main.yml b/pgbouncer/defaults/main.yml new file mode 100644 index 00000000..211e6a5d --- /dev/null +++ b/pgbouncer/defaults/main.yml @@ -0,0 +1,18 @@ +--- +pgbouncer_listen_addr: + - "127.0.0.1" +pgbouncer_listen_port: "6432" + +# For PostgreSQL version < 14, use "md5" +# For PostgreSQL version >= 14, use "scram-sha-256" +pgbouncer_auth_type: "scram-sha-256" + +# Each entry must have "name", "host" and "port" keys +pgbouncer_databases: [] + +pgbouncer_admin_users: [] + +pgbouncer_stats_users: [] + +# Each entry must have "name" and "hash" keys +pgbouncer_account_list: [] \ No newline at end of file diff --git a/pgbouncer/handlers/main.yml b/pgbouncer/handlers/main.yml new file mode 100644 index 00000000..9ce44055 --- /dev/null +++ b/pgbouncer/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart pgbouncer + ansible.builtin.systemd: + name: pgbouncer.service + state: restarted diff --git a/pgbouncer/tasks/main.yml b/pgbouncer/tasks/main.yml new file mode 100644 index 00000000..0463899d --- /dev/null +++ b/pgbouncer/tasks/main.yml @@ -0,0 +1,24 @@ +--- +- name: PgBouncer is installed + ansible.builtin.apt: + name: pgbouncer + state: present + +- name: Limit for PgBouncer is set + ansible.builtin.lineinfile: + path: /etc/default/pgbouncer + regexp: "ulimit -n" + line: ulimit -n 65536 + notify: restart pgbouncer + +- name: Add config file for PgBouncer + ansible.builtin.template: + src: pgbouncer.ini.j2 + dest: /etc/pgbouncer/pgbouncer.ini + notify: restart pgbouncer + +- name: Populate userlist.txt + ansible.builtin.template: + src: userlist.txt.j2 + dest: /etc/pgbouncer/userlist.txt + notify: restart pgbouncer diff --git a/pgbouncer/templates/pgbouncer.ini.j2 b/pgbouncer/templates/pgbouncer.ini.j2 new file mode 100644 index 00000000..b2d89e47 --- /dev/null +++ b/pgbouncer/templates/pgbouncer.ini.j2 @@ -0,0 +1,32 @@ +[databases] +{% for db in pgbouncer_databases %} +{{ db.name }} = host={{ db.host }} port={{ db.port }} +{% endfor %} + +[pgbouncer] +logfile = /var/log/postgresql/pgbouncer.log +pidfile = /var/run/postgresql/pgbouncer.pid + +listen_addr = {{ pgbouncer_listen_addr | join(',') }} +listen_port = {{ pgbouncer_listen_port }} +unix_socket_dir = + +auth_type = {{ pgbouncer_auth_type }} +auth_file = /etc/pgbouncer/userlist.txt + +admin_users = {{ pgbouncer_admin_users | join(",") }} +stats_users = {{ pgbouncer_stats_users | join(",") }} + +# La connexion au serveur redevient libre lorsque le client termine une transaction +# Autres valeurs possibles : session (lorsque le client ferme la session), statement (lorsque la requête se termine) +pool_mode = transaction + +# Nombre maximum de connexions entrantes +max_client_conn = 5000 + +# Nombre de connexion maintenues avec le serveur +default_pool_size = 20 + +# Ne pas enregistrer les connexions qui se passent bien +log_connections = 0 +log_disconnections = 0 \ No newline at end of file diff --git a/pgbouncer/templates/userlist.txt.j2 b/pgbouncer/templates/userlist.txt.j2 new file mode 100644 index 00000000..abf316a3 --- /dev/null +++ b/pgbouncer/templates/userlist.txt.j2 @@ -0,0 +1,3 @@ +{% for account in pgbouncer_account_list %} +"{{ account.name }}" "{{ account.hash }}" +{% endfor %} diff --git a/php/README.md b/php/README.md index e0a194ac..e2190a3c 100644 --- a/php/README.md +++ b/php/README.md @@ -6,6 +6,8 @@ Installation and basic configuration of PHP Minimal configuration is in `tasks/main.yml` +Set variable `php_version` in your playbook. + ## Available variables The full list of variables (with default values) can be found in `defaults/main.yml`. diff --git a/php/defaults/main.yml b/php/defaults/main.yml index 19040baf..2e633d0f 100644 --- a/php/defaults/main.yml +++ b/php/defaults/main.yml @@ -8,3 +8,5 @@ php_symfony_requirements: False php_modules_mysqlnd: False php_fpm_remove_default_pool: False + +apt_keyring_dir: "{{ ansible_distribution_major_version is version('12', '<') | ternary('/etc/apt/trusted.gpg.d', '/etc/apt/keyrings') }}" \ No newline at end of file diff --git a/php/files/reg.asc b/php/files/reg.asc new file mode 100644 index 00000000..3fadeb07 --- /dev/null +++ b/php/files/reg.asc @@ -0,0 +1,920 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: SKS 1.1.6 +Comment: Hostname: keyserver.ubuntu.com + +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 +BWcdiQ== +=iUyJ +-----END PGP PUBLIC KEY BLOCK----- diff --git a/php/files/sury.preferences b/php/files/sury.preferences index cc4901c2..adcc5918 100644 --- a/php/files/sury.preferences +++ b/php/files/sury.preferences @@ -1,4 +1,4 @@ -Package: php* libapache2-mod-php* libpcre2* libzip4* +Package: php* libapache2-mod-php* libpcre2* libzip4* libgd* libpcre3* Pin: origin packages.sury.org Pin-Priority: 999 diff --git a/php/handlers/main.yml b/php/handlers/main.yml index 973c0069..b333fe9b 100644 --- a/php/handlers/main.yml +++ b/php/handlers/main.yml @@ -1,21 +1,36 @@ --- - name: restart php5-fpm - service: + ansible.builtin.service: name: php5-fpm state: restarted +- name: restart php5.6-fpm + ansible.builtin.service: + name: php5.6-fpm + state: restarted + - name: restart php7.0-fpm - service: + ansible.builtin.service: name: php7.0-fpm state: restarted - name: restart php7.3-fpm - service: + ansible.builtin.service: name: php7.3-fpm state: restarted - name: restart php7.4-fpm - service: + ansible.builtin.service: name: php7.4-fpm state: restarted + +- name: restart php8.1-fpm + ansible.builtin.service: + name: php8.1-fpm + state: restarted + +- name: restart php8.2-fpm + ansible.builtin.service: + name: php8.2-fpm + state: restarted diff --git a/php/tasks/config_apache.yml b/php/tasks/config_apache.yml index 795678fd..addab08c 100644 --- a/php/tasks/config_apache.yml +++ b/php/tasks/config_apache.yml @@ -1,7 +1,7 @@ --- - name: Set default values for PHP - ini_file: + community.general.ini_file: dest: "{{ php_apache_defaults_ini_file }}" section: PHP option: "{{ item.option }}" @@ -19,7 +19,7 @@ - { option: "opcache.max_accelerated_files", value: "8000" } - name: Disable PHP functions - ini_file: + community.general.ini_file: dest: "{{ php_apache_defaults_ini_file }}" section: PHP option: disable_functions @@ -27,16 +27,16 @@ mode: "0644" - name: Custom php.ini - copy: + ansible.builtin.copy: dest: "{{ php_apache_custom_ini_file }}" content: | ; Put customized values here. ; default_charset = "ISO-8859-1" mode: "0644" - force: no + force: false - name: "Set custom values for PHP to enable Symfony" - ini_file: + community.general.ini_file: dest: "{{ php_apache_custom_ini_file }}" section: PHP option: "{{ item.option }}" diff --git a/php/tasks/config_cli.yml b/php/tasks/config_cli.yml index d327690a..e7084d9b 100644 --- a/php/tasks/config_cli.yml +++ b/php/tasks/config_cli.yml @@ -1,6 +1,6 @@ --- - name: "Set default php.ini values for CLI" - ini_file: + community.general.ini_file: dest: "{{ php_cli_defaults_ini_file }}" section: PHP option: "{{ item.option }}" @@ -13,21 +13,21 @@ - { option: "disable_functions", value: "" } - name: Custom php.ini for CLI - copy: + ansible.builtin.copy: dest: "{{ php_cli_custom_ini_file }}" content: | ; Put customized values here. - force: no + force: false # This task is not merged with the above copy -# because "force: no" prevents any fix after the fact +# because "force: false" prevents any fix after the fact - name: "Permissions for custom php.ini for CLI" - file: + ansible.builtin.file: dest: "{{ php_cli_custom_ini_file }}" mode: "0644" - name: "Set custom values for PHP to enable Symfony" - ini_file: + community.general.ini_file: dest: "{{ php_cli_custom_ini_file }}" section: PHP option: "{{ item.option }}" diff --git a/php/tasks/config_fpm.yml b/php/tasks/config_fpm.yml index ad543f19..836559bf 100644 --- a/php/tasks/config_fpm.yml +++ b/php/tasks/config_fpm.yml @@ -1,7 +1,7 @@ --- - name: Set default php.ini values for FPM - ini_file: + community.general.ini_file: dest: "{{ php_fpm_defaults_ini_file }}" section: PHP option: "{{ item.option }}" @@ -20,7 +20,7 @@ notify: "restart {{ php_fpm_service_name }}" - name: Disable PHP functions for FPM - ini_file: + community.general.ini_file: dest: "{{ php_fpm_defaults_ini_file }}" section: PHP option: disable_functions @@ -28,15 +28,15 @@ notify: "restart {{ php_fpm_service_name }}" - name: Custom php.ini for FPM - copy: + ansible.builtin.copy: dest: "{{ php_fpm_custom_ini_file }}" content: | ; Put customized values here. - force: no + force: false notify: "restart {{ php_fpm_service_name }}" - name: Set default PHP FPM values - ini_file: + community.general.ini_file: dest: "{{ php_fpm_default_pool_file }}" section: www option: "{{ item.option }}" @@ -60,17 +60,17 @@ when: ansible_distribution_major_version is version('9', '>=') - name: Custom PHP FPM values - copy: + ansible.builtin.copy: dest: "{{ php_fpm_default_pool_custom_file }}" content: | ; Put customized values here. ; default_charset = "ISO-8859-1" mode: "0644" - force: no + force: false notify: "restart {{ php_fpm_service_name }}" - name: "Set custom values for PHP to enable Symfony" - ini_file: + community.general.ini_file: dest: "{{ php_cli_custom_ini_file }}" section: PHP option: "{{ item.option }}" @@ -82,7 +82,7 @@ when: php_symfony_requirements | bool - name: Delete debian default pool - file: + ansible.builtin.file: path: "{{ php_fpm_debian_default_pool_file | mandatory }}" state: absent notify: "restart {{ php_fpm_service_name }}" diff --git a/php/tasks/main.yml b/php/tasks/main.yml index 86bde74f..f9144832 100644 --- a/php/tasks/main.yml +++ b/php/tasks/main.yml @@ -1,20 +1,23 @@ --- -- assert: +- ansible.builtin.assert: that: - ansible_distribution == "Debian" - ansible_distribution_major_version is version('8', '>=') - - ansible_distribution_major_version is version('11', '<=') - msg: This is only compatible with Debian 8 → 11 + - ansible_distribution_major_version is version('12', '<=') + msg: This is only compatible with Debian 8 → 12 -- include: main_jessie.yml +- ansible.builtin.include_tasks: main_jessie.yml when: ansible_distribution_release == "jessie" -- include: main_stretch.yml +- ansible.builtin.include_tasks: main_stretch.yml when: ansible_distribution_release == "stretch" -- include: main_buster.yml +- ansible.builtin.include_tasks: main_buster.yml when: ansible_distribution_release == "buster" -- include: main_bullseye.yml +- ansible.builtin.include_tasks: main_bullseye.yml when: ansible_distribution_release == "bullseye" + +- ansible.builtin.include_tasks: main_bookworm.yml + when: ansible_distribution_release == "bookworm" diff --git a/php/tasks/main_bookworm.yml b/php/tasks/main_bookworm.yml new file mode 100644 index 00000000..68de60d6 --- /dev/null +++ b/php/tasks/main_bookworm.yml @@ -0,0 +1,110 @@ +--- + +- name: "Set php version to 8.2 (Debian 12)" + ansible.builtin.set_fact: + php_version: "8.2" + when: not (php_sury_enable | bool) + check_mode: no + +- name: "Set php config directories (Debian 12)" + ansible.builtin.set_fact: + php_cli_conf_dir: "/etc/php/{{ php_version }}/cli/conf.d" + php_apache_conf_dir: "/etc/php/{{ php_version }}/apache2/conf.d" + php_fpm_conf_dir: "/etc/php/{{ php_version }}/fpm/conf.d" + php_fpm_pool_dir: "/etc/php/{{ php_version }}/fpm/pool.d" + +- name: "Set php config files (Debian 12)" + ansible.builtin.set_fact: + php_cli_defaults_ini_file: "{{ php_cli_conf_dir }}/z-evolinux-defaults.ini" + php_cli_custom_ini_file: "{{ php_cli_conf_dir }}/zzz-evolinux-custom.ini" + php_apache_defaults_ini_file: "{{ php_apache_conf_dir }}/z-evolinux-defaults.ini" + php_apache_custom_ini_file: "{{ php_apache_conf_dir }}/zzz-evolinux-custom.ini" + php_fpm_defaults_ini_file: "{{ php_fpm_conf_dir }}/z-evolinux-defaults.ini" + php_fpm_custom_ini_file: "{{ php_fpm_conf_dir }}/zzz-evolinux-custom.ini" + php_fpm_debian_default_pool_file: "{{ php_fpm_pool_dir }}/www.conf" + php_fpm_default_pool_file: "{{ php_fpm_pool_dir }}/www-evolinux-defaults.conf" + php_fpm_default_pool_custom_file: "{{ php_fpm_pool_dir }}/www-evolinux-zcustom.conf" + php_fpm_default_pool_socket: "/var/run/php/php{{ php_version }}-fpm.sock" + php_fpm_service_name: "php{{ php_version }}-fpm" + +# Packages + +- name: "Set package list (Debian 12)" + ansible.builtin.set_fact: + php_stretch_packages: + - php-cli + - php-gd + - php-intl + - php-imap + - php-ldap + - php-mysql + # php-mcrypt is no longer packaged for PHP 7.2 + - php-pgsql + - php-sqlite3 + - php-curl + - php-ssh2 + - php-xml + - php-zip + - composer + - libphp-phpmailer + +- ansible.builtin.include: sury_pre.yml + when: php_sury_enable + +- name: "Install PHP packages (Debian 12)" + ansible.builtin.apt: + name: '{{ php_stretch_packages }}' + state: present + +- name: "Install mod_php packages (Debian 12)" + ansible.builtin.apt: + name: + - libapache2-mod-php + - php + state: present + when: php_apache_enable + +- name: "Install PHP FPM packages (Debian 12)" + ansible.builtin.apt: + name: + - php-fpm + - php + state: present + when: php_fpm_enable + +# Configuration + +- name: "Enforce permissions on PHP directory (Debian 12)" + ansible.builtin.file: + dest: "{{ item }}" + mode: "0755" + with_items: + - /etc/php + - /etc/php/{{ php_version }} + +- ansible.builtin.include: config_cli.yml +- name: "Enforce permissions on PHP cli directory (Debian 12)" + ansible.builtin.file: + dest: /etc/php/{{ php_version }}/cli + mode: "0755" + +- ansible.builtin.include: config_fpm.yml + when: php_fpm_enable + +- name: "Enforce permissions on PHP fpm directory (Debian 12)" + ansible.builtin.file: + dest: /etc/php/{{ php_version }}/fpm + mode: "0755" + when: php_fpm_enable + +- ansible.builtin.include: config_apache.yml + when: php_apache_enable + +- name: "Enforce permissions on PHP apache2 directory (Debian 12)" + ansible.builtin.file: + dest: /etc/php/{{ php_version }}/apache2 + mode: "0755" + when: php_apache_enable + +- ansible.builtin.include: sury_post.yml + when: php_sury_enable diff --git a/php/tasks/main_bullseye.yml b/php/tasks/main_bullseye.yml index a1f7d5f5..f8232c45 100644 --- a/php/tasks/main_bullseye.yml +++ b/php/tasks/main_bullseye.yml @@ -1,23 +1,29 @@ --- +- name: "Set php version to 7.4 if Sury repo is not enabled" + ansible.builtin.set_fact: + php_version: "7.4" + when: not (php_sury_enable | bool) + check_mode: no + - name: "Set variables (Debian 11)" - set_fact: - php_cli_defaults_ini_file: /etc/php/7.4/cli/conf.d/z-evolinux-defaults.ini - php_cli_custom_ini_file: /etc/php/7.4/cli/conf.d/zzz-evolinux-custom.ini - php_apache_defaults_ini_file: /etc/php/7.4/apache2/conf.d/z-evolinux-defaults.ini - php_apache_custom_ini_file: /etc/php/7.4/apache2/conf.d/zzz-evolinux-custom.ini - php_fpm_defaults_ini_file: /etc/php/7.4/fpm/conf.d/z-evolinux-defaults.ini - php_fpm_custom_ini_file: /etc/php/7.4/fpm/conf.d/zzz-evolinux-custom.ini - php_fpm_debian_default_pool_file: /etc/php/7.4/fpm/pool.d/www.conf - php_fpm_default_pool_file: /etc/php/7.4/fpm/pool.d/www-evolinux-defaults.conf - php_fpm_default_pool_custom_file: /etc/php/7.4/fpm/pool.d/www-evolinux-zcustom.conf - php_fpm_default_pool_socket: /var/run/php/php7.4-fpm.sock - php_fpm_service_name: php7.4-fpm + ansible.builtin.set_fact: + php_cli_defaults_ini_file: /etc/php/{{ php_version }}/cli/conf.d/z-evolinux-defaults.ini + php_cli_custom_ini_file: /etc/php/{{ php_version }}/cli/conf.d/zzz-evolinux-custom.ini + php_apache_defaults_ini_file: /etc/php/{{ php_version }}/apache2/conf.d/z-evolinux-defaults.ini + php_apache_custom_ini_file: /etc/php/{{ php_version }}/apache2/conf.d/zzz-evolinux-custom.ini + php_fpm_defaults_ini_file: /etc/php/{{ php_version }}/fpm/conf.d/z-evolinux-defaults.ini + php_fpm_custom_ini_file: /etc/php/{{ php_version }}/fpm/conf.d/zzz-evolinux-custom.ini + php_fpm_debian_default_pool_file: /etc/php/{{ php_version }}/fpm/pool.d/www.conf + php_fpm_default_pool_file: /etc/php/{{ php_version }}/fpm/pool.d/www-evolinux-defaults.conf + php_fpm_default_pool_custom_file: /etc/php/{{ php_version }}/fpm/pool.d/www-evolinux-zcustom.conf + php_fpm_default_pool_socket: /var/run/php/php{{ php_version }}-fpm.sock + php_fpm_service_name: php{{ php_version }}-fpm # Packages - name: "Set package list (Debian 11)" - set_fact: + ansible.builtin.set_fact: php_stretch_packages: - php-cli - php-gd @@ -25,25 +31,25 @@ - php-imap - php-ldap - php-mysql - # php-mcrypt is no longer packaged for PHP 7.2 - php-pgsql - php-sqlite3 - php-curl - php-ssh2 + - php-xml - php-zip - composer - libphp-phpmailer -- include: sury_pre.yml +- ansible.builtin.include: sury_pre.yml when: php_sury_enable - name: "Install PHP packages (Debian 11)" - apt: + ansible.builtin.apt: name: '{{ php_stretch_packages }}' state: present - name: "Install mod_php packages (Debian 11)" - apt: + ansible.builtin.apt: name: - libapache2-mod-php - php @@ -51,46 +57,46 @@ when: php_apache_enable - name: "Install PHP FPM packages (Debian 11)" - apt: + ansible.builtin.apt: name: - - php-fpm - - php + - php{{ php_version }}-fpm + - php{{ php_version }} state: present when: php_fpm_enable # Configuration - name: "Enforce permissions on PHP directory (Debian 11)" - file: + ansible.builtin.file: dest: "{{ item }}" mode: "0755" with_items: - /etc/php - - /etc/php/7.4 + - /etc/php/{{ php_version }} -- include: config_cli.yml +- ansible.builtin.include: config_cli.yml - name: "Enforce permissions on PHP cli directory (Debian 11)" - file: - dest: /etc/php/7.4/cli + ansible.builtin.file: + dest: /etc/php/{{ php_version }}/cli mode: "0755" -- include: config_fpm.yml +- ansible.builtin.include: config_fpm.yml when: php_fpm_enable - name: "Enforce permissions on PHP fpm directory (Debian 11)" - file: - dest: /etc/php/7.4/fpm + ansible.builtin.file: + dest: /etc/php/{{ php_version }}/fpm mode: "0755" when: php_fpm_enable -- include: config_apache.yml +- ansible.builtin.include: config_apache.yml when: php_apache_enable - name: "Enforce permissions on PHP apache2 directory (Debian 11)" - file: - dest: /etc/php/7.4/apache2 + ansible.builtin.file: + dest: /etc/php/{{ php_version }}/apache2 mode: "0755" when: php_apache_enable -- include: sury_post.yml - when: php_sury_enable +#- include: sury_post.yml +# when: php_sury_enable diff --git a/php/tasks/main_buster.yml b/php/tasks/main_buster.yml index 2fc4293e..6a5f1d1a 100644 --- a/php/tasks/main_buster.yml +++ b/php/tasks/main_buster.yml @@ -1,23 +1,32 @@ --- +- ansible.builtin.debug: + var: php_sury_enable + +- name: "Set php version to 7.3 if Sury repo is not enabled" + ansible.builtin.set_fact: + php_version: "7.3" + check_mode: no + when: not (php_sury_enable | bool) + - name: "Set variables (Debian 10)" - set_fact: - php_cli_defaults_ini_file: /etc/php/7.3/cli/conf.d/z-evolinux-defaults.ini - php_cli_custom_ini_file: /etc/php/7.3/cli/conf.d/zzz-evolinux-custom.ini - php_apache_defaults_ini_file: /etc/php/7.3/apache2/conf.d/z-evolinux-defaults.ini - php_apache_custom_ini_file: /etc/php/7.3/apache2/conf.d/zzz-evolinux-custom.ini - php_fpm_defaults_ini_file: /etc/php/7.3/fpm/conf.d/z-evolinux-defaults.ini - php_fpm_custom_ini_file: /etc/php/7.3/fpm/conf.d/zzz-evolinux-custom.ini - php_fpm_debian_default_pool_file: /etc/php/7.3/fpm/pool.d/www.conf - php_fpm_default_pool_file: /etc/php/7.3/fpm/pool.d/www-evolinux-defaults.conf - php_fpm_default_pool_custom_file: /etc/php/7.3/fpm/pool.d/www-evolinux-zcustom.conf - php_fpm_default_pool_socket: /var/run/php/php7.3-fpm.sock - php_fpm_service_name: php7.3-fpm + ansible.builtin.set_fact: + php_cli_defaults_ini_file: /etc/php/{{ php_version }}/cli/conf.d/zvolinux-defaults.ini + php_cli_custom_ini_file: /etc/php/{{ php_version }}/cli/conf.d/zzz-evolinux-custom.ini + php_apache_defaults_ini_file: /etc/php/{{ php_version }}/apache2/conf.d/z-evolinux-defaults.ini + php_apache_custom_ini_file: /etc/php/{{ php_version }}/apache2/conf.d/zzz-evolinux-custom.ini + php_fpm_defaults_ini_file: /etc/php/{{ php_version }}/fpm/conf.d/z-evolinux-defaults.ini + php_fpm_custom_ini_file: /etc/php/{{ php_version }}/fpm/conf.d/zzz-evolinux-custom.ini + php_fpm_debian_default_pool_file: /etc/php/{{ php_version }}/fpm/pool.d/www.conf + php_fpm_default_pool_file: /etc/php/{{ php_version }}/fpm/pool.d/www-evolinux-defaults.conf + php_fpm_default_pool_custom_file: /etc/php/{{ php_version }}/fpm/pool.d/www-evolinux-zcustom.conf + php_fpm_default_pool_socket: /var/run/php/php{{ php_version }}-fpm.sock + php_fpm_service_name: php{{ php_version }}-fpm # Packages - name: "Set package list (Debian 10)" - set_fact: + ansible.builtin.set_fact: php_stretch_packages: - php-cli - php-gd @@ -35,16 +44,16 @@ - composer - libphp-phpmailer -- include: sury_pre.yml +- ansible.builtin.include: sury_pre.yml when: php_sury_enable | bool - name: "Install PHP packages (Debian 10)" - apt: + ansible.builtin.apt: name: '{{ php_stretch_packages }}' state: present - name: "Install mod_php packages (Debian 10)" - apt: + ansible.builtin.apt: name: - libapache2-mod-php - php @@ -52,46 +61,46 @@ when: php_apache_enable | bool - name: "Install PHP FPM packages (Debian 10)" - apt: + ansible.builtin.apt: name: - - php-fpm - - php + - php{{ php_version }}-fpm + - php{{ php_version }} state: present when: php_fpm_enable | bool # Configuration - name: "Enforce permissions on PHP directory (Debian 10)" - file: + ansible.builtin.file: dest: "{{ item }}" mode: "0755" loop: - /etc/php - - /etc/php/7.3 + - /etc/php/{{ php_version }} -- include: config_cli.yml +- ansible.builtin.include: config_cli.yml - name: "Enforce permissions on PHP cli directory (Debian 10)" - file: - dest: /etc/php/7.3/cli + ansible.builtin.file: + dest: /etc/php/{{ php_version }}/cli mode: "0755" -- include: config_fpm.yml +- ansible.builtin.include: config_fpm.yml when: php_fpm_enable | bool - name: "Enforce permissions on PHP fpm directory (Debian 10)" - file: - dest: /etc/php/7.3/fpm + ansible.builtin.file: + dest: /etc/php/{{ php_version }}/fpm mode: "0755" when: php_fpm_enable | bool -- include: config_apache.yml +- ansible.builtin.include: config_apache.yml when: php_apache_enable | bool - name: "Enforce permissions on PHP apache2 directory (Debian 10)" - file: - dest: /etc/php/7.3/apache2 + ansible.builtin.file: + dest: /etc/php/{{ php_version }}/apache2 mode: "0755" when: php_apache_enable | bool -- include: sury_post.yml - when: php_sury_enable | bool +#- include: sury_post.yml +# when: php_sury_enable | bool diff --git a/php/tasks/main_jessie.yml b/php/tasks/main_jessie.yml index 75105166..fc517533 100644 --- a/php/tasks/main_jessie.yml +++ b/php/tasks/main_jessie.yml @@ -1,7 +1,7 @@ --- - name: "Set variables (Debian 8)" - set_fact: + ansible.builtin.set_fact: php_cli_defaults_ini_file: /etc/php5/cli/conf.d/z-evolinux-defaults.ini php_cli_custom_ini_file: /etc/php5/cli/conf.d/zzz-evolinux-custom.ini php_apache_defaults_ini_file: /etc/php5/apache2/conf.d/z-evolinux-defaults.ini @@ -17,7 +17,7 @@ # Packages - name: "Install PHP packages (Debian 8)" - apt: + ansible.builtin.apt: name: - php5-cli - php5-gd @@ -35,7 +35,7 @@ state: present - name: "Install mod_php packages (Debian 8)" - apt: + ansible.builtin.apt: name: - libapache2-mod-php5 - php5 @@ -43,7 +43,7 @@ when: php_apache_enable | bool - name: "Install PHP FPM packages (Debian 8)" - apt: + ansible.builtin.apt: name: - php5-fpm - php5 @@ -53,31 +53,31 @@ # Configuration - name: Enforce permissions on PHP directory (Debian 8) - file: + ansible.builtin.file: dest: /etc/php5 mode: "0755" -- include: config_cli.yml +- ansible.builtin.include: config_cli.yml - name: Enforce permissions on PHP cli directory (Debian 8) - file: + ansible.builtin.file: dest: /etc/php5/cli mode: "0755" -- include: config_fpm.yml +- ansible.builtin.include: config_fpm.yml when: php_fpm_enable | bool - name: Enforce permissions on PHP fpm directory (Debian 8) - file: + ansible.builtin.file: dest: /etc/php5/fpm mode: "0755" when: php_fpm_enable | bool -- include: config_apache.yml +- ansible.builtin.include: config_apache.yml when: php_apache_enable | bool - name: Enforce permissions on PHP apache2 directory (Debian 8) - file: + ansible.builtin.file: dest: /etc/php5/apache2 mode: "0755" when: php_apache_enable | bool diff --git a/php/tasks/main_stretch.yml b/php/tasks/main_stretch.yml index 698621ac..25f264b7 100644 --- a/php/tasks/main_stretch.yml +++ b/php/tasks/main_stretch.yml @@ -1,7 +1,7 @@ --- - name: "Set variables (Debian 9)" - set_fact: + ansible.builtin.set_fact: php_cli_defaults_ini_file: /etc/php/7.0/cli/conf.d/z-evolinux-defaults.ini php_cli_custom_ini_file: /etc/php/7.0/cli/conf.d/zzz-evolinux-custom.ini php_apache_defaults_ini_file: /etc/php/7.0/apache2/conf.d/z-evolinux-defaults.ini @@ -17,7 +17,7 @@ # Packages - name: "Set package list (Debian 9)" - set_fact: + ansible.builtin.set_fact: php_stretch_packages: - php-cli - php-gd @@ -35,16 +35,16 @@ - composer - libphp-phpmailer -- include: sury_pre.yml +- ansible.builtin.include: sury_pre.yml when: php_sury_enable | bool - name: "Install PHP packages (Debian 9)" - apt: + ansible.builtin.apt: name: '{{ php_stretch_packages }}' state: present - name: "Install mod_php packages (Debian 9)" - apt: + ansible.builtin.apt: name: - libapache2-mod-php - php @@ -52,7 +52,7 @@ when: php_apache_enable | bool - name: "Install PHP FPM packages (Debian 9)" - apt: + ansible.builtin.apt: name: - php-fpm - php @@ -62,37 +62,37 @@ # Configuration - name: "Enforce permissions on PHP directory (Debian 9)" - file: + ansible.builtin.file: dest: "{{ item }}" mode: "0755" loop: - /etc/php - /etc/php/7.0 -- include: config_cli.yml +- ansible.builtin.include: config_cli.yml - name: "Enforce permissions on PHP cli directory (Debian 9)" - file: + ansible.builtin.file: dest: /etc/php/7.0/cli mode: "0755" -- include: config_fpm.yml +- ansible.builtin.include: config_fpm.yml when: php_fpm_enable | bool - name: "Enforce permissions on PHP fpm directory (Debian 9)" - file: + ansible.builtin.file: dest: /etc/php/7.0/fpm mode: "0755" when: php_fpm_enable | bool -- include: config_apache.yml +- ansible.builtin.include: config_apache.yml when: php_apache_enable | bool - name: "Enforce permissions on PHP apache2 directory (Debian 9)" - file: + ansible.builtin.file: dest: /etc/php/7.0/apache2 mode: "0755" when: php_apache_enable | bool -- include: sury_post.yml +- ansible.builtin.include: sury_post.yml when: php_sury_enable | bool diff --git a/php/tasks/sury_post.yml b/php/tasks/sury_post.yml index 4e706889..d529ec53 100644 --- a/php/tasks/sury_post.yml +++ b/php/tasks/sury_post.yml @@ -1,25 +1,25 @@ --- - name: Symlink Evolix CLI config files from 7.4 to 7.0 - file: + ansible.builtin.file: src: "{{ item.src }}" dest: "{{ item.dest }}" - force: yes + force: true state: link loop: - { src: "{{ php_cli_defaults_ini_file }}", dest: "/etc/php/7.4/cli/conf.d/z-evolinux-defaults.ini" } - { src: "{{ php_cli_custom_ini_file }}", dest: "/etc/php/7.4/cli/conf.d/zzz-evolinux-custom.ini" } - name: Enforce permissions on PHP 7.4/cli directory - file: + ansible.builtin.file: dest: /etc/php/7.4/cli mode: "0755" - name: Symlink Evolix Apache config files from 7.4 to 7.0 - file: + ansible.builtin.file: src: "{{ item.src }}" dest: "{{ item.dest }}" - force: yes + force: true state: link loop: - { src: "{{ php_apache_defaults_ini_file }}", dest: "/etc/php/7.4/apache2/conf.d/z-evolinux-defaults.ini" } @@ -27,16 +27,16 @@ when: php_apache_enable | bool - name: Enforce permissions on PHP 7.4/cli directory - file: + ansible.builtin.file: dest: /etc/php/7.4/apache2 mode: "0755" when: php_apache_enable | bool - name: Symlink Evolix FPM config files from 7.4 to 7.0 - file: + ansible.builtin.file: src: "{{ item.src }}" dest: "{{ item.dest }}" - force: yes + force: true state: link loop: - { src: "{{ php_fpm_defaults_ini_file }}", dest: "/etc/php/7.4/fpm/conf.d/z-evolinux-defaults.ini" } @@ -46,7 +46,7 @@ when: php_fpm_enable | bool - name: Enforce permissions on PHP 7.4/cli directory - file: + ansible.builtin.file: dest: /etc/php/7.4/fpm mode: "0755" when: php_fpm_enable | bool diff --git a/php/tasks/sury_pre.yml b/php/tasks/sury_pre.yml index c421fe04..1f04b661 100644 --- a/php/tasks/sury_pre.yml +++ b/php/tasks/sury_pre.yml @@ -1,42 +1,76 @@ --- -- name: Setup deb.sury.org repository - Add GPG key - copy: - src: sury.gpg - dest: /etc/apt/trusted.gpg.d/sury.gpg +- name: Setup deb.sury.org repository - Install apt-transport-https + ansible.builtin.apt: + name: apt-transport-https + state: present + when: ansible_distribution_major_version is version('10', '<') + +- name: copy pub.evolix.org GPG key + ansible.builtin.copy: + src: pub_evolix.asc + dest: "{{ apt_keyring_dir }}/pub_evolix.asc" mode: "0644" owner: root group: root -- name: Setup deb.sury.org repository - Install apt-transport-https - apt: +- name: Setup pub.evolix.org repository - Add source list + ansible.builtin.apt_repository: + repo: "deb [signed-by={{ apt_keyring_dir }}/pub_evolix.asc] http://pub.evolix.org/evolix {{ ansible_distribution_release }}-php81 main" + filename: evolix-php state: present - name: apt-transport-https + when: + - ansible_distribution_release == "bullseye" - name: Setup deb.sury.org repository - Add preferences file - copy: + ansible.builtin.copy: src: sury.preferences dest: /etc/apt/preferences.d/z-sury + when: + - ansible_distribution_release != "bullseye" -- name: Setup deb.sury.org repository - Add source list - apt_repository: - repo: "deb https://packages.sury.org/php/ {{ ansible_distribution_release }} main" +- name: Setup deb.sury.org repository - Add GPG key + ansible.builtin.copy: + src: sury.gpg + dest: "{{ apt_keyring_dir }}/sury.gpg" + mode: "0644" + owner: root + group: root + +- name: Add Sury repository (Debian <12) + ansible.builtin.apt_repository: + repo: "deb [signed-by={{ apt_keyring_dir }}/sury.gpg] https://packages.sury.org/php/ {{ ansible_distribution_release }} main" filename: sury state: present + update_cache: yes + when: ansible_distribution_major_version is version('12', '<') + +- name: Add Sury repository (Debian >=12) + ansible.builtin.template: + src: sury.sources.j2 + dest: /etc/apt/sources.list.d/sury.sources + state: present + register: sury_sources + when: ansible_distribution_major_version is version('12', '>=') + +- name: Update APT cache + ansible.builtin.apt: + update_cache: yes + when: sury_sources is changed - name: "Override package list for Sury (Debian 9 or later)" - set_fact: + ansible.builtin.set_fact: php_stretch_packages: - - php-cli - - php-gd - - php-intl - - php-imap - - php-ldap - - php-mysql + - php{{ php_version }}-cli + - php{{ php_version }}-gd + - php{{ php_version }}-intl + - php{{ php_version }}-imap + - php{{ php_version }}-ldap + - php{{ php_version }}-mysql # php-mcrypt is no longer packaged for PHP 7.2 - - php-pgsql - - php-gettext - - php-curl - - php-ssh2 - - composer - - libphp-phpmailer + - php{{ php_version }}-pgsql + - php{{ php_version }}-gettext + - php{{ php_version }}-curl + - php{{ php_version }}-ssh2 +# - composer +# - libphp-phpmailer diff --git a/php/templates/sury.sources.j2 b/php/templates/sury.sources.j2 new file mode 100644 index 00000000..7d8a95c5 --- /dev/null +++ b/php/templates/sury.sources.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +Types: deb +URIs: https://packages.sury.org/php/ +Suites: {{ ansible_distribution_release }} +Components: main +Signed-by: {{ apt_keyring_dir }}/sury.gpg +Enabled: yes \ No newline at end of file diff --git a/policy_pam/defaults/main.yml b/policy_pam/defaults/main.yml new file mode 100644 index 00000000..fb8075ac --- /dev/null +++ b/policy_pam/defaults/main.yml @@ -0,0 +1,37 @@ +--- + +# PAM -- pam_pwquality +# Ensure password meet a given quality/complexity requirement +policy_pam_pwquality: false + +# Configuration settings for pam_pwquality +# For more in depth info, see man pam_pwquality(8) + +# Minimum password lengh/credit +policy_pam_pwquality_minlen: 16 + +# Credits values for char types +# Value : Interger N with : +# N >= 0 - Maximum credit given for each char type in the password +# N < 0 - Minimum number of chars of given type in the password +# digit chars +policy_pam_pwquality_dcredit: -1 +# uppercase chars +policy_pam_pwquality_ucredit: 0 +# lowercase chars +policy_pam_pwquality_lcredit: -1 +# other chars +policy_pam_pwquality_ocredit: -1 + + +# PAM -- pam_pwhistory +# Prevent old password re-use +policy_pam_pwhistory: false + +# How many old passwords to retain +policy_pam_pwhistory_length: 5 + +# How (days) old the password should be before allowing user to change it's password +# It is to prevent circumvention of pam_pwhistory +# Set to 0 to disable +policy_pam_password_min_days: 0 \ No newline at end of file diff --git a/policy_pam/meta/main.yml b/policy_pam/meta/main.yml new file mode 100644 index 00000000..5da132b9 --- /dev/null +++ b/policy_pam/meta/main.yml @@ -0,0 +1,27 @@ +--- +galaxy_info: + author: Evolix + company: Evolix + description: Add repositories to APT sources list. + + issue_tracker_url: https://gitea.evolix.org/evolix/ansible-roles/issues + + license: GPLv2 + + min_ansible_version: "2.10" + + platforms: + - name: Debian + versions: + - bullseye + - buster + - stretch + + galaxy_tags: [] + # Be sure to remove the '[]' above if you add dependencies + # to this list. + +dependencies: [] + # List your role dependencies here, one per line. + # Be sure to remove the '[]' above if you add dependencies + # to this list. diff --git a/policy_pam/tasks/main.yml b/policy_pam/tasks/main.yml new file mode 100644 index 00000000..a2746011 --- /dev/null +++ b/policy_pam/tasks/main.yml @@ -0,0 +1,104 @@ +--- + +# System compatibility check. +# Untested on old (Jessie & older) Debian versions +- name: "System compatibility check" + ansible.builtin.assert: + that: + - ansible_distribution == "Debian" + - ansible_distribution_major_version is version_compare('9', '>=') + msg: pam_policy is only compatible with Debian >= 9 + +# yescrypt, Debian 11 default hashing alg isn't present on Debian 10 and lower +- name: "Set hashing alg (sha512 - Debian <= 10)" + ansible.builtin.set_fact: + pam_policy_hashing_alg: 'sha512' + when: + ansible_distribution_major_version is version_compare('10', '<=') + +- name: "Set hashing alg (yescrypt - Debian >= 11 )" + ansible.builtin.set_fact: + pam_policy_hashing_alg: 'yescrypt' + when: + ansible_distribution_major_version is version_compare('11', '>=') + + +# PAM -- pam_pwquality + +- name: libpam-pwquality is installed + ansible.builtin.apt: + state: present + name: + - libpam-pwquality + - cracklib-runtime + when: policy_pam_pwquality + +- name: Enable pam_pwquality + ansible.builtin.lineinfile: + dest: /etc/pam.d/common-password + regexp: '^password\s+requisite\s+pam_pwquality.so' + line: "password requisite pam_pwquality.so retry=3" + insertafter: '(the "Primary" block)' + when: policy_pam_pwquality + +- name: Disable pam_pwquality + ansible.builtin.lineinfile: + dest: /etc/pam.d/common-password + regexp: '^password\s+requisite\s+pam_pwquality.so' + state: absent + when: policy_pam_pwquality is false + +- name: Configure pam_pwquality + ansible.builtin.replace: + dest: /etc/security/pwquality.conf + regexp: "^#? ?{{ item.name }} = .*" + replace: "{{ item.name }} = {{ item.value }}" + with_items: + - { name: minlen, value: "{{ policy_pam_pwquality_minlen }}" } + - { name: dcredit, value: "{{ policy_pam_pwquality_dcredit }}" } + - { name: ucredit, value: "{{ policy_pam_pwquality_ucredit }}" } + - { name: lcredit, value: "{{ policy_pam_pwquality_lcredit }}" } + - { name: ocredit, value: "{{ policy_pam_pwquality_ocredit }}" } + when: policy_pam_pwquality + + + +# PAM -- pam_pwhistory + +- name: Enable pam_pwhistory + ansible.builtin.lineinfile: + dest: /etc/pam.d/common-password + regexp: '^password\s+required\s+pam_pwhistory.so' + line: "password required pam_pwhistory.so remember={{ policy_pam_pwhistory_length }} {{ 'use_authtok' if policy_pam_pwquality}}" + insertbefore: 'pam_unix.so' + when: policy_pam_pwhistory + +- name: Disable pam_pwhistory + ansible.builtin.lineinfile: + dest: /etc/pam.d/common-password + regexp: '^password\s+required\s+pam_pwhistory.so' + state: absent + when: policy_pam_pwhistory is false + +# Enforce password minimal age to prevent pam_pwhistory to be circumvented by multiples password changes +- name: Change PASS_MIN_DAYS + ansible.builtin.replace: + dest: /etc/login.defs + replace: 'PASS_MIN_DAYS\g<1>{{ policy_pam_password_min_days }}' + regexp: '^PASS_MIN_DAYS(\s+).*' + + +# PAM -- pam_unix +- name: Update pam_unix if previous modules were enabled + ansible.builtin.lineinfile: + dest: /etc/pam.d/common-password + regexp: 'pam_unix.so obscure' + line: "password [success=1 default=ignore] pam_unix.so obscure use_authtok try_first_pass {{ pam_policy_hashing_alg }}" + when: policy_pam_pwhistory or policy_pam_pwquality + +- name: Update pam_unix if previous modules are all disabled + ansible.builtin.lineinfile: + dest: /etc/pam.d/common-password + regexp: 'pam_unix.so obscure' + line: "password [success=1 default=ignore] pam_unix.so obscure {{ pam_policy_hashing_alg }}" + when: policy_pam_pwhistory is false and policy_pam_pwquality is false \ No newline at end of file diff --git a/postfix/handlers/main.yml b/postfix/handlers/main.yml index 6c2e879b..d8cef9f7 100644 --- a/postfix/handlers/main.yml +++ b/postfix/handlers/main.yml @@ -1,13 +1,14 @@ --- - name: restart postfix - service: + ansible.builtin.service: name: postfix state: restarted - name: reload postfix - service: + ansible.builtin.service: name: postfix state: reloaded - name: postmap transport - command: postmap /etc/postfix/transport + ansible.builtin.command: + cmd: postmap /etc/postfix/transport diff --git a/postfix/meta/main.yml b/postfix/meta/main.yml index 188769a2..b39e6795 100644 --- a/postfix/meta/main.yml +++ b/postfix/meta/main.yml @@ -25,8 +25,8 @@ galaxy_info: # alphanumeric characters. Maximum 20 tags per role. dependencies: - - { role: evolix/ldap, ldap_schema: 'cn4evolix.ldif', when: postfix_packmail == True } - - { role: evolix/spamassasin, when: postfix_packmail == True } - - { role: evolix/clamav, when: postfix_packmail == True } - - { role: evolix/opendkim, when: postfix_packmail == True } - - { role: evolix/dovecot, when: postfix_packmail == True } + - { role: evolix/ldap, ldap_schema: 'cn4evolix.ldif', when: postfix_packmail | bool } + - { role: evolix/spamassasin, when: postfix_packmail | bool } + - { role: evolix/clamav, when: postfix_packmail | bool } + - { role: evolix/opendkim, when: postfix_packmail | bool } + - { role: evolix/dovecot, when: postfix_packmail | bool } diff --git a/postfix/tasks/common.yml b/postfix/tasks/common.yml index bcd5ed79..29e6dd07 100644 --- a/postfix/tasks/common.yml +++ b/postfix/tasks/common.yml @@ -1,7 +1,8 @@ --- - name: check if main.cf is default - shell: 'grep -v -E "^(myhostname|mydestination|mailbox_command)" /etc/postfix/main.cf | md5sum -' + ansible.builtin.shell: + cmd: 'grep -v -E "^(myhostname|mydestination|mailbox_command)" /etc/postfix/main.cf | md5sum -' changed_when: False check_mode: no register: default_main_cf @@ -9,7 +10,7 @@ - postfix - name: add lines in /etc/.gitignore - lineinfile: + ansible.builtin.lineinfile: dest: /etc/.gitignore line: '{{ item }}' state: present diff --git a/postfix/tasks/main.yml b/postfix/tasks/main.yml index d8caf2b2..4ef2858a 100644 --- a/postfix/tasks/main.yml +++ b/postfix/tasks/main.yml @@ -1,12 +1,12 @@ --- -- include: common.yml +- ansible.builtin.include: common.yml -- include: minimal.yml +- ansible.builtin.include: minimal.yml when: not (postfix_packmail | bool) -- include: packmail.yml +- ansible.builtin.include: packmail.yml when: postfix_packmail | bool -- include: slow_transport.yml +- ansible.builtin.include: slow_transport.yml when: postfix_slow_transport_include | bool diff --git a/postfix/tasks/minimal.yml b/postfix/tasks/minimal.yml index 970b9dcb..36327b3e 100644 --- a/postfix/tasks/minimal.yml +++ b/postfix/tasks/minimal.yml @@ -1,19 +1,19 @@ --- - name: ensure packages are installed - apt: + ansible.builtin.apt: name: postfix state: present tags: - postfix - name: create minimal main.cf - template: + ansible.builtin.template: src: evolinux_main.cf.j2 dest: /etc/postfix/main.cf owner: root group: root mode: "0644" - force: yes + force: true notify: restart postfix when: (postfix_force_main_cf | bool) or (postfix_maincf_md5_jessie in default_main_cf.stdout) or (postfix_maincf_md5_stretch in default_main_cf.stdout) tags: diff --git a/postfix/tasks/packmail.yml b/postfix/tasks/packmail.yml index 90d424b2..f5ccf66d 100644 --- a/postfix/tasks/packmail.yml +++ b/postfix/tasks/packmail.yml @@ -1,6 +1,6 @@ --- - name: ensure packages are installed - apt: + ansible.builtin.apt: name: - postfix - postfix-ldap @@ -10,21 +10,35 @@ tags: - postfix +- name: make /var/lib/mailgraph accessible by www-data + ansible.builtin.file: + path: "/var/lib/mailgraph" + state: directory + owner: www-data + group: www-data + mode: '0755' + +- name: make sure a service Mailgraph is running + ansible.builtin.systemd: + name: mailgraph.service + state: started + enabled: true + - name: create packmail main.cf - template: + ansible.builtin.template: src: packmail_main.cf.j2 dest: /etc/postfix/main.cf owner: root group: root mode: "0644" - force: yes + force: true notify: restart postfix when: (postfix_force_main_cf | bool) or (postfix_maincf_md5_jessie in default_main_cf.stdout) or (postfix_maincf_md5_stretch in default_main_cf.stdout) tags: - postfix - name: deploy packmail master.cf - template: + ansible.builtin.template: src: packmail_master.cf.j2 dest: /etc/postfix/master.cf mode: "0644" @@ -33,10 +47,10 @@ - postfix - name: copy default filter files - copy: + ansible.builtin.copy: src: filter dest: "/etc/postfix/{{ item }}" - force: no + force: false loop: - virtual - client.access @@ -54,7 +68,8 @@ - postfix - name: postmap filter files - command: "postmap /etc/postfix/{{ item }}" + ansible.builtin.command: + cmd: "postmap /etc/postfix/{{ item }}" loop: - virtual - client.access @@ -72,7 +87,7 @@ - postfix - name: deploy ldap postfix config - template: + ansible.builtin.template: src: "{{ item }}.j2" dest: "/etc/postfix/{{ item }}" mode: "0644" @@ -84,13 +99,13 @@ tags: - postfix -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr tags: - postfix - name: copy spam.sh script - copy: + ansible.builtin.copy: src: spam.sh dest: /usr/share/scripts/spam.sh mode: "0700" @@ -98,8 +113,8 @@ - postfix - name: Check if cron is installed - shell: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'" - args: + ansible.builtin.shell: + cmd: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'" executable: /bin/bash check_mode: no failed_when: False @@ -107,7 +122,7 @@ register: is_cron_installed - name: enable spam.sh cron - lineinfile: + ansible.builtin.lineinfile: dest: /etc/cron.d/spam line: "42 * * * * root /usr/share/scripts/spam.sh" create: yes @@ -118,7 +133,8 @@ - postfix - name: update antispam list - command: /usr/share/scripts/spam.sh - changed_when: false + ansible.builtin.command: + cmd: /usr/share/scripts/spam.sh + changed_when: False tags: - postfix diff --git a/postfix/tasks/slow_transport.yml b/postfix/tasks/slow_transport.yml index 2f1867ae..6e42ef1d 100644 --- a/postfix/tasks/slow_transport.yml +++ b/postfix/tasks/slow_transport.yml @@ -1,6 +1,6 @@ --- - name: slow transport is defined in master.cf - lineinfile: + ansible.builtin.lineinfile: dest: /etc/postfix/master.cf regexp: "^slow " line: "slow unix - - n - - smtp" @@ -9,7 +9,7 @@ - postfix - name: list of providers for slow transport - lineinfile: + ansible.builtin.lineinfile: dest: /etc/postfix/transport line: "{{ item }}" create: yes diff --git a/postfix/templates/evolinux_main.cf.j2 b/postfix/templates/evolinux_main.cf.j2 index 0c871546..b9464669 100644 --- a/postfix/templates/evolinux_main.cf.j2 +++ b/postfix/templates/evolinux_main.cf.j2 @@ -5,7 +5,7 @@ myhostname = {{ postfix_hostname }} alias_maps = hash:/etc/aliases alias_database = hash:/etc/aliases myorigin = $myhostname -mydestination = $myhostname localhost.localdomain localhost +mydestination = $myhostname localhost localhost.localdomain localhost.$mydomain relayhost = mynetworks = 127.0.0.0/8 mailbox_size_limit = 0 @@ -24,7 +24,7 @@ smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_una {% if postfix_slow_transport_include == True %} # Slow transports configuration -minimal_backoff_time = 2h +minimal_backoff_time = 15m maximal_backoff_time = 6h maximal_queue_lifetime = 4d queue_run_delay = 100s diff --git a/postfix/templates/packmail_main.cf.j2 b/postfix/templates/packmail_main.cf.j2 index 397abc0d..0d80cf00 100644 --- a/postfix/templates/packmail_main.cf.j2 +++ b/postfix/templates/packmail_main.cf.j2 @@ -74,7 +74,7 @@ myhostname = {{ ansible_fqdn }} # Liste des noms de domaine (ou IP) consideres comme local #par defaut, = $myhostname, localhost.$mydomain, localhost -mydestination = $myhostname +mydestination = $myhostname localhost localhost.localdomain localhost.$mydomain # Indique le domaine apparaissant dans le courrier envoye #par defaut, = $myhostname @@ -294,7 +294,7 @@ slow_destination_concurrency_failed_cohort_limit = 100 # bounce : envoie les entetes de tous les message renvoyes # 2bounce : envoie les entetes de tous les messages renvoyes non delivres #par defaut, = resource, software -notify_classes = resource, software, bounce, 2bounce, delay, policy, protocol +#notify_classes = resource, software # A qui les reporter ? #Pour delay @@ -410,14 +410,19 @@ broken_sasl_auth_clients = yes smtpd_sasl_type = dovecot smtpd_sasl_path = private/auth-client -# Amavis and OpenDKIM +# Amavis content_filter = smtp-amavis:[127.0.0.1]:10024 -smtpd_milters = inet:[127.0.0.1]:54321 -non_smtpd_milters = inet:[127.0.0.1]:54321 +smtp-amavis_destination_concurrency_failed_cohort_limit = 0 +smtp-amavis_destination_concurrency_negative_feedback = 0 +smtp-amavis_destination_concurrency_limit = 2 + +# OpenDKIM +smtpd_milters = inet:[127.0.0.1]:8891 +non_smtpd_milters = inet:[127.0.0.1]:8891 {% if postfix_slow_transport_include == True %} # Slow transports configuration -minimal_backoff_time = 2h +minimal_backoff_time = 15m maximal_backoff_time = 6h maximal_queue_lifetime = 4d queue_run_delay = 100s @@ -430,3 +435,4 @@ slow_destination_concurrency_failed_cohort_limit = 100 slow_destination_recipient_limit = 25 transport_maps = hash:$config_directory/transport {% endif %} + 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/postfix/templates/virtual_aliases.cf.j2 b/postfix/templates/virtual_aliases.cf.j2 index 1a6e5f9c..97f4baf3 100644 --- a/postfix/templates/virtual_aliases.cf.j2 +++ b/postfix/templates/virtual_aliases.cf.j2 @@ -2,4 +2,3 @@ search_base = {{ ldap_suffix }} query_filter = (&(mailacceptinggeneralid=%u@%d)(isActive=TRUE)) result_attribute = maildrop version = 3 -aliases_scope = sub diff --git a/postgresql/defaults/main.yml b/postgresql/defaults/main.yml index 7b2b3734..634ea4f1 100644 --- a/postgresql/defaults/main.yml +++ b/postgresql/defaults/main.yml @@ -7,6 +7,11 @@ postgresql_shared_buffers: "{{ (ansible_memtotal_mb * 0.25) | int }}MB" postgresql_work_mem: 8MB postgresql_random_page_cost: 1.5 postgresql_effective_cache_size: "{{ (ansible_memtotal_mb * 0.5) | int }}MB" +postgresql_max_connections: None + +# Binding +postgresql_listen_addresses: + - "127.0.0.1" # PostgreSQL version postgresql_version: '' @@ -16,3 +21,5 @@ locales_default: fr_FR.UTF-8 # PostGIS postgresql_install_postgis: False + +apt_keyring_dir: "{{ ansible_distribution_major_version is version('12', '<') | ternary('/etc/apt/trusted.gpg.d', '/etc/apt/keyrings') }}" \ No newline at end of file diff --git a/postgresql/files/logrotate_postgresql b/postgresql/files/logrotate_postgresql index a9306aa3..656e2991 100644 --- a/postgresql/files/logrotate_postgresql +++ b/postgresql/files/logrotate_postgresql @@ -1,11 +1,10 @@ /var/log/postgresql/*.log { + su postgres postgres daily rotate 10 copytruncate nodelaycompress compress - notifempty missingok - su root root + dateext } - diff --git a/postgresql/handlers/main.yml b/postgresql/handlers/main.yml index 5275b6a1..0cb017d4 100644 --- a/postgresql/handlers/main.yml +++ b/postgresql/handlers/main.yml @@ -1,25 +1,28 @@ --- - name: restart munin-node - service: + ansible.builtin.service: name: munin-node state: restarted - name: restart nagios-nrpe-server - service: + ansible.builtin.service: name: nagios-nrpe-server state: restarted - name: restart postgresql - systemd: + ansible.builtin.systemd: name: postgresql state: restarted daemon_reload: yes - name: reload systemd - command: systemctl daemon-reload + ansible.builtin.systemd: + daemon-reload: yes - name: Restart minifirewall - command: /etc/init.d/minifirewall restart + ansible.builtin.command: + cmd: /etc/init.d/minifirewall restart - name: reconfigure locales - command: dpkg-reconfigure -f noninteractive locales + ansible.builtin.command: + cmd: dpkg-reconfigure -f noninteractive locales diff --git a/postgresql/tasks/config.yml b/postgresql/tasks/config.yml index f29026df..2f735df0 100644 --- a/postgresql/tasks/config.yml +++ b/postgresql/tasks/config.yml @@ -1,41 +1,41 @@ --- - name: Ensure /etc/systemd/system/postgresql.service.d exists - file: + ansible.builtin.file: path: /etc/systemd/system/postgresql@.service.d state: directory recurse: true - name: Override PostgreSQL systemd unit - copy: + ansible.builtin.copy: src: postgresql.service.override.conf dest: /etc/systemd/system/postgresql@.service.d/override.conf - force: yes + force: true mode: "0644" notify: - reload systemd - restart postgresql - name: Allow conf.d/*.conf files to be included in PostgreSQL configuration - lineinfile: - name: "/etc/postgresql/{{postgresql_version}}/main/postgresql.conf" + ansible.builtin.lineinfile: + name: "/etc/postgresql/{{ postgresql_version }}/main/postgresql.conf" line: include_dir = 'conf.d' notify: restart postgresql - name: Create conf.d directory - file: - name: "/etc/postgresql/{{postgresql_version}}/main/conf.d/" + ansible.builtin.file: + name: "/etc/postgresql/{{ postgresql_version }}/main/conf.d/" state: directory owner: postgres group: postgres mode: "0755" - name: Copy PostgreSQL config file - template: + ansible.builtin.template: src: postgresql.conf.j2 - dest: "/etc/postgresql/{{postgresql_version}}/main/conf.d/zz-evolinux.conf" + dest: "/etc/postgresql/{{ postgresql_version }}/main/conf.d/zz-evolinux.conf" owner: postgres group: postgres mode: "0644" notify: restart postgresql -- meta: flush_handlers +- ansible.builtin.meta: flush_handlers diff --git a/postgresql/tasks/locales.yml b/postgresql/tasks/locales.yml index 8cf70989..7446b485 100644 --- a/postgresql/tasks/locales.yml +++ b/postgresql/tasks/locales.yml @@ -1,18 +1,18 @@ --- -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - name: select locales to be generated - locale_gen: + community.general.locale_gen: name: "{{ item }}" state: present loop: - "fr_FR.UTF-8" - become: yes + become: true notify: reconfigure locales - name: set default locale - lineinfile: + ansible.builtin.lineinfile: dest: "/etc/default/locale" regexp: "^LANG=" line: "LANG={{ locales_default }}" diff --git a/postgresql/tasks/logrotate.yml b/postgresql/tasks/logrotate.yml index f67f407a..dbe66bb8 100644 --- a/postgresql/tasks/logrotate.yml +++ b/postgresql/tasks/logrotate.yml @@ -1,6 +1,6 @@ --- - name: logrotate configuration - copy: + ansible.builtin.copy: src: logrotate_postgresql dest: /etc/logrotate.d/postgresql-common - force: no + force: false diff --git a/postgresql/tasks/main.yml b/postgresql/tasks/main.yml index 1783a763..14d9f9eb 100644 --- a/postgresql/tasks/main.yml +++ b/postgresql/tasks/main.yml @@ -1,25 +1,28 @@ --- -- include: locales.yml +- ansible.builtin.import_tasks: locales.yml -- include: packages_jessie.yml +- ansible.builtin.import_tasks: packages_jessie.yml when: ansible_distribution_release == "jessie" -- include: packages_stretch.yml +- ansible.builtin.import_tasks: packages_stretch.yml when: ansible_distribution_release == "stretch" -- include: packages_buster.yml +- ansible.builtin.import_tasks: packages_buster.yml when: ansible_distribution_release == "buster" -- include: packages_bullseye.yml - when: ansible_distribution_major_version is version('11', '>=') +- ansible.builtin.import_tasks: packages_bullseye.yml + when: ansible_distribution_release == "bullseye" -- include: config.yml +- ansible.builtin.import_tasks: packages_bookworm.yml + when: ansible_distribution_release == "bookworm" -- include: nrpe.yml +- ansible.builtin.import_tasks: config.yml -- include: munin.yml +- ansible.builtin.import_tasks: nrpe.yml -- include: logrotate.yml +- ansible.builtin.import_tasks: munin.yml -- include: postgis.yml +- ansible.builtin.import_tasks: logrotate.yml + +- ansible.builtin.import_tasks: postgis.yml when: postgresql_install_postgis | bool diff --git a/postgresql/tasks/munin.yml b/postgresql/tasks/munin.yml index ed2cc883..f826a639 100644 --- a/postgresql/tasks/munin.yml +++ b/postgresql/tasks/munin.yml @@ -1,19 +1,19 @@ --- - name: Are Munin plugins present in /etc ? - stat: + ansible.builtin.stat: path: /etc/munin/plugins register: etc_munin_plugins - name: Are Munin plugins present in /usr/share ? - stat: + ansible.builtin.stat: path: /usr/share/munin/plugins register: usr_share_munin_plugins - name: Add Munin plugins for PostgreSQL - file: + ansible.builtin.file: state: link - src: '/usr/share/munin/plugins/{{item}}' - dest: '/etc/munin/plugins/{{item}}' + src: '/usr/share/munin/plugins/{{ item }}' + dest: '/etc/munin/plugins/{{ item }}' loop: - postgres_bgwriter - postgres_checkpoints @@ -24,13 +24,21 @@ when: etc_munin_plugins.stat.exists and usr_share_munin_plugins.stat.exists - name: Add Munin plugins for PostgreSQL (for specific databases) - file: + ansible.builtin.file: state: link - src: '/usr/share/munin/plugins/{{item[0]}}' - dest: '/etc/munin/plugins/{{item[0]}}{{item[1]}}' + src: '/usr/share/munin/plugins/{{ item[0] }}' + dest: '/etc/munin/plugins/{{ item[0] }}{{ item[1] }}' loop: "{{ _plugins | product(_databases) | list }}" vars: - _plugins: ['postgres_cache_', 'postgres_connections_', 'postgres_locks_', 'postgres_querylength_', 'postgres_scans_', 'postgres_size_', 'postgres_transactions_', 'postgres_tuples_'] - _databases: postgresql_databases + _plugins: + - 'postgres_cache_' + - 'postgres_connections_' + - 'postgres_locks_' + - 'postgres_querylength_' + - 'postgres_scans_' + - 'postgres_size_' + - 'postgres_transactions_' + - 'postgres_tuples_' + _databases: "{{ postgresql_databases }}" notify: restart munin-node when: etc_munin_plugins.stat.exists and usr_share_munin_plugins.stat.exists diff --git a/postgresql/tasks/nrpe.yml b/postgresql/tasks/nrpe.yml index 4aea2d81..a78c249b 100644 --- a/postgresql/tasks/nrpe.yml +++ b/postgresql/tasks/nrpe.yml @@ -1,36 +1,37 @@ --- - name: apg package is installed - apt: + ansible.builtin.apt: name: apg state: present - name: Generate random password for nrpe user - command: apg -n1 -m 12 -M SCNL + ansible.builtin.command: + cmd: apg -n1 -m 12 -M SCNL register: postgresql_nrpe_password changed_when: False - name: python-psycopg2 is installed (Ansible dependency) - apt: + ansible.builtin.apt: name: python-psycopg2 state: present when: ansible_python_version is version('3', '<') - name: python3-psycopg2 is installed (Ansible dependency) - apt: + ansible.builtin.apt: name: python3-psycopg2 state: present when: ansible_python_version is version('3', '>=') - name: Is nrpe present ? - stat: + ansible.builtin.stat: path: /etc/nagios/nrpe.d/evolix.cfg register: nrpe_evolix_config - block: - name: Create nrpe user - become: yes + become: true become_user: postgres - postgresql_user: + community.postgresql.postgresql_user: name: nrpe password: '{{ postgresql_nrpe_password.stdout }}' encrypted: yes @@ -39,10 +40,10 @@ when: nrpe_evolix_config.stat.exists - name: Add NRPE check - lineinfile: + ansible.builtin.lineinfile: name: /etc/nagios/nrpe.d/evolix.cfg regexp: '^command\[check_pgsql\]=' - line: 'command[check_pgsql]=/usr/lib/nagios/plugins/check_pgsql -H localhost -l nrpe -p "{{postgresql_nrpe_password.stdout}}"' + line: 'command[check_pgsql]=/usr/lib/nagios/plugins/check_pgsql -H localhost -l nrpe -p "{{ postgresql_nrpe_password.stdout }}"' notify: restart nagios-nrpe-server when: postgresql_create_nrpe_user is changed when: nrpe_evolix_config.stat.exists diff --git a/postgresql/tasks/packages_bookworm.yml b/postgresql/tasks/packages_bookworm.yml new file mode 100644 index 00000000..c2088c39 --- /dev/null +++ b/postgresql/tasks/packages_bookworm.yml @@ -0,0 +1,16 @@ +--- + +- name: "Set variables (Debian 12)" + ansible.builtin.set_fact: + postgresql_version: '15' + when: postgresql_version is none or postgresql_version | length == 0 + +- ansible.builtin.import_tasks: pgdg-repo.yml + when: postgresql_version != '15' + +- name: Install postgresql package + ansible.builtin.apt: + name: + - "postgresql-{{ postgresql_version }}" + - pgtop + - libdbd-pg-perl diff --git a/postgresql/tasks/packages_bullseye.yml b/postgresql/tasks/packages_bullseye.yml index 558578f2..4f42119b 100644 --- a/postgresql/tasks/packages_bullseye.yml +++ b/postgresql/tasks/packages_bullseye.yml @@ -1,16 +1,17 @@ --- - + - name: "Set variables (Debian 11)" - set_fact: + ansible.builtin.set_fact: postgresql_version: '13' when: postgresql_version is none or postgresql_version | length == 0 -- include: pgdg-repo.yml +- ansible.builtin.include: pgdg-repo.yml when: postgresql_version != '13' - name: Install postgresql package - apt: + ansible.builtin.apt: name: - - postgresql + - "postgresql-{{ postgresql_version }}" - pgtop - libdbd-pg-perl + update_cache: yes diff --git a/postgresql/tasks/packages_buster.yml b/postgresql/tasks/packages_buster.yml index 76017545..f35182ba 100644 --- a/postgresql/tasks/packages_buster.yml +++ b/postgresql/tasks/packages_buster.yml @@ -1,16 +1,17 @@ --- - name: "Set variables (Debian 10)" - set_fact: + ansible.builtin.set_fact: postgresql_version: '11' when: postgresql_version is none or postgresql_version | length == 0 -- include: pgdg-repo.yml +- ansible.builtin.include: pgdg-repo.yml when: postgresql_version != '11' - name: Install postgresql package - apt: + ansible.builtin.apt: name: - - postgresql + - "postgresql-{{ postgresql_version }}" - pgtop - libdbd-pg-perl + update_cache: yes diff --git a/postgresql/tasks/packages_jessie.yml b/postgresql/tasks/packages_jessie.yml index b9f9b31b..632ddacb 100644 --- a/postgresql/tasks/packages_jessie.yml +++ b/postgresql/tasks/packages_jessie.yml @@ -1,17 +1,17 @@ --- - name: "Set variables (Debian 8)" - set_fact: + ansible.builtin.set_fact: postgresql_version: '9.4' when: postgresql_version is none or postgresql_version | length == 0 -- include: pgdg-repo.yml +- ansible.builtin.include: pgdg-repo.yml when: postgresql_version != '9.4' - name: Install postgresql package - apt: - name: '{{item}}' - loop: - - "postgresql-{{postgresql_version}}" - - ptop - - libdbd-pg-perl + ansible.builtin.apt: + name: + - "postgresql-{{ postgresql_version }}" + - ptop + - libdbd-pg-perl + update_cache: yes diff --git a/postgresql/tasks/packages_stretch.yml b/postgresql/tasks/packages_stretch.yml index d8ebb9e4..494fce3f 100644 --- a/postgresql/tasks/packages_stretch.yml +++ b/postgresql/tasks/packages_stretch.yml @@ -1,16 +1,17 @@ --- - name: "Set variables (Debian 9)" - set_fact: + ansible.builtin.set_fact: postgresql_version: '9.6' when: postgresql_version is none or postgresql_version | length == 0 -- include: pgdg-repo.yml +- ansible.builtin.include: pgdg-repo.yml when: postgresql_version != '9.6' - name: Install postgresql package - apt: + ansible.builtin.apt: name: - - postgresql + - "postgresql-{{ postgresql_version }}" - ptop - libdbd-pg-perl + update_cache: yes diff --git a/postgresql/tasks/pgdg-repo.yml b/postgresql/tasks/pgdg-repo.yml index a13b7469..b988618a 100644 --- a/postgresql/tasks/pgdg-repo.yml +++ b/postgresql/tasks/pgdg-repo.yml @@ -1,46 +1,44 @@ --- - name: Open firewall for PGDG repository - replace: + ansible.builtin.replace: name: /etc/default/minifirewall - regexp: "^(HTTPSITES='((?!apt\\.postgresql\\.org).)*)'$" + regexp: "^(HTTPSITES='((?!apt\\.postgresql\\.org|0\\.0\\.0\\.0).)*)'$" replace: "\\1 apt.postgresql.org'" notify: Restart minifirewall -- meta: flush_handlers - -- name: Add PGDG repository - apt_repository: - repo: "deb http://apt.postgresql.org/pub/repos/apt/ {{ansible_distribution_release}}-pgdg main" - update_cache: yes - -- name: Look for legacy apt keyring - stat: - path: /etc/apt/trusted.gpg - register: _trusted_gpg_keyring - -- name: PGDG embedded GPG key is absent - apt_key: - id: "ACCC4CF8" - keyring: /etc/apt/trusted.gpg - state: absent - when: _trusted_gpg_keyring.stat.exists +- ansible.builtin.meta: flush_handlers - name: Add PGDG GPG key - copy: + ansible.builtin.copy: src: postgresql.asc - dest: /etc/apt/trusted.gpg.d/postgresql.asc - force: yes + dest: "{{ apt_keyring_dir }}/postgresql.asc" + force: true mode: "0644" owner: root group: root -- name: Update and upgrade apt packages for PGDG repository - apt: - upgrade: yes +- name: Add PGDG repository (Debian <12) + ansible.builtin.apt_repository: + repo: "deb [signed-by={{ apt_keyring_dir }}/postgresql.asc] http://apt.postgresql.org/pub/repos/apt/ {{ ansible_distribution_release }}-pgdg main" + filename: postgresql update_cache: yes + when: ansible_distribution_major_version is version('12', '<') + +- name: Add PGDG repository (Debian >=12) + ansible.builtin.template: + src: postgresql.sources.j2 + dest: /etc/apt/sources.list.d/postgresql.sources + state: present + register: postgresql_sources + when: ansible_distribution_major_version is version('12', '>=') + +- name: Update APT cache + ansible.builtin.apt: + update_cache: yes + when: elastic_sources is changed - name: Add APT preference file - template: + ansible.builtin.template: src: postgresql.pref.j2 dest: /etc/apt/preferences.d/postgresql.pref mode: "0644" diff --git a/postgresql/tasks/postgis.yml b/postgresql/tasks/postgis.yml index f2300943..ea50fc61 100644 --- a/postgresql/tasks/postgis.yml +++ b/postgresql/tasks/postgis.yml @@ -1,7 +1,8 @@ --- - name: Install PostGIS extention - apt: + ansible.builtin.apt: name: - postgis - "postgresql-{{ postgresql_version }}-postgis-2.5" - "postgresql-{{ postgresql_version }}-postgis-2.5-scripts" + update_cache: yes diff --git a/postgresql/templates/postgresql.conf.j2 b/postgresql/templates/postgresql.conf.j2 index 25597519..2f39937f 100644 --- a/postgresql/templates/postgresql.conf.j2 +++ b/postgresql/templates/postgresql.conf.j2 @@ -1,6 +1,7 @@ # Tuning shared_buffers = {{ postgresql_shared_buffers }} work_mem = {{ postgresql_work_mem }} +listen_addresses = '{{ postgresql_listen_addresses | join(',') }}' #shared_preload_libraries = 'pg_stat_statements' #synchronous_commit = off {% if postgresql_version is version('9.5', '<') %} @@ -8,17 +9,22 @@ checkpoint_segments = 30 {% else %} max_wal_size = 15GB {% endif %} +{% if postgresql_max_connections and postgresql_max_connections | int > 0 %} +max_connections = {{ postgresql_max_connections }} +{% endif %} checkpoint_completion_target = 0.9 random_page_cost = {{ postgresql_random_page_cost }} effective_cache_size = {{ postgresql_effective_cache_size }} -# Loging +# Logging log_min_duration_statement = 1s log_checkpoints = on log_lock_waits = on log_temp_files = 5MB log_autovacuum_min_duration = 1s +log_line_prefix = '%t [%p]: user=%u,db=%d,app=%a,client=%h ' +lc_messages = 'C' # Locales lc_monetary = 'fr_FR.UTF-8' diff --git a/postgresql/templates/postgresql.pref.j2 b/postgresql/templates/postgresql.pref.j2 index 74196cf6..5d252e05 100644 --- a/postgresql/templates/postgresql.pref.j2 +++ b/postgresql/templates/postgresql.pref.j2 @@ -1,3 +1,3 @@ -Package: postgresql-{{postgresql_version}} postgresql-client-common postgresql-common libpq5 ptop -Pin: release a={{ansible_distribution_release}}-pgdg +Package: postgresql-{{ postgresql_version }} postgresql-client-common postgresql-common libpq5 ptop +Pin: release a={{ ansible_distribution_release }}-pgdg Pin-Priority: 999 diff --git a/postgresql/templates/postgresql.sources.j2 b/postgresql/templates/postgresql.sources.j2 new file mode 100644 index 00000000..38284d20 --- /dev/null +++ b/postgresql/templates/postgresql.sources.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +Types: deb +URIs: http://apt.postgresql.org/pub/repos/apt/ +Suites: {{ ansible_distribution_release }}-pgdg +Components: main +Signed-by: {{ apt_keyring_dir }}/postgresql.asc +Enabled: yes \ No newline at end of file diff --git a/postgresql/tests/test.yml b/postgresql/tests/test.yml index 438eddee..5472e972 100644 --- a/postgresql/tests/test.yml +++ b/postgresql/tests/test.yml @@ -3,18 +3,18 @@ pre_tasks: - name: Install locales - apt: + ansible.builtin.apt: name: locales state: present - changed_when: false + changed_when: False - name: Setting default locales - lineinfile: + ansible.builtin.lineinfile: dest: /etc/locale.gen line: "{{ item }}" create: yes state: present - changed_when: false + changed_when: False loop: - "en_US.UTF-8 UTF-8" - "fr_FR ISO-8859-1" @@ -22,8 +22,9 @@ register: test_locales - name: Reconfigure locales - command: /usr/sbin/locale-gen - changed_when: false + ansible.builtin.command: + cmd: /usr/sbin/locale-gen + changed_when: False when: test_locales is changed roles: diff --git a/proftpd/README.md b/proftpd/README.md index dae8abef..6e96e05a 100644 --- a/proftpd/README.md +++ b/proftpd/README.md @@ -41,5 +41,5 @@ proftpd_accounts: For generate the sha512 version of yours password : ~~~ -echo "test" | mkpasswd --method=sha-512 - +printf "test" | mkpasswd --stdin --method=sha-512 ~~~ diff --git a/proftpd/defaults/main.yml b/proftpd/defaults/main.yml index 80edecd2..25d60d5b 100644 --- a/proftpd/defaults/main.yml +++ b/proftpd/defaults/main.yml @@ -3,12 +3,16 @@ proftpd_hostname: "{{ ansible_hostname }}" proftpd_fqdn: "{{ ansible_fqdn }}" proftpd_default_address: [] proftpd_ftp_enable: True +proftpd_ftp_override: False proftpd_port: 21 proftpd_ftps_enable: False +proftpd_ftps_override: False proftpd_ftps_port: 990 proftpd_ftps_cert: "/etc/ssl/certs/ssl-cert-snakeoil.pem" proftpd_ftps_key: "/etc/ssl/private/ssl-cert-snakeoil.key" proftpd_sftp_enable: False +proftpd_sftp_override: False +proftpd_sftp_use_publickeys: False proftpd_sftp_port: 22222 proftpd_accounts: [] proftpd_accounts_final: [] diff --git a/proftpd/handlers/main.yml b/proftpd/handlers/main.yml index 0914d289..2b320f4a 100644 --- a/proftpd/handlers/main.yml +++ b/proftpd/handlers/main.yml @@ -1,5 +1,5 @@ --- - name: restart proftpd - service: + ansible.builtin.service: name: proftpd state: restarted diff --git a/proftpd/tasks/account.yml b/proftpd/tasks/account.yml index a03fd1f1..4ad009e2 100644 --- a/proftpd/tasks/account.yml +++ b/proftpd/tasks/account.yml @@ -1,7 +1,8 @@ --- - name: Check if FTP account exist - command: grep "^{{ proftpd_name }}:" /etc/proftpd/vpasswd - failed_when: false + ansible.builtin.command: + cmd: grep "^{{ proftpd_name }}:" /etc/proftpd/vpasswd + failed_when: False check_mode: no changed_when: check_ftp_account.rc != 0 register: check_ftp_account @@ -9,7 +10,8 @@ - proftpd - name: Generate FTP password - command: apg -n1 + ansible.builtin.command: + cmd: apg -n1 register: ftp_password check_mode: no when: check_ftp_account.rc != 0 @@ -17,14 +19,14 @@ - proftpd - name: Print generated password - debug: + ansible.builtin.debug: msg: "{{ ftp_password.stdout }}" when: check_ftp_account.rc != 0 tags: - proftpd - name: Hash generated FTP password - set_fact: + ansible.builtin.set_fact: proftpd_password: "{{ ftp_password.stdout | password_hash('sha512') }}" check_mode: no when: check_ftp_account.rc != 0 @@ -32,25 +34,26 @@ - proftpd - name: Get current FTP password - shell: grep "^{{ proftpd_name }}:" /etc/proftpd/vpasswd | cut -d':' -f2 + ansible.builtin.shell: + cmd: grep "^{{ proftpd_name }}:" /etc/proftpd/vpasswd | cut -d':' -f2 register: hashed_ftp_password check_mode: no when: check_ftp_account.rc == 0 - changed_when: false + changed_when: False tags: - proftpd - name: Get current FTP password - set_fact: + ansible.builtin.set_fact: proftpd_password: "{{ hashed_ftp_password.stdout }}" check_mode: no when: check_ftp_account.rc == 0 - changed_when: false + changed_when: False tags: - proftpd - name: Create FTP account - lineinfile: + ansible.builtin.lineinfile: dest: /etc/proftpd/vpasswd state: present create: yes @@ -61,7 +64,7 @@ - proftpd - name: Allow FTP account - lineinfile: + ansible.builtin.lineinfile: dest: /etc/proftpd/conf.d/z-evolinux.conf state: present line: " AllowUser {{ proftpd_name }}" diff --git a/proftpd/tasks/accounts.yml b/proftpd/tasks/accounts.yml index 756e0ff0..99b036c9 100644 --- a/proftpd/tasks/accounts.yml +++ b/proftpd/tasks/accounts.yml @@ -1,11 +1,11 @@ --- -- include: accounts_password.yml +- ansible.builtin.include: accounts_password.yml when: item.password is undefined loop: "{{ proftpd_accounts }}" tags: - proftpd -- set_fact: +- ansible.builtin.set_fact: proftpd_accounts_final: "{{ proftpd_accounts_final + [ item ] }}" when: item.password is defined loop: "{{ proftpd_accounts }}" @@ -13,7 +13,7 @@ - proftpd - name: Create FTP account - lineinfile: + ansible.builtin.lineinfile: dest: /etc/proftpd/vpasswd state: present create: yes @@ -26,7 +26,7 @@ - proftpd - name: Allow FTP account (FTP) - lineinfile: + ansible.builtin.lineinfile: dest: /etc/proftpd/conf.d/z-evolinux.conf state: present line: "\tAllowUser {{ item.name }}" @@ -38,7 +38,7 @@ - proftpd - name: Allow FTP account (FTPS) - lineinfile: + ansible.builtin.lineinfile: dest: /etc/proftpd/conf.d/ftps.conf state: present line: "\tAllowUser {{ item.name }}" @@ -50,7 +50,7 @@ - proftpd - name: Allow FTP account (SFTP) - lineinfile: + ansible.builtin.lineinfile: dest: /etc/proftpd/conf.d/sftp.conf state: present line: "\tAllowUser {{ item.name }}" @@ -60,3 +60,18 @@ when: proftpd_sftp_enable | bool tags: - proftpd + +- name: Allow keys for SFTP account + ansible.builtin.template: + dest: "/etc/proftpd/sftp.authorized_keys/{{ _proftpd_account.name }}" + src: authorized_keys.j2 + mode: 0644 + loop: "{{ proftpd_accounts_final }}" + loop_control: + loop_var: _proftpd_account + notify: restart proftpd + when: + - proftpd_sftp_enable | bool + - proftpd_sftp_use_publickeys | bool + tags: + - proftpd diff --git a/proftpd/tasks/accounts_password.yml b/proftpd/tasks/accounts_password.yml index 01517083..0b986f39 100644 --- a/proftpd/tasks/accounts_password.yml +++ b/proftpd/tasks/accounts_password.yml @@ -1,7 +1,8 @@ --- - name: Check if FTP account exist - command: grep "^{{ item.name }}:" /etc/proftpd/vpasswd - failed_when: false + ansible.builtin.command: + cmd: grep "^{{ item.name }}:" /etc/proftpd/vpasswd + failed_when: False check_mode: no changed_when: check_ftp_account.rc != 0 register: check_ftp_account @@ -9,13 +10,14 @@ - block: - name: Get current FTP password - shell: grep "^{{ item.name }}:" /etc/proftpd/vpasswd | cut -d':' -f2 + ansible.builtin.shell: + cmd: grep "^{{ item.name }}:" /etc/proftpd/vpasswd | cut -d':' -f2 register: protftpd_cur_password check_mode: no - changed_when: false + changed_when: False - name: Set password for this account - set_fact: + ansible.builtin.set_fact: protftpd_password: "{{ protftpd_cur_password.stdout }}" when: check_ftp_account.rc == 0 @@ -23,20 +25,21 @@ - block: - name: Generate FTP password - command: "apg -n 1 -m 16 -M lcN" + ansible.builtin.command: + cmd: "apg -n 1 -m 16 -M lcN" register: proftpd_apg_password check_mode: no - name: Print generated password - debug: + ansible.builtin.debug: msg: "{{ proftpd_apg_password.stdout }}" - name: Hash generated password - set_fact: + ansible.builtin.set_fact: protftpd_password: "{{ proftpd_apg_password.stdout | password_hash('sha512') }}" when: check_ftp_account.rc != 0 - name: Update proftpd_accounts with password - set_fact: + ansible.builtin.set_fact: proftpd_accounts_final: "{{ proftpd_accounts_final + [ item | combine({ 'password': protftpd_password }) ] }}" diff --git a/proftpd/tasks/main.yml b/proftpd/tasks/main.yml index 457887a1..26dcfb85 100644 --- a/proftpd/tasks/main.yml +++ b/proftpd/tasks/main.yml @@ -1,6 +1,6 @@ --- - name: package is installed - apt: + ansible.builtin.apt: name: proftpd-basic state: present tags: @@ -8,7 +8,8 @@ - packages - name: ftpusers groupe exists - group: + + ansible.builtin.group: name: ftpusers state: present notify: restart proftpd @@ -16,40 +17,54 @@ - proftpd - name: FTP jail is installed - template: + ansible.builtin.template: src: evolinux.conf.j2 dest: /etc/proftpd/conf.d/z-evolinux.conf mode: "0644" - force: no + force: "{{ proftpd_ftp_override }}" notify: restart proftpd when: proftpd_ftp_enable | bool tags: - proftpd - name: FTPS jail is installed - template: + ansible.builtin.template: src: ftps.conf.j2 dest: /etc/proftpd/conf.d/ftps.conf mode: "0644" - force: no + force: "{{ proftpd_ftps_override }}" notify: restart proftpd when: proftpd_ftps_enable | bool tags: - proftpd - name: SFTP jail is installed - template: + ansible.builtin.template: src: sftp.conf.j2 dest: /etc/proftpd/conf.d/sftp.conf mode: "0644" - force: no + force: "{{ proftpd_sftp_override }}" notify: restart proftpd when: proftpd_sftp_enable | bool tags: - proftpd +- name: SFTP key folder exists if needed + ansible.builtin.file: + path: /etc/proftpd/sftp.authorized_keys/ + state: directory + mode: "0755" + owner: root + group: root + notify: restart proftpd + when: + - proftpd_sftp_enable | bool + - proftpd_sftp_use_publickeys | bool + tags: + - proftpd + - name: mod_tls_memcache is disabled - replace: + ansible.builtin.replace: dest: /etc/proftpd/modules.conf regexp: '^LoadModule mod_tls_memcache.c' replace: '#LoadModule mod_tls_memcache.c' @@ -58,10 +73,10 @@ - proftpd - name: Put empty vpasswd file if missing - copy: + ansible.builtin.copy: src: vpasswd dest: /etc/proftpd/vpasswd - force: no + force: false notify: restart proftpd tags: - proftpd @@ -70,7 +85,7 @@ # So, readonly when opened with vim. # Then readable by group. - name: Enforce permissions on password file - file: + ansible.builtin.file: path: /etc/proftpd/vpasswd mode: "0440" owner: root @@ -79,5 +94,5 @@ tags: - proftpd -- include: accounts.yml +- ansible.builtin.include: accounts.yml when: proftpd_accounts | length > 0 diff --git a/proftpd/templates/authorized_keys.j2 b/proftpd/templates/authorized_keys.j2 new file mode 100644 index 00000000..620e50f9 --- /dev/null +++ b/proftpd/templates/authorized_keys.j2 @@ -0,0 +1,3 @@ +{%- for key in _proftpd_account.sshkeys %} +{{ key }} +{%- endfor %} diff --git a/proftpd/templates/evolinux.conf.j2 b/proftpd/templates/evolinux.conf.j2 index 8a810a99..8ad06927 100644 --- a/proftpd/templates/evolinux.conf.j2 +++ b/proftpd/templates/evolinux.conf.j2 @@ -1,5 +1,13 @@ # Evolix's specific configuration +{% if proftpd_ftp_override %} +# WARNING : **Probably** ansible managed +{% endif %} + + + LoadModule mod_ident.c + + ServerName "{{ proftpd_hostname }} FTP Server" ServerIdent on "FTP Server Ready" AccessGrantMsg "Hey, bienvenue %u sur le serveur FTP {{ proftpd_fqdn }} !" diff --git a/proftpd/templates/ftps.conf.j2 b/proftpd/templates/ftps.conf.j2 index 33a2cff3..f9826989 100644 --- a/proftpd/templates/ftps.conf.j2 +++ b/proftpd/templates/ftps.conf.j2 @@ -1,8 +1,12 @@ +{% if proftpd_ftps_override %} +# WARNING : **Probably** ansible managed +{% endif %} + LoadModule mod_tls.c - + TLSEngine on TLSLog /var/log/proftpd/ftps.log TLSProtocol TLSv1 diff --git a/proftpd/templates/sftp.conf.j2 b/proftpd/templates/sftp.conf.j2 index 9a96e5ef..457f638b 100644 --- a/proftpd/templates/sftp.conf.j2 +++ b/proftpd/templates/sftp.conf.j2 @@ -1,3 +1,7 @@ +{% if proftpd_sftp_override %} +# WARNING : **Probably** ansible managed +{% endif %} + LoadModule mod_tls.c @@ -6,15 +10,21 @@ LoadModule mod_sftp.c - + SFTPEngine on Port {{ proftpd_sftp_port }} DefaultRoot ~ SFTPLog /var/log/proftpd/sftp.log TransferLog /var/log/proftpd/xferlog - + +{% if proftpd_sftp_use_publickeys %} + SFTPAuthMethods publickey password + SFTPAuthorizedUserKeys file:/etc/proftpd/sftp.authorized_keys/%u +{% else %} SFTPAuthMethods password +{% endif %} + SFTPHostKey /etc/ssh/ssh_host_ecdsa_key SFTPHostKey /etc/ssh/ssh_host_rsa_key diff --git a/rabbitmq/files/check_rabbitmq.python3 b/rabbitmq/files/check_rabbitmq.python3 new file mode 100644 index 00000000..0a941dd4 --- /dev/null +++ b/rabbitmq/files/check_rabbitmq.python3 @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +from optparse import OptionParser +import shlex +import subprocess +import sys +import requests +import json + +if "check_output" not in dir( subprocess ): # duck punch it in! + def f(*popenargs, **kwargs): + if 'stdout' in kwargs: + raise ValueError('stdout argument not allowed, it will be overridden.') + process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + raise subprocess.CalledProcessError(retcode, cmd) + return output + subprocess.check_output = f + + +class RabbitCmdWrapper(object): + """So basically this just runs rabbitmqctl commands and returns parsed output. + Typically this means you need root privs for this to work. + Made this it's own class so it could be used in other monitoring tools + if desired.""" + + @classmethod + def list_connections(cls): + args = shlex.split("sudo rabbitmqctl list_connections") + cmd_result = subprocess.check_output(args, text=True).strip() + results = cls._parse_list_results(cmd_result) + return results + + @classmethod + def list_queues(cls): + args = shlex.split('sudo rabbitmqctl list_queues') + cmd_result = subprocess.check_output(args, text=True).strip() + results = cls._parse_list_results(cmd_result) + return results + + @classmethod + def status(cls): + args = shlex.split('sudo rabbitmqctl status') + cmd_result = subprocess.check_output(args, text=True).strip() + results = cls._parse_list_results(cmd_result) + return results + + @classmethod + def _parse_list_results(cls, result_string): + results = result_string.strip().split('\n') + #remove text fluff + if "Listing connections ..." in results: results.remove("Listing connections ...") + if "Listing queues ..." in results: results.remove("Listing queues ...") + return_data = [] + for row in results: + return_data.append(row.split('\t')) + return return_data + + +def check_connection_count(critical=0, warning=0): + """Checks to make sure the numbers of connections are within parameters.""" + try: + count = len(RabbitCmdWrapper.list_connections()) + if count >= critical: + print("CRITICAL - Connection Count %d" % count) + sys.exit(2) + elif count >= warning: + print("WARNING - Connection Count %d" % count) + sys.exit(1) + else: + print("OK - Connection Count %d" % count) + except Exception as err: + print("CRITICAL - %s" % err) + + +def check_queues_count(critical=1000, warning=1000): + """ + A blanket check to make sure all queues are within count parameters. + TODO: Possibly break this out so test can be done on individual queues. + """ + try: + critical_q = [] + warning_q = [] + results = RabbitCmdWrapper.list_queues() + for queue in results: + if queue.count == 2: + count = int(queue[1]) + if count >= critical: + critical_q.append("%s: %s" % (queue[0], count)) + elif count >= warning: + warning_q.append("%s: %s" % (queue[0], count)) + if critical_q: + print("CRITICAL - %s" % ", ".join(critical_q)) + sys.exit(2) + elif warning_q: + print("WARNING - %s" % ", ".join(warning_q)) + sys.exit(1) + else: + print("OK - NO QUEUES EXCEED THRESHOLDS") + sys.exit(0) + except Exception as err: + print("CRITICAL - %s" % err) + sys.exit(2) + +def check_mem_usage(critical=75, warning=50): + """Check to make sure the RAM usage of rabbitmq process does not exceed 50%% of its max""" + try: + results = RabbitCmdWrapper.status() + + for idx,val in enumerate(results): + if "memory," in str(val): + mem_used_raw = str(results[idx + 1]) + if "vm_memory_limit" in str(val): + mem_limit_raw = str(val) + + memory_used = float(filter(str.isdigit, mem_used_raw)) + memory_limit = float(filter(str.isdigit, mem_limit_raw)) + percent_usage = int(memory_used/memory_limit * 100) + + if percent_usage > critical: + print("CRITICAL - RABBITMQ RAM USAGE at %s%% of max" % percent_usage) + sys.exit(2) + elif percent_usage > warning: + print("WARNING - RABBITMQ RAM USAGE at %s%% of max" % percent_usage) + sys.exit(1) + else: + print("OK - RABBITMQ RAM USAGE OK at %s%% of max" % percent_usage) + sys.exit(0) + except Exception as err: + print("Critical - %s" % err) + sys.exit(2) + +def check_aliveness(username, password, timeout, cluster): + """Declares a test queue, then publishes and consumes a message. Intended for use by monitoring tools. If everything is working correctly, will return HTTP status 200 with body""" + try: + r = requests.get("http://%s:15672/api/aliveness-test/%%2F" % cluster, auth=(username, password), timeout=timeout) + except requests.exceptions.RequestException as e: # Throw error if rabbitmq is down + print("Critical - %s" % e) + sys.exit(2) + if r.status_code == 200: + print("OK - RABBITMQ Aliveness Test Returns: %s" % r) + sys.exit(0) + elif r.status_code != 200: + print("CRITICAL - RabbitMQ Error: %s" % r.content) + sys.exit(2) + else: + print("UNKNOWN - RABBITMQ Aliveness Test") + sys.ext(1) + +def check_cluster(username, password, timeout, cluster): + """Checks the health of a cluster, if a node is not running mark as offline """ + try: + url = "http://%s:15672/api/nodes" % cluster + r = requests.get(url, auth=(username, password), timeout=timeout) + except requests.exceptions.RequestException as e: # Throw error if no response + print("Critical - %s" % e) + sys.exit(2) + text = r.text + nodes = json.loads(text) + + running_nodes = [] + failed_nodes = [] + for node in nodes: + if not node['running']: + failed_nodes.append(node['name']) + if node['running']: + running_nodes.append(node['name']) + if len(failed_nodes) == 1: + print("WARNING: RabbitMQ cluster is degraged: Not running %s" % failed_nodes[0]) + sys.exit(1) + elif len(failed_nodes) >= 2: + print("CRITICAL: RabbitMQ cluster is critical: Not running %s" % failed_nodes) + sys.exit(2) + else: + print("OK: RabbitMQ cluster members: %s" % (" ".join(running_nodes))) + sys.exit(0) + + +USAGE = """Usage: ./check_rabbitmq -a [action] -C [critical] -W [warning] + Actions: + - connection_count + checks the number of connection in rabbitmq's list_connections + - queues_count + checks the count in each of the queues in rabbitmq's list_queues + - mem_usage + checks to ensure mem usage of rabbitmq process does not exceed 50% + - aliveness + Use the /api/aliveness-test API to send/receive a message. (requires -u username -p password args) + - cluster_status + Parse /api/nodes to check the cluster status. (requires -u username -p password""" + +if __name__ == "__main__": + parser = OptionParser(USAGE) + parser.add_option("-a", "--action", dest="action", + help="Action to Check") + parser.add_option("-C", "--critical", dest="critical", + type="int", help="Critical Threshold") + parser.add_option("-W", "--warning", dest="warning", + type="int", help="Warning Threshold") + parser.add_option("-u", "--username", dest="username", default="guest", + type="string", help="RabbitMQ username, Default guest") + parser.add_option("-p", "--password", dest="password", default="guest", + type="string", help="RabbitMQ password, Default guest") + parser.add_option("-t", "--timeout", dest="timeout", default=1, + type="int", help="Request Timeout, defaults to 1 second") + parser.add_option("-c", "--cluster", dest="cluster", default="localhost", + type="string", help="Cluster IP/DNS name, defaults to localhost") + (options, args) = parser.parse_args() + + if options.action == "connection_count": + check_connection_count(options.critical, options.warning) + elif options.action == "queues_count": + check_queues_count(options.critical, options.warning) + elif options.action == "mem_usage": + check_mem_usage(options.critical, options.warning) + elif options.action == "aliveness": + check_aliveness(options.username, options.password, options.timeout, options.cluster) + elif options.action == "cluster_status": + check_cluster(options.username, options.password, options.timeout, options.cluster) + else: + print("Invalid action: %s" % options.action) + print(USAGE) diff --git a/rabbitmq/handlers/main.yml b/rabbitmq/handlers/main.yml index 9f73baa6..ecd03471 100644 --- a/rabbitmq/handlers/main.yml +++ b/rabbitmq/handlers/main.yml @@ -1,15 +1,15 @@ --- - name: restart rabbitmq - service: + ansible.builtin.service: name: rabbitmq-server state: restarted - name: restart nagios-nrpe-server - service: + ansible.builtin.service: name: nagios-nrpe-server state: restarted - name: restart munin-node - service: + ansible.builtin.service: name: munin-node state: restarted diff --git a/rabbitmq/tasks/main.yml b/rabbitmq/tasks/main.yml index c8e49407..d78c70fd 100644 --- a/rabbitmq/tasks/main.yml +++ b/rabbitmq/tasks/main.yml @@ -1,49 +1,55 @@ - name: Install packages - apt: + ansible.builtin.apt: name: rabbitmq-server state: present - name: Create rabbitmq-env.conf - copy: + ansible.builtin.copy: src: evolinux-rabbitmq-env.conf dest: /etc/rabbitmq/rabbitmq-env.conf owner: rabbitmq group: rabbitmq mode: "0600" - force: no + force: false - name: Create rabbitmq.config - copy: + ansible.builtin.copy: src: evolinux-rabbitmq.config dest: /etc/rabbitmq/rabbitmq.config owner: rabbitmq group: rabbitmq mode: "0600" - force: no + force: false - name: Adjust ulimit - lineinfile: + ansible.builtin.lineinfile: dest: /etc/default/rabbitmq-server line: ulimit -n 2048 - name: is NRPE present ? - stat: + ansible.builtin.stat: path: /etc/nagios/nrpe.d/evolix.cfg check_mode: no register: nrpe_evolix_config tags: - nrpe -- include: nrpe.yml +- ansible.builtin.include: nrpe.yml when: nrpe_evolix_config.stat.exists - name: is Munin present ? - stat: + ansible.builtin.stat: path: /etc/munin check_mode: no register: etc_munin_directory tags: - nrpe -- include: munin.yml +- ansible.builtin.include: munin.yml when: etc_munin_directory.stat.exists + +- name: entry for RabbitMQ in web page is present + ansible.builtin.lineinfile: + dest: /var/www/index.html + insertbefore: '' + line: '
  • RabbitMQ
  • ' diff --git a/rabbitmq/tasks/munin.yml b/rabbitmq/tasks/munin.yml index cb872391..63ad5a15 100644 --- a/rabbitmq/tasks/munin.yml +++ b/rabbitmq/tasks/munin.yml @@ -1,13 +1,13 @@ --- -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr tags: - rabbitmq - munin - name: Create local munin directory - file: + ansible.builtin.file: name: /usr/local/share/munin/ state: directory mode: "0755" @@ -16,7 +16,7 @@ - munin - name: Create local plugins directory - file: + ansible.builtin.file: name: /usr/local/share/munin/plugins/ state: directory mode: "0755" @@ -25,7 +25,7 @@ - munin - name: Copy rabbitmq_connections munin plugin - copy: + ansible.builtin.copy: src: rabbitmq_connections dest: /usr/local/share/munin/plugins/rabbitmq_connections mode: "0755" @@ -35,7 +35,7 @@ - munin - name: Enable rabbitmq_connections munin plugin - file: + ansible.builtin.file: src: /usr/local/share/munin/plugins/rabbitmq_connections dest: "/etc/munin/plugins/rabbitmq_connections" state: link diff --git a/rabbitmq/tasks/nrpe.yml b/rabbitmq/tasks/nrpe.yml index 4272f57b..d181b07c 100644 --- a/rabbitmq/tasks/nrpe.yml +++ b/rabbitmq/tasks/nrpe.yml @@ -1,39 +1,50 @@ --- - name: python-requests is installed (check_rabbitmq dependency) - apt: + ansible.builtin.apt: name: python-requests state: present when: ansible_python_version is version('3', '<') - name: python3-requests is installed (check_rabbitmq dependency) - apt: + ansible.builtin.apt: name: python3-requests state: present when: ansible_python_version is version('3', '>=') -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr # https://raw.githubusercontent.com/CaptPhunkosis/check_rabbitmq/master/check_rabbitmq - name: check_rabbitmq is installed - copy: + ansible.builtin.copy: src: check_rabbitmq dest: /usr/local/lib/nagios/plugins/check_rabbitmq owner: root group: root mode: "0755" - force: yes + force: true + when: ansible_distribution_major_version is version('11', '<=') + +- name: check_rabbitmq (Python 3 version) is installed + ansible.builtin.copy: + src: check_rabbitmq.python3 + dest: /usr/local/lib/nagios/plugins/check_rabbitmq + owner: root + group: root + mode: "0755" + force: true + when: ansible_distribution_major_version is version('11', '>=') - name: check_rabbitmq is available for NRPE - lineinfile: + ansible.builtin.lineinfile: dest: /etc/nagios/nrpe.d/evolix.cfg regexp: 'command\[check_rab_connection_count\]' line: 'command[check_rab_connection_count]=sudo /usr/local/lib/nagios/plugins/check_rabbitmq -a connection_count -C {{ rabbitmq_connections_critical }} -W {{ rabbitmq_connections_warning }}' notify: restart nagios-nrpe-server - name: sudo without password for nagios - lineinfile: + ansible.builtin.lineinfile: dest: /etc/sudoers.d/evolinux regexp: 'check_rabbitmq' line: 'nagios ALL = NOPASSWD: /usr/local/lib/nagios/plugins/check_rabbitmq' diff --git a/rbenv/tasks/main.yml b/rbenv/tasks/main.yml index de366e78..ea73a9e6 100644 --- a/rbenv/tasks/main.yml +++ b/rbenv/tasks/main.yml @@ -1,7 +1,7 @@ --- - name: "Rbenv dependencies are installed" - apt: + ansible.builtin.apt: name: - build-essential - git @@ -19,7 +19,7 @@ - packages - name: "gemrc for {{ username }}" - copy: + ansible.builtin.copy: src: gemrc dest: "~{{ username }}/.gemrc" owner: '{{ username }}' @@ -28,19 +28,19 @@ - rbenv - name: "Rbenv repository is checked out for {{ username }}" - git: + ansible.builtin.git: repo: '{{ rbenv_repo }}' dest: '{{ rbenv_root }}' version: '{{ rbenv_version }}' accept_hostkey: yes - force: yes + force: true become_user: "{{ username }}" - become: yes + become: true tags: - rbenv - name: "default gems are installed for {{ username }}" - lineinfile: + ansible.builtin.lineinfile: dest: '{{ rbenv_root }}/default-gems' line: "{{ item }}" owner: '{{ username }}' @@ -48,86 +48,82 @@ create: yes loop: '{{ rbenv_default_gems }}' become_user: "{{ username }}" - become: yes + become: true tags: - rbenv - name: "plugins directory for {{ username }}" - file: + ansible.builtin.file: path: '{{ rbenv_root }}/plugins' state: directory become_user: "{{ username }}" - become: yes + become: true tags: - rbenv - name: "plugins are installed for {{ username }}" - git: + ansible.builtin.git: repo: '{{ item.repo }}' dest: '{{ rbenv_root }}/plugins/{{ item.name }}' version: '{{ item.version }}' accept_hostkey: yes - force: yes + force: true loop: "{{ rbenv_plugins }}" become_user: "{{ username }}" - become: yes + become: true tags: - rbenv - name: "Rbenv is initialized in profile for {{ username }}" - blockinfile: + ansible.builtin.blockinfile: dest: '~{{ username }}/.profile' marker: "# {mark} ANSIBLE MANAGED RBENV INIT" block: | export PATH="{{ rbenv_root }}/bin:$PATH" eval "$(rbenv init -)" become_user: "{{ username }}" - become: yes + become: true tags: - rbenv - name: "is Ruby {{ rbenv_ruby_version }} available for {{ username }} ?" - shell: /bin/bash -lc "rbenv versions | grep {{ rbenv_ruby_version }}" - args: - warn: no + ansible.builtin.shell: + cmd: /bin/bash -lc "rbenv versions | grep {{ rbenv_ruby_version }}" failed_when: False changed_when: False check_mode: False register: ruby_installed become_user: "{{ username }}" - become: yes + become: true tags: - rbenv - name: "Ruby {{ rbenv_ruby_version }} is available for {{ username }} (be patient... could be long)" - shell: /bin/bash -lc "TMPDIR=~/tmp rbenv install {{ rbenv_ruby_version }}" - args: - warn: no + ansible.builtin.shell: + cmd: /bin/bash -lc "TMPDIR=~/tmp rbenv install {{ rbenv_ruby_version }}" when: ruby_installed.rc != 0 become_user: "{{ username }}" - become: yes + become: true tags: - rbenv - name: "is Ruby {{ rbenv_ruby_version }} selected for {{ username }} ?" - shell: /bin/bash -lc "rbenv version | cut -d ' ' -f 1 | grep -Fx '{{ rbenv_ruby_version }}'" - args: - warn: no + ansible.builtin.shell: + cmd: /bin/bash -lc "rbenv version | cut -d ' ' -f 1 | grep -Fx '{{ rbenv_ruby_version }}'" register: ruby_selected changed_when: False failed_when: False check_mode: False become_user: "{{ username }}" - become: yes + become: true tags: - rbenv - name: "select Ruby {{ rbenv_ruby_version }} for {{ username }}" - shell: /bin/bash -lc "rbenv global {{ rbenv_ruby_version }} && rbenv rehash" - args: - warn: no + ansible.builtin.shell: + cmd: /bin/bash -lc "rbenv global {{ rbenv_ruby_version }} && rbenv rehash" when: ruby_selected.rc != 0 become_user: "{{ username }}" - become: yes + become: true tags: - rbenv diff --git a/redis/README.md b/redis/README.md index 850af13a..57aa4f41 100644 --- a/redis/README.md +++ b/redis/README.md @@ -14,7 +14,7 @@ Main variables are : * `redis_conf_dir`: config directory ; * `redis_port`: listening TCP port ; -* `redis_bind_interface`: listening IP address ; +* `redis_bind_interfaces`: listening IP addresses (array) ; * `redis_password`: password for redis. Empty means no password ; * `redis_socket_dir`: Unix socket directory ; * `redis_log_level`: log verbosity ; diff --git a/redis/defaults/main.yml b/redis/defaults/main.yml index 93bbc741..b5547597 100644 --- a/redis/defaults/main.yml +++ b/redis/defaults/main.yml @@ -6,7 +6,8 @@ redis_conf_dir_prefix: /etc/redis redis_force_instance_port: False redis_port: 6379 -redis_bind_interface: 127.0.0.1 +redis_bind_interfaces: + - 127.0.0.1 redis_socket_enabled: True redis_socket_dir_prefix: '/run/redis' @@ -60,5 +61,9 @@ redis_sentinel_install: False redis_default_server_disabled: False +# Set to Null to leave as is +# Set to "always", "madvise" or "never" for custom value +redis_sysctl_transparent_hugepage_enabled: Null + general_alert_email: "root@localhost" log2mail_alert_email: Null diff --git a/redis/handlers/main.yml b/redis/handlers/main.yml index d85dcbf8..73a7a09d 100644 --- a/redis/handlers/main.yml +++ b/redis/handlers/main.yml @@ -1,25 +1,30 @@ --- - name: restart redis - systemd: + ansible.builtin.systemd: name: "{{ redis_systemd_name }}" state: restarted - name: restart redis (noop) - meta: noop + ansible.builtin.meta: noop failed_when: False changed_when: False - name: restart munin-node - service: + ansible.builtin.service: name: munin-node state: restarted - name: restart nagios-nrpe-server - service: + ansible.builtin.service: name: nagios-nrpe-server state: restarted - name: restart log2mail - service: + ansible.builtin.service: name: log2mail state: restarted + +- name: restart sysfsutils + ansible.builtin.service: + name: sysfsutils + state: restarted diff --git a/redis/tasks/default-log2mail.yml b/redis/tasks/default-log2mail.yml index 21628b0c..55466e16 100644 --- a/redis/tasks/default-log2mail.yml +++ b/redis/tasks/default-log2mail.yml @@ -1,7 +1,7 @@ --- - name: log2mail config is present - blockinfile: + ansible.builtin.blockinfile: dest: /etc/log2mail/config/redis.conf owner: log2mail group: adm @@ -17,3 +17,14 @@ tags: - redis - log2mail + +- name: log2mail user is in redis group + ansible.builtin.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..44c45011 100644 --- a/redis/tasks/default-munin.yml +++ b/redis/tasks/default-munin.yml @@ -1,18 +1,18 @@ --- - name: Install munin check dependencies - apt: + ansible.builtin.apt: name: libswitch-perl state: present tags: - redis -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr tags: - redis - name: Create plugin directory - file: + ansible.builtin.file: name: /usr/local/share/munin/ state: directory mode: "0755" @@ -20,7 +20,7 @@ - redis - name: Create plugin directory - file: + ansible.builtin.file: name: /usr/local/share/munin/plugins/ state: directory mode: "0755" @@ -28,7 +28,7 @@ - redis - name: Copy redis munin plugin - copy: + ansible.builtin.copy: src: munin_redis dest: /usr/local/share/munin/plugins/redis_ mode: "0755" @@ -37,9 +37,9 @@ - redis - name: Enable redis munin plugin - file: + ansible.builtin.file: src: /usr/local/share/munin/plugins/redis_ - dest: "/etc/munin/plugins/redis_{{item}}" + dest: "/etc/munin/plugins/redis_{{ plugin_name }}" state: link loop: - connected_clients @@ -48,19 +48,23 @@ - per_sec - used_keys - used_memory + loop_control: + loop_var: plugin_name notify: restart munin-node + when: not ansible_check_mode tags: - redis - name: Count redis condif blocks in munin-node configuration - command: grep -c "\[redis_" /etc/munin/plugin-conf.d/munin-node + ansible.builtin.command: + cmd: grep -c "\[redis_" /etc/munin/plugin-conf.d/munin-node register: munin_redis_blocs_in_config failed_when: False changed_when: False check_mode: no - name: Add redis password for munin (if no more than 1 config block) - ini_file: + community.general.ini_file: dest: /etc/munin/plugin-conf.d/munin-node section: 'redis_*' option: env.password @@ -74,7 +78,7 @@ - name: Warn if multiple instance in munin-plugins configuration - debug: + ansible.builtin.debug: msg: "WARNING - It seems you have multiple redis sections in your munin-node configuration - Munin config NOT changed" when: - redis_password is not none diff --git a/redis/tasks/default-server.yml b/redis/tasks/default-server.yml index 08653cfa..89a664e6 100644 --- a/redis/tasks/default-server.yml +++ b/redis/tasks/default-server.yml @@ -1,7 +1,7 @@ --- - name: Redis is configured. - template: + ansible.builtin.template: src: redis.conf.j2 dest: "{{ redis_conf_dir }}/redis.conf" mode: "0640" @@ -11,8 +11,17 @@ tags: - redis +- name: Config directory permissions are set + ansible.builtin.file: + dest: "{{ redis_conf_dir }}" + mode: "0750" + owner: redis + group: redis + tags: + - redis + - name: Redis is running and enabled on boot. - systemd: + ansible.builtin.systemd: name: "{{ redis_systemd_name }}" enabled: yes state: started diff --git a/redis/tasks/instance-log2mail.yml b/redis/tasks/instance-log2mail.yml index a20e1a0a..c57e5745 100644 --- a/redis/tasks/instance-log2mail.yml +++ b/redis/tasks/instance-log2mail.yml @@ -1,7 +1,7 @@ --- - name: log2mail config is present - blockinfile: + ansible.builtin.blockinfile: dest: /etc/log2mail/config/redis.conf owner: log2mail group: adm diff --git a/redis/tasks/instance-munin.yml b/redis/tasks/instance-munin.yml index 80c67c6f..3d2274e7 100644 --- a/redis/tasks/instance-munin.yml +++ b/redis/tasks/instance-munin.yml @@ -1,18 +1,18 @@ --- - name: Install munin check dependencies - apt: + ansible.builtin.apt: name: libswitch-perl state: present tags: - redis -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr tags: - redis - name: Create plugin directory - file: + ansible.builtin.file: name: /usr/local/share/munin/ state: directory mode: "0755" @@ -20,7 +20,7 @@ - redis - name: Create plugin directory - file: + ansible.builtin.file: name: /usr/local/share/munin/plugins/ state: directory mode: "0755" @@ -28,7 +28,7 @@ - redis - name: Copy redis munin plugin - copy: + ansible.builtin.copy: src: munin_redis dest: /usr/local/share/munin/plugins/redis_ mode: "0755" @@ -37,9 +37,9 @@ - redis - name: Enable redis munin plugin - file: + ansible.builtin.file: src: /usr/local/share/munin/plugins/redis_ - dest: "/etc/munin/plugins/{{ redis_instance_name }}_redis_{{item}}" + dest: "/etc/munin/plugins/{{ redis_instance_name }}_redis_{{ plugin_name }}" state: link loop: - connected_clients @@ -48,14 +48,17 @@ - per_sec - used_keys - used_memory + loop_control: + loop_var: plugin_name notify: restart munin-node + when: not ansible_check_mode tags: - redis - name: Configure redis plugin for munin - template: + ansible.builtin.template: src: templates/munin-plugin-instances.conf.j2 dest: '/etc/munin/plugin-conf.d/evolinux.redis_{{ redis_instance_name }}' - mode: 0740 + mode: "0740" notify: restart munin-node tags: redis diff --git a/redis/tasks/instance-server.yml b/redis/tasks/instance-server.yml index 462ee8f4..42dc1876 100644 --- a/redis/tasks/instance-server.yml +++ b/redis/tasks/instance-server.yml @@ -1,14 +1,15 @@ --- - name: Verify Redis port - assert: + ansible.builtin.assert: that: - redis_port | int != 6379 msg: "If you want to use port 6379, use the default instance, not a named instance." when: not (redis_force_instance_port | bool) - name: "Instance '{{ redis_instance_name }}' group is present" - group: + + ansible.builtin.group: name: "redis-{{ redis_instance_name }}" state: present system: True @@ -16,7 +17,7 @@ - redis - name: "Instance '{{ redis_instance_name }}' user is present" - user: + ansible.builtin.user: name: "redis-{{ redis_instance_name }}" group: "redis-{{ redis_instance_name }}" state: present @@ -26,22 +27,22 @@ - redis - name: "Instance '{{ redis_instance_name }}' config directory is present" - file: + ansible.builtin.file: dest: "{{ redis_conf_dir }}" - mode: "0755" - owner: "root" - group: "root" + mode: "0750" + owner: "redis-{{ redis_instance_name }}" + group: "redis-{{ redis_instance_name }}" follow: yes state: directory tags: - redis - name: "Instance '{{ redis_instance_name }}' config hooks directories are present" - file: - dest: "{{ item }}" - mode: "0755" - owner: "root" - group: "root" + ansible.builtin.file: + dest: "{{ _dir }}" + mode: "0750" + owner: "redis-{{ redis_instance_name }}" + group: "redis-{{ redis_instance_name }}" follow: yes state: directory loop: @@ -49,6 +50,8 @@ - "{{ redis_conf_dir }}/redis-server.post-up.d" - "{{ redis_conf_dir }}/redis-server.pre-down.d" - "{{ redis_conf_dir }}/redis-server.post-down.d" + loop_control: + loop_var: _dir when: - ansible_distribution == "Debian" - ansible_distribution_major_version is version('9', '=') @@ -56,14 +59,17 @@ - redis - name: "Instance '{{ redis_instance_name }}' hooks examples are present" - command: "cp -a /etc/redis/{{ item }}/00_example {{ redis_conf_dir }}/{{ item }}" + ansible.builtin.command: + cmd: "cp -a /etc/redis/{{ _dir }}/00_example {{ redis_conf_dir }}/{{ _dir }}" args: - creates: "{{ redis_conf_dir }}/{{ item }}/00_example" + creates: "{{ redis_conf_dir }}/{{ _dir }}/00_example" loop: - "redis-server.pre-up.d" - "redis-server.post-up.d" - "redis-server.pre-down.d" - "redis-server.post-down.d" + loop_control: + loop_var: _dir when: - ansible_distribution == "Debian" - ansible_distribution_major_version is version('9', '=') @@ -71,8 +77,8 @@ - redis - name: "Instance '{{ redis_instance_name }}' socket/pid directories are present" - file: - dest: "{{ item }}" + ansible.builtin.file: + dest: "{{ _dir }}" mode: "0755" owner: "redis-{{ redis_instance_name }}" group: "redis-{{ redis_instance_name }}" @@ -81,12 +87,14 @@ loop: - "{{ redis_pid_dir }}" - "{{ redis_socket_dir }}" + loop_control: + loop_var: _dir tags: - redis - name: "Instance '{{ redis_instance_name }}' data/log directories are present" - file: - dest: "{{ item }}" + ansible.builtin.file: + dest: "{{ _dir }}" mode: "0751" owner: "redis-{{ redis_instance_name }}" group: "redis-{{ redis_instance_name }}" @@ -95,11 +103,13 @@ loop: - "{{ redis_data_dir }}" - "{{ redis_log_dir }}" + loop_control: + loop_var: _dir tags: - redis - name: "Instance '{{ redis_instance_name }}' log file are present" - file: + ansible.builtin.file: path: "{{ redis_log_dir }}/redis-server.log" mode: "660" owner: "redis-{{ redis_instance_name }}" @@ -110,7 +120,7 @@ - name: "Instance '{{ redis_instance_name }}' configuration file is present" - template: + ansible.builtin.template: src: redis.conf.j2 dest: "{{ redis_conf_dir }}/redis.conf" mode: "0640" @@ -121,7 +131,7 @@ - redis - name: Systemd template for redis instances is installed (Debian 8) - template: + ansible.builtin.template: src: 'redis-server@jessie.service.j2' dest: '/etc/systemd/system/redis-server@.service' mode: "0644" @@ -134,7 +144,7 @@ - redis - name: Systemd template for redis instances is installed (Debian 9) - template: + ansible.builtin.template: src: 'redis-server@stretch.service.j2' dest: '/etc/systemd/system/redis-server@.service' mode: "0644" @@ -147,7 +157,7 @@ - redis - name: Systemd template for redis instances is installed (Debian 10 or later) - template: + ansible.builtin.template: src: 'redis-server@buster.service.j2' dest: '/etc/systemd/system/redis-server@.service' mode: "0644" @@ -160,7 +170,7 @@ - redis - name: "Instance '{{ redis_instance_name }}' systemd unit is enabled and started" - systemd: + ansible.builtin.systemd: name: "{{ redis_systemd_name }}" enabled: yes state: started @@ -169,7 +179,7 @@ - redis - name: Redis SysVinit script is stopped and disabled - service: + ansible.builtin.service: name: "redis-server" enabled: no state: stopped diff --git a/redis/tasks/main.yml b/redis/tasks/main.yml index 90f0aa12..1077811b 100644 --- a/redis/tasks/main.yml +++ b/redis/tasks/main.yml @@ -1,10 +1,22 @@ --- -- set_fact: +- ansible.builtin.set_fact: redis_restart_handler_name: "{{ redis_restart_if_needed | bool | ternary('restart redis', 'restart redis (noop)') }}" -- name: Redis is installed. - apt: +- name: Linux kernel overcommit memory setting is enabled + ansible.posix.sysctl: + name: "vm.overcommit_memory" + value: "1" + sysctl_file: "/etc/sysctl.d/evolinux-redis.conf" + state: present + reload: yes + +- name: Customize Kernel Transparent Huge Page + ansible.builtin.include: thp.yml + when: redis_sysctl_transparent_hugepage_enabled is not none + +- name: Redis is installed + ansible.builtin.apt: name: - redis-server - redis-tools @@ -13,8 +25,8 @@ - redis - packages -- name: Redis Sentinel is installed. - apt: +- name: Redis Sentinel is installed + ansible.builtin.apt: name: "redis-sentinel" state: present tags: @@ -23,21 +35,22 @@ when: redis_sentinel_install | bool - name: Get Redis version - shell: "redis-server -v | grep -Eo '(v=\\S+)' | cut -d'=' -f 2 | grep -E '^([0-9]|\\.)+$'" - changed_when: false + ansible.builtin.shell: + cmd: "redis-server -v | grep -Eo '(v=\\S+)' | cut -d'=' -f 2 | grep -E '^([0-9]|\\.)+$'" + changed_when: False check_mode: no register: _redis_installed_version tags: - redis -- set_fact: +- ansible.builtin.set_fact: redis_installed_version: "{{ _redis_installed_version.stdout }}" check_mode: no tags: - redis - name: set variables for default mode - set_fact: + ansible.builtin.set_fact: redis_conf_dir: "{{ redis_conf_dir_prefix }}" redis_socket_dir: "{{ redis_socket_dir_prefix }}" redis_pid_dir: "{{ redis_pid_dir_prefix }}" @@ -46,7 +59,7 @@ when: redis_instance_name is not defined - name: set variables for instance mode - set_fact: + ansible.builtin.set_fact: redis_systemd_name: "redis-server@{{ redis_instance_name }}" redis_conf_dir: "{{ redis_conf_dir_prefix }}-{{ redis_instance_name }}" redis_socket_dir: "{{ redis_socket_dir_prefix }}-{{ redis_instance_name }}" @@ -55,16 +68,24 @@ redis_data_dir: "{{ redis_data_dir_prefix }}-{{ redis_instance_name }}" when: redis_instance_name is defined +- name: Fail if redis_bind_interface is set + ansible.builtin.fail: + msg: "Please change 'redis_bind_interface' (String) to 'redis_bind_interfaces' (List)" + when: + - redis_bind_interface is defined + - redis_bind_interface is not none + - redis_bind_interface | length > 0 + - name: configure Redis for default mode - include: default-server.yml + ansible.builtin.include: default-server.yml when: redis_instance_name is not defined - name: configure Redis for instance mode - include: instance-server.yml + ansible.builtin.include: instance-server.yml when: redis_instance_name is defined - name: Is Munin installed - stat: + ansible.builtin.stat: path: /etc/munin/plugins register: _munin_installed tags: @@ -72,7 +93,7 @@ - munin - name: configure Munin for default mode - include: default-munin.yml + ansible.builtin.include: default-munin.yml when: - _munin_installed.stat.exists - _munin_installed.stat.isdir @@ -82,7 +103,7 @@ - munin - name: configure Munin for instance mode - include: instance-munin.yml + ansible.builtin.include: instance-munin.yml when: - _munin_installed.stat.exists - _munin_installed.stat.isdir @@ -92,7 +113,7 @@ - munin - name: Is log2mail installed - stat: + ansible.builtin.stat: path: /etc/log2mail/config register: _log2mail_installed tags: @@ -100,7 +121,7 @@ - log2mail - name: configure log2mail for default mode - include: default-log2mail.yml + ansible.builtin.include: default-log2mail.yml when: - _log2mail_installed.stat.exists - _log2mail_installed.stat.isdir @@ -110,7 +131,7 @@ - log2mail - name: configure log2mail for instance mode - include: instance-log2mail.yml + ansible.builtin.include: instance-log2mail.yml when: - _log2mail_installed.stat.exists - _log2mail_installed.stat.isdir @@ -120,7 +141,7 @@ - log2mail - name: is NRPE present ? - stat: + ansible.builtin.stat: path: /etc/nagios/nrpe.d/evolix.cfg check_mode: no register: nrpe_evolix_config @@ -128,13 +149,14 @@ - redis - nrpe -- include: nrpe.yml +- ansible.builtin.include: nrpe.yml when: nrpe_evolix_config.stat.exists tags: - redis - nrpe - name: Force restart redis - command: /bin/true + ansible.builtin.command: + cmd: /bin/true notify: restart redis when: redis_restart_force | bool diff --git a/redis/tasks/nrpe.yml b/redis/tasks/nrpe.yml index b317c4e6..a786c78f 100644 --- a/redis/tasks/nrpe.yml +++ b/redis/tasks/nrpe.yml @@ -1,7 +1,7 @@ --- - name: Install perl lib-redis (needed by check_redis) - apt: + ansible.builtin.apt: name: libredis-perl state: present tags: @@ -9,10 +9,10 @@ - nrpe - name: install check_redis on Jessie - copy: + ansible.builtin.copy: src: check_redis.pl dest: /usr/local/lib/nagios/plugins/check_redis - force: yes + force: true mode: "0755" owner: root group: root @@ -24,7 +24,7 @@ - nrpe - name: set the path of check_redis on Jessie - set_fact: + ansible.builtin.set_fact: redis_check_redis_path: /usr/local/lib/nagios/plugins/check_redis when: - ansible_distribution == "Debian" @@ -34,7 +34,7 @@ - nrpe - name: set the path of check_redis on Stretch and later - set_fact: + ansible.builtin.set_fact: redis_check_redis_path: /usr/lib/nagios/plugins/check_redis when: - ansible_distribution == "Debian" @@ -44,7 +44,7 @@ - nrpe - name: sudo without password for nagios - lineinfile: + ansible.builtin.lineinfile: dest: /etc/sudoers.d/evolinux regexp: 'check_redis$' line: 'nagios ALL = NOPASSWD: {{ redis_check_redis_path }}' @@ -57,10 +57,10 @@ - nrpe - name: Use check_redis for NRPE - replace: + ansible.builtin.replace: dest: /etc/nagios/nrpe.d/evolix.cfg regexp: '^command\[check_redis\]=.+' - replace: 'command[check_redis]=sudo {{ redis_check_redis_path }} -H {{ redis_bind_interface }} -p {{ redis_port }}' + replace: 'command[check_redis]=sudo {{ redis_check_redis_path }} -H {{ redis_bind_interfaces | first }} -p {{ redis_port }}' when: redis_instance_name is undefined notify: restart nagios-nrpe-server tags: @@ -68,7 +68,7 @@ - nrpe - name: sudo without password for nagios - lineinfile: + ansible.builtin.lineinfile: dest: /etc/sudoers.d/evolinux regexp: 'check_redis$' line: 'nagios ALL = NOPASSWD: {{ redis_check_redis_path }}' @@ -79,11 +79,15 @@ - redis - nrpe +- name: "Remount /usr with RW for 'install check_redis instance'" + ansible.builtin.include_role: + name: evolix/remount-usr + - name: install check_redis_instances - copy: + ansible.builtin.copy: src: check_redis_instances.sh dest: /usr/local/lib/nagios/plugins/check_redis_instances - force: yes + force: true mode: "0755" owner: root group: root @@ -92,7 +96,7 @@ - nrpe - name: Use check_redis_instances for NRPE - replace: + ansible.builtin.replace: dest: /etc/nagios/nrpe.d/evolix.cfg regexp: '^command\[check_redis\]=.+' replace: 'command[check_redis]=sudo /usr/local/lib/nagios/plugins/check_redis_instances' @@ -103,7 +107,7 @@ - nrpe - name: sudo without password for nagios - lineinfile: + ansible.builtin.lineinfile: dest: /etc/sudoers.d/evolinux regexp: 'check_redis_instances$' line: 'nagios ALL = NOPASSWD: /usr/local/lib/nagios/plugins/check_redis_instances' diff --git a/redis/tasks/thp.yml b/redis/tasks/thp.yml new file mode 100644 index 00000000..7a215788 --- /dev/null +++ b/redis/tasks/thp.yml @@ -0,0 +1,35 @@ +--- + +- name: sysfsutils is installed + ansible.builtin.apt: + name: + - sysfsutils + state: present + tags: + - redis + - packages + - kernel + +- name: Check possible values for THP + ansible.builtin.assert: + that: redis_sysctl_transparent_hugepage_enabled is in ['always', 'madvise', 'never'] + msg: "redis_sysctl_transparent_hugepage_enabled has incorrect value : '{{ redis_sysctl_transparent_hugepage_enabled }}' not in ['always', 'madvise', 'never']" + tags: + - redis + - kernel + +- name: "Set THP to {{ redis_sysctl_transparent_hugepage_enabled }} at boot" + ansible.builtin.lineinfile: + path: /etc/sysfs.conf + line: kernel/mm/transparent_hugepage/enabled = {{ redis_sysctl_transparent_hugepage_enabled }} + regexp: "kernel/mm/transparent_hugepage/enabled" + tags: + - redis + - kernel + +- name: "Set THP to {{ redis_sysctl_transparent_hugepage_enabled }} for this boot" + ansible.builtin.shell: + cmd: "echo '{{ redis_sysctl_transparent_hugepage_enabled }}' >> /sys/kernel/mm/transparent_hugepage/enabled" + tags: + - redis + - kernel \ No newline at end of file diff --git a/redis/templates/redis.conf.j2 b/redis/templates/redis.conf.j2 index b10a11b9..4afced22 100644 --- a/redis/templates/redis.conf.j2 +++ b/redis/templates/redis.conf.j2 @@ -1,24 +1,24 @@ daemonize yes -pidfile {{ redis_pid_dir }}/redis-server.pid +pidfile "{{ redis_pid_dir }}/redis-server.pid" port {{ redis_port }} -bind {{ redis_bind_interface }} +bind {{ redis_bind_interfaces | join(' ') }} {% if redis_socket_enabled %} -unixsocket {{ redis_socket_dir }}/redis.sock +unixsocket "{{ redis_socket_dir }}/redis.sock" unixsocketperm {{ redis_socket_perms }} {% endif %} {% if redis_password %} -requirepass {{ redis_password }} +requirepass "{{ redis_password }}" {% endif %} {% if redis_password_master %} -masterauth {{ redis_password_master }} +masterauth "{{ redis_password_master }}" {% endif %} timeout {{ redis_timeout }} loglevel {{ redis_log_level }} -logfile {{ redis_log_dir }}/redis-server.log +logfile "{{ redis_log_dir }}/redis-server.log" # To enable logging to the system logger, just set 'syslog-enabled' to yes, # and optionally update the other syslog parameters to suit your needs. @@ -33,8 +33,8 @@ save {{ save }} {% endfor %} rdbcompression {{ redis_rdbcompression | bool | ternary('yes','no') }} -dbfilename {{ redis_data_file }} -dir {{ redis_data_dir }} +dbfilename "{{ redis_data_file }}" +dir "{{ redis_data_dir }}" {% if redis_installed_version is version('3.2', '>=') %} protected-mode {{ redis_protected_mode | bool | ternary('yes','no') }} diff --git a/redmine/handlers/main.yml b/redmine/handlers/main.yml index 3759afc4..595d83f4 100644 --- a/redmine/handlers/main.yml +++ b/redmine/handlers/main.yml @@ -1,10 +1,10 @@ --- - name: restart rsyslog - service: + ansible.builtin.service: name: rsyslog state: restarted - name: reload nginc - service: + ansible.builtin.service: name: nginx state: reloaded diff --git a/redmine/tasks/config.yml b/redmine/tasks/config.yml index e45bcea5..282f20f6 100644 --- a/redmine/tasks/config.yml +++ b/redmine/tasks/config.yml @@ -1,6 +1,6 @@ --- - name: Create systemd config dir - file: + ansible.builtin.file: state: directory dest: "/home/{{ redmine_user }}/{{ item }}" mode: "0750" @@ -14,7 +14,7 @@ - redmine - name: Deploy systemd unit - copy: + ansible.builtin.copy: src: puma.service dest: "/home/{{ redmine_user }}/.config/systemd/user/puma.service" mode: "0644" @@ -24,7 +24,7 @@ - redmine - name: Set user .profile - copy: + ansible.builtin.copy: src: profile dest: "/home/{{ redmine_user }}/.profile" owner: "{{ redmine_user }}" @@ -34,7 +34,7 @@ - redmine - name: Create config directory - file: + ansible.builtin.file: path: "/home/{{ redmine_user }}/config" state: directory owner: "{{ redmine_user }}" @@ -44,7 +44,7 @@ - redmine - name: Copy configurations file - template: + ansible.builtin.template: src: "{{ item }}.j2" dest: "/home/{{ redmine_user }}/config/{{ item }}" owner: "{{ redmine_user }}" diff --git a/redmine/tasks/main.yml b/redmine/tasks/main.yml index eb5c5915..41acd751 100644 --- a/redmine/tasks/main.yml +++ b/redmine/tasks/main.yml @@ -1,8 +1,8 @@ --- -- include: packages.yml -- include: syslog.yml -- include: user.yml -- include_role: +- ansible.builtin.include: packages.yml +- ansible.builtin.include: syslog.yml +- ansible.builtin.include: user.yml +- ansible.builtin.include_role: name: evolix/rbenv vars: - username: "{{ redmine_user }}" diff --git a/redmine/tasks/mysql.yml b/redmine/tasks/mysql.yml index 6c40a338..6cf3ef36 100644 --- a/redmine/tasks/mysql.yml +++ b/redmine/tasks/mysql.yml @@ -1,15 +1,17 @@ --- - name: Get actual Mysql password - shell: "grep password /home/{{ redmine_user }}/.my.cnf | awk '{ print $3 }'" + ansible.builtin.shell: + cmd: "grep password /home/{{ redmine_user }}/.my.cnf | awk '{ print $3 }'" register: redmine_get_mysql_password check_mode: no changed_when: False - failed_when: false + failed_when: False tags: - redmine - name: Generate Mysql password - shell: perl -e 'print map{("a".."z","A".."Z",0..9)[int(rand(62))]}(1..16)' + ansible.builtin.shell: + cmd: perl -e 'print map{("a".."z","A".."Z",0..9)[int(rand(62))]}(1..16)' register: redmine_generate_mysql_password check_mode: no changed_when: False @@ -18,13 +20,13 @@ - redmine - name: Set Mysql password - set_fact: + ansible.builtin.set_fact: redmine_db_pass: "{{ redmine_generate_mysql_password.stdout | default(redmine_get_mysql_password.stdout) }}" tags: - redmine - name: Create Mysql database - mysql_db: + community.mysql.mysql_db: name: "{{ redmine_db_name }}" config_file: "/root/.my.cnf" state: present @@ -34,7 +36,7 @@ - redmine - name: Store credentials in my.cnf - ini_file: + community.general.ini_file: dest: "/home/{{ redmine_user }}/.my.cnf" owner: "{{ redmine_user }}" group: "{{ redmine_user }}" @@ -51,7 +53,7 @@ - redmine - name: Create Mysql user - mysql_user: + community.mysql.mysql_user: name: "{{ redmine_db_username }}" password: '{{ redmine_db_pass }}' priv: "{{ redmine_user }}.*:ALL" diff --git a/redmine/tasks/nginx.yml b/redmine/tasks/nginx.yml index 1ea1f40a..3ceebb0e 100644 --- a/redmine/tasks/nginx.yml +++ b/redmine/tasks/nginx.yml @@ -1,6 +1,6 @@ --- - name: Add www-data to Redmine group - user: + ansible.builtin.user: name: www-data groups: "{{ redmine_user }}" append: True @@ -9,7 +9,7 @@ - nginx - name: Copy nginx vhost - template: + ansible.builtin.template: src: nginx.conf.j2 dest: "/etc/nginx/sites-available/{{ redmine_user }}.conf" mode: "0644" @@ -19,7 +19,7 @@ - nginx - name: Enable nginx vhost - file: + ansible.builtin.file: src: "/etc/nginx/sites-available/{{ redmine_user }}.conf" dest: "/etc/nginx/sites-enabled/{{ redmine_user }}.conf" state: link diff --git a/redmine/tasks/packages.yml b/redmine/tasks/packages.yml index 294ef693..9d6978a7 100644 --- a/redmine/tasks/packages.yml +++ b/redmine/tasks/packages.yml @@ -1,6 +1,6 @@ --- - name: Install dependency - apt: + ansible.builtin.apt: name: - libpam-systemd - imagemagick @@ -20,7 +20,7 @@ # dependency for mysql_user and mysql_db - name: python modules is installed (Ansible dependency) - apt: + ansible.builtin.apt: name: - python-mysqldb - python-pymysql @@ -31,7 +31,7 @@ # dependency for mysql_user and mysql_db - name: python3 modules is installed (Ansible dependency) - apt: + ansible.builtin.apt: name: - python3-mysqldb - python3-pymysql diff --git a/redmine/tasks/release.yml b/redmine/tasks/release.yml index 548132fc..5c07e6a0 100644 --- a/redmine/tasks/release.yml +++ b/redmine/tasks/release.yml @@ -1,6 +1,7 @@ --- - name: Get id of user - command: "id -u {{ redmine_user }}" + ansible.builtin.command: + cmd: "id -u {{ redmine_user }}" register: redmine_command_user_id changed_when: False check_mode: False @@ -8,7 +9,7 @@ - redmine - name: Define user environment - set_fact: + ansible.builtin.set_fact: user_env: XDG_RUNTIME_DIR: "/run/user/{{ redmine_command_user_id.stdout }}" RAILS_ENV: production @@ -16,7 +17,7 @@ - redmine - name: Stop puma service - systemd: + ansible.builtin.systemd: name: puma daemon_reload: yes state: stopped @@ -27,7 +28,7 @@ - redmine - name: Create mysqldump directory - file: + ansible.builtin.file: path: "/home/{{ redmine_user }}/mysqldump" state: directory owner: "{{ redmine_user }}" @@ -37,7 +38,7 @@ - redmine - name: Dump mysql database - mysql_db: + community.mysql.mysql_db: state: dump config_file: "/home/{{ redmine_user }}/.my.cnf" name: "{{ redmine_db_name }}" @@ -46,7 +47,7 @@ - redmine - name: Change www link - file: + ansible.builtin.file: state: link src: "/home/{{ redmine_user }}/releases/{{ redmine_version }}" dest: "/home/{{ redmine_user }}/www" @@ -56,25 +57,28 @@ - redmine - name: Update Gemfile.lock - command: "~/.rbenv/bin/rbenv exec bundle lock" + ansible.builtin.command: + cmd: "~/.rbenv/bin/rbenv exec bundle lock" args: chdir: "/home/{{ redmine_user }}/www" become_user: "{{ redmine_user }}" - become: yes + become: true tags: - redmine - name: Update local gems with bundle - command: "~/.rbenv/bin/rbenv exec bundle install --deployment" + ansible.builtin.command: + cmd: "~/.rbenv/bin/rbenv exec bundle install --deployment" args: chdir: "/home/{{ redmine_user }}/www" become_user: "{{ redmine_user }}" - become: yes + become: true tags: - redmine - name: Generate secret token - command: "~/.rbenv/bin/rbenv exec bundle exec rake -q generate_secret_token" + ansible.builtin.command: + cmd: "~/.rbenv/bin/rbenv exec bundle exec rake -q generate_secret_token" args: chdir: "/home/{{ redmine_user }}/www" creates: "/home/{{ redmine_user }}/www/config/initializers/secret_token.rb" @@ -84,7 +88,8 @@ - redmine - name: Migrate database with rake - command: "~/.rbenv/bin/rbenv exec bundle exec rake -q db:migrate" + ansible.builtin.command: + cmd: "~/.rbenv/bin/rbenv exec bundle exec rake -q db:migrate" args: chdir: "/home/{{ redmine_user }}/www/" become_user: "{{ redmine_user }}" @@ -93,7 +98,8 @@ - redmine - name: Populate Mysql database - command: "~/.rbenv/bin/rbenv exec bundle exec rake -q redmine:load_default_data REDMINE_LANG=fr" + ansible.builtin.command: + cmd: "~/.rbenv/bin/rbenv exec bundle exec rake -q redmine:load_default_data REDMINE_LANG=fr" args: chdir: "/home/{{ redmine_user }}/www/" become_user: "{{ redmine_user }}" @@ -103,7 +109,8 @@ - redmine - name: Migrate plugins - command: "~/.rbenv/bin/rbenv exec bundle exec rake -q redmine:plugins:migrate" + ansible.builtin.command: + cmd: "~/.rbenv/bin/rbenv exec bundle exec rake -q redmine:plugins:migrate" args: chdir: "/home/{{ redmine_user }}/www/" become_user: "{{ redmine_user }}" @@ -112,7 +119,7 @@ - redmine - name: Start puma service - systemd: + ansible.builtin.systemd: name: puma daemon_reload: yes state: started diff --git a/redmine/tasks/source.yml b/redmine/tasks/source.yml index 7893a5ad..980d2c13 100644 --- a/redmine/tasks/source.yml +++ b/redmine/tasks/source.yml @@ -1,6 +1,6 @@ --- - name: Create releases directory - file: + ansible.builtin.file: path: "/home/{{ redmine_user }}/{{ item }}" state: directory owner: "{{ redmine_user }}" @@ -13,7 +13,7 @@ - redmine - name: Download Redmine archive - unarchive: + ansible.builtin.unarchive: src: "https://redmine.org/releases/redmine-{{ redmine_version }}.tar.gz" dest: "/home/{{ redmine_user }}/releases/{{ redmine_version }}" remote_src: True @@ -24,7 +24,7 @@ - redmine - name: Link config files - file: + ansible.builtin.file: state: link src: "/home/{{ redmine_user }}/config/{{ item }}" dest: "/home/{{ redmine_user }}/releases/{{ redmine_version }}/config/{{ item }}" @@ -38,7 +38,7 @@ - redmine - name: Copy/Update plugin from archive - unarchive: + ansible.builtin.unarchive: src: "{{ item.zip }}" dest: "/home/{{ redmine_user }}/releases/{{ redmine_version }}/plugins/" remote_src: yes @@ -51,7 +51,7 @@ - redmine - name: Copy/Update plugin from git repository - git: + ansible.builtin.git: repo: "{{ item.git }}" dest: "/home/{{ redmine_user }}/releases/{{ redmine_version }}/plugins/{{ item.git | basename | splitext | first }}" version: "{{ item.tree | default('master') }}" @@ -63,7 +63,7 @@ - redmine - name: Copy/Update theme from archive - unarchive: + ansible.builtin.unarchive: src: "{{ item.zip }}" dest: "/home/{{ redmine_user }}/releases/{{ redmine_version }}/public/themes" remote_src: yes @@ -76,7 +76,7 @@ - redmine - name: Copy/Update theme from git repository - git: + ansible.builtin.git: repo: "{{ item.git }}" dest: "/home/{{ redmine_user }}/releases/{{ redmine_version }}/public/themes/{{ item.git | basename | splitext | first }}" version: "{{ item.tree | default('master') }}" @@ -88,7 +88,7 @@ - redmine - name: Deploy custom Gemfile - template: + ansible.builtin.template: src: Gemfile.local.j2 dest: "/home/{{ redmine_user }}/releases/{{ redmine_version }}/Gemfile.local" owner: "{{ redmine_user }}" diff --git a/redmine/tasks/syslog.yml b/redmine/tasks/syslog.yml index b53e2660..14be7827 100644 --- a/redmine/tasks/syslog.yml +++ b/redmine/tasks/syslog.yml @@ -1,6 +1,6 @@ --- - name: Create log directory - file: + ansible.builtin.file: state: directory dest: /var/log/redmine owner: root @@ -10,7 +10,7 @@ - redmine - name: Copy syslog configuration - copy: + ansible.builtin.copy: src: syslog.conf dest: /etc/rsyslog.d/redmine.conf mode: "0644" @@ -19,7 +19,7 @@ - redmine - name: Copy logrotate configuration - copy: + ansible.builtin.copy: src: logrotate dest: /etc/logrotate.d/redmine mode: "0644" diff --git a/redmine/tasks/user.yml b/redmine/tasks/user.yml index 932e049c..db9cbd1a 100644 --- a/redmine/tasks/user.yml +++ b/redmine/tasks/user.yml @@ -1,13 +1,14 @@ --- - name: Create redmine group - group: + + ansible.builtin.group: name: "{{ redmine_user }}" state: present tags: - redmine - name: Create redmine user - user: + ansible.builtin.user: name: "{{ redmine_user }}" state: present group: "{{ redmine_user }}" @@ -18,7 +19,7 @@ - redmine - name: Add redmine user to Redis group - user: + ansible.builtin.user: name: "{{ redmine_user }}" groups: "redis-{{ redmine_user }}" append: True @@ -27,7 +28,7 @@ - redmine - name: Create required directory - file: + ansible.builtin.file: path: "{{ item }}" state: directory owner: "{{ redmine_user }}" @@ -40,5 +41,6 @@ - redmine - name: Enable systemd user mode - command: "loginctl enable-linger {{ redmine_user }}" - changed_when: false + ansible.builtin.command: + cmd: "loginctl enable-linger {{ redmine_user }}" + changed_when: False diff --git a/remount-usr/handlers/main.yml b/remount-usr/handlers/main.yml index 5f197e78..ea22acee 100644 --- a/remount-usr/handlers/main.yml +++ b/remount-usr/handlers/main.yml @@ -1,6 +1,5 @@ --- - name: remount usr - command: "mount -o remount /usr" - failed_when: false - args: - warn: no + ansible.builtin.command: + cmd: "mount -o remount /usr" + failed_when: False \ No newline at end of file diff --git a/remount-usr/tasks/main.yml b/remount-usr/tasks/main.yml index 1bfedc64..eb5c0109 100644 --- a/remount-usr/tasks/main.yml +++ b/remount-usr/tasks/main.yml @@ -2,16 +2,16 @@ # findmnt returns 0 on hit, 1 on miss # If the return code is higher than 1, it's a blocking failure - name: "check if /usr is a read-only partition" - command: 'findmnt /usr --noheadings --options ro' + ansible.builtin.command: + cmd: 'findmnt /usr --noheadings --options ro' changed_when: False failed_when: usr_partition.rc > 1 check_mode: no register: usr_partition - name: "mount /usr in rw" - command: 'mount -o remount,rw /usr' - args: - warn: no + ansible.builtin.command: + cmd: 'mount -o remount,rw /usr' changed_when: False when: usr_partition.rc == 0 notify: remount usr diff --git a/spamassasin/handlers/main.yml b/spamassasin/handlers/main.yml index 7479d736..78597a37 100644 --- a/spamassasin/handlers/main.yml +++ b/spamassasin/handlers/main.yml @@ -1,5 +1,5 @@ --- - name: restart spamassassin - service: + ansible.builtin.service: name: spamassassin state: restarted diff --git a/spamassasin/tasks/main.yml b/spamassasin/tasks/main.yml index a7568391..9f2889ca 100644 --- a/spamassasin/tasks/main.yml +++ b/spamassasin/tasks/main.yml @@ -1,6 +1,6 @@ --- - name: install SpamAssasin - apt: + ansible.builtin.apt: name: - spamassassin state: present @@ -8,7 +8,7 @@ - spamassassin - name: configure SpamAssasin - copy: + ansible.builtin.copy: src: spamassassin.cf dest: /etc/spamassassin/local_evolix.cf mode: "0644" @@ -17,7 +17,7 @@ - spamassassin - name: enable SpamAssasin - replace: + ansible.builtin.replace: dest: /etc/default/spamassassin regexp: 'ENABLED=0' replace: 'ENABLED=1' @@ -26,7 +26,7 @@ - spamassassin - name: add amavis user to debian-spamd group - user: + ansible.builtin.user: name: amavis groups: debian-spamd append: yes @@ -34,31 +34,31 @@ - spamassassin - name: fix right on /var/lib/spamassassin - file: + ansible.builtin.file: dest: /var/lib/spamassassin state: directory mode: "0750" tags: - spamassassin -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr tags: - spamassassin - name: Check evomaintenance config - stat: + ansible.builtin.stat: path: /etc/evomaintenance.cf register: _evomaintenance_config - name: Verify sa-update dependency - assert: + ansible.builtin.assert: that: - _evomaintenance_config.stat.exists msg: sa-update.sh needs /etc/evomaintenance.cf - name: copy sa-update.sh script - copy: + ansible.builtin.copy: src: sa-update.sh dest: /usr/share/scripts/sa-update.sh mode: "0750" @@ -66,8 +66,8 @@ - spamassassin - name: Check if cron is installed - shell: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'" - args: + ansible.builtin.shell: + cmd: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'" executable: /bin/bash check_mode: no failed_when: False @@ -75,7 +75,7 @@ register: is_cron_installed - name: enable sa-update.sh cron - lineinfile: + ansible.builtin.lineinfile: dest: /etc/cron.d/sa-update line: "42 6 5 1,4,7,10 * root /usr/share/scripts/sa-update.sh" create: yes @@ -86,13 +86,14 @@ - spamassassin - name: update SpamAssasin's rules - command: "/usr/share/scripts/sa-update.sh" - changed_when: false + ansible.builtin.command: + cmd: "/usr/share/scripts/sa-update.sh" + changed_when: False tags: - spamassassin - name: ensure SpamAssasin is started and enabled - systemd: + ansible.builtin.systemd: name: spamassassin state: started enabled: True 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/files/evolinux-whitelist-defaults.conf b/squid/files/evolinux-whitelist-defaults.conf index fe9c0fb4..433b9ef8 100644 --- a/squid/files/evolinux-whitelist-defaults.conf +++ b/squid/files/evolinux-whitelist-defaults.conf @@ -11,6 +11,7 @@ ^repo\.mysql\.com$ ^deb\.nodesource\.com$ ^dl\.yarnpkg\.com$ +^deb\.freexian\.com$ # Let's Encrypt .+\.letsencrypt.org$ diff --git a/squid/files/whitelist-evolinux.conf b/squid/files/whitelist-evolinux.conf index 41b81221..c445e835 100644 --- a/squid/files/whitelist-evolinux.conf +++ b/squid/files/whitelist-evolinux.conf @@ -10,6 +10,7 @@ http://spamassassin.apache.org/.* http://.*sa-update.* http://pear.php.net/.* http://repo.mysql.com/.* +http://deb.freexian.com/.* # Let's Encrypt http://.*.letsencrypt.org/.* diff --git a/squid/handlers/main.yml b/squid/handlers/main.yml index 4f5329b9..149d4827 100644 --- a/squid/handlers/main.yml +++ b/squid/handlers/main.yml @@ -1,33 +1,34 @@ --- - name: restart munin-node - service: + ansible.builtin.service: name: munin-node state: restarted - name: restart squid - service: + ansible.builtin.service: name: squid state: restarted - name: reload squid - service: + ansible.builtin.service: name: squid state: reloaded - name: restart squid3 - service: + ansible.builtin.service: name: squid3 state: restarted - name: reload squid3 - service: + ansible.builtin.service: name: squid3 state: reloaded - name: restart log2mail - service: + ansible.builtin.service: name: log2mail state: restarted - name: restart minifirewall - command: /etc/init.d/minifirewall restart + ansible.builtin.command: + cmd: /etc/init.d/minifirewall restart diff --git a/squid/tasks/log2mail.yml b/squid/tasks/log2mail.yml index 5454dc10..1d36eb09 100644 --- a/squid/tasks/log2mail.yml +++ b/squid/tasks/log2mail.yml @@ -1,14 +1,14 @@ --- - name: is log2mail installed? - stat: + ansible.builtin.stat: path: /etc/log2mail/config/ check_mode: no register: log2mail_config - block: - name: log2mail proxy config is present - template: + ansible.builtin.template: src: log2mail.j2 dest: /etc/log2mail/config/squid.conf mode: "0640" @@ -17,7 +17,7 @@ notify: restart log2mail - name: log2mail user is in proxy group - user: + ansible.builtin.user: name: log2mail groups: proxy append: yes diff --git a/squid/tasks/logrotate_jessie.yml b/squid/tasks/logrotate_jessie.yml index 010d13cc..a0cfdff1 100644 --- a/squid/tasks/logrotate_jessie.yml +++ b/squid/tasks/logrotate_jessie.yml @@ -11,7 +11,8 @@ # is the one provided by the package. - name: check if logrotate file is default - shell: 'printf "43994674706b672ae5018f592beccf2e /etc/logrotate.d/{{ squid_daemon_name }}" | md5sum --check' + ansible.builtin.shell: + cmd: 'printf "43994674706b672ae5018f592beccf2e /etc/logrotate.d/{{ squid_daemon_name }}" | md5sum --check' changed_when: False failed_when: False check_mode: no @@ -20,10 +21,10 @@ - squid - name: logrotate configuration - template: + ansible.builtin.template: src: logrotate_jessie.j2 dest: /etc/logrotate.d/{{ squid_daemon_name }} - force: yes + force: true when: squid_logrotate_md5.rc == 0 tags: - squid diff --git a/squid/tasks/logrotate_stretch.yml b/squid/tasks/logrotate_stretch.yml index 579c228c..965256d4 100644 --- a/squid/tasks/logrotate_stretch.yml +++ b/squid/tasks/logrotate_stretch.yml @@ -11,7 +11,8 @@ # is the one provided by the package. - name: check if logrotate file is default - shell: 'printf "c210feea019412adac8a5d5dcba427af /etc/logrotate.d/{{ squid_daemon_name }}" | md5sum --check' + ansible.builtin.shell: + cmd: 'printf "c210feea019412adac8a5d5dcba427af /etc/logrotate.d/{{ squid_daemon_name }}" | md5sum --check' changed_when: False failed_when: False check_mode: no @@ -20,10 +21,10 @@ - squid - name: logrotate configuration - template: + ansible.builtin.template: src: logrotate_stretch.j2 dest: /etc/logrotate.d/{{ squid_daemon_name }} - force: yes + force: true when: squid_logrotate_md5.rc == 0 tags: - squid diff --git a/squid/tasks/main.yml b/squid/tasks/main.yml index 4a3cab4d..2f0e94aa 100644 --- a/squid/tasks/main.yml +++ b/squid/tasks/main.yml @@ -1,139 +1,139 @@ --- -- fail: +- ansible.builtin.fail: msg: only compatible with Debian >= 8 when: - ansible_distribution != "Debian" or ansible_distribution_major_version is version('8', '<') - name: "Set squid name (jessie)" - set_fact: + ansible.builtin.set_fact: squid_daemon_name: squid3 when: ansible_distribution_release == "jessie" - name: "Set squid name (Debian 9 or later)" - set_fact: + ansible.builtin.set_fact: squid_daemon_name: squid when: ansible_distribution_major_version is version('9', '>=') - name: "Install Squid packages" - apt: + ansible.builtin.apt: name: - "{{ squid_daemon_name }}" - squidclient state: present - name: Fetch packages - package_facts: - manager: auto + ansible.builtin.package_facts: + manager: auto -- debug: +- ansible.builtin.debug: var: ansible_facts.packages[squid_daemon_name] - name: "Set alternative config file (Debian 9 or later)" - copy: + ansible.builtin.copy: src: default_squid dest: /etc/default/squid when: ansible_distribution_major_version is version('9', '>=') - name: "squid.conf is present (jessie)" - template: + ansible.builtin.template: src: squid.conf.j2 dest: /etc/squid3/squid.conf notify: "restart squid3" when: ansible_distribution_release == "jessie" - name: "evolix whitelist is present (jessie)" - copy: + ansible.builtin.copy: src: whitelist-evolinux.conf dest: /etc/squid3/whitelist.conf - force: no + force: false notify: "reload squid3" when: ansible_distribution_release == "jessie" - name: "evolinux defaults squid file (Debian 9 or later)" - copy: + ansible.builtin.copy: src: evolinux-defaults.conf dest: /etc/squid/evolinux-defaults.conf notify: "restart squid" when: ansible_distribution_major_version is version('9', '>=') - name: "evolinux defaults whitelist (Debian 9 or later)" - copy: + ansible.builtin.copy: src: evolinux-whitelist-defaults.conf dest: /etc/squid/evolinux-whitelist-defaults.conf notify: "reload squid" when: ansible_distribution_major_version is version('9', '>=') - name: "evolinux custom whitelist (Debian 9 or later)" - copy: + ansible.builtin.copy: dest: /etc/squid/evolinux-whitelist-custom.conf content: | # Put customized values here. - force: no + force: false when: ansible_distribution_major_version is version('9', '>=') - name: "evolinux acl for local proxy (Debian 9 or later)" - template: + ansible.builtin.template: src: evolinux-acl.conf.j2 dest: /etc/squid/evolinux-acl.conf - force: no + force: false notify: "reload squid" when: - squid_localproxy_enable | bool - ansible_distribution_major_version is version('9', '>=') - name: "evolinux custom acl (Debian 9 or later)" - copy: + ansible.builtin.copy: dest: /etc/squid/evolinux-acl.conf content: | # Put customized values here. - force: no + force: false when: - not (squid_localproxy_enable | bool) - ansible_distribution_major_version is version('9', '>=') - name: "evolinux http_access for local proxy (Debian 9 or later)" - copy: + ansible.builtin.copy: src: evolinux-httpaccess.conf dest: /etc/squid/evolinux-httpaccess.conf - force: no + force: false notify: "reload squid" when: - squid_localproxy_enable | bool - ansible_distribution_major_version is version('9', '>=') - name: "evolinux custom http_access (Debian 9 or later)" - copy: + ansible.builtin.copy: dest: /etc/squid/evolinux-httpaccess.conf content: | # Put customized values here. - force: no + force: false when: - not (squid_localproxy_enable | bool) - ansible_distribution_major_version is version('9', '>=') - name: "evolinux overrides for local proxy (Debian 9 or later)" - template: + ansible.builtin.template: src: evolinux-custom.conf.j2 dest: /etc/squid/evolinux-custom.conf - force: no + force: false notify: "reload squid" when: - squid_localproxy_enable | bool - ansible_distribution_major_version is version('9', '>=') - name: "evolinux custom overrides (Debian 9 or later)" - copy: + ansible.builtin.copy: dest: /etc/squid/evolinux-custom.conf content: | # Put customized values here. - force: no + force: false when: - not (squid_localproxy_enable | bool) - ansible_distribution_major_version is version('9', '>=') - name: add some URL in whitelist (Debian 8) - lineinfile: + ansible.builtin.lineinfile: insertafter: EOF dest: /etc/squid3/whitelist.conf line: "{{ item }}" @@ -143,7 +143,7 @@ when: ansible_distribution_major_version == '8' - name: add some URL in whitelist (Debian 9 or later) - lineinfile: + ansible.builtin.lineinfile: insertafter: EOF dest: /etc/squid/evolinux-whitelist-custom.conf line: "{{ item }}" @@ -152,15 +152,15 @@ notify: "reload squid" when: ansible_distribution_major_version is version('9', '>=') -- include: systemd.yml +- ansible.builtin.include: systemd.yml when: ansible_distribution_major_version is version('10', '>=') -- include: logrotate_jessie.yml +- ansible.builtin.include: logrotate_jessie.yml when: ansible_distribution_release == "jessie" -- include: logrotate_stretch.yml +- ansible.builtin.include: logrotate_stretch.yml when: ansible_distribution_major_version is version('9', '>=') -- include: minifirewall.yml +- ansible.builtin.include: minifirewall.yml -- include: log2mail.yml +- ansible.builtin.include: log2mail.yml diff --git a/squid/tasks/minifirewall.legacy.yml b/squid/tasks/minifirewall.legacy.yml new file mode 100644 index 00000000..18ee45aa --- /dev/null +++ b/squid/tasks/minifirewall.legacy.yml @@ -0,0 +1,43 @@ +--- +- name: Check if Minifirewall is present + ansible.builtin.stat: + path: "/etc/default/minifirewall" + check_mode: no + register: minifirewall_test + +- block: + - name: HTTPSITES list is commented in minifirewall + ansible.builtin.replace: + dest: "/etc/default/minifirewall" + regexp: "^(HTTPSITES='[^0-9])" + replace: '#\1' + notify: restart minifirewall + + - name: all HTTPSITES are authorized in minifirewall + ansible.builtin.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 + ansible.builtin.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 + ansible.builtin.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..7cece087 100644 --- a/squid/tasks/minifirewall.yml +++ b/squid/tasks/minifirewall.yml @@ -1,29 +1,38 @@ --- - name: Check if Minifirewall is present - stat: - path: "{{ minifirewall_main_file }}" + ansible.builtin.stat: + path: "/etc/default/minifirewall" check_mode: no register: minifirewall_test - block: - name: HTTPSITES list is commented in minifirewall - replace: - dest: "{{ minifirewall_main_file }}" + ansible.builtin.replace: + 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 }}" + ansible.builtin.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: "{{ minifirewall_main_file }}" + # The PROXY variable means that minifirewall is "modern" + - name: Look for PROXY variable + ansible.builtin.shell: + cmd: "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) + ansible.builtin.lineinfile: + dest: "/etc/default/minifirewall" regexp: "^#? *{{ item }}" line: "{{ item }}" insertafter: "^# Proxy" @@ -33,11 +42,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 - lineinfile: - dest: "{{ minifirewall_main_file }}" + - name: remove minifirewall example rule for the proxy (legacy mode) + ansible.builtin.lineinfile: + 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) + ansible.builtin.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/squid/tasks/systemd.yml b/squid/tasks/systemd.yml index c84e52d6..da07eb81 100644 --- a/squid/tasks/systemd.yml +++ b/squid/tasks/systemd.yml @@ -1,14 +1,15 @@ --- - name: Look for existing systemd unit - command: systemctl -q is-active squid.service + ansible.builtin.command: + cmd: systemctl -q is-active squid.service changed_when: False failed_when: False check_mode: no register: _squid_systemd_active - name: Squid systemd overrides directory exists - file: + ansible.builtin.file: dest: /etc/systemd/system/squid.service.d/ state: directory owner: root @@ -16,15 +17,15 @@ mode: "0755" - name: "Squid systemd unit service is present" - template: + ansible.builtin.template: src: systemd-override.conf.j2 dest: /etc/systemd/system/squid.service.d/override.conf mode: "0644" - force: yes + force: true register: _squid_systemd_override - name: "Systemd daemon is reloaded and Squid restarted" - systemd: + ansible.builtin.systemd: name: squid state: restarted daemon_reload: yes diff --git a/ssl/handlers/main.yml b/ssl/handlers/main.yml index 3393e45a..d4dcb52a 100644 --- a/ssl/handlers/main.yml +++ b/ssl/handlers/main.yml @@ -1,5 +1,5 @@ --- - name: reload haproxy - service: + ansible.builtin.service: name: haproxy state: reloaded diff --git a/ssl/tasks/haproxy.yml b/ssl/tasks/haproxy.yml index 2ba30ac9..878524f3 100644 --- a/ssl/tasks/haproxy.yml +++ b/ssl/tasks/haproxy.yml @@ -1,6 +1,6 @@ --- - name: Concatenate SSL certificate, key and dhparam - set_fact: + ansible.builtin.set_fact: ssl_cat: "{{ ssl_cat | default() }}{{ lookup('file', item) }}\n" with_fileglob: - "ssl/{{ ssl_cert }}.pem" @@ -10,7 +10,7 @@ - ssl - name: Create haproxy ssl directory - file: + ansible.builtin.file: dest: /etc/haproxy/ssl state: directory mode: "0700" @@ -18,7 +18,7 @@ - ssl - name: Copy concatenated certificate and key - copy: + ansible.builtin.copy: content: "{{ ssl_cat }}" dest: "/etc/haproxy/ssl/{{ ssl_cert }}.pem" mode: "0600" @@ -27,7 +27,7 @@ - ssl - name: Reset ssl_cat variable - set_fact: + ansible.builtin.set_fact: ssl_cat: "" tags: - ssl diff --git a/ssl/tasks/main.yml b/ssl/tasks/main.yml index 3ec71115..01398dec 100644 --- a/ssl/tasks/main.yml +++ b/ssl/tasks/main.yml @@ -1,6 +1,6 @@ --- - name: Copy SSL certificate - copy: + ansible.builtin.copy: src: "ssl/{{ ssl_cert }}.pem" dest: "/etc/ssl/certs/{{ ssl_cert }}.pem" mode: "0644" @@ -9,7 +9,7 @@ - ssl - name: Copy SSL key - copy: + ansible.builtin.copy: src: "ssl/{{ ssl_cert }}.key" dest: "/etc/ssl/private/{{ ssl_cert }}.key" mode: "0640" @@ -20,7 +20,7 @@ - ssl - name: Copy SSL dhparam - copy: + ansible.builtin.copy: src: "ssl/{{ ssl_cert }}.dhp" dest: "/etc/ssl/certs/{{ ssl_cert }}.dhp" mode: "0644" @@ -29,8 +29,8 @@ - ssl - name: Check if Haproxy is installed - shell: "set -o pipefail && dpkg -l haproxy 2>/dev/null | grep -q -E '^(i|h)i'" - args: + ansible.builtin.shell: + cmd: "set -o pipefail && dpkg -l haproxy 2>/dev/null | grep -q -E '^(i|h)i'" executable: /bin/bash register: haproxy_check check_mode: no @@ -39,5 +39,5 @@ tags: - ssl -- include: haproxy.yml +- ansible.builtin.include: haproxy.yml when: haproxy_check.rc == 0 diff --git a/supervisord/handlers/main.yml b/supervisord/handlers/main.yml index be10ba0a..dde2339d 100644 --- a/supervisord/handlers/main.yml +++ b/supervisord/handlers/main.yml @@ -1,5 +1,5 @@ --- - name: restart supervisor - service: + ansible.builtin.service: name: supervisor state: restarted diff --git a/supervisord/tasks/main.yml b/supervisord/tasks/main.yml index b35bd03f..8d30c649 100644 --- a/supervisord/tasks/main.yml +++ b/supervisord/tasks/main.yml @@ -1,16 +1,16 @@ --- - name: Install Supervisor - apt: + ansible.builtin.apt: name: supervisor tags: - supervisord - name: Add http configuration for Supervisor - copy: + ansible.builtin.copy: src: http.conf dest: /etc/supervisor/conf.d/ mode: "0644" - force: no + force: false notify: restart supervisor when: supervisord_enable_http | bool tags: diff --git a/tomcat-instance/defaults/main.yml b/tomcat-instance/defaults/main.yml index 6a2ec877..92e68738 100644 --- a/tomcat-instance/defaults/main.yml +++ b/tomcat-instance/defaults/main.yml @@ -1,5 +1,5 @@ --- tomcat_instance_java_path: '/usr/lib/jvm/java-7-openjdk-amd64' tomcat_instance_root: '/srv/tomcat' -tomcat_instance_shutdown: "{{ tomcat_instance_port | int + 1 }}" +tomcat_instance_shutdown: "{{ tomcat_instance_port | int + 1 }}" tomcat_instance_mps: 256 diff --git a/tomcat-instance/tasks/alias.yml b/tomcat-instance/tasks/alias.yml index 99ae1910..b61b27e5 100644 --- a/tomcat-instance/tasks/alias.yml +++ b/tomcat-instance/tasks/alias.yml @@ -1,6 +1,6 @@ --- - name: Create bin dir for alias - file: + ansible.builtin.file: path: "{{ tomcat_instance_root }}/{{ tomcat_instance_name }}/bin" state: directory mode: "0770" @@ -8,7 +8,7 @@ group: "{{ tomcat_instance_name }}" - name: Copy alias script for systemctl --user - template: + ansible.builtin.template: src: "{{ item }}" dest: "{{ tomcat_instance_root }}/{{ tomcat_instance_name }}/bin/" mode: "0770" diff --git a/tomcat-instance/tasks/bootstrap.yml b/tomcat-instance/tasks/bootstrap.yml index 001088b1..c53a4a14 100644 --- a/tomcat-instance/tasks/bootstrap.yml +++ b/tomcat-instance/tasks/bootstrap.yml @@ -1,6 +1,6 @@ --- - name: Create tomcat dirs - file: + ansible.builtin.file: path: "{{ tomcat_instance_root }}/{{ tomcat_instance_name }}/{{ item }}" state: directory mode: "u=rwx,g=rwxs,o=" @@ -15,30 +15,30 @@ - 'lib' - name: Templating of env file - template: + ansible.builtin.template: src: 'templates/env.j2' dest: "{{ tomcat_instance_root }}/{{ tomcat_instance_name }}/conf/env" mode: "0660" owner: "{{ tomcat_instance_name }}" group: "{{ tomcat_instance_name }}" - force: no + force: false - name: Templating of server.xml file - template: + ansible.builtin.template: src: 'templates/server.xml-tomcat{{ tomcat_version }}.j2' dest: "{{ tomcat_instance_root }}/{{ tomcat_instance_name }}/conf/server.xml" mode: "0660" owner: "{{ tomcat_instance_name }}" group: "{{ tomcat_instance_name }}" - force: no + force: false - name: Copy config file - copy: + ansible.builtin.copy: src: "{{ item }}" dest: "{{ tomcat_instance_root }}/{{ tomcat_instance_name }}/conf/{{ item | basename }}" mode: "0660" owner: "{{ tomcat_instance_name }}" group: "{{ tomcat_instance_name }}" - force: no + force: false with_fileglob: - "tomcat{{ tomcat_version }}/*" diff --git a/tomcat-instance/tasks/check.yml b/tomcat-instance/tasks/check.yml index eff9d236..e172d5c3 100644 --- a/tomcat-instance/tasks/check.yml +++ b/tomcat-instance/tasks/check.yml @@ -1,23 +1,26 @@ --- - name: Check tomcat_instance_name - debug: + ansible.builtin.debug: msg: "{{ tomcat_instance_name }}" - name: Check use of gid - command: id -ng "{{ tomcat_instance_port }}" + ansible.builtin.command: + cmd: id -ng "{{ tomcat_instance_port }}" register: check_port_gid - changed_when: false + changed_when: False failed_when: - check_port_gid | success - check_port_gid.stdout != "{{ tomcat_instance_name }}" - name: Check use of uid - command: id -nu "{{ tomcat_instance_port }}" + ansible.builtin.command: + cmd: id -nu "{{ tomcat_instance_port }}" register: check_port_uid - changed_when: false + changed_when: False failed_when: - check_port_uid | success - check_port_uid.stdout != "{{ tomcat_instance_name }}" #- name: Check use of http port -# command: grep '/dev/null | grep -v \.bak\.); do + USER="$(user_for $i)" + rotate $i root:$USER + done +done + +for log in production.log delayed_job.log development.log test.log; do + for i in $(ls -1 -d $HOMEPREFIX/*/www/{,current/}log/$log 2>/dev/null | grep -v \.bak\.); do + USER="$(user_for $i)" + rotate $i $USER:$USER + done +done + +test -x /usr/sbin/apache2ctl && if /etc/init.d/apache2 status > /dev/null ; then \ + /etc/init.d/apache2 reload > /dev/null; \ +fi; + +test -x /usr/sbin/nginx && invoke-rc.d nginx rotate >/dev/null 2>&1 + +# Zipping is done after web server reload, so that the file descriptor is released. +# Else, an error is raised (gzip file size changed while zipping) +# and logs written during the zipping process might be lost. + +for log in access.log*[!\.gz] access-*.log*[!\.gz] error.log*[!\.gz]; do + for i in $(ls -1 -d $HOMEPREFIX/*/log/$log 2>/dev/null | grep -v \.bak\.); do + if test -f "$i"; then + gzip "$i" + fi + done +done + +for log in production.log*[!\.gz] delayed_job.log*[!\.gz] development.log*[!\.gz] test.log*[!\.gz]; do + for i in $(ls -1 -d $HOMEPREFIX/*/www/{,current/}log/$log 2>/dev/null | grep -v \.bak\.); do + if test -f "$i"; then + gzip "$i" + fi + done +done + +exit 0 diff --git a/packweb-apache/files/userlogrotate_jessie b/userlogrotate/files/userlogrotate_jessie similarity index 54% rename from packweb-apache/files/userlogrotate_jessie rename to userlogrotate/files/userlogrotate_jessie index 339101a9..347736fc 100644 --- a/packweb-apache/files/userlogrotate_jessie +++ b/userlogrotate/files/userlogrotate_jessie @@ -5,7 +5,6 @@ HOMEPREFIX="/home" rotate () { mv $1 $1.$DATE - gzip $1.$DATE touch $1 chown $2 $1 chmod g+r $1 @@ -36,3 +35,25 @@ for log in production.log delayed_job.log development.log test.log; do done apache2ctl restart > /dev/null + +# Zipping is done after web server reload, so that the file descriptor is released. +# Else, an error is raised (gzip file size changed while zipping) +# and logs written during the zipping process might be lost. + +for log in access.log*[!\.gz] access-*.log*[!\.gz] error.log*[!\.gz]; do + for i in `ls -1 -d $HOMEPREFIX/*/log/$log 2>/dev/null | grep -v \.bak\.`; do + if test -f "$i"; then + gzip "$i" + fi + done +done + +for log in production.log*[!\.gz] delayed_job.log*[!\.gz] development.log*[!\.gz] test.log*[!\.gz]; do + for i in `ls -1 -d $HOMEPREFIX/*/www/{,current/}log/$log 2>/dev/null | grep -v \.bak\.`; do + if test -f "$i"; then + gzip "$i" + fi + done +done + + diff --git a/userlogrotate/tasks/main.yml b/userlogrotate/tasks/main.yml new file mode 100644 index 00000000..4f9c5fc7 --- /dev/null +++ b/userlogrotate/tasks/main.yml @@ -0,0 +1,34 @@ +--- +- name: "Is userlogrotate present ?" + ansible.builtin.find: + paths: ["/etc/cron.weekly", "/etc/cron.daily"] + patterns: ["userlogrotate"] + register: find_logrotate + check_mode: no + +- name: "Update userlogrotate" + ansible.builtin.copy: + src: userlogrotate + dest: "{{ item.path }}" + mode: "0755" + loop: "{{ find_logrotate.files }}" + when: find_logrotate.files | length>0 + +- name: "Install userlogrotate (jessie)" + ansible.builtin.copy: + src: userlogrotate_jessie + dest: /etc/cron.weekly/userlogrotate + mode: "0755" + when: + - ansible_distribution_release == "jessie" + - find_logrotate.files | length==0 + +- name: "Install userlogrotate (Debian 9 or later)" + ansible.builtin.copy: + src: userlogrotate + dest: /etc/cron.weekly/userlogrotate + mode: "0755" + when: + - ansible_distribution_major_version is version('9', '>=') + - find_logrotate.files | length==0 + diff --git a/varnish/defaults/main.yml b/varnish/defaults/main.yml index 2de75a15..560e241f 100644 --- a/varnish/defaults/main.yml +++ b/varnish/defaults/main.yml @@ -16,5 +16,10 @@ varnish_thread_pool_max: 5000 varnish_jail: "unix,user=vcache" varnish_additional_options: "" +varnish_systemd_override_template: Null + varnish_config_file: /etc/varnish/default.vcl +varnish_update_config: true varnish_secret_file: /etc/varnish/secret + +varnish_tmp_dir: /var/tmp-vcache diff --git a/varnish/handlers/main.yml b/varnish/handlers/main.yml index 7f9fd3ff..96b9fb5a 100644 --- a/varnish/handlers/main.yml +++ b/varnish/handlers/main.yml @@ -1,20 +1,21 @@ --- - name: reload varnish - systemd: + ansible.builtin.systemd: name: varnish state: reloaded daemon_reload: yes - name: restart varnish - systemd: + ansible.builtin.systemd: name: varnish state: restarted daemon_reload: yes - name: reload systemd - command: systemctl daemon-reload + ansible.builtin.systemd: + daemon-reload: yes - name: restart munin-node - service: + ansible.builtin.service: name: munin-node state: restarted diff --git a/varnish/tasks/main.yml b/varnish/tasks/main.yml index 75268841..68f3fc74 100644 --- a/varnish/tasks/main.yml +++ b/varnish/tasks/main.yml @@ -1,13 +1,38 @@ --- - name: Install Varnish - apt: + ansible.builtin.apt: name: varnish state: present tags: - varnish +- name: Fetch packages + ansible.builtin.package_facts: + manager: auto + check_mode: no + tags: + - varnish + - config + - update-config + +- ansible.builtin.set_fact: + varnish_package_facts: "{{ ansible_facts.packages['varnish'] | first }}" + check_mode: no + tags: + - varnish + - config + - update-config + +# - debug: +# var: varnish_package_facts +# check_mode: no +# tags: +# - varnish +# - config +# - update-config + - name: Remove default varnish configuration files - file: + ansible.builtin.file: path: "{{ item }}" state: absent loop: @@ -19,8 +44,8 @@ - varnish - config -- name: Copy Custom Varnish ExecReload script (Debian <10) - template: +- name: Copy Custom Varnish ExecReload script (Debian < 10) + ansible.builtin.template: src: "reload-vcl.sh.j2" dest: "/etc/varnish/reload-vcl.sh" mode: "0700" @@ -32,33 +57,63 @@ - varnish - name: Create a system config directory for systemd overrides - file: + ansible.builtin.file: path: /etc/systemd/system/varnish.service.d state: directory tags: - varnish + - config -- name: Override Varnish systemd unit (Stretch and before) - template: - src: varnish.conf.jessie.j2 - dest: /etc/systemd/system/varnish.service.d/evolinux.conf - force: yes - when: ansible_distribution_major_version is version('10', '<') +- name: Remove legacy systemd override + ansible.builtin.file: + path: /etc/systemd/system/varnish.service.d/evolinux.conf + state: absent notify: - reload systemd - - restart varnish + tags: + - varnish + - config + +- name: Varnish systemd override template (Varnish 4 and 5) + ansible.builtin.set_fact: + varnish_systemd_override_template: override.conf.varnish4.j2 + when: + - varnish_package_facts['version'] is version('4', '>=') + - varnish_package_facts['version'] is version('6', '<') + - varnish_systemd_override_template is none or varnish_systemd_override_template | length == 0 tags: - varnish - config - update-config -# TODO: verify if it's still necessary for Debian 11 -- name: Override Varnish systemd unit (Buster and later) - template: - src: varnish.conf.buster.j2 - dest: /etc/systemd/system/varnish.service.d/evolinux.conf - force: yes - when: ansible_distribution_major_version is version('10', '>=') +- name: Varnish systemd override template (Varnish 6) + ansible.builtin.set_fact: + varnish_systemd_override_template: override.conf.varnish6.j2 + when: + - varnish_package_facts['version'] is version('6', '>=') + - varnish_package_facts['version'] is version('7', '<') + - varnish_systemd_override_template is none or varnish_systemd_override_template | length == 0 + tags: + - varnish + - config + - update-config + +- name: Varnish systemd override template (Varnish 7 and later) + ansible.builtin.set_fact: + varnish_systemd_override_template: override.conf.varnish7.j2 + when: + - varnish_package_facts['version'] is version('7', '>=') + - varnish_systemd_override_template is none or varnish_systemd_override_template | length == 0 + tags: + - varnish + - config + - update-config + +- name: Override Varnish systemd unit + ansible.builtin.template: + src: "{{ varnish_systemd_override_template }}" + dest: /etc/systemd/system/varnish.service.d/override.conf + force: true notify: - reload systemd - restart varnish @@ -68,10 +123,10 @@ - update-config - name: Patch logrotate conf - replace: + ansible.builtin.replace: name: /etc/logrotate.d/varnish - regexp: '^(\s+)(/usr/sbin/invoke-rc.d {{item}}.*)' - replace: '\1systemctl -q is-active {{item}} && \2' + regexp: '^(\s+)(/usr/sbin/invoke-rc.d {{ item }}.*)' + replace: '\1systemctl -q is-active {{ item }} && \2' loop: - varnishlog - varnishncsa @@ -80,11 +135,12 @@ - logrotate - name: Copy Varnish configuration - template: + ansible.builtin.template: src: "{{ item }}" dest: "{{ varnish_config_file }}" mode: "0644" - force: yes + force: true + when: varnish_update_config | bool loop: "{{ query('first_found', templates) }}" vars: templates: @@ -103,7 +159,7 @@ - update-config - name: Create Varnish config dir - file: + ansible.builtin.file: path: /etc/varnish/conf.d state: directory mode: "0755" @@ -113,10 +169,10 @@ - update-config - name: Copy included Varnish config - template: + ansible.builtin.template: src: "{{ item }}" dest: /etc/varnish/conf.d/ - force: yes + force: true mode: "0644" with_fileglob: - "templates/varnish/conf.d/*.vcl" @@ -126,4 +182,15 @@ - config - update-config -- include: munin.yml +# To validate the configuration, we must use a tmp directory that is mounted as exec +# We usually use /vat/tmp-cache then validate the syntax with this command: +# sudo -u vcache TMPDIR=/var/tmp-vcache varnishd -Cf /etc/varnish/default.vcl > /dev/null +- name: Special tmp directory + ansible.builtin.file: + path: "{{ varnish_tmp_dir }}" + state: directory + owner: vcache + group: varnish + mode: "0750" + +- ansible.builtin.include: munin.yml diff --git a/varnish/tasks/munin.yml b/varnish/tasks/munin.yml index 1ccf5f88..3b329d46 100644 --- a/varnish/tasks/munin.yml +++ b/varnish/tasks/munin.yml @@ -1,29 +1,29 @@ --- - name: Install dependencies - apt: + ansible.builtin.apt: name: libxml-parser-perl tags: varnish -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr tags: varnish - name: Create plugin directory - file: + ansible.builtin.file: name: /usr/local/share/munin/ state: directory mode: "0755" tags: varnish - name: Create plugin directory - file: + ansible.builtin.file: name: /usr/local/share/munin/plugins/ state: directory mode: "0755" tags: varnish - name: Copy varnish5 munin plugin - copy: + ansible.builtin.copy: src: munin/varnish5_ dest: /usr/local/share/munin/plugins/ mode: "0755" @@ -31,9 +31,9 @@ tags: varnish - name: Enable varnish5 munin plugin - file: + ansible.builtin.file: src: /usr/local/share/munin/plugins/varnish5_ - dest: "/etc/munin/plugins/varnish5_{{item}}" + dest: "/etc/munin/plugins/varnish5_{{ item }}" state: link loop: - memory_usage @@ -51,7 +51,7 @@ tags: varnish - name: Copy varnish5 munin plugin config - copy: + ansible.builtin.copy: src: munin/varnish5.conf dest: /etc/munin/plugin-conf.d/varnish5 mode: "0644" diff --git a/varnish/templates/varnish.conf.jessie.j2 b/varnish/templates/override.conf.varnish4.j2 similarity index 88% rename from varnish/templates/varnish.conf.jessie.j2 rename to varnish/templates/override.conf.varnish4.j2 index f340323d..c3653708 100644 --- a/varnish/templates/varnish.conf.jessie.j2 +++ b/varnish/templates/override.conf.varnish4.j2 @@ -2,6 +2,6 @@ [Service] ExecStart= -ExecStart=/usr/sbin/varnishd -F -j {{ varnish_jail }} {{ varnish_addresses | map('regex_replace', '^(.*)$', '-a \\1') | list | join(' ') }} -T {{ varnish_management_address }} -f {{ varnish_config_file }} -S {{ varnish_secret_file }} -s {{ varnish_storage }} -p thread_pools={{ varnish_thread_pools }} -p thread_pool_add_delay={{ varnish_thread_pool_add_delay }} -p thread_pool_min={{ varnish_thread_pool_min }} -p thread_pool_max={{ varnish_thread_pool_max }} +ExecStart=/usr/sbin/varnishd -j {{ varnish_jail }} -F {{ varnish_addresses | map('regex_replace', '^(.*)$', '-a \\1') | list | join(' ') }} -T {{ varnish_management_address }} -f {{ varnish_config_file }} -S {{ varnish_secret_file }} -s {{ varnish_storage }} -p thread_pools={{ varnish_thread_pools }} -p thread_pool_add_delay={{ varnish_thread_pool_add_delay }} -p thread_pool_min={{ varnish_thread_pool_min }} -p thread_pool_max={{ varnish_thread_pool_max }} ExecReload= ExecReload=/etc/varnish/reload-vcl.sh diff --git a/varnish/templates/varnish.conf.buster.j2 b/varnish/templates/override.conf.varnish6.j2 similarity index 100% rename from varnish/templates/varnish.conf.buster.j2 rename to varnish/templates/override.conf.varnish6.j2 diff --git a/varnish/templates/override.conf.varnish7.j2 b/varnish/templates/override.conf.varnish7.j2 new file mode 100644 index 00000000..14a0b315 --- /dev/null +++ b/varnish/templates/override.conf.varnish7.j2 @@ -0,0 +1,18 @@ +# {{ ansible_managed }} + +[Service] +ExecStart= +ExecStart=/usr/sbin/varnishd \ + -j {{ varnish_jail }} \ + {{ varnish_addresses | map('regex_replace', '^(.*)$', '-a \\1') | list | join(' ') }} \ + -P %t/%N/varnishd.pid \ + -T {{ varnish_management_address }} \ + -f {{ varnish_config_file }} \ + -S {{ varnish_secret_file }} \ + -s {{ varnish_storage }} \ + -p feature=+http2 \ + -p thread_pools={{ varnish_thread_pools }} \ + -p thread_pool_add_delay={{ varnish_thread_pool_add_delay }} \ + -p thread_pool_min={{ varnish_thread_pool_min }} \ + -p thread_pool_max={{ varnish_thread_pool_max }} \ + {{ varnish_additional_options }} diff --git a/vrrpd/defaults/main.yml b/vrrpd/defaults/main.yml new file mode 100644 index 00000000..f5950a14 --- /dev/null +++ b/vrrpd/defaults/main.yml @@ -0,0 +1,13 @@ +--- + +vrrp_addresses: [] +# - { +# interface: Null # the interface name to run on +# delay: 10 # the advertisement interval (in sec) (default: 1) +# id: Null # the id of the virtual server [1-255] +# priority: Null # the priority of this host in the virtual server (default: 100) +# authentication: Null # authentification type: auth=(none|pw/hexkey|ah/hexkey) hexkey=0x[0-9a-fA-F]+ +# label: Null # use this name is syslog messages (helps when several vrid are running) +# ip: Null # the ip address(es) (and optionnaly subnet mask) of the virtual server +# state: Null # 'started' or 'stopped' +# } \ No newline at end of file diff --git a/vrrpd/tasks/ip.yml b/vrrpd/tasks/ip.yml new file mode 100644 index 00000000..b46a8954 --- /dev/null +++ b/vrrpd/tasks/ip.yml @@ -0,0 +1,22 @@ +--- + +- name: set unit name + ansible.builtin.set_fact: + vrrp_systemd_unit_name: "vrrp-{{ vrrp_address.id }}.service" + +- name: add systemd unit + ansible.builtin.template: + src: vrrp.service.j2 + dest: "/etc/systemd/system/{{ vrrp_systemd_unit_name }}" + force: true + register: vrrp_systemd_unit + +- name: enable and start systemd unit + ansible.builtin.systemd: + name: "{{ vrrp_systemd_unit_name }}" + daemon_reload: yes + enabled: yes + state: "{{ vrrp_address.state }}" + when: + - vrrp_systemd_unit is changed + - not ansible_check_mode \ No newline at end of file diff --git a/vrrpd/tasks/main.yml b/vrrpd/tasks/main.yml index 74dfa5c2..605fb0fd 100644 --- a/vrrpd/tasks/main.yml +++ b/vrrpd/tasks/main.yml @@ -1,29 +1,65 @@ --- - name: Install Evolix public repositry - include_role: + ansible.builtin.include_role: name: evolix/apt tasks_from: evolix_public.yml + tags: + - vrrpd - name: Install vrrpd packages - apt: + ansible.builtin.apt: name: vrrpd=1.0-2.evolix allow_unauthenticated: yes state: present tags: - - vrrpd + - vrrpd -- name: Adjust sysctl config - sysctl: +- name: Adjust sysctl config (except rp_filter) + ansible.posix.sysctl: name: "{{ item.name }}" value: "{{ item.value }}" + sysctl_file: /etc/sysctl.d/vrrpd.conf sysctl_set: yes state: present loop: - - { name: 'net.ipv4.conf.default.rp_filter', value: 0 } - - { name: 'net.ipv4.conf.eth0.rp_filter', value: 0 } - - { name: 'net.ipv4.conf.all.rp_filter', value: 0 } - { name: 'net.ipv4.conf.all.arp_ignore', value: 1 } - { name: 'net.ipv4.conf.all.arp_announce', value: 2 } - { name: 'net.ipv4.ip_nonlocal_bind', value: 1 } tags: - - vrrpd + - vrrpd + +- name: look if rp_filter is managed by minifirewall + ansible.builtin.command: + cmd: grep "SYSCTL_RP_FILTER=" /etc/default/minifirewall + failed_when: False + changed_when: False + check_mode: no + register: grep_sysctl_rp_filter_minifirewall + +- name: Configure SYSCTL_RP_FILTER in minifirewall + ansible.builtin.lineinfile: + dest: "/etc/default/minifirewall" + line: "SYSCTL_RP_FILTER='0'" + regexp: "SYSCTL_RP_FILTER=('|\").*('|\")" + create: no + when: grep_sysctl_rp_filter_minifirewall.rc == 0 + +- name: Adjust sysctl config (only rp_filter) + ansible.posix.sysctl: + name: "{{ item.name }}" + value: "{{ item.value }}" + sysctl_file: /etc/sysctl.d/vrrpd.conf + sysctl_set: yes + state: present + loop: + - { name: 'net.ipv4.conf.default.rp_filter', value: 0 } + - { name: 'net.ipv4.conf.all.rp_filter', value: 0 } + when: grep_sysctl_rp_filter_minifirewall.rc != 0 + tags: + - vrrpd + +- name: Create VRRP address + ansible.builtin.include: ip.yml + loop: "{{ vrrp_addresses }}" + loop_control: + loop_var: "vrrp_address" \ No newline at end of file diff --git a/vrrpd/templates/vrrp.service.j2 b/vrrpd/templates/vrrp.service.j2 new file mode 100644 index 00000000..4db5d7a9 --- /dev/null +++ b/vrrpd/templates/vrrp.service.j2 @@ -0,0 +1,15 @@ +[Unit] +Description=VRRP Daemon for IP {{ vrrp_address.ip }} on {{ vrrp_address.interface }} +After=network.target + +[Service] +ExecStart=/usr/sbin/vrrpd -i {{ vrrp_address.interface | mandatory }} -x -D -d {{ vrrp_address.delay | mandatory }} -v {{ vrrp_address.id | mandatory }} -p {{ vrrp_address.priority | mandatory }} -a {{ vrrp_address.authentication | mandatory }} -l {{ vrrp_address.label | mandatory }} {{ vrrp_address.ip | mandatory }} +# PIDFile=/var/run/vrrpd_{{ vrrp_address.label }}_{{ vrrp_address.id }}.pid +Restart=on-failure +Type=forking +IgnoreSIGPIPE=no +KillMode=process +RemainAfterExit=yes + +[Install] +WantedBy=default.target \ No newline at end of file diff --git a/webapps/evoadmin-mail/handlers/main.yml b/webapps/evoadmin-mail/handlers/main.yml index beb030e2..a8638ea5 100644 --- a/webapps/evoadmin-mail/handlers/main.yml +++ b/webapps/evoadmin-mail/handlers/main.yml @@ -1,15 +1,15 @@ --- - name: reload apache2 - service: + ansible.builtin.service: name: apache2 state: reloaded - name: reload nginx - service: + ansible.builtin.service: name: nginx state: reloaded - name: reload php-fpm - service: + ansible.builtin.service: name: php7.0-fpm state: reloaded diff --git a/webapps/evoadmin-mail/tasks/apache.yml b/webapps/evoadmin-mail/tasks/apache.yml index f975c5f9..26c2b53b 100644 --- a/webapps/evoadmin-mail/tasks/apache.yml +++ b/webapps/evoadmin-mail/tasks/apache.yml @@ -1,6 +1,6 @@ --- - name: Install evoadminmail VHost - template: + ansible.builtin.template: src: apache_evoadminmail.conf.j2 dest: /etc/apache2/sites-available/evoadminmail.conf notify: reload apache2 @@ -8,7 +8,7 @@ - evoadmin-mail - name: Enable evoadminmail vhost - file: + ansible.builtin.file: src: "/etc/apache2/sites-available/evoadminmail.conf" dest: "/etc/apache2/sites-enabled/evoadminmail.conf" state: link @@ -18,7 +18,7 @@ - evoadmin-mail - name: Disable evoadminmail vhost - file: + ansible.builtin.file: dest: "/etc/apache2/sites-enabled/evoadminmail.conf" state: absent notify: reload apache2 diff --git a/webapps/evoadmin-mail/tasks/main.yml b/webapps/evoadmin-mail/tasks/main.yml index e5af6a7f..a1018eca 100644 --- a/webapps/evoadmin-mail/tasks/main.yml +++ b/webapps/evoadmin-mail/tasks/main.yml @@ -1,13 +1,18 @@ --- + +- name: Remount /usr RW + ansible.builtin.include_role: + name: evolix/remount-usr + - name: Install evoadmin-mail package - apt: - name: evoadmin-mail + ansible.builtin.apt: + deb: /tmp/evoadmin-mail.deb state: present tags: - evoadmin-mail - name: Configure contact mail - ini_file: + community.general.ini_file: dest: /etc/evoadmin-mail/config.ini section: global option: mail @@ -15,16 +20,16 @@ tags: - evoadmin-mail -- include: ssl.yml +- ansible.builtin.include: ssl.yml -- include: apache.yml +- ansible.builtin.include: apache.yml when: evoadminmail_webserver == "apache" -- include: nginx.yml +- ansible.builtin.include: nginx.yml when: evoadminmail_webserver == "nginx" - name: enable evoadmin-mail link in default site index - lineinfile: + ansible.builtin.lineinfile: dest: /var/www/index.html state: present regexp: "EvoAdmin-mail" diff --git a/webapps/evoadmin-mail/tasks/nginx.yml b/webapps/evoadmin-mail/tasks/nginx.yml index 2cb490e8..9b527009 100644 --- a/webapps/evoadmin-mail/tasks/nginx.yml +++ b/webapps/evoadmin-mail/tasks/nginx.yml @@ -1,6 +1,6 @@ --- - name: Copy php-fpm evoadmin-mail pool - copy: + ansible.builtin.copy: src: pool.evoadmin-mail.conf dest: /etc/php/7.0/fpm/pool.d/evoadmin-mail.conf notify: reload php-fpm @@ -8,7 +8,7 @@ - evoadmin-mail - name: Install evoadminmail VHost - template: + ansible.builtin.template: src: nginx_evoadminmail.conf.j2 dest: /etc/nginx/sites-available/evoadminmail.conf notify: reload nginx @@ -16,7 +16,7 @@ - evoadmin-mail - name: Active evoadminmail VHost - file: + ansible.builtin.file: src: "/etc/nginx/sites-available/evoadminmail.conf" dest: "/etc/nginx/sites-enabled/evoadminmail.conf" state: link @@ -26,7 +26,7 @@ - evoadmin-mail - name: Disable evoadminmail vhost - file: + ansible.builtin.file: dest: "/etc/nginx/sites-enabled/evoadminmail.conf" state: absent notify: reload nginx diff --git a/webapps/evoadmin-mail/tasks/ssl.yml b/webapps/evoadmin-mail/tasks/ssl.yml index b6f47127..9d9c9896 100644 --- a/webapps/evoadmin-mail/tasks/ssl.yml +++ b/webapps/evoadmin-mail/tasks/ssl.yml @@ -1,20 +1,21 @@ --- - name: ssl-cert package is installed - apt: + ansible.builtin.apt: name: ssl-cert state: present tags: - evoadmin-mail - name: Create private key and csr for default site ({{ ansible_fqdn }}) - command: openssl req -newkey rsa:2048 -sha256 -nodes -keyout /etc/ssl/private/{{ evoadminmail_host }}.key -out /etc/ssl/{{ evoadminmail_host }}.csr -batch -subj "/CN={{ evoadminmail_host }}" + ansible.builtin.command: + cmd: openssl req -newkey rsa:2048 -sha256 -nodes -keyout /etc/ssl/private/{{ evoadminmail_host }}.key -out /etc/ssl/{{ evoadminmail_host }}.csr -batch -subj "/CN={{ evoadminmail_host }}" args: creates: "/etc/ssl/private/{{ evoadminmail_host }}.key" tags: - evoadmin-mail - name: Adjust rights on private key - file: + ansible.builtin.file: dest: /etc/ssl/private/{{ evoadminmail_host }}.key owner: root group: ssl-cert @@ -23,7 +24,8 @@ - evoadmin-mail - name: Create certificate for default site - command: openssl x509 -req -days 3650 -sha256 -in /etc/ssl/{{ evoadminmail_host }}.csr -signkey /etc/ssl/private/{{ evoadminmail_host }}.key -out /etc/ssl/certs/{{ evoadminmail_host }}.crt + ansible.builtin.command: + cmd: openssl x509 -req -days 3650 -sha256 -in /etc/ssl/{{ evoadminmail_host }}.csr -signkey /etc/ssl/private/{{ evoadminmail_host }}.key -out /etc/ssl/certs/{{ evoadminmail_host }}.crt args: creates: "/etc/ssl/certs/{{ evoadminmail_host }}.crt" tags: diff --git a/webapps/evoadmin-web/handlers/main.yml b/webapps/evoadmin-web/handlers/main.yml index 669b0553..2c49ce24 100644 --- a/webapps/evoadmin-web/handlers/main.yml +++ b/webapps/evoadmin-web/handlers/main.yml @@ -1,14 +1,15 @@ --- - name: reload apache2 - service: + ansible.builtin.service: name: apache2 state: reloaded - name: restart apache2 - service: + ansible.builtin.service: name: apache2 state: restarted - name: newaliases - command: newaliases + ansible.builtin.command: + cmd: newaliases diff --git a/webapps/evoadmin-web/tasks/config.yml b/webapps/evoadmin-web/tasks/config.yml index 1053360c..8c3dc801 100644 --- a/webapps/evoadmin-web/tasks/config.yml +++ b/webapps/evoadmin-web/tasks/config.yml @@ -1,13 +1,13 @@ --- - name: "Create /etc/evolinux" - file: + ansible.builtin.file: dest: "/etc/evolinux" recurse: True state: directory - name: Configure web-add config file - template: + ansible.builtin.template: src: "{{ item }}" dest: /etc/evolinux/web-add.conf force: "{{ evoadmin_add_conf_force }}" @@ -21,7 +21,7 @@ register: evoadmin_add_conf_template - name: Configure web-add template file for mail - template: + ansible.builtin.template: src: "{{ item }}" dest: "{{ evoadmin_scripts_dir }}/web-mail.tpl" force: "{{ evoadmin_mail_tpl_force }}" diff --git a/webapps/evoadmin-web/tasks/ftp.yml b/webapps/evoadmin-web/tasks/ftp.yml index 98f275ff..8c400e68 100644 --- a/webapps/evoadmin-web/tasks/ftp.yml +++ b/webapps/evoadmin-web/tasks/ftp.yml @@ -1,12 +1,12 @@ --- - name: patch must be installed - apt: + ansible.builtin.apt: name: patch state: present - name: Patch ProFTPd config file - patch: + ansible.posix.patch: remote_src: False src: ftp/evolinux.conf.diff dest: /etc/proftpd/conf.d/z-evolinux.conf diff --git a/webapps/evoadmin-web/tasks/main.yml b/webapps/evoadmin-web/tasks/main.yml index 1acb2aa5..19253bf5 100644 --- a/webapps/evoadmin-web/tasks/main.yml +++ b/webapps/evoadmin-web/tasks/main.yml @@ -1,24 +1,24 @@ --- - name: "Ensure that evoadmin_contact_email is defined" - fail: + ansible.builtin.fail: msg: Please configure var evoadmin_contact_email when: evoadmin_contact_email is none or evoadmin_contact_email | length == 0 -- include: packages.yml +- ansible.builtin.include: packages.yml -- include: user.yml +- ansible.builtin.include: user.yml -- include: config.yml +- ansible.builtin.include: config.yml -- include: ssl.yml +- ansible.builtin.include: ssl.yml -- include: web.yml +- ansible.builtin.include: web.yml -- include: ftp.yml +- ansible.builtin.include: ftp.yml - name: enable evoadmin-web link in default site index - blockinfile: + ansible.builtin.blockinfile: dest: /var/www/index.html marker: "" block: | diff --git a/webapps/evoadmin-web/tasks/packages.yml b/webapps/evoadmin-web/tasks/packages.yml index e78f6c7b..d44ca731 100644 --- a/webapps/evoadmin-web/tasks/packages.yml +++ b/webapps/evoadmin-web/tasks/packages.yml @@ -1,31 +1,39 @@ --- -- include_role: +- ansible.builtin.include_role: name: evolix/apt tasks_from: evolix_public.yml # /!\ Warning, this is a temporary hack -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr # /!\ Warning, this is a temporary hack - name: Install PHP packages from sid (Debian 10) - apt: + ansible.builtin.apt: deb: '{{ item }}' state: present loop: - 'http://mirror.evolix.org/debian/pool/main/p/php-log/php-log_1.12.9-2_all.deb' when: ansible_distribution_major_version is version('10', '=') +- name: Install PHP packages from sid (Debian 12) + ansible.builtin.apt: + deb: '{{ item }}' + state: present + loop: + - 'http://mirror.evolix.org/debian/pool/main/p/php-log/php-log_1.13.2-1_all.deb' + when: ansible_distribution_major_version is version('12', '=') + - name: Install PHP packages - apt: + ansible.builtin.apt: name: - php-pear - php-log state: present - name: Install PHP5 packages (jessie) - apt: + ansible.builtin.apt: name: php5-pam state: present allow_unauthenticated: True diff --git a/webapps/evoadmin-web/tasks/ssl.yml b/webapps/evoadmin-web/tasks/ssl.yml index 6bdf1421..04fed56c 100644 --- a/webapps/evoadmin-web/tasks/ssl.yml +++ b/webapps/evoadmin-web/tasks/ssl.yml @@ -2,23 +2,25 @@ - name: ssl-cert package is installed - apt: + ansible.builtin.apt: name: ssl-cert state: present - name: Create private key and csr for default site ({{ ansible_fqdn }}) - command: openssl req -newkey rsa:2048 -sha256 -nodes -keyout /etc/ssl/private/{{ evoadmin_host }}.key -out /etc/ssl/{{ evoadmin_host }}.csr -batch -subj "/CN={{ evoadmin_host }}" + ansible.builtin.command: + cmd: openssl req -newkey rsa:2048 -sha256 -nodes -keyout /etc/ssl/private/{{ evoadmin_host }}.key -out /etc/ssl/{{ evoadmin_host }}.csr -batch -subj "/CN={{ evoadmin_host }}" args: creates: "/etc/ssl/private/{{ evoadmin_host }}.key" - name: Adjust rights on private key - file: + ansible.builtin.file: path: /etc/ssl/private/{{ evoadmin_host }}.key owner: root group: ssl-cert mode: "0640" - name: Create certificate for default site - command: openssl x509 -req -days 3650 -sha256 -in /etc/ssl/{{ evoadmin_host }}.csr -signkey /etc/ssl/private/{{ evoadmin_host }}.key -out /etc/ssl/certs/{{ evoadmin_host }}.crt + ansible.builtin.command: + cmd: openssl x509 -req -days 3650 -sha256 -in /etc/ssl/{{ evoadmin_host }}.csr -signkey /etc/ssl/private/{{ evoadmin_host }}.key -out /etc/ssl/certs/{{ evoadmin_host }}.crt args: creates: "/etc/ssl/certs/{{ evoadmin_host }}.crt" diff --git a/webapps/evoadmin-web/tasks/user.yml b/webapps/evoadmin-web/tasks/user.yml index bbad1b8f..96c29803 100644 --- a/webapps/evoadmin-web/tasks/user.yml +++ b/webapps/evoadmin-web/tasks/user.yml @@ -1,7 +1,7 @@ --- - name: Create evoadmin account - user: + ansible.builtin.user: name: evoadmin comment: "Evoadmin Web Account" home: "{{ evoadmin_home_dir }}" @@ -9,30 +9,31 @@ system: yes - name: Create www-evoadmin group - group: + + ansible.builtin.group: name: www-evoadmin state: present - name: "Create www-evoadmin and add to group shadow (jessie)" - user: + ansible.builtin.user: name: www-evoadmin groups: shadow append: True when: ansible_distribution_release == "jessie" - name: "Create www-evoadmin (Debian 9 or later)" - user: + ansible.builtin.user: name: www-evoadmin system: yes when: ansible_distribution_major_version is version('9', '>=') - name: Is /etc/aliases present? - stat: + ansible.builtin.stat: path: /etc/aliases register: etc_aliases - name: Set evoadmin aliases - lineinfile: + ansible.builtin.lineinfile: dest: /etc/aliases line: "{{ item.line }}" regexp: "{{ item.regexp }}" @@ -44,12 +45,12 @@ when: etc_aliases.stat.exists - name: Git is needed to clone the evoadmin repository - apt: + ansible.builtin.apt: name: git state: present - name: "Clone evoadmin repository (jessie)" - git: + ansible.builtin.git: repo: https://forge.evolix.org/evoadmin-web.git dest: "{{ evoadmin_document_root }}" version: jessie @@ -57,7 +58,7 @@ when: ansible_distribution_release == "jessie" - name: "Clone evoadmin repository (Debian 9 or later)" - git: + ansible.builtin.git: repo: https://forge.evolix.org/evoadmin-web.git dest: "{{ evoadmin_document_root }}" version: master @@ -65,46 +66,46 @@ when: ansible_distribution_major_version is version('9', '>=') - name: Change ownership on git repository - file: + ansible.builtin.file: dest: "{{ evoadmin_document_root }}" owner: "{{ evoadmin_username }}" group: "{{ evoadmin_username }}" recurse: True - name: Create evoadmin log directory - file: + ansible.builtin.file: name: "{{ evoadmin_log_dir }}" owner: "{{ evoadmin_username }}" group: "{{ evoadmin_username }}" state: directory -- include_role: +- ansible.builtin.include_role: name: evolix/remount-usr - when: evoadmin_scripts_dir is search ("/usr") + when: evoadmin_scripts_dir is search("/usr") - name: "Create {{ evoadmin_scripts_dir }}" - file: + ansible.builtin.file: dest: "{{ evoadmin_scripts_dir }}" # recurse: True mode: "0700" state: directory - name: Install scripts like web-add.sh - shell: "cp {{ evoadmin_document_root }}/scripts/* {{ evoadmin_scripts_dir }}/" + ansible.builtin.shell: + cmd: "cp {{ evoadmin_document_root }}/scripts/* {{ evoadmin_scripts_dir }}/" args: creates: "{{ evoadmin_scripts_dir }}/web-add.sh" # we use a shell command to have a "changed" that really reflects the result. - name: Fix permissions - command: "chmod -R --verbose u=rwX,g=rX,o= {{ evoadmin_document_root }}" + ansible.builtin.command: + cmd: "chmod -R --verbose u=rwX,g=rX,o= {{ evoadmin_document_root }}" register: command_result changed_when: "'changed' in command_result.stdout" # failed_when: False - args: - warn: False - name: Add evoadmin sudoers file - template: + ansible.builtin.template: src: "{{ item }}" dest: /etc/sudoers.d/evoadmin mode: "0600" diff --git a/webapps/evoadmin-web/tasks/web.yml b/webapps/evoadmin-web/tasks/web.yml index 9778da4e..fc266462 100644 --- a/webapps/evoadmin-web/tasks/web.yml +++ b/webapps/evoadmin-web/tasks/web.yml @@ -1,7 +1,7 @@ --- - name: "Set custom values for PHP config (jessie)" - ini_file: + community.general.ini_file: dest: /etc/php5/apache2/conf.d/zzz-evolinux-custom.ini section: PHP option: "disable_functions" @@ -10,34 +10,43 @@ when: ansible_distribution_release == "jessie" - name: "Set custom values for PHP config (Debian 9)" - ini_file: + community.general.ini_file: dest: /etc/php/7.0/apache2/conf.d/zzz-evolinux-custom.ini section: PHP option: "disable_functions" value: "shell-exec,system,passthru,putenv,popen" notify: reload apache2 - when: ansible_distribution_major_version is version('9', '=') + when: ansible_distribution_release == "stretch" - name: "Set custom values for PHP config (Debian 10)" - ini_file: + community.general.ini_file: dest: /etc/php/7.3/apache2/conf.d/zzz-evolinux-custom.ini section: PHP option: "disable_functions" value: "shell-exec,system,passthru,putenv,popen" notify: reload apache2 - when: ansible_distribution_major_version is version('10', '=') + when: ansible_distribution_release == "buster" - name: "Set custom values for PHP config (Debian 11)" - ini_file: + community.general.ini_file: dest: /etc/php/7.4/apache2/conf.d/zzz-evolinux-custom.ini section: PHP option: "disable_functions" value: "shell-exec,system,passthru,putenv,popen" notify: reload apache2 - when: ansible_distribution_major_version is version('11', '=') + when: ansible_distribution_release == "bullseye" + +- name: "Set custom values for PHP config (Debian 11)" + community.general.ini_file: + dest: /etc/php/8.1/apache2/conf.d/zzz-evolinux-custom.ini + section: PHP + option: "disable_functions" + value: "shell-exec,system,passthru,putenv,popen" + notify: reload apache2 + when: ansible_distribution_release == "bookworm" - name: Install evoadmin VHost - template: + ansible.builtin.template: src: "{{ item }}" dest: /etc/apache2/sites-available/evoadmin.conf force: "{{ evoadmin_force_vhost }}" @@ -52,21 +61,23 @@ notify: reload apache2 - name: Enable evoadmin vhost - command: "a2ensite evoadmin.conf" + ansible.builtin.command: + cmd: "a2ensite evoadmin.conf" register: cmd_a2ensite changed_when: "'Enabling site' in cmd_a2ensite.stdout" notify: reload apache2 when: evoadmin_enable_vhost | bool - name: Disable evoadmin vhost - command: "a2dissite evoadmin.conf" + ansible.builtin.command: + cmd: "a2dissite evoadmin.conf" register: cmd_a2dissite changed_when: "'Disabling site' in cmd_a2dissite.stdout" notify: reload apache2 when: not (evoadmin_enable_vhost | bool) - name: Copy htpasswd for evoadmin - template: + ansible.builtin.template: src: "{{ item }}" dest: "/var/www/.ansible_evoadmin_htpasswd" mode: "0644" @@ -84,7 +95,7 @@ when: evoadmin_htpasswd | bool - name: Copy config file for evoadmin - template: + ansible.builtin.template: src: "{{ item }}" dest: "{{ evoadmin_document_root }}/conf/config.local.php" mode: "0640" diff --git a/webapps/evoadmin-web/templates/config.local.php.j2 b/webapps/evoadmin-web/templates/config.local.php.j2 index d4cd4903..335bc34b 100644 --- a/webapps/evoadmin-web/templates/config.local.php.j2 +++ b/webapps/evoadmin-web/templates/config.local.php.j2 @@ -6,11 +6,11 @@ $localconf['debug'] = FALSE; $localconf['superadmin'] = array(); $localconf['script_path'] = '{{ evoadmin_scripts_dir }}'; $localconf['cluster'] = FALSE; -// auth (sha256 hashs) / echo -n YourPass | sha256sum -$oriconf['logins'] = array(); -//$oriconf['logins']['foo'] = 'd5d3c723fb82cb0078f399888af78204234535ec2ef3da56710fdd51f90d2477'; -//$oriconf['logins']['bar'] = '7938c84d6e43d1659612a7ea7c1101ed02e52751bb64597a8c20ebaba8ba4303'; +// Generate password hashes : mkpasswd --method=sha-512 (cli) or with PHP's password_hash() +$localconf['logins'] = array(); +//$localconf['logins']['foo'] = '$6$X0jqa/ausLSBkj4m$dLMMcPGVxak.aDPo4V/GJLm2d8vU8/QA5LbGTuqXCdxSNYU0kRKBgDl16GAyp0GqXXZ5wwDEJKQ1npgFwiuV81'; +//$localconf['logins']['bar'] = '$6$Q6233S6mlWAF6p.j$LtzwG02YucozwqjAgSpeldh24Mnz7lBuVSbOQYbKKh9FiUx3tMVl6kJZkmrNdPqeadFXKAYXrqn.gy8KposF5.'; {% if evoadmin_multiphp_versions != [] %} $localconf['php_versions'] = array( {{ evoadmin_multiphp_versions | join(', ') | replace('php', '') }} ); diff --git a/webapps/nextcloud/defaults/main.yml b/webapps/nextcloud/defaults/main.yml index 3c1bf40a..c6e0a316 100644 --- a/webapps/nextcloud/defaults/main.yml +++ b/webapps/nextcloud/defaults/main.yml @@ -1,7 +1,6 @@ --- -nextcloud_webserver: 'nginx' -nextcloud_version: "21.0.0" -nextcloud_archive_name: "nextcloud-{{ nextcloud_version }}.tar.bz2" +nextcloud_version: "latest-24" +nextcloud_archive_name: "{{ nextcloud_version }}.tar.bz2" nextcloud_releases_baseurl: "https://download.nextcloud.com/server/releases/" nextcloud_instance_name: "nextcloud" @@ -10,7 +9,7 @@ nextcloud_domains: [] nextcloud_home: "/home/{{ nextcloud_user }}" nextcloud_webroot: "{{ nextcloud_home }}/nextcloud" -nextcloud_data: "{{ nextcloud_webroot }}/data" +nextcloud_data: "{{ nextcloud_home }}/data" nextcloud_db_user: "{{ nextcloud_user }}" nextcloud_db_name: "{{ nextcloud_instance_name }}" diff --git a/webapps/nextcloud/handlers/main.yml b/webapps/nextcloud/handlers/main.yml index 2db4770d..6997c361 100644 --- a/webapps/nextcloud/handlers/main.yml +++ b/webapps/nextcloud/handlers/main.yml @@ -1,10 +1,15 @@ --- - name: reload php-fpm - service: + ansible.builtin.service: name: php7.3-fpm state: reloaded - name: reload nginx - service: + ansible.builtin.service: name: nginx state: reloaded + +- name: reload apache + ansible.builtin.service: + name: apache2 + state: reloaded \ No newline at end of file diff --git a/webapps/nextcloud/meta/main.yml b/webapps/nextcloud/meta/main.yml index d5852e32..ed97d539 100644 --- a/webapps/nextcloud/meta/main.yml +++ b/webapps/nextcloud/meta/main.yml @@ -1,4 +1 @@ --- -# dependencies: - # - { role: nginx, when: nextcloud_webserver == 'nginx' } - # - { role: php, php_fpm_enable: True } diff --git a/webapps/nextcloud/tasks/apache-system.yml b/webapps/nextcloud/tasks/apache-system.yml new file mode 100644 index 00000000..bbea82a4 --- /dev/null +++ b/webapps/nextcloud/tasks/apache-system.yml @@ -0,0 +1,34 @@ +--- + +- name: "Get PHP Version" + ansible.builtin.shell: + cmd: 'php -v | grep "PHP [0-9]." | sed -E "s/PHP ([0-9]\.[0-9]).*/\1/g;"' + register: shell_php + check_mode: no + +- name: "Set variables" + ansible.builtin.set_fact: + php_version: "{{ shell_php.stdout }}" + +- name: Apply specific PHP settings (apache) + community.general.ini_file: + path: "/etc/php/{{ php_version }}/apache2/conf.d/zzz-evolinux-custom.ini" + section: '' + option: "{{ item.option }}" + value: "{{ item.value }}" + notify: reload apache + with_items: + - {option: 'allow_url_fopen', value: 'On'} + - {option: 'disable_functions', value: ''} + - {option: 'max_execution_time', value: '300'} + - {option: 'memory_limit', value: '512M'} + +- name: Apply specific PHP settings (cli) + community.general.ini_file: + path: "/etc/php/{{ php_version }}/cli/conf.d/zzz-evolinux-custom.ini" + section: '' + option: "{{ item.option }}" + value: "{{ item.value }}" + with_items: + - {option: 'allow_url_fopen', value: 'On'} + - {option: 'apc.enable_cli', value: 'On'} diff --git a/webapps/nextcloud/tasks/apache-vhost.yml b/webapps/nextcloud/tasks/apache-vhost.yml new file mode 100644 index 00000000..36e5b989 --- /dev/null +++ b/webapps/nextcloud/tasks/apache-vhost.yml @@ -0,0 +1,23 @@ +--- +- name: Copy Apache vhost + ansible.builtin.template: + src: apache-vhost.conf.j2 + dest: "/etc/apache2/sites-available/{{ nextcloud_instance_name }}.conf" + mode: "0640" + notify: reload apache + tags: + - nextcloud + +- name: Enable Apache vhost + ansible.builtin.file: + src: "/etc/apache2/sites-available/{{ nextcloud_instance_name }}.conf" + dest: "/etc/apache2/sites-enabled/{{ nextcloud_instance_name }}.conf" + state: link + notify: reload apache + tags: + - nextcloud + +# - name: Generate ssl config +# shell: +# cmd: "/usr/local/sbin/vhost-domains {{ nextcloud_instance_name }} | /usr/local/sbin/make-csr {{ nextcloud_instance_name }}" +# creates: "/etc/nginx/ssl/{{ nextcloud_instance_name }}.conf" \ No newline at end of file diff --git a/webapps/nextcloud/tasks/archive.yml b/webapps/nextcloud/tasks/archive.yml index d59bd582..f4e1cf26 100644 --- a/webapps/nextcloud/tasks/archive.yml +++ b/webapps/nextcloud/tasks/archive.yml @@ -1,23 +1,24 @@ --- - name: Retrieve Nextcloud archive - get_url: + ansible.builtin.get_url: url: "{{ nextcloud_releases_baseurl }}{{ nextcloud_archive_name }}" dest: "{{ nextcloud_home }}/{{ nextcloud_archive_name }}" - force: no + force: false tags: - nextcloud - name: Retrieve Nextcloud sha256 checksum - get_url: + ansible.builtin.get_url: url: "{{ nextcloud_releases_baseurl }}{{ nextcloud_archive_name }}.sha256" dest: "{{ nextcloud_home }}/{{ nextcloud_archive_name }}.sha256" - force: no + force: false tags: - nextcloud - name: Verify Nextcloud sha256 checksum - command: "sha256sum -c {{ nextcloud_archive_name }}.sha256" + ansible.builtin.command: + cmd: "sha256sum -c {{ nextcloud_archive_name }}.sha256" changed_when: "False" args: chdir: "{{ nextcloud_home }}" @@ -25,7 +26,7 @@ - nextcloud - name: Extract Nextcloud archive - unarchive: + ansible.builtin.unarchive: src: "{{ nextcloud_home }}/{{ nextcloud_archive_name }}" dest: "{{ nextcloud_home }}" creates: "{{ nextcloud_home }}/nextcloud" diff --git a/webapps/nextcloud/tasks/config.yml b/webapps/nextcloud/tasks/config.yml index 85142726..93b9b925 100644 --- a/webapps/nextcloud/tasks/config.yml +++ b/webapps/nextcloud/tasks/config.yml @@ -2,15 +2,16 @@ - block: - name: Generate admin password - command: 'apg -n 1 -m 16 -M lcN' + ansible.builtin.command: + cmd: 'apg -n 1 -m 16 -M lcN' register: nextcloud_admin_password_apg check_mode: no changed_when: False - - debug: + - ansible.builtin.debug: var: nextcloud_admin_password_apg - - set_fact: + - ansible.builtin.set_fact: nextcloud_admin_password: "{{ nextcloud_admin_password_apg.stdout }}" tags: @@ -18,7 +19,8 @@ when: nextcloud_admin_password | length == 0 - name: Get Nextcloud Status - shell: "php ./occ status --output json | grep -v 'Nextcloud is not installed'" + ansible.builtin.shell: + cmd: "php ./occ status --output json | grep -v 'Nextcloud is not installed'" args: chdir: "{{ nextcloud_webroot }}" become_user: "{{ nextcloud_user }}" @@ -28,17 +30,18 @@ - nextcloud - name: Install Nextcloud - command: "php ./occ maintenance:install --database mysql --database-name {{ nextcloud_db_name | mandatory }} --database-user {{ nextcloud_db_user | mandatory }} --database-pass {{ nextcloud_db_pass | mandatory }} --admin-user {{ nextcloud_admin_login | mandatory }} --admin-pass {{ nextcloud_admin_password | mandatory }} --data-dir {{ nextcloud_data | mandatory }}" + ansible.builtin.command: + cmd: "php ./occ maintenance:install --database mysql --database-name {{ nextcloud_db_name | mandatory }} --database-user {{ nextcloud_db_user | mandatory }} --database-pass {{ nextcloud_db_pass | mandatory }} --admin-user {{ nextcloud_admin_login | mandatory }} --admin-pass {{ nextcloud_admin_password | mandatory }} --data-dir {{ nextcloud_data | mandatory }}" args: chdir: "{{ nextcloud_webroot }}" creates: "{{ nextcloud_home }}/config/config.php" become_user: "{{ nextcloud_user }}" - when: (nc_status.stdout | from_json).installed == false + when: not ((nc_status.stdout | from_json).installed | bool) tags: - nextcloud - name: Configure Nextcloud Mysql password - replace: + ansible.builtin.replace: dest: "{{ nextcloud_home }}/nextcloud/config/config.php" regexp: "'dbpassword' => '([^']*)'," replace: "'dbpassword' => '{{ nextcloud_db_pass }}'," @@ -46,7 +49,7 @@ - nextcloud - name: Configure Nextcloud cron - cron: + ansible.builtin.cron: name: 'Nextcloud' minute: "*/5" job: "php -f {{ nextcloud_webroot }}/cron.php" @@ -55,7 +58,8 @@ - nextcloud - name: Erase previously trusted domains config - command: "php ./occ config:system:set trusted_domains" + ansible.builtin.command: + cmd: "php ./occ config:system:set trusted_domains" args: chdir: "{{ nextcloud_webroot }}" become_user: "{{ nextcloud_user }}" @@ -63,7 +67,8 @@ - nextcloud - name: Configure trusted domains - command: "php ./occ config:system:set trusted_domains {{ item.0 }} --value {{ item.1 }}" + ansible.builtin.command: + cmd: "php ./occ config:system:set trusted_domains {{ item.0 }} --value {{ item.1 }}" args: chdir: "{{ nextcloud_webroot }}" with_indexed_items: diff --git a/webapps/nextcloud/tasks/main.yml b/webapps/nextcloud/tasks/main.yml index a6d39b4b..02304334 100644 --- a/webapps/nextcloud/tasks/main.yml +++ b/webapps/nextcloud/tasks/main.yml @@ -1,6 +1,6 @@ --- - name: Install dependencies - apt: + ansible.builtin.apt: state: present name: - bzip2 @@ -16,12 +16,14 @@ - php-apcu - php-redis - php-bcmath + - php-imagick + - libmagickcore-6.q16-6-extra tags: - nextcloud -# dependency for mysql_user and mysql_db +# dependency for mysql_user and mysql_db - python2 - name: python modules is installed (Ansible dependency) - apt: + ansible.builtin.apt: name: - python-mysqldb - python-pymysql @@ -30,9 +32,9 @@ - nextcloud when: ansible_python_version is version('3', '<') -# dependency for mysql_user and mysql_db +# dependency for mysql_user and mysql_db - python3 - name: python3 modules is installed (Ansible dependency) - apt: + ansible.builtin.apt: name: - python3-mysqldb - python3-pymysql @@ -41,12 +43,14 @@ - nextcloud when: ansible_python_version is version('3', '>=') -- include: user.yml +- ansible.builtin.include: apache-system.yml -- include: archive.yml +- ansible.builtin.include: user.yml -- include: vhost.yml +- ansible.builtin.include: archive.yml -- include: mysql.yml +- ansible.builtin.include: apache-vhost.yml -- include: config.yml +- ansible.builtin.include: mysql-user.yml + +- ansible.builtin.include: config.yml diff --git a/webapps/nextcloud/tasks/mysql.yml b/webapps/nextcloud/tasks/mysql-user.yml similarity index 82% rename from webapps/nextcloud/tasks/mysql.yml rename to webapps/nextcloud/tasks/mysql-user.yml index a12a80f4..82c3acb3 100644 --- a/webapps/nextcloud/tasks/mysql.yml +++ b/webapps/nextcloud/tasks/mysql-user.yml @@ -1,6 +1,7 @@ --- - name: Get actual Mysql password - shell: "grep password {{ nextcloud_home }}/.my.cnf | awk '{ print $3 }'" + ansible.builtin.shell: + cmd: "grep password {{ nextcloud_home }}/.my.cnf | awk '{ print $3 }'" register: nextcloud_db_pass_grep check_mode: no changed_when: False @@ -9,7 +10,8 @@ - nextcloud - name: Generate Mysql password - command: 'apg -n 1 -m 16 -M lcN' + ansible.builtin.command: + cmd: 'apg -n 1 -m 16 -M lcN' register: nextcloud_db_pass_apg check_mode: no changed_when: False @@ -17,17 +19,17 @@ - nextcloud - name: Set Mysql password - set_fact: + ansible.builtin.set_fact: nextcloud_db_pass: "{{ nextcloud_db_pass_grep.stdout | default(nextcloud_db_pass_apg.stdout, True) }}" tags: - nextcloud -- debug: +- ansible.builtin.debug: var: nextcloud_db_pass verbosity: 1 - name: Create Mysql database - mysql_db: + community.mysql.mysql_db: name: "{{ nextcloud_db_name }}" config_file: "/root/.my.cnf" state: present @@ -35,7 +37,7 @@ - nextcloud - name: Create Mysql user - mysql_user: + community.mysql.mysql_user: name: "{{ nextcloud_db_user }}" password: '{{ nextcloud_db_pass }}' priv: "{{ nextcloud_db_name }}.*:ALL" @@ -46,7 +48,7 @@ - nextcloud - name: Store credentials in my.cnf - ini_file: + community.general.ini_file: dest: "{{ nextcloud_home }}/.my.cnf" owner: "{{ nextcloud_user }}" group: "{{ nextcloud_user }}" diff --git a/webapps/nextcloud/tasks/user.yml b/webapps/nextcloud/tasks/user.yml index e89fe41a..fdc5b7a6 100644 --- a/webapps/nextcloud/tasks/user.yml +++ b/webapps/nextcloud/tasks/user.yml @@ -1,15 +1,16 @@ --- -- name: Create Nextcloud group - group: - name: "{{ nextcloud_instance_name | mandatory }}" +- name: Create {{ nextcloud_user }} unix group + + ansible.builtin.group: + name: "{{ nextcloud_user | mandatory }}" state: present tags: - nextcloud -- name: Create Nextcloud user - user: +- name: Create {{ nextcloud_user | mandatory }} unix user + ansible.builtin.user: name: "{{ nextcloud_user | mandatory }}" - group: "{{ nextcloud_user }}" + group: "{{ nextcloud_user | mandatory }}" home: "{{ nextcloud_home | mandatory }}" shell: '/bin/bash' create_home: True @@ -18,17 +19,11 @@ tags: - nextcloud -- name: Add the user 'www-data' to Nextcloud group - user: - name: www-data - groups: "{{ nextcloud_user | mandatory }}" - append: yes - - name: Create top-level directories - file: + ansible.builtin.file: dest: "{{ item }}" state: directory - mode: "0770" + mode: "0700" owner: "{{ nextcloud_user }}" group: "{{ nextcloud_user }}" loop: @@ -37,3 +32,11 @@ - "{{ nextcloud_home }}/data" tags: - nextcloud + +- name: Mount up Ceph volume by UUID + ansible.posix.mount: + path: "{{ nextcloud_data }}" + src: "{{ nextcloud_data_uuid }}" + fstype: ext4 + opts: defaults,noexec,nosuid,nodev,relatime,lazytime + state: present diff --git a/webapps/nextcloud/tasks/vhost.yml b/webapps/nextcloud/tasks/vhost.yml deleted file mode 100644 index 1f1592cc..00000000 --- a/webapps/nextcloud/tasks/vhost.yml +++ /dev/null @@ -1,34 +0,0 @@ ---- -- block: - - name: Copy Nginx vhost - template: - src: nginx.conf.j2 - dest: "/etc/nginx/sites-available/{{ nextcloud_instance_name }}.conf" - mode: "0640" - notify: reload nginx - tags: - - nextcloud - - - name: Enable Nginx vhost - file: - src: "/etc/nginx/sites-available/{{ nextcloud_instance_name }}.conf" - dest: "/etc/nginx/sites-enabled/{{ nextcloud_instance_name }}.conf" - state: link - notify: reload nginx - tags: - - nextcloud - - - name: Generate ssl config - shell: - cmd: "/usr/local/sbin/vhost-domains {{ nextcloud_instance_name }} | /usr/local/sbin/make-csr {{ nextcloud_instance_name }}" - creates: "/etc/nginx/ssl/{{ nextcloud_instance_name }}.conf" - - - name: Copy PHP-FPM pool - template: - src: php-fpm.conf.j2 - dest: "/etc/php/7.3/fpm/pool.d/{{ nextcloud_instance_name }}.conf" - mode: "0640" - notify: reload php-fpm - tags: - - nextcloud - when: nextcloud_webserver == 'nginx' diff --git a/webapps/nextcloud/templates/apache-vhost.conf.j2 b/webapps/nextcloud/templates/apache-vhost.conf.j2 new file mode 100644 index 00000000..556fa4cb --- /dev/null +++ b/webapps/nextcloud/templates/apache-vhost.conf.j2 @@ -0,0 +1,52 @@ + + ServerName {{ nextcloud_domains[0] }} + + {% for domain_alias in nextcloud_domains[1:] %} + ServerAlias {{ domain_alias }} + {% endfor %} + + # SSL + # SSLEngine on + # SSLCertificateFile /etc/letsencrypt/live/{{ nextcloud_instance_name }}/fullchain.pem + # SSLCertificateKeyFile /etc/letsencrypt/live/{{ nextcloud_instance_name }}/privkey.pem + # Header always set Strict-Transport-Security "max-age=15552000" + + DocumentRoot {{ nextcloud_webroot }}/ + + + Require all granted + AllowOverride All + Options FollowSymLinks MultiViews + + + Dav off + + + + + Require all denied + AllowOverride None + + + Dav off + + + + # SSL Redirect + # RewriteEngine On + # RewriteCond %{HTTPS} !=on + # RewriteCond %{HTTP:X-Forwarded-Proto} !=https + # RewriteRule ^ https://%{HTTP:Host}%{REQUEST_URI} [L,R=permanent] + + # ITK + AssignUserID {{ nextcloud_user }} {{ nextcloud_user }} + + # LOG + CustomLog /var/log/apache2/access.log vhost_combined + ErrorLog /var/log/apache2/error.log + + # PHP + php_admin_value sendmail_path "/usr/sbin/sendmail -t -i -f {{ nextcloud_user }}" + php_admin_value open_basedir "/usr/share/php:{{ nextcloud_home }}:/tmp" + + \ No newline at end of file diff --git a/webapps/nextcloud/templates/nginx.conf.j2 b/webapps/nextcloud/templates/nginx.conf.j2 deleted file mode 100644 index c2b7b7e3..00000000 --- a/webapps/nextcloud/templates/nginx.conf.j2 +++ /dev/null @@ -1,134 +0,0 @@ -upstream php-handler-{{ nextcloud_instance_name }} { - server unix:/var/run/php/php-fpm-{{ nextcloud_instance_name }}.sock; -} - -server { - listen 80; - listen [::]:80; - listen 443 ssl http2; - listen [::]:443 ssl http2; - - server_name {{ nextcloud_domains | join(' ') }}; - - access_log {{ nextcloud_home }}/log/access.log; - error_log {{ nextcloud_home }}/log/error.log; - - include /etc/nginx/snippets/letsencrypt.conf; - include /etc/nginx/ssl/{{ nextcloud_instance_name }}.conf; - - add_header Referrer-Policy "no-referrer" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-Download-Options "noopen" always; - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Permitted-Cross-Domain-Policies "none" always; - add_header X-Robots-Tag "none" always; - add_header X-XSS-Protection "1; mode=block" always; - - # Remove X-Powered-By, which is an information leak - fastcgi_hide_header X-Powered-By; - - root {{ nextcloud_webroot }}; - - location = /robots.txt { - allow all; - log_not_found off; - access_log off; - } - - # Make a regex exception for `/.well-known` so that clients can still - # access it despite the existence of the regex rule - # `location ~ /(\.|autotest|...)` which would otherwise handle requests - # for `/.well-known`. - location ^~ /.well-known { - # The following 6 rules are borrowed from `.htaccess` - - location = /.well-known/carddav { return 301 /remote.php/dav/; } - location = /.well-known/caldav { return 301 /remote.php/dav/; } - # Anything else is dynamically handled by Nextcloud - location ^~ /.well-known { return 301 /index.php$uri; } - location ~ ^/.well-known/acme-challenge/* { allow all; } - - try_files $uri $uri/ =404; - } - - # set max upload size - client_max_body_size 512M; - fastcgi_buffers 64 4K; - - # Enable gzip but do not remove ETag headers - gzip on; - gzip_vary on; - gzip_comp_level 4; - gzip_min_length 256; - gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; - gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; - - - location / { - rewrite ^ /index.php; - } - - location ~ ^\/(?:build|tests|config|lib|3rdparty|templates|data)\/ { - deny all; - } - location ~ ^\/(?:\.|autotest|occ|issue|indie|db_|console) { - deny all; - } - - - location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+|.+\/richdocumentscode\/proxy)\.php(?:$|\/) { - fastcgi_split_path_info ^(.+?\.php)(\/.*|)$; - set $path_info $fastcgi_path_info; - try_files $fastcgi_script_name =404; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $path_info; - fastcgi_param HTTPS on; - # Avoid sending the security headers twice - fastcgi_param modHeadersAvailable true; - # Enable pretty urls - fastcgi_param front_controller_active true; - fastcgi_pass php-handler-{{ nextcloud_instance_name }}; - fastcgi_intercept_errors on; - fastcgi_request_buffering off; - } - - location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) { - try_files $uri/ =404; - index index.php; - } - - # Adding the cache control header for js, css and map files - # Make sure it is BELOW the PHP block - location ~ \.(?:css|js|woff2?|svg|gif|map)$ { - try_files $uri /index.php$request_uri; - add_header Cache-Control "public, max-age=15778463"; - # Add headers to serve security related headers (It is intended to - # have those duplicated to the ones above) - # Before enabling Strict-Transport-Security headers please read into - # this topic first. - #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always; - # - # WARNING: Only add the preload option once you read about - # the consequences in https://hstspreload.org/. This option - # will add the domain to a hardcoded list that is shipped - # in all major browsers and getting removed from this list - # could take several months. - add_header Referrer-Policy "no-referrer" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-Download-Options "noopen" always; - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Permitted-Cross-Domain-Policies "none" always; - add_header X-Robots-Tag "none" always; - add_header X-XSS-Protection "1; mode=block" always; - - # Optional: Don't log access to assets - access_log off; - } - - location ~ \.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$ { - try_files $uri /index.php$request_uri; - # Optional: Don't log access to other assets - access_log off; - } -} diff --git a/webapps/nextcloud/templates/php-fpm.conf.j2 b/webapps/nextcloud/templates/php-fpm.conf.j2 deleted file mode 100644 index 1b4c7861..00000000 --- a/webapps/nextcloud/templates/php-fpm.conf.j2 +++ /dev/null @@ -1,17 +0,0 @@ -[{{ nextcloud_instance_name }}] -user = {{ nextcloud_user }} -group = {{ nextcloud_user }} -listen = /run/php/php-fpm-{{ nextcloud_instance_name }}.sock -listen.owner = {{ nextcloud_user }} -listen.group = {{ nextcloud_user }} - -pm = ondemand -pm.max_children = 50 -pm.process_idle_timeout = 120s -pm.status_path = /fpm_status - -env[HOSTNAME] = $HOSTNAME -env[PATH] = /usr/local/bin:/usr/bin:/bin -env[TMP] = {{ nextcloud_home }}/tmp -env[TMPDIR] = {{ nextcloud_home }}/tmp -env[TEMP] = {{ nextcloud_home }}/tmp diff --git a/webapps/roundcube/handlers/main.yml b/webapps/roundcube/handlers/main.yml index 98b530d9..f16ba8d6 100644 --- a/webapps/roundcube/handlers/main.yml +++ b/webapps/roundcube/handlers/main.yml @@ -1,15 +1,15 @@ --- - name: restart imapproxy - systemd: + ansible.builtin.systemd: name: imapproxy state: restarted - name: reload apache2 - service: + ansible.builtin.service: name: apache2 state: reloaded - name: reload nginx - service: + ansible.builtin.service: name: nginx state: reloaded diff --git a/webapps/roundcube/tasks/main.yml b/webapps/roundcube/tasks/main.yml index 08fe73d1..17422246 100644 --- a/webapps/roundcube/tasks/main.yml +++ b/webapps/roundcube/tasks/main.yml @@ -1,6 +1,6 @@ --- - name: configure roundcube-core - debconf: + ansible.builtin.debconf: name: roundcube-core question: "{{ item.key }}" value: "{{ item.value }}" @@ -12,7 +12,7 @@ - roundcube - name: install Roundcube - apt: + ansible.builtin.apt: name: - imapproxy - roundcube @@ -25,7 +25,7 @@ - roundcube - name: configure imapproxy imap host - lineinfile: + ansible.builtin.lineinfile: dest: /etc/imapproxy.conf regexp: "^server_hostname" line: "server_hostname {{ roundcube_imap_host }}" @@ -34,7 +34,7 @@ - roundcube - name: configure imapproxy imap port - lineinfile: + ansible.builtin.lineinfile: dest: /etc/imapproxy.conf regexp: "^server_port" line: "server_port {{ roundcube_imap_port }}" @@ -43,7 +43,7 @@ - roundcube - name: enable and start imapproxy - service: + ansible.builtin.service: name: imapproxy state: started enabled: True @@ -51,7 +51,7 @@ - roundcube - name: configure roundcube imap host - lineinfile: + ansible.builtin.lineinfile: dest: /etc/roundcube/config.inc.php regexp: "\\$config\\['default_host'\\]" line: "$config['default_host'] = array('127.0.0.1');" @@ -59,7 +59,7 @@ - roundcube - name: configure roudcube imap port - lineinfile: + ansible.builtin.lineinfile: dest: /etc/roundcube/config.inc.php regexp: "\\$config\\['default_port'\\]" insertafter: "\\$config\\['default_host'\\]" @@ -68,7 +68,7 @@ - roundcube - name: configure managesieve plugin - copy: + ansible.builtin.copy: src: /usr/share/roundcube/plugins/managesieve/config.inc.php.dist dest: /etc/roundcube/plugins/managesieve/config.inc.php mode: "0644" @@ -77,7 +77,7 @@ - roundcube - name: enable default plugins - replace: + ansible.builtin.replace: dest: /etc/roundcube/config.inc.php regexp: "^\\$config\\['plugins'\\] = array\\($" replace: "$config['plugins'] = array('zipdownload','managesieve'" @@ -85,7 +85,7 @@ - roundcube - name: deploy apache roundcube vhost - template: + ansible.builtin.template: src: apache2.conf.j2 dest: /etc/apache2/sites-available/roundcube.conf mode: "0640" @@ -95,7 +95,7 @@ - roundcube - name: enable apache roundcube vhost - file: + ansible.builtin.file: src: /etc/apache2/sites-available/roundcube.conf dest: /etc/apache2/sites-enabled/roundcube.conf state: link @@ -105,14 +105,14 @@ - roundcube - name: deploy Nginx roundcube vhost - template: + ansible.builtin.template: src: nginx.conf.j2 dest: /etc/nginx/sites-available/roundcube.conf when: roundcube_webserver == "nginx" notify: reload nginx - name: enable Nginx roundcube vhost - file: + ansible.builtin.file: src: "/etc/nginx/sites-available/roundcube.conf" dest: "/etc/nginx/sites-enabled/roundcube.conf" state: link @@ -120,7 +120,7 @@ notify: reload nginx - name: enable roundcube link in default site index - lineinfile: + ansible.builtin.lineinfile: dest: /var/www/index.html state: present regexp: "Webmail" diff --git a/webapps/roundcube/templates/apache2.conf.j2 b/webapps/roundcube/templates/apache2.conf.j2 index 01c25f3a..87bdf79e 100644 --- a/webapps/roundcube/templates/apache2.conf.j2 +++ b/webapps/roundcube/templates/apache2.conf.j2 @@ -9,7 +9,7 @@ ServerName {{ roundcube_host }} # Repertoire principal - DocumentRoot /var/lib/roundcube/ + DocumentRoot /var/lib/roundcube/public_html # Return 503 if imapproxy doesn't run diff --git a/webapps/roundcube/templates/nginx.conf.j2 b/webapps/roundcube/templates/nginx.conf.j2 index 1719c407..66dcb8a5 100644 --- a/webapps/roundcube/templates/nginx.conf.j2 +++ b/webapps/roundcube/templates/nginx.conf.j2 @@ -10,7 +10,7 @@ server { access_log /var/log/nginx/.{{ roundcube_host }}.access.log; error_log /var/log/nginx/.{{ roundcube_host }}.error.log; - root /var/lib/roundcube/; + root /var/lib/roundcube/public_html; index index.php; location / { diff --git a/webapps/wordpress/tasks/main.yml b/webapps/wordpress/tasks/main.yml index e1f442c0..3ef832a8 100644 --- a/webapps/wordpress/tasks/main.yml +++ b/webapps/wordpress/tasks/main.yml @@ -1,34 +1,36 @@ --- - name: Create bin dir - file: + ansible.builtin.file: state: directory dest: "{{ ansible_env.HOME }}/bin" mode: "0750" - name: Download wp-cli - get_url: + ansible.builtin.get_url: url: "https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar" dest: "{{ ansible_env.HOME }}/bin/wp-cli.phar" mode: "0750" - name: Download Wordpress - shell: '{{ wordpress_wpcli }} core download --locale=fr_FR --version={{ wordpress_version }}' + ansible.builtin.shell: + cmd: '{{ wordpress_wpcli }} core download --locale=fr_FR --version={{ wordpress_version }}' args: creates: "{{ ansible_env.HOME }}/www/index.php" - name: Retrieve .my.cnf - fetch: + ansible.builtin.fetch: src: "{{ ansible_env.HOME }}/.my.cnf" dest: "/tmp/wordpress-{{ ansible_user }}.cnf" flat: yes - name: Generate random password - command: apg -n1 -m 12 -M LCN + ansible.builtin.command: + cmd: apg -n1 -m 12 -M LCN register: shell_password - changed_when: false + changed_when: False - name: Read mysql config from .my.cnf - set_fact: + ansible.builtin.set_fact: db_host: "{{ lookup('ini', 'host section=client file=/tmp/wordpress-{{ ansible_user }}.cnf default=127.0.0.1') }}" db_user: "{{ lookup('ini', 'user section=client file=/tmp/wordpress-{{ ansible_user }}.cnf default={{ ansible_user }}') }}" db_pwd: "{{ lookup('ini', 'password section=client file=/tmp/wordpress-{{ ansible_user }}.cnf') }}" @@ -36,50 +38,57 @@ admin_pwd: "{{ shell_password.stdout }}" - name: Remove local .my.cnf - file: + ansible.builtin.file: path: "/tmp/wordpress-{{ ansible_user }}.cnf" state: absent delegate_to: localhost - name: Configure Wordpress (wp-config.php) - shell: '{{ wordpress_wpcli }} core config --dbhost={{ db_host }} --dbuser={{ db_user }} --dbpass={{ db_pwd }} --dbname={{ db_name }}' + ansible.builtin.shell: + cmd: '{{ wordpress_wpcli }} core config --dbhost={{ db_host }} --dbuser={{ db_user }} --dbpass={{ db_pwd }} --dbname={{ db_name }}' args: creates: "{{ ansible_env.HOME }}/www/wp-config.php" - name: Configure site - shell: '{{ wordpress_wpcli }} core install --url={{ wordpress_host | quote }} --title={{ wordpress_title | quote }} --admin_user=admin --admin_password="{{ admin_pwd | quote }}" --admin_email={{ wordpress_email }} --skip-email' - changed_when: false + ansible.builtin.shell: + cmd: '{{ wordpress_wpcli }} core install --url={{ wordpress_host | quote }} --title={{ wordpress_title | quote }} --admin_user=admin --admin_password="{{ admin_pwd | quote }}" --admin_email={{ wordpress_email }} --skip-email' + changed_when: False - name: Check if Wordpress is up to date - shell: '{{ wordpress_wpcli }} core check-update | grep -q Success' + ansible.builtin.shell: + cmd: '{{ wordpress_wpcli }} core check-update | grep -q Success' register: check_version check_mode: no - failed_when: false + failed_when: False changed_when: check_version.rc == 1 - name: Update Wordpress - shell: '{{ wordpress_wpcli }} core update --version={{ wordpress_version }}' + ansible.builtin.shell: + cmd: '{{ wordpress_wpcli }} core update --version={{ wordpress_version }}' args: removes: "{{ ansible_env.HOME }}/www/index.php" when: check_version.rc == 1 - name: Install default plugin - shell: '{{ wordpress_wpcli }} plugin is-installed {{ item }} || {{ wordpress_wpcli }} plugin install {{ item }}' - changed_when: false + ansible.builtin.shell: + cmd: '{{ wordpress_wpcli }} plugin is-installed {{ item }} || {{ wordpress_wpcli }} plugin install {{ item }}' + changed_when: False loop: "{{ wordpress_plugins }}" - name: Update default plugins - shell: '{{ wordpress_wpcli }} plugin is-installed {{ item }} && {{ wordpress_wpcli }} plugin update {{ item }}' - changed_when: false + ansible.builtin.shell: + cmd: '{{ wordpress_wpcli }} plugin is-installed {{ item }} && {{ wordpress_wpcli }} plugin update {{ item }}' + changed_when: False loop: "{{ wordpress_plugins }}" - name: Activate default plugins - shell: '{{ wordpress_wpcli }} plugin is-installed {{ item }} && {{ wordpress_wpcli }} plugin activate {{ item }}' - changed_when: false + ansible.builtin.shell: + cmd: '{{ wordpress_wpcli }} plugin is-installed {{ item }} && {{ wordpress_wpcli }} plugin activate {{ item }}' + changed_when: False loop: "{{ wordpress_plugins }}" - name: Send a summary mail - mail: + community.general.mail: host: 'localhost' port: 25 to: "{{ wordpress_email }}"