Update to 2022-03-07 14:00

This commit is contained in:
Daniel Berteaud
2022-03-07 14:00:06 +01:00
parent c55f851cbd
commit 8b7e505180
58 changed files with 1119 additions and 89 deletions

View File

@@ -0,0 +1,9 @@
---
- name: reset permissions
command: sh /opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}/bin/perms.sh
loop: "{{ wh_clients | subelements('apps') }}"
when: item.1.backend | default(item.0.backend) | default(wh_defaults.backend) == inventory_hostname
- name: restart wh-acld
service: name=wh-acld state=restarted

View File

@@ -0,0 +1,4 @@
---
dependencies:
- role: wh_common
- role: httpd_php

View File

@@ -0,0 +1,207 @@
---
- include_vars: "{{ item }}"
with_first_found:
- vars/{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml
- vars/{{ ansible_os_family }}-{{ ansible_distribution_major_version }}.yml
- vars/{{ ansible_distribution }}.yml
- vars/{{ ansible_os_family }}.yml
tags: web
- name: Install needed tools
yum: name{{ wh_backend_packages }}
tags: web
- set_fact: wh_app_dir=[]
tags: web
- name: Build a list of app root
set_fact:
wh_app_dir: "{{ wh_app_dir }} + [ '/opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}' ]"
loop: "{{ wh_clients | subelements('apps') }}"
when: item.1.backend | default(item.0.backend) | default(wh_defaults.backend) == inventory_hostname
tags: web
- name: Create unix accounts
user:
name: "wh-{{ item.name }}"
comment: "Unix account for {{ item.name }}"
system: True
shell: "{{ shell | default('/sbin/nologin') }}"
home: /opt/wh/{{ item.name }}
loop: "{{ wh_clients }}"
tags: web
- name: Create ssh directories
file: path=/etc/ssh/wh/{{ item.name }}/ state=directory mode=755
loop: "{{ wh_clients }}"
tags: web
- name: Deploy SSH keys
authorized_key:
user: root
key: "{{ item.ssh_keys | default([]) | join(\"\n\") }}"
path: /etc/ssh/wh/{{ item.name }}/authorized_keys
manage_dir: False
exclusive: True
loop: "{{ wh_clients }}"
tags: web
- name: Set correct permissions on authorized_key files
file: path=/etc/ssh/wh/{{ item.name }}/authorized_keys owner=root group=root mode=644
loop: "{{ wh_clients }}"
when: item.ssh_keys | default([]) | length > 0
tags: web
- name: List all authorized keys directories
shell: ls -1 /etc/ssh/wh | xargs -n1 basename
register: wh_existing_ssh_keys
changed_when: False
tags: web
- name: Remove unmanaged ssh keys
file: path=/etc/ssh/wh/{{ item }} state=absent
with_items: "{{ wh_existing_ssh_keys.stdout_lines | default([]) }}"
when: item not in wh_clients | map(attribute='name')
tags: web
- name: Create applications directories
file: path={{ item.0 }}/{{ item.1 }} state=directory
loop: "{{ wh_app_dir | product(['web','data','tmp','logs','archives','bin','info', 'db_dumps']) | list }}"
notify: reset permissions
tags: web
- name: Set correct SELinux context for apps directories
sefcontext:
target: "{{ item }}(/.*)?"
setype: httpd_sys_content_t
state: present
when: ansible_selinux.status == 'enabled'
loop: "{{ wh_app_dir }}"
notify: reset permissions
tags: web
- name: Deploy PHP FPM pools
template: src=php-fpm.conf.j2 dest=/etc/opt/remi/php{{ item }}/php-fpm.d/wh.conf
vars:
wh_php_version: "{{ item }}"
loop: "{{ httpd_php_versions }}"
notify: restart php-fpm
tags: web
- name: Deploy httpd configuration
template: src=httpd.conf.j2 dest=/etc/httpd/ansible_conf.d/31-wh.conf
notify: reload httpd
tags: web
- name: Deploy permissions scripts
template: src=perms.sh.j2 dest=/opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}/bin/perms.sh
loop: "{{ wh_clients | subelements('apps') }}"
when: item.1.backend | default(item.0.backend) | default(wh_defaults.backend) == inventory_hostname
notify: reset permissions
tags: web
- name: Create databases
mysql_db:
name: "{{ item.0.name[0:7] }}_{{ item.1.name[0:7] }}"
login_host: "{{ (wh_default_app | combine(item.1)).database.server | default(mysql_server) }}"
login_user: sqladmin
login_password: "{{ mysql_admin_pass }}"
collation: "{{ (wh_default_app | combine(item.1)).database.collation }}"
encoding: "{{ (wh_default_app | combine(item.1)).database.encoding }}"
state: present
loop: "{{ wh_clients | subelements('apps') }}"
when:
- (wh_default_app | combine(item.1)).database.enabled
- (wh_default_app | combine(item.1)).database.engine == 'mysql'
- item.1.backend | default(item.0.backend) | default(wh_defaults.backend) == inventory_hostname
tags: web
- name: Create applications database users
mysql_user:
name: "{{ item.0.name[0:7] }}_{{ item.1.name[0:7] }}"
password: "{{ (wh_default_app | combine(item.1)).database.pass | default((wh_pass_seed | password_hash('sha256', 65534 | random(seed=item.0.name + item.1.name) | string))[9:27] ) }}"
priv: "{{ item.0.name[0:7] }}_{{ item.1.name[0:7] }}.*:ALL"
host: "%"
login_host: "{{ (wh_default_app | combine(item.1)).database.server | default(mysql_server) }}"
login_user: sqladmin
login_password: "{{ mysql_admin_pass }}"
state: present
loop: "{{ wh_clients | subelements('apps') }}"
when:
- (wh_default_app | combine(item.1)).database.enabled
- (wh_default_app | combine(item.1)).database.engine == 'mysql'
- item.1.backend | default(item.0.backend) | default(wh_defaults.backend) == inventory_hostname
tags: web
- name: Create clients database user
mysql_user:
name: "{{ item.0.name[0:15] }}"
password: "{{ item.0.db_pass | default((wh_pass_seed | password_hash('sha256', 65534 | random(seed=item.0.name) | string))[9:27]) }}"
priv: "{{ item.0.name[0:7] }}_{{ item.1.name[0:7] }}.*:ALL"
host: "%"
login_host: "{{ (wh_default_app | combine(item.1)).database.server | default(mysql_server) }}"
login_user: sqladmin
login_password: "{{ mysql_admin_pass }}"
append_privs: True
state: present
loop: "{{ wh_clients | subelements('apps')}}"
when:
- (wh_default_app | combine(item.1)).database.enabled
- (wh_default_app | combine(item.1)).database.engine == 'mysql'
- item.1.backend | default(item.0.backend) | default(wh_defaults.backend) == inventory_hostname
tags: web
- name: Deploy databases info file
template: src=database.txt.j2 dest=/opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}/info/database.txt
loop: "{{ wh_clients | subelements('apps') }}"
when: item.1.backend | default(item.0.backend) | default(wh_defaults.backend) == inventory_hostname
notify: reset permissions
tags: web
- name: Deploy per app backup scripts
template: src=backup.sh.j2 dest=/opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}/bin/backup.sh mode=750
loop: "{{ wh_clients | subelements('apps') }}"
when: item.1.backend | default(item.0.backend) | default(wh_defaults.backend) == inventory_hostname
tags: web
- name: Deploy wh_create_archives script to archive all the hosted apps
template: src=wh_create_archives.sh.j2 dest=/usr/local/bin/wh_create_archives.sh mode=750
tags: web
- name: Setup a daily cronjob to take automatic archives of webapps
cron:
name: wh_backups
special_time: daily
user: root
job: 'systemd-cat /usr/local/bin/wh_create_archives.sh'
cron_file: wh
state: present
tags: web
- name: Deploy global pre/post backup scripts
template: src={{ item }}_backup.sh.j2 dest=/etc/backup/{{ item }}.d/wh.sh mode=700
loop: [ 'pre', 'post' ]
tags: web
- name: Deploy logrotate snippet
template: src=logrotate.j2 dest=/etc/logrotate.d/wh
tags: web
- name: Deploy wh-acld
template: src=wh-acld.j2 dest=/usr/local/bin/wh-acld mode=750
notify: restart wh-acld
tags: web
- name: Deploy wh-acld service unit
template: src=wh-acld.service.j2 dest=/etc/systemd/system/wh-acld.service
register: wh_acld_unit
tags: web
- name: Reload systemd
systemd: daemon_reload=True
when: wh_acld_unit.changed
tags: web
- name: Start and enable wh-acld
service: name=wh-acld state=started enabled=True
tags: web

