This repository contains an Ansible role for adding user accounts to Debian servers.
Lots of documentation needs to be added to this file, what follows probably
isn't up to date, see the releases
for updates and changes, do not use the master branch for production, it is
used for development.
For an example of a users YAML dictionary see the users dictionary for the development server that is used for testing this role.
By default this role will update users defined in the users dictionary for
whom their YAML dictionary has changed (see the defaults),
when their state is either "present" or "absent", if their state is set to
"ignore" then the account will simply be ignored.
The reason for this is since running all the tasks in this role for all the users takes a long time and usually runs a lot of tasks that won't make changes.
In order to keep track of the users state, on the server (so that updates can
be applied from different places), YAML files for each user are written to
sub-directories of /root/users called, current, previous and proposed.
By setting the users_update_strategy variable to "all" (rather than the
default of "changed") all, rather than only users with a changed users
dictionary, will be updated.
Alternative update strategies can be specified by setting the
users_update_strategy variable to a few optional values as explained below.
This is the default:
ansible-playbook users.yml --extra-vars "users_update_strategy=changed"Supply a comma separated list of users (or just one user name):
ansible-playbook wsh.yml --extra-vars "users_update_strategy=users users_update_users=foo,bar"ansible-playbook wsh.yml --extra-vars "users_update_strategy=all"ansible-playbook users.yml --extra-vars "users_update_strategy=check"ansible-playbook users.yml --extra-vars "users_update_strategy=apache"ansible-playbook users.yml --extra-vars "users_update_strategy=mariadb"ansible-playbook users.yml --extra-vars "users_update_strategy=matomo"ansible-playbook users.yml --extra-vars "users_update_strategy=quotas"ansible-playbook users.yml --extra-vars "users_update_strategy=phpfpm"ansible-playbook users.yml --extra-vars "users_update_strategy=sshkeys"Update all users which have users_groups defined:
ansible-playbook users.yml --extra-vars "users_update_strategy=groups"ansible-playbook users.yml --extra-vars "users_update_strategy=absent"To use this role you need to use Ansible Galaxy to install it into another
repository under galaxy/roles/users by adding a requirements.yml file in
that repo that contains:
---
- name: users
src: https://git.coop/webarch/users.git
version: master
scm: gitIf you want to use any of the quota_ variables then you also need to include
the quota role and make sure that quota_dir
is set to a mount point for a partition, for example have a seperate /home
partition.
---
- name: quota
src: https://git.coop/webarch/quota.git
version: master
scm: gitAnd a ansible.cfg that contains:
[defaults]
retry_files_enabled = False
pipelining = True
inventory = hosts.yml
roles_path = galaxy/roles
And a .gitignore containing:
galaxy/rolesTo pull this repo in run:
ansible-galaxy install -r requirements.yml --forceThe other repo should also contain a users.yml file that contains:
---
- name: Add user accounts
become: yes
vars:
users:
foo:
users_name: Foo Bar
users_email: [email protected]
users_home: /var/www/foo
users_skel: /usr/local/etc/skel.d/www
users_group: users
users_home_owner: root
users_home_group: users
# You must quote the mode or the leaving zero is stripped"
users_home_mode: "0750"
users_system: true
users_shell: /usr/sbin/nologin
users_generate_ssh_key: true
users_editor: vim
users_groups:
- users
- ssl-cert
users_group_members:
- www-data
bar:
users_home: /opt/bar
users_shell: /bin/false
users_system: true
users_quota: 1G
baz:
users_groups:
- staff
- users
users_editor: vim
users_quota_block_softlimit: 1908874
users_quota_block_hardlimit: 2097152
users_quota_inode_softlimit: 9532
users_quota_inode_hardlimit: 10484
chris:
users_groups:
- sudo
- operator
users_editor: vim
users_ssh_public_keys:
- https://git.coop/chris.keys
fred:
users_state: absent
hosts:
- users_servers
roles:
- usersAnd a hosts.yml file that contains lists of servers, for example:
---
all:
children:
users_servers:
hosts:
host3.example.org:
host4.example.org:
cloud.example.com:
cloud.example.org:
cloud.example.net:Then it can be run as follows:
ansible-playbook users.ymlIf users_update_strategy: check, for example on the command line using
--extra-vars "users_update_strategy=true" then no changes will be made other
than to generate /root/users/proposed/*.yml state files.
If users_domain_check is set to strict then if a domain name doesn't
resolve to the servers IP address then the tasks will stop rather than just
warn.
So, for example:
ansible-galaxy install -r requirements.yml --force && \
ansible-playbook users.yml --extra-vars "users_update_strategy=check"The boolean variable users_notify_passwd can be used to trigger the setting and sending of a SSH password to the users_email address, the password will only be sent one and the date it was sent and the email address it was sent to is recorded in a ~/.notify_passwd file, this is also used to ensure that a email is not sent twice -- if the file exists a new password will not be sent, to trigger the sending of a new password delete the ~/.notify_passwd file.
If users_cron is defined and true, at a server level then for users that are
in the chroot group a daily, hourly and minutely cron job is created to run
bash scripts that are created (empty by default) at ~/bin/cron_daily.sh,
~/bin/cron_hourly.sh and ~/bin/cron_minutely.sh in the chroot, for
non-chrooted users a cron job is created to run the same bash scripts.
Tasks can be added to the Bash scripts using the users_daily_scripts,
users_hourly_scripts and users_minutely_scripts arrays at a user level, or
they can be manually added not using Ansible by users, for example to archive
Matomo stats on an hourly basis:
users_hourly_scripts:
- "cd ~/sites/default && php console --no-ansi -qn core:archive --force-all-websites > ~/private/matomo-archive.log"
- "cd ~/sites/default && php console --no-ansi -qn core:run-scheduled-tasks > ~/private/matomo-archive.log"To run the Nextcloud cron job every minute:
users_minutely_scripts:
- "php --php-ini ~/.php.ini -f ~/sites/cloud/cron.php"The number of minutes part the hour that the daily and hourly script run at in
set randomly for each user and saved in ~/.cron_min to ensure that all the
jobs for different users don't run at the same time.
Also some features of the Ansible cron
module
can used used via a users_cron_jobs array set at the users level, for
example:
users_cron_jobs:
- name: printenv
job: printenv
minute: 2
- name: echo foo
job: echo foo
state: absent
minute: "*/5"The users_ssh_public_keys array should be set to a list of one or more URL's
for public keys (eg from GitHub).
All the files at the URL's will be downloaded to files named:
~/.ssh/authorized_keys.d/authorized_keys_0~/.ssh/authorized_keys.d/authorized_keys_1~/.ssh/authorized_keys.d/authorized_keys_2
Then the ~/.ssh/authorized_keys.d/authorized_keys_* files are assembled to
~/.ssh/authorized_keys, (inless this file name is overridden from the
default, see the users_ssh_authorized_keys_file_name variable) this means if
you want to add additional keys then you can simply add them to this directory,
with a suitable filename, eg ~/.ssh/authorized_keys.d/authorized_keys_extra.
When users_apache_virtual_hosts_enabled is not defined for a user or it is
set to True the Apache config generate for the user will be anabled using
a2ensite, when users_apache_virtual_hosts_enabled is set to False then
a2dissite will be run for the user.
By default a DocumentRoot and Directory set of directives are generated for
each VirtualHost based on the YAML dictionaries defined by
users_apache_virtual_hosts. This DocumentRoot and Directory will be
omitted if users_apache_vhost_docroot is set to False at a VirtualHost
level (prior to version 3.0.0 of this role the users_apache_vhost_docroot
was not a boolean and could be set to a path, however this wasn't used so it
was re-purposed) . Additional Directory sections can be added using
users_apache_directories at a VirtualHost level.
SSH and PHP-FPM users in the chroot group are chrooted to a a read-only
chroot at /chroots/USER which has /home/USER mounted read-write at
/chroots/USER/home/USER.
Apache, unlike SSH and PHP-FPM, which can have a seperate chroot per user, has
one chroot for the whole server, not one per user or VirtualHost, however
when combined with suEXEC which allows the group and user that runs scripts
via CGID or FastCGI to be set via SuexecUserGroup for CGI applications it is
possible to use the Apache chroot isolate users running CGI from the
environment in which other services are running.
One restriction that suEXEC has is to only allow CGI to be run in
sub-directories on /var/www, so to get around this when Apache is chrooted
users home directories are also mounted under /var/www/users and under the
same path in the Apache chroot and under the same path in the SSH / PHP-FPM
chroots.
NOTE: When Apache is chrooted the PHP-FPM config needs to be regenerated, the Apache config does self detect if Apache is chrooted and configure itself but this isn't the case for PHP-FPM, generate new PHP=FPM config using:
ansible-playbook users.yml --extra-vars "users_update_strategy=phpfpm"The way this role has been designed to implement this is having a read-write
chroot at /chroot which is then mounted at /chroots/www-data read-only and
then on top of that /home/USER is mounted read-write at
/chroots/www-data/home/USER and also at
/chroots/www-data/var/www/users/USER.
In addition some other directories are automatically mounted to get it all to work. ### Options
Server wide settings for VirtualHosts can be set like this (see the commented
out variables in defaults/main.yml):
users_apache_options:
- -Indexes
- +SymlinksIfOwnerMatch
- -MultiViews
- +IncludesNOEXEC
- -ExecCGI
users_apache_index:
- index.php
- index.html
- index.shtml
- index.htm
users_apache_override:
- AuthConfig
- FileInfo
- Indexes
- Limit
- Options=Indexes,SymLinksIfOwnerMatch,MultiViews,IncludesNOEXEC
- Nonfatal=OverrideIf these variables ar not set server-wide then the users_apache_type variable
can be used per VirtualHost and if it is set to php then these directives
are used:
Options -Indexes +SymlinksIfOwnerMatch -MultiViews +IncludesNOEXEC -ExecCGI
DirectoryIndex index.php index.html index.htm index.shtml
AllowOverride AuthConfig FileInfo Indexes Limit Options=Indexes,SymLinksIfOwnerMatch,MultiViews,IncludesNOEXEC Nonfatal=OverrideAnd if users_apache_type is set to cgi then these directives are used:
Options -Indexes +SymlinksIfOwnerMatch -MultiViews +IncludesNOEXEC +ExecCGI
DirectoryIndex index.cgi index.pl index.html index.htm index.shtml
AllowOverride AuthConfig FileInfo Indexes Limit Options=ExecCGI,SymLinksIfOwnerMatch,MultiViews,IncludesNOEXEC Nonfatal=OverrideAnd if users_apache_type is omitted (or set to a value such as static) then
these defaults are used:
Options -Indexes +SymlinksIfOwnerMatch -MultiViews +IncludesNOEXEC -ExecCGI
DirectoryIndex index.html index.htm index.shtml
AllowOverride AuthConfig FileInfo Indexes Limit Options=Indexes,SymLinksIfOwnerMatch,MultiViews,IncludesNOEXEC Nonfatal=OverrideIn addition there is support for cgi and fcgi together with Apache being
chrooted and running suEXEC to ensure the the processes run as the user
specified and have limited access to the host.
Support for enabling several users_apache_type's is to be implemended and the
variable might be renamed to a more sensible users_apache_handlers, but for
now the users_apache_type options are:
cgifcgiphpphp+cgiphp+fcgistatic
The optional Apache configuration arrays users_apache_cgi_extensions and
users_phpfpm_extensions can be used at a role, VirtualHost or Directory
level to change the defaults from cgi and pl for CGI / FCGI and php for
PHP-FPM.
The optional Apache configuration boolean users_apache_cgi_extension_match
can be set to false to enable all files for a user, VirtualHost or
Directory to be processed as CGI / FCGI, this is needed some things like a
Munin Master.
The arrays, users_apache_options, users_apache_index and
users_apache_override can also be set by VirtualHost aand if they are these
overrule the other settings for the DocumentRoot directory, see the Apache
template for the details.
Basic authentication can be set on the DocumentRoot directory, for example
for MediaWiki (the VisualEditor needs access via the localhost):
users_apache_auth_name: Private
users_apache_auth_type: Basic
users_apache_require:
- valid-user
- ip "{{ ansible_facts.default_ipv4.address }}"
- ip 127.0.0.1The same users_apache_htauth_users array is used for the usernames and
passwords as documented below.
The users_apache_env array can be used to set and remove environmental
variables, at a server or VirtualHost level, via the
SetEnv and
the
UnsetEnv
Apache directives, for example:
users_apache_env:
- env: FOO
value: bar
- env: BAZ
set: falseWill generate:
SetEnv FOO bar
UnsetEnv BAZThe users_apache_set_env_if array can be used to set env vars, at a server or
VirtualHost level, using the
SetEnvIf
and the
SetEnvIfNoCase
Apache directives, for example:
users_apache_set_env_if:
- attribute: Host
regex: "^(.*)$"
env: THE_HOST=$1
case: falseWill generate:
SetEnvIfNoCase Host "^(.*)$" THE_HOST=$1And if case is omitted or set to True:
SetEnvIf Host "^(.*)$" THE_HOST=$1Will generate:
SetEnvIf Host "^(.*)$" THE_HOST=$1See the Apache SetEnvIf documentation.
The users_apache_headers array can be used to set Header and RequestHeader directives, at a VirtualHost level, for example:
users_apache_headers:
- type: request
action: set
expr: "Content-Security-Policy: default-src 'self'"
- type: request
action: setifempty
arg: X-Forwarded-Proto https
- type: request
action: setifempty
arg: X-Forwarded-Host %{THE_HOST}eThe response header type accepts an optional condition, con, an action, action and an expression, expr, for example
Header {% if header.con is defined %}{{ header.con }} {% endif %}{{ header.action }} {{ header.expr }}The request type accepts an action, action and an argument, arg:
RequestHeader {{ header.action }} {{ header.arg }}The users_apache_redirects array can be used to generate
Redirect,
RedirectMatch,
RedirectPermanent
and
RedirectTemp
directives, for example:
users_apache_redirects:
- path: /service
url: http://foo2.example.com/service
status: "301"
- path: /one
url: http://example.com/two
status: permanent
- regex_path: (.*)\.gif$
url: http://other.example.com$1.jpg
# Use a `regex_path` if you want to use RedirectMatch rather than Redirect so that any URL
# on the domain is redirected to a specific page
- regex_path: (.*)
url: http://new.example.com/aboutThe users_apache_alias array can be used to generate
Alias and
AliasMatch
directives, for example:
users_apache_alias:
- url: /image
path: /ftp/pub/image
- url: /icons/
path: /usr/local/apache/icons/
- url_regex: ^/icons(/|$)(.*)
path: /usr/local/apache/icons$1$2The users_apache_alias array can also be used to generate
ScriptAlias and
ScriptAliasMatch
directives, for example:
users_apache_alias:
- url: /munin/static
path: /var/cache/munin/www/static
- script: /munin
path: /usr/lib/munin/cgi/munin-cgi-html
- script: /munin-cgi/munin-cgi-graph
path: /usr/lib/munin/cgi/munin-cgi-graphThe users_apache_rewrite array can be used to set RewriteCond and
RewriteRule directives, for example:
users_apache_rewrite:
- cond: %{HTTP_USER_AGENT} DavClnt
- rule: ^$ /remote.php/webdav/ [L,R=302]
- rule: .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
- rule: ^\.well-known/carddav /remote.php/dav/ [R=301,L]
- rule: ^\.well-known/caldav /remote.php/dav/ [R=301,L]
- rule: ^remote/(.*) remote.php [QSA,L]
- rule: ^(?:build|tests|config|lib|3rdparty|templates)/.* - [R=404,L]
- rule: ^\.well-known/(?!acme-challenge|pki-validation) /index.php [QSA,L]
- rule: ^(?:\.(?!well-known)|autotest|occ|issue|indie|db_|console).* - [R=404,L]To generate:
RewriteEngine on
RewriteCond %{HTTP_USER_AGENT} DavClnt
RewriteRule ^$ /remote.php/webdav/ [L,R=302]
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteRule ^\.well-known/carddav /remote.php/dav/ [R=301,L]
RewriteRule ^\.well-known/caldav /remote.php/dav/ [R=301,L]
RewriteRule ^remote/(.*) remote.php [QSA,L]
RewriteRule ^(?:build|tests|config|lib|3rdparty|templates)/.* - [R=404,L]
RewriteRule ^\.well-known/(?!acme-challenge|pki-validation) /index.php [QSA,L]
RewriteRule ^(?:\.(?!well-known)|autotest|occ|issue|indie|db_|console).* - [R=404,L]The users_apache_locations array can be used to apply HTTP Authentication to
URL paths, for example:
users_apache_locations:
- authname: WordPress Login
location: /wp-login.php
- authname: WordPress Admin
location: /wp-admin/
- authname: WordPress Admin Ajax
location: /wp-admin/admin-ajax.php
authtype: None
require:
- all granted
- authname: WordPress Config
location: /wp-config.php
authtype: None
require:
- all deniedThe users_apache_htauth_users array can be used to set usernames and
passwords, these are written to ~/.htpasswd/ in one file per
users_apache_server_name, the optional state variables can be set to absent
to remove users, for example:
users_apache_htauth_users:
- name: foo
password: bar
- name: baz
state: absentIf the authtype is set to None then AuthUserFile isn't set and then a
require array can be set to do things like only allow a few IP
addresses,
for example:
users_apache_locations:
- authname: Drupal Login
location: /user/login
authtype: None
require:
- ip 10 172.20 192.168.2
- method GET POSTIt is also possible to set Redirect, see the Apache
Documentation:
users_apache_locations:
- location: old-site/
redirect: https://example.org/An Alias can also be used in a Location:
users_apache_locations:
- location: /static
alias: /home/foo/sites/www/staticfilesAnd ProxyPass and ProxyPassReverse can be used in a Location, see the
Apache ProxyPass documentation:
users_apache_locations:
- location: /push/
proxy_pass: http://127.0.0.1:7867/
reverse: true
- location: /push/ws
proxy_pass: ws://127.0.0.1:7867/wsResults in:
<Location "/push/" >
ProxyPass "http://127.0.0.1:7867/"
ProxyPassReverse "http://127.0.0.1:7867/
</Location>
<Location "/push/ws" >
ProxyPass "ws://127.0.0.1:7867/ws"
</Location>If match is set rather than location then a LocationMatch directive is generated, for example:
users_apache_locations:
- match: ^/$
redirect: https://example.org/welcome/Results in:
<LocationMatch "^/$">
Redirect https://example.org/welcome/
</LocationMatch>The users_apache_directories variable can be used at a VirtualHost level to
list dictionaries representing Directory directives, either relative to the
users_sites_dir path, when they don't start with a / or using fill paths.
The variables that can be used are the same as the ones for the DocumentRoot
directory apart from users_apache_type, this can't be used to set the
Directory type to php or static.
Note that only relative paths will automatically be updated depending on Apache being chrooted or not.
Prior to version 3.0.0 of this role users_apache_directories was used for
an array of directories and when it was used the default DocumentRoot /
Directory was omitted, from version 3.0.0 and up a dictionary rather than
an array is used and the default DocumentRoot / Directory can be ommitted
by setting users_apache_vhost_docroot to False.
This is handy when some directories are needed for static
content and Alias and also a proxy, for example:
users_apache_virtual_hosts:
api:
users_apache_type: static
users_apache_server_name: "api.{{ inventory_hostname }}"
users_apache_alias:
- url: /static
path: /home/api/sites/api/staticfiles
- url: /media
path: /home/api/sites/api/media
users_apache_directories:
api/static:
users_apache_options:
- Indexes
api/media:
users_apache_options:
- Indexes
users_apache_proxy:
- path: /static
url: "!"
- path: /media
url: "!"
- path: /
url: http://127.0.0.1:8000/
reverse: trueAnd this will generate:
Alias "/static" "/home/api/sites/api/staticfiles"
Alias "/media" "/home/api/sites/api/media"
<Directory "/home/api/sites/api/staticfiles">
Options Indexes
AllowOverride None
Require all granted
</Directory>
<Directory "/home/api/sites/api/media">
Options Indexes
AllowOverride None
Require all granted
</Directory>
ProxyPass "/static" "!"
ProxyPass "/media" "!"
ProxyPass "/" "http://127.0.0.1:8000/"
ProxyPassReverse "/" "http://127.0.0.1:8000/"Prior to version 4.3.1 of this role users_apache_directories could only be used for directories relative to the users_sites_dir path, this is still the case when they doesn't start with a /, but now a full path can also be used, for example:
users_apache_virtual_hosts:
openproject:
users_apache_server_name: "{{ inventory_hostname }}"
users_apache_vhost_docroot: false
users_apache_expires: medium
users_apache_directories:
"/opt/openproject/public":
users_apache_override:
- "None"
users_apache_options:
- "-Indexes"
users_apache_headers:
- type: request
action: setifempty
arg: X-Forwarded-Proto https
users_apache_alias:
- url: /assets
path: /opt/openproject/public/assets
- url: /uploads
path: /opt/openproject/public/uploads
users_apache_locations:
- location: "/"
proxy_pass: http://127.0.0.1:6000/
reverse: true
- location: "/assets"
proxy_pass: "!"
- match: "^/sys"
authname: Local connections only
authtype: None
require:
- localWill generate:
<IfModule headers_module>
RequestHeader setifempty X-Forwarded-Proto https
Header setifempty Strict-Transport-Security "max-age=155520225;"
Header setifempty Permissions-Policy "interest-cohort=()"
</IfModule>
<Location "/">
# ProxyPass Location so no AuthUserFile
ProxyPass "http://127.0.0.1:6000/"
ProxyPassReverse "http://127.0.0.1:6000/"
</Location>
<Location "/assets">
# ProxyPass Location so no AuthUserFile
ProxyPass "!"
</Location>
<LocationMatch "^/sys">
# No AuthUserFile for this Location as AuthType is None
AuthName "Local connections only"
AuthType "None"
Require local
</LocationMatch>
<Location "/.well-known/acme-challenge">
AuthType "None"
Require all granted
</Location>
<IfModule alias_module>
Alias "/assets" "/opt/openproject/public/assets"
Alias "/uploads" "/opt/openproject/public/uploads"
</IfModule>
<IfFile "/opt/openproject/public">
<Directory "/opt/openproject/public">
Options -Indexes
DirectoryIndex index.html index.htm
AllowOverride None
AuthType "None"
Require all granted
</Directory>
</IfFile>If no Directories are required use users_apache_directories: [].
The following variables can be used to control the content of the Directory directive:
A list of AddOutputFilter directives, for example:
users_apache_add_output_filters:
- INCLUDES;DEFLATE shtmlWill generate:
AddOutputFilter INCLUDES;DEFLATE shtmlA list of MIME types and extensions for AddType, for example:
users_apache_add_type:
- ext: bar
type: text/plainWill generate:
AddType text/plain .barNote that the dot before the file extensions is added automatically.
A AuthName, for example:
users_apache_auth_name: Authentication Required AuthName Authentication RequiredA AuthType, when set to Basic a AuthUserFile directive is automatically added to point to the htpasswd for for the VirtualHost, for example:
users_apache_auth_type: Basic AuthType Basic
AuthUserFile: /home/example/.htpasswd/fooThe other options, Digest and Form and None can be used but they don't add any additional directives.
Set users_apache_expires to one of these values:
- active
- forever
- medium
- strict
For one of the Apache role templates to be added as a IncludeOptional for the Directory:
A list ofFilesMatch and Require directives for example:
users_apache_filesmatch:
- regex: "^(?<sitename>[^/]+)"
require:
- "ldap-group cn=%{env:MATCH_SITENAME},ou=combined,o=Example"<FilesMatch "^(?<sitename>[^/]+)">
Require ldap-group cn=%{env:MATCH_SITENAME},ou=combined,o=Example
</FilesMatch>If require is omitted then it will default to Require all denied.
A values for the HeaderName, for example:
users_apache_header_name: HEADER.htmlHeaderName HEADER.htmlA list of file names for the DirectoryIndex directive, for example:
users_apache_index:
- index.htm
- index.html
- index.phpDirectoryIndex index.htm index.html index.phpText for the IndexHeadInsert directive, for example:
users_apache_head_insert: '<link rel=\"sitemap\" href=\"/sitemap.html\">'IndexHeadInsert '<link rel=\"sitemap\" href=\"/sitemap.html\">'A list of options for the IndexOptions, for example:
users_apache_index_options:
- +ScanHTMLTitles
- -IconsAreLinks
- FancyIndexingIndexOptions +ScanHTMLTitles -IconsAreLinks FancyIndexingA values for the ReadmeName, for example:
users_apache_readme_name: FOOTER.htmlReadmeName FOOTER.htmlA list of requirements for the Apache Require directive, for example:
users_apache_require:
- ip 10 172.20 192.168.2
- method http-method GET HEADRequire ip 10 172.20 192.168.2
Require method http-method GET HEADA boolean, enable the SSILegacyExprParser.
A boolean, enable SSILastModified.
Configure a reverse proxy, for example for a Nextcloud notify_push server like this you can specify:
users_apache_proxy:
- path: /push/ws
url: ws://127.0.0.1:7867/ws
- path: /push/
url: http://127.0.0.1:7867/
reverse: trueAnd this will generate:
ProxyPass "/push/ws" "ws://127.0.0.1:7867/ws"
ProxyPass "/push/" "http://127.0.0.1:7867/"
ProxyPassReverse "/push/" "http://127.0.0.1:7867/"For a reverse proxy to a Rocket.Chat server installed using snaps:
users_apache_proxy:
- path: /
url: http://127.0.0.1:3000/
rewrite_conditions:
- '%{HTTP:Upgrade} websocket [NC]'
- '%{HTTP:Connection} upgrade [NC]'
rewrite_rules:
- '^/?(.*) "ws://127.0.0.1:3000/$1" [P,L]'
reverse: trueAnd this will generate:
ProxyPass "/" "http://127.0.0.1:3000/"
RewriteEngine on
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://127.0.0.1:3000/$1" [P,L]
ProxyPassReverse "/" "http://127.0.0.1:3000/"For an ONLYOFFICE server:
users_apache_set_env_if:
- attribute: Host
regex: "^(.*)$"
env: THE_HOST=$1
users_apache_headers:
- type: request
action: setifempty
argument: X-Forwarded-Proto https
- type: request
action: setifempty
argument: X-Forwarded-Host %{THE_HOST}e
users_apache_proxy:
- add_headers: false
path: /.well-known
url: !
- pathmatch: (.*)(\/websocket)$
url: "ws://127.0.0.1:8006/$1$2"
- path: /
url: http://127.0.0.1:8006/
reverse: trueA reverse proxy to an applicaton that doesn't have any user authentication can be configured to use HTTP Authentication, for example for Mailcatcher:
users_apache_proxy:
- path: /
url: http://127.0.0.1:1080/
reverse: true
authname: Password protected
authtype: Basic
require: valid-user
users_apache_htauth_users:
- name: mailcatcher
password: fooBy default the reverse proxy doesn't proxy error documents served from /wsh:
ProxyErrorOverride On
ProxyPass "/wsh/" "!"If an users_apache_filesmatch array specified at the VirtualHost level with
a list of regex like this:
users_apache_filesmatch:
- regex: '^license\.txt$'
- regex: '^readme\.html$'
- regex: '^xmlrpc\.php$'Then access to these files will be denied, unless one of more require items
are listed, for example:
users_apache_filesmatch:
- regex: '^xmlrpc\.php$'
require:
- method GET HEAD
- ip 9.9.9.9The optional users_apache_expires variable can be used to select the
medium
or
strict
configuration to Include into the VirtualHost.
The optional users_apache_robots variable can be set to deny to Include
the robots
config
and this will also set an Alias for the
robots.txt
file.
Some specific PHP variables include the users_apache_nophp_dirs array, this
can be used to list directories that PHP cannot be used in, for example
directories where users can upload files, for example:
users_apache_nophp_dirs:
- wp-content/uploadsIn order to enable PHP settings for the CLI to be configured differently for each user this Bash alias is written to ~/.bash_aliases:
alias php="php --php-ini ~/.php.ini"And ~/.php.ini is created with the following three lines (with example replaced with the username):
sys_temp_dir = "/home/example/tmp"
memory_limit = -1
apc.enable_cli = 1Setting memory_limit = -1 overides the default of 128M, this is required for the Nextcloud CLI updater.
By default, for users in the phpfpm group, the PHP socket is created in the $HOME directory as ~/php-fpm.sock, the use of this path by the Apache configuratoon can be overridden when Apache is not chrooted and the users is not chrooted, per VirtualHost by setting users_apache_php_socket_path to a path to another socket however this variable is not used by this roles PHP tasks, the socket configuration needs to be done by the PHP role.
The optional users_phpfpm_admin_flags array can be used to set per-pool configuration, for example Nextcloud uses SabreDav so requires the following in the PHP-FPM pool.d file:
php_admin_flag[always_populate_raw_post_data] = no
php_admin_flag[magic_quotes_gpc] = no
php_admin_flag[mbstring.func_overload] = no
php_admin_flag[output_buffering] = noThis can be achieved by setting the following at a user level:
users_phpfpm_admin_flags:
- name: always_populate_raw_post_data
value: false
- name: magic_quotes_gpc
value: false
- name: mbstring.func_overload
value: false
- name: output_buffering
value: falseIf output_buffering needs to be set to a value other than on / off then this need to be set at the PHP version level rather than a users level.
If a user has a set of variables like this:
users_apache_virtual_hosts:
default:
users_apache_type: php
users_apache_php_socket_path: /run/php/php8.2-fpm.sock
users_apache_nophp_dirs:
- wp-content/uploads
users_apache_server_name: wordpress.example.org
users_apache_server_aliases:
- www.wordpress.example.org
users_cms: wordpress
wordpress_dbname: wordpress_live
users_daily_scripts:
- "wp-update {{ users_basedir }}/wordpress/{{ users_sites_dir }}/default"
users_apache_htauth_locations:
- name: WordPress Config
location: /wp-config.php
type: None
require:
- all denied
users_apache_nophp_dirs:
- wp-content/uploads
users_apache_expires: medium
dev:
users_apache_type: php
users_apache_nophp_dirs:
- wp-content/uploads
users_apache_robots: deny
users_apache_server_name: dev.wordpress.example.org
users_apache_server_aliases:
- www.dev.wordpress.example.org
users_cms: wordpress
wordpress_dbname: wordpress_dev
users_daily_scripts:
- "wp-update {{ users_basedir }}/wordpress/{{ users_sites_dir }}/default"
users_apache_htauth_users:
- name: foo
password: bar
users_apache_htauth_locations:
- name: WordPress Login
location: /wp-login.php
- name: WordPress Config
location: /wp-config.php
type: None
require:
- all denied
users_apache_expires: medium
It will generate an Apache config like this:
# Ansible managed
# wordpress.example.org
# /home/wordpress/sites/default
<VirtualHost *:80>
ServerName wordpress.example.org
ServerAlias www.wordpress.example.org
RedirectMatch 301 ^(?!/\.well-known/acme-challenge/).* https://wordpress.example.org$0
</VirtualHost>
# wordpress.example.org
# /home/wordpress/sites/default
<VirtualHost *:443>
ServerName wordpress.example.org
ServerAlias www.wordpress.example.org
SSLEngine on
SSLCertificateFile /etc/ssl/le/wordpress.wordpress.example.org.cert.pem
SSLCertificateKeyFile /etc/ssl/le/wordpress.wordpress.example.org.key.pem
SSLCertificateChainFile /etc/ssl/le/wordpress.wordpress.example.org.ca.pem
ServerAdmin "[email protected]"
<Location "/wp-config.php" >
# No AuthUserFile for this Location
AuthName "WordPress Config"
AuthType None
Require all denied
</Location>
DocumentRoot "/home/wordpress/sites/default"
<Directory "/home/wordpress/sites/default">
Options -Indexes +SymlinksIfOwnerMatch -MultiViews +IncludesNOEXEC -ExecCGI
DirectoryIndex index.php index.html index.htm index.shtml wsh.shtml
AllowOverride AuthConfig FileInfo Indexes Limit Options=Indexes,SymLinksIfOwnerMatch,MultiViews,IncludesNOEXEC Nonfatal=Override
<IfModule proxy_fcgi_module>
<IfModule setenvif_module>
SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1
</IfModule>
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
SetHandler "proxy:unix:/users/wordpress/home/wordpress/php-fpm.sock|fcgi://localhost"
</If>
</FilesMatch>
</IfModule>
Require all granted
IncludeOptional "/etc/apache2/conf-available/expires-medium.conf"
</Directory>
# No PHP allowed in this directory
<Directory "/home/wordpress/sites/default/wp-content/uploads">
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
Require all denied
</If>
</FilesMatch>
</Directory>
CustomLog /var/log/apache2/wordpress_access.log bandwidth
LogLevel error
ErrorLog /home/wordpress/logs/apache.error.log
CustomLog /home/wordpress/logs/apache.access.log combinedio
</VirtualHost>
# dev.wordpress.example.org
# /home/wordpress/sites/dev
<VirtualHost *:80>
ServerName dev.wordpress.example.org
ServerAlias www.dev.wordpress.example.org
RedirectMatch 301 ^(?!/\.well-known/acme-challenge/).* https://dev.wordpress.example.org$0
</VirtualHost>
# dev.wordpress.example.org
# /home/wordpress/sites/dev
<VirtualHost *:443>
ServerName dev.wordpress.example.org
ServerAlias www.dev.wordpress.example.org
SSLEngine on
SSLCertificateFile /etc/ssl/le/wordpress.wordpress.example.org.cert.pem
SSLCertificateKeyFile /etc/ssl/le/wordpress.wordpress.example.org.key.pem
SSLCertificateChainFile /etc/ssl/le/wordpress.wordpress.example.org.ca.pem
ServerAdmin "[email protected]"
IncludeOptional /etc/apache2/conf-available/robots-deny.conf
<Location "/wp-login.php" >
AuthUserFile "/home/wordpress/.htpasswd/dev.wordpress.example.org"
AuthName "WordPress Login"
AuthType Basic
Require valid-user
</Location>
<Location "/wp-config.php" >
# No AuthUserFile for this Location
AuthName "WordPress Config"
AuthType None
Require all denied
</Location>
DocumentRoot "/home/wordpress/sites/dev"
<Directory "/home/wordpress/sites/dev">
Options -Indexes +SymlinksIfOwnerMatch -MultiViews +IncludesNOEXEC -ExecCGI
DirectoryIndex index.php index.html index.htm index.shtml wsh.shtml
AllowOverride AuthConfig FileInfo Indexes Limit Options=Indexes,SymLinksIfOwnerMatch,MultiViews,IncludesNOEXEC Nonfatal=Override
<IfModule proxy_fcgi_module>
<IfModule setenvif_module>
SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1
</IfModule>
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
SetHandler "proxy:unix:/users/wordpress/home/wordpress/php-fpm.sock|fcgi://localhost"
</If>
</FilesMatch>
</IfModule>
Require all granted
IncludeOptional "/etc/apache2/conf-available/expires-medium.conf"
</Directory>
# No PHP allowed in this directory
<Directory "/home/wordpress/sites/dev/wp-content/uploads">
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
Require all denied
</If>
</FilesMatch>
</Directory>
CustomLog /var/log/apache2/wordpress_access.log bandwidth
LogLevel error
ErrorLog /home/wordpress/logs/apache.error.log
CustomLog /home/wordpress/logs/apache.access.log combinedio
</VirtualHost>
# vim: set ft=apache:
-
Generate a CHANGELOG.md from the releases perhaps using Release Exporter
-
Better documentation
-
Add more options from the Ansible user module
Copyright 2018-2025 Chris Croome, <[email protected]>.
This role is released under the same terms as Ansible itself, the GNU GPLv3.