SSH server + SFTP hardening

BOA hardens SSH and deploys supporting config from its conf-template set. This page is the operator reference for what BOA configures and what to know before editing. The templates live in aegir/conf/var/.

Important — SSH configs are edited in place, not template-deployed. BOA does not copy aegir/conf/var/sshd_config or aegir/conf/var/ssh_config over /etc/ssh/. Those template files exist in the source tree, but the live /etc/ssh/sshd_config and /etc/ssh/ssh_config are the OS-provided files that BOA edits in place (via sed/echo, see the reconciliation section below). The reconciliation logic — not the template — determines what ends up in the live files.

Files deployed by barracuda upgrade

Config files BOA copies verbatim (distinct from the in-place SSH editing above):

Template (in BOA source) Lands at Purpose
aegir/conf/var/sftp_config /etc/ssh/sftp_config (or MySecureShell config) SFTP subsystem config
aegir/conf/var/boa.bashrc.txt /root/.bashrc (snippet) Root shell rc with BOA paths
aegir/conf/var/rsyslog.conf /etc/rsyslog.conf syslog routing (SSH logs)
aegir/conf/var/mysql-notices.conf (various) Percona notice rules
aegir/conf/var/galera.cnf /etc/mysql/conf.d/ Galera config (legacy cluster era)
aegir/conf/var/named.conf.options /etc/bind/ (if BIND xtras installed) DNS server options
aegir/conf/var/get.htaccess.txt (download dir) Default .htaccess shipped to platforms

What BOA's sshd_config enforces

