From 18594c772ab1b1d330e43e15874c265812a4e657 Mon Sep 17 00:00:00 2001 From: Michael Reber Date: Wed, 21 Jan 2026 12:25:17 +0100 Subject: [PATCH] Fix ssh dev-container to use the official hooks from linuxserver.io --- .../ssh_and_local/container-compose.yml | 70 +++---------------- .../99-ssh-acl | 68 ++++++++++++++++++ .../fail2ban-ssh-custom-services.d/sshd | 2 + 3 files changed, 78 insertions(+), 62 deletions(-) create mode 100755 development/ssh_and_local/fail2ban-ssh-custom-cont-init.d/99-ssh-acl create mode 100755 development/ssh_and_local/fail2ban-ssh-custom-services.d/sshd diff --git a/development/ssh_and_local/container-compose.yml b/development/ssh_and_local/container-compose.yml index f1b3439..b63c99d 100644 --- a/development/ssh_and_local/container-compose.yml +++ b/development/ssh_and_local/container-compose.yml @@ -59,79 +59,25 @@ services: cap_add: - NET_ADMIN - NET_RAW - - SYS_ADMIN network_mode: bridge ports: - - "2222:22" # SSH port mapping + - "2222:22" environment: - TZ=Europe/Zurich - VERBOSITY=-vv - - PUID=0 # Run as root for SSH setup + - PUID=0 - PGID=0 + # Install needed packages at startup (non-persistent, but simplest) + - DOCKER_MODS=linuxserver/mods:universal-package-install + - INSTALL_PACKAGES=openssh-server|sudo|acl|bash volumes: - ./fail2ban-config-ssh:/config:z - /var/log:/var/log:ro - /var/log/httpd:/remotelogs/apache2:ro # Mount persistent SSH keys - shared between containers - ./ssh-keys:/mnt/ssh-keys:z - # We use entrypoint override to run SSH setup before the container's init - entrypoint: /bin/sh - command: > - -c " - apk update && apk add --no-cache openssh-server openssh-keygen sudo acl; - ssh-keygen -A; - useradd -m -s /bin/bash testuser 2>/dev/null || true; - passwd -d testuser 2>/dev/null || usermod -U testuser 2>/dev/null || true; - chage -E -1 testuser 2>/dev/null || true; - mkdir -p /home/testuser/.ssh; - if [ ! -f /mnt/ssh-keys/id_rsa ]; then - echo 'Generating new SSH key pair...'; - ssh-keygen -t rsa -b 4096 -m PEM -f /mnt/ssh-keys/id_rsa -N ''; - chmod 600 /mnt/ssh-keys/id_rsa; - chmod 644 /mnt/ssh-keys/id_rsa.pub; - echo 'SSH key pair generated in persistent volume'; - else - echo 'Using existing SSH key pair from persistent volume'; - fi; - cp /mnt/ssh-keys/id_rsa /home/testuser/.ssh/id_rsa; - cp /mnt/ssh-keys/id_rsa.pub /home/testuser/.ssh/id_rsa.pub; - cat /mnt/ssh-keys/id_rsa.pub > /home/testuser/.ssh/authorized_keys; - chmod 700 /home/testuser/.ssh; - chmod 600 /home/testuser/.ssh/id_rsa; - chmod 644 /home/testuser/.ssh/id_rsa.pub; - chmod 600 /home/testuser/.ssh/authorized_keys; - chown -R testuser:testuser /home/testuser/.ssh; - echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config; - echo 'PasswordAuthentication no' >> /etc/ssh/sshd_config; - echo 'PubkeyAuthentication yes' >> /etc/ssh/sshd_config; - echo 'LogLevel VERBOSE' >> /etc/ssh/sshd_config; - echo 'AuthorizedKeysFile .ssh/authorized_keys' >> /etc/ssh/sshd_config; - echo 'SyslogFacility AUTH' >> /etc/ssh/sshd_config; - mkdir -p /etc/sudoers.d; - echo 'testuser ALL=(ALL) NOPASSWD: /usr/bin/fail2ban-client *' > /etc/sudoers.d/fail2ban-ui; - echo 'testuser ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart fail2ban' >> /etc/sudoers.d/fail2ban-ui; - echo 'testuser ALL=(ALL) NOPASSWD: /usr/bin/systemctl reload fail2ban' >> /etc/sudoers.d/fail2ban-ui; - chmod 440 /etc/sudoers.d/fail2ban-ui; - mkdir -p /etc/fail2ban/jail.d /etc/fail2ban/filter.d /etc/fail2ban/action.d; - setfacl -Rm u:testuser:rwX /etc/fail2ban 2>/dev/null || true; - setfacl -dRm u:testuser:rwX /etc/fail2ban 2>/dev/null || true; - [ -d /etc/fail2ban/action.d ] && setfacl -m u:testuser:rwX /etc/fail2ban/action.d 2>/dev/null || true; - [ -d /etc/fail2ban/filter.d ] && setfacl -m u:testuser:rwX /etc/fail2ban/filter.d 2>/dev/null || true; - [ -d /etc/fail2ban/jail.d ] && setfacl -m u:testuser:rwX /etc/fail2ban/jail.d 2>/dev/null || true; - [ -d /config/fail2ban/action.d ] && setfacl -m u:testuser:rwX /config/fail2ban/action.d 2>/dev/null || true; - [ -d /config/fail2ban/filter.d ] && setfacl -m u:testuser:rwX /config/fail2ban/filter.d 2>/dev/null || true; - [ -d /config/fail2ban/jail.d ] && setfacl -m u:testuser:rwX /config/fail2ban/jail.d 2>/dev/null || true; - echo '========================================'; - echo 'SSH Test Container Ready'; - echo '========================================'; - echo 'Host: 127.0.0.1'; - echo 'Port: 2222'; - echo 'User: testuser'; - echo 'Key: /config/.ssh/id_rsa (in fail2ban-ui container)'; - echo 'Test command: podman exec -it DEV_fail2ban-ui ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes -i /config/.ssh/id_rsa -p 2222 testuser@127.0.0.1'; - echo '========================================'; - /usr/sbin/sshd -D -e & - exec /init - " + # New: custom init + service hooks (read-only is recommended) + - ./fail2ban-ssh-custom-cont-init.d:/custom-cont-init.d:ro,z + - ./fail2ban-ssh-custom-services.d:/custom-services.d:ro,z restart: unless-stopped diff --git a/development/ssh_and_local/fail2ban-ssh-custom-cont-init.d/99-ssh-acl b/development/ssh_and_local/fail2ban-ssh-custom-cont-init.d/99-ssh-acl new file mode 100755 index 0000000..335e5e2 --- /dev/null +++ b/development/ssh_and_local/fail2ban-ssh-custom-cont-init.d/99-ssh-acl @@ -0,0 +1,68 @@ +#!/usr/bin/with-contenv bash +set -euo pipefail + +echo "[custom-init] ssh + acl setup starting" + +# ---- user management (Alpine-friendly, idempotent) ---- +if ! id -u testuser >/dev/null 2>&1; then + # Alpine has adduser by default + adduser -D -s /bin/bash testuser + # UNLOCK the account for SSH publickey auth (BusyBox adduser -D sets '!' in /etc/shadow) + # Convert "testuser:!:" (locked) -> "testuser::" (no password, unlocked) + sed -i -e 's/^testuser:!:/testuser::/' -e 's/^testuser:\*:/testuser::/' /etc/shadow || true +fi + +# ---- SSH host keys ---- +ssh-keygen -A + +# ---- persistent client keypair in /mnt/ssh-keys ---- +mkdir -p /mnt/ssh-keys +if [ ! -f /mnt/ssh-keys/id_rsa ]; then + echo "[custom-init] generating new SSH key pair in /mnt/ssh-keys" + ssh-keygen -t rsa -b 4096 -m PEM -f /mnt/ssh-keys/id_rsa -N '' + chmod 600 /mnt/ssh-keys/id_rsa + chmod 644 /mnt/ssh-keys/id_rsa.pub +fi + +# ---- authorize key for testuser ---- +install -d -m 700 -o testuser -g testuser /home/testuser/.ssh +cat /mnt/ssh-keys/id_rsa.pub > /home/testuser/.ssh/authorized_keys +chown testuser:testuser /home/testuser/.ssh/authorized_keys +chmod 600 /home/testuser/.ssh/authorized_keys + +# ---- sshd_config (idempotent; avoid duplicate lines every restart) ---- +SSHD_CFG=/etc/ssh/sshd_config + +grep -qE '^PermitRootLogin\s+' "$SSHD_CFG" || echo 'PermitRootLogin no' >> "$SSHD_CFG" +grep -qE '^PasswordAuthentication\s+' "$SSHD_CFG" || echo 'PasswordAuthentication no' >> "$SSHD_CFG" +grep -qE '^KbdInteractiveAuthentication\s+' "$SSHD_CFG" || echo 'KbdInteractiveAuthentication no' >> "$SSHD_CFG" +grep -qE '^PermitEmptyPasswords\s+' "$SSHD_CFG" || echo 'PermitEmptyPasswords no' >> "$SSHD_CFG" +grep -qE '^PubkeyAuthentication\s+' "$SSHD_CFG" || echo 'PubkeyAuthentication yes' >> "$SSHD_CFG" +grep -qE '^LogLevel\s+' "$SSHD_CFG" || echo 'LogLevel VERBOSE' >> "$SSHD_CFG" +grep -qE '^AuthorizedKeysFile\s+' "$SSHD_CFG" || echo 'AuthorizedKeysFile .ssh/authorized_keys' >> "$SSHD_CFG" + +# ---- sudoers: use fail2ban-client (systemctl isn't present in LSIO containers) ---- +# (LSIO support guidance is to control fail2ban via fail2ban-client, not systemctl/service) :contentReference[oaicite:5]{index=5} +install -d -m 0750 /etc/sudoers.d +cat > /etc/sudoers.d/fail2ban-ui <<'EOF' +testuser ALL=(ALL) NOPASSWD: /usr/bin/fail2ban-client * +EOF +chmod 0440 /etc/sudoers.d/fail2ban-ui + +# ---- ACLs: run AFTER LSIO has created /config/fail2ban on first boot ---- +# The container creates /config content on first run. :contentReference[oaicite:6]{index=6} +mkdir -p /config/fail2ban/{action.d,filter.d,jail.d} + +# Apply both access ACL and default ACL (so new files/dirs inherit) +for d in /config/fail2ban /config/fail2ban/action.d /config/fail2ban/filter.d /config/fail2ban/jail.d; do + setfacl -m u:testuser:rwX,m::rwX "$d" + setfacl -d -m u:testuser:rwX,m::rwX "$d" +done + +# /etc/fail2ban is a symlink to /config/fail2ban in this image; setfacl follows symlinks by default. +if [ -e /etc/fail2ban ]; then + setfacl -m u:testuser:rwX,m::rwX /etc/fail2ban || true + setfacl -d -m u:testuser:rwX,m::rwX /etc/fail2ban || true +fi + +echo "[custom-init] ssh + acl setup complete" diff --git a/development/ssh_and_local/fail2ban-ssh-custom-services.d/sshd b/development/ssh_and_local/fail2ban-ssh-custom-services.d/sshd new file mode 100755 index 0000000..5fb6d9a --- /dev/null +++ b/development/ssh_and_local/fail2ban-ssh-custom-services.d/sshd @@ -0,0 +1,2 @@ +#!/usr/bin/with-contenv bash +exec /usr/sbin/sshd -D -e