Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
96 changes: 96 additions & 0 deletions roles/build_openconext/files/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>OpenConext build artifacts</title>

<style>
body {
margin: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f5f5f5;
color: #222;
}

main {
max-width: 800px;
margin: 4rem auto;
padding: 2rem;
background: #fff;
border-radius: 8px;
}

header {
display: flex;
align-items: center;
gap: 1.5rem;
margin-bottom: 2rem;
}

header img {
width: 120px;
height: auto;
}

h1 {
margin: 0;
font-size: 1.8rem;
}

.artifact-list {
list-style: none;
padding: 0;
margin: 1.5rem 0 2rem;
}

.artifact-list li {
margin: 0.5rem 0;
}

.artifact-list a {
display: block;
padding: 0.8rem 1rem;
border: 1px solid #ddd;
border-radius: 5px;
text-decoration: none;
color: inherit;
background: #fafafa;
}

.artifact-list a:hover {
background: #f0f0f0;
}

footer {
margin-top: 2rem;
font-size: 0.9rem;
color: #666;
}

footer a {
color: inherit;
}
</style>
</head>

<body>
<main>
<header>
<img src="openconext.png" alt="OpenConext logo">
<h1>OpenConext build artifacts distribution server</h1>
</header>

<p>Available artifact repositories:</p>

<ul class="artifact-list">
<li><a href="/repository/">repository/</a></li>
</ul>

<footer>
For more information, see
<a href="https://openconext.org/">openconext.org</a>.
</footer>
</main>
</body>
</html>
6 changes: 6 additions & 0 deletions roles/build_openconext/handlers/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---

- name: "Reload apache"
ansible.builtin.systemd:
name: "apache2"
state: "reloaded"
95 changes: 95 additions & 0 deletions roles/build_openconext/tasks/get_cert.yml
Original file line number Diff line number Diff line change
@@ -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:

@crosmuller crosmuller Sep 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wordt het certificate met deze opties steeds vernieuwd bij een deploy? Of alleen indien nodig?

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"
90 changes: 90 additions & 0 deletions roles/build_openconext/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -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:

@crosmuller crosmuller Sep 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is het de bedoeling om later een acme role te maken? Die gedeployed kan op haproxy en apache?

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"
66 changes: 66 additions & 0 deletions roles/build_openconext/templates/apacheconf.j2
Original file line number Diff line number Diff line change
@@ -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

<VirtualHost *:80>
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/
RewriteRule ^.*$ https://%{HTTP_HOST}%{REQUEST_URI} [R=308,QSA,L]
</VirtualHost>


<VirtualHost *:443>
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

<Directory "/srv/www">
Require all granted
AllowOverride None
</Directory>

<Directory "/srv/www/repository">
Options +Indexes
AllowOverride None

Dav on

AuthType Basic
AuthName "Build"
AuthUserFile /etc/apache2/htpasswd
<RequireAny>
Require method GET
Require valid-user
</RequireAny>
</Directory>

<Directory "/srv/www/vagrant_boxes">
# ancient stuff, remove if noone complains by June 2027
Require all denied
AllowOverride None
</Directory>

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
</VirtualHost>
13 changes: 12 additions & 1 deletion roles/haproxy/tasks/acme.yml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
Expand Down