Key directives BOA sets (operator can override with care):

  • PermitRootLogin prohibit-password — root SSH only with an ed25519 key, never a password.
  • PasswordAuthentication yes — the global default in the conf template, for non-root accounts (tenants need it for oN.ftp SFTP). At upgrade system.sh.inc re-appends it as PasswordAuthentication no instead when /root/.ssh.auth.keys.only.cnf exists (key-only enforcement).
  • PubkeyAuthentication — left at the OpenSSH default (the template ships it commented as #PubkeyAuthentication yes, so pubkey auth is enabled by default but not explicitly written).
  • PermitEmptyPasswords no.
  • X11Forwarding no.
  • AllowTcpForwarding local — local TCP forwarding is permitted (remote / gateway forwarding is not). This is intentional: it restores SQL-client tunnels (Sequel Ace/Pro, TablePlus) over SSH that an earlier all-forwarding-disabled hardening pass had broken. system.sh.inc forcibly normalises any existing AllowTcpForwarding value back to local on every upgrade.
  • AllowAgentForwarding no.
  • MaxAuthTries 3 — fast LFD trigger.
  • LoginGraceTime 30 — quick disconnect on stalls.
  • KexAlgorithms + Ciphers + MACs + HostKey — a modern crypto set with legacy algorithms disabled. This is opt-in, not a default: the whole crypto block in system.sh.inc (_sshd_armour) is gated on _SSH_ARMOUR, which ships as _SSH_ARMOUR=NO. When _SSH_ARMOUR is NO or empty, BOA instead restores the vanilla sshd_config/ssh_config (from its .vanilla.* backups) and applies none of these crypto directives — so on a default host they are not set. Only when _SSH_ARMOUR=YES does BOA rewrite Protocol 2 / KexAlgorithms / Ciphers / MACs / HostKey (and regenerate host keys + moduli). The strip-and-append directives below (MaxAuthTries, AllowTcpForwarding, etc.) are separate and are applied unconditionally at upgrade.

There is no Match User oN.ftp block in BOA's sshd_config (the only Match example in the template is the stock commented-out #Match User anoncvs). Per-tenant SFTP chroot is not handled by OpenSSH internal-sftp — it is provided by the MySecureShell + Pure-FTPd layer described below.

What you should NOT change

  1. PermitRootLogin prohibit-password — switching to yes re-enables password root SSH and lets every brute-force attempt try. Keep key-only.
  2. The tenant SFTP chroot (MySecureShell + Pure-FTPd) — tenants depend on it. The chroot is enforced by the MySecureShell / Pure-FTPd layer (see below), not by an sshd_config Match block; do not disable or reconfigure that layer or tenants can escape their home.
  3. AllowUsers if present — a typo here locks you out.

What you can change safely

  • Port — change from 22 to a non-standard port. Reduces log noise from random scans (CSF still catches them; this is cosmetic noise reduction).
  • AllowGroups / AllowUsers — restrict SSH access to specific users. Be sure your operator account is included.

Note that ClientAliveInterval / ClientAliveCountMax are not safe to hand-tune: they are in the strip-and-append set below, so system.sh.inc forces them back to ClientAliveInterval 300 / ClientAliveCountMax 10000 on every upgrade (lib/functions/system.sh.inc). Any operator value for these does not survive.

After editing /etc/ssh/sshd_config:

# Validate config (catches typos that would prevent SSH start)
sshd -t

# Reload
service ssh reload

Always validate before reloading. If sshd -t errors, fix the typo. If you reload broken config, the running daemon keeps working but new connections fail.

How barracuda upgrade reconciles operator edits

At upgrade, system.sh.inc does not re-deploy the template file or use any marker-comment mechanism. Instead it edits the live /etc/ssh/sshd_config in place: it sed-strips a fixed set of directives by name (regardless of their value) and then re-appends each one with BOA's hardened value. There is no per-line preservation logic — whether your edit survives depends solely on whether the directive is in that fixed strip-and-append set.

The directives BOA strips and forcibly re-appends on every upgrade include: UseDNS, UsePAM, PrintMotd, ClientAliveInterval, ClientAliveCountMax, IgnoreUserKnownHosts, PasswordAuthentication, TCPKeepAlive, PermitEmptyPasswords, KbdInteractiveAuthentication, MaxAuthTries, LoginGraceTime, MaxStartups, X11Forwarding, AllowTcpForwarding, AllowAgentForwarding, and Subsystem. PermitRootLogin is separately forced to prohibit-password on hosted systems or when /root/.ssh.root.auth.keys.only.cnf is present. Any operator value for these is overwritten.

Operator changes that survive — but only incidentally, because these directives are simply not in the strip set (BOA neither preserves nor touches them):

  • Custom Port setting.
  • AllowUsers / AllowGroups lines.
  • Custom Match User blocks for non-tenant accounts.

Changes that do not survive (intentional — security baseline):

  • Switching PermitRootLogin to yes (forced back to prohibit-password).
  • Re-enabling all-direction TCP forwarding — AllowTcpForwarding is stripped and re-added as AllowTcpForwarding local on every upgrade.
  • Operator edits to any other directive in the strip-and-append set above (e.g. MaxAuthTries, X11Forwarding, PasswordAuthentication).

The tenant SFTP chroot is unaffected by this reconciliation — it lives in the MySecureShell / Pure-FTPd layer, not in sshd_config.

SFTP subsystem — MySecureShell + Pure-FTPd

For tenant SFTP, BOA layers MySecureShell on top of OpenSSH's sftp subsystem (the template ships Subsystem sftp /usr/lib/openssh/sftp-server -u 0002, the external sftp-server binary — not internal-sftp). The MySecureShell layer adds per-user bandwidth limits, stronger chroot enforcement, and per-user connection limits.

For FTPS (FTP over SSL), BOA runs Pure-FTPd with forced FTPS plus chroot. See aegir/conf/ftpd/.

Tenants get SFTP + FTPS access automatically as part of the oN.ftp account (see lshell + manage_ltd_users).

The jail's platforms/ tree — how SFTP reaches site files

Each oN.ftp home carries a platforms/ tree of symlinks — the jail's platform-side route to site files (a sibling clients symlink covers the per-client view):

/home/oN.ftp/platforms/<tree>/<platform> -> <platform docroot>/sites

<tree> is the codebase revision directory under the account's distro/; each tree also gets a keys symlink to the tree's own keys directory, which the platform loop skips by name.

Historically these symlinks were created only for Drupal-7-era codebases with a root-level sites/ dir. Composer D8+ codebases keep the docroot in a subdir (web/, docroot/ or html/), so every such platform was silently skipped — no symlink, no SFTP path to those sites' files — and a vestigial root-level sites/ (e.g. left behind by drupal_cms_installer) could be mis-linked. Both symlink writers — the install/upgrade path (lib/functions/satellite.sh.inc) and its nightly twin (aegir/tools/system/night/10-account.sh) — now resolve the real docroot via _detect_real_docroot(): it probes for index.php at the platform root, then under docroot/, html/, web/, in that order, testing with -f so a dangling-symlink decoy (e.g. web -> /etc) is rejected. The helper is defined in lib/functions/helper.sh.inc and deliberately duplicated into the deployed night/night.inc.sh, so the nightly tools carry their own copy (BOA separate-copies convention). The symlink is named after the platform dir and points at <real docroot>/sites; D7 platform behaviour is unchanged.

A complementary permission fix in the batch cleanup (_satellite_batch_cleanup, same file) now chowns <codebase>/{web,docroot,html}/sites to root:users in addition to the root-level sites — on both storage layouts — so jailed .ftp accounts (group users) can traverse the D8+ sites tree the new symlinks point at. Net effect: Composer D8+ platforms now appear in the jail's platforms/ tree, where previously they were simply missing; the symlinks are written on install/upgrade and refreshed by the nightly account pass.

SSH key management

For root operators

/root/.ssh/authorized_keys

ed25519 keys preferred. RSA acceptable. ECDSA discouraged. DSA explicitly disabled by BOA.

For tenants

/home/oN.ftp/.ssh/authorized_keys

Populated via SFTP or by editing the file directly. Aegir's UI does not expose key management; it is a tenant-shell operation.

Per-Aegir-Client subaccount keys

Extra-account subkeys go in the per-Client subaccount's ~/.ssh/authorized_keys. See shell extras.

SSH host-key changed on rebuild

If you rebuild the BOA host (fresh install on the same hostname), existing SSH clients see the host-key change and refuse to connect. For operator workstations, remove the old key:

ssh-keygen -R server.example.com

Then reconnect — your ~/.ssh/known_hosts gets the new fingerprint. The tenant-facing recovery is in Troubleshooting.

Logging

/var/log/auth.log              # SSH auth attempts + sessions
/var/log/messages              # General system
/var/log/csf.log               # CSF + LFD actions related to SSH

LFD watches auth.log and CSF acts on repeated SSH failures — see CSF + LFD firewall.

AppArmor profile for sshd

The usr.local.sbin.sshd AppArmor profile confines the OpenSSH daemon — see AppArmor confinement. Even a hypothetical sshd RCE is bounded by what the profile allows.

Related