From ab050fddd7d7dcec37ddf6fe88c8de96eb1506e3 Mon Sep 17 00:00:00 2001 From: Louis Simoneau Date: Fri, 10 Apr 2026 08:31:41 +1000 Subject: [PATCH] Pin image versions, add security headers, log limits, unattended upgrades - Pin Miniflux to 2.2.19, Gitea to 1.25 (from :latest) - Add security headers (X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy) to all Caddy sites - Add Docker JSON log rotation (10m x 3 files) to all containers - Add SHA256 checksum verification for GoatCounter binary download - Install and configure unattended-upgrades for security patches Co-Authored-By: Claude Opus 4.6 --- infra/Caddyfile | 28 ++++++++++++++++++++++++++++ infra/ansible/playbook.yml | 28 ++++++++++++++++++++++++++++ infra/gitea/docker-compose.yml | 12 +++++++++++- infra/miniflux/docker-compose.yml | 12 +++++++++++- 4 files changed, 78 insertions(+), 2 deletions(-) diff --git a/infra/Caddyfile b/infra/Caddyfile index 7945ce4..db3df81 100644 --- a/infra/Caddyfile +++ b/infra/Caddyfile @@ -2,6 +2,14 @@ monotrope.au { root * /var/www/monotrope file_server + # Security headers + header { + X-Content-Type-Options "nosniff" + X-Frame-Options "DENY" + Referrer-Policy "strict-origin-when-cross-origin" + Permissions-Policy "camera=(), microphone=(), geolocation=()" + } + # Compression encode zstd gzip @@ -27,6 +35,13 @@ www.monotrope.au { reader.monotrope.au { reverse_proxy localhost:8080 + header { + X-Content-Type-Options "nosniff" + X-Frame-Options "DENY" + Referrer-Policy "strict-origin-when-cross-origin" + Permissions-Policy "camera=(), microphone=(), geolocation=()" + } + encode zstd gzip } @@ -34,6 +49,12 @@ reader.monotrope.au { git.monotrope.au { reverse_proxy localhost:3000 + header { + X-Content-Type-Options "nosniff" + Referrer-Policy "strict-origin-when-cross-origin" + Permissions-Policy "camera=(), microphone=(), geolocation=()" + } + encode zstd gzip } @@ -41,5 +62,12 @@ git.monotrope.au { stats.monotrope.au { reverse_proxy localhost:8081 + header { + X-Content-Type-Options "nosniff" + X-Frame-Options "DENY" + Referrer-Policy "strict-origin-when-cross-origin" + Permissions-Policy "camera=(), microphone=(), geolocation=()" + } + encode zstd gzip } diff --git a/infra/ansible/playbook.yml b/infra/ansible/playbook.yml index 9a80909..f7d2f05 100644 --- a/infra/ansible/playbook.yml +++ b/infra/ansible/playbook.yml @@ -33,8 +33,35 @@ - apt-transport-https - curl - ufw + - unattended-upgrades state: present + - name: Configure unattended-upgrades + copy: + dest: /etc/apt/apt.conf.d/50unattended-upgrades + owner: root + group: root + mode: '0644' + content: | + Unattended-Upgrade::Allowed-Origins { + "${distro_id}:${distro_codename}-security"; + "${distro_id}ESMApps:${distro_codename}-apps-security"; + "${distro_id}ESM:${distro_codename}-infra-security"; + }; + Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; + Unattended-Upgrade::Remove-Unused-Dependencies "true"; + Unattended-Upgrade::Automatic-Reboot "false"; + + - name: Enable automatic updates + copy: + dest: /etc/apt/apt.conf.d/20auto-upgrades + owner: root + group: root + mode: '0644' + content: | + APT::Periodic::Update-Package-Lists "1"; + APT::Periodic::Unattended-Upgrade "1"; + # ── Caddy ─────────────────────────────────────────────────────────────── - name: Add Caddy GPG key @@ -309,6 +336,7 @@ url: "https://github.com/arp242/goatcounter/releases/download/v{{ goatcounter_version }}/goatcounter-v{{ goatcounter_version }}-linux-amd64.gz" dest: /tmp/goatcounter.gz mode: '0644' + checksum: "sha256:98d221cb9c8ef2bf76d8daa9cca647839f8d8b0bb5bc7400ff9337c5da834511" tags: goatcounter - name: Decompress GoatCounter binary diff --git a/infra/gitea/docker-compose.yml b/infra/gitea/docker-compose.yml index 2128798..3c22be6 100644 --- a/infra/gitea/docker-compose.yml +++ b/infra/gitea/docker-compose.yml @@ -1,7 +1,12 @@ services: gitea: - image: gitea/gitea:latest + image: gitea/gitea:1.25 restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" depends_on: db: condition: service_healthy @@ -26,6 +31,11 @@ services: db: image: postgres:16-alpine restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" volumes: - gitea_db:/var/lib/postgresql/data environment: diff --git a/infra/miniflux/docker-compose.yml b/infra/miniflux/docker-compose.yml index 61f4df7..10d6400 100644 --- a/infra/miniflux/docker-compose.yml +++ b/infra/miniflux/docker-compose.yml @@ -1,7 +1,12 @@ services: miniflux: - image: miniflux/miniflux:latest + image: miniflux/miniflux:2.2.19 restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" depends_on: db: condition: service_healthy @@ -20,6 +25,11 @@ services: db: image: postgres:16-alpine restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" volumes: - miniflux_db:/var/lib/postgresql/data environment: