diff --git a/CONVENTIONS.md b/CONVENTIONS.md new file mode 100644 index 00000000..56720afd --- /dev/null +++ b/CONVENTIONS.md @@ -0,0 +1,158 @@ +# Conventions + +## Roles + +We can use the `ansible-galaxy init` command to bootstrap a new role : + + $ ansible-galaxy init foo + - foo was created successfully + $ tree foo + foo + ├── defaults + │   └── main.yml + ├── files + ├── handlers + │   └── main.yml + ├── meta + │   └── main.yml + ├── README.md + ├── tasks + │   └── main.yml + ├── templates + ├── tests + │   ├── inventory + │   └── test.yml + └── vars + └── main.yml + +All `main.yml` file will be picked up by Ansible automatically, with respect to their own responsibility. + +The main directory is `tasks`. It will contains tasks, either all in the `main.yml` file, or grouped in files that can be included in the main file. + +`defaults/main.yml` is the place to put the list of all variables for the role with a default value. + +`vars` will hold files with variables definitions. Those differ from the defaults because of a much higher precedence (see below). + +`files` is the directory where we'll put files to copy on hosts. They will be copied "as-is". When a role has multiple logical groups of tasks, it's best to create a sub-directroy for each group that needs files. The name of files in these directories doesn't have to be the same as the destination name. Example : + + copy: + src: apt/jessie_backports_preferences + dest: /etc/apt/apt.conf.d/backports + +`templates` is the twin brother of `files`, but differs in that it contains files that can be pre-processed by the Jinja2 templating language. It can contain variables that will be extrapolated before copying the file to its destination. + +`handlers` is the place to put special tasks that can be triggered by the `notify` argument of modules. For example an `nginx -s reload` command. + +`meta/main.yml` contains … well … "meta" information. There we can define role dependencies, but also some "galaxy" information like the desired Ansible version, supported OS and distributions, a destription, author/ownership, license… + +`tests` and `.travis.yml` are here to help testing with a test matrix, a test inventory and a test playbook. + +We can delete parts we don't need. + +### How much goes into a role + +We create roles (instead of a plain tasks files) when it makes sense as a whole, and it is more that a series of tasks. It often has variables, files/templates, handlers… + +## Syntax + +### Pure YAML + +It's possible to use a compact (Ansible specific) syntax, + + - name: Add evomaintenance trap for '{{ user.name }}' + lineinfile: state=present dest='/home/{{ user.name }}/.profile' insertafter=EOF line='trap "sudo /usr/share/scripts/evomaintenance.sh" 0' + when: evomaintenance_script.stat.exists + +but we prefer the pure-YAML syntax + + - name: Add evomaintenance trap for '{{ user.name }}' + lineinfile: + state: present + dest: '/home/{{ user.name }}/.profile' + insertafter: EOF + line: 'trap "sudo /usr/share/scripts/evomaintenance.sh" 0' + when: evomaintenance_script.stat.exists + +Here are some reasons : + +* when lines get long, it's easier to read ; +* it's a pure YAML syntax, so there is no Ansible-specific preprocessing +* … with means that IDE can show the proper syntax highligthing ; +* each argument stands on its own. + +## Variables + +### defaults + +When a role is using variables, they must be defined (for example in the `defaults/main.yml`) with a default value (possibly Ǹull). That way, there will never be an "foo is undefined" situation. + +### progressive specificity + +In many roles, we use a *progressive specificity* pattern for some variables. +The most common is for "alert_email" ; we want to have a default email address where all alerts or message will be sent, but it can be customized globally, and also customized per task/role. + +For the *evolinux-base* role we have those defaults : + + general_alert_email: "root@localhost" + reboot_alert_email: Null + log2mail_alert_email: Null + raid_alert_email: Null + +In the *log2mail* template, we set the email address like this : + + mailto = {{ log2mail_alert_email or general_alert_email | mandatory }} + +if nothing is customize, the mail will be sent to root@localhost, if geeral_alert_email is changed, it will be use, but if log2mail_alert_email is set to a non-null value, it will have precedence. + +## precedence + +There are multiple places where we can define variables ans there is a specific precedence order for the resolution. Here is [the (ascending) order](http://docs.ansible.com/ansible/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable) : + +* role defaults +* inventory vars +* inventory group_vars +* inventory host_vars +* playbook group_vars +* playbook host_vars +* host facts +* play vars +* play vars_prompt +* play vars_files +* registered vars +* set_facts +* role and include vars +* block vars (only for tasks in block) +* task vars (only for the task) +* extra vars (always win precedence) + +## Configuration patterns + +### lineinfile vs. blockinfile vs. copy/template + +When possible, we prefer using the [lineinfile](http://docs.ansible.com/ansible/lineinfile_module.html) module to make very specific changes. +If a `regexp` argument is specified, every line that matches the pattern will be updated. It's a good way to comment/uncomment variable, of add a piece inside a line. + +When it's not possible (multi-line changes, for example), we can use the [blockinfile](http://docs.ansible.com/ansible/blockinfile_module.html) module. It managed blocs of text with begin/end markers. The marker can be customized, mostly to use the proper comment syntax, but also to prevent collisions within a file. + +If none of the previous ca be used, we can use [copy](http://docs.ansible.com/ansible/copy_module.html) or [template](http://docs.ansible.com/ansible/template_module.html) modules to copy an entire file. + +### defaults and custom files + +We try not to alter configuration files managed by packages. It makes upgrading easier, so when a piece of software has a "foo.d" configuration directory, we add custom files there. + +We usually put a `z-evolinux-defaults` with our core configuration. This file can be changed later via Ansible and must not be edited by hand. Example : + + copy: + src: evolinux-defaults.cnf + dest: /etc/mysql/conf.d/z-evolinux-defaults.cnf + force: yes + + +We also create a blank `zzz-evolinux-custom` file, with commented examples, to allow custom configuration that will never be reverted by Ansible. Example : + + copy: + src: evolinux-custom.cnf + dest: /etc/mysql/conf.d/zzz-evolinux-custom.cnf + force: no + +The source file or template shouldn't to be prefixed for ordering (eg. `z-` or `zzz-`). It's the task's responsibility to choose how destination files must be ordered. diff --git a/Vagrantfile b/Vagrantfile index afd16ad6..70dab1dc 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -9,7 +9,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.synced_folder "./vagrant_share/", "/vagrant", disabled: true config.vm.provider :virtualbox do |v| - v.memory = 1024 + v.memory = 2048 v.cpus = 2 v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"] v.customize ["modifyvm", :id, "--ioapic", "on"] diff --git a/apache/README.md b/apache/README.md index a33745a4..bd45539b 100644 --- a/apache/README.md +++ b/apache/README.md @@ -6,6 +6,13 @@ Install Apache Everything is in the `tasks/main.yml` file for now. -## Variables +## Available variables -To add IP to apache whitelist, define apache_ipaddr_whitelist variable as list. +Main variables are : + +* `apache_private_ipaddr_whitelist_present` : list of IP addresses to have in the private whitelist ; +* `apache_private_ipaddr_whitelist_absent` : list of IP addresses **not** to have in the whitelist; +* `apache_private_htpasswd_present` : list of users to have in the private htpasswd ; +* `apache_private_htpasswd_absent` : list of users to **not** have in the private htpasswd. + +The full list of variables (with default values) can be found in `defaults/main.yml`. diff --git a/apache/defaults/main.yml b/apache/defaults/main.yml index d40721b8..70140cad 100644 --- a/apache/defaults/main.yml +++ b/apache/defaults/main.yml @@ -1 +1,6 @@ -apache_ipaddr_whitelist: [] +--- +apache_private_ipaddr_whitelist_present: [] +apache_private_ipaddr_whitelist_absent: [] + +apache_private_htpasswd_present: [] +apache_private_htpasswd_absent: [] diff --git a/apache/files/z_evolinux.conf b/apache/files/evolinux-defaults.conf similarity index 100% rename from apache/files/z_evolinux.conf rename to apache/files/evolinux-defaults.conf diff --git a/apache/files/private_htpasswd b/apache/files/private_htpasswd new file mode 100644 index 00000000..0c16dd41 --- /dev/null +++ b/apache/files/private_htpasswd @@ -0,0 +1 @@ +# user:password for HTTP Basic authentication diff --git a/apache/files/ipaddr_whitelist.conf b/apache/files/private_ipaddr_whitelist.conf similarity index 100% rename from apache/files/ipaddr_whitelist.conf rename to apache/files/private_ipaddr_whitelist.conf diff --git a/apache/tasks/main.yml b/apache/tasks/main.yml index 96be16b0..520c0eb7 100644 --- a/apache/tasks/main.yml +++ b/apache/tasks/main.yml @@ -1,4 +1,4 @@ -- name: Ensure packages are installed +- name: packages are installed apt: name: '{{ item }}' state: present @@ -7,8 +7,10 @@ - apachetop - libapache2-mod-evasive - libwww-perl + tags: + - apache -- name: Ensure basic modules are enabled +- name: basic modules are enabled apache2_module: name: '{{ item }}' state: present @@ -18,48 +20,120 @@ - headers - rewrite - cgi + tags: + - apache -- name: Copy Apache config files +- name: Copy Apache defaults config file copy: - src: "{{ item.file }}" - dest: "/etc/apache2/conf-available/{{ item.file }}" + src: evolinux-defaults.conf + dest: "/etc/apache2/conf-available/z-evolinux-defaults.conf owner: root group: root - mode: "{{ item.mode }}" - with_items: - - { file: z_evolinux.conf, mode: 0644 } - - { file: zzz_evolinux.conf, mode: 0640 } + mode: 0644 + force: yes + tags: + - apache -- name: Ensure Apache default config is enabled - command: a2enconf z_evolinux.conf zzz_evolinux.conf +- name: Copy Apache custom config file + template: + src: evolinux-custom.conf.j2 + dest: "/etc/apache2/conf-available/zzz-evolinux-custom.conf + owner: root + group: root + mode: 0644 + force: no + tags: + - apache + +- name: Ensure Apache config files are enabled + command: "a2enconf {{ item }}" register: command_result changed_when: "'Enabling' in command_result.stderr" + with_items: + - z-evolinux-defaults.conf + - zzz-evolinux-custom.conf + tags: + - apache -- name: Init ipaddr_whitelist.conf file +- name: Init private_ipaddr_whitelist.conf file copy: - src: ipaddr_whitelist.conf - dest: /etc/apache2/ipaddr_whitelist.conf + src: private_ipaddr_whitelist.conf + dest: /etc/apache2/private_ipaddr_whitelist.conf owner: root group: root mode: 0640 force: no + tags: + - apache -- name: Add IP addresses to private IP whitelist if defined +- name: add IP addresses to private IP whitelist lineinfile: - dest: /etc/apache2/ipaddr_whitelist.conf + dest: /etc/apache2/private_ipaddr_whitelist.conf line: "Allow from {{ item }}" state: present - with_items: "{{ apache_ipaddr_whitelist }}" + with_items: "{{ apache_private_ipaddr_whitelist_present }}" + notify: reload apache + tags: + - apache + +- name: remove IP addresses from private IP whitelist + lineinfile: + dest: /etc/apache2/private_ipaddr_whitelist.conf + line: "Allow from {{ item }}" + state: absent + with_items: "{{ apache_private_ipaddr_whitelist_absent }}" + notify: reload apache + tags: + - apache + +- name: Copy private_htpasswd + copy: + src: private_htpasswd + dest: /etc/apache2/private_htpasswd + owner: root + group: root + mode: 0640 + force: no + notify: reload apache + tags: + - apache + +- name: add user:pwd to private htpasswd + lineinfile: + dest: /etc/apache2/private_htpasswd + line: "{{ item }}" + state: present + with_items: "{{ apache_private_htpasswd_present }}" + notify: reload apache + tags: + - apache + +- name: remove user:pwd from private htpasswd + lineinfile: + dest: /etc/apache2/private_htpasswd + line: "{{ item }}" + state: absent + with_items: "{{ apache_private_htpasswd_absent }}" + notify: reload apache + tags: + - apache + +- name: is umask already present? + command: "grep -E '^umask ' /etc/apache2/envvars" + failed_when: False + changed_when: False + register: envvar_grep_umask + tags: + - apache - name: Add a mark in envvars for umask blockinfile: dest: /etc/apache2/envvars + marker: "## {mark} ANSIBLE MANAGED BLOCK" block: | ## Set umask for writing by Apache user. ## Set rights on files and directories written by Apache - -- name : Ensure umask is set in envvars (default is umask 007) - lineinfile: - dest: /etc/apache2/envvars - regexp: "^umask" - line: "umask 007" + umask 007 + when: envvar_grep_umask.rc != 0 + tags: + - apache diff --git a/apache/files/zzz_evolinux.conf b/apache/templates/evolinux-custom.conf.j2 similarity index 100% rename from apache/files/zzz_evolinux.conf rename to apache/templates/evolinux-custom.conf.j2 diff --git a/apt-backports/tasks/main.yml b/apt-backports/tasks/main.yml deleted file mode 100644 index 05e0d7c7..00000000 --- a/apt-backports/tasks/main.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -- name: Jessie-backports list is available - apt_repository: - repo: "deb http://mirror.evolix.org/debian jessie-backports main contrib non-free" - update_cache: yes - state: present - tags: - - system - - packages - -- name: Backports have a low priority - blockinfile: - dest: /etc/apt/preferences.d/backports - marker: "// {mark} ANSIBLE MANAGED BLOCK" - insertafter: EOF - create: yes - block: | - Package: * - Pin: release a=jessie-backports - Pin-Priority: 50 - tags: - - system - - packages diff --git a/apt-backports/README.md b/apt-repositories/README.md similarity index 100% rename from apt-backports/README.md rename to apt-repositories/README.md diff --git a/apt-repositories/defaults/main.yml b/apt-repositories/defaults/main.yml new file mode 100644 index 00000000..837ff138 --- /dev/null +++ b/apt-repositories/defaults/main.yml @@ -0,0 +1 @@ +apt_repositories_components: "main" diff --git a/apt-repositories/files/jessie_backports_preferences b/apt-repositories/files/jessie_backports_preferences new file mode 100644 index 00000000..dd3cef12 --- /dev/null +++ b/apt-repositories/files/jessie_backports_preferences @@ -0,0 +1,3 @@ +Package: * +Pin: release a=jessie-backports +Pin-Priority: 50 diff --git a/apt-repositories/tasks/main.yml b/apt-repositories/tasks/main.yml new file mode 100644 index 00000000..cecf9534 --- /dev/null +++ b/apt-repositories/tasks/main.yml @@ -0,0 +1,17 @@ +--- + +- name: Backports sources list is installed + template: + src: backports.list.j2 + dest: /etc/apt/sources.list.d/backports.list + force: yes + backup: yes + mode: 0640 + +- name: Backports configuration + copy: + src: jessie_backports_preferences + dest: /etc/apt/preferences.d/backports + force: yes + backup: yes + mode: 0640 diff --git a/apt-repositories/templates/backports.list.j2 b/apt-repositories/templates/backports.list.j2 new file mode 100644 index 00000000..488d0d05 --- /dev/null +++ b/apt-repositories/templates/backports.list.j2 @@ -0,0 +1 @@ +deb http://mirror.evolix.org/debian jessie-backports {{ apt_repositories_components | mandatory }} diff --git a/apt-upgrade/README.md b/apt-upgrade/README.md deleted file mode 100644 index 0b7ac94b..00000000 --- a/apt-upgrade/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# apt-upgrade - -Upgrades Debian packages - -## Tasks - -Everything is in the `tasks/main.yml` file. - -## Available variables - -* `apt_upgrade_mode` : kind of upgrade to do (cf. http://docs.ansible.com/ansible/apt_module.html#options) - -Choice of upgrade mode can be set in a variables file (ex. `vars/main.yml`) or when invoking the role (`- { role: apt-upgrade, apt_upgrade_mode: safe }`). diff --git a/apt-upgrade/tasks/main.yml b/apt-upgrade/tasks/main.yml deleted file mode 100644 index c93e9b54..00000000 --- a/apt-upgrade/tasks/main.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -- name: Ensure Debian is up-to-date - apt: - update_cache: yes - upgrade: "{{ apt_upgrade_mode | default('safe') }}" - tags: - - system - - packages diff --git a/elasticsearch-plugin-head/defaults/main.yml b/elasticsearch-plugin-head/defaults/main.yml index 536c01d1..56693740 100644 --- a/elasticsearch-plugin-head/defaults/main.yml +++ b/elasticsearch-plugin-head/defaults/main.yml @@ -1,3 +1,4 @@ +--- elasticsearch_plugin_head_home: /home/elasticsearch-head elasticsearch_plugin_head_clone_dir: "{{ elasticsearch_plugin_head_home }}/www" elasticsearch_plugin_head_owner: "elasticsearch-head" diff --git a/elasticsearch-plugin-head/meta/main.yml b/elasticsearch-plugin-head/meta/main.yml new file mode 100644 index 00000000..c0447167 --- /dev/null +++ b/elasticsearch-plugin-head/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - nodejs diff --git a/elasticsearch-plugin-head/tasks/main.yml b/elasticsearch-plugin-head/tasks/main.yml index 78810a9f..e0bbed3a 100644 --- a/elasticsearch-plugin-head/tasks/main.yml +++ b/elasticsearch-plugin-head/tasks/main.yml @@ -1,41 +1,5 @@ --- - -- name: APT https transport is enabled - apt: - name: apt-transport-https - state: installed - tags: - - system - - packages - -- name: Node GPG key is installed - apt_key: - url: https://deb.nodesource.com/gpgkey/nodesource.gpg.key - state: present - tags: - - system - - packages - - npm - -- name: Node sources list is available - apt_repository: - repo: "deb https://deb.nodesource.com/node_6.x jessie main" - state: present - tags: - - system - - packages - - npm - -- name: Node is installed - apt: - name: nodejs - update_cache: yes - state: installed - tags: - - packages - - npm - - name: "User {{ elasticsearch_plugin_head_owner }} is present" user: name: "{{ elasticsearch_plugin_head_owner }}" diff --git a/elasticsearch/defaults/main.yml b/elasticsearch/defaults/main.yml index f1a54e9c..a3a7180f 100644 --- a/elasticsearch/defaults/main.yml +++ b/elasticsearch/defaults/main.yml @@ -1,3 +1,4 @@ +--- elasticsearch_cluster_name: Null elasticsearch_node_name: "${HOSTNAME}" elasticsearch_network_host: "[_site_, _local_]" diff --git a/etc-git/README.md b/etc-git/README.md new file mode 100644 index 00000000..c1eb49c5 --- /dev/null +++ b/etc-git/README.md @@ -0,0 +1,7 @@ +# etc-git + +Put /etc under Git version control. + +## Tasks + +Everything is in the `tasks/main.yml` file. diff --git a/etc-git/files/gitignore b/etc-git/files/gitignore new file mode 100644 index 00000000..f72b3e48 --- /dev/null +++ b/etc-git/files/gitignore @@ -0,0 +1 @@ +aliases.db diff --git a/etc-git/tasks/main.yml b/etc-git/tasks/main.yml new file mode 100644 index 00000000..b1820ef6 --- /dev/null +++ b/etc-git/tasks/main.yml @@ -0,0 +1,36 @@ +--- + +- name: Git is installed + apt: + name: git + state: present + +- name: /etc is versioned with git + command: "git init ." + args: + chdir: /etc + creates: /etc/.git/ + register: git_init + +- name: /etc/.gitignore is present + copy: + src: gitignore + dest: /etc/.gitignore + owner: root + group: root + mode: 0600 + +- name: does /etc/ have any commit? + command: "git log" + args: + chdir: /etc + changed_when: False + failed_when: False + register: git_log + +- name: initial commit is present? + shell: "git add -A . && git commit -m \"Initial commit via Ansible\"" + args: + chdir: /etc + register: git_commit + when: git_init.changed or git_log.rc != 0 diff --git a/evocheck/README.md b/evocheck/README.md new file mode 100644 index 00000000..7284534b --- /dev/null +++ b/evocheck/README.md @@ -0,0 +1,7 @@ +# evocheck + +Install a script to verify compliance of a Debian/OpenBSD server + +## Tasks + +Everything is in the `tasks/main.yml` file. diff --git a/evocheck/meta/main.yml b/evocheck/meta/main.yml new file mode 100644 index 00000000..d3bddff3 --- /dev/null +++ b/evocheck/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - { role: evolinux-sources-list } diff --git a/evocheck/tasks/main.yml b/evocheck/tasks/main.yml new file mode 100644 index 00000000..e471fb74 --- /dev/null +++ b/evocheck/tasks/main.yml @@ -0,0 +1,8 @@ +--- + +- name: evocheck is installed + command: "apt-get install -yq --allow-unauthenticated evomaintenance" + register: installed_evomaintenance + changed_when: not (installed_evomaintenance.stdout | search("0 upgraded") and installed_evomaintenance.stdout | search("0 newly installed")) + +# TODO make sure that the package is in the right version diff --git a/evolinux-admin-users/README.md b/evolinux-admin-users/README.md new file mode 100644 index 00000000..e98f70c2 --- /dev/null +++ b/evolinux-admin-users/README.md @@ -0,0 +1,29 @@ +# evolinux-admin-users + +Creates admin users accounts, based on a configuration data structure. + +## Tasks + +Everything is in the `tasks/main.yml` file. + +## Available variables + +The variable `evolinux_admin_users` must be a "hash" of one or more users : + +``` +evolinux_admin_users: + - name: foo + uid: 1001 + fullname: 'Mr Foo' + password_hash: 'sdfgsdfgsdfgsdfg' + ssh_key: 'ssh-rsa AZERTYXYZ' + - name: bar + uid: 1002 + fullname: 'Mr Bar' + password_hash: 'gsdfgsdfgsdfgsdf' + ssh_key: 'ssh-rsa QWERTYUIOP' +``` + +* `general_scripts_dir`: general directory for scripts installation (default: `/usr/local/bin`). +* `listupgrade_scripts_dir`: script directory for listupgrade (default: `general_scripts_dir`). +* `evomaintenance_scripts_dir`: script directory for evomaintenance (default: `general_scripts_dir`). diff --git a/evolinux-admin-users/defaults/main.yml b/evolinux-admin-users/defaults/main.yml new file mode 100644 index 00000000..f0b78dba --- /dev/null +++ b/evolinux-admin-users/defaults/main.yml @@ -0,0 +1,6 @@ +--- +evolinux_admin_users: [] + +general_scripts_dir: "/usr/local/bin" +evomaintenance_scripts_dir: Null +listupgrade_scripts_dir: Null diff --git a/evolinux-admin-users/tasks/adduser_debian.yml b/evolinux-admin-users/tasks/adduser_debian.yml new file mode 100644 index 00000000..4cbbecf8 --- /dev/null +++ b/evolinux-admin-users/tasks/adduser_debian.yml @@ -0,0 +1,95 @@ +--- + +- name: Test if uid exists for '{{ user.name }}' + command: 'getent passwd {{ user.uid }}' + register: uidisbusy + failed_when: False + changed_when: False + +- name: Add Unix account with classical uid for '{{ user.name }}' + user: + state: present + uid: '{{ user.uid }}' + name: '{{ user.name }}' + comment: '{{ user.fullname }}' + shell: /bin/bash + password: '{{ user.password_hash }}' + update_password: on_create + when: uidisbusy|failed + +- name: Add Unix account with random uid for '{{ user.name }}' + user: + state: present + name: '{{ user.name }}' + comment: '{{ user.fullname }}' + shell: /bin/bash + password: '{{ user.password_hash }}' + update_password: on_create + when: uidisbusy|success + +- name: Fix perms on homedirectory for '{{ user.name }}' + file: + name: '/home/{{ user.name }}' + mode: 0700 + state: directory + +- name: is evomaintenance installed? + stat: + path: "{{ evomaintenance_scripts_dir or general_scripts_dir | mandatory }}/evomaintenance.sh" + register: evomaintenance_script + +- name: Add evomaintenance trap for '{{ user.name }}' + lineinfile: + state: present + dest: '/home/{{ user.name }}/.profile' + insertafter: EOF + line: 'trap "sudo {{ evomaintenance_scripts_dir or general_scripts_dir | mandatory }}/evomaintenance.sh" 0' + when: evomaintenance_script.stat.exists + +- name: Create .ssh directory for '{{ user.name }}' + file: + dest: '/home/{{ user.name }}/.ssh/' + state: directory + mode: 0700 + owner: '{{ user.name }}' + group: '{{ user.name }}' + +- name: Add user's SSH public key for '{{ user.name }}' + lineinfile: + dest: '/home/{{ user.name }}/.ssh/authorized_keys' + create: yes + line: '{{ user.ssh_key }}' + owner: '{{ user.name }}' + group: '{{ user.name }}' + +- name: Modify AllowUsers' sshd directive for '{{ user.name }}' + replace: + dest: /etc/ssh/sshd_config + regexp: '^(AllowUsers ((?!{{ user.name }}).)*)$' + replace: '\1 {{ user.name }}' + notify: + - reload sshd + +- name: Modify Match User's sshd directive for '{{ user.name }}' + replace: + dest: /etc/ssh/sshd_config + regexp: '^(Match User ((?!{{ user.name }}).)*)$' + replace: '\1,{{ user.name }}' + notify: + - reload sshd + +- name: Evolinux sudoers file is present + template: + src: sudoers_debian.j2 + dest: /etc/sudoers.d/evolinux + force: false + validate: '/usr/sbin/visudo -cf %s' + register: copy_sudoers_evolinux + +- name: Add user in sudoers file for '{{ user.name }}' + replace: + dest: /etc/sudoers.d/evolinux + regexp: '^(User_Alias\s+ADMINS\s+=((?!{{ user.name }}).)*)$' + replace: '\1,{{ user.name }}' + validate: '/usr/sbin/visudo -cf %s' + when: not copy_sudoers_evolinux.changed diff --git a/evolinux-admin-users/tasks/main.yml b/evolinux-admin-users/tasks/main.yml new file mode 100644 index 00000000..f30ca901 --- /dev/null +++ b/evolinux-admin-users/tasks/main.yml @@ -0,0 +1,8 @@ +--- + +- include: adduser_debian.yml user={{ item }} + with_items: "{{ evolinux_admin_users }}" + when: ansible_distribution == "Debian" + +# - include: openbsd.yml +# when: ansible_distribution == "OpenBSD" diff --git a/evolinux-admin-users/templates/sudoers_debian.j2 b/evolinux-admin-users/templates/sudoers_debian.j2 new file mode 100644 index 00000000..88ef5f98 --- /dev/null +++ b/evolinux-admin-users/templates/sudoers_debian.j2 @@ -0,0 +1,10 @@ +Defaults umask=0077 + +Cmnd_Alias MAINT = {{ evomaintenance_scripts_dir or general_scripts_dir | mandatory }}/evomaintenance.sh, {{ listupgrade_scripts_dir or general_scripts_dir | mandatory }}/listupgrade.sh, /usr/bin/apt, /bin/mount +User_Alias ADMINS = {{ user.name }} + +nagios ALL = NOPASSWD: /usr/lib/nagios/plugins/check_procs +nagios ALL = (clamav) NOPASSWD: /usr/bin/clamscan /tmp/safe.txt + +ADMINS ALL = (ALL:ALL) ALL +ADMINS ALL = NOPASSWD: MAINT diff --git a/evolinux-base/README.md b/evolinux-base/README.md new file mode 100644 index 00000000..2a722eb2 --- /dev/null +++ b/evolinux-base/README.md @@ -0,0 +1,24 @@ +# evolinux-base + +Various tasks for Evolinux setup. + +## Tasks + +* `system.yml` : +* `apt.yml` : +* `install_tools.yml` : +* `root.yml` : +* `logs.yml` : + +## Available variables + +Main variables are : + +* `evolinux_delete_nfs`: delete NFS tools (default: `True`) +* `evolinux_ntp_server`: custom NTP server host or IP (default: `Null`) +* `evolinux_additional_packages`: optional additional packages to install (default: `[]`) +* `general_alert_email`: email address to send various alert messages (default: `root@localhost`). +* `apt_alert_email`: email address to send APT messages to (default: `general_alert_email`). +* `log2mail_alert_email`: email address to send Log2mail messages to (default: `general_alert_email`). + +The full list of variables (with default values) can be found in `defaults/main.yml`. diff --git a/evolinux-base/defaults/main.yml b/evolinux-base/defaults/main.yml new file mode 100644 index 00000000..ac194d34 --- /dev/null +++ b/evolinux-base/defaults/main.yml @@ -0,0 +1,41 @@ +--- +general_alert_email: "root@localhost" +reboot_alert_email: Null +apt_alert_email: Null +log2mail_alert_email: Null +raid_alert_email: Null + +# hostname + +evolinux_hostname: "{{ ansible_hostname }}" +evolinux_domain: "{{ ansible_domain }}" +evolinux_fqdn: "{{ ansible_fqdn }}" +evolinux_internal_hostname: "{{ evolinux_hostname }}" + +# apt + +evolinux_apt_repositories_components: "main" +evolinux_apt_hooks: False +# kernel + +evolinux_kernel_reboot_after_panic: True +evolinux_kernel_disable_tcp_timestamps: True +evolinux_kernel_reduce_swapiness: True +evolinux_kernel_cve20165696: True + +# providers + +evolinux_provider_online: False +evolinux_provider_orange_fce: False + +# default www + +evolinux_default_www_redirect_url: "http://evolix.fr" +evolinux_default_www_ssl_subject: "/CN={{ ansible_fqdn }}" +evolinux_default_www_nginx_enabled: False +evolinux_default_www_apache_enabled: False + +# misc. + +evolinux_ntp_server: Null +evolinux_delete_nfs: True diff --git a/evolinux-base/files/default_www/img/background-top.png b/evolinux-base/files/default_www/img/background-top.png new file mode 100644 index 00000000..a54eec48 Binary files /dev/null and b/evolinux-base/files/default_www/img/background-top.png differ diff --git a/evolinux-base/files/default_www/img/favicon.ico b/evolinux-base/files/default_www/img/favicon.ico new file mode 100644 index 00000000..f08f1081 Binary files /dev/null and b/evolinux-base/files/default_www/img/favicon.ico differ diff --git a/evolinux-base/files/logs/logrotate.d/apache2-php b/evolinux-base/files/logs/logrotate.d/apache2-php new file mode 100644 index 00000000..6c7eb9a8 --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/apache2-php @@ -0,0 +1,9 @@ +/var/log/php.log { + weekly + missingok + rotate 52 + compress + delaycompress + notifempty + create 640 www-data adm +} diff --git a/evolinux-base/files/logs/logrotate.d/apt b/evolinux-base/files/logs/logrotate.d/apt new file mode 100644 index 00000000..89b53287 --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/apt @@ -0,0 +1,16 @@ +/var/log/apt/term.log { + rotate 120 + monthly + compress + missingok + notifempty +} + +/var/log/apt/history.log { + rotate 120 + monthly + compress + missingok + notifempty +} + diff --git a/evolinux-base/files/logs/logrotate.d/bind.disabled b/evolinux-base/files/logs/logrotate.d/bind.disabled new file mode 100644 index 00000000..00378e8f --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/bind.disabled @@ -0,0 +1,14 @@ +/var/chroot-bind/var/log/bind.log { + weekly + missingok + notifempty + rotate 4 + create 640 bind bind + compress + delaycompress + sharedscripts + postrotate + rndc reload > /dev/null + endscript +} + diff --git a/evolinux-base/files/logs/logrotate.d/dhcp b/evolinux-base/files/logs/logrotate.d/dhcp new file mode 100644 index 00000000..04f5e6dc --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/dhcp @@ -0,0 +1,9 @@ +/var/log/dhcp.log { + weekly + missingok + rotate 52 + compress + delaycompress + create 640 root adm + notifempty +} diff --git a/evolinux-base/files/logs/logrotate.d/dpkg b/evolinux-base/files/logs/logrotate.d/dpkg new file mode 100644 index 00000000..16ac22fe --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/dpkg @@ -0,0 +1,19 @@ +/var/log/dpkg.log { + monthly + rotate 120 + compress + delaycompress + missingok + notifempty + create 644 root root +} +/var/log/alternatives.log { + monthly + rotate 120 + compress + delaycompress + missingok + notifempty + create 644 root root +} + diff --git a/evolinux-base/files/logs/logrotate.d/freeradius b/evolinux-base/files/logs/logrotate.d/freeradius new file mode 100644 index 00000000..d3a757c5 --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/freeradius @@ -0,0 +1,8 @@ +/var/log/freeradius/*.log { + weekly + missingok + rotate 52 + compress + delaycompress + notifempty +} diff --git a/evolinux-base/files/logs/logrotate.d/ftp.disabled b/evolinux-base/files/logs/logrotate.d/ftp.disabled new file mode 100644 index 00000000..87b8b9d4 --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/ftp.disabled @@ -0,0 +1,31 @@ +/var/log/proftpd.log { + weekly + missingok + rotate 13 + compress + delaycompress + notifempty + create 640 root adm + sharedscripts + postrotate + /etc/init.d/proftpd restart > /dev/null + endscript +} + + +/var/log/xferlog.log { + weekly + rotate 1 + missingok + create 640 root adm + sharedscripts + postrotate + DATE=$(date +"%d-%m-%Y") + cd /var/log + ftpstats -a -r -l 2 -d i-f xferlog.log.1 2>/dev/null >xferreport.$DATE + mv xferlog.log.1 xferlog.log.$DATE + gzip xferlog.log.$DATE + gzip xferreport.$DATE + endscript +} + diff --git a/evolinux-base/files/logs/logrotate.d/ldap b/evolinux-base/files/logs/logrotate.d/ldap new file mode 100644 index 00000000..59372a33 --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/ldap @@ -0,0 +1,9 @@ +/var/log/openldap.log { + weekly + missingok + rotate 3 + compress + notifempty + create 640 root adm +} + diff --git a/evolinux-base/files/logs/logrotate.d/lighttpd.disabled b/evolinux-base/files/logs/logrotate.d/lighttpd.disabled new file mode 100644 index 00000000..8f463c98 --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/lighttpd.disabled @@ -0,0 +1,19 @@ +/var/log/lighttpd/*.log { + weekly + missingok + copytruncate + rotate 52 + compress + delaycompress + notifempty + sharedscripts + postrotate + if [ -f /var/run/lighttpd.pid ]; then \ + if [ -x /usr/sbin/invoke-rc.d ]; then \ + invoke-rc.d lighttpd force-reload > /dev/null; \ + else \ + /etc/init.d/lighttpd force-reload > /dev/null; \ + fi; \ + fi; + endscript +} diff --git a/evolinux-base/files/logs/logrotate.d/lvm-common.disabled b/evolinux-base/files/logs/logrotate.d/lvm-common.disabled new file mode 100644 index 00000000..9dcfffcc --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/lvm-common.disabled @@ -0,0 +1,6 @@ +/var/log/lvm { + daily + rotate 3 + missingok + create 0640 root adm +} diff --git a/evolinux-base/files/logs/logrotate.d/news.disabled b/evolinux-base/files/logs/logrotate.d/news.disabled new file mode 100644 index 00000000..f1f2bbda --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/news.disabled @@ -0,0 +1,8 @@ +/var/log/news.log { + monthly + missingok + notifempty + rotate 1 + create 640 root adm +} + diff --git a/evolinux-base/files/logs/logrotate.d/nginx b/evolinux-base/files/logs/logrotate.d/nginx new file mode 100644 index 00000000..3350c84e --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/nginx @@ -0,0 +1,18 @@ +/var/log/nginx/*.log { + weekly + missingok + rotate 52 + compress + delaycompress + notifempty + create 640 root adm + sharedscripts + prerotate + if [ -d /etc/logrotate.d/httpd-prerotate ]; then \ + run-parts /etc/logrotate.d/httpd-prerotate; \ + fi; \ + endscript + postrotate + [ ! -f /var/run/nginx.pid ] || kill -USR1 `cat /var/run/nginx.pid` + endscript +} diff --git a/evolinux-base/files/logs/logrotate.d/ntp.disabled b/evolinux-base/files/logs/logrotate.d/ntp.disabled new file mode 100644 index 00000000..a38311d0 --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/ntp.disabled @@ -0,0 +1,14 @@ +/var/log/ntp.log { + weekly + rotate 1 + missingok + create 640 root adm + sharedscripts + postrotate + DATE=$(date +"%d-%m-%Y") + cd /var/log + mv ntp.log.1 ntp.log.$DATE + gzip ntp.log.$DATE + endscript +} + diff --git a/evolinux-base/files/logs/logrotate.d/postgresql b/evolinux-base/files/logs/logrotate.d/postgresql new file mode 100644 index 00000000..4fef188d --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/postgresql @@ -0,0 +1,7 @@ +/var/log/postgresql.log { + weekly + missingok + rotate 8 + create 640 root adm +} + diff --git a/evolinux-base/files/logs/logrotate.d/procmail b/evolinux-base/files/logs/logrotate.d/procmail new file mode 100644 index 00000000..d42323f1 --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/procmail @@ -0,0 +1,11 @@ +/var/log/procmail.log { + daily + rotate 365 + dateext + dateyesterday + dateformat .%Y%m%d + missingok + rotate 365 + create 640 root adm +} + diff --git a/evolinux-base/files/logs/logrotate.d/samba b/evolinux-base/files/logs/logrotate.d/samba new file mode 100644 index 00000000..f3f53287 --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/samba @@ -0,0 +1,14 @@ +# Attention, bien mettre "log file = /var/log/samba/%m.log" dans la conf Samba +/var/log/samba/*.log { + weekly + missingok + rotate 52 + postrotate + invoke-rc.d --quiet samba reload > /dev/null + [ ! -f /var/run/samba/nmbd.pid ] || kill -HUP `cat /var/run/samba/nmbd.pid` + [ -f /var/run/samba/winbindd.pid ] && kill -HUP `cat /var/run/samba/winbindd.pid` || true + endscript + compress + notifempty +} + diff --git a/evolinux-base/files/logs/logrotate.d/squid3.disabled b/evolinux-base/files/logs/logrotate.d/squid3.disabled new file mode 100644 index 00000000..95946eb9 --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/squid3.disabled @@ -0,0 +1,11 @@ +/var/log/squid3/*.log { + monthly + compress + rotate 12 + missingok + create 640 proxy adm + sharedscripts + postrotate + test ! -e /var/run/squid3.pid || /usr/sbin/squid3 -k rotate + endscript +} \ No newline at end of file diff --git a/evolinux-base/files/logs/logrotate.d/zsyslog b/evolinux-base/files/logs/logrotate.d/zsyslog new file mode 100644 index 00000000..016dcfa6 --- /dev/null +++ b/evolinux-base/files/logs/logrotate.d/zsyslog @@ -0,0 +1,35 @@ +# Custom EvoLinux +create 640 root adm +dateext +dateyesterday +dateformat .%Y%m%d +missingok +notifempty +delaycompress +compress +postrotate + invoke-rc.d rsyslog rotate > /dev/null +endscript + +/var/log/daemon.log +/var/log/kern.log +/var/log/lpr.log +{ + weekly + rotate 5 +} + +/var/log/auth.log +/var/log/user.log +/var/log/cron.log +/var/log/debug +/var/log/messages +/var/log/syslog +/var/log/mail.info +/var/log/mail.warn +/var/log/mail.err +/var/log/mail.log +{ + daily + rotate 365 +} \ No newline at end of file diff --git a/evolinux-base/files/logs/rsyslog.conf b/evolinux-base/files/logs/rsyslog.conf new file mode 100644 index 00000000..9c2a2d22 --- /dev/null +++ b/evolinux-base/files/logs/rsyslog.conf @@ -0,0 +1,122 @@ +# Syslog for Pack Evolix serveur - Debian Squeeze + + +################# +#### MODULES #### +################# + +$ModLoad imuxsock # provides support for local system logging +$ModLoad imklog # provides kernel logging support (previously done by rklogd) +#$ModLoad immark # provides --MARK-- message capability + +# provides UDP syslog reception +#$ModLoad imudp +#$UDPServerRun 514 + +# provides TCP syslog reception +#$ModLoad imtcp +#$InputTCPServerRun 514 + + +########################### +#### GLOBAL DIRECTIVES #### +########################### + +# +# Use traditional timestamp format. +# To enable high precision timestamps, comment out the following line. +# +$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat + +# +# Set the default permissions for all log files. +# +$FileOwner root +$FileGroup adm +$FileCreateMode 0640 +$DirCreateMode 0755 +$Umask 0022 + +# +# Include all config files in /etc/rsyslog.d/ +# +$IncludeConfig /etc/rsyslog.d/*.conf + + +############### +#### RULES #### +############### + +# +# First some standard log files. Log by facility. +# +auth,authpriv.* /var/log/auth.log +*.*;auth,authpriv.none;cron,mail,local4,local5.none -/var/log/syslog +cron.* /var/log/cron.log +daemon.* -/var/log/daemon.log +kern.* -/var/log/kern.log +lpr.* -/var/log/lpr.log +mail.* -/var/log/mail.log +user.* -/var/log/user.log +uucp.* /var/log/uucp.log +news.* /var/log/news.log + +local4.* -/var/log/openldap.log +local1.* /var/log/sympa.log +local0.* /var/log/postgresql.log +local7.* -/var/log/dhcp.log +local5.* -/var/log/haproxy.log + + +# +# Logging for the mail system. Split it up so that +# it is easy to write scripts to parse these files. +# +#mail.info -/var/log/mail.info +#mail.warn -/var/log/mail.warn +#mail.err /var/log/mail.err + +# +# Logging for INN news system. +# +#news.crit /var/log/news/news.crit +#news.err /var/log/news/news.err +#news.notice -/var/log/news/news.notice + +# +# Some "catch-all" log files. +# +#*.=debug;\ +# auth,authpriv.none;\ +# news.none;mail.none -/var/log/debug +#*.=info;*.=notice;*.=warn;\ +# auth,authpriv.none;\ +# cron,daemon.none;\ +# mail,news.none -/var/log/messages + +# +# Emergencies are sent to everybody logged in. +# +*.emerg * + +# +# I like to have messages displayed on the console, but only on a virtual +# console I usually leave idle. +# +#daemon,mail.*;\ +# news.=crit;news.=err;news.=notice;\ +# *.=debug;*.=info;\ +# *.=notice;*.=warn /dev/tty8 + +# The named pipe /dev/xconsole is for the `xconsole' utility. To use it, +# you must invoke `xconsole' with the `-file' option: +# +# $ xconsole -file /dev/xconsole [...] +# +# NOTE: adjust the list below, or you'll go crazy if you have a reasonably +# busy site.. +# +#daemon.*;mail.*;\ +# news.err;\ +# *.=debug;*.=info;\ +# *.=notice;*.=warn |/dev/xconsole diff --git a/evolinux-base/files/root/gitconfig b/evolinux-base/files/root/gitconfig new file mode 100644 index 00000000..06e6b2a8 --- /dev/null +++ b/evolinux-base/files/root/gitconfig @@ -0,0 +1,22 @@ +[core] + filemode = true + bare = false +[color] + branch = auto + status = auto + diff = auto + interactive = auto + decorate = auto + grep = auto + ui = true +[apply] + whitespace = nowarn +[alias] + a = add + aa = add -A . + c = commit -v + ca = commit -v -a + d = diff --ignore-space-change --patience --no-prefix + dw = diff --word-diff + lg = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative + s = status -s -b diff --git a/evolinux-base/handlers/main.yml b/evolinux-base/handlers/main.yml new file mode 100644 index 00000000..0866ad79 --- /dev/null +++ b/evolinux-base/handlers/main.yml @@ -0,0 +1,49 @@ +--- +- name: dpkg-reconfigure-debconf + command: dpkg-reconfigure --frontend noninteractive debconf + +- name: dpkg-reconfigure-locales + command: dpkg-reconfigure --frontend noninteractive locales + +- name: dpkg-reconfigure-apt + command: dpkg-reconfigure --frontend noninteractive apt-listchanges + +# - name: debconf-set-selections +# command: debconf-set-selections /root/debconf-preseed + +- name: apt update + apt: + update_cache: yes + +- name: restart rsyslog + service: + name: rsyslog + state: restarted + + +- name: remount /home + command: mount -o remount /home + +- name: remount /var + command: mount -o remount /var + + +- name: restart nginx + service: + name: nginx + state: restarted + +- name: reload nginx + service: + name: nginx + state: reloaded + +- name: restart apache + service: + name: apache2 + state: restarted + +- name: reload apache + service: + name: apache2 + state: reloaded diff --git a/evolinux-base/tasks/apt.yml b/evolinux-base/tasks/apt.yml new file mode 100644 index 00000000..b4b8fab7 --- /dev/null +++ b/evolinux-base/tasks/apt.yml @@ -0,0 +1,55 @@ +--- + +- name: Setting apt config + lineinfile: + dest: /etc/apt/apt.conf.d/z-evolinux.conf + line: "{{ item }}" + create: yes + state: present + mode: 0640 + with_items: + - "APT::Install-Recommends \"0\";" + - "APT::Install-Suggests \"0\";" + +- name: DPKg invoke hooks + lineinfile: + dest: /etc/apt/apt.conf.d/z-evolinux.conf + line: "{{ item }}" + create: yes + state: present + mode: 0640 + with_items: + - "DPkg::Pre-Invoke { \"mount -oremount,exec /tmp && mount -oremount,rw /usr || true\"; };" + - "DPkg::Post-Invoke { \"mount -oremount /tmp && mount -oremount /usr || exit 0\"; };" + when: evolinux_apt_hooks + +- name: Original repositories are disabled + replace: + dest: /etc/apt/sources.list + regexp: '^(deb(-src)? {{ item }}.+)' + replace: '# \1' + with_items: + # - '.+\.debian\.org' + - 'cdrom:' + +- name: Basic sources list is installed + lineinfile: + dest: /etc/apt/sources.list + line: "{{ item }}" + with_items: + - "deb http://security.debian.org/ jessie/updates {{ evolinux_apt_components | mandatory }}" + - "deb http://mirror.evolix.org/debian/ jessie {{ evolinux_apt_components | mandatory }}" + - "deb http://mirror.evolix.org/debian/ jessie-updates {{ evolinux_apt_components | mandatory }}" + +- name: Evolix public list is installed + template: + src: apt/evolix_public.list.j2 + dest: /etc/apt/sources.list.d/evolix_public.list + force: yes + backup: yes + mode: 0640 + +- name: Upgrading system + apt: + upgrade: dist + update_cache: yes diff --git a/evolinux-base/tasks/default_packages.yml b/evolinux-base/tasks/default_packages.yml new file mode 100644 index 00000000..d2fee898 --- /dev/null +++ b/evolinux-base/tasks/default_packages.yml @@ -0,0 +1,14 @@ +--- +- name: Install/Update default packages (might take some time) + command: "apt-get install -yq --allow-unauthenticated {{ evolinux_default_packages | join(' ') }}" + register: install_default_packages + changed_when: not (install_default_packages.stdout | search("0 upgraded") and install_default_packages.stdout | search("0 newly installed")) + +- name: Deleting rpcbin and nfs-common + apt: + name: "{{ item }}" + state: absent + with_items: + - rpcbind + - nfs-common + when: evolinux_delete_nfs diff --git a/evolinux-base/tasks/default_www.yml b/evolinux-base/tasks/default_www.yml new file mode 100644 index 00000000..aa12b452 --- /dev/null +++ b/evolinux-base/tasks/default_www.yml @@ -0,0 +1,108 @@ +--- +- name: /var/www is present + file: + path: /var/www + state: directory + mode: 0755 + +- name: images are copied + copy: + src: default_www/img + dest: /var/www/ + mode: 0755 + directory_mode: 0755 + follow: yes + +- name: index is copied + template: + src: default_www/index.html.j2 + dest: /var/www/index.html + mode: 0755 + +# SSL cert + +- name: ssl-cert package is installed + apt: + name: ssl-cert + state: installed + +- name: Create private key and csr for default site ({{ ansible_fqdn }}) + shell: openssl req -newkey rsa:2048 -sha256 -nodes -keyout /etc/ssl/private/{{ ansible_fqdn }}.key -out /etc/ssl/{{ ansible_fqdn }}.csr -batch -subj "{{ evolinux_default_www_ssl_subject }}" + args: + creates: "/etc/ssl/private/{{ ansible_fqdn }}.key" + +- name: Adjust rights on private key + file: + path: /etc/ssl/private/{{ ansible_fqdn }}.key + owner: root + group: ssl-cert + mode: 0640 + +- name: Create certificate for default site + shell: openssl x509 -req -days 3650 -sha256 -in /etc/ssl/{{ ansible_fqdn }}.csr -signkey /etc/ssl/private/{{ ansible_fqdn }}.key -out /etc/ssl/certs/{{ ansible_fqdn }}.crt + args: + creates: "/etc/ssl/certs/{{ ansible_fqdn }}.crt" + + +# Nginx vhost + +- name: is Nginx installed? + stat: + path: /etc/nginx/sites-available + register: nginx_sites_available + +- block: + - name: nginx vhost is installed + template: + src: default_www/nginx_default_site.j2 + dest: /etc/nginx/sites-available/000-default + mode: 0640 + # force: yes + notify: reload nginx + tags: + - nginx + + + - name: nginx vhost is enabled + file: + src: /etc/nginx/sites-available/000-default + dest: /etc/nginx/sites-enabled/000-default + state: link + notify: reload nginx + when: evolinux_default_www_nginx_enabled + tags: + - nginx + + when: nginx_sites_available.stat.exists + + +# Apache vhost + +- name: is Apache installed? + stat: + path: /etc/apache2/sites-available + register: apache_sites_available + +- block: + - name: Apache vhost is installed + template: + src: default_www/apache_default_site.j2 + dest: /etc/apache2/sites-available/000-default + mode: 0640 + # force: yes + notify: reload apache + tags: + - apache + + + - name: Apache vhost is enabled + file: + src: /etc/apache2/sites-available/000-default + dest: /etc/apache2/sites-enabled/000-default + state: link + notify: reload apache + when: evolinux_default_www_apache_enabled + tags: + - apache + + when: apache_sites_available.stat.exists diff --git a/evolinux-base/tasks/fstab.yml b/evolinux-base/tasks/fstab.yml new file mode 100644 index 00000000..1a4952f9 --- /dev/null +++ b/evolinux-base/tasks/fstab.yml @@ -0,0 +1,53 @@ +--- +# TODO: trouver comment faire une copie initiale de /etc/fstab +# TODO: try to use the custom mount_uuid module for a different approach + +- name: Fetch fstab content + shell: "grep -v '^#' /etc/fstab" + register: fstab_content + failed_when: False + changed_when: False + +- name: /home partition is customized (noexec,nosuid,nodev) + replace: + dest: /etc/fstab + regexp: '(\s+/home\s+\w+\s+defaults)(\s+)' + replace: '\1,noexec,nosuid,nodev\2' + backup: yes + notify: remount /home + when: "' /home ' in fstab_content.stdout" + +- name: /tmp partition is customized (noexec,nosuid,nodev) + replace: + dest: /etc/fstab + regexp: '(\s+/tmp\s+\w+\s+defaults)(\s+)' + replace: '\1,noexec,nosuid,nodev\2' + backup: yes + when: "' /tmp ' in fstab_content.stdout" + +- name: /usr partition is customized (ro) + replace: + dest: /etc/fstab + regexp: '(\s+/usr\s+\w+\s+defaults)(\s+)' + replace: '\1,ro\2' + backup: yes + when: "' /usr ' in fstab_content.stdout" + +- name: /var partition is customized (nosuid) + replace: + dest: /etc/fstab + regexp: '(\s+/var\s+\w+\s+defaults)(\s+)' + replace: '\1,nosuid\2' + backup: yes + notify: remount /var + when: "' /var ' in fstab_content.stdout" + +- name: /var/tmp is created + mount: + src: tmpfs + name: /var/tmpfs + fstype: tmpfs + opts: defaults,noexec,nosuid,nodev,size=1024m + state: mounted + +- meta: flush_handlers diff --git a/evolinux-base/tasks/hardware.yml b/evolinux-base/tasks/hardware.yml new file mode 100644 index 00000000..9988d7f3 --- /dev/null +++ b/evolinux-base/tasks/hardware.yml @@ -0,0 +1,71 @@ +--- +- name: Install pciutils + apt: + name: pciutils + state: present + +## Broadcom NetXtreme II + +- name: Check if Broadcom NetXtreme II device is present + shell: "lspci | grep -q 'NetXtreme II'" + register: broadcom + failed_when: False + changed_when: False + +# TODO: add the "non-free" part to the existing sources +# instead of adding a new source + +- name: Add non-free repo for Broadcom NetXtreme II + apt_repository: + repo: 'deb http://mirror.evolix.org/debian/ jessie non-free' + state: present + when: broadcom|success + +## RAID + +- name: Detect if RAID is installed + shell: lspci | grep "RAID bus controller" | grep -v Intel + register: raidmodel + changed_when: "'FAILED' in raidmodel.stdout" + failed_when: "'FAILED' in raidmodel.stdout" + +- block: + - name: Install packages for HP hardware + apt: + name: cciss-vol-status + state: present + + - name: Configure packages for HP hardware + template: + src: hardware/cciss-vol-statusd.j2 + dest: /etc/init.d/cciss-vol-statusd + mode: 0755 + + - name: Enable HP hardware in sysctl + service: + name: cciss-vol-statusd + enabled: true + state: started + when: "'Hewlett-Packard Company Smart Array' in raidmodel.stdout" + +- block: + - name: Install packages for DELL/LSI hardware + apt: + name: "{{ item }}" + state: present + with_items: + - megacli + - megaclisas-statusd + + - name: Configure packages for DELL/LSI hardware + template: + src: hardware/megaclisas-statusd.j2 + dest: /etc/default/megaclisas-statusd + mode: 0755 + + - name: Enable DELL/LSI hardware in sysctl + service: + name: megaclisas-statusd + enabled: true + state: started + when: "'MegaRAID SAS' in raidmodel.stdout" diff --git a/evolinux-base/tasks/hostname.yml b/evolinux-base/tasks/hostname.yml new file mode 100644 index 00000000..aed8201e --- /dev/null +++ b/evolinux-base/tasks/hostname.yml @@ -0,0 +1,37 @@ +--- +- name: Set hostname "{{ evolinux_hostname }}" + hostname: + name: "{{ evolinux_hostname }}" + +- name: Set right localhost line in /etc/hosts + replace: + dest: /etc/hosts + regexp: '^127.0.0.1(\s+)localhost.*$' + replace: '127.0.0.1\1localhost.localdomain localhost' + +- name: Set ip+fqdn+hostname in /etc/hosts + lineinfile: + dest: /etc/hosts + line: "{{ ansible_default_ipv4.address }} {{ evolinux_fqdn }} {{ evolinux_hostname }}" + insertafter: '127.0.0.1\s+localhost.localdomain' + +- name: 127.0.1.1 is removed + lineinfile: + dest: /etc/hosts + regexp: '^127.0.1.1\s+{{ evolinux_fqdn }}' + state: absent + +- name: override ansible_hostname fact + set_fact: + ansible_hostname: "{{ evolinux_hostname }}" + when: ansible_hostname != evolinux_hostname + +- name: override ansible_domain fact + set_fact: + ansible_domain: "{{ evolinux_domain }}" + when: ansible_domain != evolinux_domain + +- name: override ansible_fqdn fact + set_fact: + ansible_fqdn: "{{ evolinux_fqdn }}" + when: ansible_fqdn != evolinux_fqdn diff --git a/evolinux-base/tasks/kernel.yml b/evolinux-base/tasks/kernel.yml new file mode 100644 index 00000000..1754be47 --- /dev/null +++ b/evolinux-base/tasks/kernel.yml @@ -0,0 +1,39 @@ +--- +- name: Enable reboot after panic + sysctl: + name: "{{ item.name }}" + value: "{{ item.value }}" + sysctl_file: /etc/sysctl.d/evolinux.conf + state: present + reload: yes + with_items: + - { name: kernel.panic_on_oops, value: 1 } + - { name: kernel.panic, value: 60 } + when: evolinux_kernel_reboot_after_panic + +- name: Disable net.ipv4.tcp_timestamps + sysctl: + name: net.ipv4.tcp_timestamps + value: 0 + sysctl_file: /etc/sysctl.d/evolinux.conf + state: present + reload: yes + when: evolinux_kernel_disable_tcp_timestamps + +- name: Reduce the swapiness + sysctl: + name: vm.swappiness + value: 20 + sysctl_file: /etc/sysctl.d/evolinux.conf + state: present + reload: yes + when: evolinux_kernel_reduce_swapiness + +- name: Patch for TCP stack vulnerability CVE-2016-5696 + sysctl: + name: net.ipv4.tcp_challenge_ack_limit + value: 1073741823 + sysctl_file: /etc/sysctl.d/evolinux.conf + state: present + reload: yes + when: evolinux_kernel_cve20165696 diff --git a/evolinux-base/tasks/logs.yml b/evolinux-base/tasks/logs.yml new file mode 100644 index 00000000..f09f36f0 --- /dev/null +++ b/evolinux-base/tasks/logs.yml @@ -0,0 +1,28 @@ +--- + +# TODO: voir comment faire des backups initiaux des fichiers + +- name: Copy rsyslog.conf + copy: + src: logs/rsyslog.conf + dest: /etc/rsyslog.conf + mode: 0644 + notify: restart rsyslog + +- name: Disable logrotate default conf + command: mv /etc/logrotate.d/rsyslog /etc/logrotate.d/rsyslog.disabled + args: + removes: /etc/logrotate.d/rsyslog + creates: /etc/logrotate.d/rsyslog.disabled + notify: restart rsyslog + +- name: Configure logrotate + copy: + src: logs/logrotate.d + dest: /etc/logrotate.d + +- name: Configure logrotate.conf + replace: + dest: /etc/logrotate.conf + regexp: "rotate [0-9]*" + replace: "rotate 12" diff --git a/evolinux-base/tasks/main.yml b/evolinux-base/tasks/main.yml new file mode 100644 index 00000000..867e2411 --- /dev/null +++ b/evolinux-base/tasks/main.yml @@ -0,0 +1,39 @@ +--- +- name: Hostname + include: hostname.yml + +- name: Kernel tuning + include: kernel.yml + +- name: Apt configuration and packages install + include: apt.yml + +- name: Fstab configuration + include: fstab.yml + +- name: Default packages + include: default_packages.yml + +- name: System settings + include: system.yml + +- name: Root user configuration + include: root.yml + +- name: Logs management + include: logs.yml + + +- name: Default index page + include: default_www.yml + +- name: Hardware drivers and tools + include: hardware.yml + +- name: Customize for Online.net + include: provider_online.yml + when: evolinux_provider_online + +- name: Customize for Orange FCE + include: provider_orange_fce.yml + when: evolinux_provider_orange_fce diff --git a/evolinux-base/tasks/provider_online.yml b/evolinux-base/tasks/provider_online.yml new file mode 100644 index 00000000..b226ed5f --- /dev/null +++ b/evolinux-base/tasks/provider_online.yml @@ -0,0 +1,2 @@ +- debug: + msg: "Online DNS servers fails sometimes! Please change them in /etc/resolv.conf." diff --git a/evolinux-base/tasks/provider_orange_fce.yml b/evolinux-base/tasks/provider_orange_fce.yml new file mode 100644 index 00000000..43775ac0 --- /dev/null +++ b/evolinux-base/tasks/provider_orange_fce.yml @@ -0,0 +1,14 @@ +- name: Customize kernel for Orange FCE + sysctl: + name: "{{ item.name }}" + value: "{{ item.value }}" + sysctl_file: /etc/sysctl.d/evolinux_fce.conf + state: present + reload: yes + with_items: + - { name: net.ipv4.tcp_keepalive_time, value: 250 } + - { name: net.ipv4.tcp_keepalive_intvl, value: 60 } + - { name: net.ipv6.conf.all.disable_ipv6, value: 1 } + +- debug: + msg: "Orange DNS servers suck! Please change them in /etc/resolv.conf." diff --git a/evolinux-base/tasks/root.yml b/evolinux-base/tasks/root.yml new file mode 100644 index 00000000..7a57dd66 --- /dev/null +++ b/evolinux-base/tasks/root.yml @@ -0,0 +1,78 @@ +--- + +- name: chmod 700 /root + file: + path: /root + state: directory + mode: 0700 + tags: + - root + +- name: "Customize root's bashrc..." + lineinfile: + dest: /root/.bashrc + line: "{{ item }}" + create: yes + state: present + with_items: + - "export HISTCONTROL=$HISTCONTROL${HISTCONTROL+,}ignoreboth" + - "export HISTSIZE=65535" + - "export HISTTIMEFORMAT=\"%c : \"" + tags: + - root + +## .bash_history should be append-only + +- name: Create .bash_history if missing + copy: + content: "" + dest: "/root/.bash_history" + force: no + tags: + - root + +- name: Set umask in /root/.profile + lineinfile: + dest: "/root/.profile" + line: "umask 0077" + regexp: "umask [0-9]+" + tags: + - root + +- name: Custom git config for root + copy: + src: root/gitconfig + dest: "/root/.gitconfig" + force: no + tags: + - root + +- name: Is .bash_history append-only + shell: lsattr /root/.bash_history | grep -E "^.*a.* " + register: bash_history_append_only + failed_when: False + changed_when: False + tags: + - root + +- name: Set .bash_history append-only + command: chattr +a /root/.bash_history + when: bash_history_append_only.rc != 0 + tags: + - root + +- name: Setting vim root configuration + lineinfile: + dest: /root/.vimrc + line: "{{ item }}" + create: yes + state: present + with_items: + - "syntax on" + - "set hlsearch" + - "set background=dark" + - "set expandtab" + - "set tabstop=4" + - "set softtabstop=0" + - "set shiftwidth=4" + - "set smarttab" diff --git a/evolinux-base/tasks/system.yml b/evolinux-base/tasks/system.yml new file mode 100644 index 00000000..d6837afd --- /dev/null +++ b/evolinux-base/tasks/system.yml @@ -0,0 +1,104 @@ +--- +- name: /tmp must be world-writable + file: + path: /tmp + state: directory + mode: 1777 + +- name: Setting default locales + lineinfile: + dest: /etc/locale.gen + line: "{{ item }}" + state: present + with_items: + - "en_US.UTF-8 UTF-8" + - "fr_FR ISO-8859-1" + - "fr_FR.UTF-8 UTF-8" + notify: dpkg-reconfigure-locales + # when: not docker + +- name: Setting vim as default editor + alternatives: + name: editor + path: /usr/bin/vim.basic + +- name: Add "umask 027" to /etc/profile.d/evolinux.sh + lineinfile: + dest: /etc/profile.d/evolinux.sh + line: "umask 027" + create: yes + state: present + +- name: Set /etc/adduser.conf DIR_MODE to 0700 + replace: + dest: /etc/adduser.conf + regexp: "^DIR_MODE=.*$" + replace: "DIR_MODE=0700" + +# TODO: trouver comment ne pas faire ça sur Xen Dom-U + +- name: Deactivating login on all tty except tty2 + lineinfile: + dest: /etc/securetty + line: "tty2" + create: yes + state: present + +- name: Setting TMOUT to deconnect inactive users + lineinfile: + dest: /etc/profile + line: "export TMOUT=36000" + state: present + +#- name: Customizing /etc/fstab + +- name: Modify default umask for cron deamon + lineinfile: + dest: /etc/default/cron + line: "umask 022" + create: yes + state: present + +- name: Randomize periodic crontabs + replace: + dest: /etc/crontab + regexp: "{{ item.regexp }}" + replace: "{{ item.replace }}" + backup: "{{ item.backup }}" + with_items: + - {regexp: '^17((\s*\*){4})', replace: '{{ 59|random(start=1) }}\1', backup: "yes"} + - {regexp: '^25\s*6((\s*\*){3})', replace: '{{ 59|random(start=1) }} {{ [0,1,3,4,5,6,7]|random }}\1', backup: "no"} + - {regexp: '^47\s*6((\s*\*){2}\s*7)', replace: '{{ 59|random(start=1) }} {{ [0,1,3,4,5,6,7]|random }}\1', backup: "no"} + - {regexp: '^52\s*6(\s*1(\s*\*){2})', replace: '{{ 59|random(start=1) }} {{ [0,1,3,4,5,6,7]|random }}\1', backup: "no"} + +# NTP server address + +- name: Configure NTP + replace: + dest: /etc/ntp.conf + regexp: "^server .*$" + replace: "server {{ evolinux_ntp_server }}" + backup: yes + when: evolinux_ntp_server | default(False) + +## alert5 + +- name: "Install alert5 init script" + template: + src: system/init_alert5.j2 + dest: /etc/init.d/alert5 + mode: 0755 + +- name: Enable alert5 init script + service: + name: alert5 + enabled: yes + +## network interfaces + +- name: "Network interfaces must be \"auto\" and not \"allow-hotplug\"" + replace: + dest: /etc/network/interfaces + regexp: "allow-hotplug" + replace: "auto" + backup: yes diff --git a/evolinux-base/templates/apt/evolix_public.list.j2 b/evolinux-base/templates/apt/evolix_public.list.j2 new file mode 100644 index 00000000..be799691 --- /dev/null +++ b/evolinux-base/templates/apt/evolix_public.list.j2 @@ -0,0 +1,2 @@ +deb http://pub.evolix.net/ kernel/ +deb http://pub.evolix.net/ jessie/ diff --git a/evolinux-base/templates/default_www/apache_default_site.j2 b/evolinux-base/templates/default_www/apache_default_site.j2 new file mode 100644 index 00000000..8f29785a --- /dev/null +++ b/evolinux-base/templates/default_www/apache_default_site.j2 @@ -0,0 +1,55 @@ + + ServerName {{ ansible_fqdn }} + ServerAdmin webmaster@localhost + DocumentRoot /var/www/ + + SSLEngine on + SSLCertificateFile /etc/ssl/certs/{{ ansible_fqdn }}.crt + SSLCertificateKeyFile /etc/ssl/private/{{ ansible_fqdn }}.key + SSLProtocol all -SSLv2 -SSLv3 + + # Redirect to HTTPS, execpt for server-status, because Munin plugin + # can't handle HTTPS! :( + RewriteEngine on + RewriteCond %{REQUEST_URI} !^/server-status.*$ [NC] + RewriteCond %{REQUEST_URI} !^/munin_opcache.php$ [NC] + RewriteRule ^/(.*) https://{{ ansible_fqdn }}/$1 [L,R=permanent] + + + Options FollowSymLinks + AllowOverride None + Deny from all + Include /etc/apache2/private_ipaddr_whitelist.conf + + + + Options Indexes FollowSymLinks MultiViews + AllowOverride None + + + + Deny from all + Allow from 127.0.0.1 + Include /etc/apache2/private_ipaddr_whitelist.conf + + + ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ + + AllowOverride None + Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch + + + ErrorDocument 403 {{ evolinux_default_www_redirect_url }} + CustomLog /var/log/apache2/access.log vhost_combined + ErrorLog /var/log/apache2/error.log + LogLevel warn + + Alias /munin /var/cache/munin/www + Alias /phpmyadmin-SED_RANDOM /usr/share/phpmyadmin/ + IncludeOptional /etc/apache2/conf-available/phpmyadmin* + + + deny from all + + + diff --git a/evolinux-base/templates/default_www/index.html.j2 b/evolinux-base/templates/default_www/index.html.j2 new file mode 100644 index 00000000..25a967b4 --- /dev/null +++ b/evolinux-base/templates/default_www/index.html.j2 @@ -0,0 +1,78 @@ + + + + + + + {{ ansible_hostname }} + + + + +
+