View File

@@ -0,0 +1,14 @@
#!/bin/bash -e
cd /opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}
# Remove old archives
find archives/ -type f -mtime +2 -exec rm -f "{}" \;
# Create the new daily archive, with a dump of the DB and the web, data and logs dir
TS=$(date +%Y-%m-%d_%Hh%M)
mysqldump --add-drop-table --single-transaction \
--host={{ (wh_default_app | combine(item.1)).database.server | default(mysql_server) }} \
--user={{ item.0.name[0:7] }}_{{ item.1.name[0:7] }} \
--password="{{ (wh_default_app | combine(item.1)).database.pass | default((wh_pass_seed | password_hash('sha256', 65534 | random(seed=item.0.name + item.1.name) | string))[9:27] ) }}" \
{{ item.0.name[0:7] }}_{{ item.1.name[0:7] }} | \
zstd -c > archives/$TS.sql.zst
ZSTD_CLEVEL=15 ZSTD_NBTHREADS=0 nice -n 9 tar cf archives/$TS.tar.zst --use-compress-program=zstd data web logs

View File

@@ -0,0 +1,5 @@
Type: {{ (wh_default_app | combine(item.1)).database.engine | default('mysql') }}
Server: {{ (wh_default_app | combine(item.1)).database.server | default(mysql_server) }}
Database: {{ item.0.name[0:7] }}_{{ item.1.name[0:7] }}
User: {{ item.0.name[0:7] }}_{{ item.1.name[0:7] }}
Password: {{ (wh_default_app | combine(item.1)).database.pass | default((wh_pass_seed | password_hash('sha256', 65534 | random(seed=item.0.name + item.1.name) | string))[9:27] ) }}

