summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoshua Lusk <luskjh@gmail.com>2026-05-26 09:20:55 -0400
committerJoshua Lusk <luskjh@gmail.com>2026-05-26 09:20:55 -0400
commit1e0b678fa320ecdcf1e36363e87911d93fa6920b (patch)
tree36c982975e248527f74e2d9b62e561523b7665cd
parent94b0799bc018132084cab540efb521454c918375 (diff)
add devops playbook
-rw-r--r--Makefile4
-rw-r--r--README.md13
-rw-r--r--inventory.yaml2
-rw-r--r--playbooks/devops.yaml150
-rw-r--r--playbooks/files/cockpit/network-config.conf2
-rw-r--r--playbooks/templates/nginx/hollyhock.conf.j250
6 files changed, 219 insertions, 2 deletions
diff --git a/Makefile b/Makefile
index a139adc..2e1c864 100644
--- a/Makefile
+++ b/Makefile
@@ -41,3 +41,7 @@ http:
.PHONY: https
https:
$(BIN)/ansible-playbook -e @vault.yaml playbooks/https.yaml
+
+.PHONY: devops
+devops:
+ $(BIN)/ansible-playbook -e @vault.yaml playbooks/devops.yaml
diff --git a/README.md b/README.md
index e385b86..53b6ac7 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,8 @@ _Listed in applicable order._
| `bootstrap`<sup>*</sup> | Bootstrap access |
| `security` | Security hardening |
| `http` | Web server |
-| `https` | SSL certificates |
+| `https` | SSL certificates |
+| `devops`<sup>†</sup> | DevOps setup |
### <sup>*</sup>Pre-bootstraped targets
@@ -50,6 +51,16 @@ $ make ping ANSIBLE_USER=root ANSIBLE_PORT=22
$ make bootstrap ANSIBLE_USER=root ANSIBLE_PORT=22
```
+#### <sup>†</sup>Hollyhock's mTLS Protection
+
+The subdomain `hollyhock` is secured with mTLS - once the `devops`
+playbook is run succesfully, on macOS you need to add
+`tmp/hollyhock.p12` to your keychain:
+
+```sh
+$ security import tmp/hollyhock.p12 -k ~/Library/Keychains/login.keychain-db -P <mtls_p12_password>
+```
+
## CI / deployments
There is a CI workflow that runs the same pre-commit hooks on GitHub as
diff --git a/inventory.yaml b/inventory.yaml
index 985e6df..15ceb49 100644
--- a/inventory.yaml
+++ b/inventory.yaml
@@ -34,7 +34,7 @@ all:
mtls:
ca:
- cn: "Midharvest CA"
+ cn: "Sorantics CA"
days: 3650 # 10 years
dir: /etc/nginx/mtls
client:
diff --git a/playbooks/devops.yaml b/playbooks/devops.yaml
new file mode 100644
index 0000000..29126e0
--- /dev/null
+++ b/playbooks/devops.yaml
@@ -0,0 +1,150 @@
+- name: DevOps setup
+ hosts: hollyhock
+ become: true
+ tasks:
+ - name: Install cockpit
+ ansible.builtin.apt:
+ name: cockpit
+ default_release: "{{ ansible_facts['distribution_release'] }}-backports"
+ state: present
+ update_cache: true
+
+ - name: Enable cockpit
+ ansible.builtin.systemd:
+ name: cockpit
+ state: started
+ enabled: true
+
+ - name: Copy network configuration
+ ansible.builtin.copy:
+ src: cockpit/network-config.conf
+ dest: /etc/NetworkManager/conf.d/10-globally-managed-devices.conf
+ mode: "0644"
+
+ - name: Create dummy network connection
+ community.general.nmcli:
+ conn_name: fake
+ type: dummy
+ ifname: fake0
+ ip4: 1.2.3.4/24
+ gw4: 1.2.3.1
+ state: present
+ notify: Restart network
+
+ - name: Ensure mtls directory exists on server
+ ansible.builtin.file:
+ path: "{{ mtls.ca.dir }}"
+ state: directory
+ owner: root
+ group: root
+ mode: "0700"
+
+ - name: Generate ca private key
+ community.crypto.openssl_privatekey:
+ path: "{{ mtls.ca.dir }}/ca.key"
+ size: 4096
+ mode: "0600"
+
+ - name: Generate ca ssr
+ community.crypto.openssl_csr:
+ path: "{{ mtls.ca.dir }}/ca.csr"
+ privatekey_path: "{{ mtls.ca.dir }}/ca.key"
+ common_name: "{{ mtls.ca.cn }}"
+ basic_constraints:
+ - "CA:TRUE"
+ basic_constraints_critical: true
+ key_usage:
+ - keyCertSign
+ - cRLSign
+ key_usage_critical: true
+
+ - name: Generate self-signed ca certificate
+ community.crypto.x509_certificate:
+ path: "{{ mtls.ca.dir }}/ca.crt"
+ privatekey_path: "{{ mtls.ca.dir }}/ca.key"
+ csr_path: "{{ mtls.ca.dir }}/ca.csr"
+ provider: selfsigned
+ selfsigned_not_after: "+{{ mtls.ca.days }}d"
+ mode: "0644"
+
+ - name: Generate client private key
+ community.crypto.openssl_privatekey:
+ path: "{{ mtls.ca.dir }}/client.key"
+ size: 2048
+ mode: "0600"
+
+ - name: Generate client csr
+ community.crypto.openssl_csr:
+ path: "{{ mtls.ca.dir }}/client.csr"
+ privatekey_path: "{{ mtls.ca.dir }}/client.key"
+ common_name: "{{ mtls.client.cn }}"
+ extended_key_usage:
+ - clientAuth
+
+ - name: Sign client certificate with ca
+ community.crypto.x509_certificate:
+ path: "{{ mtls.ca.dir }}/client.crt"
+ csr_path: "{{ mtls.ca.dir }}/client.csr"
+ provider: ownca
+ ownca_path: "{{ mtls.ca.dir }}/ca.crt"
+ ownca_privatekey_path: "{{ mtls.ca.dir }}/ca.key"
+ ownca_not_after: "+{{ mtls.client.days }}d"
+ mode: "0600"
+
+ - name: Bundle client cert + key into pkcs#12
+ community.crypto.openssl_pkcs12:
+ action: export
+ path: "{{ mtls.ca.dir }}/client.p12"
+ friendly_name: "{{ mtls.client.cn }}"
+ privatekey_path: "{{ mtls.ca.dir }}/client.key"
+ certificate_path: "{{ mtls.ca.dir }}/client.crt"
+ other_certificates:
+ - "{{ mtls.ca.dir }}/ca.crt"
+ passphrase: "{{ mtls_p12_password }}"
+ encryption_level: compatibility2022
+ mode: "0600"
+
+ - name: Fetch client bundle to local machine
+ ansible.builtin.fetch:
+ src: "{{ mtls.ca.dir }}/client.p12"
+ dest: "{{ tmp_dir }}/hollyhock.p12"
+ flat: true
+
+ - name: Save ca cert in tmp/
+ ansible.builtin.fetch:
+ src: "{{ mtls.ca.dir }}/ca.crt"
+ dest: "{{ tmp_dir }}/hollyhock_ca.crt"
+ flat: true
+
+ - name: Copy nginx config
+ ansible.builtin.template:
+ src: nginx/hollyhock.conf.j2
+ dest: "/etc/nginx/sites-available/hollyhock"
+ mode: "0644"
+
+ - name: Disable http and https nginx sites
+ ansible.builtin.file:
+ path: "{{ item }}"
+ state: absent
+ loop:
+ - /etc/nginx/sites-enabled/hollyhock-http
+ - /etc/nginx/sites-enabled/hollyhock-https
+
+ - name: Enable nginx config
+ ansible.builtin.file:
+ src: /etc/nginx/sites-available/hollyhock
+ dest: /etc/nginx/sites-enabled/hollyhock
+ state: link
+ owner: "{{ nginx_user }}"
+ group: "{{ nginx_group }}"
+ notify:
+ - Test and restart nginx
+
+ handlers:
+ - name: Test and restart nginx
+ ansible.builtin.include_tasks: tasks/test_and_restart_nginx.yaml
+
+ - name: Restart network
+ ansible.builtin.systemd:
+ name: NetworkManager
+ state: restarted
diff --git a/playbooks/files/cockpit/network-config.conf b/playbooks/files/cockpit/network-config.conf
new file mode 100644
index 0000000..970ba08
--- /dev/null
+++ b/playbooks/files/cockpit/network-config.conf
@@ -0,0 +1,2 @@
+[keyfile]
+unmanaged-devices=none
diff --git a/playbooks/templates/nginx/hollyhock.conf.j2 b/playbooks/templates/nginx/hollyhock.conf.j2
new file mode 100644
index 0000000..18a30d4
--- /dev/null
+++ b/playbooks/templates/nginx/hollyhock.conf.j2
@@ -0,0 +1,50 @@
+server {
+ listen 80;
+ server_name hollyhock.{{ domain }};
+
+ location /.well-known/acme-challenge/ {
+ root /var/www/certbot;
+ }
+
+ location / {
+ return 444;
+ }
+}
+
+server {
+ server_name hollyhock.{{ domain }};
+
+ listen 443 ssl;
+
+ ssl_client_certificate /etc/nginx/mtls/ca.crt;
+ ssl_verify_client on;
+ ssl_verify_depth 1;
+
+ location /.well-known/acme-challenge/ {
+ try_files $uri $uri/ =404;
+ }
+
+ location / {
+ proxy_pass https://127.0.0.1:9090;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-Proto $scheme;
+
+ proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
+ proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;
+
+ proxy_http_version 1.1;
+ proxy_buffering off;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+
+ gzip off;
+ }
+
+ ssl_certificate /etc/letsencrypt/live/hollyhock.{{ domain }}/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/hollyhock.{{ domain }}/privkey.pem;
+ include /etc/letsencrypt/options-ssl-nginx.conf;
+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
+
+ access_log /var/log/nginx/hollyhock/access.log;
+ error_log /var/log/nginx/hollyhock/error.log;
+}