{{ ansible_hostname }}

+ + + + +
+ + + diff --git a/nginx/templates/default_site.j2 b/evolinux-base/templates/default_www/nginx_default_site.j2 similarity index 95% rename from nginx/templates/default_site.j2 rename to evolinux-base/templates/default_www/nginx_default_site.j2 index 1e1ceab5..803ff4ad 100644 --- a/nginx/templates/default_site.j2 +++ b/evolinux-base/templates/default_www/nginx_default_site.j2 @@ -18,7 +18,7 @@ server { access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; - error_page 403 {{ nginx_default_redirect_url }}; + error_page 403 {{ evolinux_default_www_redirect_url }}; root /var/www; diff --git a/evolinux-base/templates/hardware/cciss-vol-statusd.j2 b/evolinux-base/templates/hardware/cciss-vol-statusd.j2 new file mode 100644 index 00000000..275f9373 --- /dev/null +++ b/evolinux-base/templates/hardware/cciss-vol-statusd.j2 @@ -0,0 +1,191 @@ +#! /bin/sh + +# Author: Petter Reinholdtsen +# Author: Adam Cécile (Le_Vert) +# License: GNU General Public License v2 or later +# +### BEGIN INIT INFO +# Provides: cciss-vol-statusd +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Check cciss_vol_status values in the background. +### END INIT INFO + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +DESC="cciss-vol-status monitor" +NAME="cciss-vol-statusd" +PIDFILE=/var/run/$NAME.pid +STATUSFILE=/var/run/$NAME.status +SCRIPTNAME=/etc/init.d/$NAME + +MAILTO="{{ raid_alert_email or general_alert_email | mandatory }}" # Where to report problems +PERIOD=600 # Seconds between each check (default 10 minutes) +REMIND=86400 # Seconds between each reminder (default 2 hours) +RUN_DAEMON=yes +ID=/dev/cciss/sda + +[ -e /etc/default/cciss-vol-statusd ] && . /etc/default/cciss-vol-statusd + +# Gracefully exit if the package has been removed. +test -x /usr/sbin/cciss_vol_status || exit 0 + +. /lib/lsb/init-functions +[ -e /etc/default/rcS ] && . /etc/default/rcS + +if [ $RUN_DAEMON = "no" ] ; then + log_begin_msg "cciss-vol-statusd is disabled in /etc/default/cciss-vol-statusd, not starting." + log_end_msg 0 + exit 0 +fi + +check_cciss() { + echo $$ > $PIDFILE.new && mv $PIDFILE.new $PIDFILE + while true ; do + # Check ever $PERIOD seconds, send email on every status + # change and repeat ever $REMIND seconds if the raid is still + # bad. + if (cciss_vol_status $ID); then + BADRAID=false + else + BADRAID=true + logger -t cciss-vol-statusd "detected non-optimal RAID status" + fi + STATUSCHANGE=false + if [ true = "$BADRAID" ] ; then + # RAID not OK + (cciss_vol_status $ID) > $STATUSFILE.new + if [ ! -f $STATUSFILE ] ; then # RAID just became broken + STATUSCHANGE=true + mv $STATUSFILE.new $STATUSFILE + elif cmp -s $STATUSFILE $STATUSFILE.new ; then + # No change. Should we send reminder? + LASTTIME="`stat -c '%Z' $STATUSFILE`" + NOW="`date +%s`" + SINCELAST="`expr $NOW - $LASTTIME`" + if [ $REMIND -le "$SINCELAST" ]; then + # Time to send reminder + STATUSCHANGE=true + mv $STATUSFILE.new $STATUSFILE + else + rm $STATUSFILE.new + fi + else + STATUSCHANGE=true + mv $STATUSFILE.new $STATUSFILE + fi + else + # RAID OK + if [ -f $STATUSFILE ] ; then + rm $STATUSFILE + STATUSCHANGE=true + fi + fi + + if [ true = "$STATUSCHANGE" ]; then + hostname="`uname -n`" + ( + cat < /dev/null 2>&1 + rm -f $PIDFILE + else + log_progress_msg "Daemon is already stopped." + return 0 + fi +} + +# This is a workaround function which does not directly exit and +# therefore can be used by a restart +d_stop_by_restart() { + if [ -f $PIDFILE ] ; then + start-stop-daemon --oknodo --stop --quiet --pidfile $PIDFILE + rm -f $PIDFILE + log_end_msg 0 + else + log_progress_msg "Daemon is already stopped." + log_end_msg 0 + fi +} + +case "$1" in + start) + echo -n "" + log_begin_msg "Starting $DESC: $NAME" + d_start ; CODE=$? + log_end_msg $CODE + ;; + stop) + log_begin_msg "Stopping $DESC: $NAME" + d_stop ; CODE=$? + log_end_msg $CODE + ;; + check_cciss) + check_cciss + ;; + restart|force-reload) + log_begin_msg "Restarting $DESC: $NAME" + d_stop_by_restart + sleep 1 + d_start || CODE=$? + log_end_msg $CODE + ;; + *) + # echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/evolinux-base/templates/hardware/megaclisas-statusd.j2 b/evolinux-base/templates/hardware/megaclisas-statusd.j2 new file mode 100644 index 00000000..02cd9c69 --- /dev/null +++ b/evolinux-base/templates/hardware/megaclisas-statusd.j2 @@ -0,0 +1,4 @@ +MAILTO={{ raid_alert_email or general_alert_email | mandatory }} # Where to report problems +PERIOD=600 # Seconds between each check (default 10 minutes) +REMIND=86400 # Seconds between each reminder (default 2 hours) +RUN_DAEMON=yes diff --git a/evolinux-base/templates/system/init_alert5.j2 b/evolinux-base/templates/system/init_alert5.j2 new file mode 100644 index 00000000..41a5b611 --- /dev/null +++ b/evolinux-base/templates/system/init_alert5.j2 @@ -0,0 +1,16 @@ +#!/bin/sh + +### BEGIN INIT INFO +# Provides: alert5 +# Required-Start: $local_fs +# Required-Stop: $local_fs +# Should-Start: $all +# Should-Stop: $all +# Default-Start: 2 +# Default-Stop: +# Short-Description: Send boot/reboot mail on start +# Description: Send boot/reboot mail on start +### END INIT INFO + +date | mail -s'boot/reboot' {{ reboot_alert_email or general_alert_email | mandatory }} +#/etc/init.d/minifirewall start diff --git a/evolinux-base/vars/main.yml b/evolinux-base/vars/main.yml new file mode 100644 index 00000000..11aae658 --- /dev/null +++ b/evolinux-base/vars/main.yml @@ -0,0 +1,31 @@ +evolinux_default_packages: + - serveur-base + - strace + - htop + - iftop + - iptraf + - ncdu + - vim + - iotop + - rsync + - mtr-tiny + - sudo + - git + - subversion + - ntp + - screen + - pv + - apg + - tcpdump + - ntpdate + - lsb-release + - mutt + - pinentry-curses + - bc + - dnsutils + - lm-sensors + - conntrack + - hdparm + - lsb-invalid-mta + - smartmontools + - tree diff --git a/evomaintenance/defaults/main.yml b/evomaintenance/defaults/main.yml new file mode 100644 index 00000000..80c7c189 --- /dev/null +++ b/evomaintenance/defaults/main.yml @@ -0,0 +1,23 @@ +--- +general_alert_email: "root@localhost" +evomaintenance_alert_email: Null + +general_scripts_dir: "/usr/local/bin" +evomaintenance_scripts_dir: Null + +evomaintenance_hostname: "{{ ansible_fqdn }}" + +evomaintenance_pg_host: Null +evomaintenance_pg_passwd: Null +evomaintenance_pg_db: Null +evomaintenance_pg_table: Null + +evomaintenance_from: "evomaintenance@{{ ansible_fqdn }}" +evomaintenance_full_from: "Evomaintenance <{{ evomaintenance_from }}>" + +evomaintenance_urgency_from: mama.doe@example.com +evomaintenance_urgency_tel: "06.00.00.00.00" + +evomaintenance_realm: "{{ ansible_domain }}" + +evomaintenance_hosts: [] diff --git a/evomaintenance/meta/main.yml b/evomaintenance/meta/main.yml new file mode 100644 index 00000000..d3bddff3 --- /dev/null +++ b/evomaintenance/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - { role: evolinux-sources-list } diff --git a/evomaintenance/tasks/main.yml b/evomaintenance/tasks/main.yml new file mode 100644 index 00000000..8fa8a36e --- /dev/null +++ b/evomaintenance/tasks/main.yml @@ -0,0 +1,32 @@ +--- + +- name: evomaintenance is installed + command: "apt-get install -yq --allow-unauthenticated evomaintenance" + register: installed_evomaintenance + changed_when: not (installed_evomaintenance.stdout | search("0 upgraded") and installed_evomaintenance.stdout | search("0 newly installed")) + +- name: configuration is applied + template: + src: evomaintenance.j2 + dest: /etc/evomaintenance.cf + +- name: list users with a shell + shell: "cat /etc/passwd | grep -vE \"^root:\" | grep -E \":/[^:]+sh$\" | cut -d: -f6" + changed_when: False + register: home_of_shell_users + +- include: trap.yml home={{ item }} + with_items: "{{ home_of_shell_users.stdout_lines }}" + +- name: minifirewall section for evomaintenance + lineinfile: + dest: /etc/default/minifirewall + line: "/sbin/iptables -A INPUT -p tcp --sport 5432 --dport 1024:65535 -s {{ item }} -m state --state ESTABLISHED,RELATED -j ACCEPT" + insertafter: "^# EvoMaintenance" + with_items: "{{ evomaintenance_hosts }}" + +- name: remove minifirewall example rule for the proxy + lineinfile: + dest: /etc/default/minifirewall + regexp: '^#.*(--sport 5432).*(-s X\.X\.X\.X)' + state: absent diff --git a/evomaintenance/tasks/trap.yml b/evomaintenance/tasks/trap.yml new file mode 100644 index 00000000..b3c0ef80 --- /dev/null +++ b/evomaintenance/tasks/trap.yml @@ -0,0 +1,26 @@ +- name: is {{ home }}/.bash_profile present? + stat: + path: "{{ home }}/.bash_profile" + register: bash_profile + +- name: install shell trap in {{ home }}/.bash_profile + lineinfile: + dest: "{{ home }}/.bash_profile" + line: "trap \"sudo {{ evomaintenance_scripts_dir or general_scripts_dir | mandatory }}/evomaintenance.sh\" 0" + insertafter: EOF + create: no + when: bash_profile.stat.exists + +- name: is {{ home }}/.profile present? + stat: + path: "{{ home }}/.profile" + register: profile + when: not bash_profile.stat.exists + +- name: install shell trap in {{ home }}/.profile + lineinfile: + dest: "{{ home }}/.profile" + line: "trap \"sudo {{ evomaintenance_scripts_dir or general_scripts_dir | mandatory }}/evomaintenance.sh\" 0" + insertafter: EOF + create: yes + when: not bash_profile.stat.exists diff --git a/evomaintenance/templates/evomaintenance.j2 b/evomaintenance/templates/evomaintenance.j2 new file mode 100644 index 00000000..c7f6c69b --- /dev/null +++ b/evomaintenance/templates/evomaintenance.j2 @@ -0,0 +1,13 @@ +HOSTNAME={{ evomaintenance_hostname }} +EVOMAINTMAIL={{ evomaintenance_alert_email or general_alert_email | mandatory }} + +export PGPASSWORD={{ evomaintenance_pg_passwd }} + +PGDB={{ evomaintenance_pg_db }} +PGTABLE={{ evomaintenance_pg_table }} +PGHOST={{ evomaintenance_pg_host }} +FROM={{ evomaintenance_from }} +FULLFROM="{{ evomaintenance_full_from }}" +URGENCYFROM={{ evomaintenance_urgency_from }} +URGENCYTEL="{{ evomaintenance_urgency_tel }}" +REALM={{ evomaintenance_realm }} diff --git a/fail2ban/README.md b/fail2ban/README.md new file mode 100644 index 00000000..55168279 --- /dev/null +++ b/fail2ban/README.md @@ -0,0 +1,16 @@ +# fail2ban + +Install Fail2ban. + +## Tasks + +Everything is in the `tasks/main.yml` file. + +## Available variables + +Main variables are : + +* `general_alert_email`: email address to send various alert messages (default: `root@localhost`). +* `fail2ban_alert_email`: email address for messages sent to root (default: `general_alert_email`). + +The full list of variables (with default values) can be found in `defaults/main.yml`. diff --git a/fail2ban/defaults/main.yml b/fail2ban/defaults/main.yml new file mode 100644 index 00000000..ba35f912 --- /dev/null +++ b/fail2ban/defaults/main.yml @@ -0,0 +1,3 @@ +--- +general_alert_email: "root@localhost" +fail2ban_alert_email: Null diff --git a/fail2ban/files/dovecot-evolix.conf b/fail2ban/files/dovecot-evolix.conf new file mode 100644 index 00000000..5ca484af --- /dev/null +++ b/fail2ban/files/dovecot-evolix.conf @@ -0,0 +1,3 @@ +[Definition] +failregex = (?: pop3-login|imap-login): .*(?:Authentication failure|Aborted login \(auth failed|Aborted login \(tried to use disabled|Disconnected \(auth failed|Aborted login \(\d+ authentication attempts).*rip=(?P\S*),.* +ignoreregex = diff --git a/fail2ban/files/sasl-evolix.conf b/fail2ban/files/sasl-evolix.conf new file mode 100644 index 00000000..4115f017 --- /dev/null +++ b/fail2ban/files/sasl-evolix.conf @@ -0,0 +1,3 @@ +[Definition] +failregex = (?i): warning: [-._\w]+\[\]: SASL (?:LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed: +ignoreregex = diff --git a/fail2ban/handlers/main.yml b/fail2ban/handlers/main.yml new file mode 100644 index 00000000..d83f78d9 --- /dev/null +++ b/fail2ban/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart fail2ban + service: + name: fail2ban + state: restarted diff --git a/fail2ban/tasks/main.yml b/fail2ban/tasks/main.yml new file mode 100644 index 00000000..0a25e269 --- /dev/null +++ b/fail2ban/tasks/main.yml @@ -0,0 +1,24 @@ +--- +- name: package is installed + apt: + name: fail2ban + state: installed + tags: + - packages + +- name: custom filters are installed + copy: + src: "{{ item }}" + dest: /etc/fail2ban/filter.d/ + mode: 0644 + with_items: + - dovecot-evolix.conf + - sasl-evolix.conf + notify: restart fail2ban + +- name: local jail is installed + template: + src: jail.local.j2 + dest: /etc/fail2ban/jail.local + mode: 0644 + notify: restart fail2ban diff --git a/fail2ban/templates/jail.local.j2 b/fail2ban/templates/jail.local.j2 new file mode 100644 index 00000000..e7d0545d --- /dev/null +++ b/fail2ban/templates/jail.local.j2 @@ -0,0 +1,27 @@ +# EvoLinux Fail2Ban config. + +[DEFAULT] + +# "ignoreip" can be an IP address, a CIDR mask or a DNS host +ignoreip = 127.0.0.1/8 +bantime = 600 +maxretry = 3 + +# "backend" specifies the backend used to get files modification. Available +# options are "gamin", "polling" and "auto". +# yoh: For some reason Debian shipped python-gamin didn't work as expected +# This issue left ToDo, so polling is default backend for now +backend = auto + +destemail = {{ fail2ban_alert_email or general_alert_email | mandatory }} + +# ACTIONS + +banaction = iptables-multiport +mta = sendmail +protocol = tcp +chain = INPUT +action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] + %(mta)s-whois-lines[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"] + +action = %(action_mwl)s diff --git a/filebeat/defaults/main.yml b/filebeat/defaults/main.yml index e5d78014..6a950e17 100644 --- a/filebeat/defaults/main.yml +++ b/filebeat/defaults/main.yml @@ -1,2 +1,3 @@ +--- filebeat_kibana_dashboards: False filebeat_logstash_plugin: False diff --git a/java8/defaults/main.yml b/java8/defaults/main.yml index 19bf7803..9f57f290 100644 --- a/java8/defaults/main.yml +++ b/java8/defaults/main.yml @@ -1 +1,2 @@ +--- java8_default_alternative: True diff --git a/kibana-proxy-nginx/defaults/main.yml b/kibana-proxy-nginx/defaults/main.yml index 2d2e6480..28477d87 100644 --- a/kibana-proxy-nginx/defaults/main.yml +++ b/kibana-proxy-nginx/defaults/main.yml @@ -1,3 +1,4 @@ +--- kibana_proxy_domain: "kibana.{{ ansible_fqdn }}" kibana_proxy_ssl_cert: "/etc/ssl/certs/{{ ansible_fqdn }}.crt" kibana_proxy_ssl_key: "/etc/ssl/private/{{ ansible_fqdn }}.key" diff --git a/listupgrade/defaults/main.yml b/listupgrade/defaults/main.yml new file mode 100644 index 00000000..0f0f1395 --- /dev/null +++ b/listupgrade/defaults/main.yml @@ -0,0 +1,6 @@ +--- +general_alert_email: "root@localhost" +listupgrade_alert_email: Null + +general_scripts_dir: "/usr/local/bin" +listupgrade_scripts_dir: Null diff --git a/listupgrade/tasks/main.yml b/listupgrade/tasks/main.yml new file mode 100644 index 00000000..e2f67284 --- /dev/null +++ b/listupgrade/tasks/main.yml @@ -0,0 +1,39 @@ +--- +- name: Scripts dir is present + file: + path: "{{ evomaintenance_scripts_dir or general_scripts_dir | mandatory }}" + state: directory + mode: 0700 + +- name: Copy listupgrade script + template: + src: listupgrade.sh.j2 + dest: "{{ evomaintenance_scripts_dir or general_scripts_dir | mandatory }}/listupgrade.sh" + mode: 0700 + owner: root + group: root + force: yes + backup: yes + +- name: Create /etc/evolinux + file: + path: /etc/evolinux + state: directory + mode: 0600 + +- name: Copy listupgrade config + template: + src: listupgrade.cnf.j2 + dest: /etc/evolinux/listupgrade.cnf + mode: 0600 + owner: root + group: root + force: no + +- name: Enable listupgrade cron + template: + src: listupgrade_cron.j2 + dest: /etc/cron.d/listupgrade + mode: 0600 + owner: root + group: root diff --git a/listupgrade/templates/listupgrade.cnf.j2 b/listupgrade/templates/listupgrade.cnf.j2 new file mode 100644 index 00000000..99b00362 --- /dev/null +++ b/listupgrade/templates/listupgrade.cnf.j2 @@ -0,0 +1,4 @@ +#date="Ce jeudi entre 18h00 et 23h00." +#clientmail="client@evolix.net" +#mailto="{{ listupgrade_alert_email or general_alert_email | mandatory }}" +#hostname="" diff --git a/listupgrade/templates/listupgrade.sh.j2 b/listupgrade/templates/listupgrade.sh.j2 new file mode 100644 index 00000000..46d5c005 --- /dev/null +++ b/listupgrade/templates/listupgrade.sh.j2 @@ -0,0 +1,217 @@ +#!/bin/bash + +# Exit codes : +# - 30 : $skip_releases or $skip_packages is set to "all" +# - 40 : current release is in $skip_releases list +# - 50 : all upgradable packages are in the $skip_packages list +# - 60 : current release is not in the $r_releases list +# - 70 : at least an upgradable package is not in the $r_packages list + +set -e + +configFile="/etc/evolinux/listupgrade.cnf" + +packages=$(mktemp --tmpdir=/tmp evoupdate.XXX) +packagesHold=$(mktemp --tmpdir=/tmp evoupdate.XXX) +servicesToRestart=$(mktemp --tmpdir=/tmp evoupdate.XXX) +template=$(mktemp --tmpdir=/tmp evoupdate.XXX) +clientmail=$(grep EVOMAINTMAIL /etc/evomaintenance.cf | cut -d'=' -f2) +mailto=$clientmail +date="Ce jeudi entre 18h00 et 23h00." +hostname=$(grep HOSTNAME /etc/evomaintenance.cf | cut -d'=' -f2) +hostname=${hostname%%.evolix.net} +# If hostname is composed with -, remove the first part. +if [[ $hostname =~ "-" ]]; then + hostname=$(echo $hostname | cut -d'-' -f2-) +fi +# Edit $configFile to override some variables. +[ -r $configFile ] && . $configFile + +# Remove temporary files on exit. +trap "rm $packages $packagesHold $servicesToRestart $template" EXIT + +# Parse line in retrieved upgrade file and ensure there is no malicious values. +get_value() { + file="$1" + variable="$2" + value="$(grep "^$2:" $1 |head -n 1 |cut -d ':' -f 2 |sed 's/^ //')" + if echo "$value" |grep -q -E '^[-.: [:alnum:]]*$'; then + echo $value + else + printf >&2 "Error parsing value \"$value\" for variable $variables.\n" + fi +} + +# Fetch which packages/releases will be upgraded. +fetch_upgrade_info() { + upgradeInfo=$(mktemp --tmpdir=/tmp evoupdate.XXX) + wget -q -O $upgradeInfo https://upgrades.evolix.org/upgrade + r_releases="$(get_value $upgradeInfo "releases")" + r_skip_releases="$(get_value $upgradeInfo "skip_releases")" + r_packages="$(get_value $upgradeInfo "packages")" + r_skip_packages="$(get_value $upgradeInfo "skip_packages")" + rm $upgradeInfo +} + +# Check if element $element is in (space separated) list $list. +is_in() { + list="$1" + element="$2" + + for i in $list; do + if [ "$element" = "$i" ]; then + return 0 + fi + done + return 1 +} + + +if [[ "$1" != "--cron" ]]; then + echo "À quel date/heure allez vous planifier l'envoi ?" + echo "Exemple : le jeudi 6 mars entre 18h00 et 23h00" + echo -n ">" + read date + echo "À qui envoyer le mail ?" + echo -n ">" + read mailto +fi + +# Update APT cache and get packages to upgrade and packages on hold. +apt -q2 update 2>&1 | (egrep -ve '^(Listing|WARNING|$)' -e upgraded -e 'up to date' || true ) +apt-mark showhold > $packagesHold +apt list --upgradable 2>&1 | grep -v -f $packagesHold | egrep -v '^(Listing|WARNING|$)' > $packages +packagesParsable=$(cut -f 1 -d / <$packages |tr '\n' ' ') + +# No updates? Exit! +test ! -s $packages && exit 0 +test ! -s $packagesHold && echo 'Aucun' > $packagesHold + +fetch_upgrade_info +local_release=$(cut -f 1 -d . >$servicesToRestart + elif echo "$pkg" |grep -q "^nginx"; then + echo "Nginx" >>$servicesToRestart + elif echo "$pkg" |grep -q "^php5-fpm"; then + echo "PHP FPM" >>$servicesToRestart + elif echo "$pkg" |grep -q "^mysql-server"; then + echo "MySQL" >>$servicesToRestart + elif echo "$pkg" |grep -q "^mariadb-server"; then + echo "MariaDB" >>$servicesToRestart + elif echo "$pkg" |grep -qE "^postgresql-[[:digit:]]+\.[[:digit:]]+$"; then + echo "PostgreSQL" >>$servicesToRestart + elif echo "$pkg" |grep -qE "^tomcat[[:digit:]]+$"; then + echo "Tomcat" >>$servicesToRestart + elif [ "$pkg" = "redis-server" ]; then + echo "redis-server" >>$servicesToRestart + elif [ "$pkg" = "mongodb-server" ]; then + echo "redis-server" >>$servicesToRestart + elif echo "$pkg" |grep -qE "^courier-(pop|imap)"; then + echo "Courier POP/IMAP" >>$servicesToRestart + elif echo "$pkg" |grep -qE "^dovecot-(pop|imap)d"; then + echo "Dovecot POP/IMAP" >>$servicesToRestart + elif [ "$pkg" = "samba" ]; then + echo "Samba" >>$servicesToRestart + elif [ "$pkg" = "slapd" ]; then + echo "OpenLDAP" >>$servicesToRestart + elif [ "$pkg" = "bind9" ]; then + echo "Bind9" >>$servicesToRestart + elif [ "$pkg" = "postfix" ]; then + echo "Postfix" >>$servicesToRestart + elif [ "$pkg" = "haproxy" ]; then + echo "HAProxy" >>$servicesToRestart + elif [ "$pkg" = "varnish" ]; then + echo "Varnish" >>$servicesToRestart + + elif [ "$pkg" = "libc6" ]; then + echo "Tous les services (mise à jour de libc6)." >$servicesToRestart + break + elif echo "$pkg" |grep -q "^libssl"; then + echo "Tous les services (mise à jour de libssl)." >$servicesToRestart + break + fi +done +test ! -s $servicesToRestart && echo "Aucun" >$servicesToRestart + +cat << EOT > $template +Content-Type: text/plain; charset="utf-8" +Reply-To: equipe@evolix.fr +From: equipe@evolix.net +To: ${clientmail} +Subject: Prochain creneau pour mise a jour de votre serveur $hostname +X-Debian-Release: $local_release +X-Packages: $packagesParsable +X-Date: $date + +Bonjour, + +Des mises-à-jour de sécurité ou mineures sont à réaliser sur votre serveur +${hostname}. +Sauf indication contraire de votre part, le prochain créneau prévu pour +intervenir manuellement pour réaliser ces mises-à-jour est : +${date} + +Si nous intervenons, un redémarrage des éventuels services concernés sera +réalisé, entraînant a priori quelques secondes de coupure. Si nous ne sommes +pas intervenus sur ce créneau, vous recevrez une nouvelle notification la +semaine prochaine. + +Voici la listes de packages qui seront mis à jour : + +$(cat $packages) + +Liste des packages dont la mise-à-jour a été manuellement suspendue : + +$(cat $packagesHold) + +Liste des services qui seront redémarrés : + +$(cat $servicesToRestart) + +N'hésitez pas à nous faire toute remarque sur ce créneau d'intervention le plus +tôt possible. + +Cordialement, +-- +Équipe Evolix +Evolix - Hébergement et Infogérance Open Source http://www.evolix.fr/ +EOT + +<$template /usr/sbin/sendmail $mailto diff --git a/listupgrade/templates/listupgrade_cron.j2 b/listupgrade/templates/listupgrade_cron.j2 new file mode 100644 index 00000000..0e284d65 --- /dev/null +++ b/listupgrade/templates/listupgrade_cron.j2 @@ -0,0 +1 @@ +42 9 * * 2 root {{ listupgrade_scripts_dir or general_scripts_dir | mandatory }}/listupgrade.sh --cron diff --git a/logstash/defaults/main.yml b/logstash/defaults/main.yml index e853af53..2d3c7b40 100644 --- a/logstash/defaults/main.yml +++ b/logstash/defaults/main.yml @@ -1,2 +1,3 @@ +--- logstash_jvm_xms: 256m logstash_jvm_xmx: 1g diff --git a/minifirewall/README.md b/minifirewall/README.md new file mode 100644 index 00000000..2d775a6c --- /dev/null +++ b/minifirewall/README.md @@ -0,0 +1,19 @@ +# minifirewall + +Install minifirewall a simple and versatile local firewall. + +The firewall is not started by default, but an init script is installed. + +## Tasks + +Everything is in the `tasks/main.yml` file. + +## Available variables + +* `minifirewall_int`: which network interface to protect (default: detected default ipv4 interface) +* `minifirewall_ipv6_enabled`: (default: `on`) +* `minifirewall_int_lan`: (default: IP/32) +* `minifirewall_trusted_ips`: with IP/hosts should be trusted for full access (default: none) +* `minifirewall_privilegied_ips`: with IP/hosts should be trusted for restricted access (default: none) + +Some IP/hosts must be configured or the server will be inaccessible via network. diff --git a/minifirewall/defaults/main.yml b/minifirewall/defaults/main.yml new file mode 100644 index 00000000..6eb9ca02 --- /dev/null +++ b/minifirewall/defaults/main.yml @@ -0,0 +1,8 @@ +--- +minifirewall_git_url: "https://forge.evolix.org/minifirewall.git" +minifirewall_checkout_path: "/usr/local/src/minifirewall" +minifirewall_int: "{{ ansible_default_ipv4.interface }}" +minifirewall_ipv6: "on" +minifirewall_intlan: "{{ ansible_default_ipv4.address }}/32" +minifirewall_trusted_ips: [] +minifirewall_privilegied_ips: [] diff --git a/minifirewall/tasks/main.yml b/minifirewall/tasks/main.yml new file mode 100644 index 00000000..a5ce72a9 --- /dev/null +++ b/minifirewall/tasks/main.yml @@ -0,0 +1,42 @@ +--- + +- name: clone git repository + git: + repo: "{{ minifirewall_git_url}}" + dest: "{{ minifirewall_checkout_path }}" + clone: yes + +# WARN: these tasks copy the file if there are not already there +# They don't update files. + +- name: is init script present? + stat: + path: /etc/init.d/minifirewall + register: init_minifirewall + +- name: init script is copied + command: "cp {{ minifirewall_checkout_path }}/minifirewall /etc/init.d/minifirewall" + when: not init_minifirewall.stat.exists + + +- name: is configuration present? + stat: + path: /etc/default/minifirewall + register: default_minifirewall + +- block: + - name: configuration is copied + command: "cp {{ minifirewall_checkout_path }}/minifirewall.conf /etc/default/minifirewall" + + - name: configuraion is customized + replace: + dest: /etc/default/minifirewall + regexp: '{{ item.regexp }}' + replace: '{{ item.replace }}' + with_items: + - { regexp: "^(INT)='.*'", replace: "\\1='{{ minifirewall_int }}'" } + - { regexp: "^(INTLAN)='.*'", replace: "\\1='{{ minifirewall_intlan }}'" } + - { regexp: "^(IPV6)='.*'", replace: "\\1='{{ minifirewall_ipv6 }}'" } + - { regexp: "^(TRUSTEDIPS)='.*'", replace: "\\1='{{ minifirewall_trusted_ips | join(' ') }}'" } + - { regexp: "^(PRIVILEGIEDIPS)='.*'", replace: "\\1='{{ minifirewall_privilegied_ips | join(' ') }}'" } + when: not default_minifirewall.stat.exists diff --git a/monit/defaults/main.yml b/monit/defaults/main.yml index d306312a..39d28f24 100644 --- a/monit/defaults/main.yml +++ b/monit/defaults/main.yml @@ -1,3 +1,4 @@ +--- monit_daemon_time: 60 monit_alert_dest: monit_httpd_enable: true diff --git a/monit/tasks/main.yml b/monit/tasks/main.yml index da86b4bd..604667a5 100644 --- a/monit/tasks/main.yml +++ b/monit/tasks/main.yml @@ -10,8 +10,8 @@ - name: custom config is installed template: - src: custom.j2 - dest: /etc/monit/conf.d/custom + src: custom.conf.j2 + dest: /etc/monit/conf.d/custom.conf mode: 0640 force: yes notify: restart monit diff --git a/monit/templates/custom.j2 b/monit/templates/custom.conf.j2 similarity index 100% rename from monit/templates/custom.j2 rename to monit/templates/custom.conf.j2 diff --git a/munin/tasks/main.yml b/munin/tasks/main.yml index 38cbfd95..572d6547 100644 --- a/munin/tasks/main.yml +++ b/munin/tasks/main.yml @@ -66,7 +66,22 @@ src: /usr/share/munin/plugins/sensors_ dest: /etc/munin/plugins/sensors_temp state: link - when: not ansible_virtualization_role == "guest" + when: ansible_virtualization_role != "guest" notify: restart munin-node tags: - munin + +- name: adjustments for grsec kernel + blockinfile: + dest: /etc/munin/plugin-conf.d/munin-node + block: | + + [processes] + user root + + [vmstat] + user root + + [swap] + user root + when: ansible_kernel | search("-grs-") diff --git a/mysql/README.md b/mysql/README.md index d4bd34ee..48652b4c 100644 --- a/mysql/README.md +++ b/mysql/README.md @@ -24,5 +24,9 @@ Tasks are extracted in several files, included in `tasks/main.yml` : * `mysql_innodb_buffer_pool_size`: amount of RAM dedicated to InnoDB ; * `mysql_custom_datadir`: custom datadir * `mysql_custom_tmpdir`: custom tmpdir. +* `general_alert_email`: email address to send various alert messages (default: `root@localhost`). +* `log2mail_alert_email`: email address to send Log2mail messages to (default: `general_alert_email`). +* `general_scripts_dir`: general directory for scripts installation (default: `/usr/local/bin`). +* `mysql_scripts_dir`: email address to send Log2mail messages to (default: `general_scripts_dir`). NB : changing the _datadir_ location can be done multiple times, as long as it is not restored to the default initial location, (because a symlink is created and can't be switched back, yet). diff --git a/mysql/defaults/main.yml b/mysql/defaults/main.yml index 58cb3a52..c59f3f1c 100644 --- a/mysql/defaults/main.yml +++ b/mysql/defaults/main.yml @@ -1,3 +1,10 @@ +--- +general_alert_email: "root@localhost" +log2mail_alert_email: Null + +general_scripts_dir: "/usr/local/bin" +mysql_scripts_dir: Null + mysql_use_mariadb: False mysql_replace_root_with_mysqladmin: True @@ -7,7 +14,3 @@ mysql_custom_tmpdir: '' mysql_thread_cache_size: '{{ ansible_processor_cores }}' mysql_innodb_buffer_pool_size: '{{ (ansible_memtotal_mb * 0.3) | int }}M' - -default_alert_mail: root -general_alert_mail: Null -log2mail_alert_mail: Null diff --git a/mysql/files/z_evolinux.cnf b/mysql/files/evolinux-defaults.cnf similarity index 100% rename from mysql/files/z_evolinux.cnf rename to mysql/files/evolinux-defaults.cnf diff --git a/mysql/files/mysql-optimize b/mysql/files/mysql-optimize.sh similarity index 100% rename from mysql/files/mysql-optimize rename to mysql/files/mysql-optimize.sh diff --git a/mysql/files/zzz_evolinux.cnf b/mysql/files/zzz_evolinux.cnf deleted file mode 100644 index e3d6334a..00000000 --- a/mysql/files/zzz_evolinux.cnf +++ /dev/null @@ -1,2 +0,0 @@ -[mysqld] -#bind-address = 0.0.0.0 diff --git a/mysql/tasks/config.yml b/mysql/tasks/config.yml index 8388a022..617f846d 100644 --- a/mysql/tasks/config.yml +++ b/mysql/tasks/config.yml @@ -1,35 +1,22 @@ --- -- name: Is zzz_evolinux.cnf already present ? - stat: - path: /etc/mysql/conf.d/zzz_evolinux.cnf - register: mysql_zzz_evolinux - tags: - - mysql - - log2mail - -- name: Copy MySQL config files +- name: Copy MySQL defaults config file copy: - src: "{{ item.file }}" - dest: "/etc/mysql/conf.d/{{ item.file }}" + src: evolinux-defaults.cnf + dest: /etc/mysql/conf.d/z-evolinux-defaults.cnf owner: root group: root - mode: "{{ item.mode }}" - force: no - with_items: - - { file: z_evolinux.cnf, mode: 0644 } - - { file: zzz_evolinux.cnf, mode: 0640 } + mode: 0644 + force: yes tags: - mysql -- name: Custom MySQL config - ini_file: - dest: /etc/mysql/conf.d/zzz_evolinux.cnf - section: mysqld - option: '{{ item.option }}' - value: '{{ item.value }}' - with_items: - - { option: thread_cache_size, value: '{{ mysql_thread_cache_size }}' } - - { option: innodb_buffer_pool_size, value: '{{ mysql_innodb_buffer_pool_size }}' } - when: mysql_zzz_evolinux.stat.exists == False +- name: Copy MySQL custom config file + template: + src: evolinux-custom.cnf.j2 + dest: /etc/mysql/conf.d/zzz-evolinux-custom.cnf + owner: root + group: root + mode: 0640 + force: no tags: - mysql diff --git a/mysql/tasks/log2mail.yml b/mysql/tasks/log2mail.yml index b0fbd547..f4e4c2e0 100644 --- a/mysql/tasks/log2mail.yml +++ b/mysql/tasks/log2mail.yml @@ -9,8 +9,8 @@ - log2mail - name: Copy log2mail config - copy: - src: log2mail.conf + template: + src: log2mail.j2 dest: /etc/log2mail/config/mysql.conf mode: 0640 when: log2mail_config_dir.stat.exists diff --git a/mysql/tasks/users.yml b/mysql/tasks/users.yml index 8add6ef0..7d861df6 100644 --- a/mysql/tasks/users.yml +++ b/mysql/tasks/users.yml @@ -2,7 +2,7 @@ # dependency for mysql_user and mysql_db -- name: python-mysqldb is installed +- name: python-mysqldb is installed (Ansible dependency) apt: name: python-mysqldb state: installed diff --git a/mysql/tasks/utils.yml b/mysql/tasks/utils.yml index a831f71e..b66c5a3b 100644 --- a/mysql/tasks/utils.yml +++ b/mysql/tasks/utils.yml @@ -31,8 +31,8 @@ - name: Install mysqltuner copy: src: mysqltuner.pl - dest: /usr/local/bin/mysqltuner.pl - mode: 700 + dest: "{{ mysql_scripts_dir or general_scripts_dir | mandatory }}/mysqltuner.pl" + mode: 0700 tags: - mysql - mysqltuner @@ -49,7 +49,7 @@ - name: Weekly cron to optimize MySQL copy: src: mysql-optimize - dest: /etc/cron.monthly/mysql-optimize + dest: /etc/cron.weekly/mysql-optimize.sh mode: 0755 tags: - mysql @@ -73,7 +73,7 @@ - name: Install my-add.sh copy: src: my-add.sh - dest: /usr/local/bin/my-add.sh + dest: "{{ mysql_scripts_dir or general_scripts_dir | mandatory }}/my-add.sh" mode: 0700 tags: - mysql diff --git a/mysql/templates/evolinux-custom.cnf.j2 b/mysql/templates/evolinux-custom.cnf.j2 new file mode 100644 index 00000000..fa818eaf --- /dev/null +++ b/mysql/templates/evolinux-custom.cnf.j2 @@ -0,0 +1,4 @@ +[mysqld] +#bind-address = 0.0.0.0 +thread_cache_size = {{ mysql_thread_cache_size }} +innodb_buffer_pool_size = {{ mysql_innodb_buffer_pool_size }} diff --git a/mysql/files/log2mail.conf b/mysql/templates/log2mail.j2 similarity index 53% rename from mysql/files/log2mail.conf rename to mysql/templates/log2mail.j2 index e1fa3a68..948c7f07 100644 --- a/mysql/files/log2mail.conf +++ b/mysql/templates/log2mail.j2 @@ -1,24 +1,24 @@ file = /var/log/syslog pattern = "is marked as crashed and should be repaired" -mailto = {{ log2mail_alert_email | general_alert_email | default_alert_email }} +mailto = {{ log2mail_alert_email or general_alert_email | mandatory }} template = /etc/log2mail/mail file = /var/log/syslog pattern = "init function returned error" -mailto = {{ log2mail_alert_email | general_alert_email | default_alert_email }} +mailto = {{ log2mail_alert_email or general_alert_email | mandatory }} template = /etc/log2mail/mail file = /var/log/syslog pattern = "try to repair it" -mailto = {{ log2mail_alert_email | general_alert_email | default_alert_email }} +mailto = {{ log2mail_alert_email or general_alert_email | mandatory }} template = /etc/log2mail/mail file = /var/log/syslog pattern = "InnoDB: Fatal error" -mailto = {{ log2mail_alert_email | general_alert_email | default_alert_email }} +mailto = {{ log2mail_alert_email or general_alert_email | mandatory }} template = /etc/log2mail/mail file = /var/log/syslog pattern = "as a STORAGE ENGINE failed" -mailto = {{ log2mail_alert_email | general_alert_email | default_alert_email }} +mailto = {{ log2mail_alert_email or general_alert_email | mandatory }} template = /etc/log2mail/mail diff --git a/nagios-nrpe/README.md b/nagios-nrpe/README.md new file mode 100644 index 00000000..05de2d6c --- /dev/null +++ b/nagios-nrpe/README.md @@ -0,0 +1,13 @@ +# nagios-nrpe + +Install Nagios NRPE server. + +## Tasks + +Everything is in the `tasks/main.yml` file. + +## Available variables + +* `nagios_nrpe_allowed_hosts` : list of IP/hosts authorized (default: none). + +The full list of variables (with default values) can be found in `defaults/main.yml`. diff --git a/nagios-nrpe/defaults/main.yml b/nagios-nrpe/defaults/main.yml new file mode 100644 index 00000000..4ad1e504 --- /dev/null +++ b/nagios-nrpe/defaults/main.yml @@ -0,0 +1,7 @@ +--- +nagios_nrpe_allowed_hosts: [] +nagios_nrpe_ldap_dc: "dc=DOMAIN,dc=EXT" +nagios_nrpe_ldap_passwd: LDAP_PASSWD +nagios_nrpe_pgsql_passwd: PGSQL_PASSWD +nagios_nrpe_mysql_passwd: MYSQL_PASSWD +nagios_nrpe_amavis_from: "foobar@{{ ansible_domain }}" diff --git a/nagios-nrpe/files/plugins/check_amavis b/nagios-nrpe/files/plugins/check_amavis new file mode 100755 index 00000000..43e69bcb --- /dev/null +++ b/nagios-nrpe/files/plugins/check_amavis @@ -0,0 +1,72 @@ +#!/usr/bin/perl + +use Getopt::Long; +use MIME::Entity; +use Net::SMTP; + +my $server = ''; +my $port = 10024; +my $from = ''; +my $to = ''; +my $debug = 0; + +$result = GetOptions ( + "server|s=s" => \$server, + "port|p=s" => \$port, + "from|f=s" => \$from, + "debug|d" => \$debug, + "to|t=s" => \$to, +); + +if (!$server || !$from) { + die ("Please specify server, from\n"); +} + +if (!$to) { $to = $from; } + +my $EICAR = <<'EOF'; +X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H* +EOF + +my $top = MIME::Entity->build( + Type =>"multipart/mixed", + From => $from, + To => $to, + Subject => "EICAR test", + Data => "This is a test", +); + +$top->attach( + Data => $EICAR, + Type => "application/x-msdos-program", + Encoding => "base64"); + +my $smtp = new Net::SMTP( + $server, + Port => $port, + Debug => $debug, +); + +if (!$smtp) { + print "CRITICAL - amavisd-new server unreachable\n"; + exit 2; +} + +$smtp->mail($from); +$smtp->to($to); +$smtp->data(); +$smtp->datasend($top->stringify); +$smtp->dataend(); +my $result = $smtp->message(); +$smtp->close(); + +print "$result\n"; + +if ($result =~/2.7.0 Ok, discarded/) { + print "OK - All fine\n"; + exit 0; +} else { + print "CRITICAL - amavisd-new returned $result"; + exit 2; +} + diff --git a/nagios-nrpe/files/plugins/check_cpu b/nagios-nrpe/files/plugins/check_cpu new file mode 100755 index 00000000..a26fa61f --- /dev/null +++ b/nagios-nrpe/files/plugins/check_cpu @@ -0,0 +1,219 @@ +#!/bin/bash +# ======================================================================================== +# CPU Utilization Statistics plugin for Nagios +# +# Written by : Andreas Baess based on a script by Steve Bosek +# Release : 1.1 +# Creation date : 3 May 2008 +# Package : DTB Nagios Plugin +# Description : Nagios plugin (script) to check cpu utilization statistics. +# This script has been designed and written on Unix plateform (Linux, Aix, Solaris), +# requiring iostat as external program. The locations of these can easily +# be changed by editing the variables $IOSTAT at the top of the script. +# The script is used to query 4 of the key cpu statistics (user,system,iowait,idle) +# at the same time. +# +# Usage : ./check_cpu.sh [-w ] [-c ] [-uc ] +# [-sw ] [-sc ] +# [-iw ] [-ic ] +# [-i ] [-n ] +# ---------------------------------------------------------------------------------------- +# ======================================================================================== +# +# HISTORY : +# Release | Date | Authors | Description +# --------------+---------------+---------------+------------------------------------------ +# 1.1 | 03.05.08 | Andreas Baess | Changed script to use vmstat on Linux because +# | | | iostat does not use integers +# | | | Fixed output to display the IO-wait warning threshhold +# --------------+---------------+---------------+------------------------------------------ +# 1.0 | 03.05.08 | Andreas Baess | Changed script so that thresholds are global +# | | | and output can be parsed by perfprocessing +# | | | changed default warning to 70 critical to 90 +# ========================================================================================= + +# Paths to commands used in this script. These may have to be modified to match your system setup. + +IOSTAT=/usr/bin/iostat + +# Nagios return codes +STATE_OK=0 +STATE_WARNING=1 +STATE_CRITICAL=2 +STATE_UNKNOWN=3 + +# Plugin parameters value if not define +WARNING_THRESHOLD=${WARNING_THRESHOLD:="70"} +CRITICAL_THRESHOLD=${CRITICAL_THRESHOLD:="90"} +INTERVAL_SEC=${INTERVAL_SEC:="1"} +NUM_REPORT=${NUM_REPORT:="3"} +U_CPU_W=${WARNING_THRESHOLD} +S_CPU_W=${WARNING_THRESHOLD} +IO_CPU_W=${WARNING_THRESHOLD} +U_CPU_C=${CRITICAL_THRESHOLD} +S_CPU_C=${CRITICAL_THRESHOLD} +IO_CPU_C=${CRITICAL_THRESHOLD} + +# Plugin variable description +PROGNAME=$(basename $0) +RELEASE="Revision 1.0" +AUTHOR="by Andreas Baess based on a work from Steve Bosek (sbosek@mac.com)" + +if [ ! -x $IOSTAT ]; then + echo "UNKNOWN: iostat not found or is not executable by the nagios user." + exit $STATE_UNKNOWN +fi + +# Functions plugin usage +print_release() { + echo "$RELEASE $AUTHOR" +} + +print_usage() { + echo "" + echo "$PROGNAME $RELEASE - CPU Utilization check script for Nagios" + echo "" + echo "Usage: check_cpu.sh [flags]" + echo "" + echo "Flags:" + echo " -w : Global Warning level in % for user/system/io-wait cpu" + echo " -uw : Warning level in % for user cpu" + echo " -iw : Warning level in % for IO_wait cpu" + echo " -sw : Warning level in % for system cpu" + echo " -c : Global Critical level in % for user/system/io-wait cpu" + echo " -uc : Critical level in % for user cpu" + echo " -ic : Critical level in % for IO_wait cpu" + echo " -sc : Critical level in % for system cpu" + echo " -i : Interval in seconds for iostat (default : 1)" + echo " -n : Number report for iostat (default : 3)" + echo " -h Show this page" + echo "" + echo "Usage: $PROGNAME" + echo "Usage: $PROGNAME --help" + echo "" +} + +print_help() { + print_usage + echo "" + echo "This plugin will check cpu utilization (user,system,iowait,idle in %)" + echo "" + exit 0 +} + +# Parse parameters +while [ $# -gt 0 ]; do + case "$1" in + -h | --help) + print_help + exit $STATE_OK + ;; + -v | --version) + print_release + exit $STATE_OK + ;; + -w | --warning) + shift + WARNING_THRESHOLD=$1 + U_CPU_W=$1 + S_CPU_W=$1 + IO_CPU_W=$1 + ;; + -c | --critical) + shift + CRITICAL_THRESHOLD=$1 + U_CPU_C=$1 + S_CPU_C=$1 + IO_CPU_C=$1 + ;; + -uw | --uwarn) + shift + U_CPU_W=$1 + ;; + -uc | --ucrit) + shift + U_CPU_C=$1 + ;; + -sw | --swarn) + shift + S_CPU_W=$1 + ;; + -sc | --scrit) + shift + S_CPU_C=$1 + ;; + -iw | --iowarn) + shift + IO_CPU_W=$1 + ;; + -ic | --iocrit) + shift + IO_CPU_C=$1 + ;; + -i | --interval) + shift + INTERVAL_SEC=$1 + ;; + -n | --number) + shift + NUM_REPORT=$1 + ;; + *) echo "Unknown argument: $1" + print_usage + exit $STATE_UNKNOWN + ;; + esac +shift +done + +# CPU Utilization Statistics Unix Plateform ( Linux,AIX,Solaris are supported ) +case `uname` in +# Linux ) CPU_REPORT=`iostat -c $INTERVAL_SEC $NUM_REPORT | tr -s ' ' ';' | sed '/^$/d' | tail -1` +# CPU_USER=`echo $CPU_REPORT | cut -d ";" -f 2` +# CPU_SYSTEM=`echo $CPU_REPORT | cut -d ";" -f 4` +# CPU_IOWAIT=`echo $CPU_REPORT | cut -d ";" -f 5` +# CPU_IDLE=`echo $CPU_REPORT | cut -d ";" -f 6` +# ;; + AIX ) CPU_REPORT=`iostat -t $INTERVAL_SEC $NUM_REPORT | sed -e 's/,/./g'|tr -s ' ' ';' | tail -1` + CPU_USER=`echo $CPU_REPORT | cut -d ";" -f 4` + CPU_SYSTEM=`echo $CPU_REPORT | cut -d ";" -f 5` + CPU_IOWAIT=`echo $CPU_REPORT | cut -d ";" -f 7` + CPU_IDLE=`echo $CPU_REPORT | cut -d ";" -f 6` + ;; + Linux ) CPU_REPORT=`vmstat -n $INTERVAL_SEC $NUM_REPORT | tail -1` + CPU_USER=`echo $CPU_REPORT | awk '{ print $13 }'` + CPU_SYSTEM=`echo $CPU_REPORT | awk '{ print $14 }'` + CPU_IOWAIT=`echo $CPU_REPORT | awk '{ print $16 }'` + CPU_IDLE=`echo $CPU_REPORT | awk '{ print $15 }'` + ;; + SunOS ) CPU_REPORT=`iostat -c $INTERVAL_SEC $NUM_REPORT | tail -1` + CPU_USER=`echo $CPU_REPORT | awk '{ print $1 }'` + CPU_SYSTEM=`echo $CPU_REPORT | awk '{ print $2 }'` + CPU_IOWAIT=`echo $CPU_REPORT | awk '{ print $3 }'` + CPU_IDLE=`echo $CPU_REPORT | awk '{ print $4 }'` + ;; + *) echo "UNKNOWN: `uname` not yet supported by this plugin. Coming soon !" + exit $STATE_UNKNOWN + ;; + esac + +# Return + +# Are we in a critical state? +if [ ${CPU_IOWAIT} -ge ${IO_CPU_C} -o ${CPU_USER} -ge ${U_CPU_C} -o ${CPU_SYSTEM} -ge ${S_CPU_C} ]; +then + echo "CPU CRITICAL : user=${CPU_USER}% system=${CPU_SYSTEM}% iowait=${CPU_IOWAIT}% idle=${CPU_IDLE}% | cpu_user=${CPU_USER}%;${U_CPU_W};${U_CPU_C}; cpu_sys=${CPU_SYSTEM}%;${S_CPU_W};${S_CPU_C}; cpu_iowait=${CPU_IOWAIT}%;${IO_CPU_W};${IO_CPU_C}; cpu_idle=${CPU_IDLE}%;" + exit $STATE_CRITICAL +fi + +# Are we in a warning state? +if [ ${CPU_IOWAIT} -ge ${IO_CPU_W} -o ${CPU_USER} -ge ${U_CPU_W} -o ${CPU_SYSTEM} -ge ${S_CPU_W} ]; +then + echo "CPU WARNING : user=${CPU_USER}% system=${CPU_SYSTEM}% iowait=${CPU_IOWAIT}% idle=${CPU_IDLE}% | cpu_user=${CPU_USER}%;${U_CPU_W};${U_CPU_C}; cpu_sys=${CPU_SYSTEM}%;${S_CPU_W};${S_CPU_C}; cpu_iowait=${CPU_IOWAIT}%;${IO_CPU_W};${IO_CPU_C}; cpu_idle=${CPU_IDLE}%;" + exit $STATE_WARNING +fi + +# If we got this far, everything seems to be OK - IDLE has no threshold +echo "CPU OK : user=${CPU_USER}% system=${CPU_SYSTEM}% iowait=${CPU_IOWAIT}% idle=${CPU_IDLE}% | cpu_user=${CPU_USER}%;${U_CPU_W};${U_CPU_C}; cpu_sys=${CPU_SYSTEM}%;${S_CPU_W};${S_CPU_C}; cpu_iowait=${CPU_IOWAIT}%;${IO_CPU_W};${IO_CPU_C}; cpu_idle=${CPU_IDLE}%;" +exit $STATE_OK diff --git a/nagios-nrpe/files/plugins/check_daemon b/nagios-nrpe/files/plugins/check_daemon new file mode 100755 index 00000000..67e62c6c --- /dev/null +++ b/nagios-nrpe/files/plugins/check_daemon @@ -0,0 +1,64 @@ +#!/bin/sh + +usage() { + cat <&2; exit 1; fi +eval set -- "$TEMP" + +OPT_USER="" +OPT_SANE=0 +OPT_UNIQ=0 + +while true; do + case "$1" in + -u|--user) OPT_USER="$2" ; shift 2 ;; + -U|--uniq) OPT_UNIQ=1; shift ;; + -s|--sane) OPT_SANE=1; shift ;; + -h|--help) usage; exit 0 ;; + --) shift; break ;; + *) echo "Erreur interne"; exit -1 ;; + esac +done + +ID_USER="`getent passwd $OPT_USER | cut -d: -f3`" + +if [ $# -ne 1 ]; then usage; exit -1; fi + +NAME="$1" +STATUS=0 +RES="`ps n -eo pid,user,state,command | egrep "$NAME" | egrep "$ID_USER " | egrep -v "grep|$0"`" +P_USER="` echo $RES | cut -d' ' -f2`" +P_STATE="` echo $RES | cut -d' ' -f3`" + +if [ -n "$RES" ]; then + if [ -n "$OPT_USER" ]; then + if [ "`getent passwd $OPT_USER`" != "`getent passwd $P_USER`" ]; then + STATUS=2 + fi + fi + if [ $OPT_SANE -eq 1 -a "$P_STATE" = "Z" ]; then + STATUS=2 + fi + if [ $OPT_UNIQ -eq 1 ]; then + if [ `echo -e "$RES" |wc -l` -gt 1 ]; then + STATUS=2 + fi + fi +else + STATUS=2 +fi + +[ $STATUS -eq 0 ] && echo "$NAME running" + +exit $STATUS diff --git a/nagios-nrpe/files/plugins/check_drbd b/nagios-nrpe/files/plugins/check_drbd new file mode 100755 index 00000000..aeda397a --- /dev/null +++ b/nagios-nrpe/files/plugins/check_drbd @@ -0,0 +1,456 @@ +#!/usr/bin/perl -w + +#################################################### +# check_drbd v0.5.3 # +# by Brandon Lee Poyner bpoyner / CCAC.edu # +#################################################### + +use strict; +use File::Basename; +use Getopt::Long; + +my $drbd_proc='/proc/drbd'; +my $drbd_devices=0; +my ($drbd_expect, $drbd_role, $drbd_version, $debug_mode); +my (%options, %cs, %st, %ld, %ds, %check, %warning, %critical); + +my $prog_name=basename($0); +my $prog_revision='0.5.3'; + +my %errorcodes = ( + 'OK' => { 'retvalue' => 0 }, + 'WARNING' => { 'retvalue' => 1 }, + 'CRITICAL' => { 'retvalue' => 2 }, + 'UNKNOWN' => { 'retvalue' => 3 } +); + +# +# Define various states and default alarm values +# +my %state = ( + 'Primary' => { 'value' => 'OK', 'type' => 'st' }, + 'Secondary' => { 'value' => 'OK', 'type' => 'st' }, + 'Unknown' => { 'value' => 'CRITICAL', 'type' => 'st' }, + 'StandAlone' => { 'value' => 'WARNING', 'type' => 'cs' }, + 'Unconnected' => { 'value' => 'CRITICAL', 'type' => 'cs' }, + 'Timeout' => { 'value' => 'CRITICAL', 'type' => 'cs' }, + 'BrokenPipe' => { 'value' => 'CRITICAL', 'type' => 'cs' }, + 'WFConnection' => { 'value' => 'CRITICAL', 'type' => 'cs' }, + 'WFReportParams' => { 'value' => 'CRITICAL', 'type' => 'cs' }, + 'Connected' => { 'value' => 'OK', 'type' => 'cs' }, + 'Unconfigured' => { 'value' => 'CRITICAL', 'type' => 'cs' }, + # DRBD 0.6 + 'SyncingAll' => { 'value' => 'WARNING', 'type' => 'cs' }, + 'SyncingQuick' => { 'value' => 'WARNING', 'type' => 'cs' }, + 'SyncPaused' => { 'value' => 'CRITICAL', 'type' => 'cs' }, + # DRBD 0.7 + 'WFBitMapS' => { 'value' => 'CRITICAL', 'type' => 'cs' }, + 'WFBitMapT' => { 'value' => 'CRITICAL', 'type' => 'cs' }, + 'SyncSource' => { 'value' => 'WARNING', 'type' => 'cs' }, + 'SyncTarget' => { 'value' => 'WARNING', 'type' => 'cs' }, + 'PausedSyncS' => { 'value' => 'CRITICAL', 'type' => 'cs' }, + 'PausedSyncT' => { 'value' => 'CRITICAL', 'type' => 'cs' }, + 'NetworkFailure' => { 'value' => 'CRITICAL', 'type' => 'cs' }, + 'SkippedSyncS' => { 'value' => 'CRITICAL', 'type' => 'cs' }, + 'SkippedSyncT' => { 'value' => 'CRITICAL', 'type' => 'cs' }, + 'Consistent' => { 'value' => 'OK', 'type' => 'ld' }, + 'Inconsistent' => { 'value' => 'CRITICAL', 'type' => 'ld' }, + # DRBD 8.0 + 'UpToDate' => { 'value' => 'OK', 'type' => 'ds' }, + 'Consistent' => { 'value' => 'OK', 'type' => 'ds' }, + 'Negotiating' => { 'value' => 'WARNING', 'type' => 'ds' }, + 'Attaching' => { 'value' => 'WARNING', 'type' => 'ds' }, + 'Diskless' => { 'value' => 'CRITICAL', 'type' => 'ds' }, + 'Failed' => { 'value' => 'CRITICAL', 'type' => 'ds' }, + 'Outdated' => { 'value' => 'CRITICAL', 'type' => 'ds' }, + 'Inconsistent' => { 'value' => 'CRITICAL', 'type' => 'ds' }, + 'DUnknown' => { 'value' => 'CRITICAL', 'type' => 'ds' }, + # DRBD 8.2 + 'VerifyS' => { 'value' => 'WARNING', 'type' => 'cs' }, + 'VerifyT' => { 'value' => 'WARNING', 'type' => 'cs' }, + # DRBD 8.3 + 'Disconnecting' => { 'value' => 'WARNING', 'type' => 'cs' }, + 'ProtocolError' => { 'value' => 'CRITICAL', 'type' => 'cs' }, + 'TearDown' => { 'value' => 'WARNING', 'type' => 'cs' }, + 'StartingSyncS' => { 'value' => 'WARNING', 'type' => 'cs' }, + 'StartingSyncT' => { 'value' => 'WARNING', 'type' => 'cs' }, + 'WFSyncUUID' => { 'value' => 'WARNING', 'type' => 'cs' } +); + +&parse_options; +&parse_proc; +&parse_drbd_devices; +&check_drbd_state; +&report_status; +&myexit('UNKNOWN',"$prog_name should never reach here"); + +sub print_usage { + print <] [-e expect] [-p proc] [-r role] [-o states] [-w states] [-c states] [--debug] + Options: + -d STRING [default: $drbd_devices. Example: 0,1,2 ] + -p STRING [default: $drbd_proc. Use '-' for stdin] + -e STRING [Must be this connected state. Example: Connected] + -r STRING [Must be this node state. Example: Primary] + -o STRING [Change value to OK. Example: StandAlone] + -w STRING [Change value to WARNING. Example: SyncingAll] + -c STRING [Change value to CRITICAL. Example: Inconsistent,WFConnection] +EOF +} + +sub print_revision { + print <{'retvalue'}; +} + +sub parse_options { + my ($help, $version, $debug, $ok_string, $warning_string, + $critical_string); + # + # Get command line options + # + GetOptions("h|help" => \$help, + "V|version" => \$version, + "d|device|devices=s" => \$drbd_devices, + "e|expect=s" => \$drbd_expect, + "p|proc=s" => \$drbd_proc, + "r|role=s" => \$drbd_role, + "o|ok=s" => \$ok_string, + "w|warning=s" => \$warning_string, + "c|critical=s" => \$critical_string, + "debug" => \$debug); + if (defined($help) && ($help ne "")) { + &print_help; + exit $errorcodes{'UNKNOWN'}->{'retvalue'}; + } + if (defined($version) && ($version ne "")) { + &print_revision; + exit $errorcodes{'UNKNOWN'}->{'retvalue'}; + } + if (defined($drbd_expect) && ($drbd_expect ne "")) { + # User requested the connected state to be very specific + &change_values($drbd_expect,'cs','expect','connected state'); + } + if (defined($drbd_role) && ($drbd_role ne "")) { + # User requested the node state to be very specific + &change_values($drbd_role,'st','role','node state'); + } + if (defined($ok_string) && ($ok_string ne "")) { + # User requested certain values to be OK + &set_values($ok_string,'OK'); + } + if (defined($warning_string) && ($warning_string ne "")) { + # User requested certain values to be WARNING + &set_values($warning_string,'WARNING'); + } + if (defined($critical_string) && ($critical_string ne "")) { + # User requested certain values to be CRITICAL + &set_values($critical_string,'CRITICAL'); + } + if (defined($debug) && ($debug ne "")) { + # + # Debugging information + # + $debug_mode=1; + print STDERR "<$prog_name settings>\n"; + print STDERR "DRBD Devices: $drbd_devices\n"; + printf STDERR "DRBD Proc: %s\n", defined($drbd_proc)?$drbd_proc:""; + printf STDERR "DRBD Expect: %s\n", defined($drbd_expect)?$drbd_expect:""; + printf STDERR "DRBD Role: %s\n", defined($drbd_role)?$drbd_role:""; + my (@ok, @critical, @warning); + for my $key ( keys %state ) { + if ($state{$key}->{'value'} eq 'OK') { + push(@ok,$key); + } + if ($state{$key}->{'value'} eq 'WARNING') { + push(@warning,$key); + } + if ($state{$key}->{'value'} eq 'CRITICAL') { + push(@critical,$key); + } + } + printf STDERR "DRBD OK: %s\n", join(" ",sort(@ok)); + printf STDERR "DRBD WARNING: %s\n", join(" ",sort(@warning)); + printf STDERR "DRBD CRITICAL: %s\n", join(" ",sort(@critical)); + print STDERR "\n"; + } +} + +sub parse_proc { + # + # Read in contents of proc file, feed results into hashes + # + my $input; + if ( $drbd_proc ne "-" ) { + $input = "DRBD"; + if ( ! -e $drbd_proc ) { + &myexit('UNKNOWN',"No such file $drbd_proc"); + } + open(DRBD, "$drbd_proc") || + &myexit('UNKNOWN',"Could not open $drbd_proc"); + } else { + $input = "STDIN"; + } + while(<$input>) { + if (/^version: (\d+).(\d+)/) { + $drbd_version = "$1.$2"; + } + if (/^\s?(\d+):.* cs:(\w+)/) { + $cs{$1} = $2; + } + if (/^\s?(\d+):.* st:(\w+)\//) { + $st{$1} = $2; + } + if (/^\s?(\d+):.* ld:(\w+)/) { + $ld{$1} = $2; + } + if (/^\s?(\d+):.* ds:(\w+)/) { + $ds{$1} = $2; + } + } + if ( $drbd_proc ne "-" ) { + close(DRBD); + } + if (defined($debug_mode) && ($debug_mode == 1)) { + # + # Debugging information + # + print STDERR "<$prog_name devices found>\n"; + for my $key ( sort keys %cs ) { + printf STDERR "Found Device $key $cs{$key}%s%s%s\n", defined($st{$key})?" $st{$key}":"", defined($ld{$key})?" $ld{$key}":"", defined($ds{$key})?" $ds{$key}":""; + } + print STDERR "\n"; + } +} + +sub parse_drbd_devices { + # + # Determine which DRBD devices to monitor + # + my @devices; + if ($drbd_devices =~ /^all$/i) { + for my $device ( keys %cs ) { + push(@devices,$device); + } + } elsif ($drbd_devices =~ /^configured$/i) { + for my $device ( keys %cs ) { + next if ($cs{$device} eq "Unconfigured"); + push(@devices,$device); + } + } else { + @devices = split(/,/,$drbd_devices); + } + foreach my $device (@devices) { + if (!(defined($cs{$device}))) { + &myexit('UNKNOWN',"Could not find device $device"); + } + $check{$device} = 1; + } + if (int(keys %check) == 0) { + &myexit('UNKNOWN',"No configured devices found"); + } + if (defined($debug_mode) && ($debug_mode == 1)) { + # + # Debugging information + # + print STDERR "<$prog_name devices to check>\n"; + for my $key ( sort keys %check ) { + printf STDERR "Checking enabled for device $key\n"; + } + print STDERR "\n"; + } +} + +sub check_drbd_state { + for my $drbd_device ( sort keys %check ) { + if ((defined($drbd_version)) && ($drbd_version >= '8.0')) { + # + # We're dealing with version 8.0 or greater + # Set data state + # + if ((defined($ds{$drbd_device})) && + (defined($state{$ds{$drbd_device}}))) { + $state{$ds{$drbd_device}}->{$drbd_device}->{'level'} = 1; + } elsif (defined($ds{$drbd_device})) { + &myexit('CRITICAL',"Data state unknown value '$ds{$drbd_device}' for device $drbd_device"); + } + } + if ((defined($drbd_version)) && ($drbd_version == '0.7')) { + # + # We're dealing with version 0.7 + # Set local data consistency + # + if ((defined($ld{$drbd_device})) && + (defined($state{$ld{$drbd_device}}))) { + $state{$ld{$drbd_device}}->{$drbd_device}->{'level'} = 1; + } elsif (defined($ld{$drbd_device})) { + &myexit('CRITICAL',"Local data consistency unknown value '$ld{$drbd_device}' for device $drbd_device"); + } + } + # + # Check for a state value (Primary, Secondary, etc) + # + if ((defined($st{$drbd_device})) && + (defined($state{$st{$drbd_device}}))) { + $state{$st{$drbd_device}}->{$drbd_device}->{'level'} = 1; + } elsif (defined($st{$drbd_device})) { + &myexit('CRITICAL',"Node state unknown value '$st{$drbd_device}' for device $drbd_device"); + } + # + # Check for a connected state value (Connected, StandAlone, etc) + # + if (defined($state{$cs{$drbd_device}})) { + $state{$cs{$drbd_device}}->{$drbd_device}->{'level'} = 1; + } else { + &myexit('CRITICAL',"Connection state unknown value '$cs{$drbd_device}' for device $drbd_device"); + } + # + # Debugging information + # + if (defined($debug_mode) && ($debug_mode == 1)) { + print STDERR "<$prog_name device $drbd_device status>\n"; + for my $key ( keys %state ) { + if (defined($state{$key}->{$drbd_device}->{'level'})) { + print STDERR "$key $state{$key}->{'value'}\n"; + } + } + print STDERR "\n"; + } + # + # Determine if any values are CRITICAL or WARNING + # + for my $key ( keys %state ) { + if (defined($state{$key}->{$drbd_device}->{'level'})) { + if ($state{$key}->{'value'} eq "CRITICAL") { + $critical{$drbd_device} = 1; + } + if ($state{$key}->{'value'} eq "WARNING") { + $warning{$drbd_device} = 1; + } + } + } + } +} + +sub report_status { + my $message; + my $critical_count=int(keys %critical); + my $warning_count=int(keys %warning); + if ($critical_count > 0) { + # + # We found a CRITICAL situation + # + my $i = 0; + for my $device (sort keys %critical) { + $message.=sprintf("Device %d%s $cs{$device}%s%s", $device,defined($st{$device})?" $st{$device}":"",defined($ld{$device})?" $ld{$device}":"",defined($ds{$device})?" $ds{$device}":""); + $i++; + if ($i != $critical_count) { + $message.=", "; + } + } + &myexit('CRITICAL',$message); + } elsif ($warning_count > 0) { + # + # We found a WARNING situation + # + my $i = 0; + for my $device (sort keys %warning) { + $message.=sprintf("Device %d%s $cs{$device}%s%s", $device,defined($st{$device})?" $st{$device}":"",defined($ld{$device})?" $ld{$device}":"",defined($ds{$device})?" $ds{$device}":""); + $i++; + if ($i != $warning_count) { + $message.=", "; + } + } + &myexit('WARNING',$message); + } else { + # + # Everything checks out OK + # + my $device_count=int(keys %check); + if ($device_count == 1) { + for my $device ( sort keys %check ) { + $message=sprintf("Device %d%s $cs{$device}%s%s", $device,defined($st{$device})?" $st{$device}":"",defined($ld{$device})?" $ld{$device}":"",defined($ds{$device})?" $ds{$device}":""); + } + } else { + my $i = 0; + for my $device ( sort keys %check ) { + $message.=sprintf("Dev %d %0.3s%0.3s%0.3s%0.3s", $device,defined($st{$device})?"$st{$device}":"",$cs{$device},defined($ld{$device})?"$ld{$device}":"",defined($ds{$device})?"$ds{$device}":""); + $i++; + if ($i != $device_count) { + $message.=", "; + } + } + } + &myexit('OK',$message); + } +} + +sub set_values { + # + # Set item to value requested + # + my ($items,$value) = @_; + my @items = split(/,/,$items); + foreach my $item (@items) { + if (defined($state{$item})) { + $state{$item}->{'value'} = "$value"; + } else { + print STDERR "State '$item' not found\n"; + } + } +} + +sub change_values { + # + # Look for all values of a given type, set requested value to OK + # and all other values to CRITICAL + # + my ($argument,$type,$error1,$error2) = @_; + if ((defined($state{$argument})) && + ($state{$argument}->{'type'} eq "$type")) { + for my $key ( keys %state ) { + if ($state{$key}->{'type'} eq "$type") { + if ($key eq $argument) { + &set_values($argument,'OK'); + } else { + &set_values($key,'CRITICAL'); + } + } + } + } else { + &myexit('UNKNOWN',"$error1 option only works for $error2"); + } +} + +sub myexit { + # + # Print error message and exit + # + my ($error, $message) = @_; + if (!(defined($errorcodes{$error}))) { + printf STDERR "Error code $error not known\n"; + print "DRBD UNKNOWN: $message\n"; + exit $errorcodes{'UNKNOWN'}->{'retvalue'}; + } + print "DRBD $error: $message\n"; + exit $errorcodes{$error}->{'retvalue'}; +} diff --git a/nagios-nrpe/files/plugins/check_evobackup b/nagios-nrpe/files/plugins/check_evobackup new file mode 100755 index 00000000..d45b3c58 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_evobackup @@ -0,0 +1,10 @@ +#!/bin/sh +ports="2222 2223" + +for port in $ports; do + /usr/lib/nagios/plugins/check_ssh -p $port localhost + exitCode=$? + [ $exitCode -ne 0 ] && exit $exitCode +done + +exit 0 diff --git a/nagios-nrpe/files/plugins/check_fs b/nagios-nrpe/files/plugins/check_fs new file mode 100755 index 00000000..4dc07338 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_fs @@ -0,0 +1,146 @@ +#!/bin/bash + +TUNE2FS="/sbin/tune2fs" + +MOUNT_MAX=14 +INTERVAL_MAX=200 +MAIL_ALERT="alert3@evolix.fr" + +PROGNAME=`/usr/bin/basename $0` + +# Nagios compatibility +STATE_OK=0 +STATE_WARNING=1 +STATE_CRITICAL=2 +STATE_UNKNOWN=3 +STATE_DEPENDENT=4 + +print_usage() { + echo "Usage: $PROGNAME --dev /dev/sdaX [--mount-max $MOUNT_MAX] [--interval-max $INTERVAL_MAX] [--alert]" + echo "Usage: $PROGNAME -d /dev/sdaX [-c $MOUNT_MAX] [-i $INTERVAL_MAX] [-a]" + echo "Usage: $PROGNAME --help" +} + +print_help() { + print_usage + echo "" + echo "Options:" + echo "--dev, -d /dev/sdNX" + echo "--mount-max, -c X" + echo "--interval-max, -i X (days)" + echo "--alert, -a (send a mail to $MAIL_ALERT if state critical)" +} + +alert_mail() { +# $1 max_mount +# $2 days + + if [ "$send_mail" = "1" ]; then + if [ "$exitstatus" = "$STATE_CRITICAL" ]; then + echo "From: Equipe Evolix +To: $MAIL_ALERT +Subject: [alert] Fsck on $(hostname) + +Number of mount since last fsck: $1 +Days since last fsck: $2 + +"|/usr/lib/sendmail -oi -t -f "$MAIL_ALERT" + fi + fi +} + +send_mail=0 +exitstatus=$STATE_OK + +# check command line arguments + +if [ $# -lt 1 ]; then + print_usage + exit $STATE_UNKNOWN +fi + +while test -n "$1"; do + case "$1" in + --help) + print_help + exit $STATE_OK + ;; + -h) + print_help + exit $STATE_OK + ;; + --dev) + dev=$2 + shift + ;; + -d) + dev=$2 + shift + ;; + --mount-max) + mount_max=$2 + shift + ;; + -c) + mount_max=$2 + shift + ;; + --interval-max) + interval_max=$2 + shift + ;; + -i) + interval_max=$2 + shift + ;; + --alert) + send_mail=1 + ;; + -a) + send_mail=1 + ;; + *) + echo "Unknown argument: $1" + print_usage + exit $STATE_UNKNOWN + ;; + esac + shift +done + +[ -z "$dev" ] && print_usage && exit $STATE_UNKNOWN +[ -z "$mount_max" ] && mount_max=$MOUNT_MAX +[ -z "$interval_max" ] && interval_max=$INTERVAL_MAX + +# is $dev support tune2fs ? +bad_magic=$($TUNE2FS -l $dev|grep "Bad magic number") +[ -n "$bad_magic" ] && echo "Bad magic number in super-block while trying to open $dev" && exit $STATE_UNKNOWN + +# grep max_count && last_checked +max_count=$($TUNE2FS -l $dev|grep "Mount count" | awk -F":" '{ print $2 }'| sed 's/^\s*//') +last_checked=$($TUNE2FS -l $dev|grep "Last checked"| awk -F":" '{ print $2":"$3":"$4 }'|sed 's/^\s*//') + +[ -z "$max_count" ] && echo "No Mount count value on $TUNE2FS $dev" && exit $STATE_UNKNOWN +[ -z "$last_checked" ] && echo "No Last checked value on $TUNE2FS $dev" && exit $STATE_UNKNOWN + +# check max mount +if [ $max_count -ge $mount_max ]; then + echo "Number of mount since last fsck: $max_count" + echo " - threshold $mount_max reached !\n" + exitstatus=$STATE_CRITICAL; +fi + +# compute day interval +timestamp=$( date -d "$last_checked" "+%s" ) +today=$( date "+%s" ) +days=$(( ($today - $timestamp) / 86400 )) + +if [ $days -ge $interval_max ]; then + echo "Days since last fsck: $days" + echo " - threshold $interval_max reached !\n" + exitstatus=$STATE_CRITICAL; +fi + +alert_mail $max_count $days + +exit $exitstatus diff --git a/nagios-nrpe/files/plugins/check_glusterfs b/nagios-nrpe/files/plugins/check_glusterfs new file mode 100755 index 00000000..b933fc33 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_glusterfs @@ -0,0 +1,150 @@ +#!/bin/bash + +# This Nagios script was written against version 3.3 & 3.4 of Gluster. Older +# versions will most likely not work at all with this monitoring script. +# +# Gluster currently requires elevated permissions to do anything. In order to +# accommodate this, you need to allow your Nagios user some additional +# permissions via sudo. The line you want to add will look something like the +# following in /etc/sudoers (or something equivalent): +# +# Defaults:nagios !requiretty +# nagios ALL=(root) NOPASSWD:/usr/sbin/gluster volume status [[\:graph\:]]* detail,/usr/sbin/gluster volume heal [[\:graph\:]]* info +# +# That should give us all the access we need to check the status of any +# currently defined peers and volumes. + +# Inspired by a script of Mark Nipper +# +# 2013, Mark Ruys, mark.ruys@peercode.nl + +PATH=/sbin:/bin:/usr/sbin:/usr/bin + +PROGNAME=$(basename -- $0) +PROGPATH=`echo $0 | sed -e 's,[\\/][^\\/][^\\/]*$,,'` +REVISION="1.0.0" + +. /usr/lib/nagios/plugins/utils.sh + +# parse command line +usage () { + echo "" + echo "USAGE: " + echo " $PROGNAME -v VOLUME -n BRICKS [-w GB -c GB]" + echo " -n BRICKS: number of bricks" + echo " -w and -c values in GB" + exit $STATE_UNKNOWN +} + +while getopts "v:n:w:c:" opt; do + case $opt in + v) VOLUME=${OPTARG} ;; + n) BRICKS=${OPTARG} ;; + w) WARN=${OPTARG} ;; + c) CRIT=${OPTARG} ;; + *) usage ;; + esac +done + +if [ -z "${VOLUME}" -o -z "${BRICKS}" ]; then + usage +fi + +Exit () { + echo "$1: ${2:0}" + status=STATE_$1 + exit ${!status} +} + +# check for commands +for cmd in basename bc awk sudo pidof gluster; do + if ! type -p "$cmd" >/dev/null; then + Exit UNKNOWN "$cmd not found" + fi +done + +# check for glusterd (management daemon) +if ! pidof glusterd &>/dev/null; then + Exit CRITICAL "glusterd management daemon not running" +fi + +# check for glusterfsd (brick daemon) +if ! pidof glusterfsd &>/dev/null; then + Exit CRITICAL "glusterfsd brick daemon not running" +fi + +# get volume heal status +heal=0 +for entries in $(sudo gluster volume heal ${VOLUME} info | awk '/^Number of entries: /{print $4}'); do + if [ "$entries" -gt 0 ]; then + let $((heal+=entries)) + fi +done +if [ "$heal" -gt 0 ]; then + errors=("${errors[@]}" "$heal unsynched entries") +fi + +# get volume status +bricksfound=0 +freegb=9999999 +shopt -s nullglob +while read -r line; do + field=($(echo $line)) + case ${field[0]} in + Brick) + brick=${field[@]:2} + ;; + Disk) + key=${field[@]:0:3} + if [ "${key}" = "Disk Space Free" ]; then + freeunit=${field[@]:4} + free=${freeunit:0:-2} + unit=${freeunit#$free} + if [ "$unit" != "GB" ]; then + Exit UNKNOWN "unknown disk space size $freeunit" + fi + free=$(echo "${free} / 1" | bc -q) + if [ $free -lt $freegb ]; then + freegb=$free + fi + fi + ;; + Online) + online=${field[@]:2} + if [ "${online}" = "Y" ]; then + let $((bricksfound++)) + else + errors=("${errors[@]}" "$brick offline") + fi + ;; + esac +done < <(sudo gluster volume status ${VOLUME} detail) + +if [ $bricksfound -eq 0 ]; then + Exit CRITICAL "no bricks found" +elif [ $bricksfound -lt $BRICKS ]; then + errors=("${errors[@]}" "found $bricksfound bricks, expected $BRICKS ") +fi + +if [ -n "$CRIT" -a -n "$WARN" ]; then + if [ $CRIT -ge $WARN ]; then + Exit UNKNOWN "critical threshold below warning" + elif [ $freegb -lt $CRIT ]; then + Exit CRITICAL "free space ${freegb}GB" + elif [ $freegb -lt $WARN ]; then + errors=("${errors[@]}" "free space ${freegb}GB") + fi +fi + +# exit with warning if errors +if [ -n "$errors" ]; then + sep='; ' + msg=$(printf "${sep}%s" "${errors[@]}") + msg=${msg:${#sep}} + + Exit WARNING "${msg}" +fi + +# exit with no errors +Exit OK "${bricksfound} bricks; free space ${freegb}GB" + diff --git a/nagios-nrpe/files/plugins/check_haproxy_stats b/nagios-nrpe/files/plugins/check_haproxy_stats new file mode 100755 index 00000000..e3e8ff4b --- /dev/null +++ b/nagios-nrpe/files/plugins/check_haproxy_stats @@ -0,0 +1,225 @@ +#!/usr/bin/env perl +# vim: se et ts=4: + +# +# Copyright (C) 2012, Giacomo Montagner +# +# This program is free software; you can redistribute it and/or modify it +# under the same terms as Perl 5.10.1. +# For more details, see http://dev.perl.org/licenses/artistic.html +# +# 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. +# + +our $VERSION = "1.0.1"; + +# CHANGELOG: +# 1.0.0 - first release +# 1.0.1 - fixed empty message if all proxies are OK +# + +use strict; +use warnings; +use 5.010.001; +use File::Basename qw/basename/; +use IO::Socket::UNIX; +use Getopt::Long; + +sub usage { + my $me = basename $0; + print <. + $me is distributed under GPL and the Artistic License 2.0 + +SEE ALSO + Check out online haproxy documentation at + +EOU +} + +my %check_statuses = ( + UNK => "unknown", + INI => "initializing", + SOCKERR => "socket error", + L4OK => "layer 4 check OK", + L4CON => "connection error", + L4TMOUT => "layer 1-4 timeout", + L6OK => "layer 6 check OK", + L6TOUT => "layer 6 (SSL) timeout", + L6RSP => "layer 6 protocol error", + L7OK => "layer 7 check OK", + L7OKC => "layer 7 conditionally OK", + L7TOUT => "layer 7 (HTTP/SMTP) timeout", + L7RSP => "layer 7 protocol error", + L7STS => "layer 7 status error", +); + +my @status_names = (qw/OK WARNING CRITICAL UNKNOWN/); + +# Defaults +my $swarn = 80.0; +my $scrit = 90.0; +my $sock = "/var/run/haproxy.sock"; +my $dump; +my $proxy; +my $help; + +# Read command line +Getopt::Long::Configure ("bundling"); +GetOptions ( + "c|critical=i" => \$scrit, + "d|dump" => \$dump, + "h|help" => \$help, + "p|proxy=s" => \$proxy, + "s|sock|socket=s" => \$sock, + "w|warning=i" => \$swarn, +); + +# Want help? +if ($help) { + usage; + exit 3; +} + +# Connect to haproxy socket and get stats +my $haproxy = new IO::Socket::UNIX ( + Peer => $sock, + Type => SOCK_STREAM, +); +die "Unable to connect to haproxy socket: $@" unless $haproxy; +print $haproxy "show stat\n" or die "Print to socket failed: $!"; + +# Dump stats and exit if requested +if ($dump) { + while (<$haproxy>) { + print; + } + exit 0; +} + +# Get labels from first output line and map them to their position in the line +my $labels = <$haproxy>; +chomp($labels); +$labels =~ s/^# // or die "Data format not supported."; +my @labels = split /,/, $labels; +{ + no strict "refs"; + my $idx = 0; + map { $$_ = $idx++ } @labels; +} + +# Variables I will use from here on: +our $pxname; +our $svname; +our $status; + +my @proxies = split ',', $proxy if $proxy; +my $exitcode = 0; +my $msg; +my $checked = 0; +while (<$haproxy>) { + chomp; + next if /^[[:space:]]*$/; + my @data = split /,/, $_; + if (@proxies) { next unless grep {$data[$pxname] eq $_} @proxies; }; + + # Is session limit enforced? + our $slim; + if ($data[$slim]) { + # Check current session # against limit + our $scur; + my $sratio = $data[$scur]/$data[$slim]; + if ($sratio >= $scrit || $sratio >= $swarn) { + $exitcode = $sratio >= $scrit ? 2 : + $exitcode < 2 ? 1 : $exitcode; + $msg .= sprintf "%s:%s sessions: %.2f%%; ", $data[$pxname], $data[$svname], $sratio; + } + } + + # Check of BACKENDS + if ($data[$svname] eq 'BACKEND') { + if ($data[$status] !~ '(UP|MAINT)') { + $msg .= sprintf "BACKEND: %s is %s; ", $data[$pxname], $data[$status]; + $exitcode = 2; + } + # Check of FRONTENDS + } elsif ($data[$svname] eq 'FRONTEND') { + if ($data[$status] ne 'OPEN') { + $msg .= sprintf "FRONTEND: %s is %s; ", $data[$pxname], $data[$status]; + $exitcode = 2; + } + # Check of servers + } else { + if ($data[$status] !~ '(UP|MAINT)') { + next if $data[$status] eq 'no check'; # Ignore server if no check is configured to be run + $exitcode = 2; + our $check_status; + $msg .= sprintf "server: %s:%s is %s", $data[$pxname], $data[$svname], $data[$status]; + $msg .= sprintf " (check status: %s)", $check_statuses{$data[$check_status]} if $check_statuses{$data[$check_status]}; + $msg .= "; "; + } + } + ++$checked; +} + +unless ($msg) { + $msg = @proxies ? sprintf("checked proxies: %s", join ', ', sort @proxies) : "checked $checked proxies."; +} +say "Check haproxy $status_names[$exitcode] - $msg"; +exit $exitcode; + diff --git a/nagios-nrpe/files/plugins/check_http_many b/nagios-nrpe/files/plugins/check_http_many new file mode 100644 index 00000000..90d8d9e2 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_http_many @@ -0,0 +1,84 @@ +#!/bin/bash + +result=/tmp/nagios.check_http_many.result +trap "rm ${result}*" EXIT +warning=false +critical=false + +check_state() { + + if [[ $1 -eq 0 ]]; then + cat $result >> ${result}.ok + :> $result + fi + if [[ $1 -eq 1 ]]; then + warning=true + cat $result >> ${result}.err + :> $result + fi + if [[ $1 -eq 2 ]]; then + critical=true + cat $result >> ${result}.err + :> $result + fi + +} + +## Return OK between 02am and 05am. +#date=$(date +%H) +#if [[ $date > 01 && $date < 05 ]]; then +# echo "Maintenance time, no check! All green!" +# exit 0 +#fi + +# Check HTTP +sites="" +for site in $sites; do + echo -n "Site ${site}: " >> $result + /usr/lib/nagios/plugins/check_http -f critical -I 127.0.0.1 -H ${site%%/*} -u /${site#*/} >> $result + check_state $? +done + +## Check HTTPs +sites="" +for site in $sites; do + echo -n "Site ${site}: " >> $result + /usr/lib/nagios/plugins/check_http -w4 -c6 -f critical -p 443 -S -I 127.0.0.1 -H ${site%%/*} -u /${site#*/} >> $result + check_state $? +done + +# Check HTTPs certs +sites="" +for site in $sites; do + echo -n "Certificate ${site}: " >> $result + /usr/lib/nagios/plugins/check_http -p 443 -S --sni -H $site -C14,7 >> $result + check_state $? +done + +# Check Sockets +sockets="" +for socket in $sockets; do + echo -n "Socket ${socket}: " >> $result + /usr/lib/nagios/plugins/check_tcp -H $socket >> $result + check_state $?0 +done + +# Check Ports +ports="" +for port in $ports; do + echo -n "Port ${port}: " >> $result + /usr/lib/nagios/plugins/check_tcp -p $port >> $result + check_state $? +done + +if ($critical); then + cat ${result}.err + exit 2 +fi +if ($warning); then + cat ${result}.err + exit 1 +else + cat ${result}.ok + exit 0 +fi diff --git a/nagios-nrpe/files/plugins/check_iptables b/nagios-nrpe/files/plugins/check_iptables new file mode 100755 index 00000000..1272936f --- /dev/null +++ b/nagios-nrpe/files/plugins/check_iptables @@ -0,0 +1,29 @@ +#!/bin/sh + +usage() { + + cat <&2 +Usage: $0 -w -c +EOT +} + +while getopts ':w:c:' o; do + case $o in + w) + warn=$OPTARG + ;; + c) + crit=$OPTARG + ;; + *) + usage + exit 2 + ;; + esac +done + +if [ -z $warn ] || [ -z $crit ]; then + usage + exit 2 +fi + + +POSTCONFSPOOL="$(postconf -h queue_directory || echo /var/spool/postfix)" + +# If find fail (eg. Permission denied), script continues and exits normally, +# even with set -e. +# So check here if we are sufficient permission to list a Postfix directory +if ! find $POSTCONFSPOOL/corrupt >/dev/null; then + echo "Script does not seem to have permission to list spool directories." + exit 2 +fi + +deferred=$(find $POSTCONFSPOOL/deferred -type f |wc -l) +active=$(find $POSTCONFSPOOL/active -type f | wc -l) +maildrop=$(find $POSTCONFSPOOL/maildrop -type f | wc -l) +incoming=$(find $POSTCONFSPOOL/incoming -type f | wc -l) +corrupt=$(find $POSTCONFSPOOL/corrupt -type f | wc -l) +hold=$(find $POSTCONFSPOOL/hold -type f | wc -l) +total=$(($deferred + $active + $maildrop + $incoming + $corrupt + $hold)) + +echo "total: $total (deferred: $deferred, active: $active, maildrop: \ +$maildrop, incoming: $incoming, corrupt: $corrupt, hold: $hold)" + +if [ $total -ge $crit ]; then + exit 2 +elif [ $total -ge $warn ]; then + exit 1 +fi diff --git a/nagios-nrpe/files/plugins/check_mem b/nagios-nrpe/files/plugins/check_mem new file mode 100755 index 00000000..8487255c --- /dev/null +++ b/nagios-nrpe/files/plugins/check_mem @@ -0,0 +1,266 @@ +#!/usr/bin/perl -w + +# Heavily based on the script from: +# check_mem.pl Copyright (C) 2000 Dan Larsson +# heavily modified by +# Justin Ellison +# +# The MIT License (MIT) +# Copyright (c) 2011 justin@techadvise.com + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this +# software and associated documentation files (the "Software"), to deal in the Software +# without restriction, including without limitation the rights to use, copy, modify, +# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies +# or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +# FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +# Tell Perl what we need to use +use strict; +use Getopt::Std; + +#TODO - Convert to Nagios::Plugin +#TODO - Use an alarm + +# Predefined exit codes for Nagios +use vars qw($opt_c $opt_f $opt_u $opt_w $opt_C $opt_v %exit_codes); +%exit_codes = ('UNKNOWN' ,-1, + 'OK' , 0, + 'WARNING' , 1, + 'CRITICAL', 2, + ); + +# Get our variables, do our checking: +init(); + +# Get the numbers: +my ($free_memory_kb,$used_memory_kb,$caches_kb) = get_memory_info(); +print "$free_memory_kb Free\n$used_memory_kb Used\n$caches_kb Cache\n" if ($opt_v); + +if ($opt_C) { #Do we count caches as free? + $used_memory_kb -= $caches_kb; + $free_memory_kb += $caches_kb; +} + +# Round to the nearest KB +$free_memory_kb = sprintf('%d',$free_memory_kb); +$used_memory_kb = sprintf('%d',$used_memory_kb); +$caches_kb = sprintf('%d',$caches_kb); + +# Tell Nagios what we came up with +tell_nagios($used_memory_kb,$free_memory_kb,$caches_kb); + + +sub tell_nagios { + my ($used,$free,$caches) = @_; + + # Calculate Total Memory + my $total = $free + $used; + print "$total Total\n" if ($opt_v); + + my $perfdata = "|TOTAL=${total}KB;;;; USED=${used}KB;;;; FREE=${free}KB;;;; CACHES=${caches}KB;;;;"; + + if ($opt_f) { + my $percent = sprintf "%.1f", ($free / $total * 100); + if ($percent <= $opt_c) { + finish("CRITICAL - $percent% ($free kB) free!$perfdata",$exit_codes{'CRITICAL'}); + } + elsif ($percent <= $opt_w) { + finish("WARNING - $percent% ($free kB) free!$perfdata",$exit_codes{'WARNING'}); + } + else { + finish("OK - $percent% ($free kB) free.$perfdata",$exit_codes{'OK'}); + } + } + elsif ($opt_u) { + my $percent = sprintf "%.1f", ($used / $total * 100); + if ($percent >= $opt_c) { + finish("CRITICAL - $percent% ($used kB) used!$perfdata",$exit_codes{'CRITICAL'}); + } + elsif ($percent >= $opt_w) { + finish("WARNING - $percent% ($used kB) used!$perfdata",$exit_codes{'WARNING'}); + } + else { + finish("OK - $percent% ($used kB) used.$perfdata",$exit_codes{'OK'}); + } + } +} + +# Show usage +sub usage() { + print "\ncheck_mem.pl v1.0 - Nagios Plugin\n\n"; + print "usage:\n"; + print " check_mem.pl - -w -c \n\n"; + print "options:\n"; + print " -f Check FREE memory\n"; + print " -u Check USED memory\n"; + print " -C Count OS caches as FREE memory\n"; + print " -w PERCENT Percent free/used when to warn\n"; + print " -c PERCENT Percent free/used when critical\n"; + print "\nCopyright (C) 2000 Dan Larsson \n"; + print "check_mem.pl comes with absolutely NO WARRANTY either implied or explicit\n"; + print "This program is licensed under the terms of the\n"; + print "MIT License (check source code for details)\n"; + exit $exit_codes{'UNKNOWN'}; +} + +sub get_memory_info { + my $used_memory_kb = 0; + my $free_memory_kb = 0; + my $total_memory_kb = 0; + my $caches_kb = 0; + + my $uname; + if ( -e '/usr/bin/uname') { + $uname = `/usr/bin/uname -a`; + } + elsif ( -e '/bin/uname') { + $uname = `/bin/uname -a`; + } + else { + die "Unable to find uname in /usr/bin or /bin!\n"; + } + print "uname returns $uname" if ($opt_v); + if ( $uname =~ /Linux/ ) { + my @meminfo = `/bin/cat /proc/meminfo`; + foreach (@meminfo) { + chomp; + if (/^Mem(Total|Free):\s+(\d+) kB/) { + my $counter_name = $1; + if ($counter_name eq 'Free') { + $free_memory_kb = $2; + } + elsif ($counter_name eq 'Total') { + $total_memory_kb = $2; + } + } + elsif (/^(Buffers|Cached):\s+(\d+) kB/) { + $caches_kb += $2; + } + } + $used_memory_kb = $total_memory_kb - $free_memory_kb; + } + elsif ( $uname =~ /SunOS/ ) { + eval "use Sun::Solaris::Kstat"; + if ($@) { #Kstat not available + if ($opt_C) { + print "You can't report on Solaris caches without Sun::Solaris::Kstat available!\n"; + exit $exit_codes{UNKNOWN}; + } + my @vmstat = `/usr/bin/vmstat 1 2`; + my $line; + foreach (@vmstat) { + chomp; + $line = $_; + } + $free_memory_kb = (split(/ /,$line))[5] / 1024; + my @prtconf = `/usr/sbin/prtconf`; + foreach (@prtconf) { + if (/^Memory size: (\d+) Megabytes/) { + $total_memory_kb = $1 * 1024; + } + } + $used_memory_kb = $total_memory_kb - $free_memory_kb; + + } + else { # We have kstat + my $kstat = Sun::Solaris::Kstat->new(); + my $phys_pages = ${kstat}->{unix}->{0}->{system_pages}->{physmem}; + my $free_pages = ${kstat}->{unix}->{0}->{system_pages}->{freemem}; + # We probably should account for UFS caching here, but it's unclear + # to me how to determine UFS's cache size. There's inode_cache, + # and maybe the physmem variable in the system_pages module?? + # In the real world, it looks to be so small as not to really matter, + # so we don't grab it. If someone can give me code that does this, + # I'd be glad to put it in. + my $arc_size = (exists ${kstat}->{zfs} && ${kstat}->{zfs}->{0}->{arcstats}->{size}) ? + ${kstat}->{zfs}->{0}->{arcstats}->{size} / 1024 + : 0; + $caches_kb += $arc_size; + my $pagesize = `pagesize`; + + $total_memory_kb = $phys_pages * $pagesize / 1024; + $free_memory_kb = $free_pages * $pagesize / 1024; + $used_memory_kb = $total_memory_kb - $free_memory_kb; + } + } + elsif ( $uname =~ /AIX/ ) { + my @meminfo = `/usr/bin/vmstat -v`; + foreach (@meminfo) { + chomp; + if (/^\s*([0-9.]+)\s+(.*)/) { + my $counter_name = $2; + if ($counter_name eq 'memory pages') { + $total_memory_kb = $1*4; + } + if ($counter_name eq 'free pages') { + $free_memory_kb = $1*4; + } + if ($counter_name eq 'file pages') { + $caches_kb = $1*4; + } + } + } + $used_memory_kb = $total_memory_kb - $free_memory_kb; + } + else { + if ($opt_C) { + print "You can't report on $uname caches!\n"; + exit $exit_codes{UNKNOWN}; + } + my $command_line = `vmstat | tail -1 | awk '{print \$4,\$5}'`; + chomp $command_line; + my @memlist = split(/ /, $command_line); + + # Define the calculating scalars + $used_memory_kb = $memlist[0]/1024; + $free_memory_kb = $memlist[1]/1024; + $total_memory_kb = $used_memory_kb + $free_memory_kb; + } + return ($free_memory_kb,$used_memory_kb,$caches_kb); +} + +sub init { + # Get the options + if ($#ARGV le 0) { + &usage; + } + else { + getopts('c:fuCvw:'); + } + + # Shortcircuit the switches + if (!$opt_w or $opt_w == 0 or !$opt_c or $opt_c == 0) { + print "*** You must define WARN and CRITICAL levels!\n"; + &usage; + } + elsif (!$opt_f and !$opt_u) { + print "*** You must select to monitor either USED or FREE memory!\n"; + &usage; + } + + # Check if levels are sane + if ($opt_w <= $opt_c and $opt_f) { + print "*** WARN level must not be less than CRITICAL when checking FREE memory!\n"; + &usage; + } + elsif ($opt_w >= $opt_c and $opt_u) { + print "*** WARN level must not be greater than CRITICAL when checking USED memory!\n"; + &usage; + } +} + +sub finish { + my ($msg,$state) = @_; + print "$msg\n"; + exit $state; +} diff --git a/nagios-nrpe/files/plugins/check_memcached_instances b/nagios-nrpe/files/plugins/check_memcached_instances new file mode 100755 index 00000000..0a04b867 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_memcached_instances @@ -0,0 +1,10 @@ +#!/bin/bash + +returnCode=0 + +for port in $(grep "^-p " /etc/memcached*.conf |cut -d ' ' -f 2); do + /usr/lib/nagios/plugins/check_tcp -p $port + returnCode=$(($returnCode|$?)) +done + +exit $returnCode diff --git a/nagios-nrpe/files/plugins/check_mongodb b/nagios-nrpe/files/plugins/check_mongodb new file mode 100755 index 00000000..bc6278ac --- /dev/null +++ b/nagios-nrpe/files/plugins/check_mongodb @@ -0,0 +1,1456 @@ +#!/usr/bin/env python + +# +# A MongoDB Nagios check script +# + +# Script idea taken from a Tag1 script I found and I modified it a lot +# +# Main Author +# - Mike Zupan +# Contributers +# - Frank Brandewiede +# - Sam Perman +# - Shlomo Priymak +# - @jhoff909 on github +# - @jbraeuer on github +# - Dag Stockstad +# - @Andor on github +# - Steven Richards - Captainkrtek on github +# - Max Vernimmen +# +# USAGE +# +# See the README.md +# + +import sys +import time +import optparse +import textwrap +import re +import os + +try: + import pymongo +except ImportError, e: + print e + sys.exit(2) + +# As of pymongo v 1.9 the SON API is part of the BSON package, therefore attempt +# to import from there and fall back to pymongo in cases of older pymongo +if pymongo.version >= "1.9": + import bson.son as son +else: + import pymongo.son as son + + +# +# thanks to http://stackoverflow.com/a/1229667/72987 +# +def optional_arg(arg_default): + def func(option, opt_str, value, parser): + if parser.rargs and not parser.rargs[0].startswith('-'): + val = parser.rargs[0] + parser.rargs.pop(0) + else: + val = arg_default + setattr(parser.values, option.dest, val) + return func + + +def performance_data(perf_data, params): + data = '' + if perf_data: + data = " |" + for p in params: + p += (None, None, None, None) + param, param_name, warning, critical = p[0:4] + data += "%s=%s" % (param_name, str(param)) + if warning or critical: + warning = warning or 0 + critical = critical or 0 + data += ";%s;%s" % (warning, critical) + + data += " " + + return data + + +def numeric_type(param): + if ((type(param) == float or type(param) == int or param == None)): + return True + return False + + +def check_levels(param, warning, critical, message, ok=[]): + if (numeric_type(critical) and numeric_type(warning)): + if param >= critical: + print "CRITICAL - " + message + sys.exit(2) + elif param >= warning: + print "WARNING - " + message + sys.exit(1) + else: + print "OK - " + message + sys.exit(0) + else: + if param in critical: + print "CRITICAL - " + message + sys.exit(2) + + if param in warning: + print "WARNING - " + message + sys.exit(1) + + if param in ok: + print "OK - " + message + sys.exit(0) + + # unexpected param value + print "CRITICAL - Unexpected value : %d" % param + "; " + message + return 2 + + +def get_server_status(con): + try: + set_read_preference(con.admin) + data = con.admin.command(pymongo.son_manipulator.SON([('serverStatus', 1)])) + except: + data = con.admin.command(son.SON([('serverStatus', 1)])) + return data + + +def main(argv): + p = optparse.OptionParser(conflict_handler="resolve", description="This Nagios plugin checks the health of mongodb.") + + p.add_option('-H', '--host', action='store', type='string', dest='host', default='127.0.0.1', help='The hostname you want to connect to') + p.add_option('-P', '--port', action='store', type='int', dest='port', default=27017, help='The port mongodb is runnung on') + p.add_option('-u', '--user', action='store', type='string', dest='user', default=None, help='The username you want to login as') + p.add_option('-p', '--pass', action='store', type='string', dest='passwd', default=None, help='The password you want to use for that user') + p.add_option('-W', '--warning', action='store', dest='warning', default=None, help='The warning threshold we want to set') + p.add_option('-C', '--critical', action='store', dest='critical', default=None, help='The critical threshold we want to set') + p.add_option('-A', '--action', action='store', type='choice', dest='action', default='connect', help='The action you want to take', + choices=['connect', 'connections', 'replication_lag', 'replication_lag_percent', 'replset_state', 'memory', 'memory_mapped', 'lock', + 'flushing', 'last_flush_time', 'index_miss_ratio', 'databases', 'collections', 'database_size', 'database_indexes', 'collection_indexes', 'collection_size', + 'queues', 'oplog', 'journal_commits_in_wl', 'write_data_files', 'journaled', 'opcounters', 'current_lock', 'replica_primary', 'page_faults', + 'asserts', 'queries_per_second', 'page_faults', 'chunks_balance', 'connect_primary', 'collection_state', 'row_count', 'replset_quorum']) + p.add_option('--max-lag', action='store_true', dest='max_lag', default=False, help='Get max replication lag (for replication_lag action only)') + p.add_option('--mapped-memory', action='store_true', dest='mapped_memory', default=False, help='Get mapped memory instead of resident (if resident memory can not be read)') + p.add_option('-D', '--perf-data', action='store_true', dest='perf_data', default=False, help='Enable output of Nagios performance data') + p.add_option('-d', '--database', action='store', dest='database', default='admin', help='Specify the database to check') + p.add_option('--all-databases', action='store_true', dest='all_databases', default=False, help='Check all databases (action database_size)') + p.add_option('-s', '--ssl', dest='ssl', default=False, action='callback', callback=optional_arg(True), help='Connect using SSL') + p.add_option('-r', '--replicaset', dest='replicaset', default=None, action='callback', callback=optional_arg(True), help='Connect to replicaset') + p.add_option('-q', '--querytype', action='store', dest='query_type', default='query', help='The query type to check [query|insert|update|delete|getmore|command] from queries_per_second') + p.add_option('-c', '--collection', action='store', dest='collection', default='admin', help='Specify the collection to check') + p.add_option('-T', '--time', action='store', type='int', dest='sample_time', default=1, help='Time used to sample number of pages faults') + + options, arguments = p.parse_args() + host = options.host + port = options.port + user = options.user + passwd = options.passwd + query_type = options.query_type + collection = options.collection + sample_time = options.sample_time + if (options.action == 'replset_state'): + warning = str(options.warning or "") + critical = str(options.critical or "") + else: + warning = float(options.warning or 0) + critical = float(options.critical or 0) + + action = options.action + perf_data = options.perf_data + max_lag = options.max_lag + database = options.database + ssl = options.ssl + replicaset = options.replicaset + + if action == 'replica_primary' and replicaset is None: + return "replicaset must be passed in when using replica_primary check" + elif not action == 'replica_primary' and replicaset: + return "passing a replicaset while not checking replica_primary does not work" + + # + # moving the login up here and passing in the connection + # + start = time.time() + err, con = mongo_connect(host, port, ssl, user, passwd, replicaset) + if err != 0: + return err + + conn_time = time.time() - start + conn_time = round(conn_time, 0) + + if action == "connections": + return check_connections(con, warning, critical, perf_data) + elif action == "replication_lag": + return check_rep_lag(con, host, port, warning, critical, False, perf_data, max_lag, user, passwd) + elif action == "replication_lag_percent": + return check_rep_lag(con, host, port, warning, critical, True, perf_data, max_lag, user, passwd) + elif action == "replset_state": + return check_replset_state(con, perf_data, warning, critical) + elif action == "memory": + return check_memory(con, warning, critical, perf_data, options.mapped_memory) + elif action == "memory_mapped": + return check_memory_mapped(con, warning, critical, perf_data) + elif action == "queues": + return check_queues(con, warning, critical, perf_data) + elif action == "lock": + return check_lock(con, warning, critical, perf_data) + elif action == "current_lock": + return check_current_lock(con, host, warning, critical, perf_data) + elif action == "flushing": + return check_flushing(con, warning, critical, True, perf_data) + elif action == "last_flush_time": + return check_flushing(con, warning, critical, False, perf_data) + elif action == "index_miss_ratio": + index_miss_ratio(con, warning, critical, perf_data) + elif action == "databases": + return check_databases(con, warning, critical, perf_data) + elif action == "collections": + return check_collections(con, warning, critical, perf_data) + elif action == "oplog": + return check_oplog(con, warning, critical, perf_data) + elif action == "journal_commits_in_wl": + return check_journal_commits_in_wl(con, warning, critical, perf_data) + elif action == "database_size": + if options.all_databases: + return check_all_databases_size(con, warning, critical, perf_data) + else: + return check_database_size(con, database, warning, critical, perf_data) + elif action == "database_indexes": + return check_database_indexes(con, database, warning, critical, perf_data) + elif action == "collection_indexes": + return check_collection_indexes(con, database, collection, warning, critical, perf_data) + elif action == "collection_size": + return check_collection_size(con, database, collection, warning, critical, perf_data) + elif action == "journaled": + return check_journaled(con, warning, critical, perf_data) + elif action == "write_data_files": + return check_write_to_datafiles(con, warning, critical, perf_data) + elif action == "opcounters": + return check_opcounters(con, host, warning, critical, perf_data) + elif action == "asserts": + return check_asserts(con, host, warning, critical, perf_data) + elif action == "replica_primary": + return check_replica_primary(con, host, warning, critical, perf_data, replicaset) + elif action == "queries_per_second": + return check_queries_per_second(con, query_type, warning, critical, perf_data) + elif action == "page_faults": + check_page_faults(con, sample_time, warning, critical, perf_data) + elif action == "chunks_balance": + chunks_balance(con, database, collection, warning, critical) + elif action == "connect_primary": + return check_connect_primary(con, warning, critical, perf_data) + elif action == "collection_state": + return check_collection_state(con, database, collection) + elif action == "row_count": + return check_row_count(con, database, collection, warning, critical, perf_data) + elif action == "replset_quorum": + return check_replset_quorum(con, perf_data) + else: + return check_connect(host, port, warning, critical, perf_data, user, passwd, conn_time) + + +def mongo_connect(host=None, port=None, ssl=False, user=None, passwd=None, replica=None): + try: + # ssl connection for pymongo > 2.3 + if pymongo.version >= "2.3": + if replica is None: + con = pymongo.MongoClient(host, port) + else: + con = pymongo.Connection(host, port, read_preference=pymongo.ReadPreference.SECONDARY, ssl=ssl, replicaSet=replica, network_timeout=10) + else: + if replica is None: + con = pymongo.Connection(host, port, slave_okay=True, network_timeout=10) + else: + con = pymongo.Connection(host, port, slave_okay=True, network_timeout=10) + #con = pymongo.Connection(host, port, slave_okay=True, replicaSet=replica, network_timeout=10) + + if user and passwd: + db = con["admin"] + if not db.authenticate(user, passwd): + sys.exit("Username/Password incorrect") + except Exception, e: + if isinstance(e, pymongo.errors.AutoReconnect) and str(e).find(" is an arbiter") != -1: + # We got a pymongo AutoReconnect exception that tells us we connected to an Arbiter Server + # This means: Arbiter is reachable and can answer requests/votes - this is all we need to know from an arbiter + print "OK - State: 7 (Arbiter)" + sys.exit(0) + return exit_with_general_critical(e), None + return 0, con + + +def exit_with_general_warning(e): + if isinstance(e, SystemExit): + return e + else: + print "WARNING - General MongoDB warning:", e + return 1 + + +def exit_with_general_critical(e): + if isinstance(e, SystemExit): + return e + else: + print "CRITICAL - General MongoDB Error:", e + return 2 + + +def set_read_preference(db): + if pymongo.version >= "2.1": + db.read_preference = pymongo.ReadPreference.SECONDARY + + +def check_connect(host, port, warning, critical, perf_data, user, passwd, conn_time): + warning = warning or 3 + critical = critical or 6 + message = "Connection took %i seconds" % conn_time + message += performance_data(perf_data, [(conn_time, "connection_time", warning, critical)]) + + return check_levels(conn_time, warning, critical, message) + + +def check_connections(con, warning, critical, perf_data): + warning = warning or 80 + critical = critical or 95 + try: + data = get_server_status(con) + + current = float(data['connections']['current']) + available = float(data['connections']['available']) + + used_percent = int(float(current / (available + current)) * 100) + message = "%i percent (%i of %i connections) used" % (used_percent, current, current + available) + message += performance_data(perf_data, [(used_percent, "used_percent", warning, critical), + (current, "current_connections"), + (available, "available_connections")]) + return check_levels(used_percent, warning, critical, message) + + except Exception, e: + return exit_with_general_critical(e) + + +def check_rep_lag(con, host, port, warning, critical, percent, perf_data, max_lag, user, passwd): + # Get mongo to tell us replica set member name when connecting locally + if "127.0.0.1" == host: + host = con.admin.command("ismaster","1")["me"].split(':')[0] + + if percent: + warning = warning or 50 + critical = critical or 75 + else: + warning = warning or 600 + critical = critical or 3600 + rs_status = {} + slaveDelays = {} + try: + set_read_preference(con.admin) + + # Get replica set status + try: + rs_status = con.admin.command("replSetGetStatus") + except pymongo.errors.OperationFailure, e: + if e.code == None and str(e).find('failed: not running with --replSet"'): + print "OK - Not running with replSet" + return 0 + + serverVersion = tuple(con.server_info()['version'].split('.')) + if serverVersion >= tuple("2.0.0".split(".")): + # + # check for version greater then 2.0 + # + rs_conf = con.local.system.replset.find_one() + for member in rs_conf['members']: + if member.get('slaveDelay') is not None: + slaveDelays[member['host']] = member.get('slaveDelay') + else: + slaveDelays[member['host']] = 0 + + # Find the primary and/or the current node + primary_node = None + host_node = None + + for member in rs_status["members"]: + if member["stateStr"] == "PRIMARY": + primary_node = member + if member["name"].split(':')[0] == host and int(member["name"].split(':')[1]) == port: + host_node = member + + # Check if we're in the middle of an election and don't have a primary + if primary_node is None: + print "WARNING - No primary defined. In an election?" + return 1 + + # Check if we failed to find the current host + # below should never happen + if host_node is None: + print "CRITICAL - Unable to find host '" + host + "' in replica set." + return 2 + + # Is the specified host the primary? + if host_node["stateStr"] == "PRIMARY": + if max_lag == False: + print "OK - This is the primary." + return 0 + else: + #get the maximal replication lag + data = "" + maximal_lag = 0 + for member in rs_status['members']: + if not member['stateStr'] == "ARBITER": + lastSlaveOpTime = member['optimeDate'] + replicationLag = abs(primary_node["optimeDate"] - lastSlaveOpTime).seconds - slaveDelays[member['name']] + data = data + member['name'] + " lag=%d;" % replicationLag + maximal_lag = max(maximal_lag, replicationLag) + if percent: + err, con = mongo_connect(primary_node['name'].split(':')[0], int(primary_node['name'].split(':')[1]), False, user, passwd) + if err != 0: + return err + primary_timediff = replication_get_time_diff(con) + maximal_lag = int(float(maximal_lag) / float(primary_timediff) * 100) + message = "Maximal lag is " + str(maximal_lag) + " percents" + message += performance_data(perf_data, [(maximal_lag, "replication_lag_percent", warning, critical)]) + else: + message = "Maximal lag is " + str(maximal_lag) + " seconds" + message += performance_data(perf_data, [(maximal_lag, "replication_lag", warning, critical)]) + return check_levels(maximal_lag, warning, critical, message) + elif host_node["stateStr"] == "ARBITER": + print "OK - This is an arbiter" + return 0 + + # Find the difference in optime between current node and PRIMARY + + optime_lag = abs(primary_node["optimeDate"] - host_node["optimeDate"]) + + if host_node['name'] in slaveDelays: + slave_delay = slaveDelays[host_node['name']] + elif host_node['name'].endswith(':27017') and host_node['name'][:-len(":27017")] in slaveDelays: + slave_delay = slaveDelays[host_node['name'][:-len(":27017")]] + else: + raise Exception("Unable to determine slave delay for {0}".format(host_node['name'])) + + try: # work starting from python2.7 + lag = optime_lag.total_seconds() + except: + lag = float(optime_lag.seconds + optime_lag.days * 24 * 3600) + + if percent: + err, con = mongo_connect(primary_node['name'].split(':')[0], int(primary_node['name'].split(':')[1]), False, user, passwd) + if err != 0: + return err + primary_timediff = replication_get_time_diff(con) + if primary_timediff != 0: + lag = int(float(lag) / float(primary_timediff) * 100) + else: + lag = 0 + message = "Lag is " + str(lag) + " percents" + message += performance_data(perf_data, [(lag, "replication_lag_percent", warning, critical)]) + else: + message = "Lag is " + str(lag) + " seconds" + message += performance_data(perf_data, [(lag, "replication_lag", warning, critical)]) + return check_levels(lag, warning + slaveDelays[host_node['name']], critical + slaveDelays[host_node['name']], message) + else: + # + # less than 2.0 check + # + # Get replica set status + rs_status = con.admin.command("replSetGetStatus") + + # Find the primary and/or the current node + primary_node = None + host_node = None + for member in rs_status["members"]: + if member["stateStr"] == "PRIMARY": + primary_node = (member["name"], member["optimeDate"]) + if member["name"].split(":")[0].startswith(host): + host_node = member + + # Check if we're in the middle of an election and don't have a primary + if primary_node is None: + print "WARNING - No primary defined. In an election?" + sys.exit(1) + + # Is the specified host the primary? + if host_node["stateStr"] == "PRIMARY": + print "OK - This is the primary." + sys.exit(0) + + # Find the difference in optime between current node and PRIMARY + optime_lag = abs(primary_node[1] - host_node["optimeDate"]) + lag = optime_lag.seconds + if percent: + err, con = mongo_connect(primary_node['name'].split(':')[0], int(primary_node['name'].split(':')[1])) + if err != 0: + return err + primary_timediff = replication_get_time_diff(con) + lag = int(float(lag) / float(primary_timediff) * 100) + message = "Lag is " + str(lag) + " percents" + message += performance_data(perf_data, [(lag, "replication_lag_percent", warning, critical)]) + else: + message = "Lag is " + str(lag) + " seconds" + message += performance_data(perf_data, [(lag, "replication_lag", warning, critical)]) + return check_levels(lag, warning, critical, message) + + except Exception, e: + return exit_with_general_critical(e) + + +def check_memory(con, warning, critical, perf_data, mapped_memory): + # + # These thresholds are basically meaningless, and must be customized to your system's ram + # + + # Get the total system merory and calculate based on that how much memory used by Mongodb is ok or not. + meminfo = open('/proc/meminfo').read() + matched = re.search(r'^MemTotal:\s+(\d+)', meminfo) + if matched: + mem_total_kB = int(matched.groups()[0]) + + # Old way + #critical = critical or 16 + # The new way. if using >80% then warn, if >90% then critical level + warning = warning or (mem_total_kB * 0.8) / 1024.0 / 1024.0 + critical = critical or (mem_total_kB * 0.9) / 1024.0 / 1024.0 + + # debugging + #print "mem total: {0}kb, warn: {1}GB, crit: {2}GB".format(mem_total_kB,warning, critical) + + try: + data = get_server_status(con) + if not data['mem']['supported'] and not mapped_memory: + print "OK - Platform not supported for memory info" + return 0 + # + # convert to gigs + # + message = "Memory Usage:" + try: + mem_resident = float(data['mem']['resident']) / 1024.0 + message += " %.2fGB resident," % (mem_resident) + except: + mem_resident = 0 + message += " resident unsupported," + try: + mem_virtual = float(data['mem']['virtual']) / 1024.0 + message += " %.2fGB virtual," % mem_virtual + except: + mem_virtual = 0 + message += " virtual unsupported," + try: + mem_mapped = float(data['mem']['mapped']) / 1024.0 + message += " %.2fGB mapped," % mem_mapped + except: + mem_mapped = 0 + message += " mapped unsupported," + try: + mem_mapped_journal = float(data['mem']['mappedWithJournal']) / 1024.0 + message += " %.2fGB mappedWithJournal" % mem_mapped_journal + except: + mem_mapped_journal = 0 + message += performance_data(perf_data, [("%.2f" % mem_resident, "memory_usage", warning, critical), + ("%.2f" % mem_mapped, "memory_mapped"), ("%.2f" % mem_virtual, "memory_virtual"), ("%.2f" % mem_mapped_journal, "mappedWithJournal")]) + #added for unsupported systems like Solaris + if mapped_memory and mem_resident == 0: + return check_levels(mem_mapped, warning, critical, message) + else: + return check_levels(mem_resident, warning, critical, message) + + except Exception, e: + return exit_with_general_critical(e) + + +def check_memory_mapped(con, warning, critical, perf_data): + # + # These thresholds are basically meaningless, and must be customized to your application + # + warning = warning or 8 + critical = critical or 16 + try: + data = get_server_status(con) + if not data['mem']['supported']: + print "OK - Platform not supported for memory info" + return 0 + # + # convert to gigs + # + message = "Memory Usage:" + try: + mem_mapped = float(data['mem']['mapped']) / 1024.0 + message += " %.2fGB mapped," % mem_mapped + except: + mem_mapped = -1 + message += " mapped unsupported," + try: + mem_mapped_journal = float(data['mem']['mappedWithJournal']) / 1024.0 + message += " %.2fGB mappedWithJournal" % mem_mapped_journal + except: + mem_mapped_journal = 0 + message += performance_data(perf_data, [("%.2f" % mem_mapped, "memory_mapped"), ("%.2f" % mem_mapped_journal, "mappedWithJournal")]) + + if not mem_mapped == -1: + return check_levels(mem_mapped, warning, critical, message) + else: + print "OK - Server does not provide mem.mapped info" + return 0 + + except Exception, e: + return exit_with_general_critical(e) + + +def check_lock(con, warning, critical, perf_data): + warning = warning or 10 + critical = critical or 30 + try: + data = get_server_status(con) + # + # calculate percentage + # + lockTime = data['globalLock']['lockTime'] + totalTime = data['globalLock']['totalTime'] + if lockTime > totalTime: + lock_percentage = 0.00 + else: + lock_percentage = float(lockTime) / float(totalTime) * 100 + message = "Lock Percentage: %.2f%%" % lock_percentage + message += performance_data(perf_data, [("%.2f" % lock_percentage, "lock_percentage", warning, critical)]) + return check_levels(lock_percentage, warning, critical, message) + + except Exception, e: + return exit_with_general_critical(e) + + +def check_flushing(con, warning, critical, avg, perf_data): + # + # These thresholds mean it's taking 5 seconds to perform a background flush to issue a warning + # and 10 seconds to issue a critical. + # + warning = warning or 5000 + critical = critical or 15000 + try: + data = get_server_status(con) + if avg: + flush_time = float(data['backgroundFlushing']['average_ms']) + stat_type = "Average" + else: + flush_time = float(data['backgroundFlushing']['last_ms']) + stat_type = "Last" + + message = "%s Flush Time: %.2fms" % (stat_type, flush_time) + message += performance_data(perf_data, [("%.2fms" % flush_time, "%s_flush_time" % stat_type.lower(), warning, critical)]) + + return check_levels(flush_time, warning, critical, message) + + except Exception, e: + return exit_with_general_critical(e) + + +def index_miss_ratio(con, warning, critical, perf_data): + warning = warning or 10 + critical = critical or 30 + try: + data = get_server_status(con) + + try: + serverVersion = tuple(con.server_info()['version'].split('.')) + if serverVersion >= tuple("2.4.0".split(".")): + miss_ratio = float(data['indexCounters']['missRatio']) + else: + miss_ratio = float(data['indexCounters']['btree']['missRatio']) + except KeyError: + not_supported_msg = "not supported on this platform" + if data['indexCounters'].has_key('note'): + print "OK - MongoDB says: " + not_supported_msg + return 0 + else: + print "WARNING - Can't get counter from MongoDB" + return 1 + + message = "Miss Ratio: %.2f" % miss_ratio + message += performance_data(perf_data, [("%.2f" % miss_ratio, "index_miss_ratio", warning, critical)]) + + return check_levels(miss_ratio, warning, critical, message) + + except Exception, e: + return exit_with_general_critical(e) + +def check_replset_quorum(con, perf_data): + db = con['admin'] + warning = 1 + critical = 2 + primary = 0 + + try: + rs_members = db.command("replSetGetStatus")['members'] + + for member in rs_members: + if member['state'] == 1: + primary += 1 + + if primary == 1: + state = 0 + message = "Cluster is quorate" + else: + state = 2 + message = "Cluster is not quorate and cannot operate" + + return check_levels(state, warning, critical, message) + except Exception, e: + return exit_with_general_critical(e) + + + +def check_replset_state(con, perf_data, warning="", critical=""): + try: + warning = [int(x) for x in warning.split(",")] + except: + warning = [0, 3, 5] + try: + critical = [int(x) for x in critical.split(",")] + except: + critical = [8, 4, -1] + + ok = range(-1, 8) # should include the range of all posiible values + try: + try: + try: + set_read_preference(con.admin) + data = con.admin.command(pymongo.son_manipulator.SON([('replSetGetStatus', 1)])) + except: + data = con.admin.command(son.SON([('replSetGetStatus', 1)])) + state = int(data['myState']) + except pymongo.errors.OperationFailure, e: + if e.code == None and str(e).find('failed: not running with --replSet"'): + state = -1 + + if state == 8: + message = "State: %i (Down)" % state + elif state == 4: + message = "State: %i (Fatal error)" % state + elif state == 0: + message = "State: %i (Starting up, phase1)" % state + elif state == 3: + message = "State: %i (Recovering)" % state + elif state == 5: + message = "State: %i (Starting up, phase2)" % state + elif state == 1: + message = "State: %i (Primary)" % state + elif state == 2: + message = "State: %i (Secondary)" % state + elif state == 7: + message = "State: %i (Arbiter)" % state + elif state == -1: + message = "Not running with replSet" + else: + message = "State: %i (Unknown state)" % state + message += performance_data(perf_data, [(state, "state")]) + return check_levels(state, warning, critical, message, ok) + except Exception, e: + return exit_with_general_critical(e) + + +def check_databases(con, warning, critical, perf_data=None): + try: + try: + set_read_preference(con.admin) + data = con.admin.command(pymongo.son_manipulator.SON([('listDatabases', 1)])) + except: + data = con.admin.command(son.SON([('listDatabases', 1)])) + + count = len(data['databases']) + message = "Number of DBs: %.0f" % count + message += performance_data(perf_data, [(count, "databases", warning, critical, message)]) + return check_levels(count, warning, critical, message) + except Exception, e: + return exit_with_general_critical(e) + + +def check_collections(con, warning, critical, perf_data=None): + try: + try: + set_read_preference(con.admin) + data = con.admin.command(pymongo.son_manipulator.SON([('listDatabases', 1)])) + except: + data = con.admin.command(son.SON([('listDatabases', 1)])) + + count = 0 + for db in data['databases']: + dbase = con[db['name']] + set_read_preference(dbase) + count += len(dbase.collection_names()) + + message = "Number of collections: %.0f" % count + message += performance_data(perf_data, [(count, "collections", warning, critical, message)]) + return check_levels(count, warning, critical, message) + + except Exception, e: + return exit_with_general_critical(e) + + +def check_all_databases_size(con, warning, critical, perf_data): + warning = warning or 100 + critical = critical or 1000 + try: + set_read_preference(con.admin) + all_dbs_data = con.admin.command(pymongo.son_manipulator.SON([('listDatabases', 1)])) + except: + all_dbs_data = con.admin.command(son.SON([('listDatabases', 1)])) + + total_storage_size = 0 + message = "" + perf_data_param = [()] + for db in all_dbs_data['databases']: + database = db['name'] + data = con[database].command('dbstats') + storage_size = round(data['storageSize'] / 1024 / 1024, 1) + message += "; Database %s size: %.0f MB" % (database, storage_size) + perf_data_param.append((storage_size, database + "_database_size")) + total_storage_size += storage_size + + perf_data_param[0] = (total_storage_size, "total_size", warning, critical) + message += performance_data(perf_data, perf_data_param) + message = "Total size: %.0f MB" % total_storage_size + message + return check_levels(total_storage_size, warning, critical, message) + + +def check_database_size(con, database, warning, critical, perf_data): + warning = warning or 100 + critical = critical or 1000 + perfdata = "" + try: + set_read_preference(con.admin) + data = con[database].command('dbstats') + storage_size = data['storageSize'] / 1024 / 1024 + if perf_data: + perfdata += " | database_size=%i;%i;%i" % (storage_size, warning, critical) + #perfdata += " database=%s" %(database) + + if storage_size >= critical: + print "CRITICAL - Database size: %.0f MB, Database: %s%s" % (storage_size, database, perfdata) + return 2 + elif storage_size >= warning: + print "WARNING - Database size: %.0f MB, Database: %s%s" % (storage_size, database, perfdata) + return 1 + else: + print "OK - Database size: %.0f MB, Database: %s%s" % (storage_size, database, perfdata) + return 0 + except Exception, e: + return exit_with_general_critical(e) + + +def check_database_indexes(con, database, warning, critical, perf_data): + # + # These thresholds are basically meaningless, and must be customized to your application + # + warning = warning or 100 + critical = critical or 1000 + perfdata = "" + try: + set_read_preference(con.admin) + data = con[database].command('dbstats') + index_size = data['indexSize'] / 1024 / 1024 + if perf_data: + perfdata += " | database_indexes=%i;%i;%i" % (index_size, warning, critical) + + if index_size >= critical: + print "CRITICAL - %s indexSize: %.0f MB %s" % (database, index_size, perfdata) + return 2 + elif index_size >= warning: + print "WARNING - %s indexSize: %.0f MB %s" % (database, index_size, perfdata) + return 1 + else: + print "OK - %s indexSize: %.0f MB %s" % (database, index_size, perfdata) + return 0 + except Exception, e: + return exit_with_general_critical(e) + + +def check_collection_indexes(con, database, collection, warning, critical, perf_data): + # + # These thresholds are basically meaningless, and must be customized to your application + # + warning = warning or 100 + critical = critical or 1000 + perfdata = "" + try: + set_read_preference(con.admin) + data = con[database].command('collstats', collection) + total_index_size = data['totalIndexSize'] / 1024 / 1024 + if perf_data: + perfdata += " | collection_indexes=%i;%i;%i" % (total_index_size, warning, critical) + + if total_index_size >= critical: + print "CRITICAL - %s.%s totalIndexSize: %.0f MB %s" % (database, collection, total_index_size, perfdata) + return 2 + elif total_index_size >= warning: + print "WARNING - %s.%s totalIndexSize: %.0f MB %s" % (database, collection, total_index_size, perfdata) + return 1 + else: + print "OK - %s.%s totalIndexSize: %.0f MB %s" % (database, collection, total_index_size, perfdata) + return 0 + except Exception, e: + return exit_with_general_critical(e) + + +def check_queues(con, warning, critical, perf_data): + warning = warning or 10 + critical = critical or 30 + try: + data = get_server_status(con) + + total_queues = float(data['globalLock']['currentQueue']['total']) + readers_queues = float(data['globalLock']['currentQueue']['readers']) + writers_queues = float(data['globalLock']['currentQueue']['writers']) + message = "Current queue is : total = %d, readers = %d, writers = %d" % (total_queues, readers_queues, writers_queues) + message += performance_data(perf_data, [(total_queues, "total_queues", warning, critical), (readers_queues, "readers_queues"), (writers_queues, "writers_queues")]) + return check_levels(total_queues, warning, critical, message) + + except Exception, e: + return exit_with_general_critical(e) + +def check_collection_size(con, database, collection, warning, critical, perf_data): + warning = warning or 100 + critical = critical or 1000 + perfdata = "" + try: + set_read_preference(con.admin) + data = con[database].command('collstats', collection) + size = data['size'] / 1024 / 1024 + if perf_data: + perfdata += " | collection_size=%i;%i;%i" % (size, warning, critical) + + if size >= critical: + print "CRITICAL - %s.%s size: %.0f MB %s" % (database, collection, size, perfdata) + return 2 + elif size >= warning: + print "WARNING - %s.%s size: %.0f MB %s" % (database, collection, size, perfdata) + return 1 + else: + print "OK - %s.%s size: %.0f MB %s" % (database, collection, size, perfdata) + return 0 + except Exception, e: + return exit_with_general_critical(e) + +def check_queries_per_second(con, query_type, warning, critical, perf_data): + warning = warning or 250 + critical = critical or 500 + + if query_type not in ['insert', 'query', 'update', 'delete', 'getmore', 'command']: + return exit_with_general_critical("The query type of '%s' is not valid" % query_type) + + try: + db = con.local + data = get_server_status(con) + + # grab the count + num = int(data['opcounters'][query_type]) + + # do the math + last_count = db.nagios_check.find_one({'check': 'query_counts'}) + try: + ts = int(time.time()) + diff_query = num - last_count['data'][query_type]['count'] + diff_ts = ts - last_count['data'][query_type]['ts'] + + query_per_sec = float(diff_query) / float(diff_ts) + + # update the count now + db.nagios_check.update({u'_id': last_count['_id']}, {'$set': {"data.%s" % query_type: {'count': num, 'ts': int(time.time())}}}) + + message = "Queries / Sec: %f" % query_per_sec + message += performance_data(perf_data, [(query_per_sec, "%s_per_sec" % query_type, warning, critical, message)]) + except KeyError: + # + # since it is the first run insert it + query_per_sec = 0 + message = "First run of check.. no data" + db.nagios_check.update({u'_id': last_count['_id']}, {'$set': {"data.%s" % query_type: {'count': num, 'ts': int(time.time())}}}) + except TypeError: + # + # since it is the first run insert it + query_per_sec = 0 + message = "First run of check.. no data" + db.nagios_check.insert({'check': 'query_counts', 'data': {query_type: {'count': num, 'ts': int(time.time())}}}) + + return check_levels(query_per_sec, warning, critical, message) + + except Exception, e: + return exit_with_general_critical(e) + + +def check_oplog(con, warning, critical, perf_data): + """ Checking the oplog time - the time of the log currntly saved in the oplog collection + defaults: + critical 4 hours + warning 24 hours + those can be changed as usual with -C and -W parameters""" + warning = warning or 24 + critical = critical or 4 + try: + db = con.local + ol = db.system.namespaces.find_one({"name": "local.oplog.rs"}) + if (db.system.namespaces.find_one({"name": "local.oplog.rs"}) != None): + oplog = "oplog.rs" + else: + ol = db.system.namespaces.find_one({"name": "local.oplog.$main"}) + if (db.system.namespaces.find_one({"name": "local.oplog.$main"}) != None): + oplog = "oplog.$main" + else: + message = "neither master/slave nor replica set replication detected" + return check_levels(None, warning, critical, message) + + try: + set_read_preference(con.admin) + data = con.local.command(pymongo.son_manipulator.SON([('collstats', oplog)])) + except: + data = con.admin.command(son.SON([('collstats', oplog)])) + + ol_size = data['size'] + ol_storage_size = data['storageSize'] + ol_used_storage = int(float(ol_size) / ol_storage_size * 100 + 1) + ol = con.local[oplog] + firstc = ol.find().sort("$natural", pymongo.ASCENDING).limit(1)[0]['ts'] + lastc = ol.find().sort("$natural", pymongo.DESCENDING).limit(1)[0]['ts'] + time_in_oplog = (lastc.as_datetime() - firstc.as_datetime()) + message = "Oplog saves " + str(time_in_oplog) + " %d%% used" % ol_used_storage + try: # work starting from python2.7 + hours_in_oplog = time_in_oplog.total_seconds() / 60 / 60 + except: + hours_in_oplog = float(time_in_oplog.seconds + time_in_oplog.days * 24 * 3600) / 60 / 60 + approx_level = hours_in_oplog * 100 / ol_used_storage + message += performance_data(perf_data, [("%.2f" % hours_in_oplog, 'oplog_time', warning, critical), ("%.2f " % approx_level, 'oplog_time_100_percent_used')]) + return check_levels(-approx_level, -warning, -critical, message) + + except Exception, e: + return exit_with_general_critical(e) + + +def check_journal_commits_in_wl(con, warning, critical, perf_data): + """ Checking the number of commits which occurred in the db's write lock. +Most commits are performed outside of this lock; committed while in the write lock is undesirable. +Under very high write situations it is normal for this value to be nonzero. """ + + warning = warning or 10 + critical = critical or 40 + try: + data = get_server_status(con) + j_commits_in_wl = data['dur']['commitsInWriteLock'] + message = "Journal commits in DB write lock : %d" % j_commits_in_wl + message += performance_data(perf_data, [(j_commits_in_wl, "j_commits_in_wl", warning, critical)]) + return check_levels(j_commits_in_wl, warning, critical, message) + + except Exception, e: + return exit_with_general_critical(e) + + +def check_journaled(con, warning, critical, perf_data): + """ Checking the average amount of data in megabytes written to the recovery log in the last four seconds""" + + warning = warning or 20 + critical = critical or 40 + try: + data = get_server_status(con) + journaled = data['dur']['journaledMB'] + message = "Journaled : %.2f MB" % journaled + message += performance_data(perf_data, [("%.2f" % journaled, "journaled", warning, critical)]) + return check_levels(journaled, warning, critical, message) + + except Exception, e: + return exit_with_general_critical(e) + + +def check_write_to_datafiles(con, warning, critical, perf_data): + """ Checking the average amount of data in megabytes written to the databases datafiles in the last four seconds. +As these writes are already journaled, they can occur lazily, and thus the number indicated here may be lower +than the amount physically written to disk.""" + warning = warning or 20 + critical = critical or 40 + try: + data = get_server_status(con) + writes = data['dur']['writeToDataFilesMB'] + message = "Write to data files : %.2f MB" % writes + message += performance_data(perf_data, [("%.2f" % writes, "write_to_data_files", warning, critical)]) + return check_levels(writes, warning, critical, message) + + except Exception, e: + return exit_with_general_critical(e) + + +def get_opcounters(data, opcounters_name, host): + try: + insert = data[opcounters_name]['insert'] + query = data[opcounters_name]['query'] + update = data[opcounters_name]['update'] + delete = data[opcounters_name]['delete'] + getmore = data[opcounters_name]['getmore'] + command = data[opcounters_name]['command'] + except KeyError, e: + return 0, [0] * 100 + total_commands = insert + query + update + delete + getmore + command + new_vals = [total_commands, insert, query, update, delete, getmore, command] + return maintain_delta(new_vals, host, opcounters_name) + + +def check_opcounters(con, host, warning, critical, perf_data): + """ A function to get all opcounters delta per minute. In case of a replication - gets the opcounters+opcountersRepl""" + warning = warning or 10000 + critical = critical or 15000 + + data = get_server_status(con) + err1, delta_opcounters = get_opcounters(data, 'opcounters', host) + err2, delta_opcounters_repl = get_opcounters(data, 'opcountersRepl', host) + if err1 == 0 and err2 == 0: + delta = [(x + y) for x, y in zip(delta_opcounters, delta_opcounters_repl)] + delta[0] = delta_opcounters[0] # only the time delta shouldn't be summarized + per_minute_delta = [int(x / delta[0] * 60) for x in delta[1:]] + message = "Test succeeded , old values missing" + message = "Opcounters: total=%d,insert=%d,query=%d,update=%d,delete=%d,getmore=%d,command=%d" % tuple(per_minute_delta) + message += performance_data(perf_data, ([(per_minute_delta[0], "total", warning, critical), (per_minute_delta[1], "insert"), + (per_minute_delta[2], "query"), (per_minute_delta[3], "update"), (per_minute_delta[5], "delete"), + (per_minute_delta[5], "getmore"), (per_minute_delta[6], "command")])) + return check_levels(per_minute_delta[0], warning, critical, message) + else: + return exit_with_general_critical("problem reading data from temp file") + + +def check_current_lock(con, host, warning, critical, perf_data): + """ A function to get current lock percentage and not a global one, as check_lock function does""" + warning = warning or 10 + critical = critical or 30 + data = get_server_status(con) + + lockTime = float(data['globalLock']['lockTime']) + totalTime = float(data['globalLock']['totalTime']) + + err, delta = maintain_delta([totalTime, lockTime], host, "locktime") + if err == 0: + lock_percentage = delta[2] / delta[1] * 100 # lockTime/totalTime*100 + message = "Current Lock Percentage: %.2f%%" % lock_percentage + message += performance_data(perf_data, [("%.2f" % lock_percentage, "current_lock_percentage", warning, critical)]) + return check_levels(lock_percentage, warning, critical, message) + else: + return exit_with_general_warning("problem reading data from temp file") + + +def check_page_faults(con, host, warning, critical, perf_data): + """ A function to get page_faults per second from the system""" + warning = warning or 10 + critical = critical or 30 + data = get_server_status(con) + + try: + page_faults = float(data['extra_info']['page_faults']) + except: + # page_faults unsupported on the underlaying system + return exit_with_general_critical("page_faults unsupported on the underlaying system") + + err, delta = maintain_delta([page_faults], host, "page_faults") + if err == 0: + page_faults_ps = delta[1] / delta[0] + message = "Page faults : %.2f ps" % page_faults_ps + message += performance_data(perf_data, [("%.2f" % page_faults_ps, "page_faults_ps", warning, critical)]) + return check_levels(page_faults_ps, warning, critical, message) + else: + return exit_with_general_warning("problem reading data from temp file") + + +def check_asserts(con, host, warning, critical, perf_data): + """ A function to get asserts from the system""" + warning = warning or 1 + critical = critical or 10 + data = get_server_status(con) + + asserts = data['asserts'] + + #{ "regular" : 0, "warning" : 6, "msg" : 0, "user" : 12, "rollovers" : 0 } + regular = asserts['regular'] + warning_asserts = asserts['warning'] + msg = asserts['msg'] + user = asserts['user'] + rollovers = asserts['rollovers'] + + err, delta = maintain_delta([regular, warning_asserts, msg, user, rollovers], host, "asserts") + + if err == 0: + if delta[5] != 0: + #the number of rollovers were increased + warning = -1 # no matter the metrics this situation should raise a warning + # if this is normal rollover - the warning will not appear again, but if there will be a lot of asserts + # the warning will stay for a long period of time + # although this is not a usual situation + + regular_ps = delta[1] / delta[0] + warning_ps = delta[2] / delta[0] + msg_ps = delta[3] / delta[0] + user_ps = delta[4] / delta[0] + rollovers_ps = delta[5] / delta[0] + total_ps = regular_ps + warning_ps + msg_ps + user_ps + message = "Total asserts : %.2f ps" % total_ps + message += performance_data(perf_data, [(total_ps, "asserts_ps", warning, critical), (regular_ps, "regular"), + (warning_ps, "warning"), (msg_ps, "msg"), (user_ps, "user")]) + return check_levels(total_ps, warning, critical, message) + else: + return exit_with_general_warning("problem reading data from temp file") + + +def get_stored_primary_server_name(db): + """ get the stored primary server name from db. """ + if "last_primary_server" in db.collection_names(): + stored_primary_server = db.last_primary_server.find_one()["server"] + else: + stored_primary_server = None + + return stored_primary_server + + +def check_replica_primary(con, host, warning, critical, perf_data, replicaset): + """ A function to check if the primary server of a replica set has changed """ + if warning is None and critical is None: + warning = 1 + warning = warning or 2 + critical = critical or 2 + + primary_status = 0 + message = "Primary server has not changed" + db = con["nagios"] + data = get_server_status(con) + if replicaset != data['repl'].get('setName'): + message = "Replica set requested: %s differs from the one found: %s" % (replicaset, data['repl'].get('setName')) + primary_status = 2 + return check_levels(primary_status, warning, critical, message) + current_primary = data['repl'].get('primary') + saved_primary = get_stored_primary_server_name(db) + if current_primary is None: + current_primary = "None" + if saved_primary is None: + saved_primary = "None" + if current_primary != saved_primary: + last_primary_server_record = {"server": current_primary} + db.last_primary_server.update({"_id": "last_primary"}, {"$set": last_primary_server_record}, upsert=True, safe=True) + message = "Primary server has changed from %s to %s" % (saved_primary, current_primary) + primary_status = 1 + return check_levels(primary_status, warning, critical, message) + + +def check_page_faults(con, sample_time, warning, critical, perf_data): + warning = warning or 10 + critical = critical or 20 + try: + try: + set_read_preference(con.admin) + data1 = con.admin.command(pymongo.son_manipulator.SON([('serverStatus', 1)])) + time.sleep(sample_time) + data2 = con.admin.command(pymongo.son_manipulator.SON([('serverStatus', 1)])) + except: + data1 = con.admin.command(son.SON([('serverStatus', 1)])) + time.sleep(sample_time) + data2 = con.admin.command(son.SON([('serverStatus', 1)])) + + try: + #on linux servers only + page_faults = (int(data2['extra_info']['page_faults']) - int(data1['extra_info']['page_faults'])) / sample_time + except KeyError: + print "WARNING - Can't get extra_info.page_faults counter from MongoDB" + sys.exit(1) + + message = "Page Faults: %i" % (page_faults) + + message += performance_data(perf_data, [(page_faults, "page_faults", warning, critical)]) + check_levels(page_faults, warning, critical, message) + + except Exception, e: + exit_with_general_critical(e) + + +def chunks_balance(con, database, collection, warning, critical): + warning = warning or 10 + critical = critical or 20 + nsfilter = database + "." + collection + try: + try: + set_read_preference(con.admin) + col = con.config.chunks + nscount = col.find({"ns": nsfilter}).count() + shards = col.distinct("shard") + + except: + print "WARNING - Can't get chunks infos from MongoDB" + sys.exit(1) + + if nscount == 0: + print "WARNING - Namespace %s is not sharded" % (nsfilter) + sys.exit(1) + + avgchunksnb = nscount / len(shards) + warningnb = avgchunksnb * warning / 100 + criticalnb = avgchunksnb * critical / 100 + + for shard in shards: + delta = abs(avgchunksnb - col.find({"ns": nsfilter, "shard": shard}).count()) + message = "Namespace: %s, Shard name: %s, Chunk delta: %i" % (nsfilter, shard, delta) + + if delta >= criticalnb and delta > 0: + print "CRITICAL - Chunks not well balanced " + message + sys.exit(2) + elif delta >= warningnb and delta > 0: + print "WARNING - Chunks not well balanced " + message + sys.exit(1) + + print "OK - Chunks well balanced across shards" + sys.exit(0) + + except Exception, e: + exit_with_general_critical(e) + + print "OK - Chunks well balanced across shards" + sys.exit(0) + + +def check_connect_primary(con, warning, critical, perf_data): + warning = warning or 3 + critical = critical or 6 + + try: + try: + set_read_preference(con.admin) + data = con.admin.command(pymongo.son_manipulator.SON([('isMaster', 1)])) + except: + data = con.admin.command(son.SON([('isMaster', 1)])) + + if data['ismaster'] == True: + print "OK - This server is primary" + return 0 + + phost = data['primary'].split(':')[0] + pport = int(data['primary'].split(':')[1]) + start = time.time() + + err, con = mongo_connect(phost, pport) + if err != 0: + return err + + pconn_time = time.time() - start + pconn_time = round(pconn_time, 0) + message = "Connection to primary server " + data['primary'] + " took %i seconds" % pconn_time + message += performance_data(perf_data, [(pconn_time, "connection_time", warning, critical)]) + + return check_levels(pconn_time, warning, critical, message) + + except Exception, e: + return exit_with_general_critical(e) + + +def check_collection_state(con, database, collection): + try: + con[database][collection].find_one() + print "OK - Collection %s.%s is reachable " % (database, collection) + return 0 + + except Exception, e: + return exit_with_general_critical(e) + + +def check_row_count(con, database, collection, warning, critical, perf_data): + try: + count = con[database][collection].count() + message = "Row count: %i" % (count) + message += performance_data(perf_data, [(count, "row_count", warning, critical)]) + + return check_levels(count, warning, critical, message) + + except Exception, e: + return exit_with_general_critical(e) + + +def build_file_name(host, action): + #done this way so it will work when run independently and from shell + module_name = re.match('(.*//*)*(.*)\..*', __file__).group(2) + return "/tmp/" + module_name + "_data/" + host + "-" + action + ".data" + + +def ensure_dir(f): + d = os.path.dirname(f) + if not os.path.exists(d): + os.makedirs(d) + + +def write_values(file_name, string): + f = None + try: + f = open(file_name, 'w') + except IOError, e: + #try creating + if (e.errno == 2): + ensure_dir(file_name) + f = open(file_name, 'w') + else: + raise IOError(e) + f.write(string) + f.close() + return 0 + + +def read_values(file_name): + data = None + try: + f = open(file_name, 'r') + data = f.read() + f.close() + return 0, data + except IOError, e: + if (e.errno == 2): + #no previous data + return 1, '' + except Exception, e: + return 2, None + + +def calc_delta(old, new): + delta = [] + if (len(old) != len(new)): + raise Exception("unequal number of parameters") + for i in range(0, len(old)): + val = float(new[i]) - float(old[i]) + if val < 0: + val = new[i] + delta.append(val) + return 0, delta + + +def maintain_delta(new_vals, host, action): + file_name = build_file_name(host, action) + err, data = read_values(file_name) + old_vals = data.split(';') + new_vals = [str(int(time.time()))] + new_vals + delta = None + try: + err, delta = calc_delta(old_vals, new_vals) + except: + err = 2 + write_res = write_values(file_name, ";" . join(str(x) for x in new_vals)) + return err + write_res, delta + + +def replication_get_time_diff(con): + col = 'oplog.rs' + local = con.local + ol = local.system.namespaces.find_one({"name": "local.oplog.$main"}) + if ol: + col = 'oplog.$main' + firstc = local[col].find().sort("$natural", 1).limit(1) + lastc = local[col].find().sort("$natural", -1).limit(1) + first = firstc.next() + last = lastc.next() + tfirst = first["ts"] + tlast = last["ts"] + delta = tlast.time - tfirst.time + return delta + +# +# main app +# +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/nagios-nrpe/files/plugins/check_mysql_instances b/nagios-nrpe/files/plugins/check_mysql_instances new file mode 100755 index 00000000..479b56c0 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_mysql_instances @@ -0,0 +1,11 @@ +#!/bin/sh + +for port in 3308 3309; do + /usr/lib/nagios/plugins/check_mysql -H 127.0.0.1 -P $port -u nrpe -p MYSQL_PASSWD + ret=$? + + [ $ret -ne 0 ] && exit $ret +done + +exit 0 + diff --git a/nagios-nrpe/files/plugins/check_mysql_queries b/nagios-nrpe/files/plugins/check_mysql_queries new file mode 100755 index 00000000..2c37c1eb --- /dev/null +++ b/nagios-nrpe/files/plugins/check_mysql_queries @@ -0,0 +1,63 @@ +#!/bin/sh + +# Avant d'exécuter le check : +# mysql> CREATE DATABASE nrpe; +# mysql> CREATE TABLE nrpe (test TINYINT); +# mysql> GRANT ALL ON nrpe.nrpe TO nrpe@localhost; + +usage() { + cat <&2 +Usage : $0 -H host -P port -u user -p password -b base +All these options are required +EOT +} + +while getopts ':H:P:u:p:b:' o +do + case $o in + H) + host=$OPTARG + ;; + P) + port=$OPTARG + ;; + u) + user=$OPTARG + ;; + p) + passwd=$OPTARG + ;; + b) + base=$OPTARG + ;; + ?) + usage + exit 2 + ;; + esac +done + +if [ -z $host ] || [ -z $port ] || [ -z $user ] || [ -z $passwd ] || [ -z $base ]; then + usage + exit 2 +fi + +MYSQLCMD="mysql -h $host -P $port -u $user -p$passwd $base" + +$MYSQLCMD -e "INSERT INTO nrpe (test) VALUES('4')" +if [ $? -ne 0 ]; then + echo "INSERT query failed" + exit 2 +fi + +$MYSQLCMD -e "SELECT test FROM nrpe" +if [ $? -ne 0 ]; then + echo "SELECT query failed" + exit 2 +fi + +$MYSQLCMD -e "DELETE FROM nrpe" +if [ $? -ne 0 ]; then + echo "DELETE query failed" + exit 2 +fi diff --git a/nagios-nrpe/files/plugins/check_nfsclient b/nagios-nrpe/files/plugins/check_nfsclient new file mode 100755 index 00000000..a5565985 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_nfsclient @@ -0,0 +1,23 @@ +#!/bin/sh + +HOSTNAME=$(hostname) +nfsmount=/srv/data + +mkdir $nfsmount/.nrpe + +touch $nfsmount/.nrpe/.nfstest.$HOSTNAME +if [ ! "$?" -eq "0" ]; then + echo "CRITICAL - error in check" + exit 2 +fi + +rm $nfsmount/.nrpe/.nfstest.$HOSTNAME +if [ ! "$?" -eq "0" ]; then + echo "CRITICAL - error in check" + exit 2 +fi + +rmdir $nfsmount/.nrpe + +echo "OK - All fine" +exit 0 diff --git a/nagios-nrpe/files/plugins/check_nfsserver b/nagios-nrpe/files/plugins/check_nfsserver new file mode 100755 index 00000000..80752bfd --- /dev/null +++ b/nagios-nrpe/files/plugins/check_nfsserver @@ -0,0 +1,14 @@ +#!/bin/sh +# Check if nfs server is running using rpcinfo + +rpcinfo -u localhost nfs +if [ $? -ne 0 ]; then + exit 2 +fi + +rpcinfo -t localhost nfs +if [ $? -ne 0 ]; then + exit 2 +fi + +# vim: set ft=shell diff --git a/nagios-nrpe/files/plugins/check_openvpn b/nagios-nrpe/files/plugins/check_openvpn new file mode 100755 index 00000000..b62ea6bc --- /dev/null +++ b/nagios-nrpe/files/plugins/check_openvpn @@ -0,0 +1,215 @@ +#!/usr/bin/perl -w + +####################################################################### +# +# Copyright (c) 2007 Jaime Gascon Romero +# +# 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 . +# +# $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/lib/nagios/plugins/"; +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 -p | --port [-P | --password] [-t | --timeout] + [-i | --ip] [-n | --numeric] [-C | --common_name] [-r | --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 diff --git a/nagios-nrpe/files/plugins/check_process b/nagios-nrpe/files/plugins/check_process new file mode 100755 index 00000000..780ef233 --- /dev/null +++ b/nagios-nrpe/files/plugins/check_process @@ -0,0 +1,7 @@ +#!/bin/bash + +rc=0 +for proc in cron rsyslogd ntpd munin-node; do + sudo /usr/lib/nagios//plugins/check_procs -C $proc -c 1: + rc=$(($rc|$?)) +done diff --git a/nagios-nrpe/files/plugins/check_spamd b/nagios-nrpe/files/plugins/check_spamd new file mode 100755 index 00000000..df8477da --- /dev/null +++ b/nagios-nrpe/files/plugins/check_spamd @@ -0,0 +1,197 @@ +#!/usr/bin/perl -w + +# $Id: check_spamd.pl,v 1.1 2009-08-11 09:47:11 tmartin Exp $ +# +# check SPAMD connections +# +# Copyright (c) 2005, 2006 Holger Weiss +# +# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# --------------------------------------------------------------- +# +# New in 1.4: +# - Checking SPAMD using a UNIX domain socket specified via the new +# -U/--socket option is now supported (thanks to Zeus Panchenko). +# +# New in 1.3: +# - The "-v" option now works again. +# +# New in 1.2: +# - A proper error message is reported if spamd(1) closes the connection +# unexpectedly. This happens, for example, if spamd(1)s "-A" option is +# used, but the client hasn't been added to the list of authorized hosts. +# - Minor code cleanup. + +use strict; +use Socket; +use Getopt::Long; +use lib "/usr/lib/nagios/plugins"; +use utils qw(%ERRORS $TIMEOUT &print_revision &support); +use vars qw($opt_H $opt_U $opt_V $opt_c $opt_h $opt_p $opt_t $opt_v $opt_w); + +sub talk_spamd ($$); +sub die_crit ($); +sub die_warn ($); +sub die_unknown ($); +sub print_usage (); +sub print_help (); + +my $PROGNAME = "check_spamd"; +my $PORT = 783; +my $CRIT = $TIMEOUT; +my ($spamd_target, $spamd_port, $warning, $critical, $timeout, $response, + $start, $duration); + +$SIG{'ALRM'} = sub { die_unknown("Timeout"); }; +$ENV{'PATH'}=''; +$ENV{'ENV'}=''; + +Getopt::Long::Configure("bundling"); +if (!GetOptions("V" => \$opt_V, "version" => \$opt_V, + "h" => \$opt_h, "help" => \$opt_h, + "v+" => \$opt_v, "verbose+" => \$opt_v, + "H=s" => \$opt_H, "hostname=s" => \$opt_H, + "U=s" => \$opt_U, "socket=s" => \$opt_U, + "c=i" => \$opt_c, "critical=i" => \$opt_c, + "p=i" => \$opt_p, "port=i" => \$opt_p, + "t=i" => \$opt_t, "timeout=i" => \$opt_t, + "w=i" => \$opt_w, "warning=i" => \$opt_w)) { + print "SPAMD UNKNOWN - Error processing command line options\n"; + print_usage(); + exit $ERRORS{'UNKNOWN'}; +} +if ($opt_V) { + print_revision($PROGNAME,'$Revision: 1.1 $ '); + exit $ERRORS{'OK'}; +} +if ($opt_h) { + print_help(); + exit $ERRORS{'OK'}; +} +if (not $opt_H and not $opt_U) { + print "SPAMD UNKNOWN - No target host or socket specified\n"; + print_usage(); + exit $ERRORS{'UNKNOWN'}; +} +if ($opt_U and ($opt_H or $opt_p)) { + print "SPAMD UNKNOWN - UNIX domain socket AND host/port specified\n"; + print_usage(); + exit $ERRORS{'UNKNOWN'}; +} +alarm($opt_t ? $opt_t : $TIMEOUT); +$critical = $opt_c ? $opt_c : $CRIT; +$warning = $opt_w ? $opt_w : $critical; +if ($warning > $critical) { + print "SPAMD UNKNOWN - Warning larger than critical threshold\n"; + print_usage(); + exit $ERRORS{'UNKNOWN'}; +} +if ($opt_U) { + $spamd_target = $opt_U; + $spamd_port = undef; +} else { + $spamd_target = $opt_H; + $spamd_port = $opt_p ? $opt_p : getservbyname("spamd", "tcp"); + $spamd_port = $PORT unless defined $spamd_port; +} +$start = time; +$response = talk_spamd($spamd_target, $spamd_port); +$duration = time - $start; +die_crit("$response - $duration sec. response time") if $duration >= $critical; +die_warn("$response - $duration sec. response time") if $duration >= $warning; +$response .= " - $duration sec. response time" if $opt_v; +print "SPAMD OK - Server response: $response\n"; +exit $ERRORS{'OK'}; + +sub talk_spamd ($$) { + my ($host, $port) = @_; + my ($buffer, $iaddr, $message, $n, $paddr, $proto, $status, $str); + + if ($port) { # check host name or IP address + $iaddr = inet_aton($host) + || die_unknown("Invalid hostname/address: $host"); + $paddr = sockaddr_in($port, $iaddr); + socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp')) + || die_unknown("Socket error: $!"); + } else { # check UNIX domain socket + $paddr = sockaddr_un($host); + socket(SOCK, PF_UNIX, SOCK_STREAM, 0) + || die_unknown("Socket error: $!"); + } + connect(SOCK, $paddr) || die_crit("Error connecting to $host: $!"); + defined(send(SOCK, "PING SPAMC/1.2\r\n", 0)) + || die_unknown("Error sending request to $host: $!"); + $buffer = ; + die_unknown("Connection reset by peer") unless $buffer; + close SOCK || die_unknown("Cannot close connection: $!"); + $n = ($proto, $status, $message) + = ($buffer =~ /^(\S+) +(\S+) +([^\r\n]+)/); + if ($n != 3) { + my $error = "Protocol error"; + + $error .= " - Server said: $buffer" if $buffer; + die_crit($error); + } + $str = "$proto $status $message"; + die_crit($str) unless $status == 0; + $str; +} + +sub die_unknown ($) { + printf "SPAMD UNKNOWN - %s\n", shift; + exit $ERRORS{'UNKNOWN'}; +} + +sub die_warn ($) { + printf "SPAMD WARNING - %s\n", shift; + exit $ERRORS{'WARNING'}; +} + +sub die_crit ($) { + printf "SPAMD CRITICAL - %s\n", shift; + exit $ERRORS{'CRITICAL'}; +} + +sub print_usage () { + print "Usage: $PROGNAME { -H host | -U socket } [-p port] [-w warn]\n" . + " [-c crit] [-t timeout] [-v]\n"; +} + +sub print_help () { + print_revision($PROGNAME, '$Revision: 1.1 $'); + print "Copyright (c) 2005, 2006 Holger Weiss\n\n"; + print "Check SPAMD connections with the specified host.\n\n"; + print_usage(); + print </dev/null - -- name: Adjust rights on private key - file: - path: /etc/ssl/private/{{ ansible_fqdn }}.key - owner: root - group: ssl-cert - mode: 0640 - -- name: Create certificate for default site - shell: openssl x509 -req -days 3650 -sha256 -in /etc/ssl/{{ ansible_fqdn }}.csr -signkey /etc/ssl/private/{{ ansible_fqdn }}.key -out /etc/ssl/certs/{{ ansible_fqdn }}.crt 2>/dev/null diff --git a/nginx/tasks/main.yml b/nginx/tasks/main.yml index fe1c4758..5cc397c4 100644 --- a/nginx/tasks/main.yml +++ b/nginx/tasks/main.yml @@ -7,17 +7,34 @@ tags: - nginx -- name: Install Nginx configuration - template: - src: nginx.conf.j2 +# TODO: find a way to override the main configuration +# without touching the main file + +- name: customize worker_connections + lineinfile: dest: /etc/nginx/nginx.conf + regexp: '^(\s*worker_connections)\s+.+;' + line: ' worker_connections 1024;' + insertafter: 'events \{' + +- name: use epoll + lineinfile: + dest: /etc/nginx/nginx.conf + regexp: '^(\s*use)\s+.+;' + line: ' use epoll;' + insertafter: 'events \{' + +- name: Install Nginx http configuration + copy: + src: nginx/evolinux-defaults.conf + dest: /etc/nginx/conf.d/z-evolinux-defaults.conf mode: 0640 # force: yes notify: reload nginx tags: - nginx -# TODO: verify that those permisisons are correct : +# TODO: verify that those permissions are correct : # not too strict for private_ipaddr_whitelist # and not too loose for private_htpasswd @@ -79,37 +96,6 @@ with_items: "{{ nginx_private_htpasswd_absent }}" notify: reload nginx -- name: Check if a certificate is present for default site - stat: - path: /etc/ssl/certs/{{ ansible_fqdn }}.crt - register: stat_crt - tags: - - nginx - -- include: create_default_cert.yml - when: not stat_crt.stat.exists - tags: - - nginx - -- name: Install Nginx default site - template: - src: default_site.j2 - dest: /etc/nginx/sites-available/default - mode: 0640 - # force: yes - notify: reload nginx - tags: - - nginx - -- name: Enable Nginx default site - file: - src: /etc/nginx/sites-available/default - dest: /etc/nginx/sites-enabled/default - state: link - notify: reload nginx - tags: - - nginx - - name: Verify that the service is enabled and started service: name: nginx @@ -125,7 +111,12 @@ tags: - munin -- include: configure_munin.yml +- include: munin_vhost.yml + when: stat_munin_node.stat.exists + tags: + - munin + +- include: munin_graphs.yml when: stat_munin_node.stat.exists tags: - munin diff --git a/nginx/tasks/munin_graphs.yml b/nginx/tasks/munin_graphs.yml new file mode 100644 index 00000000..1133f525 --- /dev/null +++ b/nginx/tasks/munin_graphs.yml @@ -0,0 +1,18 @@ +--- + +- name: Copy Munin config for Nginx + copy: + src: munin/evolinux.nginx + dest: /etc/munin/plugin-conf.d/ + mode: 0644 + notify: restart munin + +- name: Enable Munin plugins for Nginx + file: + src: '/usr/share/munin/plugins/{{ item }}' + dest: '/etc/munin/plugins/{{ item }}' + state: link + with_items: + - nginx_request + - nginx_status + notify: restart munin diff --git a/nginx/tasks/configure_munin.yml b/nginx/tasks/munin_vhost.yml similarity index 63% rename from nginx/tasks/configure_munin.yml rename to nginx/tasks/munin_vhost.yml index 0b75af18..c97da1a2 100644 --- a/nginx/tasks/configure_munin.yml +++ b/nginx/tasks/munin_vhost.yml @@ -3,24 +3,8 @@ lineinfile: dest: /etc/hosts regexp: 'munin$' - line: '127.0.0.1 munin' - -- name: Copy Munin config for Nginx - copy: - src: munin/plugin-conf.d/evolinux.nginx - dest: /etc/munin/plugin-conf.d/ - mode: 0644 - notify: restart munin - -- name: Enable Munin plugins for Nginx - file: - src: '/usr/share/munin/plugins/{{ item }}' - dest: '/etc/munin/plugins/{{ item }}' - state: link - with_items: - - nginx_request - - nginx_status - notify: restart munin + line: '127.0.0.1 munin' + insertafter: EOF - name: Ensure packages for Munin CGI are installed apt: diff --git a/nginx/templates/nginx.conf.j2 b/nginx/templates/nginx.conf.j2 deleted file mode 100644 index d58a230a..00000000 --- a/nginx/templates/nginx.conf.j2 +++ /dev/null @@ -1,88 +0,0 @@ -user www-data; -worker_processes {{ nginx_worker_processes }}; -pid /var/run/nginx.pid; - -events { - worker_connections 1024; - #multi_accept on; - use epoll; -} - -http { - ## - # Basic Settings - ## - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - - ## - # Tuning Evolix - ## - #connection_pool_size 256; - #client_header_buffer_size 64k; - #client_body_buffer_size 128k; - #large_client_header_buffers 8 16k; - #request_pool_size 4k; - #client_header_timeout 10m; - #client_body_timeout 10m; - #send_timeout 10m; - #client_max_body_size 50m; - server_tokens off; - server_names_hash_max_size 512; - server_names_hash_bucket_size 128; - server_name_in_redirect off; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - ## - # Gzip Settings - ## - - gzip on; - gzip_disable "msie6"; - - # gzip_vary on; - # gzip_proxied any; - # gzip_comp_level 6; - # gzip_buffers 16 8k; - # gzip_http_version 1.1; - # gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; - - index index.html; - - ## - # Logging Settings - ## - - access_log /var/log/nginx/access_log; - error_log /var/log/nginx/error_log; - - ## - # nginx-naxsi config - ## - # Uncomment it if you installed nginx-naxsi - ## - - #include /etc/nginx/naxsi_core.rules; - - ## - # nginx-passenger config - ## - # Uncomment it if you installed nginx-passenger - ## - - #passenger_root /usr; - #passenger_ruby /usr/bin/ruby; - - ## - # Virtual Host Configs - ## - - include /etc/nginx/conf.d/*.conf; - include /etc/nginx/sites-enabled/*; -} diff --git a/nodejs/README.md b/nodejs/README.md new file mode 100644 index 00000000..f93ccfbd --- /dev/null +++ b/nodejs/README.md @@ -0,0 +1,11 @@ +# nodejs + +Install NodeJS from NPM repositories. + +## Tasks + +Everything is in the `tasks/main.yml` file. + +## Variables + +* `nodejs_apt_version`: verison for the repository (default: `node_6.x`). diff --git a/nodejs/defaults/main.yml b/nodejs/defaults/main.yml new file mode 100644 index 00000000..f51e88aa --- /dev/null +++ b/nodejs/defaults/main.yml @@ -0,0 +1,2 @@ +--- +nodejs_apt_version: 'node_6.x' diff --git a/nodejs/tasks/main.yml b/nodejs/tasks/main.yml new file mode 100644 index 00000000..3aceb48b --- /dev/null +++ b/nodejs/tasks/main.yml @@ -0,0 +1,37 @@ +--- + + +- name: APT https transport is enabled + apt: + name: apt-transport-https + state: installed + tags: + - system + - packages + +- name: Node GPG key is installed + apt_key: + url: https://deb.nodesource.com/gpgkey/nodesource.gpg.key + state: present + tags: + - system + - packages + - nodejs + +- name: Node sources list ({{ nodejs_apt_version }}) is available + apt_repository: + repo: "deb https://deb.nodesource.com/{{ nodejs_apt_version }} jessie main" + state: present + tags: + - system + - packages + - nodejs + +- name: Node is installed + apt: + name: nodejs + update_cache: yes + state: installed + tags: + - packages + - nodejs diff --git a/postfix/README.md b/postfix/README.md new file mode 100644 index 00000000..24621cfb --- /dev/null +++ b/postfix/README.md @@ -0,0 +1,18 @@ +# postfix + +Install Postfix + +## Tasks + +Everything is in the `tasks/main.yml` file. + +## Available variables + +Main variables are : + +* `general_alert_email`: email address to send various alert messages (default: `root@localhost`). +* `postfix_alias_email`: email address for messages sent to root (default: `general_alert_email`) ; +* `postfix_slow_transports_enabled`: configure slow transports (default: `True`) ; +* `postfix_remove_exim`: remove Exim4 packages (default: `True`) ; + +The full list of variables (with default values) can be found in `defaults/main.yml`. diff --git a/postfix/defaults/main.yml b/postfix/defaults/main.yml new file mode 100644 index 00000000..2e3bbb94 --- /dev/null +++ b/postfix/defaults/main.yml @@ -0,0 +1,6 @@ +--- +general_alert_email: "root@localhost" +postfix_alias_email: Null + +postfix_slow_transports_enabled: True +postfix_remove_exim: True diff --git a/postfix/handlers/main.yml b/postfix/handlers/main.yml new file mode 100644 index 00000000..7894a013 --- /dev/null +++ b/postfix/handlers/main.yml @@ -0,0 +1,17 @@ +--- + +- name: newaliases + command: newaliases + +- name: restart postfix + service: + name: postfix + state: restarted + +- name: reload postfix + service: + name: postfix + state: reloaded + +- name: postmap transport + command: postmap /etc/postfix/transport diff --git a/postfix/tasks/main.yml b/postfix/tasks/main.yml new file mode 100644 index 00000000..4d7ec042 --- /dev/null +++ b/postfix/tasks/main.yml @@ -0,0 +1,83 @@ +--- + +- name: packages are installed + apt: + name: "{{ item }}" + state: present + with_items: + - postfix + - mailgraph + tags: + - packages + - postfix + +- name: main.cf is configured + lineinfile: + dest: /etc/postfix/main.cf + regexp: "^#? *{{ item.key }} *=" + line: "{{ item.key }} = {{ item.value }}" + with_items: + - { key: "myorigin", value: "/etc/mailname" } + - { key: "disable_vrfy_command", value: "yes" } + notify: reload postfix + tags: + - postfix + +- name: fetch users list + shell: getent passwd | cut -d":" -f 1 | grep -v root + register: non_root_users_list + changed_when: False + tags: + - postfix + +- name: each user is aliased to root + lineinfile: + dest: /etc/aliases + regexp: "^{{ item }}:.*" + line: "{{ item }}: root" + with_items: "{{ non_root_users_list.stdout_lines }}" + notify: newaliases + tags: + - postfix + +- name: additional users address aliased to root + lineinfile: + dest: /etc/aliases + regexp: "^{{ item }}:.*" + line: "{{ item }}: root" + with_items: + - postmaster + - abuse + - mailer-daemon + notify: newaliases + tags: + - postfix + +- name: root alias is configured + lineinfile: + dest: /etc/aliases + regexp: "^root:" + line: "root: {{ postfix_alias_email or general_alert_email | mandatory }}" + notify: newaliases + tags: + - postfix + +- name: exim4 is absent + apt: + name: "{{ item }}" + purge: yes + state: absent + with_items: + - exim4 + - exim4-base + - exim4-config + - exim4-daemon-light + when: postfix_remove_exim + tags: + - packages + - postfix + +- include: slow_transports.yml + when: postfix_slow_transports_enabled + tags: + - postfix diff --git a/postfix/tasks/slow_transports.yml b/postfix/tasks/slow_transports.yml new file mode 100644 index 00000000..da32df89 --- /dev/null +++ b/postfix/tasks/slow_transports.yml @@ -0,0 +1,69 @@ +--- + +- name: slow transport is defined in master.cf + lineinfile: + dest: /etc/postfix/master.cf + regexp: "^slow " + line: "slow unix - - n - - smtp" + notify: reload postfix + tags: + - postfix + +- name: list of providers for slow transport + lineinfile: + dest: /etc/postfix/transport + line: "{{ item }}" + create: yes + with_items: + - "orange.fr slow:" + - "wanadoo.fr slow:" + - "voila.fr slow:" + - "laposte slow:" + - "yahoo.fr slow:" + - "yahoo.com slow:" + - "hotmail.fr slow:" + - "hotmail.com slow:" + notify: + - postmap transport + - reload postfix + tags: + - postfix + +# - name: main.cf is configured for slow transports +# lineinfile: +# dest: /etc/postfix/main.cf +# regexp: "^{{ item.key }} =" +# line: "{{ item.key }} = {{ item.value }}" +# with_items: +# - { key: "minimal_backoff_time", value: "2h" } +# - { key: "maximal_backoff_time", value: "6h" } +# - { key: "maximal_queue_lifetime", value: "4d" } +# - { key: "queue_run_delay", value: "100s" } +# - { key: "bounce_queue_lifetime", value: "1d" } +# - { key: "initial_destination_concurrency", value: "5" } +# - { key: "default_destination_concurrency_limit", value: "20" } +# - { key: "slow_destination_rate_delay", value: "0" } +# - { key: "slow_destination_concurrency_limit", value: "1" } +# - { key: "slow_destination_concurrency_failed_cohort_limit", value: "100" } +# - { key: "slow_destination_recipient_limit", value: "25" } +# - { key: "transport_maps", value: "hash:$config_directory/transport" } +# notify: reload postfix + +- name: main.cf is configured for slow transports + blockinfile: + dest: /etc/postfix/main.cf + marker: "# {mark} Slow transports configuration (managed by Ansible)" + block: | + minimal_backoff_time = 2h + maximal_backoff_time = 6h + maximal_queue_lifetime = 4d + queue_run_delay = 100s + bounce_queue_lifetime = 1d + initial_destination_concurrency = 5 + default_destination_concurrency_limit = 20 + slow_destination_rate_delay = 0 + slow_destination_concurrency_limit = 1 + slow_destination_concurrency_failed_cohort_limit = 100 + slow_destination_recipient_limit = 25 + transport_maps = hash:$config_directory/transport + notify: reload postfix diff --git a/proftpd/README.md b/proftpd/README.md new file mode 100644 index 00000000..55168279 --- /dev/null +++ b/proftpd/README.md @@ -0,0 +1,16 @@ +# fail2ban + +Install Fail2ban. + +## Tasks + +Everything is in the `tasks/main.yml` file. + +## Available variables + +Main variables are : + +* `general_alert_email`: email address to send various alert messages (default: `root@localhost`). +* `fail2ban_alert_email`: email address for messages sent to root (default: `general_alert_email`). + +The full list of variables (with default values) can be found in `defaults/main.yml`. diff --git a/proftpd/defaults/main.yml b/proftpd/defaults/main.yml new file mode 100644 index 00000000..33f9d284 --- /dev/null +++ b/proftpd/defaults/main.yml @@ -0,0 +1,6 @@ +--- +general_alert_email: "root@localhost" +fail2ban_alert_email: Null + +proftpd_hostname: "{{ ansible_hostname }}" +proftpd_fqdn: "{{ ansible_fqdn }}" diff --git a/proftpd/handlers/main.yml b/proftpd/handlers/main.yml new file mode 100644 index 00000000..0914d289 --- /dev/null +++ b/proftpd/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart proftpd + service: + name: proftpd + state: restarted diff --git a/proftpd/tasks/main.yml b/proftpd/tasks/main.yml new file mode 100644 index 00000000..8acaa3fc --- /dev/null +++ b/proftpd/tasks/main.yml @@ -0,0 +1,34 @@ +--- +- name: package is installed + apt: + name: proftpd-basic + state: installed + tags: + - proftpd + - packages + +- name: ftpusers groupe exists + group: + name: ftpusers + state: present + notify: restart proftpd + tags: + - proftpd + +- name: local jail is installed + template: + src: evolinux.conf.j2 + dest: /etc/proftpd/conf.d/z-evolinux.conf + mode: 0644 + notify: restart proftpd + tags: + - proftpd + +- name: mod_tls_memcache is disabled + replace: + dest: /etc/proftpd/modules.conf + regexp: '^LoadModule mod_tls_memcache.c' + replace: '#LoadModule mod_tls_memcache.c' + notify: restart proftpd + tags: + - proftpd diff --git a/proftpd/templates/evolinux.conf.j2 b/proftpd/templates/evolinux.conf.j2 new file mode 100644 index 00000000..e02820d6 --- /dev/null +++ b/proftpd/templates/evolinux.conf.j2 @@ -0,0 +1,29 @@ +# Evolix's specific configuration + +ServerName "{{ proftpd_hostname }} FTP Server" +ServerIdent on "FTP Server Ready" +AccessGrantMsg "Hey, bienvenue %u sur le serveur FTP {{ proftpd_fqdn }} !" + +# Connexion limits +MaxInstances 50 +MaxClients 40 +MaxClientsPerHost 20 + +# Network settings +PassivePorts 60000 61000 +UseReverseDNS off +IdentLookups off + +# Local permissions +DefaultRoot ~ +Umask 137 027 +RequireValidShell off +UseFtpUsers off + +# Allow RESUME (REST command) +AllowStoreRestart on + + + AllowGroup ftpusers + DenyAll + diff --git a/squid/README.md b/squid/README.md new file mode 100644 index 00000000..27b80d66 --- /dev/null +++ b/squid/README.md @@ -0,0 +1,15 @@ +# squid + +Install Squid. + +## Tasks + +Everything is in the `tasks/main.yml` file. + +## Available variables + +* `squid_address` : IP address for internal/outgoing traffic (default: Ansible detected IPv4 address) ; +* `general_alert_email`: email address to send various alert messages (default: `root@localhost`). +* `log2mail_alert_email`: email address to send Log2mail messages to (default: `general_alert_email`). + +The full list of variables (with default values) can be found in `defaults/main.yml`. diff --git a/squid/defaults/main.yml b/squid/defaults/main.yml new file mode 100644 index 00000000..35b4a91f --- /dev/null +++ b/squid/defaults/main.yml @@ -0,0 +1,5 @@ +--- +general_alert_email: "root@localhost" +log2mail_alert_email: Null + +squid_address: "{{ ansible_default_ipv4.address }}" diff --git a/squid/files/whitelist-custom.conf b/squid/files/whitelist-custom.conf new file mode 100644 index 00000000..5d930f2c --- /dev/null +++ b/squid/files/whitelist-custom.conf @@ -0,0 +1,2 @@ +### Custom whitelist +# http://example.com/.* diff --git a/squid/files/whitelist-evolinux.conf b/squid/files/whitelist-evolinux.conf new file mode 100644 index 00000000..30c5140e --- /dev/null +++ b/squid/files/whitelist-evolinux.conf @@ -0,0 +1,116 @@ +### Evolix & System +http://.*evolix.(net|org|com|fr)/.* +http://.*debian.org/.* +http://www.backports.org/.* +http://backports.debian.org/.* +http://www.kernel.org/.* +http://hwraid.le-vert.net/.* +http://.*clamav.net/.* +http://spamassassin.apache.org/.* +http://.*sa-update.* +http://pear.php.net/.* + +### CMS / Wordpress / Drupal / ... +# Wordpress +http://.*akismet.com/.* +http://.*wordpress.(org|com)/.* +http://.*gravatar.com/.* +http://www.wordpress-fr.net/.* +http://pixel.wp.com/.* +# Wordpress pingback +http://rpc.pingomatic.com/.* +http://blo.gs/ping.php +http://ping.blo.gs/.* +http://ping.baidu.com/ping/RPC2 +http://blogsearch.google.ru/ping/RPC2 +http://ping.pubsub.com/ping +http://rpc.twingly.com/.* +http://api.feedster.com/ping +http://api.moreover.com/RPC2 +http://api.moreover.com/ping +http://www.blogdigger.com/RPC2 +http://www.blogshares.com/rpc.php +http://www.blogsnow.com/ping +http://www.blogstreet.com/xrbin/xmlrpc.cgi +http://bulkfeeds.net/rpc +http://www.newsisfree.com/xmlrpctest.php +http://ping.feedburner.com/.* +http://ping.syndic8.com/xmlrpc.php +http://ping.weblogalot.com/rpc.php +http://rpc.blogrolling.com/pinger/.* +http://rpc.technorati.com/rpc/ping +http://rpc.weblogs.com/RPC2 +http://www.feedsubmitter.com/.* +http://www.pingerati.net/.* +http://www.pingmyblog.com/.* +http://geourl.org/ping +http://ipings.com/.* +http://www.weblogalot.com/ping +# Wordpress plugins +http://.*wpml.org/.* +http://www.wpcube.co.uk/.* +http://.*wp-rocket.me/.* +http://www.yithemes.com/.* +http://.*yoast.com/.* +http://yarpp.org/.* +http://repository.kreaturamedia.com/.* +http://api.wp-events-plugin.com/.* +http://updates.themepunch.com/.* +http://themeisle.com/.* +http://download.advancedcustomfields.com/.* +http://wpcdn.io/.* +http://vimeo.com/channels/wpetourisme/videos/rss +http://api.genesistheme.com/update-themes/ +http://www.bolderelements.net/updates/.* +# Magento Plugins +http://extensions.activo.com/.* +http://amasty.com/.* +# Joomla +http://.*.joomla.org/.* +http://getk2.org/.* +http://miwisoft.com/.* +http://mijosoft.com/.* +http://www.joomlaworks.net/.* +http://cdn.joomlaworks.org/.* +http://download.regularlabs.com/.* +# Prestashop +http://.*.prestashop.com/.* +http://www.presta-module.com/.* +http://www.presteamshop.com/.* +# Others +http://.*.drupal.org/.* +http://.*.dotclear.(net|org)/.* +http://www.phpbb.com/.* +http://www.typolight.org/.* +http://www.spip.net/.* + +### Feeds / API / WS Tools / ... +# Google +http://.*.googleapis.com/.* +http://.*.google-analytics.com/.* +http://blogsearch.google.(com|fr)/.* +http://csi.gstatic.com/.* +http://maps.google.*/.* +http://translate.google.com/.* +http://www.google.com/webmasters/tools/.* +# Facebook +http://.*.facebook.com/.* +http://.*.fbcdn.net/.* +# Maxmind +http://geolite.maxmind.com/.* +# Others +http://.*amazon.com/.* +http://.*twitter.com/.* +http://.*feedburner.com/.* +http://.*openx.(org|com|net)/.* +http://geoip-api.meteor.com/.* +http://www.bing.com/.* +http://www.telize.com/.* +http://.*ident.me/.* +http://.*icanhazip.com/.* +http://www.express-mailing.com/.* +http://bot.whatismyipaddress.com/.* +http://ipecho.net/.* + +### Various / Manual entry +http://.*.s3.amazonaws.com/.* diff --git a/squid/handlers/main.yml b/squid/handlers/main.yml new file mode 100644 index 00000000..c623bc19 --- /dev/null +++ b/squid/handlers/main.yml @@ -0,0 +1,15 @@ +--- +- name: restart munin-node + service: + name: munin-node + state: restarted + +- name: restart squid + service: + name: squid3 + state: restarted + +- name: restart log2mail + service: + name: log2mail + state: restarted diff --git a/squid/tasks/log2mail.yml b/squid/tasks/log2mail.yml new file mode 100644 index 00000000..d525fbae --- /dev/null +++ b/squid/tasks/log2mail.yml @@ -0,0 +1,26 @@ +--- + +- name: is log2mail installed? + stat: + path: /etc/log2mail/config/ + register: log2mail_config + +- block: + - name: log2mail proxy config is present + template: + src: log2mail.j2 + dest: /etc/log2mail/config/squid.conf + mode: 0640 + owner: log2mail + group: adm + notify: restart log2mail + + - name: log2mail user is in proxy group + user: + name: log2mail + groups: proxy + append: yes + state: present + notify: restart log2mail + + when: log2mail_config.stat.exists diff --git a/squid/tasks/main.yml b/squid/tasks/main.yml new file mode 100644 index 00000000..35be44cf --- /dev/null +++ b/squid/tasks/main.yml @@ -0,0 +1,31 @@ +--- + +- name: package is installed + apt: + name: squid3 + state: present + +- name: squid.conf is present + template: + src: squid.j2 + dest: /etc/squid3/squid.conf + notify: restart squid + +- name: evolix whitelist is present + copy: + src: whitelist-evolix.conf + dest: /etc/squid3/whitelist-evolix.conf + force: yes + backup: yes + notify: restart squid + +- name: custom whitelist is present + copy: + src: whitelist-custom.conf + dest: /etc/squid3/whitelist-custom.conf + force: no + notify: restart squid + +- include: minifirewall.yml + +- include: log2mail.yml diff --git a/squid/tasks/minifirewall.yml b/squid/tasks/minifirewall.yml new file mode 100644 index 00000000..8f47bdaa --- /dev/null +++ b/squid/tasks/minifirewall.yml @@ -0,0 +1,39 @@ +--- +- name: verify that minifirewall is present + stat: + path: /etc/default/minifirewall + register: minifirewall_test + +- fail: + msg: "You must install and configure minifirewall to use Squid" + when: not minifirewall_test.stat.exists + +- name: HTTPSITES list is commented in minifirewall + replace: + dest: /etc/default/minifirewall + regexp: "^(HTTPSITES='[^0-9])" + replace: '#\1' + +- name: all HTTPSITES are authorized in minifirewall + lineinfile: + dest: /etc/default/minifirewall + line: "HTTPSITES='0.0.0.0/0'" + insertafter: "^#HTTPSITES=" + +- name: add minifirewall rules for the proxy + lineinfile: + dest: /etc/default/minifirewall + regexp: "^#? *{{ item }}" + line: "{{ item }}" + insertafter: "^# Proxy" + with_items: + - "/sbin/iptables -t nat -A OUTPUT -p tcp --dport 80 -m owner --uid-owner proxy -j ACCEPT" + - "/sbin/iptables -t nat -A OUTPUT -p tcp --dport 80 -d {{ squid_address }} -j ACCEPT" + - "/sbin/iptables -t nat -A OUTPUT -p tcp --dport 80 -d 127.0.0.0/8 -j ACCEPT" + - "/sbin/iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port 8888" + +- name: remove minifirewall example rule for the proxy + lineinfile: + dest: /etc/default/minifirewall + regexp: '^#.*(-t nat).*(-d X\.X\.X\.X)' + state: absent diff --git a/squid/templates/log2mail.j2 b/squid/templates/log2mail.j2 new file mode 100644 index 00000000..223c8e27 --- /dev/null +++ b/squid/templates/log2mail.j2 @@ -0,0 +1,4 @@ +file = /var/log/squid3/access.log +pattern = "TCP_DENIED" +mailto = {{ log2mail_alert_email or general_alert_email | mandatory }} +template = /etc/log2mail/mail diff --git a/squid/templates/squid.j2 b/squid/templates/squid.j2 new file mode 100644 index 00000000..7fe7f36b --- /dev/null +++ b/squid/templates/squid.j2 @@ -0,0 +1,20 @@ +# Ports +http_port 8888 transparent +icp_port 0 + +# ACL +#acl all src 0.0.0.0/0.0.0.0 +acl localhost src 127.0.0.0/32 +acl INTERNE src {{ squid_address }}/32 127.0.0.0/8 +acl Safe_ports port 80 # http +acl SSL_ports port 443 563 +acl WHITELIST url_regex "/etc/squid3/whitelist-evolinux.conf" +acl WHITELIST url_regex "/etc/squid3/whitelist-custom.conf" +http_access deny !WHITELIST +http_access allow INTERNE +http_access deny all +tcp_outgoing_address {{ squid_address }} + +# Logs +logformat combined %>a %[ui %[un [%tl] "%rm %ru HTTP/%rv" %>Hs %h" "%{User-Agent}>h" %Ss:%Sh +access_log /var/log/squid3/access.log combined diff --git a/vagrant.yml b/vagrant.yml index e4510f41..cd0ad811 100644 --- a/vagrant.yml +++ b/vagrant.yml @@ -2,10 +2,20 @@ - hosts: all gather_facts: yes become: yes + vars_files: + - 'vars/main.yml' roles: + # - apt-upgrade # - { role: apt-upgrade, apt_upgrade_mode: safe } - - apt-upgrade + # - evolinux-base + # - listupgrade + # - etc-git + - evolinux + # - postfix + # - minifirewall + # - squid + # - evomaintenance # - munin # - monit # - redis @@ -20,8 +30,8 @@ # nginx_private_htpasswd_absent: ["toto:dsfgdfsdf"] # } # - apache - - { role: elastic-stack, - elasticsearch_jvm_xms: "256m", - elasticsearch_jvm_xmx: "256m" - } - - filebeat + # - { role: elastic-stack, + # elasticsearch_jvm_xms: "256m", + # elasticsearch_jvm_xmx: "256m" + # } + # - filebeat