Merge branch 'unstable' into stable
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone/tag Build was killed

This commit is contained in:
Jérémy Lecour 2022-03-02 09:42:28 +01:00 committed by Jérémy Lecour
commit d7d58bf158
46 changed files with 2493 additions and 1547 deletions

View file

@ -20,16 +20,41 @@ The **patch** part changes is incremented if multiple releases happen the same m
### Security
## [22.03] 2022-03-02
### Added
* apt: apt_hold_packages: broadcast message with wall, if present
* evolinux-base: option to bypass raid-related tasks
* Explicit permissions for systemd overrides
* generate-ldif: Add support for php-fpm in containers
* kvm-host: add missing default value
* lxc-php: preliminary support for PHP 8.1 container
* openvpn: now check that openvpn has been restarted since last certificates renewal
* redis: always install check_redis_instances
* redis: check_redis_instances tolerates absence of instances
### Changed
* elasticsearch: Use `/etc/elasticsearch/jvm.options.d/evolinux` instead of default `/etc/elasticsearch/jvm.options`
* evolinux-users: check permissions for /etc/sudoers.d
* evolinux-users: optimize sudo configuration
* lxc: Fail if /var is nosuid
* openvpn: make it compatible with OpenBSD and add some improvements
## [22.01.3] 2022-01-31
### Changed
* rbenv: install Ruby 3.1.0 by default
* evolinux-base: backup-server-state: add "force" mode
### Fixed
* evolinux-base: backup-server-state: fix systemctl invocation
* varnish: update munin plugin to work with recent varnish versions
## [22.01.2] 2022-01-27

View file

@ -21,7 +21,12 @@ if [ -f ${config_file} ]; then
if [ -n "${package}" ]; then
if is_installed ${package} && ! is_held ${package}; then
apt-mark hold ${package}
>&2 echo "Package \`${package}' has been marked \`hold'."
msg="Package \`${package}' has been marked \`hold'."
>&2 echo "${msg}"
wall_bin=$(command -v wall)
if [ -n "${wall_bin}" ]; then
"${wall_bin}" --timeout 5 "${msg}"
fi
return_code=1
fi
fi

View file

@ -69,6 +69,15 @@
tags:
- dovecot
- name: deploy file for custom configuration
template:
src: zzz-evolinux-custom.conf.j2
dest: /etc/dovecot/conf.d/zzz-evolinux-custom.conf
mode: "0644"
notify: reload dovecot
tags:
- dovecot
- include: munin.yml
tags:
- dovecot

View file

@ -0,0 +1 @@
## Put your customized configuration here, verify configuration with "doveconf -n" and /var/log/mail.log

View file

@ -96,17 +96,25 @@
- name: JVM Heap size (min) is set
lineinfile:
dest: /etc/elasticsearch/jvm.options
dest: /etc/elasticsearch/jvm.options.d/evolinux.options
regexp: "^-Xms"
line: "-Xms{{ elasticsearch_jvm_xms }}"
create: yes
owner: root
group: elasticsearch
mode: 0640
tags:
- config
- name: JVM Heap size (max) is set
lineinfile:
dest: /etc/elasticsearch/jvm.options
dest: /etc/elasticsearch/jvm.options.d/evolinux.options
regexp: "^-Xmx"
line: "-Xmx{{ elasticsearch_jvm_xmx }}"
create: yes
owner: root
group: elasticsearch
mode: 0640
tags:
- config

View file

@ -26,10 +26,13 @@
- name: change JVM tmpdir (< 6.x)
lineinfile:
dest: /etc/elasticsearch/jvm.options
dest: /etc/elasticsearch/jvm.options.d/evolinux.options
line: "-Djava.io.tmpdir={{ _elasticsearch_custom_tmpdir }}"
regexp: "^-Djava.io.tmpdir="
insertafter: "## JVM configuration"
create: yes
owner: root
group: elasticsearch
mode: 0640
notify:
- restart elasticsearch
tags:
@ -48,6 +51,7 @@
- elasticsearch
when: elastic_stack_version is version('6', '>=')
# 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:
dest: /etc/elasticsearch/jvm.options

View file

@ -78,6 +78,7 @@ 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
evolinux_packages_buster: True

View file

@ -101,6 +101,7 @@
when:
- "'Hewlett-Packard Company Smart Array' in raidmodel.stdout"
- "'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
@ -149,9 +150,11 @@
tags:
- packages
- config
when: "'Hewlett-Packard Company Smart Array' in raidmodel.stdout"
when:
- "'Hewlett-Packard Company Smart Array' in raidmodel.stdout"
- evolinux_packages_hardware_raid | bool
- name: MegaRAID SAS package is present
- name: MegaCLI SAS package is present
block:
- name: HWRaid embedded GPG key is absent
apt_key:
@ -206,6 +209,8 @@
tags:
- packages
- config
when: "'MegaRAID' in raidmodel.stdout"
when:
- "'MegaRAID' in raidmodel.stdout"
- evolinux_packages_hardware_raid | bool
- meta: flush_handlers

View file

@ -20,10 +20,6 @@
- name: Configure sudo
include: sudo.yml
vars:
user: "{{ item.value }}"
loop: "{{ evolinux_users | dict2items }}"
when: evolinux_users | length > 0
- name: Configure SSH
include: ssh.yml

View file

@ -1,9 +1,21 @@
---
- include: sudo_jessie.yml
when: ansible_distribution_release == "jessie"
vars:
user: "{{ item.value }}"
loop: "{{ evolinux_users | dict2items }}"
when:
- evolinux_users | length > 0
- ansible_distribution_release == "jessie"
- include: sudo_stretch.yml
- block:
- include: sudo_stretch_common.yml
- include: sudo_stretch_user.yml
vars:
user: "{{ item.value }}"
loop: "{{ evolinux_users | dict2items }}"
when:
- ansible_distribution_major_version is defined
- ansible_distribution_major_version is version('9', '>=')

View file

@ -1,5 +1,13 @@
---
- name: "/etc/sudoers.d presence and permissions"
file:
path: /etc/sudoers.d
owner: root
group: root
mode: "0750"
state: directory
- name: "Verify 'evolinux' sudoers file presence (Debian 9 or later)"
template:
src: sudoers_stretch.j2
@ -13,15 +21,3 @@
group:
name: "{{ evolinux_sudo_group }}"
system: yes
- name: "Add user to '{{ evolinux_sudo_group }}' group (Debian 9 or later)"
user:
name: '{{ user.name }}'
groups: "{{ evolinux_sudo_group }}"
append: yes
- name: "Add user to 'adm' group (Debian 9 or later)"
user:
name: '{{ user.name }}'
groups: "adm"
append: yes

View file

@ -0,0 +1,13 @@
---
- name: "Add user to '{{ evolinux_sudo_group }}' group (Debian 9 or later)"
user:
name: '{{ user.name }}'
groups: "{{ evolinux_sudo_group }}"
append: yes
- name: "Add user to 'adm' group (Debian 9 or later)"
user:
name: '{{ user.name }}'
groups: "adm"
append: yes

View file

@ -602,6 +602,96 @@ ServiceVersion: PostgreSQL ${postgresql_version}
EOT
fi
# LXC (multiphp)
if is_pkg_installed lxc; then
if lxc-ls | grep -q php56 ; then
cat <<EOT >> "${ldif_file}"
dn: ServiceName=ServiceName=php-fpm56,${computer_dn}
NagiosEnabled: TRUE
ipServiceProtocol: tcp
objectClass: EvoService
ServiceName: PHP-FPM (multiphp)
ipServicePort: 443
ServiceType: web
ServiceVersion: PHP-FPM 5.6 (multiphp)
EOT
fi
if lxc-ls | grep -q php70 ; then
cat <<EOT >> "${ldif_file}"
dn: ServiceName=ServiceName=php-fpm70,${computer_dn}
NagiosEnabled: TRUE
ipServiceProtocol: tcp
objectClass: EvoService
ServiceName: PHP-FPM (multiphp)
ipServicePort: 443
ServiceType: web
ServiceVersion: PHP-FPM 7.0 (multiphp)
EOT
fi
if lxc-ls | grep -q php73 ; then
cat <<EOT >> "${ldif_file}"
dn: ServiceName=ServiceName=php-fpm73,${computer_dn}
NagiosEnabled: TRUE
ipServiceProtocol: tcp
objectClass: EvoService
ServiceName: PHP-FPM (multiphp)
ipServicePort: 443
ServiceType: web
ServiceVersion: PHP-FPM 7.3 (multiphp)
EOT
fi
if lxc-ls | grep -q php74 ; then
cat <<EOT >> "${ldif_file}"
dn: ServiceName=ServiceName=php-fpm74,${computer_dn}
NagiosEnabled: TRUE
ipServiceProtocol: tcp
objectClass: EvoService
ServiceName: PHP-FPM (multiphp)
ipServicePort: 443
ServiceType: web
ServiceVersion: PHP-FPM 7.4 (multiphp)
EOT
fi
if lxc-ls | grep -q php80 ; then
cat <<EOT >> "${ldif_file}"
dn: ServiceName=ServiceName=php-fpm80,${computer_dn}
NagiosEnabled: TRUE
ipServiceProtocol: tcp
objectClass: EvoService
ServiceName: PHP-FPM (multiphp)
ipServicePort: 443
ServiceType: web
ServiceVersion: PHP-FPM 8.0 (multiphp)
EOT
fi
if lxc-ls | grep -q php81 ; then
cat <<EOT >> "${ldif_file}"
dn: ServiceName=ServiceName=php-fpm81,${computer_dn}
NagiosEnabled: TRUE
ipServiceProtocol: tcp
objectClass: EvoService
ServiceName: PHP-FPM (multiphp)
ipServicePort: 443
ServiceType: web
ServiceVersion: PHP-FPM 8.1 (multiphp)
EOT
fi
fi
# END - LXC (multiphp)
# mdadm
if is_pkg_installed mdadm; then
mdadm_version=$(get_pkg_version mdadm)

View file

@ -1,4 +1,6 @@
---
kvm_custom_libvirt_images_path: ''
kvm_install_drbd: True
kvm_scripts_dir: /usr/local/sbin
kvm_scripts_dir: /usr/local/sbin
kvm_pair: null

View file

@ -34,7 +34,11 @@
special_time: "hourly"
user: root
job: "rsync -a --delete /etc/libvirt/qemu/*xml {{ hostvars[kvm_pair]['lan.ip'] }}:/root/libvirt-{{ inventory_hostname }}/"
when: kvm_pair != inventory_hostname
when:
- kvm_pair is defined
- kvm_pair is not none
- kvm_pair | length > 0
- kvm_pair != inventory_hostname
tags: crontab
- name: Crontab for sync list of running vm
@ -44,5 +48,9 @@
special_time: "daily"
user: root
job: "virsh list --all | tee /root/virsh-list.txt | ssh {{ hostvars[kvm_pair]['lan.ip'] }} 'cat >/root/libvirt-{{ inventory_hostname }}/virsh-list.txt'"
when: kvm_pair != inventory_hostname
when:
- kvm_pair is defined
- kvm_pair is not none
- kvm_pair | length > 0
- kvm_pair != inventory_hostname
tags: crontab

View file

@ -20,3 +20,4 @@ lxc_php_container_releases:
php73: "buster"
php74: "bullseye"
php80: "bullseye"
php81: "bullseye"

View file

@ -1,4 +1,9 @@
---
- name: Reload php81-fpm
lxc_container:
name: "{{ lxc_php_version }}"
container_command: "systemctl reload php8.1-fpm"
- name: Reload php80-fpm
lxc_container:
name: "{{ lxc_php_version }}"

View file

@ -24,4 +24,7 @@
- include: "php80.yml"
when: lxc_php_version == "php80"
- include: "php81.yml"
when: lxc_php_version == "php81"
- include: "misc.yml"

63
lxc-php/tasks/php81.yml Normal file
View file

@ -0,0 +1,63 @@
---
- name: "{{ lxc_php_version }} - Install dependency packages"
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"
replace:
dest: "/var/lib/lxc/{{ lxc_php_version }}/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"
line: "{{ item }}"
state: present
create: yes
mode: "0644"
loop:
- "deb https://packages.sury.org/php/ bullseye main"
- "deb http://pub.evolix.net/ bullseye-php81/"
- 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
mode: "0644"
owner: root
group: root
- name: copy packages.sury.org GPG Key
copy:
src: sury.gpg
dest: /var/lib/lxc/{{ lxc_php_version }}/rootfs/etc/apt/trusted.gpg.d/sury.gpg
mode: "0644"
owner: root
group: root
- name: "{{ lxc_php_version }} - Update APT cache"
lxc_container:
name: "{{ lxc_php_version }}"
container_command: "DEBIAN_FRONTEND=noninteractive apt update"
- name: "{{ lxc_php_version }} - Install PHP packages"
lxc_container:
name: "{{ lxc_php_version }}"
container_command: "DEBIAN_FRONTEND=noninteractive apt install -y php-fpm php-cli php-gd php-intl php-imap php-ldap php-mysql php-pgsql php-sqlite3 php-curl php-zip php-mbstring php-zip composer libphp-phpmailer"
- name: "{{ lxc_php_version }} - Copy evolinux PHP configuration"
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"
loop_control:
loop_var: line_item
- include: "mail_opensmtpd.yml"

View file

@ -43,8 +43,8 @@
- lxc_unprivilegied_containers | bool
- root_subuids.rc != 0
- name: Check if /var has not mount options nodev or noexec
shell: findmnt | grep -E "/var[^/]" | grep -e nodev -e noexec
- 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"

View file

@ -32,3 +32,8 @@ lxc.arch = x86_64
# Start containers on boot by default
lxc.start.auto = 1
{% if ansible_distribution_major_version is version('9', '>') %}
# Set LXC container unconfined in AppArmor
lxc.apparmor.profile = unconfined
{% endif %}

View file

@ -102,6 +102,8 @@
option: "ProtectHome"
value: "false"
state: present
create: yes
mode: "0644"
notify:
- systemd daemon-reload
- restart munin-node

View file

