diff --git a/roles/build_openconext/files/Openconext-med-transparent.png b/roles/build_openconext/files/Openconext-med-transparent.png new file mode 100644 index 000000000..19a193005 Binary files /dev/null and b/roles/build_openconext/files/Openconext-med-transparent.png differ diff --git a/roles/build_openconext/files/index.html b/roles/build_openconext/files/index.html new file mode 100644 index 000000000..1a0d04a7a --- /dev/null +++ b/roles/build_openconext/files/index.html @@ -0,0 +1,96 @@ + + + + + + OpenConext build artifacts + + + + + +
+
+ OpenConext logo +

OpenConext build artifacts distribution server

+
+ +

Available artifact repositories:

+ + + + +
+ + diff --git a/roles/build_openconext/handlers/main.yml b/roles/build_openconext/handlers/main.yml new file mode 100644 index 000000000..3b66ffc10 --- /dev/null +++ b/roles/build_openconext/handlers/main.yml @@ -0,0 +1,6 @@ +--- + +- name: "Reload apache" + ansible.builtin.systemd: + name: "apache2" + state: "reloaded" diff --git a/roles/build_openconext/tasks/get_cert.yml b/roles/build_openconext/tasks/get_cert.yml new file mode 100644 index 000000000..efc5efd64 --- /dev/null +++ b/roles/build_openconext/tasks/get_cert.yml @@ -0,0 +1,95 @@ +--- +############################################################################## +## NB: duplicated code from haproxy/tasks/get_acme_certs.yml +## TODO: make common tasks for ACME for haproxy and apache/build.openconext.org +############################################################################## + +############################################################################## +## Set up destonation paths +############################################################################## +- name: "Create ssl dir" + ansible.builtin.file: + path: "/etc/ssl/" + state: "directory" + owner: "root" + group: "root" + mode: "0755" + +- name: "Create target dir for certificates" + ansible.builtin.file: + path: "/etc/ssl/{{ item }}" + state: "directory" + owner: "acme" + group: "ssl-cert" + mode: "0750" + loop: "{{ build_openconext_acme_hosts }}" + +- name: "Allow acme to reload apache" + community.general.sudoers: + name: "acme-may-reload-apache" + commands: "/bin/systemctl reload apache2" + user: "acme" + nopassword: true + + +############################################################################## +## get new certs +############################################################################## +- name: "Check existence of acme CNAME records" + ansible.builtin.command: + cmd: "dig +short -t CNAME '_acme-challenge.{{ item }}'" + register: "build_openconext_acme_cname" + failed_when: "build_openconext_acme_cname.stdout == ''" + changed_when: false + loop: "{{ build_openconext_acme_hosts }}" + become: false + delegate_to: "localhost" + check_mode: false # this is safe run run, even in check mode + run_once: true + +- name: "Update certificates on one host at a time" + throttle: 1 + become_user: "acme" + become: true + block: + - name: "Issue the certificates using acme" + ansible.builtin.command: + cmd: | + /home/acme/.acme.sh/acme.sh + --issue + --ecc + --keylength ec-256 + --days "{{ build_openconext_acme_renewal_days }}" + --dns dns_acmedns + --stateless + --dnssleep 3 + --server "{{ build_openconext_acme_server }}" + --domain "{{ item }}" + {{ acme_account.changed | ternary('--force', '') }} + environment: + ACMEDNS_BASE_URL: "{{ build_openconext_acmedns.baseurl }}" + ACMEDNS_USERNAME: "{{ build_openconext_acmedns.username }}" + ACMEDNS_PASSWORD: "{{ build_openconext_acmedns.password }}" + ACMEDNS_SUBDOMAIN: "{{ build_openconext_acmedns.subdomain }}" + loop: "{{ build_openconext_acme_hosts }}" + register: "acme_issue" + changed_when: "acme_issue.rc == 0" + failed_when: 'acme_issue.rc != 0 and acme_issue.rc != 2' + + - name: "Deploy cert to Apache" + ansible.builtin.command: + cmd: | + /home/acme/.acme.sh/acme.sh + --install-cert + --domain "{{ item }}" + --cert-file "/etc/ssl/{{ item }}/cert.pem" + --key-file "/etc/ssl/{{ item }}/key.pem" + --fullchain-file "/etc/ssl/{{ item }}/fullchain.pem" + --reloadcmd "sudo /bin/systemctl reload apache2" + when: "acme_issue.results[loop_idx].changed" + register: "acme_install" + changed_when: "'Running reload cmd' in acme_install.stdout" + failed_when: "'Reload successful' not in acme_install.stdout" + loop: "{{ build_openconext_acme_hosts }}" + loop_control: + index_var: "loop_idx" diff --git a/roles/build_openconext/tasks/main.yml b/roles/build_openconext/tasks/main.yml new file mode 100644 index 000000000..82ad29cad --- /dev/null +++ b/roles/build_openconext/tasks/main.yml @@ -0,0 +1,90 @@ +--- +- name: "Install apache" + ansible.builtin.apt: + name: + - "apache2" + +- name: "Remove default apache site" + ansible.builtin.file: + path: "/etc/apache2/sites-enabled/000-default.conf" + state: "absent" + notify: "Reload apache" + +- name: "Install apache config" + ansible.builtin.template: + dest: "/etc/apache2/sites-enabled/001-build_openconext_org.conf" + src: "apacheconf.j2" + mode: "0644" + notify: "Reload apache" + +- name: "Install htpasswd file" + ansible.builtin.copy: + dest: "/etc/apache2/htpasswd" + content: | + {% for user, pw_hash in build_openconext_webdav_users.items() -%} + {{ user }}:{{ pw_hash }} + {% endfor %} + owner: "root" + group: "www-data" + mode: "0640" + +- name: "Create web root" + ansible.builtin.file: + path: "/srv/www" + state: "directory" + owner: "root" + mode: "0755" + +- name: "Create webdav directories" + ansible.builtin.file: + path: "{{ item }}" + state: "directory" + owner: "www-data" + group: "www-data" + mode: "0755" + loop: + - "/srv/www/repository" + - "/srv/www/vagrant_boxes" + +- name: "Install logo" + ansible.builtin.copy: + src: "Openconext-med-transparent.png" + dest: "/srv/www/openconext.png" + owner: "root" + mode: "0644" + +- name: "Install index.html" + ansible.builtin.copy: + dest: "/srv/www/index.html" + src: "index.html" + owner: "root" + mode: "0644" + + +######################################################### +## acme +######################################################### +- name: "Install ACME scripts" + ansible.builtin.include_role: + name: "haproxy" + tasks_from: "acme.yml" + vars: + haproxy_acme_server: "{{ build_openconext_acme_server }}" + haproxy_acme_eab_kid: "{{ build_openconext_acme_eab_kid }}" + haproxy_acme_eab_hmac_key: "{{ build_openconext_acme_eab_hmac_key }}" + +- name: "Request and install ACME cert" + ansible.builtin.include_tasks: + file: "get_cert.yml" + vars: + build_openconext_acme_hosts: ["build.openconext.org"] + + +######################################################### +## finish +######################################################### +- name: "Start apache" + ansible.builtin.systemd: + name: "apache2" + enabled: true + state: "started" diff --git a/roles/build_openconext/templates/apacheconf.j2 b/roles/build_openconext/templates/apacheconf.j2 new file mode 100644 index 000000000..8da1a9ee7 --- /dev/null +++ b/roles/build_openconext/templates/apacheconf.j2 @@ -0,0 +1,66 @@ +# modern configuration +SSLProtocol -all +TLSv1.3 +SSLOpenSSLConfCmd Curves X25519:prime256v1:secp384r1 +SSLCipherSuite TLSv1.3 TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 +SSLHonorCipherOrder off +SSLSessionTickets off + + + RewriteEngine On + RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/ + RewriteRule ^.*$ https://%{HTTP_HOST}%{REQUEST_URI} [R=308,QSA,L] + + + + + ServerName "{{ build_openconext_apache_hostnames[0] }}" + {% for h in build_openconext_apache_hostnames[1:] %} + ServerAlias {{ h }} + {% endfor %} + + ServerAdmin "{{ build_openconext_admin_email }}" + DocumentRoot /srv/www + + Protocols h2 http/1.1 + SSLEngine On + SSLCertificateKeyFile /etc/ssl/build.openconext.org/key.pem + SSLCertificateFile /etc/ssl/build.openconext.org/fullchain.pem + + Header always set Strict-Transport-Security "max-age=63072000" + + ErrorLog syslog:local7:apache2-error + CustomLog "|/usr/bin/logger -p local7.info -t apache2-access" combined + + + Require all granted + AllowOverride None + + + + Options +Indexes + AllowOverride None + + Dav on + + AuthType Basic + AuthName "Build" + AuthUserFile /etc/apache2/htpasswd + + Require method GET + Require valid-user + + + + + # ancient stuff, remove if noone complains by June 2027 + Require all denied + AllowOverride None + + + Header always set X-Content-Type-Options "nosniff" + Header always set Content-Security-Policy "default-src 'none'; form-action 'none'; base-uri 'none'; frame-ancestors 'none'; img-src 'self';" + Header always set Referrer-Policy "same-origin" + Header always set X-Frame-Options "DENY" + + Redirect /.well-known/security.txt https://www.surf.nl/.well-known/security.txt + diff --git a/roles/haproxy/tasks/acme.yml b/roles/haproxy/tasks/acme.yml index 6dcb26b02..9b577720d 100644 --- a/roles/haproxy/tasks/acme.yml +++ b/roles/haproxy/tasks/acme.yml @@ -1,4 +1,13 @@ --- +# to make role reusable, make sure these groups exist +- name: "Make sure groups exist" + ansible.builtin.group: + name: "{{ item }}" + state: present + loop: + - "lbops" + - "haproxy" + - name: Create acme user ansible.builtin.user: name: "acme" @@ -10,7 +19,9 @@ - name: Install acl package so ansible can run as an unprivilegd user ansible.builtin.package: - name: "acl" + name: + - "acl" + - "bind9-dnsutils" state: "present" - name: Clone the acme.sh repo