Add support for 2 more SSL certificate retrieval methods

Adds support for managing certificates manually and for
having the playbook generate self-signed certificates for you.

With this, Let's Encrypt usage is no longer required.

Fixes Github issue #50.
This commit is contained in:
Slavi Pantaleev 2018-12-23 11:00:12 +02:00
parent bfcba5256e
commit d28bdb3258
21 changed files with 296 additions and 86 deletions

View file

@ -1,5 +1,20 @@
# 2018-12-23
## More SSL certificate retrieval methods
The playbook now lets you decide between 3 different SSL certificate retrieval methods:
- (default) obtaining free SSL certificates from Let's Encrypt
- generating self-signed SSL certificates
- managing SSL certificates manually
Learn more in [Adjusting SSL certificate retrieval](docs/configuring-playbook-ssl-certificates.md).
For people who use Let's Encrypt (mostly everyone, since it's the default), you'll also have to rename a variable in your configuration:
- before: `host_specific_matrix_ssl_support_email`
- after: `host_specific_matrix_ssl_lets_encrypt_support_email`
## (BC Break) mxisd upgrade with multiple base DN support
mxisd has bee upgraded to [version 1.2.2](https://github.com/kamax-matrix/mxisd/releases/tag/v1.2.2), which supports [multiple base DNs](https://github.com/kamax-matrix/mxisd/blob/v1.2.2/docs/stores/ldap.md#base).

View file

@ -16,7 +16,7 @@ Using this playbook, you can get the following services configured on your serve
- a [coturn](https://github.com/coturn/coturn) STUN/TURN server for WebRTC audio/video calls
- free [Let's Encrypt](https://letsencrypt.org/) SSL certificate, which secures the connection to the Synapse server and the Riot web UI
- (optional, default) free [Let's Encrypt](https://letsencrypt.org/) SSL certificate, which secures the connection to the Synapse server and the Riot web UI
- (optional, default) a [Riot](https://riot.im/) web UI, which is configured to connect to your own Matrix Synapse server by default

View file

@ -24,6 +24,6 @@ matrix_nginx_proxy_enabled: false
- ensure you set up (separate) vhosts that proxy for both Riot (`localhost:8765`) and Matrix Synapse (`localhost:8008`)
- ensure that the `/.well-known/acme-challenge` location for each "port=80 vhost" gets proxied to `http://localhost:2402` (controlled by `matrix_ssl_certbot_standalone_http_port`) for automated SSL renewal to work
- ensure that the `/.well-known/acme-challenge` location for each "port=80 vhost" gets proxied to `http://localhost:2402` (controlled by `matrix_ssl_lets_encrypt_certbot_standalone_http_port`) for automated SSL renewal to work
- ensure that you restart/reload your webserver once in a while, so that renewed SSL certificates would take effect (once a month should be enough)

View file

@ -0,0 +1,32 @@
# Adjusting SSL certificate retrieval (optional, advanced)
By default, this playbook retrieves and auto-renews free SSL certificates from [Let's Encrypt](https://letsencrypt.org/).
If that's alright, you can skip this.
## Using self-signed SSL certificates
For private deployments (not publicly accessible from the internet), you may not be able to use Let's Encrypt certificates.
If self-signed certificates are alright with you, you can ask the playbook to generate such for you with the following configuration:
```yaml
matrix_ssl_retrieval_method: self-signed
```
## Using your own SSL certificates
If you'd like to manage SSL certificates by yourself and have the playbook use your certificate files, you can use the following configuration:
```yaml
matrix_ssl_retrieval_method: manually-managed
```
With such a configuration, the playbook would expect you to drop the SSL certificate files in the directory specified by `matrix_ssl_config_dir_path` (`/matrix/ssl/config` by default) obeying the following hierarchy:
- `<matrix_ssl_config_dir_path>/live/<domain>/fullchain.pem`
- `<matrix_ssl_config_dir_path>/live/<domain>/privkey.pem`
where `<domain>` refers to the domains that you need (usually `matrix.<your-domain>` and `riot.<your-domain>`).

View file

@ -35,6 +35,8 @@ When you're done with all the configuration you'd like to do, continue with [Ins
- [Adjusting mxisd Identity Server configuration](configuring-playbook-mxisd.md) (optional)
- [Adjusting SSL certificate retrieval](configuring-playbook-ssl-certificates.md) (optional, advanced)
- [Using your own webserver, instead of this playbook's nginx proxy](configuring-playbook-own-webserver.md) (optional, advanced)
- [Setting up the REST authentication password provider module](configuring-playbook-rest-auth.md) (optional, advanced)

View file

@ -4,8 +4,12 @@
# In case SSL renewal fails at some point, you'll also get
# an email notification there.
#
# If you decide to use another method for managing SSL certifites (different than the default Let's Encrypt),
# you won't be required to define this variable
# (see `docs/configuring-playbook-ssl-certificates.md`).
#
# Example value: someone@example.com
host_specific_matrix_ssl_support_email: YOUR_EMAIL_ADDRESS_HERE
host_specific_matrix_ssl_lets_encrypt_support_email: YOUR_EMAIL_ADDRESS_HERE
# This is your bare domain name (`<your-domain`).
#

View file

@ -387,17 +387,34 @@ matrix_nginx_proxy_reload_cron_time_definition: "20 4 */5 * *"
# See: https://github.com/nginxinc/docker-nginx/issues/190
matrix_nginx_proxy_ssl_protocols: "TLSv1.1 TLSv1.2"
# By default, this playbook automatically retrieves and auto-renews
# free SSL certificates from Let's Encrypt.
#
# The following retrieval methods are supported:
# - "lets-encrypt" - the playbook obtains free SSL certificates from Let's Encrypt
# - "self-signed" - the playbook generates and self-signs certificates
# - "manually-managed" - lets you manage certificates by yourself (manually; see below)
#
# If you decide to manage certificates by yourself (`matrix_ssl_retrieval_method: manually-managed`),
# you'd need to drop them into the directory specified by `matrix_ssl_config_dir_path`
# obeying the following hierarchy:
# - <matrix_ssl_config_dir_path>/live/<domain>/fullchain.pem
# - <matrix_ssl_config_dir_path>/live/<domain>/privkey.pem
# where <domain> refers to the domains that you need (usually `hostname_matrix` and `hostname_riot`).
matrix_ssl_retrieval_method: "lets-encrypt"
# Controls whether to obtain production or staging certificates from Let's Encrypt.
matrix_ssl_lets_encrypt_staging: false
matrix_ssl_lets_encrypt_certbot_docker_image: "certbot/certbot:v0.29.1"
matrix_ssl_lets_encrypt_certbot_standalone_http_port: 2402
matrix_ssl_lets_encrypt_support_email: "{{ host_specific_matrix_ssl_lets_encrypt_support_email }}"
# Specifies when to attempt to retrieve new SSL certificates from Let's Encrypt.
matrix_ssl_lets_encrypt_renew_cron_time_definition: "15 4 */5 * *"
matrix_ssl_base_path: "{{ matrix_base_data_path }}/ssl"
matrix_ssl_config_dir_path: "{{ matrix_ssl_base_path }}/config"
matrix_ssl_log_dir_path: "{{ matrix_ssl_base_path }}/log"
matrix_ssl_support_email: "{{ host_specific_matrix_ssl_support_email }}"
matrix_ssl_certbot_docker_image: "certbot/certbot:v0.29.1"
matrix_ssl_certbot_standalone_http_port: 2402
matrix_ssl_use_staging: false
# Specifies when to attempt to retrieve new SSL certificates from Let's Encrypt.
matrix_ssl_renew_cron_time_definition: "15 4 */5 * *"
# Variables to Control which parts of the role run.
run_setup: true

View file

@ -8,7 +8,7 @@
tags:
- setup-all
- include: tasks/setup/setup_ssl.yml
- include: tasks/setup/ssl/main.yml
tags:
- setup-all
- setup-ssl

View file

@ -1,54 +0,0 @@
---
- name: Determine domains to obtain certificates for (Matrix)
set_fact:
domains_to_obtain_certificate_for: "['{{ hostname_matrix }}']"
- name: Determine domains to obtain certificates for (Riot)
set_fact:
domains_to_obtain_certificate_for: "{{ domains_to_obtain_certificate_for + [hostname_riot] }}"
when: matrix_riot_web_enabled
- name: Allow access to HTTP/HTTPS in firewalld
firewalld:
service: "{{ item }}"
state: enabled
immediate: yes
permanent: yes
with_items:
- http
- https
when: ansible_os_family == 'RedHat'
- name: Ensure certbot Docker image is pulled
docker_image:
name: "{{ matrix_ssl_certbot_docker_image }}"
- name: Ensure SSL certificate paths exists
file:
path: "{{ item }}"
state: directory
mode: 0770
owner: "{{ matrix_user_username }}"
group: "{{ matrix_user_username }}"
with_items:
- "{{ matrix_ssl_log_dir_path }}"
- "{{ matrix_ssl_config_dir_path }}"
- name: Obtain initial certificates
include_tasks: "tasks/setup/setup_ssl_for_domain.yml"
with_items: "{{ domains_to_obtain_certificate_for }}"
loop_control:
loop_var: domain_name
- name: Ensure SSL renewal script installed
template:
src: "{{ role_path }}/templates/usr-local-bin/matrix-ssl-certificates-renew.j2"
dest: "/usr/local/bin/matrix-ssl-certificates-renew"
mode: 0750
- name: Ensure periodic SSL renewal cronjob configured
template:
src: "{{ role_path }}/templates/cron.d/matrix-ssl-certificate-renewal.j2"
dest: "/etc/cron.d/matrix-ssl-certificate-renewal"
mode: 0600

View file

@ -0,0 +1,38 @@
---
- name: Fail if using unsupported SSL certificate retrieval method
fail:
msg: "The `matrix_ssl_retrieval_method` variable contains an unsupported value"
when: "matrix_ssl_retrieval_method not in ['lets-encrypt', 'self-signed', 'manually-managed']"
# Common tasks, required by any method below.
- name: Determine domains that we require certificates for (Matrix)
set_fact:
domains_requiring_certificates: "['{{ hostname_matrix }}']"
- name: Determine domains that we require certificates for (Riot)
set_fact:
domains_requiring_certificates: "{{ domains_requiring_certificates + [hostname_riot] }}"
when: "matrix_riot_web_enabled"
- name: Ensure SSL certificate paths exists
file:
path: "{{ item }}"
state: directory
mode: 0770
owner: "{{ matrix_user_username }}"
group: "{{ matrix_user_username }}"
with_items:
- "{{ matrix_ssl_log_dir_path }}"
- "{{ matrix_ssl_config_dir_path }}"
# Method specific tasks follow
- include: tasks/setup/ssl/setup_ssl_lets_encrypt.yml
- include: tasks/setup/ssl/setup_ssl_self_signed.yml
- include: tasks/setup/ssl/setup_ssl_manually_managed.yml

View file

@ -0,0 +1,61 @@
---
#
# Tasks related to setting up Let's Encrypt's management of certificates
#
- name: (Deprecation) Fail if using outdated configuration
fail:
msg: "You're using the `host_specific_matrix_ssl_support_email` variable, which has been superseded by `host_specific_matrix_ssl_lets_encrypt_support_email`. Please change your configuration to use the new name!"
when: "matrix_ssl_retrieval_method == 'lets-encrypt' and host_specific_matrix_ssl_support_email is defined"
- name: Allow access to HTTP/HTTPS in firewalld
firewalld:
service: "{{ item }}"
state: enabled
immediate: yes
permanent: yes
with_items:
- http
- https
when: "matrix_ssl_retrieval_method == 'lets-encrypt' and ansible_os_family == 'RedHat'"
- name: Ensure certbot Docker image is pulled
docker_image:
name: "{{ matrix_ssl_lets_encrypt_certbot_docker_image }}"
when: "matrix_ssl_retrieval_method == 'lets-encrypt'"
- name: Obtain certificates
include_tasks: "tasks/setup/ssl/setup_ssl_lets_encrypt_obtain_for_domain.yml"
with_items: "{{ domains_requiring_certificates }}"
loop_control:
loop_var: domain_name
when: "matrix_ssl_retrieval_method == 'lets-encrypt'"
- name: Ensure SSL renewal script installed
template:
src: "{{ role_path }}/templates/usr-local-bin/matrix-ssl-certificates-renew.j2"
dest: "/usr/local/bin/matrix-ssl-certificates-renew"
mode: 0750
when: "matrix_ssl_retrieval_method == 'lets-encrypt'"
- name: Ensure periodic SSL renewal cronjob configured
template:
src: "{{ role_path }}/templates/cron.d/matrix-ssl-certificate-renewal.j2"
dest: "/etc/cron.d/matrix-ssl-certificate-renewal"
mode: 0600
when: "matrix_ssl_retrieval_method == 'lets-encrypt'"
#
# Tasks related to getting rid of Let's Encrypt's management of certificates
#
- name: Ensure Let's Encrypt SSL certificate management files removed
file:
path: "{{ item }}"
state: absent
with_items:
- /usr/local/bin/matrix-ssl-certificates-renew
- /etc/cron.d/matrix-ssl-certificate-renewal
when: "matrix_ssl_retrieval_method != 'lets-encrypt'"

View file

@ -22,38 +22,38 @@
--net=host
-v {{ matrix_ssl_config_dir_path }}:/etc/letsencrypt
-v {{ matrix_ssl_log_dir_path }}:/var/log/letsencrypt
{{ matrix_ssl_certbot_docker_image }}
{{ matrix_ssl_lets_encrypt_certbot_docker_image }}
certonly
--non-interactive
{% if matrix_ssl_use_staging %}--staging{% endif %}
{% if matrix_ssl_lets_encrypt_staging %}--staging{% endif %}
--standalone
--preferred-challenges http
--agree-tos
--email={{ matrix_ssl_support_email }}
--email={{ matrix_ssl_lets_encrypt_support_email }}
-d {{ domain_name }}
when: "domain_name_needs_cert"
register: result_certbot_direct
ignore_errors: true
# If matrix-nginx-proxy is configured from a previous run of this playbook,
# and it's running now, it may be able to proxy requests to `matrix_ssl_certbot_standalone_http_port`.
# and it's running now, it may be able to proxy requests to `matrix_ssl_lets_encrypt_certbot_standalone_http_port`.
- name: Attempt initial SSL certificate retrieval with standalone authenticator (via proxy)
shell: >-
/usr/bin/docker run
--rm
--name=matrix-certbot
-p 127.0.0.1:{{ matrix_ssl_certbot_standalone_http_port }}:80
-p 127.0.0.1:{{ matrix_ssl_lets_encrypt_certbot_standalone_http_port }}:80
--network={{ matrix_docker_network }}
-v {{ matrix_ssl_config_dir_path }}:/etc/letsencrypt
-v {{ matrix_ssl_log_dir_path }}:/var/log/letsencrypt
{{ matrix_ssl_certbot_docker_image }}
{{ matrix_ssl_lets_encrypt_certbot_docker_image }}
certonly
--non-interactive
{% if matrix_ssl_use_staging %}--staging{% endif %}
{% if matrix_ssl_lets_encrypt_staging %}--staging{% endif %}
--standalone
--preferred-challenges http
--agree-tos
--email={{ matrix_ssl_support_email }}
--email={{ matrix_ssl_lets_encrypt_support_email }}
-d {{ domain_name }}
when: "domain_name_needs_cert and result_certbot_direct.failed"
register: result_certbot_proxy
@ -65,6 +65,6 @@
Failed to obtain a certificate directly (by listening on port 80)
and also failed to obtain by relying on the server at port 80 to proxy the request.
See above for details.
You may wish to set up proxying of /.well-known/acme-challenge to {{ matrix_ssl_certbot_standalone_http_port }} or,
You may wish to set up proxying of /.well-known/acme-challenge to {{ matrix_ssl_lets_encrypt_certbot_standalone_http_port }} or,
more easily, stop the server on port 80 while this playbook runs.
when: "domain_name_needs_cert and result_certbot_direct.failed and result_certbot_proxy.failed"

View file

@ -0,0 +1,8 @@
---
- name: Verify certificates
include_tasks: "tasks/setup/ssl/setup_ssl_manually_managed_verify_for_domain.yml"
with_items: "{{ domains_requiring_certificates }}"
loop_control:
loop_var: domain_name
when: "matrix_ssl_retrieval_method == 'manually-managed'"

View file

@ -0,0 +1,23 @@
---
- set_fact:
matrix_ssl_certificate_verification_cert_path: "{{ matrix_ssl_config_dir_path }}/live/{{ domain_name }}/fullchain.pem"
matrix_ssl_certificate_verification_cert_key_path: "{{ matrix_ssl_config_dir_path }}/live/{{ domain_name }}/privkey.pem"
- name: Check if SSL certificate file exists
stat:
path: "{{ matrix_ssl_certificate_verification_cert_path }}"
register: matrix_ssl_certificate_verification_cert_path_stat_result
- fail:
msg: "Failed finding a certificate file (for domain `{{ domain_name }}`) at `{{ matrix_ssl_certificate_verification_cert_path }}`"
when: "not matrix_ssl_certificate_verification_cert_path_stat_result.stat.exists"
- name: Check if SSL certificate key file exists
stat:
path: "{{ matrix_ssl_certificate_verification_cert_key_path }}"
register: matrix_ssl_certificate_verification_cert_key_path_stat_result
- fail:
msg: "Failed finding a certificate key file (for domain `{{ domain_name }}`) at `{{ matrix_ssl_certificate_verification_cert_key_path }}`"
when: "not matrix_ssl_certificate_verification_cert_key_path_stat_result.stat.exists"

View file

@ -0,0 +1,24 @@
---
- name: Ensure OpenSSL installed (RedHat)
yum:
name:
- openssl
state: present
update_cache: no
when: ansible_os_family == 'RedHat'
- name: Ensure APT usage dependencies are installed (Debian)
apt:
name:
- openssl
state: present
update_cache: no
when: ansible_os_family == 'Debian'
- name: Obtain certificates
include_tasks: "tasks/setup/ssl/setup_ssl_self_signed_obtain_for_domain.yml"
with_items: "{{ domains_requiring_certificates }}"
loop_control:
loop_var: domain_name
when: "matrix_ssl_retrieval_method == 'self-signed'"

View file

@ -0,0 +1,40 @@
---
- set_fact:
matrix_ssl_certificate_csr_path: "{{ matrix_ssl_config_dir_path }}/live/{{ domain_name }}/csr.csr"
matrix_ssl_certificate_cert_path: "{{ matrix_ssl_config_dir_path }}/live/{{ domain_name }}/fullchain.pem"
matrix_ssl_certificate_cert_key_path: "{{ matrix_ssl_config_dir_path }}/live/{{ domain_name }}/privkey.pem"
- name: Check if SSL certificate file exists
stat:
path: "{{ matrix_ssl_certificate_cert_path }}"
register: matrix_ssl_certificate_cert_path_stat_result
# In order to do any sort of generation (below), we need to ensure the directory exists first
- name: Ensure SSL certificate directory exists
file:
path: "{{ matrix_ssl_certificate_csr_path|dirname }}"
state: directory
mode: 0750
owner: "{{ matrix_user_username }}"
group: "{{ matrix_user_username }}"
when: "not matrix_ssl_certificate_cert_path_stat_result.stat.exists"
# The proper way to do this is by using a sequence of
# `openssl_privatekey`, `openssl_csr` and `openssl_certificate`.
#
# Unfortunately, `openssl_csr` and `openssl_certificate` require `PyOpenSSL>=0.15` to work,
# which is not available on CentOS 7 (at least).
#
# We'll do it in a more manual way.
- name: Generate SSL certificate
command: |
openssl req -x509 \
-sha256 \
-newkey rsa:4096 \
-nodes \
-subj "/CN={{ domain_name }}" \
-keyout {{ matrix_ssl_certificate_cert_key_path }} \
-out {{ matrix_ssl_certificate_cert_path }} \
-days 3650
when: "not matrix_ssl_certificate_cert_path_stat_result.stat.exists"

View file

@ -1,4 +1,4 @@
MAILTO="{{ matrix_ssl_support_email }}"
MAILTO="{{ matrix_ssl_lets_encrypt_support_email }}"
# This periodically reloads the matrix-nginx-proxy service
# to ensure it's using the latest SSL certificate

View file

@ -1,4 +1,4 @@
MAILTO="{{ matrix_ssl_support_email }}"
MAILTO="{{ matrix_ssl_lets_encrypt_support_email }}"
# The goal of this cronjob is to ask certbot to check
# the current SSL certificates and to see if some need renewal.
@ -8,4 +8,4 @@ MAILTO="{{ matrix_ssl_support_email }}"
# This is not our concern here. We simply make sure the certificates are up to date.
# Restarting of services happens on its own different schedule (other cronjobs).
{{ matrix_ssl_renew_cron_time_definition }} root /bin/bash /usr/local/bin/matrix-ssl-certificates-renew
{{ matrix_ssl_lets_encrypt_renew_cron_time_definition }} root /bin/bash /usr/local/bin/matrix-ssl-certificates-renew

View file

@ -12,7 +12,7 @@ server {
proxy_pass http://$backend;
{% else %}
{# Generic configuration for use outside of our container setup #}
proxy_pass http://localhost:{{ matrix_ssl_certbot_standalone_http_port }};
proxy_pass http://localhost:{{ matrix_ssl_lets_encrypt_certbot_standalone_http_port }};
{% endif %}
}

View file

@ -12,7 +12,7 @@ server {
proxy_pass http://$backend;
{% else %}
{# Generic configuration for use outside of our container setup #}
proxy_pass http://localhost:{{ matrix_ssl_certbot_standalone_http_port }};
proxy_pass http://localhost:{{ matrix_ssl_lets_encrypt_certbot_standalone_http_port }};
{% endif %}
}

View file

@ -4,23 +4,23 @@
# need to forward requests for `/.well-known/acme-challenge` to the certbot container.
#
# This can happen inside the container network by proxying to `http://matrix-certbot:80`
# or outside (on the host) by proxying to `http://localhost:{{ matrix_ssl_certbot_standalone_http_port }}`.
# or outside (on the host) by proxying to `http://localhost:{{ matrix_ssl_lets_encrypt_certbot_standalone_http_port }}`.
docker run \
--rm \
--name=matrix-certbot \
--network="{{ matrix_docker_network }}" \
-p 127.0.0.1:{{ matrix_ssl_certbot_standalone_http_port }}:80 \
-p 127.0.0.1:{{ matrix_ssl_lets_encrypt_certbot_standalone_http_port }}:80 \
-v {{ matrix_ssl_config_dir_path }}:/etc/letsencrypt \
-v {{ matrix_ssl_log_dir_path }}:/var/log/letsencrypt \
{{ matrix_ssl_certbot_docker_image }} \
{{ matrix_ssl_lets_encrypt_certbot_docker_image }} \
renew \
--non-interactive \
{% if matrix_ssl_use_staging %}
{% if matrix_ssl_lets_encrypt_staging %}
--staging \
{% endif %}
--quiet \
--standalone \
--preferred-challenges http \
--agree-tos \
--email={{ matrix_ssl_support_email }}
--email={{ matrix_ssl_lets_encrypt_support_email }}