@ -0,0 +1,30 @@
#!/bin/sh
output=$(mktemp --tmpdir $(basename $0).XXXXXXXXXX)
critical_count=0
ok_count=0
trap "rm -f $output" EXIT
for mountpoint in $@; do
if findmnt -O ro --noheadings "$mountpoint" 1>/dev/null 2>&1; then
echo "CRITICAL - $mountpoint" >> "$output"
critical_count=$(( critical_count + 1))
else
echo "OK - $mountpoint" >> "$output"
ok_count=$(( ok_count + 1))
fi
done
total_count=$(( ok_count + critical_count ))
if [ $ok_count -eq $total_count ]; then
printf "OK - %d/%d no read-only mountpoint\n\n" "$ok_count" "$total_count"
cat "$output"
exit 0
else
printf "CRITICAL - %d/%d read-only mountpoint\n\n" "$critical_count" "$total_count"
cat "$output"
exit 2
fi

View file

@ -80,6 +80,7 @@ command[check_php-fpm70]=sudo {{ nagios_plugins_directory }}/check_phpfpm_multi
command[check_php-fpm73]=sudo {{ nagios_plugins_directory }}/check_phpfpm_multi /var/lib/lxc/php73/rootfs/etc/php/7.3/fpm/pool.d/
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_ipmi_sensors]=sudo /usr/lib/nagios/plugins/check_ipmi_sensor
command[check_raid_status]=/usr/lib/nagios/plugins/check_raid

View file

@ -66,6 +66,20 @@
- name: Include IP address whitelist task
include: ip_whitelist.yml
- name: Copy evolinux_server_custom
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
notify: reload nginx
tags:
- nginx
- ips
- name: Copy private_htpasswd
copy:
src: nginx/snippets/private_htpasswd

View file

@ -1,5 +1,5 @@
UserID opendkim:opendkim
Socket inet:54321@127.0.0.1
Socket inet:8891@127.0.0.1
PidFile /var/run/opendkim/opendkim.pid
OversignHeaders From
TrustAnchorFile /usr/share/dns/root.key

View file

