diff --git a/.gitignore b/.gitignore index b97971c..be9e35a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +site.retry site.yml ansible_hosts *.pyc diff --git a/doc/role-doc/antivirus.md b/doc/role-doc/antivirus.md new file mode 100644 index 0000000..0b56933 --- /dev/null +++ b/doc/role-doc/antivirus.md @@ -0,0 +1,25 @@ +# Summary + +## Description + +This role installs and configure `chkrootkit`, and rootkit checker software. It +is configured to run daily and to send alerts by e-mail to the administrator. + +This role is automatically included in the `common` role. + +## Prerequired roles + +- `base-packages` +- `base-config` + +# Manual steps + +# Configuration parameters (ansible variables) + +## Mandatory parameters + +None. + +## Optional parameters + +None. diff --git a/doc/role-doc/backupninja.md b/doc/role-doc/backupninja.md index eed78f2..ab29213 100644 --- a/doc/role-doc/backupninja.md +++ b/doc/role-doc/backupninja.md @@ -12,7 +12,8 @@ backed-up directories is still static. ## Prerequired roles -- `common` +- `base-packages` +- `base-config` # Manual steps to setup backup system diff --git a/doc/role-doc/base-config.md b/doc/role-doc/base-config.md new file mode 100644 index 0000000..a8acbac --- /dev/null +++ b/doc/role-doc/base-config.md @@ -0,0 +1,44 @@ +# Summary + +## Description + +This role ensures that the `root` account in `/etc/aliases` forwards messages to +the e-mail address defined by the administrator (see below), and installs +`apticron` to notify of available package updates. It also installs `postfix` to +ensure the e-mail delivery of `apticron` alerts and configures it not to listen +on the network. + +This role is automatically included in the `common` role. + +## Prerequired roles + +None. + +# Manual steps + +# Configuration parameters (ansible variables) + +## Mandatory parameters + +### `admin_email` + +Email address of the administrator, where Cron messages and various security +alerts will be sent to. + +### `base_force_postfix_master_cf` + +Default: `False` + +This variable decides whether the role is forced to install a postfix +configuration file `master.cf` that disables any network-listening daemon of +postfix, for security reasons. When left to the default (`False`), the role will +refrain from installing it if it detects that that file was previously installed +by the `virtualmail` role, in order to prevent the two roles from fighting for +the same file. + +It is useful to set this to `True` if you used to use the `virtualmail` role but +are not using it anymore. + +## Optional parameters + +None. diff --git a/doc/role-doc/base-hardening.md b/doc/role-doc/base-hardening.md new file mode 100644 index 0000000..79d9a76 --- /dev/null +++ b/doc/role-doc/base-hardening.md @@ -0,0 +1,39 @@ +# Summary + +## Description + +This role hardens some system defaults: + +- makes every new file created by any user accessible only by the given user, by + setting PAM's `umask` parameter to 077; +- allows only one user to connect using SSH: the user ansible uses to connect to + the host (it is possible to specify additional users - see below); +- prevents the superuser from connecting directly via SSH, unless it is used by + ansible to connect to the server; +- sets some `sysctl` parameters to values more advisable for security. + +This role is automatically included in the `common` role. + +## Prerequired roles + +None. + +# Manual steps + +# Configuration parameters (ansible variables) + +## Mandatory parameters + +None. + +## Optional parameters + +### `ssh_additional_users` + +This optional parameter must be an array of authorized users that will be +allowed to access your server using SSH. They will be added to the `AllowUsers` +directive of the SSH server configuration. + +Example: + + ssh_additional_users: [kheops, timmy] diff --git a/doc/role-doc/base-packages.md b/doc/role-doc/base-packages.md new file mode 100644 index 0000000..530c259 --- /dev/null +++ b/doc/role-doc/base-packages.md @@ -0,0 +1,24 @@ +# Summary + +## Description + +This role installs some base packages on the system and activates the +"backports" Debian repository. It also removes several unwanted packages. + +This role is automatically included in the `common` role. + +## Prerequired roles + +None. + +# Manual steps + +# Configuration parameters (ansible variables) + +## Mandatory parameters + +None. + +## Optional parameters + +None. diff --git a/doc/role-doc/common.md b/doc/role-doc/common.md index f4ebf1c..b35d598 100644 --- a/doc/role-doc/common.md +++ b/doc/role-doc/common.md @@ -9,6 +9,10 @@ system: install essential packages and repositories, setting up a firewall using It is strongly recommended to include this role in first position in any Caislean installation. +This role only consists in including subroles that fulfill these tasks: +`base-packages`, `base-config`, `base-hardening`, `ufw` and `antivirus`. You may +want to rather select them individually for a finer tuning of your system. + ## Prerequired roles None. diff --git a/doc/role-doc/ldap-account-manager.md b/doc/role-doc/ldap-account-manager.md index 7218310..6d5e631 100644 --- a/doc/role-doc/ldap-account-manager.md +++ b/doc/role-doc/ldap-account-manager.md @@ -9,7 +9,8 @@ This role is currently broken. ## Prerequired roles -- `common` +- `base-packages` +- `base-config` - `openldap` - `nginx` - `php-fpm` diff --git a/doc/role-doc/letsencrypt.md b/doc/role-doc/letsencrypt.md index 4540dff..f17af93 100644 --- a/doc/role-doc/letsencrypt.md +++ b/doc/role-doc/letsencrypt.md @@ -3,10 +3,10 @@ ## Description This role configures nginx to use certificates issued by [Let's -Encrypt](https://letsencrypt.org/) instead -of self-signed certificates installed by Caislean's TLS role. The advantage is -that Let's Encrypt certificates are trusted by most browsers so visitors to -your website won't see an untrusted certificate warning. +Encrypt](https://letsencrypt.org/) instead of self-signed certificates +installed by Caislean's TLS role. The advantage is that Let's Encrypt +certificates are trusted by most browsers so visitors to your website won't see +an untrusted certificate warning. ## Notes @@ -24,28 +24,41 @@ This role adds the "testing" repository to the remote machine. The role also specifies apt preferences to make sure software is installed from the stable repositories unless explicitly specified otherwise. -This role won't work unless `website_domain_name` resolves to the IP address of -the remote machine. This is because Let's Encrypt verifies that you control the -domain for which you're requesting a certificate by placing a file in your -webserver's webroot and then checking that it can access that file from the domain -in question. +This role won't work unless every domain listed in `websites` resolves to the +IP address of the remote machine. This is because Let's Encrypt verifies that +you control the domains for which you're requesting certificates by placing +files in each virtual host's webroot and then checking that it can access those +files from the domains in question. ## Prerequired roles -- `common` +- `base-packages` +- `base-config` - `tls` - `nginx` -# Manual steps - # Configuration parameters (ansible variables) ## Mandatory parameters -### `website_domain_name` +### `websites` + +A list of domain names for which Caislean should generate certificates. This is +the same list used by the `nginx` role when creating virtual hosts to serve. + +Default: + +websites: + - "{{ server_name }}.{{ domain_name }}" + +Add or change lines to create new nginx virtual hosts and generate letsencrypt +certificates for them. + +Example: -The domain name of the website you are serving from this machine (e.g. -example.com) +websites: + - "{{ domain_name }}" + - "www.example.com" ### `webmaster_email` diff --git a/doc/role-doc/mysql.md b/doc/role-doc/mysql.md index 9a87ff0..aadd1c7 100644 --- a/doc/role-doc/mysql.md +++ b/doc/role-doc/mysql.md @@ -8,7 +8,8 @@ need a MySQL server to run. ## Prerequired roles -- `common` +- `base-packages` +- `base-config` # Manual steps diff --git a/doc/role-doc/nginx.md b/doc/role-doc/nginx.md index 3f59d9a..8f449e2 100644 --- a/doc/role-doc/nginx.md +++ b/doc/role-doc/nginx.md @@ -13,7 +13,8 @@ includes: ## Prerequired roles -- `common` +- `base-packages` +- `base-config` - `tls` # Manual steps @@ -30,6 +31,55 @@ The machine name of the administered server, e.g. "mycomputer". The domain name, e.g. "mydomain.org". +### `websites` + +Default: `[ name:` _server name_`.`_domain name_ `]` + +A set of host names that your web server will serve content for. There can be +any number of names, but the default entry is mandatory, as otherwise this or +other roles will fail. The parameter `name` has to appear explicitly. + +The role creates a folder `/var/www//` for every entry, and static content +in each of these directories will be served by nginx when your web server is +accessed with the corresponding host name. A folder `/etc/nginx/include//` +is also created for each entry, in which additional nginx configuration files +can be placed (for example to enable TLS for the given hostname or to set up a +PHP-enabled application in a specific subfolder). + +A number of reverse proxies can be optionally configured for each host name by +specifying the parameter `reverse_proxy`, inside which the mandatory parameter +`target` must be set to the remote URL to proxy to and the optional parameter +`location` must be set to the local path where the proxying will be done (it is +set to `/` if left empty). Additional `nginx` options for this reverse proxy can +be specified under the parameter `options`, using a series of `option_name` and +`option_value` parameters. + +The headers `X-Real-IP` and `X-Forwarded-For` are automatically added and do not +have to be add as options. + +Example: + + websites: + - name: "{{server_name}}.{{domain_name}}" + - name: www.otherdomain.com + - name: frontend.thirddomain.eu + reverse_proxy: + - target: 'http://backend.thirddomain.eu' + - target: 'http://specialbackend.thirddomain.eu' + location: '/specialbackend' + options: + - option_name: proxy_redirect + option_value: 'off' + - option_name: proxy_add_header + option_value: 'X-Forwarded-Proto $scheme' + ## Optional parameters -None. +### `tls_additional_domains` + +A set of additional domains for which nginx will also serve content in HTTPS. +These domains must be defined in the `websites` variable, or the role will fail +to execute. The default domain name must not be specified in this variable, as +TLS is enabled for it by default. + +See the TLS role documentation for more information on this parameter. diff --git a/doc/role-doc/openldap.md b/doc/role-doc/openldap.md index b5d4b1c..ed05af2 100644 --- a/doc/role-doc/openldap.md +++ b/doc/role-doc/openldap.md @@ -18,7 +18,8 @@ deprecated practice and should thus be changed in the future. ## Prerequired roles -- `common` +- `base-packages` +- `base-config` # Manual steps: managing users in the LDAP database @@ -82,10 +83,51 @@ To delete a user's account, simply remove their LDAP corresponding entry: ## Mandatory parameters +### `ldap_bind_addresses` + +Default: `[ 127.0.0.1 ]` + +Local IP addresses for the OpenLDAP server to listen on. By default we only +listen on the local interface (127.0.0.1). You can specify any number of IP +addresses assigned to your server. IPv6 addresses must go between square +brackets. You should be able to use special addresses `0.0.0.0` (IPv4) and `::` +(IPv6) to listen on all network interfaces. + +Be careful if you bind public IP addresses: the LDAP protocol is not encrypted +and LDAP over TLS is not (yet) supported by Caislean. Binding non-loopback +addresses may still be useful on a local area network or on a virtual network +between virtual machines. + +Example: + + ldap_bind_addresses: + - 127.0.0.1 + - '[::1]' + ### `ldap_admin_pass` The LDAP administrator password. +### `ldap_managed_domains` + +Default: `[ domain: domain_name ]` + +List of domain names managed in the LDAP directory. The role will create one +separate LDAP database for each of the domains. Optionally, use the parameter +`admin_pass` to set an administrator password specific of a given domain +(otherwise the password set in `ldap_admin_pass` will be used). + +For any given domain `example.com`, the administrator account to which to +identify is `cn=admin,dc=example,dc=com`. + +Example: + + ldap_managed_domains: + - domain: "{{ domain_name }}" + - domain: additionaldomain.com + - domain: some_other_domain.org + admin_pass: specificadminpass + ### `domain_name` The domain name, e.g. "mydomain.org". diff --git a/doc/role-doc/openvpn.md b/doc/role-doc/openvpn.md index 90c6dcc..cfe5f9e 100644 --- a/doc/role-doc/openvpn.md +++ b/doc/role-doc/openvpn.md @@ -10,7 +10,8 @@ through the use of TLS certificates, or both. ## Prerequired roles -- `common` +- `base-packages` +- `base-config` - `tls` - `openldap` (only if using the LDAP authentication) diff --git a/doc/role-doc/owncloud.md b/doc/role-doc/owncloud.md index 9ba8a36..dcccc47 100644 --- a/doc/role-doc/owncloud.md +++ b/doc/role-doc/owncloud.md @@ -2,23 +2,26 @@ ## Description -This role installs the Owncloud package from the OpenSUSE repositories and -installs the proper Nginx configuration file containing a `location` block that -configures the `/owncloud/` HTTP directory properly, also with forwarding +This role installs the Owncloud package from the Owncloud official repositories +and installs the proper Nginx configuration file containing a `location` block +that configures the `/owncloud/` HTTP directory properly, also with forwarding PHP requests to `php-fpm`. It also installs a dedicated `php-fpm` service, -running as a distinct user. +running as a distinct `owncloud` user. Note that after installation of the package through the Ansible playbook, -finalizing the installation is done via web access to the Owncloud instance. +finalizing the installation is done via web access to the Owncloud instance. You +should do it right away as it lets you set the Owncloud administrator account +and password. ## Prerequired roles -- `common` +- `base-packages` +- `base-config` - `tls` - `nginx` - `php-fpm` - `mysql` -- `openldap` (if using LDAP authentication) +- `openldap` (if using local LDAP authentication) # Manual steps: finalizing Owncloud setup @@ -35,13 +38,15 @@ required settings as follows: - specify a new administrator account - leave the default storage folder to `/var/www/owncloud/data` - select MySQL as database backend -- use `owncloud` for both database name and username, and fill in the password - you already set with the variable `owncloud_mysql_password` +- choose a database name freely, use `root` for the username, and fill in the + password you already set with the variable `mysql_root_password`: Owncloud + will use this account only once to create its own tables and less privileged + MySQL account, and then will use it in the future. ## Connection to the LDAP directory Log into Owncloud using your new administrator account. Use the button in the -upper-left corner and select the _Apps_ screen. Go the the _Not enabled_ tab and +upper-left corner and select the _Apps_ screen. Go to the _Not enabled_ tab and enable the _LDAP user and group backend_ application with the _Enable_ button. Click on your login in the upper-right corner and select _Admin_ and scroll down @@ -56,12 +61,15 @@ to the LDAP configuration. Configure the application as follows: - in the Group filter tab, enter `objectClass=mailAccount` as raw filter - select the Advanced tab, scroll down and click on the Save button +You may also connect Owncloud to another LDAP server, in which case you will +have to adapt the settings according to its configuration. + ## Enabling calendar and contacts management - log into your Owncloud instance with the administrator account - click on the top-left menu and select "Apps" - select the PIM category -- enable both Calendar 8.0 and Contacts 8.0 applications +- enable both Calendar and Contacts applications - log out: when you log back in, or whenever any user logs in, management of calendar and contacts becomes possible. @@ -73,11 +81,6 @@ to the LDAP configuration. Configure the application as follows: `root` user password for MySQL server. -### `owncloud_mysql_password` - -The MySQL password for user `owncloud` (this user is automatically created by -the role). - ## Optional parameters None. diff --git a/doc/role-doc/php-fpm.md b/doc/role-doc/php-fpm.md index 76a45c1..7da22d6 100644 --- a/doc/role-doc/php-fpm.md +++ b/doc/role-doc/php-fpm.md @@ -9,7 +9,8 @@ different users depending on the PHP process that is managing them. ## Prerequired roles -- `common` +- `base-packages` +- `base-config` - `tls` - `nginx` diff --git a/doc/role-doc/prosody.md b/doc/role-doc/prosody.md index 90fa3df..6c7e373 100644 --- a/doc/role-doc/prosody.md +++ b/doc/role-doc/prosody.md @@ -9,7 +9,8 @@ daemon), using users' email addresses as login. ## Prerequired roles -- `common` +- `base-packages` +- `base-config` - `tls` - `openldap` diff --git a/doc/role-doc/roundcube.md b/doc/role-doc/roundcube.md index dad9574..2b9eb8e 100644 --- a/doc/role-doc/roundcube.md +++ b/doc/role-doc/roundcube.md @@ -12,7 +12,8 @@ Users are identified against the locally running Dovecot server, through IMAP. ## Prerequired roles -- `common` +- `base-packages` +- `base-config` - `tls` - `nginx` - `php-fpm` diff --git a/doc/role-doc/samba.md b/doc/role-doc/samba.md index d0252c8..817196d 100644 --- a/doc/role-doc/samba.md +++ b/doc/role-doc/samba.md @@ -24,7 +24,8 @@ interoprable with other roles excepted OpenVPN. ## Prerequired roles -- `common` +- `base-packages` +- `base-config` - `openldap` # Manual steps diff --git a/doc/role-doc/suricata.md b/doc/role-doc/suricata.md index ad7d506..b17228a 100644 --- a/doc/role-doc/suricata.md +++ b/doc/role-doc/suricata.md @@ -17,7 +17,8 @@ resources will quickly be saturated by the live analysis of packets. ## Prerequired roles -- `common` +- `base-packages` +- `base-config` # Manual steps diff --git a/doc/role-doc/tls.md b/doc/role-doc/tls.md index 387a7e3..7c4bfd6 100644 --- a/doc/role-doc/tls.md +++ b/doc/role-doc/tls.md @@ -24,7 +24,7 @@ manually. ## Prerequired roles -- `common` +- `base-packages` # Manual steps @@ -84,4 +84,20 @@ Local directory where TLS files are stored (in the examples above, we used ## Optional parameters -None. +### `tls_additional_domains` + +A set of domains for which you also want to upload to your server a certificate, +a certification authority certificate chain and a private key. This is at the +moment primarily useful for a web server serving several host names, and is used +in conjunction with the `nginx` role. + +The TLS files for the specified domains must be located in your TLS directory +(see parameter `tls_directory` above) on your local machine and must exactly be +called `.ca.crt.pem` (for the certification authority certificate +chain), `.cert.crt.pem` (your certificate) and `.key.pem` +(your private key). + +Example: + + tls_additional_domains: + - www.otherdomain.com diff --git a/doc/role-doc/ufw.md b/doc/role-doc/ufw.md new file mode 100644 index 0000000..60d8c21 --- /dev/null +++ b/doc/role-doc/ufw.md @@ -0,0 +1,57 @@ +# Summary + +## Description + +This roles install `ufw` and sets it up to forbid all inbound and outbound +traffic excepted: + +- SSH port and port 25 inbound; +- ports 25, 53, 80 and 443 outbound. + +It is up to other roles that need to open more ports for additional services +(such as a web server or a XMPP server) to do so. + +This role is automatically included in the `common` role. + +## Prerequired roles + +- `base-packages` +- `base-config` + +# Manual steps + +# Configuration parameters (ansible variables) + +## Mandatory parameters + +### `ufw_allow_ports_out` + +Default: `[25, 53, 80, 443]` + +List of TCP and UDP ports for which outbound traffic will be allowed by the +firewall. + +### `ufw_allow_ports_in` + +Default: `[`_ssh port_`, 25]` + +List of TCP and UDP ports for which inbound traffic will be allowed by the +firewall. + +### `ufw_allow_ips_out` + +Default: `[]` + +List of IP addresses that should be allowed for outbound connections, on any +port or protocol. + +### `ufw_allow_ips_in` + +Default: `[]` + +List of IP addresses that should be allowed for inbound connections, on any port +or protocol. + +## Optional parameters + +None. diff --git a/doc/role-doc/usermin.md b/doc/role-doc/usermin.md index 7cf66a5..0bf593a 100644 --- a/doc/role-doc/usermin.md +++ b/doc/role-doc/usermin.md @@ -22,7 +22,8 @@ available to standard users, as it runs with `root` priviledges. ## Prerequired roles -- `common` +- `base-packages` +- `base-config` - `tls` - `nginx` diff --git a/doc/role-doc/virtualmail.md b/doc/role-doc/virtualmail.md index 213d9d4..6a8f451 100644 --- a/doc/role-doc/virtualmail.md +++ b/doc/role-doc/virtualmail.md @@ -14,7 +14,8 @@ LDAP structure. ## Prerequired roles -- `common` +- `base-packages` +- `base-config` - `tls` - `openldap` diff --git a/doc/role-doc/wordpress.md b/doc/role-doc/wordpress.md index 495f794..29f1ab9 100644 --- a/doc/role-doc/wordpress.md +++ b/doc/role-doc/wordpress.md @@ -14,7 +14,8 @@ able to authentify users against the LDAP database with the proper plugin. ## Prerequired roles -- `common` +- `base-packages` +- `base-config` - `tls` - `nginx` - `php-fpm` diff --git a/doc/roles_list.md b/doc/roles_list.md index ebe3275..c4c5d4d 100644 --- a/doc/roles_list.md +++ b/doc/roles_list.md @@ -3,10 +3,14 @@ For each role there is a specific documentation file in the `role-doc` directory, with detailed explanations, required manual steps and all available variables. -Several roles depend on the presence of other ones within the same playbook in -order to run successfully: refer to their documentation to make sure you include -all necessary roles. +Most roles depend on the presence of other roles within the same playbook in +order to run successfully. These dependencies will be automatically included in +the playbook and executed prior to the role you specified, so you don't need to +add them manually. +However, these roles may require their own variables to be set, so please refer +to the roles documentation to make sure you set all required variables +appropriately. ## Basic roles diff --git a/host_vars/caislean.domain.com b/host_vars/caislean.domain.com index bb378ef..d0864e1 100644 --- a/host_vars/caislean.domain.com +++ b/host_vars/caislean.domain.com @@ -1,10 +1,10 @@ --- - -admin_email: user@domain.com -domain_name: domain.com -webmaster_email: webmaster@website.com -website_domain_name: website.com server_name: caislean +domain_name: domain.com +admin_email: "user@{{ domain_name }}" +webmaster_email: "webmaster@{{ domain_name }}" +websites: + - "{{ server_name }}.{{ domain_name }}" tls_directory: /home/user/caislean_admin/tls openvpn_auth_mech: tls auth_use_samba: false diff --git a/roles/common/files/etc/chkrootkit.conf b/roles/antivirus/files/etc/chkrootkit.conf similarity index 100% rename from roles/common/files/etc/chkrootkit.conf rename to roles/antivirus/files/etc/chkrootkit.conf diff --git a/roles/common/files/etc/cron.daily/chkrootkit b/roles/antivirus/files/etc/cron.daily/chkrootkit similarity index 100% rename from roles/common/files/etc/cron.daily/chkrootkit rename to roles/antivirus/files/etc/cron.daily/chkrootkit diff --git a/roles/antivirus/meta/main.yml b/roles/antivirus/meta/main.yml new file mode 100644 index 0000000..cde5f9d --- /dev/null +++ b/roles/antivirus/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - role: base-packages + - role: base-config diff --git a/roles/antivirus/tasks/main.yml b/roles/antivirus/tasks/main.yml new file mode 100644 index 0000000..27b1eef --- /dev/null +++ b/roles/antivirus/tasks/main.yml @@ -0,0 +1,14 @@ +- name: Install chkrootkit + apt: pkg={{item}} state=installed + with_items: + - chkrootkit + - unhide + tags: base + +- name: Install chkrootkit configuration + copy: src=etc/chkrootkit.conf dest=/etc/chkrootkit.conf group=root owner=root mode=0600 + tags: base + +- name: Install chkrootkit daily cron job script + copy: src=etc/cron.daily/chkrootkit dest=/etc/cron.daily/chkrootkit group=root owner=root mode=0755 + tags: base diff --git a/roles/backupninja/meta/main.yml b/roles/backupninja/meta/main.yml new file mode 100644 index 0000000..cde5f9d --- /dev/null +++ b/roles/backupninja/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - role: base-packages + - role: base-config diff --git a/roles/backupninja/tasks/main.yml b/roles/backupninja/tasks/main.yml index a63d409..5e85094 100644 --- a/roles/backupninja/tasks/main.yml +++ b/roles/backupninja/tasks/main.yml @@ -90,10 +90,3 @@ tags: - base - backup - -- name: Open UFW outbound SSH connections to backup host - ufw: rule=allow port={{ backup_remote_ssh_port }} direction=out to_ip={{ backup_remote_ip }} - tags: - - base - - backup - - firewall diff --git a/roles/base-config/defaults/main.yml b/roles/base-config/defaults/main.yml new file mode 100644 index 0000000..c8d47bc --- /dev/null +++ b/roles/base-config/defaults/main.yml @@ -0,0 +1,3 @@ +--- + +base_force_postfix_master_cf: False diff --git a/roles/base-config/files/etc/postfix/master.cf b/roles/base-config/files/etc/postfix/master.cf new file mode 100644 index 0000000..975a934 --- /dev/null +++ b/roles/base-config/files/etc/postfix/master.cf @@ -0,0 +1,124 @@ +# +# Postfix master process configuration file. For details on the format +# of the file, see the master(5) manual page (command: "man 5 master" or +# on-line: http://www.postfix.org/master.5.html). +# +# Do not forget to execute "postfix reload" after editing this file. +# +# ========================================================================== +# service type private unpriv chroot wakeup maxproc command + args +# (yes) (yes) (yes) (never) (100) +# ========================================================================== +#smtp inet n - - - - smtpd +#smtp inet n - - - 1 postscreen +#smtpd pass - - - - - smtpd +#dnsblog unix - - - - 0 dnsblog +#tlsproxy unix - - - - 0 tlsproxy +#submission inet n - - - - smtpd +# -o syslog_name=postfix/submission +# -o smtpd_tls_security_level=encrypt +# -o smtpd_sasl_auth_enable=yes +# -o smtpd_reject_unlisted_recipient=no +# -o smtpd_client_restrictions=$mua_client_restrictions +# -o smtpd_helo_restrictions=$mua_helo_restrictions +# -o smtpd_sender_restrictions=$mua_sender_restrictions +# -o smtpd_recipient_restrictions= +# -o smtpd_relay_restrictions=permit_sasl_authenticated,reject +# -o milter_macro_daemon_name=ORIGINATING +#smtps inet n - - - - smtpd +# -o syslog_name=postfix/smtps +# -o smtpd_tls_wrappermode=yes +# -o smtpd_sasl_auth_enable=yes +# -o smtpd_reject_unlisted_recipient=no +# -o smtpd_client_restrictions=$mua_client_restrictions +# -o smtpd_helo_restrictions=$mua_helo_restrictions +# -o smtpd_sender_restrictions=$mua_sender_restrictions +# -o smtpd_recipient_restrictions= +# -o smtpd_relay_restrictions=permit_sasl_authenticated,reject +# -o milter_macro_daemon_name=ORIGINATING +#628 inet n - - - - qmqpd +pickup unix n - - 60 1 pickup +cleanup unix n - - - 0 cleanup +qmgr unix n - n 300 1 qmgr +#qmgr unix n - n 300 1 oqmgr +tlsmgr unix - - - 1000? 1 tlsmgr +rewrite unix - - - - - trivial-rewrite +bounce unix - - - - 0 bounce +defer unix - - - - 0 bounce +trace unix - - - - 0 bounce +verify unix - - - - 1 verify +flush unix n - - 1000? 0 flush +proxymap unix - - n - - proxymap +proxywrite unix - - n - 1 proxymap +smtp unix - - - - - smtp +relay unix - - - - - smtp +# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5 +showq unix n - - - - showq +error unix - - - - - error +retry unix - - - - - error +discard unix - - - - - discard +local unix - n n - - local +virtual unix - n n - - virtual +lmtp unix - - - - - lmtp +anvil unix - - - - 1 anvil +scache unix - - - - 1 scache +# +# ==================================================================== +# Interfaces to non-Postfix software. Be sure to examine the manual +# pages of the non-Postfix software to find out what options it wants. +# +# Many of the following services use the Postfix pipe(8) delivery +# agent. See the pipe(8) man page for information about ${recipient} +# and other message envelope options. +# ==================================================================== +# +# maildrop. See the Postfix MAILDROP_README file for details. +# Also specify in main.cf: maildrop_destination_recipient_limit=1 +# +maildrop unix - n n - - pipe + flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient} +# +# ==================================================================== +# +# Recent Cyrus versions can use the existing "lmtp" master.cf entry. +# +# Specify in cyrus.conf: +# lmtp cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4 +# +# Specify in main.cf one or more of the following: +# mailbox_transport = lmtp:inet:localhost +# virtual_transport = lmtp:inet:localhost +# +# ==================================================================== +# +# Cyrus 2.1.5 (Amos Gouaux) +# Also specify in main.cf: cyrus_destination_recipient_limit=1 +# +#cyrus unix - n n - - pipe +# user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user} +# +# ==================================================================== +# Old example of delivery via Cyrus. +# +#old-cyrus unix - n n - - pipe +# flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user} +# +# ==================================================================== +# +# See the Postfix UUCP_README file for configuration details. +# +uucp unix - n n - - pipe + flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient) +# +# Other external delivery methods. +# +ifmail unix - n n - - pipe + flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient) +bsmtp unix - n n - - pipe + flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient +scalemail-backend unix - n n - 2 pipe + flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension} +mailman unix - n n - - pipe + flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py + ${nexthop} ${user} + diff --git a/roles/base-config/handlers/main.yml b/roles/base-config/handlers/main.yml new file mode 100644 index 0000000..6cc02cc --- /dev/null +++ b/roles/base-config/handlers/main.yml @@ -0,0 +1,5 @@ +- name: update mail aliases + command: newaliases + +- name: restart postfix - base + service: name=postfix state=restarted diff --git a/roles/base-config/tasks/main.yml b/roles/base-config/tasks/main.yml new file mode 100644 index 0000000..245427d --- /dev/null +++ b/roles/base-config/tasks/main.yml @@ -0,0 +1,35 @@ +- name: Install apticron and postfix + apt: pkg={{item}} state=installed + with_items: + - bsd-mailx + - postfix + - apticron + tags: base + +- name: Stat remote postfix master.cf + stat: path=/etc/postfix/master.cf + register: remote_master_cf + +- name: Stat local postfix master.cf in virtualmail role + local_action: stat path=roles/virtualmail/files/etc/postfix/master.cf + register: local_master_cf + +- name: Install daemon-less postfix configuration file (unless role virtualmail previously installed this file and not forced) + copy: src=etc/postfix/master.cf dest=/etc/postfix/master.cf group=root owner=root mode=0644 + when: (local_master_cf.stat.checksum != remote_master_cf.stat.checksum) or (base_force_postfix_master_cf == True) + notify: + - restart postfix - base + +- name: Install Apticron configuration + template: src=apticron.conf.j2 dest=/etc/apticron/apticron.conf group=root owner=root mode=0644 + tags: base + +- name: Insert necessary aliases to /etc/aliases + lineinfile: + dest: /etc/aliases + create: yes + regexp: '^root: ' + line: 'root: {{ ansible_ssh_user }}@localhost, {{ admin_email }}' + tags: base + notify: + - update mail aliases diff --git a/roles/common/templates/apticron.conf.j2 b/roles/base-config/templates/apticron.conf.j2 similarity index 100% rename from roles/common/templates/apticron.conf.j2 rename to roles/base-config/templates/apticron.conf.j2 diff --git a/roles/common/files/etc/login.defs b/roles/base-hardening/files/etc/login.defs similarity index 100% rename from roles/common/files/etc/login.defs rename to roles/base-hardening/files/etc/login.defs diff --git a/roles/common/files/etc/pam.d/jessie-common-session b/roles/base-hardening/files/etc/pam.d/jessie-common-session similarity index 100% rename from roles/common/files/etc/pam.d/jessie-common-session rename to roles/base-hardening/files/etc/pam.d/jessie-common-session diff --git a/roles/common/files/etc/pam.d/wheezy-common-session b/roles/base-hardening/files/etc/pam.d/wheezy-common-session similarity index 100% rename from roles/common/files/etc/pam.d/wheezy-common-session rename to roles/base-hardening/files/etc/pam.d/wheezy-common-session diff --git a/roles/common/handlers/main.yml b/roles/base-hardening/handlers/main.yml similarity index 50% rename from roles/common/handlers/main.yml rename to roles/base-hardening/handlers/main.yml index c555d1d..276ebfe 100644 --- a/roles/common/handlers/main.yml +++ b/roles/base-hardening/handlers/main.yml @@ -1,7 +1,2 @@ ---- - - name: restart ssh service: name=ssh state=restarted - -- name: update mail aliases - command: newaliases diff --git a/roles/common/tasks/sysctl.yml b/roles/base-hardening/tasks/main.yml similarity index 52% rename from roles/common/tasks/sysctl.yml rename to roles/base-hardening/tasks/main.yml index 9104f33..6c2f826 100644 --- a/roles/common/tasks/sysctl.yml +++ b/roles/base-hardening/tasks/main.yml @@ -1,3 +1,32 @@ +- name: Install login.defs + copy: src=etc/login.defs dest=/etc/login.defs owner=root group=root mode=0644 + tags: base + +- name: Install PAM common-session file with umask module enabled + copy: + args: + src: etc/pam.d/{{ ansible_distribution_release }}-common-session + dest: /etc/pam.d/common-session + owner: root + group: root + mode: 0644 + tags: base + +- name: Install libpam-umask + apt: pkg=libpam-umask state=installed + tags: base + +- name: Install SSHd configuration + template: + args: + src: "{{ ansible_distribution_release }}-sshd_config.j2" + dest: /etc/ssh/sshd_config + group: root + owner: root + notify: + - restart ssh + tags: base + - sysctl: name={{ item.name }} value={{ item.value }} state=present with_items: - { name: 'fs.suid.dumpable', value: '0' } diff --git a/roles/common/templates/jessie-sshd_config.j2 b/roles/base-hardening/templates/jessie-sshd_config.j2 similarity index 74% rename from roles/common/templates/jessie-sshd_config.j2 rename to roles/base-hardening/templates/jessie-sshd_config.j2 index b712b3e..b5d871d 100644 --- a/roles/common/templates/jessie-sshd_config.j2 +++ b/roles/base-hardening/templates/jessie-sshd_config.j2 @@ -1,4 +1,4 @@ -Port 22 +Port {{ ansible_ssh_port if ansible_ssh_port is defined else 22 }} Protocol 2 HostKey /etc/ssh/ssh_host_rsa_key HostKey /etc/ssh/ssh_host_dsa_key @@ -15,8 +15,10 @@ LogLevel INFO LoginGraceTime 120 +{% if ansible_ssh_user != "root" %} PermitRootLogin no -AllowUsers {{ ansible_ssh_user }} +{% endif %} +AllowUsers {{ ansible_ssh_user }} {{ (ssh_additional_users | join(' ')) if ssh_additional_users is defined else '' }} StrictModes yes diff --git a/roles/common/templates/wheezy-sshd_config.j2 b/roles/base-hardening/templates/wheezy-sshd_config.j2 similarity index 73% rename from roles/common/templates/wheezy-sshd_config.j2 rename to roles/base-hardening/templates/wheezy-sshd_config.j2 index ec0dd32..5f8f316 100644 --- a/roles/common/templates/wheezy-sshd_config.j2 +++ b/roles/base-hardening/templates/wheezy-sshd_config.j2 @@ -1,4 +1,4 @@ -Port 22 +Port {{ ansible_ssh_port if ansible_ssh_port is defined else 22 }} Protocol 2 HostKey /etc/ssh/ssh_host_rsa_key HostKey /etc/ssh/ssh_host_dsa_key @@ -13,8 +13,10 @@ LogLevel INFO LoginGraceTime 120 +{% if ansible_ssh_user != "root" %} PermitRootLogin no -AllowUsers {{ ansible_ssh_user }} +{% endif %} +AllowUsers {{ ansible_ssh_user }} {{ (ssh_additional_users | join(' ')) if ssh_additional_users is defined else '' }} StrictModes yes diff --git a/roles/common/tasks/unwanted-pkgs.yml b/roles/base-packages/tasks/main.yml similarity index 52% rename from roles/common/tasks/unwanted-pkgs.yml rename to roles/base-packages/tasks/main.yml index 42a50d7..0424181 100644 --- a/roles/common/tasks/unwanted-pkgs.yml +++ b/roles/base-packages/tasks/main.yml @@ -1,3 +1,21 @@ +- name: Add backports Debian repository + apt_repository: + args: + repo: 'deb http://http.debian.net/debian {{ ansible_distribution_release }}-backports main' + state: present + update_cache: yes + tags: base + +- name: Install base packages + apt: pkg={{item}} state=installed + with_items: + - openssh-server + - bash + - wget + - debconf-utils + - aptitude + tags: base + - name: Make sure unwanted packages are absent - common apt: pkg={{item}} state=absent purge=yes with_items: diff --git a/roles/common/meta/main.yml b/roles/common/meta/main.yml new file mode 100644 index 0000000..e996427 --- /dev/null +++ b/roles/common/meta/main.yml @@ -0,0 +1,6 @@ +dependencies: + - role: base-packages + - role: base-config + - role: base-hardening + - role: ufw + - role: antivirus diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml deleted file mode 100644 index ecee439..0000000 --- a/roles/common/tasks/main.yml +++ /dev/null @@ -1,111 +0,0 @@ -- name: Install login.defs - copy: src=etc/login.defs dest=/etc/login.defs owner=root group=root mode=0644 - tags: base - -- name: Install PAM common-session file with umask module enabled - copy: - args: - src: etc/pam.d/{{ ansible_distribution_release }}-common-session - dest: /etc/pam.d/common-session - owner: root - group: root - mode: 0644 - tags: base - -- name: Add backports Debian repository - apt_repository: - args: - repo: 'deb http://http.debian.net/debian {{ ansible_distribution_release }}-backports main' - state: present - update_cache: yes - tags: base - -- name: Install General System Security packages - apt: pkg={{item}} state=installed - with_items: - - apticron - - cron - - openssh-server - - postfix - - ufw - - bash - - chkrootkit - - wget - - unhide - - libpam-umask - - ethtool - - debconf-utils - tags: - - base - - firewall - -- name: Install SSHd configuration - template: - args: - src: "{{ ansible_distribution_release }}-sshd_config.j2" - dest: /etc/ssh/sshd_config - group: root - owner: root - notify: - - restart ssh - tags: base - -- name: Install UFW files - copy: src={{item}} dest=/{{item}} group=root owner=root - register: ufw_config_files - with_items: - - etc/default/ufw - - etc/ufw/ufw.conf - - etc/ufw/before.rules - tags: firewall - -- name: Disable UFW - ufw: state=disabled - when: ( ufw_config_files|changed ) - tags: firewall - -- name: Reload UFW - ufw: state=reloaded - when: ( ufw_config_files|changed ) - tags: firewall - -- name: Open ports in UFW (inbound) - ufw: rule=allow port={{item}} direction=in - with_items: - - 22 - - 25 - tags: firewall - -- name: Open ports in UFW (outbound) - ufw: rule=allow port={{item}} direction=out - with_items: - - 53 - - 25 - - 80 - - 443 - tags: firewall - -- name: Enable UFW - ufw: state=enabled logging=on policy=deny - tags: firewall - -- name: Install Apticron configuration - template: src=apticron.conf.j2 dest=/etc/apticron/apticron.conf group=root owner=root mode=0644 - tags: base - -- name: Install chkrootkit configuration - copy: src=etc/chkrootkit.conf dest=/etc/chkrootkit.conf group=root owner=root mode=0600 - tags: base - -- name: Install chkrootkit daily cron job script - copy: src=etc/cron.daily/chkrootkit dest=/etc/cron.daily/chkrootkit group=root owner=root mode=0755 - tags: base - -- name: Install /etc/aliases - template: src=aliases.j2 dest=/etc/aliases group=root owner=root mode=0644 - notify: - - update mail aliases - tags: base - -- include: unwanted-pkgs.yml -- include: sysctl.yml diff --git a/roles/common/templates/aliases.j2 b/roles/common/templates/aliases.j2 deleted file mode 100644 index c29acac..0000000 --- a/roles/common/templates/aliases.j2 +++ /dev/null @@ -1,13 +0,0 @@ -mailer-daemon: postmaster -postmaster: root -nobody: root -hostmaster: root -usenet: root -news: root -webmaster: root -www: root -ftp: root -abuse: root -noc: root -security: root -root: {{ ansible_ssh_user }}@localhost, {{ admin_email }} diff --git a/roles/ldap-account-manager/meta/main.yml b/roles/ldap-account-manager/meta/main.yml new file mode 100644 index 0000000..80a098b --- /dev/null +++ b/roles/ldap-account-manager/meta/main.yml @@ -0,0 +1,6 @@ +dependencies: + - role: base-packages + - role: base-config + - role: openldap + - role: nginx + - role: php-fpm diff --git a/roles/letsencrypt/meta/main.yml b/roles/letsencrypt/meta/main.yml new file mode 100644 index 0000000..329d62b --- /dev/null +++ b/roles/letsencrypt/meta/main.yml @@ -0,0 +1,6 @@ +dependencies: + - role: base-packages + - role: base-config + - role: tls + - role: nginx + diff --git a/roles/letsencrypt/tasks/letsencrypt.yml b/roles/letsencrypt/tasks/letsencrypt.yml index 8ea5123..2ffcd6a 100644 --- a/roles/letsencrypt/tasks/letsencrypt.yml +++ b/roles/letsencrypt/tasks/letsencrypt.yml @@ -1,43 +1,44 @@ - name: Set apt preferences - copy: src=etc/apt/preferences.d/{{ item }} dest=/etc/apt/preferences.d/{{ item }} group=root owner=root + copy: + src: "etc/apt/preferences.d/{{ item }}" + dest: "/etc/apt/preferences.d/{{ item }}" + group: root + owner: root with_items: - stable.pref - security.pref - testing.pref tags: letsencrypt -- name: Add testing Debian repository +- name: Add Debian testing repository apt_repository: - args: - repo: 'deb http://http.debian.net/debian stretch main' + repo: "deb http://http.debian.net/debian stretch main" state: present update_cache: yes tags: letsencrypt - name: Install letsencrypt client - apt: pkg=letsencrypt state=installed default_release=testing + apt: + pkg: letsencrypt + state: installed + default_release: testing tags: letsencrypt -- name: Generate certificate for domain - command: "letsencrypt certonly --webroot --webroot-path /var/www/{{ server_name }}.{{ domain_name }} --email {{ webmaster_email }} -d {{ website_domain_name }} --agree-tos --keep" - tags: letsencrypt - -- name: Remove previous nginx certificate configuration - lineinfile: "dest=/etc/nginx/nginx.conf state=absent line=' ssl_certificate /etc/ssl/certs/{{ server_name }}.{{ domain_name }}.pem;'" - tags: letsencrypt - -- name: Remove previous nginx private key configuration - lineinfile: "dest=/etc/nginx/nginx.conf state=absent line=' ssl_certificate_key /etc/ssl/private/{{ server_name }}.{{ domain_name}}.key;'" - tags: letsencrypt - -- name: Add new nginx certificate configuration - lineinfile: "dest=/etc/nginx/nginx.conf state=present insertbefore='ssl_dhparam' line=' ssl_certificate /etc/letsencrypt/live/{{ website_domain_name }}/fullchain.pem;'" +- name: Generate certificates for websites + command: "letsencrypt certonly --webroot --webroot-path /var/www/{{ item.name }} --email {{ webmaster_email }} -d {{ item.name }} --agree-tos --keep" + with_items: + - "{{ websites }}" tags: letsencrypt - notify: - - restart nginx -- name: Add new nginx private key configuration - lineinfile: "dest=/etc/nginx/nginx.conf state=present insertbefore='ssl_dhparam' line=' ssl_certificate_key /etc/letsencrypt/live/{{ website_domain_name }}/privkey.pem;'" +- name: Configure nginx to use new certificates + template: + src: letsencrypt.j2 + dest: "/etc/nginx/includes/{{ item.name }}/letsencrypt" + owner: root + group: root + mode: 0644 + with_items: + - "{{ websites }}" tags: letsencrypt notify: - restart nginx @@ -46,10 +47,12 @@ # Certificate renewals seem to fall due 10 days before expiry by default. - name: Schedule certificate renewals using cron cron: - args: - cron_file: "ansible_letsencrypt_cert_renewal" - name: "letsencrypt renew certificate" - special_time: "daily" - user: "root" - job: "letsencrypt certonly --webroot --webroot-path /var/www/{{ server_name }}.{{ domain_name }} --email {{ webmaster_email }} -d {{ website_domain_name }} --agree-tos --keep && service nginx reload" + name: letsencrypt renew certificate + job: "letsencrypt certonly --webroot --webroot-path /var/www/{{ item.name }} --email {{ webmaster_email }} -d {{ item.name }} --agree-tos --keep && service nginx reload" + cron_file: "ansible_letsencrypt_{{ item.name }}_cert_renewal" + state: present + special_time: daily + user: root + with_items: + - "{{ websites }}" tags: letsencrypt diff --git a/roles/letsencrypt/templates/letsencrypt.j2 b/roles/letsencrypt/templates/letsencrypt.j2 new file mode 100644 index 0000000..2c8ecb5 --- /dev/null +++ b/roles/letsencrypt/templates/letsencrypt.j2 @@ -0,0 +1,3 @@ + add_header Strict-Transport-Security max-age=63072000; + ssl_certificate /etc/letsencrypt/live/{{ item }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ item }}/privkey.pem; diff --git a/roles/mysql/meta/main.yml b/roles/mysql/meta/main.yml new file mode 100644 index 0000000..cde5f9d --- /dev/null +++ b/roles/mysql/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - role: base-packages + - role: base-config diff --git a/roles/mysql/tasks/main.yml b/roles/mysql/tasks/main.yml index 5090352..e34431c 100644 --- a/roles/mysql/tasks/main.yml +++ b/roles/mysql/tasks/main.yml @@ -13,15 +13,32 @@ tags: - mysql -- name: Set MySQL root password +- name: Set MySQL root password (wheezy) debconf: name=mysql-server-5.5 question="{{item}}" vtype=password value="{{mysql_root_password}}" + when: ansible_distribution_release == "wheezy" with_items: - mysql-server/root_password - mysql-server/root_password_again tags: - mysql -- name: Reconfigure MySQL +- name: Set MySQL root password (jessie) + debconf: name=mysql-server-5.6 question="{{item}}" vtype=password value="{{mysql_root_password}}" + when: ansible_distribution_release == "jessie" + with_items: + - mysql-server/root_password + - mysql-server/root_password_again + tags: + - mysql + +- name: Reconfigure MySQL (wheezy) command: dpkg-reconfigure -f noninteractive mysql-server-5.5 + when: ansible_distribution_release == "wheezy" + tags: + - mysql + +- name: Reconfigure MySQL (jessie) + command: dpkg-reconfigure -f noninteractive mysql-server-5.6 + when: ansible_distribution_release == "jessie" tags: - mysql diff --git a/roles/nginx/defaults/main.yml b/roles/nginx/defaults/main.yml new file mode 100644 index 0000000..a79c748 --- /dev/null +++ b/roles/nginx/defaults/main.yml @@ -0,0 +1,4 @@ +--- + +websites: + - name: "{{ server_name }}.{{ domain_name }}" diff --git a/roles/nginx/files/etc/nginx/sites-available/000-default b/roles/nginx/files/etc/nginx/sites-available/000-default index 38ffada..b47128d 100644 --- a/roles/nginx/files/etc/nginx/sites-available/000-default +++ b/roles/nginx/files/etc/nginx/sites-available/000-default @@ -5,11 +5,6 @@ server { expires -1; - if ($request_method !~ ^(GET|HEAD|POST)$ ) - { - return 405; - } - root /var/www/default; } @@ -20,11 +15,6 @@ server { expires -1; - if ($request_method !~ ^(GET|HEAD|POST)$ ) - { - return 405; - } - root /var/www/default; } diff --git a/roles/nginx/meta/main.yml b/roles/nginx/meta/main.yml new file mode 100644 index 0000000..2d7d810 --- /dev/null +++ b/roles/nginx/meta/main.yml @@ -0,0 +1,4 @@ +dependencies: + - role: base-packages + - role: base-config + - role: tls diff --git a/roles/nginx/tasks/main.yml b/roles/nginx/tasks/main.yml index 53f9b5e..ee7e321 100644 --- a/roles/nginx/tasks/main.yml +++ b/roles/nginx/tasks/main.yml @@ -1,81 +1,166 @@ - name: Install web server packages (from backports) - apt: pkg={{item}} state=installed default_release={{ansible_distribution_release}}-backports + apt: + pkg: "{{ item }}" + state: installed + default_release: "{{ansible_distribution_release}}-backports" with_items: - nginx when: ansible_distribution_release == "wheezy" tags: webserver - name: Install web server packages - apt: pkg={{item}} state=installed + apt: + pkg: "{{ item }}" + state: installed with_items: - nginx when: ansible_distribution_release == "jessie" tags: webserver - name: Create www group - group: name=www state=present + group: + name: www + state: present tags: webserver - name: Create www user - user: name=www group=www home=/var/www createhome=no shell=/bin/nologin + user: + name: www + group: www + home: /var/www + createhome: no + shell: /bin/nologin tags: webserver - name: Create nginx log directory - file: path={{item}} state=directory group=www owner=www mode=0750 recurse=no + file: + path: "{{ item }}" + state: directory + group: www + owner: www + mode: 0750 + recurse: no with_items: - /var/log/nginx tags: webserver - name: Create nginx runtime directory - file: path={{item}} state=directory group=www owner=www mode=0750 recurse=no + file: + path: "{{ item }}" + state: directory + group: www + owner: www + mode: 0750 + recurse: no with_items: - /var/run/nginx when: ansible_distribution_release == "wheezy" tags: webserver - name: Create nginx content directories - file: path={{item}} state=directory group=root owner=root mode=0755 recurse=no + file: + path: "/var/www/{{ item.name }}" + state: directory + group: root + owner: root + mode: 0755 + recurse: no with_items: - - /var/www - - /var/www/default - - /var/www/{{ server_name }}.{{ domain_name }} + - { name: 'default' } + - "{{ websites }}" tags: webserver -- name: Create domain-specific directory in /etc/nginx/includes - file: path=/etc/nginx/includes/{{ server_name }}.{{ domain_name }} recurse=no state=directory owner=root group=root mode=0755 +- name: Add placeholder index.html file to document roots + template: + src: index.html.j2 + dest: "/var/www/{{ item.name }}/index.html" + group: www + owner: www + mode: 0644 + force: no + with_items: + - { name: 'default' } + - "{{ websites }}" tags: webserver -- name: Install nginx configuration - template: src={{ ansible_distribution_release }}-nginx.conf.j2 dest=/etc/nginx/nginx.conf +- name: Create domain-specific directories in /etc/nginx/includes + file: + path: "/etc/nginx/includes/{{ item.name }}" + state: directory + owner: root + group: root + mode: 0755 + recurse: no + with_items: + - "{{ websites }}" tags: webserver -- name: Install custom default nginx virtual server - copy: src=etc/nginx/sites-available/000-default dest=/etc/nginx/sites-available/000-default owner=root group=root mode=0644 +- name: Install nginx configuration + template: + src: "{{ ansible_distribution_release }}-nginx.conf.j2" + dest: /etc/nginx/nginx.conf tags: webserver - name: Disable old default nginx virtual server - file: path=/etc/nginx/sites-enabled/default state=absent + file: + path: /etc/nginx/sites-enabled/default + state: absent + tags: webserver + +- name: Install custom default nginx virtual server + copy: + src: etc/nginx/sites-available/000-default + dest: /etc/nginx/sites-available/000-default + owner: root + group: root + mode: 0644 tags: webserver - name: Enable custom default nginx virtual server - file: path=/etc/nginx/sites-enabled/000-default state=link src=/etc/nginx/sites-available/000-default + file: + src: /etc/nginx/sites-available/000-default + path: /etc/nginx/sites-enabled/000-default + state: link tags: webserver -- name: Install nginx virtual server specific to our server's hostname - template: src=nginx-vhost.j2 dest=/etc/nginx/sites-available/{{ server_name }}.{{ domain_name }} group=root owner=root mode=0644 +- name: Install nginx virtual servers + template: + src: nginx-vhost.j2 + dest: "/etc/nginx/sites-available/{{ item.name }}" + group: root + owner: root + mode: 0644 + with_items: + - "{{ websites }}" tags: webserver -- name: Enable nginx virtual server - file: path=/etc/nginx/sites-enabled/{{ server_name }}.{{ domain_name }} state=link src=/etc/nginx/sites-available/{{ server_name }}.{{ domain_name }} +- name: Enable nginx virtual servers + file: + src: "/etc/nginx/sites-available/{{ item.name }}" + path: "/etc/nginx/sites-enabled/{{ item.name }}" + state: link + with_items: + - "{{ websites }}" tags: webserver -- name: Open HTTP and HTTPS ports in UFW (inbound) - ufw: rule=allow port={{item}} direction=in - tags: webserver, firewall +- name: Install domain-specific TLS files + template: src=domain-tls.j2 dest=/etc/nginx/includes/{{item}}/tls-{{item}} owner=root group=root mode=0644 + when: tls_additional_domains is defined + with_items: "{{ tls_additional_domains }}" + tags: + - webserver + +- name: Install reverse proxy files + template: src=reverse-proxy.j2 dest="/etc/nginx/includes/{{item.name}}/reverse-proxy" group=root owner=root mode=0644 with_items: - - 80 - - 443 + - "{{ websites }}" + when: item.reverse_proxy is defined + tags: + - webserver + - reverse-proxy - name: Force an nginx restart to pick up changes - service: name=nginx state=restarted + service: + name: nginx + state: restarted tags: webserver diff --git a/roles/nginx/templates/domain-tls.j2 b/roles/nginx/templates/domain-tls.j2 new file mode 100644 index 0000000..7022f21 --- /dev/null +++ b/roles/nginx/templates/domain-tls.j2 @@ -0,0 +1,3 @@ + add_header Strict-Transport-Security max-age=63072000; + ssl_certificate /etc/ssl/certs/{{item}}.chain.pem; + ssl_certificate_key /etc/ssl/private/{{item}}.key.pem; diff --git a/roles/nginx/templates/index.html.j2 b/roles/nginx/templates/index.html.j2 new file mode 100644 index 0000000..ae6dd3a --- /dev/null +++ b/roles/nginx/templates/index.html.j2 @@ -0,0 +1,21 @@ + + + {{ item }} + + + +
+

