From 51b70bbfc4e1b2e9268c8b8275f3cf916cbbec39 Mon Sep 17 00:00:00 2001 From: Eliot Berriot <contact@eliotberriot.com> Date: Mon, 13 May 2019 11:49:00 +0200 Subject: [PATCH] Working setup with e2e test ! --- defaults/main.yml | 6 ++ handlers/main.yml | 5 + molecule/default/Dockerfile.j2 | 53 +++++++++ molecule/default/playbook.yml | 7 +- molecule/default/tests/test_default.py | 27 ++++- tasks/nginx.yml | 48 +++++++++ tasks/packages.yml | 2 + templates/funkwhale_proxy.conf.j2 | 13 +++ templates/nginx.conf.j2 | 144 +++++++++++++++++++++++++ 9 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 templates/funkwhale_proxy.conf.j2 create mode 100644 templates/nginx.conf.j2 diff --git a/defaults/main.yml b/defaults/main.yml index 206df3a..44ade7d 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -5,6 +5,7 @@ funkwhale_media_path: /srv/funkwhale/data/media funkwhale_static_path: /srv/funkwhale/data/static funkwhale_music_path: /srv/funkwhale/data/music funkwhale_config_path: /srv/funkwhale/config +funkwhale_external_storage_enabled: false funkwhale_username: funkwhale funkwhale_database_managed: true funkwhale_database_name: funkwhale @@ -18,3 +19,8 @@ funkwhale_settings_module: config.settings.production funkwhale_env_vars: [] funkwhale_systemd_after: redis.service postgresql.service funkwhale_systemd_service_name: funkwhale +funkwhale_letsencrypt_certbot_flags: +funkwhale_letsencrypt_enabled: true +funkwhale_letsencrypt_skip_cert: false +funkwhale_ssl_cert_path: +funkwhale_ssl_key_path: diff --git a/handlers/main.yml b/handlers/main.yml index c548e03..efd098f 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -3,3 +3,8 @@ service: name: funkwhale.target state: restarted + +- name: reload nginx + service: + name: nginx + state: reloaded diff --git a/molecule/default/Dockerfile.j2 b/molecule/default/Dockerfile.j2 index e6aa95d..4e7f4ea 100644 --- a/molecule/default/Dockerfile.j2 +++ b/molecule/default/Dockerfile.j2 @@ -12,3 +12,56 @@ RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y pyth elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml && zypper clean -a; \ elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates; \ elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates && xbps-remove -O; fi + +# Adding test SSL certs +RUN mkdir /certs && \ +echo '-----BEGIN CERTIFICATE-----\n\ +MIIDqTCCApGgAwIBAgIUBGy73NTCABwZpYk9Xj7O5QlSUd4wDQYJKoZIhvcNAQEL\n\ +BQAwZDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\n\ +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEdMBsGA1UEAwwUZnVua3doYWxlLnlv\n\ +dXJkb21haW4wHhcNMTkwNTEzMDg0MzM2WhcNMjkwNTEwMDg0MzM2WjBkMQswCQYD\n\ +VQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQg\n\ +V2lkZ2l0cyBQdHkgTHRkMR0wGwYDVQQDDBRmdW5rd2hhbGUueW91cmRvbWFpbjCC\n\ +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO7ZdBvEQlUJN5IiKLlxYd9J\n\ +ccx74oH2fsLHpESP/uWgvALXxu9XcbtaGAyIIiw5BDoNfew7SKLxx/G5I3Uqs6aI\n\ +mPhEiVDW7V8Z3slxQcGY7I5c0RkrlIvcX4pnUfqPHfANQH1rLs6emq2P7MsrQe+P\n\ +iIsvJk0BFY+ID4KlxnZzBvuu6gjSucLFtY2/OPV7Rxo9fwflknmGb9GNjWBnlT9e\n\ +OQUJRYVGY56m5XU+1C1XRPYFhtBKf0fiO/Hu2MuLKRgY+H7c33BltvOatUo74UzV\n\ +ud5X9z75a0Nvivv7elXtR5ThYHfEHODpRm6oLWLc5RUtq3qw7t/PCiEzpySpticC\n\ +AwEAAaNTMFEwHQYDVR0OBBYEFPVrtpu6+yY94B6ZQqbu4nQNeH1MMB8GA1UdIwQY\n\ +MBaAFPVrtpu6+yY94B6ZQqbu4nQNeH1MMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI\n\ +hvcNAQELBQADggEBALmL+8SAPkelsSPLocbjT1I3BQPpBARDds7JA8I+sWW8eRg4\n\ +jYJ0tXdKhJoaLoURrqGXT42CPO61shESKVTgmF84S+IXfVHaKySRQ2XbGAC0c2+W\n\ +c3nVH3QYEpGR0uINPNpzS/TB0Xiw5vTLu9i8KppgynFa8GzPVFL5U6Cjk/7sbpP+\n\ +8EMF1+fj1ol4Unr7NswK4qgZ/e7gvUghCwWUMlbWfn8YAW/Rz8cfofiYBpTLiOmn\n\ +3ZnvZpNVSUy7LIBf9hJrnpYZ62YLa3W2mBxvNNDlpt5OuqBn9xJpmzjdwnL5hake\n\ +/ofctTHKVVBLDrteZaR3zFJtcnMJBgxrHAnKDxY=\n\ +-----END CERTIFICATE-----' > /certs/test.crt && \ + echo '-----BEGIN PRIVATE KEY-----\n\ +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDu2XQbxEJVCTeS\n\ +Iii5cWHfSXHMe+KB9n7Cx6REj/7loLwC18bvV3G7WhgMiCIsOQQ6DX3sO0ii8cfx\n\ +uSN1KrOmiJj4RIlQ1u1fGd7JcUHBmOyOXNEZK5SL3F+KZ1H6jx3wDUB9ay7Onpqt\n\ +j+zLK0Hvj4iLLyZNARWPiA+CpcZ2cwb7ruoI0rnCxbWNvzj1e0caPX8H5ZJ5hm/R\n\ +jY1gZ5U/XjkFCUWFRmOepuV1PtQtV0T2BYbQSn9H4jvx7tjLiykYGPh+3N9wZbbz\n\ +mrVKO+FM1bneV/c++WtDb4r7+3pV7UeU4WB3xBzg6UZuqC1i3OUVLat6sO7fzwoh\n\ +M6ckqbYnAgMBAAECggEBAOcJHr+xEryworlPI3dljldu8o3qk4pFBg6hEGtlohk6\n\ +ZkKVuA9R1Wh1WGfe0rdWG5mP7WsxWSyMlWxjOkIeoRIIdXdIzE6O56MjIhZW4FVE\n\ ++CUo8PXY6uJys1PsZybngvztxKDMqcUhM9uJKXLJEAmLHHgNjo3T66gf42jrs9Jj\n\ +yRFtX7P2+WkGtiELiHavnwebFu7QalsbVnIM2IpkqM3EeCkN2Mesg8UjbGAaC04d\n\ +QQrgzwllRfGGz00bcW7+UM9Qvuo4ySycagkJsjUwg27Fa2bYn0nwKckmoZdqtFZi\n\ +T5MNF17/69x3KtfP4B/z/DmpIpFl3sx3DTNJZGdunykCgYEA/z67wEaLTE6eaYS6\n\ +ZMpeUgv/55sdfnbGwxJPBum28Pd9re+7wmaUHVqant9bskTKjFUTpzSkG4FMbhW4\n\ +QzoyS9xb946dZ86xIpcHdWjMRz1H+WoHyFQMc3FJL3k5bu5O0bhGlkBv9Lw6sB58\n\ +mM4TQhvCYTo4gu/CdwbipcAnBpsCgYEA745OQjKETa0kOi7Rl8IynaVL+lCyo11A\n\ +mGaIoTVVVb8HtghPwQ2gE3tGZJNqApMXy61EKpL+oBB3hYjaAxjJ6L1uHEk1d4Vh\n\ +5ZHtH/2YnL20vrJVVenNGlnkP1sFGPT6Ik/WwtFNzhyiLy3Yh2m6D2dzAJU/q6w/\n\ +JME+yjfQgWUCgYEA3HQ2fU9wqKuOrtxwkPUomfy7PjL/OmckLP/3OaVK2L0p070n\n\ +kjQ+4UuMGIQaEtLPjn08yEHzK751YO9pgscVAeiFlj+bPDmNTK7dRk8JgM4xB/7I\n\ +UgIl7t99sn8lV+aENSbP8J4fFGYu2ka6ns/+Wi7XqFo8zPqAH4XW5AoeqtkCgYEA\n\ +g6ueTDhkDCXcwbY2Mcnl9EyExGNSrQR2ms3Qz3r3GphTbrSG0b/hepPJj0KPhDbB\n\ +X4wu6Y++MNCcQWOFISqg4DIusZFzt+wycPKSeYD0P0Luc9qlmQFXYOKD2Gb5GEye\n\ +p/lmO9K7h0ypwHDckSGFyS29QlbGll+onVUyZAflrRkCgYBJtGOKfwZ8Ws82qzYC\n\ +un5cpBQknAOOZaGsPY7BbtIK0/zG66mearYOeo9TxJZgL+hDCL8is7qcGXRGcA5J\n\ +5RdqyJ6Ex1hXyc1cnIXXOrSDFVobA2/QMiAZ2kDSW/DkRHwJNDWVtwnG3HG4vEqb\n\ +FE/Prv7XTK6n66CPuThoeox2iQ==\n\ +-----END PRIVATE KEY-----\n' > /certs/test.key diff --git a/molecule/default/playbook.yml b/molecule/default/playbook.yml index 99584c4..b5dd190 100644 --- a/molecule/default/playbook.yml +++ b/molecule/default/playbook.yml @@ -6,11 +6,16 @@ # https://github.com/ansible/molecule/issues/1567#issuecomment-436876722 - role: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}" vars: + funkwhale_letsencrypt_email: test-certbot@yourdomain.funkwhale + funkwhale_letsencrypt_skip_cert: true + # funkwhale_letsencrypt_enabled: false + funkwhale_ssl_cert_path: /certs/test.crt + funkwhale_ssl_key_path: /certs/test.key funkwhale_hostname: yourdomain.funkwhale funkwhale_protocol: https funkwhale_version: 0.19.0-rc2 funkwhale_env_vars: - EMAIL_CONFIG=smtp+tls://user@:password@youremail.host:587 - - DEFAULT_FROM_EMAIL=noreply@yourdomain + - DEFAULT_FROM_EMAIL=noreply@yourdomain.funkwhale - ADDITIONAL_VAR=1 - ADDITIONAL_VAR=2 diff --git a/molecule/default/tests/test_default.py b/molecule/default/tests/test_default.py index 9adad38..ea32042 100644 --- a/molecule/default/tests/test_default.py +++ b/molecule/default/tests/test_default.py @@ -1,6 +1,6 @@ +import json import os import pytest - import testinfra.utils.ansible_runner testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( @@ -129,3 +129,28 @@ def test_funkwhale_services(service, host): service = host.service(service) assert service.is_running assert service.is_enabled + + +def test_certbot_auto_installed(host): + assert host.find_command("certbot-auto") == "/usr/local/bin/certbot-auto" + + +def test_nginx_configuration(host): + f = host.file("/etc/nginx/sites-enabled/yourdomain.funkwhale.conf") + assert f.exists is True + content = f.content.decode() + assert "ssl_certificate /certs/test.crt;" in content + assert "ssl_certificate_key /certs/test.key;" in content + + +def test_e2e_front(host): + command = 'curl -k https://localhost --header "Host: yourdomain.funkwhale"' + result = host.run(command) + assert '<meta content="Funkwhale" property="og:site_name" />' in result.stdout + + +def test_e2e_api(host): + command = 'curl -k https://localhost/api/v1/instance/nodeinfo/2.0/ --header "Host: yourdomain.funkwhale"' + payload = host.run(command).stdout + data = json.loads(payload) + assert data["software"]["version"] == "0.19.0-rc2" diff --git a/tasks/nginx.yml b/tasks/nginx.yml index 9f01d2e..08e76fe 100644 --- a/tasks/nginx.yml +++ b/tasks/nginx.yml @@ -7,7 +7,55 @@ - nginx - name: "Start Nginx" + become: true when: funkwhale_nginx_managed service: name: nginx state: started + +# from https://gist.github.com/mattiaslundberg/ba214a35060d3c8603e9b1ec8627d349 +- name: "Download certbot-auto" + when: funkwhale_nginx_managed and funkwhale_letsencrypt_enabled + become: true + get_url: + url: https://dl.eff.org/certbot-auto + dest: /usr/local/bin/certbot-auto + mode: 0750 + +- name: "Install certbot using certbot-auto" + when: funkwhale_nginx_managed and funkwhale_letsencrypt_enabled + become: true + command: certbot-auto --install-only -n -v + +- name: Create letsencrypt certificate + when: funkwhale_nginx_managed and funkwhale_letsencrypt_enabled and not funkwhale_letsencrypt_skip_cert + become: true + command: certbot-auto -v -n certonly --nginx -m {{ funkwhale_letsencrypt_email }} --agree-tos -d {{ funkwhale_hostname }} {{ funkwhale_letsencrypt_certbot_flags }} + args: + creates: /etc/letsencrypt/live/{{ funkwhale_hostname }} + +- name: Add letsencrypt cronjob for cert renewal + when: funkwhale_nginx_managed and funkwhale_letsencrypt_enabled and not funkwhale_letsencrypt_skip_cert + become: true + cron: + name: funkwhale_letsencrypt_renewal + special_time: weekly + job: certbot-auto -v -n --renew certonly --nginx -m {{ funkwhale_letsencrypt_email }} --agree-tos -d {{ funkwhale_hostname }} {{ funkwhale_letsencrypt_certbot_flags }} + +- name: "Create funkwhale proxy file" + when: funkwhale_nginx_managed + become: true + template: + src: funkwhale_proxy.conf.j2 + dest: "/etc/nginx/funkwhale_proxy.conf" + notify: + - reload nginx + +- name: "Create funkwhale vhost file" + when: funkwhale_nginx_managed + become: true + template: + src: nginx.conf.j2 + dest: "/etc/nginx/sites-enabled/{{ funkwhale_hostname }}.conf" + notify: + - reload nginx diff --git a/tasks/packages.yml b/tasks/packages.yml index 82bbeae..4286607 100644 --- a/tasks/packages.yml +++ b/tasks/packages.yml @@ -18,3 +18,5 @@ - "libmagic-dev" - "libpq-dev" - "postgresql-client" + # not strictly needed but useful + - "curl" diff --git a/templates/funkwhale_proxy.conf.j2 b/templates/funkwhale_proxy.conf.j2 new file mode 100644 index 0000000..312986f --- /dev/null +++ b/templates/funkwhale_proxy.conf.j2 @@ -0,0 +1,13 @@ +# global proxy conf +proxy_set_header Host $host; +proxy_set_header X-Real-IP $remote_addr; +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header X-Forwarded-Proto $scheme; +proxy_set_header X-Forwarded-Host $host:$server_port; +proxy_set_header X-Forwarded-Port $server_port; +proxy_redirect off; + +# websocket support +proxy_http_version 1.1; +proxy_set_header Upgrade $http_upgrade; +proxy_set_header Connection $connection_upgrade; diff --git a/templates/nginx.conf.j2 b/templates/nginx.conf.j2 new file mode 100644 index 0000000..cc12cfb --- /dev/null +++ b/templates/nginx.conf.j2 @@ -0,0 +1,144 @@ +# {{ ansible_managed }} +{% if funkwhale_protocol == 'https' %} +server { + listen 80; + server_name {{ funkwhale_hostname }}; + + {% if funkwhale_letsencrypt_enabled %} + location /.well-known/acme-challenge { + root /var/www/letsencrypt; + try_files $uri $uri/ =404; + } + {% endif %} + location / { + rewrite ^ https://{{ funkwhale_hostname }}$request_uri? permanent; + } +} +{% endif %} + +# required for websocket support +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +server { + listen {% if funkwhale_protocol == 'https' %}443 ssl{% else %}80{% endif %}; + server_name {{ funkwhale_hostname }}; + + {% if funkwhale_letsencrypt_enabled %} + location /.well-known/acme-challenge { + root /var/www/letsencrypt; + try_files $uri $uri/ =404; + } + {% endif %} + {% if funkwhale_protocol == 'https' %} + {% if funkwhale_ssl_key_path %} + ssl_certificate {{ funkwhale_ssl_cert_path }}; + ssl_certificate_key {{ funkwhale_ssl_key_path }}; + {% else %} + ssl_certificate /ect/lectsencrypt/live/{{ funkwhale_hostname }}/fullchain.pem; + ssl_certificate_key /ect/lectsencrypt/live/{{ funkwhale_hostname }}/privkey.pem; + {% endif %} + ssl_session_cache shared:le_nginx_SSL:1m; + ssl_session_timeout 1440m; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA ECDHE-ECDSA-AES256-SHA ECDHE-ECDSA-AES128-SHA256 ECDHE-ECDSA-AES256-SHA384 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-SHA ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES256-SHA384 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-SHA DHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES256-SHA256 EDH-RSA-DES-CBC3-SHA"; + add_header Strict-Transport-Security "max-age=31536000"; + {% endif %} + + root {{ funkwhale_install_path }}/front/dist; + + # compression settings + gzip on; + gzip_comp_level 5; + gzip_min_length 256; + gzip_proxied any; + gzip_vary on; + + gzip_types + application/javascript + application/vnd.geo+json + application/vnd.ms-fontobject + application/x-font-ttf + application/x-web-app-manifest+json + font/opentype + image/bmp + image/svg+xml + image/x-icon + text/cache-manifest + text/css + text/plain + text/vcard + text/vnd.rim.location.xloc + text/vtt + text/x-component + text/x-cross-domain-policy; + + # end of compression settings + location / { + include /etc/nginx/funkwhale_proxy.conf; + # this is needed if you have file import via upload enabled + client_max_body_size {{ funkwhale_nginx_max_body_size }}; + proxy_pass http://{{ funkwhale_api_ip }}:{{ funkwhale_api_port }}/; + } + + location /front/ { + alias {{ funkwhale_install_path }}/front/dist/; + expires 30d; + add_header Pragma public; + add_header Cache-Control "public, must-revalidate, proxy-revalidate"; + } + + location /federation/ { + include /etc/nginx/funkwhale_proxy.conf; + proxy_pass http://{{ funkwhale_api_ip }}:{{ funkwhale_api_port }}/federation/; + } + + # You can comment this if you do not plan to use the Subsonic API + location /rest/ { + include /etc/nginx/funkwhale_proxy.conf; + proxy_pass http://{{ funkwhale_api_ip }}:{{ funkwhale_api_port }}/api/subsonic/rest/; + } + + location /.well-known/ { + include /etc/nginx/funkwhale_proxy.conf; + proxy_pass http://{{ funkwhale_api_ip }}:{{ funkwhale_api_port }}/.well-known/; + } + + location /media/ { + alias {{ funkwhale_media_path }}/; + } + + {% if funkwhale_external_storage_enabled %} + # Comment the previous location and uncomment this one if you're storing + # media files in a S3 bucket + location ~ /_protected/media/(.+) { + internal; + proxy_pass $1; + } + {% else %} + location /_protected/media { + # this is an internal location that is used to serve + # audio files once correct permission / authentication + # has been checked on API side + internal; + alias {{ funkwhale_media_path }}; + } + {% endif %} + + location /_protected/music { + # this is an internal location that is used to serve + # audio files once correct permission / authentication + # has been checked on API side + # Set this to the same value as your MUSIC_DIRECTORY_PATH setting + internal; + alias {{ funkwhale_music_path }}; + } + + location /staticfiles/ { + # django static files + alias {{ funkwhale_static_path }}/; + } +} -- GitLab