View File

@@ -0,0 +1,44 @@
# {{ ansible_managed }}
{% for client in wh_clients %}
{% for app in client.apps | default([]) %}
{% set app = wh_default_app | combine(app, recursive=True) %}
{% if app.backend | default(client.backend) | default(wh_defaults.backend) == inventory_hostname %}
################################################
## vhost for {{ client.name }}-{{ app.name }}
################################################
<VirtualHost *:80>
ServerName {{ app.vhost | default(client.name + '-' + app.name + '.wh.fws.fr') }}
{% if app.aliases | length > 0 %}
ServerAlias {{ app.aliases | join(' ') }}
{% endif %}
ServerAdmin webmaster@fws.fr
DocumentRoot /opt/wh/{{ client.name }}/apps/{{ app.name }}/web
Alias /_deferror/ "/usr/share/httpd/error/"
Include ansible_conf.d/common_env.inc
ProxyTimeout {{ app.php.max_execution_time }}
</VirtualHost>
################################################
## webroot for {{ client.name }}-{{ app.name }}
################################################
<Directory /opt/wh/{{ client.name }}/apps/{{ app.name }}/web>
AllowOverride All
Options FollowSymLinks
Require all granted
{% if app.php.enabled %}
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php-fpm/php{{ app.php.version}}-{{ client.name }}-{{ app.name }}.sock|fcgi://localhost"
</FilesMatch>
{% endif %}
<FilesMatch "^(\.ansible_version|\.git.*|(README|LICENSE|AUTHORS|CHANGELOG|CONTRIBUTING|LEGALNOTICE|PRIVACY|SECURITY)(\.md)?|.*\.co?nf|\.htaccess|composer\.(json|lock))">
Require all denied
</FilesMatch>
</Directory>
{% endif %}
{% endfor %}
{% endfor %}

View File

