Skip to content

Latest commit

 

History

History
167 lines (127 loc) · 6.32 KB

File metadata and controls

167 lines (127 loc) · 6.32 KB

Host setup

bin/deploy.sh does almost everything. Two things it deliberately will not do, because both can lock you out of the machine: create the account, and edit sshd_config. Those are below. Everything after them is one command.

1. Configure

cp canary.conf.example canary.conf
$EDITOR canary.conf

Two choices worth thinking about before you continue:

The account name is part of the bait. It has to appear in whatever ~/.ssh/config or wrapper script leads an intruder to it, so it must fit the cover story of the key you plan to plant — svc-backup, deploy, gitlab-runner. Naming it canary gives the game away.

It must be an account nothing else ever uses. The watcher treats every successful login as this account as a hit, and that is what makes it reliable. Check the name is free, including distro system accounts — on Debian/Ubuntu, backup is already taken:

getent passwd svc-backup; getent group svc-backup   # both must be empty

2. Create the account

On the canary host, as root:

adduser --system --group --home /home/svc-backup --shell /bin/bash svc-backup

The shell must be a real shell. /usr/sbin/nologin breaks the canary completely. sshd runs a forced command= through the user's login shell ($SHELL -c "/opt/canary/trigger.sh <label>"), so with nologin the trigger never runs: nothing is logged, no mail is sent, and the client gets back This account is currently not available. — which both loses the alert and tells the intruder something is odd.

/bin/bash sounds like a step backwards; it is not, because the protection nologin was supposed to provide comes from elsewhere. Verify all three:

passwd -S svc-backup                          # L (locked) — no usable password
sshd -T | grep -i '^passwordauthentication'   # no
sudo -l -U svc-backup                         # not allowed to run sudo

adduser --system already leaves the password locked. With those three in place the only way into the account is the key — and every key carries restrict,command=. See design.md for the full argument.

The home directory and .ssh are taken over by root during deploy, so you do not need to set permissions here.

3. Restrict forwarding for the account (recommended)

restrict in authorized_keys already blocks port, agent and X11 forwarding. This is a second, independent layer for the case where that file is ever wrong. Append to the end of /etc/ssh/sshd_config:

Match User svc-backup
    DisableForwarding yes
    PermitTTY no

PermitTTY no means an interactive ssh <alias> prints PTY allocation request failed on the client before it hangs. That is intended, not a defect: it is what every restricted service account looks like — deploy keys, git-shell, rsync-only accounts — so it fits the cover story rather than breaking it. See design.md.

Three things about the rest:

  • DisableForwarding, not a list. It covers X11, agent, TCP, StreamLocal and tun in one directive, and keeps covering whatever OpenSSH adds later. Spelling the options out individually is how you end up with AllowStreamLocalForwarding left at its default yes.
  • End of the main file, not sshd_config.d/. A Match block runs until the next Match or end of file. Since Include sits near the top of sshd_config, a block placed in an included file swallows every global directive that follows it.
  • No ForceCommand here. It looks like the obvious belt-and-braces move, but ForceCommand in sshd_config overrides the per-key command= including its argument, so every key would collapse to the same unlabeled trigger and you would lose the one thing the alert is for.

Then, keeping your current session open:

sshd -t && systemctl reload ssh
sshd -T -C user=svc-backup,host=localhost,addr=127.0.0.1 | grep -i forwarding

disableforwarding yes is the line that matters. The individual allowtcpforwarding etc. still print yesDisableForwarding overrides them at runtime rather than rewriting the dumped config.

4. Deploy

bin/new-key.sh testkey     # a key you will not plant, for testing
bin/deploy.sh
bin/selftest.sh testkey

That is it. deploy.sh is idempotent; re-run it after editing any script, generating a key, or changing canary.conf.

What the installer does

For reference — you should not need to do any of this by hand.

Path Owner / mode Contents
/opt/canary/ root:root trigger.sh (755), canary_alert.py, canary_authwatch.py (644)
/etc/canary/canary.env root:<account> 640 runtime config generated from canary.conf
/var/lib/canary/ <account> 700 rate-limit stamps — the only path the account can write
~<account>/, ~<account>/.ssh/ root:root 755 so the account cannot replace its own authorized_keys
~<account>/.ssh/authorized_keys root:root 644 one restrict,command= line per key
/etc/systemd/system/canary-authwatch.service root:root 644 the watcher
crontab for <account> monthly alert-path selftest

It then verifies: the account can load the alert path and read its config; it cannot write trigger.sh, its own authorized_keys, or its own .ssh; sshd -t passes; the watcher is running; the cron exists. Any failure stops the deploy.

Operating it

# every hit, including ones whose mail was rate-limited
ssh <host> 'journalctl -t canary-ssh -n 50 --no-pager'

# is the watcher alive?
ssh <host> 'systemctl status canary-authwatch'

# add a key for another plant location
bin/new-key.sh backup-share && bin/deploy.sh

# prove the whole chain still works
bin/selftest.sh

Put bin/selftest.sh on a calendar reminder. The monthly cron on the host only proves that mail still goes out; it cannot notice a detector that has stopped detecting.

Removing it

ssh <host> 'systemctl disable --now canary-authwatch
            rm -f /etc/systemd/system/canary-authwatch.service
            systemctl daemon-reload
            rm -rf /opt/canary /etc/canary /var/lib/canary
            crontab -u svc-backup -r
            userdel -r svc-backup'

Then remove the Match User block from sshd_config, sshd -t, and reload. Collect the planted keys, or leave them — without the account they are inert.