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