@ -7,6 +7,16 @@ Install and configure OpenVPN, based on [our HowtoOpenVPN wiki](https://wiki.evo
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 :
* 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.
Then, you can use `shellpki` to generate client certificates.
## Variables
* `openvpn_lan`: network to use for OpenVPN
@ -15,5 +25,4 @@ Some manual actions are requested at the end of the playbook, to do before finis
## TODO
* Make it compatible with OpenBSD
* See TODO tasks in tasks/main.yml
* See TODO tasks in tasks/*.yml

View file

@ -16,13 +16,22 @@ CA_ECHO=""
error() {
if [ $? -eq 2 ] && [ "X$CERT_ECHO" = "X" ] && [ "X$CA_ECHO" = "X" ] ; then
echo "CRITICAL - The check exited with an error. Is the conf_file var containing the real conf file location ? On Debian, is the check executed with sudo ?"
echo "CRITICAL - The check exited with an error. Is the conf_file var containing the real conf file location ? On Debian, is the check executed with sudo ? On OpenBSD, is the check executed with doas ? Is OpenVPN running ?"
fi
}
SYSTEM=$(uname | tr '[:upper:]' '[:lower:]')
date_cmd=$(command -v date)
# Some backup servers don't have OpenVPN running while they are backup
is_backup_not_running_openvpn="1"
if [ "$SYSTEM" = "openbsd" ]; then
carp=$(/sbin/ifconfig carp0 2>/dev/null | grep 'status' | cut -d' ' -f2)
if [ "$carp" = "backup" ] && ! rcctl ls on | grep -q openvpn; then
is_backup_not_running_openvpn="0"
fi
fi
# Dates in seconds
_15_days="1296000"
_30_days="2592000"
@ -30,7 +39,7 @@ current_date=$($date_cmd +"%s")
# Trying to define the OpenVPN conf file location - default to /etc/openvpn/server.conf
conf_file=$(ps auwwwx | grep openvpn | grep -- --config | grep -v sed | sed -e "s/.*config \(\/etc\/openvpn.*.conf\).*/\1/" | head -1)
[ "$SYSTEM" = "openbsd" ] && conf_file=${conf_file:-$(grep openvpn_flags /etc/rc.conf.local | sed -e "s/.*config \(\/etc\/openvpn.*.conf\).*/\1/")}
if [ "$SYSTEM" = "openbsd" ]; then conf_file=${conf_file:-$(grep openvpn_flags /etc/rc.conf.local | sed -e "s/.*config \(\/etc\/openvpn.*.conf\).*/\1/")}; fi
conf_file=${conf_file:-"/etc/openvpn/server.conf"}
# Get the cert and ca file location, based on the OpenVPN conf file location
@ -44,22 +53,34 @@ ca_file=$(echo $ca_file | sed -e "s/^ca *\//\//")
cert_expiration_date=$(grep "Not After" $cert_file | sed -e "s/.*Not After : //")
ca_expiration_date=$(openssl x509 -enddate -noout -in $ca_file | cut -d '=' -f 2)
# Get the date of last modification of cert and ca certificates
if [ "$SYSTEM" = "openbsd" ]; then
seconds_last_cert_modification_date=$(stat -f %m "$cert_file")
seconds_last_ca_modification_date=$(stat -f %m "$ca_file")
else
seconds_last_cert_modification_date=$(stat -c %Y "$cert_file")
seconds_last_ca_modification_date=$(stat -c %Y "$ca_file")
fi
# Get the date of last OpenVPN restart
last_openvpn_restart_date=$(ps awwwx -O lstart | grep openvpn | grep -- --daemon | grep -- --config | head -1 | awk '{print $3,$4,$5,$6}')
test_cert_expiration() {
# Already expired - Cert file
if [ $current_date -ge $1 ]; then
CERT_ECHO="CRITICAL - The server certificate has expired on $formatted_cert_expiration_date"
CERT_ECHO="CRITICAL - The server certificate has expired on $formated_cert_expiration_date"
CERT_STATE=$STATE_CRITICAL
# Expiration in 15 days or less - Cert file
elif [ $((current_date+_15_days)) -ge $1 ]; then
CERT_ECHO="CRITICAL - The server certificate expires in 15 days or less : $formatted_cert_expiration_date"
CERT_ECHO="CRITICAL - The server certificate expires in 15 days or less : $formated_cert_expiration_date"
CERT_STATE=$STATE_CRITICAL
# Expiration in 30 days or less - Cert file
elif [ $((current_date+_30_days)) -ge $1 ]; then
CERT_ECHO="WARNING - The server certificate expires in 30 days or less : $formatted_cert_expiration_date"
CERT_ECHO="WARNING - The server certificate expires in 30 days or less : $formated_cert_expiration_date"
CERT_STATE=$STATE_WARNING
# Expiration in more than 30 days - Cert file
else
CERT_ECHO="OK - The server certificate expires on $formatted_cert_expiration_date"
CERT_ECHO="OK - The server certificate expires on $formated_cert_expiration_date"
CERT_STATE=$STATE_OK
fi
}
@ -67,74 +88,117 @@ test_cert_expiration() {
test_ca_expiration() {
# Already expired - CA file
if [ $current_date -ge $1 ]; then
CA_ECHO="CRITICAL - The server CA has expired on $formatted_ca_expiration_date"
CA_ECHO="CRITICAL - The server CA has expired on $formated_ca_expiration_date"
CA_STATE=$STATE_CRITICAL
# Expiration in 15 days or less - CA file
elif [ $((current_date+_15_days)) -ge $1 ]; then
CA_ECHO="CRITICAL - The server CA expires in 15 days or less : $formatted_ca_expiration_date"
CA_ECHO="CRITICAL - The server CA expires in 15 days or less : $formated_ca_expiration_date"
CA_STATE=$STATE_CRITICAL
# Expiration in 30 days or less - CA file
elif [ $((current_date+_30_days)) -ge $1 ]; then
CA_ECHO="WARNING - The server CA expires in 30 days or less : $formatted_ca_expiration_date"
CA_ECHO="WARNING - The server CA expires in 30 days or less : $formated_ca_expiration_date"
CA_STATE=$STATE_WARNING
# Expiration in more than 30 days - CA file
else
CA_ECHO="OK - The server CA expires on $formatted_ca_expiration_date"
CA_ECHO="OK - The server CA expires on $formated_ca_expiration_date"
CA_STATE=$STATE_OK
fi
}
# Linux and BSD systems do not implement 'date' the same way
if [ "$SYSTEM" = "linux" ]; then
test_openvpn_restarted_since_last_ca_cert_modification() {
if [ $is_backup_not_running_openvpn -eq "0" ]; then
RESTART_ECHO="OK - OpenVPN is not running because server is backup"
RESTART_STATE=$STATE_OK
else
if [ $seconds_last_cert_modification_date -ge $1 ] || [ $seconds_last_ca_modification_date -ge $1 ]; then
RESTART_ECHO="CRITICAL - OpenVPN hasn't been restarted since the last renewal of CA or CERT certificate"
RESTART_STATE=$STATE_CRITICAL
else
RESTART_ECHO="OK - OpenVPN has been restarted since the last renewal of CA and CERT certificate"
RESTART_STATE=$STATE_OK
fi
fi
}
# Cert expiration date human formated then in seconds
formatted_cert_expiration_date=$(TZ="Europe/Paris" $date_cmd -d "$cert_expiration_date" +"%F %T %Z")
seconds_cert_expiration_date=$(TZ="Europe/Paris" $date_cmd -d "$cert_expiration_date" +"%s")
main() {
# Linux and BSD systems do not implement 'date' the same way
if [ "$SYSTEM" = "linux" ]; then
# Cert expiration date human formated then in seconds
formated_cert_expiration_date=$(TZ="Europe/Paris" $date_cmd -d "$cert_expiration_date" +"%F %T %Z")
seconds_cert_expiration_date=$(TZ="Europe/Paris" $date_cmd -d "$cert_expiration_date" +"%s")
# CA expiration date human formated then in seconds
formated_ca_expiration_date=$(TZ="Europe/Paris" $date_cmd -d "$ca_expiration_date" +"%F %T %Z")
seconds_ca_expiration_date=$(TZ="Europe/Paris" $date_cmd -d "$ca_expiration_date" +"%s")
# CA expiration date human formated then in seconds
formatted_ca_expiration_date=$(TZ="Europe/Paris" $date_cmd -d "$ca_expiration_date" +"%F %T %Z")
seconds_ca_expiration_date=$(TZ="Europe/Paris" $date_cmd -d "$ca_expiration_date" +"%s")
# Last OpenVPN restart in seconds
seconds_last_openvpn_restart_date=$(TZ="Europe/Paris" $date_cmd -d "$last_openvpn_restart_date" +%s)
test_cert_expiration $seconds_cert_expiration_date
test_ca_expiration $seconds_ca_expiration_date
test_openvpn_restarted_since_last_ca_cert_modification $seconds_last_openvpn_restart_date
elif [ "$SYSTEM" = "openbsd" ]; then
test_cert_expiration $seconds_cert_expiration_date
test_ca_expiration $seconds_ca_expiration_date
# Cert expiration date for POSIX date, human formated then in seconds
posix_cert_expiration_date=$(echo "$cert_expiration_date" | awk '{ printf $4" "(index("JanFebMarAprMayJunJulAugSepOctNovDec",$1)+2)/3" "$2" ",split($3,time,":"); print time[1],time[2],time[3]}' | awk '{printf "%04d%02d%02d%02d%02d.%02d\n", $1, $2, $3, $4, $5, $6}')
cert_zone=$(echo "$cert_expiration_date" | awk '{print $5}')
formated_cert_expiration_date=$(TZ=$cert_zone $date_cmd -j -z "Europe/Paris" "$posix_cert_expiration_date" +"%F %T %Z")
seconds_cert_expiration_date=$(TZ=$cert_zone $date_cmd -j -z "Europe/Paris" "$posix_cert_expiration_date" +"%s")
# CA expiration date for POSIX date, human formated then in seconds
posix_ca_expiration_date=$(echo "$ca_expiration_date" | awk '{ printf $4" "(index("JanFebMarAprMayJunJulAugSepOctNovDec",$1)+2)/3" "$2" ",split($3,time,":"); print time[1],time[2],time[3]}' | awk '{printf "%04d%02d%02d%02d%02d.%02d\n", $1, $2, $3, $4, $5, $6}')
ca_zone=$(echo "$ca_expiration_date" | awk '{print $5}')
formated_ca_expiration_date=$(TZ=$ca_zone $date_cmd -j -z "Europe/Paris" "$posix_ca_expiration_date" +"%F %T %Z")
seconds_ca_expiration_date=$(TZ=$ca_zone $date_cmd -j -z "Europe/Paris" "$posix_ca_expiration_date" +"%s")
elif [ "$SYSTEM" = "openbsd" ]; then
test_cert_expiration $seconds_cert_expiration_date
test_ca_expiration $seconds_ca_expiration_date
# Cert expiration date for POSIX date, human formated then in seconds
posix_cert_expiration_date=$(echo "$cert_expiration_date" | awk '{ printf $4" "(index("JanFebMarAprMayJunJulAugSepOctNovDec",$1)+2)/3" "$2" ",split($3,time,":"); print time[1],time[2],time[3]}' | awk '{printf "%04d%02d%02d%02d%02d.%02d\n", $1, $2, $3, $4, $5, $6}')
cert_zone=$(echo "$cert_expiration_date" | awk '{print $5}')
formatted_cert_expiration_date=$(TZ=$cert_zone $date_cmd -j -z "Europe/Paris" "$posix_cert_expiration_date" +"%F %T %Z")
seconds_cert_expiration_date=$(TZ=$cert_zone $date_cmd -j -z "Europe/Paris" "$posix_cert_expiration_date" +"%s")
if [ $is_backup_not_running_openvpn -eq "0" ]; then
test_openvpn_restarted_since_last_ca_cert_modification 0
else
# Last OpenVPN restart in POSIX format, then in seconds
posix_last_openvpn_restart_date=$(echo "$last_openvpn_restart_date" | awk '{ printf $4" "(index("JanFebMarAprMayJunJulAugSepOctNovDec",$1)+2)/3" "$2" ",split($3,time,":"); print time[1],time[2],time[3]}' | awk '{printf "%04d%02d%02d%02d%02d.%02d\n", $1, $2, $3, $4, $5, $6}')
seconds_last_openvpn_restart_date=$($date_cmd -j "$posix_last_openvpn_restart_date" +%s)
# CA expiration date for POSIX date, human formated then in seconds
posix_ca_expiration_date=$(echo "$ca_expiration_date" | awk '{ printf $4" "(index("JanFebMarAprMayJunJulAugSepOctNovDec",$1)+2)/3" "$2" ",split($3,time,":"); print time[1],time[2],time[3]}' | awk '{printf "%04d%02d%02d%02d%02d.%02d\n", $1, $2, $3, $4, $5, $6}')
ca_zone=$(echo "$ca_expiration_date" | awk '{print $5}')
formatted_ca_expiration_date=$(TZ=$ca_zone $date_cmd -j -z "Europe/Paris" "$posix_ca_expiration_date" +"%F %T %Z")
seconds_ca_expiration_date=$(TZ=$ca_zone $date_cmd -j -z "Europe/Paris" "$posix_ca_expiration_date" +"%s")
test_openvpn_restarted_since_last_ca_cert_modification $seconds_last_openvpn_restart_date
fi
test_cert_expiration $seconds_cert_expiration_date
test_ca_expiration $seconds_ca_expiration_date
# If neither Linux nor BSD
else
echo "CRITICAL - OS not supported"
STATE=$STATE_CRITICAL
exit $STATE
fi
if [ $RESTART_STATE -gt $STATE_OK ]; then
echo $RESTART_ECHO
echo $CERT_ECHO
echo $CA_ECHO
exit $RESTART_STATE
else
# Display the first one that expires first
if [ $CA_STATE -gt $CERT_STATE ]; then
echo $CA_ECHO
echo $CERT_ECHO
echo $RESTART_ECHO
exit $CA_STATE
elif [ $CERT_STATE -gt $CA_STATE ]; then
echo $CERT_ECHO
echo $CA_ECHO
echo $RESTART_ECHO
exit $CERT_STATE
else
echo $CERT_ECHO
echo $CA_ECHO
echo $RESTART_ECHO
exit $CERT_STATE
fi
fi
}
# If neither Linux nor BSD
else
echo "CRITICAL - OS not supported"
STATE=$STATE_CRITICAL
exit $STATE
fi
# Display the first one that expires first
if [ $CA_STATE -gt $CERT_STATE ]; then
echo $CA_ECHO
echo $CERT_ECHO
exit $CA_STATE
elif [ $CERT_STATE -gt $CA_STATE ]; then
echo $CERT_ECHO
echo $CA_ECHO
exit $CERT_STATE
else
echo $CERT_ECHO
echo $CA_ECHO
exit $CERT_STATE
fi
main

View file

@ -0,0 +1,215 @@
#!/usr/bin/perl -w
#######################################################################
#
# Copyright (c) 2007 Jaime Gascon Romero <jgascon@gmail.com>
#
# License Information:
# 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, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# $Id: check_openvpn.pl,v 1.1 2014/09/29 08:39:24 rdessort Exp $
# $Revision: 1.1 $
# Home Site: http://emergeworld.blogspot.com/
# #####################################################################
use diagnostics;
use strict;
use Net::Telnet ();
use Getopt::Long qw(:config no_ignore_case);
use vars qw($PROGNAME $VERSION);
use lib "/usr/local/libexec/nagios/";
use utils qw(%ERRORS);
$PROGNAME = "check_openvpn";
$VERSION = '$Revision: 1.1 $';
$ENV{'PATH'}='';
$ENV{'BASH_ENV'}='';
$ENV{'ENV'}='';
my ($opt_h, $opt_H, $opt_p, $opt_P, $opt_t, $opt_i, $opt_n, $opt_c, $opt_w, $opt_C, $opt_r);
sub print_help ();
sub print_usage ();
GetOptions
("h" => \$opt_h, "help" => \$opt_h,
"H=s" => \$opt_H, "host=s" => \$opt_H,
"p=i" => \$opt_p, "port=i" => \$opt_p,
"P=s" => \$opt_P, "password=s" => \$opt_P,
"t=i" => \$opt_t, "timeout=i" => \$opt_t,
"i" => \$opt_i, "ip" => \$opt_i,
"n" => \$opt_n, "numeric" => \$opt_n,
"c" => \$opt_c, "critical" => \$opt_c,
"w" => \$opt_w, "warning" => \$opt_w,
"C=s" => \$opt_C, "common_name=s" => \$opt_C,
"r=s" => \$opt_r, "remote_ip=s" => \$opt_r,
) or exit $ERRORS{'UNKNOWN'};
# default values
unless ( defined $opt_t ) {
$opt_t = 10;
}
if ($opt_h) {print_help(); exit $ERRORS{'OK'};}
if ( ! defined($opt_H) || ! defined($opt_p) ) {
print_usage();
exit $ERRORS{'UNKNOWN'}
}
my @lines;
my @clients;
my @clients_ip;
my $t;
eval {
$t = new Net::Telnet (Timeout => $opt_t,
Port => $opt_p,
Prompt => '/END$/'
);
$t->open($opt_H);
if ( defined $opt_P ) {
$t->waitfor('/ENTER PASSWORD:$/');
$t->print($opt_P);
}
$t->waitfor('/^$/');
@lines = $t->cmd("status 2");
$t->close;
};
if ($@) {
print "OpenVPN Critical: Can't connect to server\n";
exit $ERRORS{'CRITICAL'};
}
if (defined $opt_i || defined $opt_r) {
foreach (@lines) {
if ($_ =~ /CLIENT_LIST,.*,(\d+\.\d+\.\d+\.\d+):\d+,/) {
push @clients_ip, $1;
}
}
if (defined $opt_i) {
print "OpenVPN OK: "."@clients_ip ";
exit $ERRORS{'OK'};
} elsif (defined $opt_r) {
if ( ! grep /\b$opt_r\b/, @clients_ip) {
if (defined $opt_c) {
print "OpenVPN CRITICAL: $opt_r don't found";
exit $ERRORS{'CRITICAL'};
} else {
print "OpenVPN WARNING: $opt_r don't found";
exit $ERRORS{'WARNING'};
}
}
print "OpenVPN OK: "."@clients_ip ";
exit $ERRORS{'OK'};
}
}
foreach (@lines) {
if ($_ =~ /CLIENT_LIST,(.*),\d+\.\d+\.\d+\.\d+:\d+,/) {
push @clients, $1;
}
}
if (defined $opt_C) {
if ( ! grep /\b$opt_C\b/, @clients) {
if (defined $opt_c) {
print "OpenVPN CRITICAL: $opt_C don't found";
exit $ERRORS{'CRITICAL'};
} else {
print "OpenVPN WARNING: $opt_C don't found";
exit $ERRORS{'WARNING'};
}
}
}
if (defined $opt_n) {
print "OpenVPN OK: ".@clients." connected clients.";
exit $ERRORS{'OK'};
}
print "OpenVPN OK: "."@clients ";
exit $ERRORS{'OK'};
#######################################################################
###### Subroutines ####################################################
sub print_usage() {
print "Usage: $PROGNAME -H | --host <IP or hostname> -p | --port <port number> [-P | --password] <password> [-t | --timeout] <timeout in seconds>
[-i | --ip] [-n | --numeric] [-C | --common_name] <common_name> [-r | --remote_ip] <remote_ip> [-c | --critical] [-w | --warning]\n\n";
print " $PROGNAME [-h | --help]\n";
}
sub print_help() {
print "$PROGNAME $VERSION\n\n";
print "Copyright (c) 2007 Jaime Gascon Romero
Nagios plugin to check the clients connected to a openvpn server.
";
print_usage();
print "
-H | --host
IP address or hostname of the openvpn server.
-p | --port
Management port interface of the openvpn server.
-P | --password
Password for the management interface of the openvpn server.
-t | --timeout
Timeout for the connection attempt. Optional, default 10 seconds.
Optional parameters
===================
-i | --ip
Prints the IP address of the remote client instead of the common name.
-n | --numeric
Prints the number of clients connected to the openvpn server.
Matching Parameters
===================
-C | --common_name
The common name, as it is specified in the client certificate, who is wanted to check.
-r | --remote_ip
The client remote ip address who is wanted to check.
-c | --critical
Exits with CRITICAL status if the client specified by the common name or the remote ip address is not connected.
-w | --warning
Exits with WARNING status if the client specified by the common name or the remote ip address is not connected.
Other Parameters
================
-h | --help
Show this help.
";
}
# vim:sts=2:sw=2:ts=2:et

View file

@ -4,3 +4,11 @@
service:
name: nagios-nrpe-server
state: restarted
- name: restart nrpe
service:
name: nrpe
state: restarted
- name: reload packetfilter
command: pfctl -f /etc/pf.conf

296
openvpn/tasks/debian.yml Normal file
View file

@ -0,0 +1,296 @@
---
- name: Install OpenVPN
apt:
name: openvpn
- name: Delete unwanted OpenVPN folders
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 shellpki user
user:
name: shellpki
system: yes
create_home: no
home: "/etc/shellpki"
shell: "/usr/sbin/nologin"
- name: Create /etc/shellpki
file:
dest: "/etc/shellpki"
mode: "0755"
owner: shellpki
group: shellpki
state: directory
- include_role:
name: evolix/remount-usr
- name: Copy shellpki files
copy:
src: "{{ 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"
- name: Add sudo rights
lineinfile:
dest: "/etc/sudoers.d/shellpki"
regexp: '/usr/local/sbin/shellpki'
line: "%shellpki ALL = (root) /usr/local/sbin/shellpki"
create: yes
mode: "0400"
owner: root
group: root
validate: 'visudo -cf %s'
- name: Deploy OpenVPN client config template
template:
src: "ovpn.conf.j2"
dest: "/etc/shellpki/ovpn.conf"
mode: "0600"
owner: shellpki
group: shellpki
- name: Generate dhparam
command: "openssl dhparam -out /etc/shellpki/dh2048.pem 2048"
- 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:
src: "server.conf.j2"
dest: "/etc/openvpn/server.conf"
mode: "0600"
owner: root
group: root
- name: Is minifirewall installed ?
stat:
path: "/etc/default/minifirewall"
check_mode: no
changed_when: false
register: minifirewall_config
- name: Retrieve the default interface
shell: "grep '^INT=' /etc/default/minifirewall | cut -d\\' -f 2"
check_mode: no
changed_when: false
register: minifirewall_int
when: minifirewall_config.stat.exists
- name: Add minifirewall rule in config file
lineinfile:
dest: "/etc/default/minifirewall"
line: "{{ item }}"
with_items:
- "# OpenVPN"
- "/sbin/iptables -t nat -A POSTROUTING -s {{ openvpn_lan }}/{{ openvpn_netmask_cidr }} -o $INT -j MASQUERADE"
when: minifirewall_config.stat.exists
- name: Activate minifirewall rule
iptables:
table: nat
chain: POSTROUTING
source: "{{ openvpn_lan }}/{{ openvpn_netmask_cidr }}"
out_interface: "{{ minifirewall_int.stdout }}"
jump: MASQUERADE
when: minifirewall_config.stat.exists
- name: Add 1194/udp OpenVPN port to public services in minifirewall
replace:
dest: "/etc/default/minifirewall"
regexp: "^SERVICESUDP1='(.*)?'$"
replace: "SERVICESUDP1='\\1 1194'"
backup: yes
when: minifirewall_config.stat.exists
- name: Activate minifirewall rule for IPv4
iptables:
chain: INPUT
protocol: udp
destination_port: "1194"
jump: ACCEPT
ip_version: ipv4
when: minifirewall_config.stat.exists
- name: Activate minifirewall rule for IPv6
iptables:
chain: INPUT
protocol: udp
destination_port: "1194"
jump: ACCEPT
ip_version: ipv6
when: minifirewall_config.stat.exists
- name: Enable forwarding
sysctl:
name: net.ipv4.ip_forward
value: "1"
sysctl_file: "/etc/sysctl.d/openvpn.conf"
- name: Generate a password for the management interface
set_fact:
management_pwd: "{{ lookup('password', '/dev/null length=15 chars=ascii_letters,digits') }}"
- name: Set the management password
copy:
dest: "/etc/openvpn/management-pwd"
content: "{{ management_pwd }}"
mode: "0600"
owner: root
group: root
- name: Enable openvpn service
systemd:
name: "openvpn@server.service"
enabled: yes
- name: Is NRPE installed ?
stat:
path: "/etc/nagios/nrpe.d/evolix.cfg"
check_mode: no
changed_when: false
register: nrpe_evolix_config
- name: Install NRPE check dependencies
apt:
name: libnet-telnet-perl
when: nrpe_evolix_config.stat.exists
- include_role:
name: evolix/remount-usr
- name: Install OpenVPN NRPE check
copy:
src: "files/check_openvpn_debian.pl"
dest: "/usr/local/lib/nagios/plugins/check_openvpn"
mode: "0755"
owner: root
group: nagios
when: nrpe_evolix_config.stat.exists
- name: Configure NRPE OpenVPN check
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:
name: evolix/remount-usr
- name: Install OpenVPN certificates NRPE check
copy:
src: "files/check_openvpn_certificates.sh"
dest: "/usr/local/lib/nagios/plugins/check_openvpn_certificates.sh"
mode: "0755"
owner: root
group: nagios
when: nrpe_evolix_config.stat.exists
- name: Add sudo rights for NRPE check
lineinfile:
dest: "/etc/sudoers.d/openvpn"
regexp: 'check_openvpn_certificates.sh'
line: "nagios ALL=NOPASSWD: /usr/local/lib/nagios/plugins/check_openvpn_certificates.sh"
create: yes
mode: "0400"
owner: root
group: root
validate: 'visudo -cf %s'
when: nrpe_evolix_config.stat.exists
- name: Configure NRPE certificates check
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:
name: evolix/remount-usr
- name: Copy shellpki script
copy:
src: "/root/shellpki-dev/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:
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 }}'
- name: Warn the user about command to execute manually
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".
Press enter to exit when it's done.