Caislean left this space blank intentionally

+

This is a placeholder index page for {{ item.name }}.

+

If you are the site administrator you should replace this page with your site's content.

+
+ + diff --git a/roles/nginx/templates/jessie-nginx.conf.j2 b/roles/nginx/templates/jessie-nginx.conf.j2 index 8ffec1b..87daef4 100644 --- a/roles/nginx/templates/jessie-nginx.conf.j2 +++ b/roles/nginx/templates/jessie-nginx.conf.j2 @@ -30,7 +30,6 @@ http { # SSL Settings ## - add_header Strict-Transport-Security max-age=63072000; add_header X-Frame-Options sameorigin; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; diff --git a/roles/nginx/templates/nginx-vhost.j2 b/roles/nginx/templates/nginx-vhost.j2 index b69973c..edda0ed 100644 --- a/roles/nginx/templates/nginx-vhost.j2 +++ b/roles/nginx/templates/nginx-vhost.j2 @@ -4,16 +4,11 @@ server { listen 443 ssl; listen [::]:443 ssl; - server_name {{ server_name }}.{{ domain_name }}; + server_name {{ item.name }}; expires -1; - if ($request_method !~ ^(GET|HEAD|POST)$ ) - { - return 405; - } + root /var/www/{{ item.name }}; - root /var/www/{{ server_name }}.{{ domain_name }}; - - include /etc/nginx/includes/{{ server_name }}.{{ domain_name }}/*; + include /etc/nginx/includes/{{ item.name }}/*; } diff --git a/roles/nginx/templates/reverse-proxy.j2 b/roles/nginx/templates/reverse-proxy.j2 new file mode 100644 index 0000000..f91f5b2 --- /dev/null +++ b/roles/nginx/templates/reverse-proxy.j2 @@ -0,0 +1,17 @@ +{% for rp_config in item.reverse_proxy %} +location {{ rp_config.location if rp_config.location is defined else '/' }} { + proxy_pass {{ rp_config.target }}; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + + {% if rp_config.options is defined %} + {% for ng_opt in rp_config.options %} + {{ ng_opt.option_name }} {{ ng_opt.option_value }}; + + {% endfor %} + {% endif %} + +} + +{% endfor %} diff --git a/roles/nginx/templates/wheezy-nginx.conf.j2 b/roles/nginx/templates/wheezy-nginx.conf.j2 index e915021..949f696 100644 --- a/roles/nginx/templates/wheezy-nginx.conf.j2 +++ b/roles/nginx/templates/wheezy-nginx.conf.j2 @@ -26,7 +26,6 @@ http { gzip on; gzip_disable "MSIE [1-6]\.(?!.*SV1)"; - add_header Strict-Transport-Security max-age=63072000; add_header X-Frame-Options sameorigin; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; diff --git a/roles/openldap/defaults/main.yml b/roles/openldap/defaults/main.yml new file mode 100644 index 0000000..f6419e0 --- /dev/null +++ b/roles/openldap/defaults/main.yml @@ -0,0 +1,6 @@ +--- + +ldap_bind_addresses: + - '127.0.0.1' +ldap_managed_domains: + - domain: "{{ domain_name }}" diff --git a/roles/openldap/meta/main.yml b/roles/openldap/meta/main.yml new file mode 100644 index 0000000..cde5f9d --- /dev/null +++ b/roles/openldap/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - role: base-packages + - role: base-config diff --git a/roles/openldap/tasks/ldap-domain-tree.yml b/roles/openldap/tasks/ldap-domain-tree.yml new file mode 100644 index 0000000..3fc775c --- /dev/null +++ b/roles/openldap/tasks/ldap-domain-tree.yml @@ -0,0 +1,178 @@ +- set_fact: current_ldap_domain="{{item.domain}}" + tags: + - ldap + +- set_fact: domain_ldap_admin_pass="{{item.admin_pass|default(ldap_admin_pass)}}" + tags: + - ldap + +- name: Generate hash of LDAP admin password for this domain + command: slappasswd -s {{domain_ldap_admin_pass}} + register: slappasswd_out + tags: + - ldap + +- name: Save fact with hashed LDAP password + set_fact: hashed_ldap_password={{slappasswd_out.stdout}} + tags: + - ldap + +- name: Set fact with Base DN for the current LDAP domain + set_fact: base_dn="dc={{current_ldap_domain.split('.')|join(',dc=')}}" + tags: + - ldap + +- name: Check if the LDAP database exists for current domain + command: ldapsearch -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(&(objectClass=olcDatabaseConfig)(olcSuffix={{base_dn}}))' + register: ldapsearch_db + tags: + - ldap + +- name: Create dedicated directory for current domain database + file: path=/var/lib/caislean_ldap_{{current_ldap_domain}} state=directory group=openldap owner=openldap mode=0755 + when: ldapsearch_db.stdout == "" + tags: + - ldap + +- name: Upload database creation LDIF temporary file for current domain + template: src=new_ldap_db.ldif.j2 dest=/tmp/new_ldap_db.ldif group=root owner=root mode=0600 + when: ldapsearch_db.stdout == "" + tags: + - ldap + +- name: Create database for the current domain + command: ldapadd -Y EXTERNAL -H ldapi:/// -f /tmp/new_ldap_db.ldif + when: ldapsearch_db.stdout == "" + tags: + - ldap + +- name: Remove temporary LDIF database creation file + file: state=absent path=/tmp/new_ldap_db.ldif + when: ldapsearch_db.stdout == "" + tags: + - ldap + +- name: Retrieve current domain database full DN in cn=config + command: ldapsearch -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(&(objectClass=olcDatabaseConfig)(olcSuffix={{base_dn}}))' dn + register: db_dn + tags: + - ldap + +- name: Set fact with current domain database DN in cn=config + set_fact: ldap_db_config_dn="{{db_dn.stdout_lines[0]}}" + tags: + - ldap + +- name: Upload ACL configuration LDIF file for current domain + template: src=ldap_db_acl.ldif.j2 dest=/tmp/ldap_db_acl.ldif group=root owner=root mode=0600 + tags: + - ldap + +- name: Load current domain database ACL into LDAP + command: ldapmodify -v -Y EXTERNAL -H ldapi:/// -f /tmp/ldap_db_acl.ldif + tags: + - ldap + +- name: Remove ACL temporary LDIF file + file: path=/tmp/ldap_db_acl.ldif state=absent + tags: + - ldap + +- name: Test if the administrator password works for current domain + command: ldapwhoami -w {{domain_ldap_admin_pass}} -D cn=admin,{{base_dn}} + ignore_errors: true + register: admin_auth_test + tags: + - ldap + +- name: Upload administrator password update LDIF file + template: src=ldap_db_rootpw.ldif.j2 dest=/tmp/ldap_db_rootpw.ldif group=root owner=root mode=0600 + when: admin_auth_test | failed + tags: + - ldap + +- name: Update administrator password entry + command: ldapmodify -Y EXTERNAL -H ldapi:/// -f /tmp/ldap_db_rootpw.ldif + when: admin_auth_test | failed + tags: + - ldap + +- name: Remove temporary password update LDIF file + file: path=/tmp/ldap_db_rootpw.ldif state=absent + when: admin_auth_test | failed + tags: + - ldap + +- name: Checking existence of root entry in database for current domain + command: ldapsearch -LLL -x -b {{base_dn}} -s base + ignore_errors: true + register: ldapsearch_base_dn + tags: + - ldap + +- name: Upload temporary file to add our database root entry + template: src=base_dn.ldif.j2 dest=/tmp/base_dn.ldif group=root owner=root mode=0600 + when: ldapsearch_base_dn | failed + tags: + - ldap + +- name: Add our database root entry + command: ldapadd -w {{domain_ldap_admin_pass}} -D cn=admin,{{base_dn}} -f /tmp/base_dn.ldif + when: ldapsearch_base_dn | failed + tags: + - ldap + +- name: Remove database root entry temporary file + file: path=/tmp/base_dn.ldif state=absent + when: ldapsearch_base_dn | failed + tags: + - ldap + +- name: Check presence of administrator user entry in our database + command: ldapsearch -LLL -x -b cn=admin,{{base_dn}} -s base + ignore_errors: true + register: ldapsearch_base_dn_admin + tags: + - ldap + +- name: Upload temporary file to add our database admin entry + template: src=base_dn_admin.ldif.j2 dest=/tmp/base_dn_admin.ldif group=root owner=root mode=0600 + when: ldapsearch_base_dn_admin | failed + tags: + - ldap + +- name: Add our database admin entry + command: ldapadd -w {{domain_ldap_admin_pass}} -D cn=admin,{{base_dn}} -f /tmp/base_dn_admin.ldif + when: ldapsearch_base_dn_admin | failed + tags: + - ldap + +- name: Remove database admin entry temporary file + file: path=/tmp/base_dn_admin.ldif state=absent + when: ldapsearch_base_dn_admin | failed + tags: + - ldap + +- name: Check whether organizationalUnit mail LDAP entry exists + command: ldapsearch -x -b ou=mail,{{base_dn}} -s base + ignore_errors: true + register: ldapsearch_mail_ou + tags: + - ldap + +- name: Add organizationalUnit mail LDAP entry (1/2) + template: src=mail_ou.ldif.j2 dest=/tmp/mail_ou.ldif owner=root group=root mode=0644 + when: ldapsearch_mail_ou | failed + tags: + - ldap + +- name: Add organizationalUnit mail LDAP entry (2/2) + command: ldapadd -D cn=admin,{{base_dn}} -w {{ domain_ldap_admin_pass }} -f /tmp/mail_ou.ldif + when: ldapsearch_mail_ou | failed + tags: + - ldap + +- name: Remove LDIF temporary file for organizationalUnit mail entry + file: path=/tmp/mail_ou.ldif state=absent + tags: + - ldap diff --git a/roles/openldap/tasks/main.yml b/roles/openldap/tasks/main.yml index 3ed7fdb..afbf991 100644 --- a/roles/openldap/tasks/main.yml +++ b/roles/openldap/tasks/main.yml @@ -1,8 +1,3 @@ -- name: Set fact with our Base DN - set_fact: base_dn="dc={{domain_name.split('.')|join(',dc=')}}" - tags: - - ldap - - name: Install LDAP packages apt: pkg={{item}} state=installed with_items: @@ -19,7 +14,7 @@ - ldap - name: Install slapd Debian default file - copy: src=etc/default/slapd dest=/etc/default/slapd owner=root group=root mode=0644 + template: src=slapd_default.j2 dest=/etc/default/slapd owner=root group=root mode=0644 notify: - restart slapd tags: @@ -37,168 +32,6 @@ tags: - ldap -- name: Generate hash of LDAP admin password - command: slappasswd -s {{ldap_admin_pass}} - register: slappasswd_out - tags: - - ldap - -- name: Save fact with hashed LDAP password - set_fact: hashed_ldap_password={{slappasswd_out.stdout}} - tags: - - ldap - -- name: Check if the database exists for our domain - command: ldapsearch -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(&(objectClass=olcDatabaseConfig)(olcSuffix={{base_dn}}))' - register: ldapsearch_db - tags: - - ldap - -- name: Create dedicated directory for our database - file: path=/var/lib/caislean_ldap_{{domain_name}} state=directory group=openldap owner=openldap mode=0755 - when: ldapsearch_db.stdout == "" - tags: - - ldap - -- name: Upload temporary database creation LDIF file - template: src=new_ldap_db.ldif.j2 dest=/tmp/new_ldap_db.ldif group=root owner=root mode=0600 - when: ldapsearch_db.stdout == "" - tags: - - ldap - -- name: Create our database - command: ldapadd -Y EXTERNAL -H ldapi:/// -f /tmp/new_ldap_db.ldif - when: ldapsearch_db.stdout == "" - tags: - - ldap - -- name: Remove temporary LDIF database creation file - file: state=absent path=/tmp/new_ldap_db.ldif - when: ldapsearch_db.stdout == "" - tags: - - ldap - -- name: Retrieve our database DN in cn=config - command: ldapsearch -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(&(objectClass=olcDatabaseConfig)(olcSuffix={{base_dn}}))' dn - register: db_dn - tags: - - ldap - -- name: Set fact with our database DN - set_fact: ldap_db_config_dn="{{db_dn.stdout_lines[0]}}" - tags: - - ldap - -- name: Upload ACL configuration LDIF file - template: src=ldap_db_acl.ldif.j2 dest=/tmp/ldap_db_acl.ldif group=root owner=root mode=0600 - tags: - - ldap - -- name: Load DB ACL to LDAP - command: ldapmodify -v -Y EXTERNAL -H ldapi:/// -f /tmp/ldap_db_acl.ldif - tags: - - ldap - -- name: Remove ACL temporary LDIF file - file: path=/tmp/ldap_db_acl.ldif state=absent - tags: - - ldap - -- name: Test if the administrator password works - command: ldapwhoami -w {{ldap_admin_pass}} -D cn=admin,{{base_dn}} - ignore_errors: true - register: admin_auth_test - tags: - - ldap - -- name: Upload administrator password update LDIF file - template: src=ldap_db_rootpw.ldif.j2 dest=/tmp/ldap_db_rootpw.ldif group=root owner=root mode=0600 - when: admin_auth_test | failed - tags: - - ldap - -- name: Update administrator password entry - command: ldapmodify -Y EXTERNAL -H ldapi:/// -f /tmp/ldap_db_rootpw.ldif - when: admin_auth_test | failed - tags: - - ldap - -- name: Remove temporary password update LDIF file - file: path=/tmp/ldap_db_rootpw.ldif state=absent - when: admin_auth_test | failed - tags: - - ldap - -- name: Checking presence of root entry in our database - command: ldapsearch -LLL -x -b {{base_dn}} -s base - ignore_errors: true - register: ldapsearch_base_dn - tags: - - ldap - -- name: Upload temporary file to add our database root entry - template: src=base_dn.ldif.j2 dest=/tmp/base_dn.ldif group=root owner=root mode=0600 - when: ldapsearch_base_dn | failed - tags: - - ldap - -- name: Add our database root entry - command: ldapadd -w {{ldap_admin_pass}} -D cn=admin,{{base_dn}} -f /tmp/base_dn.ldif - when: ldapsearch_base_dn | failed - tags: - - ldap - -- name: Remove database root entry temporary file - file: path=/tmp/base_dn.ldif state=absent - when: ldapsearch_base_dn | failed - tags: - - ldap - -- name: Check presence of administrator user entry in our database - command: ldapsearch -LLL -x -b cn=admin,{{base_dn}} -s base - ignore_errors: true - register: ldapsearch_base_dn_admin - tags: - - ldap - -- name: Upload temporary file to add our database admin entry - template: src=base_dn_admin.ldif.j2 dest=/tmp/base_dn_admin.ldif group=root owner=root mode=0600 - when: ldapsearch_base_dn_admin | failed - tags: - - ldap - -- name: Add our database admin entry - command: ldapadd -w {{ldap_admin_pass}} -D cn=admin,{{base_dn}} -f /tmp/base_dn_admin.ldif - when: ldapsearch_base_dn_admin | failed - tags: - - ldap - -- name: Remove database admin entry temporary file - file: path=/tmp/base_dn_admin.ldif state=absent - when: ldapsearch_base_dn_admin | failed - tags: - - ldap - -- name: Check whether organizationalUnit mail LDAP entry exists - command: ldapsearch -x -b ou=mail,{{base_dn}} -s base - ignore_errors: true - register: ldapsearch_mail_ou - tags: - - ldap - -- name: Add organizationalUnit mail LDAP entry (1/2) - template: src=mail_ou.ldif.j2 dest=/tmp/mail_ou.ldif owner=root group=root mode=0644 - when: ldapsearch_mail_ou | failed - tags: - - ldap - -- name: Add organizationalUnit mail LDAP entry (2/2) - command: ldapadd -D cn=admin,{{base_dn}} -w {{ ldap_admin_pass }} -f /tmp/mail_ou.ldif - when: ldapsearch_mail_ou | failed - tags: - - ldap - -- name: Remove LDIF temporary file for organizationalUnit mail entry - file: path=/tmp/mail_ou.ldif state=absent - tags: - - ldap +- name: Create LDAP entries for all managed domains... + include: ldap-domain-tree.yml + with_items: "{{ldap_managed_domains}}" diff --git a/roles/openldap/templates/base_dn.ldif.j2 b/roles/openldap/templates/base_dn.ldif.j2 index 74f0b5a..dd31e4e 100644 --- a/roles/openldap/templates/base_dn.ldif.j2 +++ b/roles/openldap/templates/base_dn.ldif.j2 @@ -2,6 +2,6 @@ dn: {{base_dn}} objectClass: top objectClass: dcObject objectClass: organization -o: {{domain_name}} -dc: {{domain_name.split('.')[0]}} +o: {{current_ldap_domain}} +dc: {{current_ldap_domain.split('.')[0]}} diff --git a/roles/openldap/templates/base_dn_admin.ldif.j2 b/roles/openldap/templates/base_dn_admin.ldif.j2 index 0294199..3985753 100644 --- a/roles/openldap/templates/base_dn_admin.ldif.j2 +++ b/roles/openldap/templates/base_dn_admin.ldif.j2 @@ -2,6 +2,6 @@ dn: cn=admin,{{base_dn}} objectClass: simpleSecurityObject objectClass: organizationalRole cn: admin -description: LDAP administrator +description: LDAP administrator for {{current_ldap_domain}} userPassword: {{hashed_ldap_password}} diff --git a/roles/openldap/templates/mail_ou.ldif.j2 b/roles/openldap/templates/mail_ou.ldif.j2 index 073a1ed..7fcfb8e 100644 --- a/roles/openldap/templates/mail_ou.ldif.j2 +++ b/roles/openldap/templates/mail_ou.ldif.j2 @@ -1,4 +1,3 @@ -{% set base_dn = 'dc=' + domain_name|split('.')|join(',dc=') %} dn: ou=mail,{{ base_dn }} objectClass: top objectClass: organizationalUnit diff --git a/roles/openldap/templates/new_ldap_db.ldif.j2 b/roles/openldap/templates/new_ldap_db.ldif.j2 index 21f414b..747ca85 100644 --- a/roles/openldap/templates/new_ldap_db.ldif.j2 +++ b/roles/openldap/templates/new_ldap_db.ldif.j2 @@ -2,7 +2,7 @@ dn: olcDatabase=mdb,cn=config objectClass: olcDatabaseConfig objectClass: olcMdbConfig olcDatabase: mdb -olcDbDirectory: /var/lib/caislean_ldap_{{ domain_name }} +olcDbDirectory: /var/lib/caislean_ldap_{{ current_ldap_domain }} olcSuffix: {{ base_dn }} olcAccess: to attrs=userPassword,shadowLastChange by dn="cn=admin,{{ base_dn }}" write diff --git a/roles/openldap/files/etc/default/slapd b/roles/openldap/templates/slapd_default.j2 similarity index 62% rename from roles/openldap/files/etc/default/slapd rename to roles/openldap/templates/slapd_default.j2 index 438b419..1af22f9 100644 --- a/roles/openldap/files/etc/default/slapd +++ b/roles/openldap/templates/slapd_default.j2 @@ -2,6 +2,6 @@ SLAPD_CONF= SLAPD_USER="openldap" SLAPD_GROUP="openldap" SLAPD_PIDFILE= -SLAPD_SERVICES="ldap://127.0.0.1/ ldapi:///" +SLAPD_SERVICES="ldap://{{ldap_bind_addresses|join('/ ldap://')}}/ ldapi:///" SLAPD_SENTINEL_FILE=/etc/ldap/noslapd SLAPD_OPTIONS="" diff --git a/roles/openldap_old/meta/main.yml b/roles/openldap_old/meta/main.yml new file mode 100644 index 0000000..cde5f9d --- /dev/null +++ b/roles/openldap_old/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - role: base-packages + - role: base-config diff --git a/roles/openvpn/meta/main.yml b/roles/openvpn/meta/main.yml new file mode 100644 index 0000000..83dd607 --- /dev/null +++ b/roles/openvpn/meta/main.yml @@ -0,0 +1,6 @@ +dependencies: + - role: base-packages + - role: base-config + - role: tls + +#FIXME: add optional role openldap, if ldap auth is chosen diff --git a/roles/openvpn/tasks/main.yml b/roles/openvpn/tasks/main.yml index d705fbb..cdf118e 100644 --- a/roles/openvpn/tasks/main.yml +++ b/roles/openvpn/tasks/main.yml @@ -5,12 +5,6 @@ - dnsmasq tags: vpn -- name: Open VPN port (1194, inbound) by UFW - ufw: rule=allow direction=in port=1194 - tags: - - firewall - - vpn - - name: Install OpenVPN server configuration template: src=openvpn-server.conf.j2 dest=/etc/openvpn/server.conf owner=root group=root mode=0644 notify: @@ -61,14 +55,6 @@ copy: src=etc/default/openvpn dest=/etc/default/openvpn owner=root group=root mode=0644 tags: vpn -- name: Allow VPN traffic by firewall (outbound) - ufw: rule=allow to_ip=10.1.0.0/24 - tags: vpn - -- name: Allow VPN traffic by firewall (inboud) - ufw: rule=allow from_ip=10.1.0.0/24 - tags: vpn - - name: Install dnsmasq configuration copy: src=etc/dnsmasq.d/vpn dest=/etc/dnsmasq.d/vpn group=root owner=root mode=0644 notify: diff --git a/roles/owncloud/files/etc/php5/fpm/pool.d/owncloud.conf b/roles/owncloud/files/etc/php5/fpm/pool.d/owncloud.conf index 58c5b4a..6fd5891 100644 --- a/roles/owncloud/files/etc/php5/fpm/pool.d/owncloud.conf +++ b/roles/owncloud/files/etc/php5/fpm/pool.d/owncloud.conf @@ -1,4 +1,5 @@ [owncloud] + user = owncloud group = owncloud listen = /var/run/php5-fpm/owncloud.sock @@ -12,5 +13,10 @@ pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 -php_admin_value[open_basedir] = /var/www/owncloud:/usr/share/php:/tmp +php_admin_value[open_basedir] = /var/www/owncloud:/usr/share/php:/tmp:/dev/urandom php_admin_value[session.save_path] = /var/lib/phpsession/owncloud + +env[PATH] = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +env[TMP] = /tmp +env[TMPDIR] = /tmp +env[TEMP] = /tmp diff --git a/roles/owncloud/meta/main.yml b/roles/owncloud/meta/main.yml new file mode 100644 index 0000000..e411c99 --- /dev/null +++ b/roles/owncloud/meta/main.yml @@ -0,0 +1,10 @@ +dependencies: + - role: base-packages + - role: base-config + - role: tls + - role: nginx + - role: php-fpm + - role: mysql + +# FIXME: openldap is a dependency only if LDAP authentication is chosen. Need to +# add a conditional inclusion. diff --git a/roles/owncloud/tasks/main.yml b/roles/owncloud/tasks/main.yml index 9ab4b4b..93d81e6 100644 --- a/roles/owncloud/tasks/main.yml +++ b/roles/owncloud/tasks/main.yml @@ -1,39 +1,50 @@ -- name: Add OpenSUSE PGP signing key (wheezy) - apt_key: url=http://download.opensuse.org/repositories/isv:ownCloud:community/Debian_7.0/Release.key state=present +- name: Add Owncloud PGP signing key (wheezy) + apt_key: url=https://download.owncloud.org/download/repositories/stable/Debian_7.0/Release.key state=present when: ansible_distribution_release == "wheezy" tags: - owncloud -- name: Add OpenSUSE PGP signing key (jessie) - apt_key: url=http://download.opensuse.org/repositories/isv:ownCloud:community/Debian_8.0/Release.key state=present +- name: Add Owncloud PGP signing key (jessie) + apt_key: url=https://download.owncloud.org/download/repositories/stable/Debian_8.0/Release.key state=present when: ansible_distribution_release == "jessie" tags: - owncloud -- name: Add OpenSUSE repository for Owncloud package (wheezy) - apt_repository: repo='deb http://download.opensuse.org/repositories/isv:/ownCloud:/community/Debian_7.0/ /' state=present update_cache=yes +- name: Add Owncloud APT repository (wheezy) + apt_repository: repo='deb http://download.owncloud.org/download/repositories/stable/Debian_7.0/ /' state=present update_cache=yes when: ansible_distribution_release == "wheezy" tags: - owncloud -- name: Add OpenSUSE repository for Owncloud package (jessie) - apt_repository: repo='deb http://download.opensuse.org/repositories/isv:/ownCloud:/community/Debian_8.0/ /' state=present update_cache=yes +- name: Add Owncloud APT repository (jessie) + apt_repository: repo='deb http://download.owncloud.org/download/repositories/stable/Debian_8.0/ /' state=present update_cache=yes when: ansible_distribution_release == "jessie" tags: - owncloud -- name: Install Owncloud and dependencies +- name: Install Owncloud dependencies apt: pkg={{item}} state=installed default_release={{ansible_distribution_release}}-backports with_items: - python-configparser - php5-ldap - php-apc - - owncloud-server notify: - restart php5-fpm tags: - owncloud +- name: Install Owncloud (wheezy) + apt: pkg=owncloud-files state=installed default_release=wheezy-backports + when: ansible_distribution_release == "wheezy" + tags: + - owncloud + +- name: Install Owncloud (jessie) + apt: pkg=owncloud state=installed default_release=jessie-backports + when: ansible_distribution_release == "jessie" + tags: + - owncloud + - name: PHP modules INI files for Owncloud copy: src=etc/php5/mods-available/{{item}} dest=/etc/php5/mods-available/{{item}} owner=root group=root mode=0644 when: ansible_distribution_release == "jessie" @@ -81,25 +92,8 @@ - { dirname: apps, rec: no } - { dirname: config, rec: yes } - { dirname: data, rec: yes } - - { dirname: assets, rec: yes } - tags: - - owncloud - -- name: Set correct permissions in some Owncloud apps subdirectory - file: path=/var/www/owncloud/apps/{{item}} state=directory group=www owner=owncloud mode="u=rwX,g=rX,o-rwx" recurse=yes - with_items: - - calendar - - contacts - tags: - - owncloud - -- name: Create owncloud database - mysql_db: login_user=root login_password="{{mysql_root_password}}" name=owncloud state=present - tags: - - owncloud - -- name: Create owncloud MySQL user and grant access to database - mysql_user: login_user=root login_password="{{mysql_root_password}}" name=owncloud state=present password="{{owncloud_mysql_password}}" priv=owncloud.*:ALL + - { dirname: assets, rec: yes } + - { dirname: themes, rec: yes } tags: - owncloud diff --git a/roles/owncloud/templates/nginx-owncloud.inc.j2 b/roles/owncloud/templates/nginx-owncloud.inc.j2 index d86a0be..2d41ee2 100644 --- a/roles/owncloud/templates/nginx-owncloud.inc.j2 +++ b/roles/owncloud/templates/nginx-owncloud.inc.j2 @@ -16,7 +16,11 @@ location /owncloud { rewrite ^/owncloud/.well-known/carddav /owncloud/remote.php/carddav/ redirect; rewrite ^/owncloud/.well-known/caldav /owncloud/remote.php/caldav/ redirect; - location ~ ^/owncloud/(?:\.htaccess|data|config|db_structure\.xml|README) { + location ~ ^/owncloud/(?:build|tests|config|lib|3rdparty|templates|data)/ { + deny all; + } + + location ~ ^/owncloud/(?:\.|autotest|occ|issue|indie|db_|console) { deny all; } diff --git a/roles/php-fpm/meta/main.yml b/roles/php-fpm/meta/main.yml new file mode 100644 index 0000000..33b6b86 --- /dev/null +++ b/roles/php-fpm/meta/main.yml @@ -0,0 +1,5 @@ +dependencies: + - role: base-packages + - role: base-config + - role: tls + - role: nginx diff --git a/roles/prosody/meta/main.yml b/roles/prosody/meta/main.yml new file mode 100644 index 0000000..ba2f011 --- /dev/null +++ b/roles/prosody/meta/main.yml @@ -0,0 +1,5 @@ +dependencies: + - role: base-packages + - role: base-config + - role: tls + - role: openldap diff --git a/roles/prosody/tasks/main.yml b/roles/prosody/tasks/main.yml index 2e04ac4..604e817 100644 --- a/roles/prosody/tasks/main.yml +++ b/roles/prosody/tasks/main.yml @@ -48,21 +48,3 @@ copy: src=etc/sasl/prosody.conf dest=/etc/sasl/prosody.conf owner=root group=root mode=0644 tags: - xmpp - -- name: Allow XMPP ports by UFW (inbound) - ufw: rule=allow port={{ item }} direction=in - with_items: - - 5222 - - 5269 - tags: - - xmpp - - firewall - -- name: Allow XMPP ports by UFW (outbound) - ufw: rule=allow port={{ item }} direction=out - with_items: - - 5222 - - 5269 - tags: - - xmpp - - firewall diff --git a/roles/roundcube/meta/main.yml b/roles/roundcube/meta/main.yml new file mode 100644 index 0000000..c989a46 --- /dev/null +++ b/roles/roundcube/meta/main.yml @@ -0,0 +1,8 @@ +dependencies: + - role: base-packages + - role: base-config + - role: tls + - role: nginx + - role: php-fpm + - role: openldap + - role: virtualmail diff --git a/roles/samba/meta/main.yml b/roles/samba/meta/main.yml new file mode 100644 index 0000000..deed22c --- /dev/null +++ b/roles/samba/meta/main.yml @@ -0,0 +1,4 @@ +dependencies: + - role: base-packages + - role: base-config + - role: openldap diff --git a/roles/suricata/meta/main.yml b/roles/suricata/meta/main.yml new file mode 100644 index 0000000..cde5f9d --- /dev/null +++ b/roles/suricata/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - role: base-packages + - role: base-config diff --git a/roles/tls/meta/main.yml b/roles/tls/meta/main.yml new file mode 100644 index 0000000..9bba446 --- /dev/null +++ b/roles/tls/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - role: base-packages diff --git a/roles/tls/tasks/main.yml b/roles/tls/tasks/main.yml index 7711b92..0e74df0 100644 --- a/roles/tls/tasks/main.yml +++ b/roles/tls/tasks/main.yml @@ -24,3 +24,26 @@ tags: - tls +- name: Install additional domains TLS files (certificates) + copy: content="{{lookup('file', tls_directory + '/' + item[0] + item[1])}}" dest=/etc/ssl/certs/{{item[0]}}{{item[1]}} owner=root group=ssl-cert mode=0644 + when: tls_additional_domains is defined + with_nested: + - "{{ tls_additional_domains }}" + - [ '.ca.crt.pem', '.cert.crt.pem' ] + tags: + - tls + +- name: Install additional domains TLS files (chain) + copy: content="{{lookup('file', tls_directory + '/' + item + '.cert.crt.pem')}}\n{{lookup('file', tls_directory + '/' + item + '.ca.crt.pem')}}" dest=/etc/ssl/certs/{{item}}.chain.pem owner=root group=ssl-cert mode=0644 + when: tls_additional_domains is defined + with_items: "{{ tls_additional_domains }}" + tags: + - tls + +- name: Install additional domains TLS files (keys) + copy: content="{{lookup('file', tls_directory + '/' + item + '.key.pem')}}" dest=/etc/ssl/private/{{item}}.key.pem owner=root group=ssl-cert mode=0640 + when: tls_additional_domains is defined + with_items: "{{ tls_additional_domains }}" + tags: + - tls + diff --git a/roles/ufw/defaults/main.yml b/roles/ufw/defaults/main.yml new file mode 100644 index 0000000..8aca0c5 --- /dev/null +++ b/roles/ufw/defaults/main.yml @@ -0,0 +1,14 @@ +--- + +ufw_allow_ports_out: + - 25 + - 53 + - 80 + - 443 + +ufw_allow_ports_in: + - "{{ ansible_ssh_port if ansible_ssh_port is defined else 22 }}" + - 25 + +ufw_allow_ips_in: [] +ufw_allow_ips_out: [] diff --git a/roles/common/files/etc/default/ufw b/roles/ufw/files/etc/default/ufw similarity index 100% rename from roles/common/files/etc/default/ufw rename to roles/ufw/files/etc/default/ufw diff --git a/roles/common/files/etc/ufw/before.rules b/roles/ufw/files/etc/ufw/before.rules similarity index 100% rename from roles/common/files/etc/ufw/before.rules rename to roles/ufw/files/etc/ufw/before.rules diff --git a/roles/common/files/etc/ufw/ufw.conf b/roles/ufw/files/etc/ufw/ufw.conf similarity index 100% rename from roles/common/files/etc/ufw/ufw.conf rename to roles/ufw/files/etc/ufw/ufw.conf diff --git a/roles/ufw/meta/main.yml b/roles/ufw/meta/main.yml new file mode 100644 index 0000000..cde5f9d --- /dev/null +++ b/roles/ufw/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - role: base-packages + - role: base-config diff --git a/roles/ufw/tasks/main.yml b/roles/ufw/tasks/main.yml new file mode 100644 index 0000000..6887b76 --- /dev/null +++ b/roles/ufw/tasks/main.yml @@ -0,0 +1,50 @@ +- name: Install ufw + apt: pkg=ufw state=installed + tags: firewall + +- name: Install UFW files + copy: src={{item}} dest=/{{item}} group=root owner=root + register: ufw_config_files + with_items: + - etc/default/ufw + - etc/ufw/ufw.conf + - etc/ufw/before.rules + tags: firewall + +- name: Disable UFW + ufw: state=disabled + when: ( ufw_config_files|changed ) + tags: firewall + +- name: Reload UFW + ufw: state=reloaded + when: ( ufw_config_files|changed ) + tags: firewall + +- name: Open ports in UFW (inbound) + ufw: rule=allow port={{item}} direction=in + with_items: + - "{{ ufw_allow_ports_in }}" + tags: firewall + +- name: Open ports in UFW (outbound) + ufw: rule=allow port={{item}} direction=out + with_items: + - "{{ ufw_allow_ports_out }}" + tags: firewall + +- name: Allow IPs in UFW (outbound) + ufw: rule=allow to_ip={{item}} direction=out + with_items: + - "{{ ufw_allow_ips_out }}" + tags: firewall + +- name: Allow IPs in UFW (inbound) + ufw: rule=allow from_ip={{item}} direction=in + with_items: + - "{{ ufw_allow_ips_in }}" + tags: firewall + +- name: Enable UFW + ufw: state=enabled logging=on policy=deny + tags: firewall diff --git a/roles/usermin/meta/main.yml b/roles/usermin/meta/main.yml new file mode 100644 index 0000000..33b6b86 --- /dev/null +++ b/roles/usermin/meta/main.yml @@ -0,0 +1,5 @@ +dependencies: + - role: base-packages + - role: base-config + - role: tls + - role: nginx diff --git a/roles/virtualmail/meta/main.yml b/roles/virtualmail/meta/main.yml new file mode 100644 index 0000000..ba2f011 --- /dev/null +++ b/roles/virtualmail/meta/main.yml @@ -0,0 +1,5 @@ +dependencies: + - role: base-packages + - role: base-config + - role: tls + - role: openldap diff --git a/roles/virtualmail/tasks/main.yml b/roles/virtualmail/tasks/main.yml index a43799a..1c1308f 100644 --- a/roles/virtualmail/tasks/main.yml +++ b/roles/virtualmail/tasks/main.yml @@ -144,6 +144,7 @@ - restart dovecot tags: - vmail + - name: Install Dovecot LDAP configuration template: src=dovecot-ldap.conf.j2 dest=/etc/dovecot/dovecot-ldap.conf group=root owner=root mode=0644 notify: diff --git a/roles/wordpress/meta/main.yml b/roles/wordpress/meta/main.yml new file mode 100644 index 0000000..1fff6d9 --- /dev/null +++ b/roles/wordpress/meta/main.yml @@ -0,0 +1,9 @@ +dependencies: + - role: base-packages + - role: base-config + - role: tls + - role: nginx + - role: php-fpm + - role: mysql + +#FIXME: add conditional openldap role