Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ssh-canary

A planted SSH private key that nobody legitimate ever uses. If it is ever used, you get an email naming which planted copy fired, where it was used from, and what the intruder asked for — while they get nothing at all.

This is a tripwire, not a honeypot. It grants no shell, runs nothing the client asks for, and exposes no service to play with. Its whole job is to tell you that a secret you believed was private has been found.

The threat it addresses

You are not defending the canary host. You are finding out that someone has been through a different machine — a laptop, a backup share, a CI secret store — and picked up credentials they should never have had. The bait lives there; this repo is the trap on the other end.

How it works

Two independent detectors feed one alert path:

Detector Fires on Blind to
Forced command (command= in authorized_keys) Any client that opens a session channel: ssh host, ssh host cmd, sftp A client that only authenticates — ssh -N, connect-and-drop
Auth-log watcher (systemd service) Every successful authentication as the canary account Nothing, while the service runs

Both call the same alert code, which rate-limits per label — so a normal login trips both detectors but still produces exactly one email.

They fail differently on purpose. The forced command depends on sshd's command= mechanism; the watcher depends on parsing sshd's log output. A change that silences one leaves the other working.

intruder                     canary host
   |                              |
   |-- ssh -i stolen_key -------->|  sshd: publickey accepted
   |                              |    |
   |                              |    +--> auth-log watcher ---+
   |                              |                             |
   |                              |  $SHELL -c trigger.sh LABEL |
   |                              |    |                        |
   |                              |    +--> canary_alert.py <---+
   |                              |            |         (one mail, rate-limited)
   |<-- nothing. session hangs. --|            +--> syslog

Requirements

Canary host

  • Linux with systemd and journald — the watcher reads journalctl -u <unit> -f
  • OpenSSH 7.2+ (for restrict in authorized_keys)
  • Python 3.7+, standard library only. No venv, no packages.
  • An MTA or SMTP relay it can reach

Developed and tested on Ubuntu 24.04 with OpenSSH 9.6p1 and Python 3.12. See docs/design.md for exactly which assumptions are load-bearing and which are cosmetic.

Your machine

  • bash, ssh, scp, ssh-keygen
  • root (or passwordless sudo to root) on the canary host

Quick start

cp canary.conf.example canary.conf
$EDITOR canary.conf              # host, account, notification address, SMTP

Create the account on the canary host once — see docs/setup.md. Then:

bin/new-key.sh workstation-anna  # generate a labeled bait key
bin/deploy.sh                    # install and arm it on the host
bin/selftest.sh workstation-anna # prove the whole chain works
bin/plant-key.sh workstation-anna ~/somewhere/.ssh

bin/deploy.sh is idempotent — re-run it after any change. It refuses to proceed if the account is misconfigured in a way that would leave you with a canary that looks installed but never fires.

Layout

canary.conf.example   every site-specific value; copy to canary.conf
bin/
  new-key.sh          generate a labeled keypair
  deploy.sh           stage + install on the canary host
  install-server.sh   server-side installer (invoked by deploy.sh)
  plant-key.sh        place a bait key and print its ssh_config stanza
  selftest.sh         end-to-end regression test
server/
  trigger.sh          the forced command
  canary_alert.py     rate-limiting, mail, syslog
  canary_authwatch.py the auth-log watcher
  canary-authwatch.service
keys/                 generated keys and derived files — gitignored

Testing

bin/selftest.sh uses a real bait key against the real host and checks that both detectors reported it. Run it after every deploy, and periodically — it is the only thing that catches a silently broken detector.

The installer also sets up a monthly cron on the host that mails a selftest alert. That proves the alert path (config readable, SMTP reachable); it proves nothing about detection. Both matter.

Documentation

  • docs/setup.md — one-time host setup, and what the installer does
  • docs/planting.md — the bait side: making a key findable, plausible, and safe from false alarms
  • docs/design.md — why no real login is possible, what is deliberately not done, portability limits

Related projects

The idea is not new, and this is not the first implementation of it. If one of these fits your situation better, use it.

  • m4rkw/canary-ssh — the closest prior art, and the same core idea: plant SSH keys, watch sshd's log for their fingerprints, mail on a hit. It needs no server-side account and no authorized_keys entry at all, because it alerts on keys that are rejected — which in turn requires sshd DEBUG logging, since the offered fingerprint is not in the default INFO line. Smaller and less invasive than this project; in exchange it has one detector, runs as root, and cannot tell you what the client asked for.

  • rondilley/sshcanary — despite the nearly identical name, a different category: a low-interaction honeypot with its own fake SSH daemon on its own port, which records the username/password pairs brute-force bots try. Useful for studying attacker behaviour. It fires constantly by design; this project should fire never.

  • thinkst/canarytokens — the reference implementation of the general concept, hosted at canarytokens.org or self-hostable. Many token types (documents, URLs, DNS, cloud credentials). Start here if you want breadth rather than one deep SSH-shaped tripwire.

  • GitGuardian/ggcanary — the same pattern applied to AWS credentials, deployed with Terraform. A good companion: leaked cloud keys and leaked SSH keys tend to leak together.

One property worth knowing when you choose: document-based canary tokens can be found offline — there are scanners for them. A canary SSH key cannot. It is a normal, valid Ed25519 keypair, indistinguishable from a real one until somebody connects with it, at which point you already know.

License

MIT — see LICENSE. Do what you like with it; keep the copyright notice.

Security notes

  • Never commit keys/. A published private key makes every planted copy worthless and lets anyone trigger your alerts. .gitignore covers it; check before you push.
  • canary.conf may hold SMTP credentials. Also gitignored.
  • The canary account must be used by nothing else, ever. The watcher treats every successful login as it as a hit.

About

Plant a decoy SSH key and get alerted the moment someone uses it.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages