| title | Add an SCP repository | ||||
|---|---|---|---|---|---|
| description | URL form, key-based authentication, and known_hosts setup for an SCP-backed pg_hardstorage repository. | ||||
| tags |
|
The
scp://scheme stores chunks on a remote Unix host over plain SSH command-exec —cat,stat,find,mv,rm,mkdir. Useful when corporate policy disables the SFTP subsystem (Subsystem sftpcommented out in sshd_config) but allows ssh-exec, or against embedded / appliance SSH servers that don't implement SFTP at all.
Both ride SSH; both share the same auth + known_hosts model.
Default to sftp:// unless you can't,
because SFTP is a stateful protocol (one session, many ops)
and avoids forking a remote shell per operation. Pick
scp:// when:
- The remote SSH server has the SFTP subsystem disabled.
- The remote is an embedded / appliance device that doesn't implement SFTP.
- Compliance review insists on the smallest SSH surface enabled for backups.
scp://[user@]host[:port]/<absolute-path>
Examples:
scp://backup@nas.example.com/srv/pg-hardstorage
scp://nas.example.com:2222/data/backups
If user is omitted the plugin uses $USER. The path
component is the absolute root directory on the remote
side; the plugin will not create parent directories above
this point.
Authentication is configured via extras (per-repo settings
the StoragePlugin reads from the deployment's extras map),
not URL query parameters — secrets in URLs leak through
process listings and shell history.
| Extras key | Meaning |
|---|---|
identity_file |
Path to a private key (ed25519, rsa). |
identity_passphrase |
Passphrase for the private key. Empty for unencrypted keys. |
known_hosts |
Path to a known_hosts file. Required. |
password |
Password auth. Discouraged; prefer keys. |
- An SSH-reachable host with an account dedicated to backups.
- An ed25519 keypair generated on the agent host
(
ssh-keygen -t ed25519 -f /etc/pg_hardstorage/keys/scp_id_ed25519). - The remote host's host-key fingerprint captured into a
known_hostsfile. - A directory that the backup user owns and can write to.
- The remote shell environment must have
cat,stat,find,mv,rm,mkdiron PATH. Every standard Unix shell + busybox satisfies this.
ssh-keygen -t ed25519 \
-f /etc/pg_hardstorage/keys/scp_id_ed25519 \
-N '' -C 'pg_hardstorage@$(hostname)'
chmod 600 /etc/pg_hardstorage/keys/scp_id_ed25519Copy the public key onto the remote host and append to the
backup user's ~/.ssh/authorized_keys:
ssh-copy-id -i /etc/pg_hardstorage/keys/scp_id_ed25519.pub \
backup@nas.example.comLock down the account on the remote host: chroot the user to
the backup directory if your sshd supports it, disable shell
login features the backup doesn't need
(AllowAgentForwarding no, PermitTTY no,
X11Forwarding no for that user in
/etc/ssh/sshd_config).
ssh-keyscan -t ed25519,rsa nas.example.com \
> /etc/pg_hardstorage/keys/known_hosts
chmod 644 /etc/pg_hardstorage/keys/known_hostsVerify the fingerprint out-of-band against the remote
server's /etc/ssh/ssh_host_*_key.pub files; an unverified
ssh-keyscan trusts whatever the network returned.
pg_hardstorage repo init scp://backup@nas.example.com/srv/pg-hardstoragerepo: scp://backup@nas.example.com/srv/pg-hardstorage
mode: okThe plugin reads identity_file, identity_passphrase, and
known_hosts from the deployment's extras map. Configure
them in pg_hardstorage.yaml:
deployments:
db1:
repo: scp://backup@nas.example.com/srv/pg-hardstorage
extras:
identity_file: /etc/pg_hardstorage/keys/scp_id_ed25519
known_hosts: /etc/pg_hardstorage/keys/known_hostspg_hardstorage repo check scp://backup@nas.example.com/srv/pg-hardstorageSame reason as the SFTP backend: silently trusting unknown
host keys is the single most common SSH misconfiguration in
audited environments — a network attacker who can MITM the
SSH handshake gets to MITM every backup write and read. The
plugin requires a real known_hosts file. CI sandboxes
that genuinely don't have a stable host key should write a
per-test known_hosts from the test harness.
The scp:// backend does not speak the legacy SCP wire
protocol (the binary C0644 size name framing). The scp
wire format has a documented security history
(CVE-2018-20685, CVE-2019-6111, CVE-2019-6109) and is being
deprecated by OpenSSH itself in favour of SFTP. The scp
binary shipped on modern systems already uses SFTP under
the hood by default.
Instead, this plugin uses ssh-exec with stdin / stdout
streaming for data (cat > path / cat path) and shell
commands for filesystem ops — the same posture
paramiko-scp, Ansible's synchronize module, and ad-hoc
rsync wrappers all use. It works against any SSH server
that allows command execution.
Same approach as the SFTP backend. The plugin emulates
IfNotExists with stat → write-to-temp → mv -T (POSIX
rename(2)). mv -T is atomic on the same filesystem on
every POSIX system, the standard guarantee for our
content-addressed chunk store. The TOCTOU window between
the stat and the rename is the same as sftp://'s and is
absorbed by content-addressing: a duplicate write of an
already-present chunk is a no-op. Manifest commits go
through RenameIfNotExists which has the same posture and
is the actual race winner.
Every key flows through a single shell-quoting helper —
single-quote-wrapped with embedded-quote escape via '\''.
POSIX-portable; the remote shell does no expansion on
quoted strings ($vars, backticks, glob, command
substitution all neutered). The repo's keys are
content-addressed paths (chunks/sha256/aa/bb/...) plus
manifest paths so the surface is structurally narrow, but
the quoting defends against future schema changes.
extras.known_hosts is required — the plugin refuses to
proceed without one. See step 3.
ssh: handshake failed: ssh: unable to authenticate —
the key wasn't authorised on the remote side, or the
passphrase is wrong, or the remote sshd rejects ed25519
(very old hosts). Try:
ssh -i /etc/pg_hardstorage/keys/scp_id_ed25519 backup@nas.example.comssh: handshake failed: knownhosts: key mismatch — the
remote host key changed (legitimate re-key, or attack).
Confirm out-of-band, then update known_hosts deliberately.
stat: command not found or similar — extremely rare,
but minimal embedded SSH servers may ship without coreutils.
The plugin needs cat, stat, find, mv, rm,
mkdir. If those aren't available, you're on a setup the
backup tool can't drive over plain SSH; switch to
sftp:// or pull the data through a
sidecar that does have a normal toolchain.
Permission denied on the path — the backup user must
own the path and have rwx on it. Check
ls -ld /srv/pg-hardstorage on the remote.
scp:// opens one SSH session per operation (per Stat,
per Get, per Put, etc.) versus sftp://'s single session
across the run. For small chunk-by-chunk operations the
session-setup overhead is real (~5-15 ms each on a typical
LAN). For repos with many small files (heavy CAS-dedup,
millions of chunks) prefer sftp://. For repos with
fewer larger objects (snapshot-style backup, small WAL
volume), the difference is negligible.
- Add a deployment wired to this repo
- Set retention
repo initCLI referencesftp://repository — the default-recommended SSH-based backend