diff options
| author | Joshua Lusk <luskjh@gmail.com> | 2026-05-26 09:20:55 -0400 |
|---|---|---|
| committer | Joshua Lusk <luskjh@gmail.com> | 2026-05-26 09:20:55 -0400 |
| commit | 1e0b678fa320ecdcf1e36363e87911d93fa6920b (patch) | |
| tree | 36c982975e248527f74e2d9b62e561523b7665cd | |
| parent | 94b0799bc018132084cab540efb521454c918375 (diff) | |
add devops playbook
| -rw-r--r-- | Makefile | 4 | ||||
| -rw-r--r-- | README.md | 13 | ||||
| -rw-r--r-- | inventory.yaml | 2 | ||||
| -rw-r--r-- | playbooks/devops.yaml | 150 | ||||
| -rw-r--r-- | playbooks/files/cockpit/network-config.conf | 2 | ||||
| -rw-r--r-- | playbooks/templates/nginx/hollyhock.conf.j2 | 50 |
6 files changed, 219 insertions, 2 deletions
@@ -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 @@ -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; +} |