View file

@ -1,285 +1,15 @@
---
- name: This role is only compatible with Debian
- name: System compatibility checks
assert:
that: "ansible_distribution == 'Debian'"
msg: "Only compatible with Debian"
that: "ansible_distribution == 'Debian' or ansible_distribution == 'OpenBSD'"
msg: "Only compatible with Debian and OpenBSD"
- name: Install OpenVPN
apt:
name: openvpn
- name: Include Debian version
include: debian.yml
when: ansible_distribution == "Debian"
- name: Delete unwanted OpenVPN folders
file:
state: absent
path: "/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 shellpki user
user:
name: shellpki
system: yes
create_home: no
home: "/etc/shellpki"
shell: "/usr/sbin/nologin"
- name: Create /etc/shellpki
file:
path: "/etc/shellpki"
state: directory
- include_role:
name: evolix/remount-usr
- name: Copy shellpki files
copy:
src: "{{ 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:
path: "{{ item.path }}"
mode: "{{ item.mode }}"
with_items:
- { path: "/etc/shellpki/openssl.cnf", mode: "0640" }
- { path: "/usr/local/sbin/shellpki", mode: "0755" }
- name: Delete local shellpki repo
file:
state: absent
path: "/root/shellpki"
- name: Change directory owner
file:
path: "/etc/shellpki"
owner: shellpki
recurse: yes
state: directory
- name: Add sudo rights
lineinfile:
dest: "/etc/sudoers.d/shellpki"
regexp: '/usr/local/sbin/shellpki'
line: "%shellpki ALL = (root) /usr/local/sbin/shellpki"
create: yes
validate: 'visudo -cf %s'
- name: Deploy OpenVPN client config template
template:
src: "ovpn.conf.j2"
dest: "/etc/shellpki/ovpn.conf"
- name: Generate dhparam
command: "openssl dhparam -out /etc/shellpki/dh2048.pem 2048"
- include_role:
name: evolix/remount-usr
- name: Fix CRL rights in shellpki command
lineinfile:
path: "/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:
src: "server.conf.j2"
dest: "/etc/openvpn/server.conf"
- name: Is minifirewall installed ?
stat:
path: "/etc/default/minifirewall"
check_mode: no
register: minifirewall_config
- name: Retrieve the default interface
shell: "grep '^INT=' /etc/default/minifirewall | cut -d\\' -f 2"
check_mode: no
changed_when: false
register: minifirewall_int
- name: Add minifirewall rule in config file
lineinfile:
path: "/etc/default/minifirewall"
line: "{{ item }}"
with_items:
- "# OpenVPN"
- "/sbin/iptables -t nat -A POSTROUTING -s {{ openvpn_lan }}/{{ openvpn_netmask_cidr }} -o $INT -j MASQUERADE"
when: minifirewall_config.stat.exists
- name: Activate minifirewall rule
iptables:
table: nat
chain: POSTROUTING
source: "{{ openvpn_lan }}/{{ openvpn_netmask_cidr }}"
out_interface: "{{ minifirewall_int.stdout }}"
jump: MASQUERADE
when: minifirewall_config.stat.exists
- name: Add 1194/udp OpenVPN port to public services in minifirewall
replace:
path: "/etc/default/minifirewall"
regexp: "^SERVICESUDP1='(.*)?'$"
replace: "SERVICESUDP1='\\1 1194'"
backup: yes
when: minifirewall_config.stat.exists
- name: Activate minifirewall rule for IPv4
iptables:
chain: INPUT
protocol: udp
destination_port: "1194"
jump: ACCEPT
ip_version: ipv4
when: minifirewall_config.stat.exists
- name: Activate minifirewall rule for IPv6
iptables:
chain: INPUT
protocol: udp
destination_port: "1194"
jump: ACCEPT
ip_version: ipv6
when: minifirewall_config.stat.exists
- name: Enable forwarding
sysctl:
name: net.ipv4.ip_forward
value: "1"
sysctl_file: "/etc/sysctl.d/openvpn.conf"
- name: Generate a password for the management interface
set_fact:
management_pwd: "{{ lookup('password', '/dev/null length=15 chars=ascii_letters,digits') }}"
- name: Set the management password
copy:
dest: "/etc/openvpn/management-pwd"
content: "{{ management_pwd }}"
- name: Enable openvpn service
systemd:
name: "openvpn@server.service"
enabled: yes
- name: Is NRPE installed ?
stat:
path: "/etc/nagios/nrpe.d/evolix.cfg"
check_mode: no
register: nrpe_evolix_config
- name: Install NRPE check dependencies
apt:
name: libnet-telnet-perl
when: nrpe_evolix_config.stat.exists
- include_role:
name: evolix/remount-usr
- name: Install OpenVPN NRPE check
copy:
src: "files/check_openvpn.pl"
dest: "/usr/local/lib/nagios/plugins/check_openvpn"
mode: "0755"
owner: root
group: nagios
when: nrpe_evolix_config.stat.exists
- name: Add NRPE check
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:
name: evolix/remount-usr
- name: Install OpenVPN certificates NRPE check
copy:
src: "files/check_openvpn_certificates.sh"
dest: "/usr/local/lib/nagios/plugins/check_openvpn_certificates.sh"
mode: "0755"
owner: root
group: nagios
when: nrpe_evolix_config.stat.exists
- name: Add sudo rights for NRPE check
lineinfile:
dest: "/etc/sudoers.d/openvpn"
regexp: 'check_openvpn_certificates.sh'
line: "nagios ALL=NOPASSWD: /usr/local/lib/nagios/plugins/check_openvpn_certificates.sh"
create: yes
validate: 'visudo -cf %s'
when: nrpe_evolix_config.stat.exists
- name: Add NRPE check
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:
name: evolix/remount-usr
- name: Copy shellpki script
copy:
src: "/root/shellpki-dev/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
path: "/root/shellpki-dev"
# END TODO
- name: Install cron to warn about certificates expiration
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 }}'
- name: Warn the user about command to execute manually
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 adjuste 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"
Press enter to exit when it's done.
- name: Include OpenBSD version
include: openbsd.yml
when: ansible_distribution == "OpenBSD"

235
openvpn/tasks/openbsd.yml Normal file
View file

@ -0,0 +1,235 @@
---
- name: Install OpenVPN
openbsd_pkg:
name: openvpn--
when: ansible_distribution == 'OpenBSD'
- name: Create /etc/openvpn
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:
name: _shellpki
system: yes
create_home: no
home: "/etc/shellpki"
shell: "/sbin/nologin"
- name: Create /etc/shellpki
file:
dest: "/etc/shellpki"
state: directory
owner: _shellpki
group: _shellpki
mode: "0755"
- name: Copy shellpki files
copy:
src: "{{ 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"
- name: Add sudo rights
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:
src: "ovpn.conf.j2"
dest: "/etc/shellpki/ovpn.conf"
mode: "0640"
owner: _shellpki
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$' }
- name: Deploy OpenVPN server config
template:
src: "server.conf.j2"
dest: "/etc/openvpn/server.conf"
mode: "0600"
owner: root
group: wheel
- name: Configure PacketFilter
lineinfile:
dest: "/etc/pf.conf"
line: "{{ item }}"
validate: 'pfctl -nf %s'
notify: reload packetfilter
with_items:
- "# OpenVPN"
- "pass in quick on $ext_if proto udp from any to self port 1194"
- name: Create a cron to rotate the logs
cron:
name: "OpenVPN logs rotation"
weekday: "6"
hour: "4"
minute: "0"
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:
management_pwd: "{{ lookup('password', '/dev/null length=15 chars=ascii_letters,digits') }}"
- name: Set the management password
copy:
dest: "/etc/openvpn/management-pwd"
content: "{{ management_pwd }}"
mode: "0600"
owner: root
group: wheel
- name: Enable openvpn service
service:
name: openvpn
enabled: yes
- name: Set openvpn flags
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:
path: "/etc/nrpe.d/evolix.cfg"
check_mode: no
register: nrpe_evolix_config
- name: Install NRPE check dependencies
openbsd_pkg:
name: p5-Net-Telnet
when: nrpe_evolix_config.stat.exists
- name: Install OpenVPN NRPE check
copy:
src: "files/check_openvpn_openbsd.pl"
dest: "/usr/local/libexec/nagios/plugins/check_openvpn.pl"
mode: "0755"
owner: root
group: wheel
when: nrpe_evolix_config.stat.exists
- name: Configure NRPE OpenVPN check
lineinfile:
dest: "/etc/nrpe.d/zzz_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
mode: "0644"
owner: root
group: wheel
notify: restart nrpe
when: nrpe_evolix_config.stat.exists
- name: Install OpenVPN certificates NRPE check
copy:
src: "files/check_openvpn_certificates.sh"
dest: "/usr/local/libexec/nagios/plugins/check_openvpn_certificates.sh"
mode: "0755"
owner: root
group: wheel
when: nrpe_evolix_config.stat.exists
- name: Add doas rights for NRPE check
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"
validate: 'doas -C %s'
when: nrpe_evolix_config.stat.exists
- name: Configure NRPE certificates check
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"
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:
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 }}'
- name: Warn the user about command to execute manually
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".
Press enter to exit when it's done.

View file

@ -5,6 +5,7 @@ proto udp
remote {{ ansible_fqdn }} 1194
nobind
persist-key
persist-tun

View file

@ -7,12 +7,15 @@ proto udp
dev tun
mode server
keepalive 10 120
tls-exit
cipher AES-256-GCM # AES
persist-key
persist-tun
ifconfig-pool-persist /etc/openvpn/ipp.txt
status /var/log/openvpn-status.log
log-append /var/log/openvpn.log

View file

@ -8,6 +8,23 @@ fi;
PHPVersion=$(grep SetHandler /etc/apache2/sites-enabled/$LOGNAME.conf 2>/dev/null | grep -m 1 -o 'fpm[0-9][0-9]' | head -n 1 | sed 's/php//g' | sed 's/fpm//g')
# If $PHPVersion is empty, look for "SetHandler" in the includes.
if [ "$PHPVersion" == "" ]; then
includes=$(grep -w "Include" /etc/apache2/sites-enabled/$LOGNAME.conf | uniq | awk '{print $2}')
for f in "$includes"; do
# Relative path -> absolute
if [ ${f:0:1} != "/" ]; then
f="/etc/apache2/${f}"
fi
# Grep again
PHPVersion=$(grep SetHandler "${f}" 2>/dev/null | grep -m 1 -o 'fpm[0-9][0-9]' | head -n 1 | sed 's/php//g' | sed 's/fpm//g')
if [ "$PHPVersion" != "" ]; then
break
fi
done
fi
if [ "$PHPVersion" != "" ]; then
lxc-attach -n php$PHPVersion -- su - $LOGNAME -c "cd \"${PWD@E}\" && php ${*@Q}"
else

View file

@ -38,5 +38,6 @@ dependencies:
- { 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 }

View file

@ -10,6 +10,7 @@
src: postgresql.service.override.conf
dest: /etc/systemd/system/postgresql@.service.d/override.conf
force: yes
mode: "0644"
notify:
- reload systemd
- restart postgresql

View file

@ -73,7 +73,7 @@ if systemctl is-enabled -q redis-server; then
fi
# additional instances
conf_files=$(ls -1 /etc/redis-*/redis.conf)
conf_files=$(ls -1 /etc/redis-*/redis.conf 2> /dev/null)
for conf_file in ${conf_files}; do
name=$(dirname "${conf_file}" | sed '{s|/etc/redis-||}')
if systemctl is-enabled -q "redis-server@${name}.service"; then

View file

@ -87,7 +87,6 @@
mode: "0755"
owner: root
group: root
when: redis_instance_name is defined
tags:
- redis
- nrpe

View file

@ -19,6 +19,7 @@
template:
src: systemd-override.conf.j2
dest: /etc/systemd/system/squid.service.d/override.conf
mode: "0644"
force: yes
register: _squid_systemd_override

View file