@@ -0,0 +1,21 @@
# {{ ansible_managed }}
{% for client in wh_clients %}
{% for app in client.apps %}
{% set app = wh_default_app | combine(app, recursive=True) %}
{% if app.backend | default(client.backend) | default(wh_defaults.backend) == inventory_hostname %}
/opt/wh/{{ client.name }}/apps/{{ app.name }}/logs/*.log {
rotate 52
weekly
copytruncate
missingok
compress
compressoptions -T0
compresscmd /bin/xz
uncompresscmd /bin/unxz
compressext .xz
su {{ app.run_as | default('wh-' + client.name) }} {{ app.run_as | default('wh-' + client.name) }}
}
{% endif %}
{% endfor %}
{% endfor %}

View File

@@ -0,0 +1,35 @@
#!/bin/sh
# Set correct SELinux label
restorecon -R /opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}
# Remove all the ACL so we can start from scratch
setfacl -R --remove-all --remove-default /opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}
# Set permissions on the top level client dir. Not recursively !
# Here, the corresponding client only has read permissions (pus the tech team)
chown root:root /opt/wh/{{ item.0.name }}/{,apps}
chmod 750 /opt/wh/{{ item.0.name }}/
chmod 755 /opt/wh/{{ item.0.name }}/apps
setfacl -m u:apache:rX,g:Tech:rX,g:Client_{{ item.0.name }}:rX,u:{{ item.1.run_as | default('wh-' + item.0.name) }}:rX /opt/wh/{{ item.0.name }}/
# Set decent permissions, aka rw for files and rwx for directories. With setgid so the group owner is inherited to new files
find /opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}/{data,tmp,web} -type f -exec chmod 660 "{}" \;
find /opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}/{data,tmp,web} -type d -exec chmod 2770 "{}" \;
# Now, grant apache read access (needed for serving static assets), and full rw access to the client group. Set mask to full permission, we don't want to limit ACL. And excplicitely set other perms to 0
# Members of the tech team has write access for install/debug
setfacl -R -m u:apache:rX,d:u:apache:rX,g:Tech:rwX,d:g:Tech:rwX,g:Client_{{ item.0.name }}:rwX,d:g:Client_{{ item.0.name }}:rwX,u:{{ item.1.run_as | default('wh-' + item.0.name) }}:rwX,d:u:{{ item.1.run_as | default('wh-' + item.0.name) }}:rwX,m:rwX,o:- /opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}
# The bin folder shouldn't be visible to the client, it only contains admin's scripts
setfacl -R --remove-all --remove-default /opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}/bin
chown -R root:root /opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}/bin
chmod 700 /opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}/bin
chmod 750 /opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}/bin/*
# Info is readonly for the client (and the tech team)
setfacl -R --remove-all --remove-default /opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}/info
chown -R root:Client_{{ item.0.name }} /opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}/info
chmod 750 /opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}/info
chmod 640 /opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}/info/*
setfacl -R -m g:Tech:rX,d:g:Tech:rX,m:rwX,o:- /opt/wh/{{ item.0.name }}/apps/{{ item.1.name }}/info

View File

@@ -0,0 +1,61 @@
; {{ ansible_managed }}
{% for client in wh_clients | default([]) %}
{% for app in client.apps | default([]) %}
{% set app = wh_default_app | combine(app, recursive=True) %}
{% if app.php.enabled and app.php.version | string == wh_php_version | string and app.backend | default(client.backend) | default(wh_defaults.backend) == inventory_hostname %}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Begin pool {{ client.name }}-{{ app.name }}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[{{ client.name }}-{{ app.name }}]
listen.owner = root
listen.group = {{ httpd_group }}
listen.mode = 0660
listen = /run/php-fpm/php{{ wh_php_version }}-{{ client.name }}-{{ app.name }}.sock
user = {{ client.run_as | default('wh-' + client.name) }}
group = {{ client.run_as | default('wh-' + client.name) }}
catch_workers_output = yes
pm = dynamic
pm.max_children = 15
pm.start_servers = 3
pm.min_spare_servers = 3
pm.max_spare_servers = 6
pm.max_requests = 5000
request_terminate_timeout = 5m
php_flag[display_errors] = {{ app.php.display_error | ternary('on','off') }}
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /opt/wh/{{ client.name }}/apps/{{ app.name }}/logs/php_error.log
php_admin_value[memory_limit] = {{ app.php.memory_limit }}
php_admin_value[session.save_path] = /opt/wh/{{ client.name }}/apps/{{ app.name }}/tmp
php_admin_value[upload_tmp_dir] = /opt/wh/{{ client.name }}/apps/{{ app.name }}/tmp
php_admin_value[sys_temp_dir] = /opt/wh/{{ client.name }}/apps/{{ app.name }}/tmp
php_admin_value[post_max_size] = {{ app.php.upload_max_filesize }}
php_admin_value[upload_max_filesize] = {{ app.php.upload_max_filesize }}
php_admin_value[disable_functions] = {{ app.php.disabled_functions | difference(app.php.enabled_functions) | join(', ') }}
php_admin_value[open_basedir] = /opt/wh/{{ client.name }}/apps
php_admin_value[max_execution_time] = {{ app.php.max_execution_time }}
php_admin_value[max_input_time] = {{ app.php.max_execution_time }}
php_admin_flag[allow_url_include] = off
php_admin_flag[allow_url_fopen] = {{ app.php.allow_url_fopen | ternary('on','off') }}
php_admin_flag[file_uploads] = {{ app.php.file_uploads | ternary('on','off') }}
php_admin_flag[session.cookie_httponly] = on
{% if app.php.custom_conf is defined %}
{{ app.php.custom_conf }}
{% endif %}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; End pool {{ client.name }}-{{ app.name }}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
{% endif %}
{% endfor %}
{% endfor %}

View File

@@ -0,0 +1,3 @@
#!/bin/bash -e
rm -f /opt/wh/*/apps/*/db_dumps/*.sql.zst

View File

@@ -0,0 +1,17 @@
#!/bin/sh
set -eo pipefail
{% for client in wh_clients %}
{% for app in client.apps | default([]) %}
{% set app = wh_default_app | combine(app, recursive=True) %}
{% if app.backend | default(client.backend) | default(wh_defaults.backend) == inventory_hostname %}
mysqldump --add-drop-table --single-transaction \
--host={{ (wh_default_app | combine(app)).database.server | default(mysql_server) }} \
--user={{ client.name[0:7] }}_{{ app.name[0:7] }} \
--password="{{ (wh_default_app | combine(app)).database.pass | default((wh_pass_seed | password_hash('sha256', 65534 | random(seed=client.name + app.name) | string))[9:27] ) }}" \
{{ client.name[0:7] }}_{{ app.name[0:7] }} | \
zstd -c > /opt/wh/{{ client.name }}/apps/{{ app.name }}/db_dumps/{{ client.name[0:7] }}_{{ app.name[0:7] }}.sql.zst
{% endif %}
{% endfor %}
{% endfor %}

View File

@@ -0,0 +1,17 @@
#!/bin/bash -e
while true; do
{% for client in wh_clients %}
{% for app in client.apps %}
{% if app.backend | default(client.backend) | default(wh_defaults.backend) == inventory_hostname %}
if [ -e /opt/wh/{{ client.name }}/apps/{{ app.name }}/tmp/reset -o -e /opt/wh/{{ client.name }}/apps/{{ app.name }}/tmp/reset.txt ]; then
echo Reseting permissions for {{ client.name }} - {{ app.name }}
sh /opt/wh/{{ client.name }}/apps/{{ app.name }}/bin/perms.sh
echo Permissions for {{ client.name }} - {{ app.name }} have been reseted
rm -f /opt/wh/{{ client.name }}/apps/{{ app.name }}/tmp/reset /opt/wh/{{ client.name }}/apps/{{ app.name }}/tmp/reset.txt
fi
{% endif %}
{% endfor %}
{% endfor %}
sleep 5
done

View File

@@ -0,0 +1,14 @@
[Unit]
Description=Web Hosting ACL monitor daemon
[Service]
Type=simple
ExecStart=/usr/local/bin/wh-acld
PrivateTmp=yes
PrivateDevices=yes
MemoryLimit=100M
Restart=on-failure
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,13 @@
#!/bin/bash -e
{% for client in wh_clients %}
{% for app in client.apps %}
{% set app = wh_default_app | combine(app, recursive=True) %}
{% if app.backend | default(client.backend) | default(wh_defaults.backend) == inventory_hostname %}
echo Starting archiving {{ client.name }} - {{ app.name }}
sh /opt/wh/{{ client.name }}/apps/{{ app.name }}/bin/backup.sh
echo Archive for {{ client.name }} - {{ app.name }} created
{% endif %}
{% endfor %}
{% endfor %}

View File

@@ -0,0 +1,6 @@
---
wh_backend_packages:
- acl
- MySQL-python
- mariadb

View File

@@ -0,0 +1,7 @@
---
wh_backend_packages:
- acl
- python3-mysql
- mariadb