@ -1,1165 +0,0 @@
#!/usr/bin/perl
# -*- perl -*-
#
# varnish4_ - Munin plugin to for Varnish 4.x
# Copyright (C) 2009 Redpill Linpro AS
#
# Author: Kristian Lyngstol <kristian@bohemians.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
=head1 NAME
varnish4_ - Munin plugin to monitor various aspects of varnish
=head1 APPLICABLE SYSTEMS
Varnish 4.x with varnishstat
=head1 CONFIGURATION
The plugin needs to be able to execute varnishstat.
The configuration section shows the defaults
[varnish4_*]
env.varnishstat varnishstat
env.name
env.varnishstat can be a full path to varnishstat if it's
not in the path already.
env.name is blank (undefined) by default and can be used to specify a -n
name argument to varnish if multiple instances are running on the same
server.
A few aspects are not linked by default. They are marked as
'DEBUG' => 'yes' (or any other value). They are:
vcl, bans, bans_lurker, lru, objects_per_objhead,
losthdr, esi, hcb, shm, shm_writes, overflow,
session, session_herd, gzip
You can link them yourself with something like this:
ln -s @@LIBDIR@@/plugins/varnish4_ \
@@CONFDIR@@/plugins/varnish4_data_structures
=head1 INTERPRETATION
Each graph uses data from varnishstat.
=head1 MAGIC MARKERS
#%# family=auto
#%# capabilities=autoconf suggest
=head1 VERSION
$Id$
=head1 BUGS
The hit_rate graph requires munin r2040 or newer to display
correctly.
=head1 PATCHES-TO
Please send patches to Kristian Lyngstol <kristian@bohemians.org>
and/or varnish-misc@varnish-cache.org for significant changes. Munin SVN
is the authoritative repository for this plugin.
=head1 AUTHOR
Kristian Lyngstol <kristian@bohemians.org>
=head1 MODIFICATIONS
Ingo Oppermann <ingo.oppermann@gmail.com>
=head1 LICENSE
GPLv2
=cut
use XML::Parser;
use strict;
# Set to 1 to enable output when a variable is defined in a graph but
# omitted because it doesn't exist in varnishstat.
my $DEBUG = 0;
# Set to 1 to ignore 'DEBUG' and suggest all available aspects.
my $FULL_SUGGEST = 0;
# Varnishstat executable. Include full path if it's not in your path.
my $varnishstatexec = exists $ENV{'varnishstat'} ? $ENV{'varnishstat'} : "varnishstat";
# For multiple instances
my $varnishname = exists $ENV{'name'} ? $ENV{'name'} : undef;
my $self; # Haha, myself, what a clever pun.
# Parameters that can be defined on top level of a graph. Config will print
# them as "graph_$foo $value\n"
my @graph_parameters = ('title','total','order','scale','vlabel','args');
# Parameters that can be defined on a value-to-value basis and will be
# blindly passed to config. Printed as "$fieldname.$param $value\n".
#
# 'label' is hardcoded as it defaults to a varnishstat-description if not
# set.
my @field_parameters = ('graph', 'min', 'max', 'draw', 'cdef', 'warning',
'colour', 'info', 'type');
# Varnishstat data is stored here. Example
# ('n_vbe' => { 'value' => '124', 'description'=>...,'flag'=>... }, SMA =>
# { s0 => { 'value' => '...', 'flag'=> '...' },'Transient' => ...})
# Both dynamic and static counters are kept here.
#
# Notes:
# - The 'flag' field for a counter is in RRD-dialect, not varnishstat
my %data;
# Data structure that defines all possible graphs (aspects) and how they
# are to be plotted. Every top-level entry is a graph/aspect. Each
# top-level graph MUST have title set and 'values'.
#
# The 'values' hash must have at least one value definition. The actual
# value used is either fetched from varnishstat based on the value-name, or
# if 'rpn' is defined: calculated. 'type' SHOULD be set.
#
# Graphs with 'DEBUG' set to anything is omitted from 'suggest'.
#
# 'rpn' on values allows easy access to graphs consisting of multiple
# values from varnishstat. (Reverse polish notation). The RPN
# implementation only accepts +-*/ and varnishstat-values.
#
# With the exception of 'label', which is filled with the
# varnishstat-description if left undefined, any value left undefined will
# be left up to Munin to define/ignore/yell about.
#
# For dynamic counters, the values listed need to specify a counter and
# family. This will plot the specified counter for each identity within
# that family. Example: family of SMA, counter c_fail. This will create a
# c_fail-counter for each of the SMA-identities (e.g: Transient, s0, etc).
# For dynamic graphs, the value-name is only used to identify the data
# point, and does not relate to any varnishstat data as that is set by
# family/counter.
#
# Note that dynamic counters fetch the type from the XML and things like
# min/max are currently not supported (and silently ignored).
#
# See munin documentation or rrdgraph/rrdtool for more information.
my %ASPECTS = (
'request_rate' => {
'title' => 'Request rates',
'order' => 'cache_hit cache_hitpass cache_miss '
. 'backend_conn backend_unhealthy '
. 'client_req client_conn' ,
'values' => {
'sess_conn' => {
'type' => 'DERIVE',
'min' => '0',
'colour' => '444444',
'graph' => 'ON'
},
'client_req' => {
'type' => 'DERIVE',
'colour' => '111111',
'min' => '0'
},
'cache_hit' => {
'type' => 'DERIVE',
'draw' => 'AREA',
'colour' => '00FF00',
'min' => '0'
},
'cache_hitpass' => {
'info' => 'Hitpass are cached passes: An '
. 'entry in the cache instructing '
. 'Varnish to pass. Typically '
. 'achieved after a pass in '
. 'vcl_fetch.',
'type' => 'DERIVE',
'draw' => 'STACK',
'colour' => 'FFFF00',
'min' => '0'
},
'cache_miss' => {
'type' => 'DERIVE',
'colour' => 'FF0000',
'draw' => 'STACK',
'min' => '0'
},
'backend_conn' => {
'type' => 'DERIVE',
'colour' => '995599',
'min' => '0'
},
'backend_unhealthy' => {
'type' => 'DERIVE',
'min' => '0',
'colour' => 'FF55FF'
},
's_pipe' => {
'type' => 'DERIVE',
'min' => '0',
'colour' => '1d2bdf'
},
's_pass' => {
'type' => 'DERIVE',
'min' => '0',
'colour' => '785d0d'
}
}
},
'hit_rate' => {
'title' => 'Hit rates',
'order' => 'client_req cache_hit cache_miss '
. 'cache_hitpass' ,
'vlabel' => '%',
'args' => '-u 100 --rigid',
'scale' => 'no',
'values' => {
'client_req' => {
'type' => 'DERIVE',
'min' => '0',
'graph' => 'off',
'rpn' => [ 'cache_hit' , 'cache_miss' , 'cache_hitpass' , '+' , '+' ]
},
'cache_hit' => {
'type' => 'DERIVE',
'min' => '0',
'draw' => 'AREA',
'cdef' => 'cache_hit,client_req,/,100,*'
},
'cache_miss' => {
'type' => 'DERIVE',
'draw' => 'STACK',
'min' => '0',
'cdef' => 'cache_miss,client_req,/,100,*'
},
'cache_hitpass' => {
'type' => 'DERIVE',
'draw' => 'STACK',
'min' => '0',
'cdef' => 'cache_hitpass,client_req,/,100,*'
},
}
},
'backend_traffic' => {
'title' => 'Backend traffic',
'values' => {
'backend_conn' => {
'type' => 'DERIVE',
'min' => '0'
},
'backend_unhealthy' => {
'type' => 'DERIVE',
'min' => '0',
'warning' => ':1'
},
'backend_busy' => {
'type' => 'DERIVE',
'min' => '0'
},
'backend_fail' => {
'type' => 'DERIVE',
'min' => '0'
},
'backend_reuse' => {
'type' => 'DERIVE',
'min' => 0
},
'backend_recycle' => {
'type' => 'DERIVE',
'min' => 0
},
'backend_toolate' => {
'type' => 'DERIVE',
'min' => '0'
},
'backend_retry' => {
'type' => 'DERIVE',
'min' => '0'
},
'backend_req' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'objects' => {
'title' => 'Number of objects',
'order' => 'n_object n_objectcore n_vampireobject n_objecthead',
'values' => {
'n_object' => {
'type' => 'GAUGE',
'label' => 'Number of objects'
},
'n_objectcore' => {
'type' => 'GAUGE',
'label' => 'Number of object cores'
},
'n_vampireobject' => {
'type' => 'GAUGE',
'label' => 'Number of unresurrected objects'
},
'n_objecthead' => {
'type' => 'GAUGE',
'label' => 'Number of object heads',
'info' => 'Each object head can have one '
. 'or more object attached, '
. 'typically based on the Vary: header'
}
}
},
'transfer_rates' => {
'title' => 'Transfer rates',
'order' => 's_resp_bodybytes s_resp_hdrbytes',
'args' => '-l 0',
'vlabel' => 'bit/s',
'values' => {
's_resp_hdrbytes' => {
'type' => 'DERIVE',
'label' => 'Header traffic',
'draw' => 'STACK',
'min' => '0',
'info' => 'HTTP Header traffic. TCP/IP '
. 'overhead is not included.',
'cdef' => 's_resp_hdrbytes,8,*'
},
's_resp_bodybytes' => {
'type' => 'DERIVE',
'draw' => 'AREA',
'label' => 'Body traffic',
'min' => '0',
'cdef' => 's_resp_bodybytes,8,*'
}
}
},
'threads' => {
'title' => 'Thread status',
'values' => {
'threads' => {
'type' => 'GAUGE',
'min' => '0',
'warning' => '1:'
},
'threads_created' => {
'type' => 'DERIVE',
'min' => '0'
},
'threads_failed' => {
'type' => 'DERIVE',
'min' => '0',
'warning' => ':1'
},
'threads_limited' => {
'type' => 'DERIVE',
'min' => '0'
},
'threads_destroyed' => {
'type' => 'DERIVE',
'min' => '0',
'warning' => ':1'
}
}
},
'memory_usage' => {
'title' => 'Memory usage',
'args' => '--base 1024',
'vlabel' => 'bytes',
'values' => {
'sms_balloc' => {
'type' => 'GAUGE',
},
'sms_nbytes' => {
'type' => 'GAUGE',
},
'SMA_1' => {
'counter' => 'g_bytes',
'family' => 'SMA',
},
'SMA_2' => {
'counter' => 'g_space',
'family' => 'SMA',
},
'SMA_3' => {
'counter' => 'c_bytes',
'family' => 'SMA'
},
'SMF_1' => {
'counter' => 'g_bytes',
'family' => 'SMF',
},
'SMF_2' => {
'counter' => 'g_space',
'family' => 'SMF',
}
}
},
'uptime' => {
'title' => 'Varnish uptime',
'vlabel' => 'days',
'scale' => 'no',
'values' => {
'uptime' => {
'type' => 'GAUGE',
'cdef' => 'uptime,86400,/'
}
}
},
'objects_per_objhead' => {
'title' => 'Objects per objecthead',
'DEBUG' => 'yes',
'values' => {
'obj_per_objhead' => {
'type' => 'GAUGE',
'label' => 'Objects per object heads',
'rpn' => [ 'n_object','n_objecthead','/' ]
}
}
},
'losthdr' => {
'title' => 'HTTP Header overflows',
'DEBUG' => 'yes',
'values' => {
'losthdr' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'hcb' => {
'title' => 'Critbit data',
'DEBUG' => 'yes',
'values' => {
'hcb_nolock' => {
'type' => 'DERIVE',
'min' => '0'
},
'hcb_lock' => {
'type' => 'DERIVE',
'min' => '0'
},
'hcb_insert' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'esi' => {
'title' => 'ESI',
'DEBUG' => 'yes',
'values' => {
'esi_parse' => {
'type' => 'DERIVE',
'min' => '0'
},
'esi_errors' => {
'type' => 'DERIVE',
'min' => '0'
},
'esi_warnings' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'session' => {
'title' => 'Sessions',
'DEBUG' => 'yes',
'values' => {
'sess_conn' => {
'type' => 'DERIVE',
'min' => '0'
},
'sess_drop' => {
'type' => 'DERIVE',
'min' => '0'
},
'sess_fail' => {
'type' => 'DERIVE',
'min' => '0'
},
'sess_pipe_overflow' => {
'type' => 'DERIVE',
'min' => '0'
},
'sess_queued' => {
'type' => 'DERIVE',
'min' => '0'
},
'sess_dropped' => {
'type' => 'DERIVE',
'min' => '0'
},
'sess_closed' => {
'type' => 'DERIVE',
'min' => '0'
},
'sess_pipeline' => {
'type' => 'DERIVE',
'min' => '0'
},
'sess_readahead' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'session_herd' => {
'title' => 'Session herd',
'DEBUG' => 'yes',
'values' => {
'sess_herd' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'shm_writes' => {
'title' => 'SHM writes and records',
'DEBUG' => 'yes',
'values' => {
'shm_records' => {
'type' => 'DERIVE',
'min' => '0'
},
'shm_writes' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'shm' => {
'title' => 'Shared memory activity',
'DEBUG' => 'yes',
'values' => {
'shm_flushes' => {
'type' => 'DERIVE',
'min' => '0'
},
'shm_cont' => {
'type' => 'DERIVE',
'min' => '0'
},
'shm_cycles' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'allocations' => {
'title' => 'Memory allocation requests',
'DEBUG' => 'yes',
'values' => {
'sm_nreq' => {
'type' => 'DERIVE',
'min' => '0'
},
'sma_nreq' => {
'type' => 'DERIVE',
'min' => '0'
},
'sms_nreq' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'vcl' => {
'title' => 'VCL',
'DEBUG' => 'yes',
'values' => {
'n_backend' => {
'type' => 'GAUGE'
},
'n_vcl' => {
'type' => 'DERIVE',
'min' => '0'
},
'n_vcl_avail' => {
'type' => 'DERIVE',
'min' => '0'
},
'n_vcl_discard' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'bans' => {
'title' => 'Bans',
'DEBUG' => 'yes',
'values' => {
'bans' => {
'type' => 'GAUGE'
},
'bans_added' => {
'type' => 'DERIVE',
'min' => '0'
},
'bans_deleted' => {
'type' => 'DERIVE',
'min' => '0'
},
'bans_completed' => {
'type' => 'GAUGE'
},
'bans_obj' => {
'type' => 'GAUGE'
},
'bans_req' => {
'type' => 'GAUGE'
},
'bans_tested' => {
'type' => 'DERIVE',
'min' => '0'
},
'bans_obj_killed' => {
'type' => 'DERIVE',
'min' => '0'
},
'bans_tests_tested' => {
'type' => 'DERIVE',
'min' => '0'
},
'bans_dups' => {
'type' => 'GAUGE'
},
'bans_persisted_bytes' => {
'type' => 'GAUGE'
},
'bans_persisted_fragmentation' => {
'type' => 'GAUGE'
}
}
},
'bans_lurker' => {
'title' => 'Ban Lurker',
'DEBUG' => 'yes',
'values' => {
'bans_lurker_tested' => {
'type' => 'DERIVE',
'min' => '0'
},
'bans_lurker_tests_tested' => {
'type' => 'DERIVE',
'min' => '0'
},
'bans_lurker_obj_killed' => {
'type' => 'DERIVE',
'min' => '0'
},
'bans_lurker_contention' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'expunge' => {
'title' => 'Object expunging',
'order' => 'n_expired n_lru_nuked',
'values' => {
'n_expired' => {
'type' => 'DERIVE',
'min' => '0'
},
'n_lru_nuked' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'lru' => {
'title' => 'LRU activity',
'DEBUG' => 'yes',
'values' => {
'n_lru_nuked' => {
'type' => 'DERIVE',
'min' => '0'
},
'n_lru_moved' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'bad' => {
'title' => 'Misbehavior',
'values' => {
'SMA_1' => {
'counter' => 'c_fail',
'family' => 'SMA',
},
'SMF_1' => {
'counter' => 'c_fail',
'family' => 'SMF',
},
'sess_drop' => {
'type' => 'DERIVE'
},
'backend_unhealthy' => {
'type' => 'DERIVE'
},
'fetch_failed' => {
'type' => 'DERIVE'
},
'backend_busy' => {
'type' => 'DERIVE'
},
'threads_failed' => {
'type' => 'DERIVE'
},
'threads_limited' => {
'type' => 'DERIVE'
},
'threads_destroyed' => {
'type' => 'DERIVE'
},
'thread_queue_len' => {
'type' => 'GAUGE'
},
'losthdr' => {
'type' => 'DERIVE'
},
'esi_errors' => {
'type' => 'DERIVE'
},
'esi_warnings' => {
'type' => 'DERIVE'
},
'sess_fail' => {
'type' => 'DERIVE'
},
'sess_pipe_overflow' => {
'type' => 'DERIVE'
}
}
},
'gzip' => {
'title' => 'GZIP activity',
'DEBUG' => 'yes',
'values' => {
'n_gzip' => {
'type' => 'DERIVE',
'min' => '0'
},
'n_gunzip' => {
'type' => 'DERIVE',
'min' => '0'
}
}
}
);
################################
# Various helper functions #
################################
# Translate $_[0] from varnish' internal types (flags) to munin/rrd
# variants (e.g: from 'i' to GAUGE). Returns the result.
sub translate_type
{
my $d = $_[0];
if ($d eq "i") {
$d = "GAUGE";
} elsif ($d eq "a") {
$d = "DERIVE";
}
return $d;
}
# Print the value of a two-dimensional hash if it exist.
# Returns false if non-existent.
#
# Output is formatted for plugins if arg4 is blank, otherwise arg4 is used
# as the title/name of the field (ie: arg4=graph_title).
sub print_if_exist
{
my %values = %{$_[0]};
my $value = $_[1];
my $field = $_[2];
my $title = "$value.$field";
if (defined($_[3])) {
$title = $_[3];
}
if (defined($values{$value}{$field})) {
print "$title $values{$value}{$field}\n";
} else {
return 0;
}
}
# Create a output-friendly name
sub normalize_name
{
my $name = $_[0];
$name =~ s/[^a-zA-Z0-9]/_/g;
return $name;
}
# Braindead RPN: +,-,/,* will pop two items from @stack, and perform
# the relevant operation on the items. If the item in the array isn't one
# of the 4 basic math operations, a value from varnishstat is pushed on to
# the stack. IE: 'client_req','client_conn','/' will leave the value of
# "client_req/client_conn" on the stack.
#
# If only one item is left on the stack, it is printed. Otherwise, an error
# message is printed.
sub rpn
{
my @stack;
my $left;
my $right;
foreach my $item (@{$_[0]}) {
if ($item eq "+") {
$right = pop(@stack);
$left = pop(@stack);
push(@stack,$left+$right);
} elsif ($item eq "-") {
$right = pop(@stack);
$left = pop(@stack);
push(@stack,$left-$right);
} elsif ($item eq "/") {
$right = pop(@stack);
$left = pop(@stack);
push(@stack,$left/$right);
} elsif ($item eq "*") {
$right = pop(@stack);
$left = pop(@stack);
push(@stack,$left*$right);
} else {
push(@stack,int($data{$item}{'value'}));
}
}
if (@stack > 1)
{
print STDERR "RPN error: Stack has more than one item left.\n";
print STDERR "@stack\n";
exit 255;
}
print "@stack";
print "\n";
}
# Bail-function.
sub usage
{
if (@_) {
print STDERR "@_" . "\n\n";
}
print STDERR "Known arguments: suggest, config, autoconf.\n";
print STDERR "Run with suggest to get a list of known aspects.\n";
exit 1;
}
################################
# XML Parsing #
################################
# The following code is for parsing varnishstat -x. While %data should be
# stable, the following bits can easily be replaced with anything (json, an
# other xml-parser, magic, etc)
#
# The basic concept is simple enough. Only worry about stuff inside
# <state>. Updating %state on each new data field, and commit it to %data
# when </state> is seen.
#
# We do use translate_type() on the 'flag' field.
# Internal state for the XML parsing
my %state = (
'stat' => 0, # inside <stat> or not
'field' => 'none', # <name>, <value>, <stat>, etc.
);
# Reset the state of XML, mainly used for end-elements.
sub xml_reset_state() {
$state{'stat'} = '0';
$state{'field'} = 'none';
$state{'values'} = ();
}
# Callback for data entry. Cleans leading whitespace and updates state.
sub xml_characters {
my $d = $_[1];
if ($state{'stat'} == 0) {
return;
}
if ($state{'field'} eq "type" && $d eq "MAIN") {
return;
}
$d =~ s/^\s*$//g;
if ($d eq "") {
return;
}
$state{'values'}{$state{'field'}} = $d;
}
# Store the current state in %data. Issued at </stat>
# Note that 'flag' is translated to RRD-equivalents here.
sub xml_commit_state
{
my $name = $state{'values'}{'name'};
my $type = $state{'values'}{'type'};
my $ident = $state{'values'}{'ident'};
foreach my $key (keys %{$state{'values'}}) {
my $data = $state{'values'}{$key};
if ($key eq 'flag') {
$data = translate_type($data);
}
if (defined($type) and $type ne '' and defined($ident) and $ident ne '') {
$data{$type}{$ident}{$name}{$key} = $data;
} else {
$data{$name}{$key} = $data
}
}
}
# Callback for end tag. E.g: </stat>
sub xml_end_elem {
my $element = $_[1];
if ($element ne "stat") {
return;
}
xml_commit_state();
xml_reset_state();
}
# Callback for opening tag. E.g: <stat>
sub xml_start_elem {
$state{'field'} = $_[1];
if ($state{'field'} eq "stat") {
$state{'stat'} = 1;
}
}
################################
# Internal API #
################################
# Populate %data, includes both values and descriptions and more.
# Currently driven by XML, but that could change.
sub populate_stats
{
my $arg = "-x";
my $parser = new XML::Parser(Handlers => {Start => \&xml_start_elem,
End => \&xml_end_elem,
Char => \&xml_characters} );
if ($varnishname) {
$arg .= " -n $varnishname";
}
open (XMLDATA, "$varnishstatexec $arg|") or die "meh";
$parser->parse(*XMLDATA, ProtocolEncoding => 'ISO-8859-1');
close(XMLDATA);
}
# Prints the fields in the list in $_[2] (e.g: 'value'/'description') for
# each identity of the varnish counter/family combination as defined by
# the $_[0]-counter on the aspect definition. Err, that's jibberish, so
# an example:
#
# e.g: dynamic_print('SMA_1','',('value'))
# e.g: dynamic_print('SMA_2','.label',('ident','description'))
# SMA_1 is the counter-value. If it is a dynamic counter, it has a counter
# and family-member (e.g: counter: c_req, family: SMA) and print_dynamic
# will print c_req for each SMA-identity.
#
# Note that the variables to print is a list. This is to allow printing a
# single item with multiple fields. Typically for identity+description so
# you can distinguish between different data points.
#
# Returns true if it was a dynamic counter.
sub print_dynamic
{
my $name = $_[0];
shift;
my $suffix = $_[0];
shift;
my @field = @_;
if (!defined($ASPECTS{$self}{'values'}{$name}{'counter'})) {
return 0;
}
if (!defined($ASPECTS{$self}{'values'}{$name}{'family'})) {
return 0;
}
my $counter = $ASPECTS{$self}{'values'}{$name}{'counter'};
my $type = $ASPECTS{$self}{'values'}{$name}{'family'};
foreach my $key (keys %{$data{$type}}) {
my $pname = normalize_name($type . "_" . $key . "_" . $counter);
print $pname . $suffix . " ";
my $i = 0;
foreach my $f (@field) {
if ($i != 0) {
print " ";
}
$i += 1;
print $data{$type}{$key}{$counter}{$f};
}
print "\n";
}
return 1;
}
# Read and verify the aspect ($self).
sub set_aspect
{
$self = $0;
$self =~ s/^.*\/varnish[0-9]?_//;
if (!defined($ASPECTS{$self}) && @ARGV == 0) {
usage "No such aspect";
}
}
# Print 'yes' if it's reasonable to use this plugin, or 'no' with a
# human-readable error message. Always exit true, even if the response
# is 'no'.
sub autoconf
{
# XXX: Solaris outputs errors to stderr and always returns true.
# XXX: See #873
if (`which $varnishstatexec 2>/dev/null` =~ m{^/}) {
print "yes\n";
} else {
print "no ($varnishstatexec could not be found)\n";
}
exit 0;
}
# Suggest relevant aspects/values of $self.
# 'DEBUG'-graphs are excluded.
sub suggest
{
foreach my $key (keys %ASPECTS) {
if (defined($ASPECTS{$key}{'DEBUG'}) && $FULL_SUGGEST != 1) {
next;
}
print "$key\n";
}
}
# Walk through the relevant aspect and print all top-level configuration
# values and value-definitions.
sub get_config
{
my $graph = $_[0];
# Need to double-check since set_aspect only checks this if there
# is no argument (suggest/autoconf doesn't require a valid aspect)
if (!defined($ASPECTS{$graph})) {
usage "No such aspect";
}
my %values = %{$ASPECTS{$graph}{'values'}};
print "graph_category Varnish\n";
foreach my $field (@graph_parameters) {
print_if_exist(\%ASPECTS,$graph,$field,"graph_$field");
}
foreach my $value (sort keys %values) {
# Just print the description/type if it's a dynamic
# counter. It'll be silent if it isn't.
if(print_dynamic($value,'.label',('description','type','ident'))) {
print_dynamic($value,'.type',('flag'));
next;
}
# Need either RPN definition or a varnishstat value.
if (!defined($data{$value}{'value'}) &&
!defined($values{$value}{'rpn'})) {
if ($DEBUG) {
print "ERROR: $value not part of varnishstat.\n"
}
next;
}
if (!print_if_exist(\%values,$value,'label')) {
print "$value.label $data{$value}{'description'}\n";
}
foreach my $field (@field_parameters) {
print_if_exist(\%values,$value,$field);
}
}
}
# Handle arguments (config, autoconf, suggest)
# Populate stats for config is necessary, but we want to avoid it for
# autoconf as it would generate a nasty error.
sub check_args
{
if (@ARGV && $ARGV[0] eq '') {
shift @ARGV;
}
if (@ARGV == 1) {
if ($ARGV[0] eq "config") {
populate_stats;
get_config($self);
exit 0;
} elsif ($ARGV[0] eq "autoconf") {
autoconf($self);
exit 0;
} elsif ($ARGV[0] eq "suggest") {
suggest;
exit 0;
}
usage "Unknown argument";
}
}
################################
# Execution starts here #
################################
set_aspect;
check_args;
populate_stats;
# We only get here if we're supposed to.
# Walks through the relevant values and either prints the varnishstat, or
# if the 'rpn' variable is set, calls rpn() to execute ... the rpn.
#
# NOTE: Due to differences in varnish-versions, this checks if the value
# actually exist before using it.
foreach my $value (sort keys %{$ASPECTS{$self}{'values'}}) {
if (defined($ASPECTS{$self}{'values'}{$value}{'rpn'})) {
print "$value.value ";
rpn($ASPECTS{$self}{'values'}{$value}{'rpn'});
} else {
if (print_dynamic($value,'.value',('value'))) {
next;
}
if (!defined($data{$value}{'value'})) {
if ($DEBUG) {
print STDERR "Error: $value not part of "
. "varnishstat.\n";
}
next;
}
print "$value.value ";
print "$data{$value}{'value'}\n";
}
}

View file

@ -0,0 +1,2 @@
[varnish5_*]
user vcache

View file

@ -0,0 +1,1221 @@
#!/usr/bin/perl
# -*- perl -*-
#
# varnish5_ - Munin plugin to for Varnish 5.x and 6.x
# Copyright (C) 2009,2018 Redpill Linpro AS
#
# Author: Kristian Lyngstøl <kristian@bohemians.org>
# Pål-Eivind Johnsen <pej@redpill-linpro.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
=head1 NAME
varnish5_ - Munin plugin to monitor various aspects of varnish
=head1 APPLICABLE SYSTEMS
Varnish 5.x with varnishstat
=head1 CONFIGURATION
The plugin needs to be able to execute varnishstat.
The configuration section shows the defaults
[varnish5_*]
group varnish
env.varnishstat varnishstat
env.name
env.varnishstat can be a full path to varnishstat if it's
not in the path already.
env.name is blank (undefined) by default and can be used to specify a -n
name argument to varnish if multiple instances are running on the same
server.
A few aspects are not linked by default. They are marked as
'DEBUG' => 'yes' (or any other value). They are:
vcl, bans, bans_lurker, lru, objects_per_objhead,
losthdr, esi, hcb, shm, shm_writes, overflow,
session, session_herd, gzip
You can link them yourself with something like this:
ln -s @@LIBDIR@@/plugins/varnish5_ \
@@CONFDIR@@/plugins/varnish5_data_structures
=head1 INTERPRETATION
Each graph uses data from varnishstat.
=head1 MAGIC MARKERS
#%# family=auto
#%# capabilities=autoconf suggest
=head1 VERSION
$Id$
=head1 BUGS
The hit_rate graph requires munin r2040 or newer to display
correctly.
=head1 PATCHES-TO
Please send patches to Kristian Lyngstøl <kristian@bohemians.org>
and/or varnish-misc@varnish-cache.org for significant changes. The
munin-contrib Git repo is the authoritative repository for this plugin.
=head1 AUTHOR
Kristian Lyngstøl <kristian@bohemians.org>
=head1 MODIFICATIONS
Ingo Oppermann <ingo.oppermann@gmail.com>
Pål-Eivind Johnsen <pej@redpill-linpro.com>
=head1 LICENSE
GPLv2
=cut
use XML::Parser;
use strict;
# Set to 1 to enable output when a variable is defined in a graph but
# omitted because it doesn't exist in varnishstat.
my $DEBUG = 0;
# Set to 1 to ignore 'DEBUG' and suggest all available aspects.
my $FULL_SUGGEST = 0;
# Varnishstat executable. Include full path if it's not in your path.
my $varnishstatexec = exists $ENV{'varnishstat'} ? $ENV{'varnishstat'} : "varnishstat";
# For multiple instances
my $varnishname = exists $ENV{'name'} ? $ENV{'name'} : undef;
my $self; # Haha, myself, what a clever pun.
# Parameters that can be defined on top level of a graph. Config will print
# them as "graph_$foo $value\n"
my @graph_parameters = ('title','total','order','scale','vlabel','args');
# Parameters that can be defined on a value-to-value basis and will be
# blindly passed to config. Printed as "$fieldname.$param $value\n".
#
# 'label' is hardcoded as it defaults to a varnishstat-description if not
# set.
my @field_parameters = ('graph', 'min', 'max', 'draw', 'cdef', 'warning',
'colour', 'info', 'type');
# Varnishstat data is stored here. Example
# ('n_vbe' => { 'value' => '124', 'description'=>...,'flag'=>... }, SMA =>
# { s0 => { 'value' => '...', 'flag'=> '...' },'Transient' => ...})
# Both dynamic and static counters are kept here.
#
# Notes:
# - The 'flag' field for a counter is in RRD-dialect, not varnishstat
my %data;
# Data structure that defines all possible graphs (aspects) and how they
# are to be plotted. Every top-level entry is a graph/aspect. Each
# top-level graph MUST have title set and 'values'.
#
# The 'values' hash must have at least one value definition. The actual
# value used is either fetched from varnishstat based on the value-name, or
# if 'rpn' is defined: calculated. 'type' SHOULD be set.
#
# Graphs with 'DEBUG' set to anything is omitted from 'suggest'.
#
# 'rpn' on values allows easy access to graphs consisting of multiple
# values from varnishstat. (Reverse polish notation). The RPN
# implementation only accepts +-*/ and varnishstat-values.
#
# With the exception of 'label', which is filled with the
# varnishstat-description if left undefined, any value left undefined will
# be left up to Munin to define/ignore/yell about.
#
# For dynamic counters, the values listed need to specify a counter and
# family. This will plot the specified counter for each identity within
# that family. Example: family of SMA, counter c_fail. This will create a
# c_fail-counter for each of the SMA-identities (e.g: Transient, s0, etc).
# For dynamic graphs, the value-name is only used to identify the data
# point, and does not relate to any varnishstat data as that is set by
# family/counter.
#
# Note that dynamic counters fetch the type from the XML and things like
# min/max are currently not supported (and silently ignored).
#
# See munin documentation or rrdgraph/rrdtool for more information.
my %ASPECTS = (
'request_rate' => {
'title' => 'Request rates',
'order' => 'cache_hit cache_hitpass cache_miss cache_hitmiss'
. 'backend_conn backend_unhealthy '
. 'client_req client_conn' ,
'values' => {
'sess_conn' => {
'type' => 'DERIVE',
'min' => '0',
'colour' => '444444',
'graph' => 'ON'
},
'client_req' => {
'type' => 'DERIVE',
'colour' => '111111',
'min' => '0'
},
'cache_hit' => {
'type' => 'DERIVE',
'draw' => 'AREA',
'colour' => '00FF00',
'min' => '0'
},
'cache_hitpass' => {
'info' => 'Hitpass are cached passes: An '
. 'entry in the cache instructing '
. 'Varnish to pass. Typically '
. 'achieved after a pass in '
. 'vcl_fetch.',
'type' => 'DERIVE',
'draw' => 'STACK',
'colour' => 'FFFF00',
'min' => '0'
},
'cache_miss' => {
'type' => 'DERIVE',
'colour' => 'FF0000',
'draw' => 'STACK',
'min' => '0'
},
'cache_hitmiss' => {
'info' => 'Hitmiss are cached missing: An '
. 'entry in the cache instructing '
. 'Varnish to pass. Typically '
. 'achieved after a pass in '
. 'vcl_fetch.',
'type' => 'DERIVE',
'draw' => 'STACK',
'colour' => 'FFFF00',
'min' => '0'
},
'backend_conn' => {
'type' => 'DERIVE',
'colour' => '995599',
'min' => '0'
},
'backend_unhealthy' => {
'type' => 'DERIVE',
'min' => '0',
'colour' => 'FF55FF'
},
's_pipe' => {
'type' => 'DERIVE',
'min' => '0',
'colour' => '1d2bdf'
},
's_pass' => {
'type' => 'DERIVE',
'min' => '0',
'colour' => '785d0d'
}
}
},
'hit_rate' => {
'title' => 'Hit rates',
'order' => 'client_req cache_hit cache_miss '
. 'cache_hitpass cache_hitmiss' ,
'vlabel' => '%',
'args' => '-l 0 -u 100 --rigid',
'scale' => 'no',
'values' => {
'client_req' => {
'type' => 'DERIVE',
'min' => '0',
'graph' => 'off',
'rpn' => [ 'cache_hit' , 'cache_miss' , 'cache_hitpass' ,'cache_hitmiss', '+' , '+' ,'+' ]
},
'cache_hit' => {
'type' => 'DERIVE',
'min' => '0',
'draw' => 'AREA',
'cdef' => 'cache_hit,client_req,/,100,*'
},
'cache_miss' => {
'type' => 'DERIVE',
'draw' => 'STACK',
'min' => '0',
'cdef' => 'cache_miss,client_req,/,100,*'
},
'cache_hitpass' => {
'type' => 'DERIVE',
'draw' => 'STACK',
'min' => '0',
'cdef' => 'cache_hitpass,client_req,/,100,*'
},
'cache_hitmiss' => {
'type' => 'DERIVE',
'draw' => 'STACK',
'min' => '0',
'cdef' => 'cache_hitmiss,client_req,/,100,*'
}
}
},
'backend_traffic' => {
'title' => 'Backend traffic',
'values' => {
'backend_conn' => {
'type' => 'DERIVE',
'min' => '0'
},
'backend_unhealthy' => {
'type' => 'DERIVE',
'min' => '0',
'warning' => ':1'
},
'backend_busy' => {
'type' => 'DERIVE',
'min' => '0'
},
'backend_fail' => {
'type' => 'DERIVE',
'min' => '0'
},
'backend_reuse' => {
'type' => 'DERIVE',
'min' => 0
},
'backend_recycle' => {
'type' => 'DERIVE',
'min' => 0
},
'backend_retry' => {
'type' => 'DERIVE',
'min' => '0'
},
'backend_req' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'objects' => {
'title' => 'Number of objects',
'order' => 'n_object n_objectcore n_vampireobject n_objecthead',
'values' => {
'n_object' => {
'type' => 'GAUGE',
'label' => 'Number of objects'
},
'n_objectcore' => {
'type' => 'GAUGE',
'label' => 'Number of object cores'
},
'n_vampireobject' => {
'type' => 'GAUGE',
'label' => 'Number of unresurrected objects'
},
'n_objecthead' => {
'type' => 'GAUGE',
'label' => 'Number of object heads',
'info' => 'Each object head can have one '
. 'or more object attached, '
. 'typically based on the Vary: header'
}
}
},
'transfer_rates' => {
'title' => 'Transfer rates',
'order' => 's_resp_bodybytes s_resp_hdrbytes',
'args' => '-l 0',
'vlabel' => 'bit/s',
'values' => {
's_resp_hdrbytes' => {
'type' => 'DERIVE',
'label' => 'Header traffic',
'draw' => 'STACK',
'min' => '0',
'info' => 'HTTP Header traffic. TCP/IP '
. 'overhead is not included.',
'cdef' => 's_resp_hdrbytes,8,*'
},
's_resp_bodybytes' => {
'type' => 'DERIVE',
'draw' => 'AREA',
'label' => 'Body traffic',
'min' => '0',
'cdef' => 's_resp_bodybytes,8,*'
}
}
},
'threads' => {
'title' => 'Thread status',
'values' => {
'threads' => {
'type' => 'GAUGE',
'min' => '0',
'warning' => '1:'
},
'threads_created' => {
'type' => 'DERIVE',
'min' => '0'
},
'threads_failed' => {
'type' => 'DERIVE',
'min' => '0',
'warning' => ':1'
},
'threads_limited' => {
'type' => 'DERIVE',
'min' => '0'
},
'threads_destroyed' => {
'type' => 'DERIVE',
'min' => '0',
'warning' => ':1'
}
}
},
'memory_usage' => {
'title' => 'Memory usage',
'args' => '--base 1024',
'vlabel' => 'bytes',
'values' => {
'SMA_1' => {
'counter' => 'g_bytes',
'family' => 'SMA',
},
'SMA_2' => {
'counter' => 'g_space',
'family' => 'SMA',
},
'SMA_3' => {
'counter' => 'c_bytes',
'family' => 'SMA'
},
'SMF_1' => {
'counter' => 'g_bytes',
'family' => 'SMF',
},
'SMF_2' => {
'counter' => 'g_space',
'family' => 'SMF',
},
'SMF_3' => {
'counter' => 'c_bytes',
'family' => 'SMF',
}
}
},
'main_uptime' => {
'type' => 'MAIN',
'title' => 'Varnish Child uptime',
'vlabel' => 'days',
'scale' => 'no',
'values' => {
'uptime' => {
'type' => 'GAUGE',
'cdef' => 'uptime,86400,/'
},
}
},
'mgt_uptime' => {
'type' => 'MGT',
'title' => 'Varnish Management uptime',
'vlabel' => 'days',
'scale' => 'no',
'values' => {
'uptime' => {
'type' => 'GAUGE',
'cdef' => 'uptime,86400,/'
},
}
},
'objects_per_objhead' => {
'title' => 'Objects per objecthead',
'DEBUG' => 'yes',
'values' => {
'n_objecthead' => {
'type' => 'GAUGE',
'label' => 'Objects per object heads',
'rpn' => [ 'n_object','n_objecthead','/' ]
}
}
},
'losthdr' => {
'title' => 'HTTP Header overflows',
'DEBUG' => 'yes',
'values' => {
'losthdr' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'hcb' => {
'title' => 'Critbit data',
'DEBUG' => 'yes',
'values' => {
'hcb_nolock' => {
'type' => 'DERIVE',
'min' => '0'
},
'hcb_lock' => {
'type' => 'DERIVE',
'min' => '0'
},
'hcb_insert' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'esi' => {
'title' => 'ESI',
'DEBUG' => 'yes',
'values' => {
'esi_errors' => {
'type' => 'DERIVE',
'min' => '0'
},
'esi_warnings' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'session' => {
'title' => 'Sessions',
'DEBUG' => 'yes',
'values' => {
'sess_conn' => {
'type' => 'DERIVE',
'min' => '0'
},
'sess_drop' => {
'type' => 'DERIVE',
'min' => '0'
},
'sess_fail' => {
'type' => 'DERIVE',
'min' => '0'
},
'sess_pipe_overflow' => {
'type' => 'DERIVE',
'min' => '0'
},
'sess_queued' => {
'type' => 'DERIVE',
'min' => '0'
},
'sess_dropped' => {
'type' => 'DERIVE',
'min' => '0'
},
'sess_closed' => {
'type' => 'DERIVE',
'min' => '0'
},
'sess_pipeline' => {
'type' => 'DERIVE',
'min' => '0'
},
'sess_readahead' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'session_herd' => {
'title' => 'Session herd',
'DEBUG' => 'yes',
'values' => {
'sess_herd' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'shm_writes' => {
'title' => 'SHM writes and records',
'DEBUG' => 'yes',
'values' => {
'shm_records' => {
'type' => 'DERIVE',
'min' => '0'
},
'shm_writes' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'shm' => {
'title' => 'Shared memory activity',
'DEBUG' => 'yes',
'values' => {
'shm_flushes' => {
'type' => 'DERIVE',
'min' => '0'
},
'shm_cont' => {
'type' => 'DERIVE',
'min' => '0'
},
'shm_cycles' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'allocations' => {
'title' => 'Memory allocation requests',
'DEBUG' => 'yes',
'values' => {
'sm_nreq' => {
'type' => 'DERIVE',
'min' => '0'
},
'sma_nreq' => {
'type' => 'DERIVE',
'min' => '0'
},
'sms_nreq' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'vcl' => {
'title' => 'VCL',
'DEBUG' => 'yes',
'values' => {
'n_backend' => {
'type' => 'GAUGE'
},
'n_vcl' => {
'type' => 'DERIVE',
'min' => '0'
},
'n_vcl_avail' => {
'type' => 'DERIVE',
'min' => '0'
},
'n_vcl_discard' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'bans' => {
'title' => 'Bans',
'DEBUG' => 'yes',
'values' => {
'bans' => {
'type' => 'GAUGE'
},
'bans_added' => {
'type' => 'DERIVE',
'min' => '0'
},
'bans_deleted' => {
'type' => 'DERIVE',
'min' => '0'
},
'bans_completed' => {
'type' => 'GAUGE'
},
'bans_obj' => {
'type' => 'GAUGE'
},
'bans_req' => {
'type' => 'GAUGE'
},
'bans_tested' => {
'type' => 'DERIVE',
'min' => '0'
},
'bans_obj_killed' => {
'type' => 'DERIVE',
'min' => '0'
},
'bans_tests_tested' => {
'type' => 'DERIVE',
'min' => '0'
},
'bans_dups' => {
'type' => 'GAUGE'
},
'bans_persisted_bytes' => {
'type' => 'GAUGE'
},
'bans_persisted_fragmentation' => {
'type' => 'GAUGE'
}
}
},
'bans_lurker' => {
'title' => 'Ban Lurker',
'DEBUG' => 'yes',
'values' => {
'bans_lurker_tested' => {
'type' => 'DERIVE',
'min' => '0'
},
'bans_lurker_tests_tested' => {
'type' => 'DERIVE',
'min' => '0'
},
'bans_lurker_obj_killed' => {
'type' => 'DERIVE',
'min' => '0'
},
'bans_lurker_contention' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'expunge' => {
'title' => 'Object expunging',
'order' => 'n_expired n_lru_nuked',
'values' => {
'n_expired' => {
'type' => 'DERIVE',
'min' => '0'
},
'n_lru_nuked' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'lru' => {
'title' => 'LRU activity',
'DEBUG' => 'yes',
'values' => {
'n_lru_nuked' => {
'type' => 'DERIVE',
'min' => '0'
},
'n_lru_moved' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'bad' => {
'title' => 'Misbehavior',
'values' => {
'SMA_1' => {
'counter' => 'c_fail',
'family' => 'SMA',
},
'SMF_1' => {
'counter' => 'c_fail',
'family' => 'SMF',
},
'sess_drop' => {
'type' => 'DERIVE'
},
'backend_unhealthy' => {
'type' => 'DERIVE'
},
'fetch_failed' => {
'type' => 'DERIVE'
},
'backend_busy' => {
'type' => 'DERIVE'
},
'threads_failed' => {
'type' => 'DERIVE'
},
'threads_limited' => {
'type' => 'DERIVE'
},
'threads_destroyed' => {
'type' => 'DERIVE'
},
'thread_queue_len' => {
'type' => 'GAUGE'
},
'losthdr' => {
'type' => 'DERIVE'
},
'esi_errors' => {
'type' => 'DERIVE'
},
'esi_warnings' => {
'type' => 'DERIVE'
},
'sess_fail' => {
'type' => 'DERIVE'
},
'sess_pipe_overflow' => {
'type' => 'DERIVE'
}
}
},
'gzip' => {
'title' => 'GZIP activity',
'DEBUG' => 'yes',
'values' => {
'n_gzip' => {
'type' => 'DERIVE',
'min' => '0'
},
'n_gunzip' => {
'type' => 'DERIVE',
'min' => '0'
}
}
},
'backend' => {
'title' => 'Backend Status',
'DEBUG' => 'yes',
'values' => {
'VBE.boot_1' => {
'counter' => 'happy',
'family' => 'VBE',
},
}
}
);
################################
# Various helper functions #
################################
# Translate $_[0] from varnish' internal types (flags) to munin/rrd
# variants (e.g: from 'i' to GAUGE). Returns the result.
sub translate_type
{
my $d = $_[0];
if ($d eq "i" or $d eq "g") {
$d = "GAUGE";
} elsif ($d eq "a" or $d eq "c") {
$d = "DERIVE";
}
return $d;
}
# Print the value of a two-dimensional hash if it exist.
# Returns false if non-existent.
#
# Output is formatted for plugins if arg4 is blank, otherwise arg4 is used
# as the title/name of the field (ie: arg4=graph_title).
sub print_if_exist
{
my %values = %{$_[0]};
my $value = $_[1];
my $field = $_[2];
my $pvalue = normalize_name($value);
my $title = "$pvalue.$field";
if (defined($_[3])) {
$title = $_[3];
}
if (defined($values{$value}{$field})) {
print "$title $values{$value}{$field}\n";
} else {
return 0;
}
}
# Create a output-friendly name
sub normalize_name
{
my $name = $_[0];
$name =~ s/[^a-zA-Z0-9]/_/g;
return $name;
}
# Braindead RPN: +,-,/,* will pop two items from @stack, and perform
# the relevant operation on the items. If the item in the array isn't one
# of the 4 basic math operations, a value from varnishstat is pushed on to
# the stack. IE: 'client_req','client_conn','/' will leave the value of
# "client_req/client_conn" on the stack.
#
# If only one item is left on the stack, it is printed. Otherwise, an error
# message is printed.
sub rpn
{
my @stack;
my $left;
my $right;
foreach my $item (@{$_[0]}) {
if ($item eq "+") {
$right = pop(@stack);
$left = pop(@stack);
push(@stack,$left+$right);
} elsif ($item eq "-") {
$right = pop(@stack);
$left = pop(@stack);
push(@stack,$left-$right);
} elsif ($item eq "/") {
$right = pop(@stack);
$left = pop(@stack);
push(@stack,$left/$right);
} elsif ($item eq "*") {
$right = pop(@stack);
$left = pop(@stack);
push(@stack,$left*$right);
} else {
push(@stack,int($data{$item}{'value'}));
}
}
if (@stack > 1)
{
print STDERR "RPN error: Stack has more than one item left.\n";
print STDERR "@stack\n";
exit 255;
}
print "@stack";
print "\n";
}
# Bail-function.
sub usage
{
if (@_) {
print STDERR "@_" . "\n\n";
}
print STDERR "Known arguments: suggest, config, autoconf.\n";
print STDERR "Run with suggest to get a list of known aspects.\n";
exit 1;
}
################################
# XML Parsing #
################################
# The following code is for parsing varnishstat -x. While %data should be
# stable, the following bits can easily be replaced with anything (json, an
# other xml-parser, magic, etc)
#
# The basic concept is simple enough. Only worry about stuff inside
# <state>. Updating %state on each new data field, and commit it to %data
# when </state> is seen.
#
# We do use translate_type() on the 'flag' field.
# Internal state for the XML parsing
my %state = (
'stat' => 0, # inside <stat> or not
'field' => 'none', # <name>, <value>, <stat>, etc.
);
# Reset the state of XML, mainly used for end-elements.
sub xml_reset_state() {
$state{'stat'} = '0';
$state{'field'} = 'none';
$state{'values'} = ();
}
# Callback for data entry. Cleans leading whitespace and updates state.
sub xml_characters {
my $d = $_[1];
if ($state{'stat'} == 0) {
return;
}
if ($state{'field'} eq "type" && $d eq "MAIN") {
return;
}
$d =~ s/^\s*$//g;
if ($d eq "") {
return;
}
$state{'values'}{$state{'field'}} = $d;
}
# Store the current state in %data. Issued at </stat>
# Note that 'flag' is translated to RRD-equivalents here.
sub xml_commit_state
{
my $configtype = $ASPECTS{$self}{'type'};
my $name = $state{'values'}{'name'};
my @namelist = split(/\./,$name);
my $type = shift @namelist;
if ($configtype eq '' || $configtype eq $type) {
my $name = "";
my $ident = "";
if (scalar(@namelist) == 1) {
$name = shift @namelist;
} elsif (scalar(@namelist) == 2) {
$ident = shift @namelist;
$name = shift @namelist;
} else {
$name = pop @namelist;
$ident = join('_', @namelist);
}
foreach my $key (keys %{$state{'values'}}) {
my $data = $state{'values'}{$key};
if ($key eq 'flag') {
$data = translate_type($data);
}
if (defined($type) and $type ne '' and defined($ident) and $ident ne '') {
$data{$type}{$ident}{$name}{$key} = $data;
} else {
$data{$name}{$key} = $data;
}
}
}
}
# Callback for end tag. E.g: </stat>
sub xml_end_elem {
my $element = $_[1];
if ($element ne "stat") {
return;
}
xml_commit_state();
xml_reset_state();
}
# Callback for opening tag. E.g: <stat>
sub xml_start_elem {
$state{'field'} = $_[1];
if ($state{'field'} eq "stat") {
$state{'stat'} = 1;
}
}
################################
# Internal API #
################################
# Populate %data, includes both values and descriptions and more.
# Currently driven by XML, but that could change.
sub populate_stats
{
my $arg = "-x";
my $parser = new XML::Parser(Handlers => {Start => \&xml_start_elem,
End => \&xml_end_elem,
Char => \&xml_characters} );
if ($varnishname) {
$arg .= " -n $varnishname";
}
open (XMLDATA, "$varnishstatexec $arg|") or die "meh";
$parser->parse(*XMLDATA, ProtocolEncoding => 'ISO-8859-1');
close(XMLDATA);
}
# Prints the fields in the list in $_[2] (e.g: 'value'/'description') for
# each identity of the varnish counter/family combination as defined by
# the $_[0]-counter on the aspect definition. Err, that's jibberish, so
# an example:
#
# e.g: dynamic_print('SMA_1','',('value'))
# e.g: dynamic_print('SMA_2','.label',('ident','description'))
# SMA_1 is the counter-value. If it is a dynamic counter, it has a counter
# and family-member (e.g: counter: c_req, family: SMA) and print_dynamic
# will print c_req for each SMA-identity.
#
# Note that the variables to print is a list. This is to allow printing a
# single item with multiple fields. Typically for identity+description so
# you can distinguish between different data points.
#
# Returns true if it was a dynamic counter.
sub print_dynamic
{
my $name = $_[0];
shift;
my $suffix = $_[0];
shift;
my @field = @_;
if (!defined($ASPECTS{$self}{'values'}{$name}{'counter'})) {
return 0;
}
if (!defined($ASPECTS{$self}{'values'}{$name}{'family'})) {
return 0;
}
my $counter = $ASPECTS{$self}{'values'}{$name}{'counter'};
my $type = $ASPECTS{$self}{'values'}{$name}{'family'};
foreach my $key (keys %{$data{$type}}) {
my $pname = normalize_name($type . "_" . $key . "_" . $counter);
print $pname . $suffix . " ";
my $i = 0;
foreach my $f (@field) {
if ($i != 0) {
print " ";
}
$i += 1;
print $data{$type}{$key}{$counter}{$f};
}
print "\n";
}
return 1;
}
# Read and verify the aspect ($self).
sub set_aspect
{
$self = $0;
$self =~ s/^.*\/varnish[0-9]?_//;
return if defined($ASPECTS{$self});
# remove instance name and try again
$self =~ s/^.*?_//;
if (!defined($ASPECTS{$self}) && @ARGV == 0) {
usage "No such aspect";
}
}
# Print 'yes' if it's reasonable to use this plugin, or 'no' with a
# human-readable error message. Always exit true, even if the response
# is 'no'.
sub autoconf
{
# XXX: Solaris outputs errors to stderr and always returns true.
# XXX: See #873
if (`which $varnishstatexec 2>/dev/null` =~ m{^/}) {
print "yes\n";
} else {
print "no ($varnishstatexec could not be found)\n";
}
exit 0;
}
# Suggest relevant aspects/values of $self.
# 'DEBUG'-graphs are excluded.
sub suggest
{
foreach my $key (keys %ASPECTS) {
if (defined($ASPECTS{$key}{'DEBUG'}) && $FULL_SUGGEST != 1) {
next;
}
print "$key\n";
}
}
# Walk through the relevant aspect and print all top-level configuration
# values and value-definitions.
sub get_config
{
my $graph = $_[0];
# Need to double-check since set_aspect only checks this if there
# is no argument (suggest/autoconf doesn't require a valid aspect)
if (!defined($ASPECTS{$graph})) {
usage "No such aspect";
}
my %values = %{$ASPECTS{$graph}{'values'}};
print "graph_category varnish\n";
foreach my $field (@graph_parameters) {
print_if_exist(\%ASPECTS,$graph,$field,"graph_$field");
}
foreach my $value (sort keys %values) {
# Just print the description/type if it's a dynamic
# counter. It'll be silent if it isn't.
if(print_dynamic($value,'.label',('description','type','ident'))) {
print_dynamic($value,'.type',('flag'));
next;
}
# Need either RPN definition or a varnishstat value.
if (!defined($data{$value}{'value'}) &&
!defined($values{$value}{'rpn'})) {
if ($DEBUG) {
print STDERR "ERROR: $value not part of varnishstat.\n"
}
next;
}
if (!print_if_exist(\%values,$value,'label')) {
my $pvalue = normalize_name($value);
print "$pvalue.label $data{$value}{'description'}\n";
}
foreach my $field (@field_parameters) {
print_if_exist(\%values,$value,$field);
}
}
}
# Handle arguments (config, autoconf, suggest)
# Populate stats for config is necessary, but we want to avoid it for
# autoconf as it would generate a nasty error.
sub check_args
{
if (@ARGV && $ARGV[0] eq '') {
shift @ARGV;
}
if (@ARGV == 1) {
if ($ARGV[0] eq "config") {
populate_stats;
get_config($self);
exit 0;
} elsif ($ARGV[0] eq "autoconf") {
autoconf($self);
exit 0;
} elsif ($ARGV[0] eq "suggest") {
suggest;
exit 0;
}
usage "Unknown argument";
}
}
################################
# Execution starts here #
################################
set_aspect;
check_args;
populate_stats;
# We only get here if we're supposed to.
# Walks through the relevant values and either prints the varnishstat, or
# if the 'rpn' variable is set, calls rpn() to execute ... the rpn.
#
# NOTE: Due to differences in varnish-versions, this checks if the value
# actually exist before using it.
foreach my $value (keys %{$ASPECTS{$self}{'values'}}) {
my $pvalue = normalize_name($value);
if (defined($ASPECTS{$self}{'values'}{$value}{'rpn'})) {
print "$pvalue.value ";
rpn($ASPECTS{$self}{'values'}{$value}{'rpn'});
} else {
if (print_dynamic($value,'.value',('value'))) {
next;
}
if (!defined($data{$value}{'value'})) {
if ($DEBUG) {
print STDERR "Error: $value not part of "
. "varnishstat.\n";
}
next;
}
print "$pvalue.value ";
print "$data{$value}{'value'}\n";
}
}

View file

@ -22,29 +22,38 @@
mode: "0755"
tags: varnish
- name: Copy varnish4 munin plugin
- name: Copy varnish5 munin plugin
copy:
src: munin/varnish4_
src: munin/varnish5_
dest: /usr/local/share/munin/plugins/
mode: "0755"
notify: restart munin-node
tags: varnish
- name: Enable varnish4 munin plugin
- name: Enable varnish5 munin plugin
file:
src: /usr/local/share/munin/plugins/varnish4_
dest: "/etc/munin/plugins/varnish4_{{item}}"
src: /usr/local/share/munin/plugins/varnish5_
dest: "/etc/munin/plugins/varnish5_{{item}}"
state: link
loop:
- backend_traffic
- bad
- expunge
- hit_rate
- memory_usage
- expunge
- objects
- request_rate
- mgt_uptime
- threads
- backend_traffic
- hit_rate
- main_uptime
- transfer_rates
- uptime
- bad
notify: restart munin-node
tags: varnish
- name: Copy varnish5 munin plugin config
copy:
src: munin/varnish5.conf
dest: /etc/munin/plugin-conf.d/varnish5
mode: "0644"
notify: restart munin-node
tags: varnish