Skip to content

longmun/aws-canary

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AWS Canary Token — a zero-cost honeytoken that catches stolen credentials

A decoy AWS access key with zero permissions. Plant it somewhere tempting — a fake ~/.aws/credentials, a repo .env, a CI secret. The moment anyone uses it, from any account or machine anywhere on Earth, you get a high-confidence alert. The key can do nothing; the use attempt itself is the intrusion signal.

Built entirely on AWS Always-Free tier. Idle cost: $0.

status cost

Why this works

Attackers who find credentials test them — reflexively, almost always with aws sts get-caller-identity as the first move. That reflex is the trap. A zero-permission key produces a CloudTrail record the instant it's used, even though it can't touch a single resource. You detect the intrusion at the attacker's very first step, before they can do anything.

This is the same primitive Thinkst Canary commercialized. This repo is a from-scratch, self-hostable implementation you fully control.

Architecture

Decoy IAM key (no perms)
   │  attacker authenticates with it (anywhere)
   ▼
CloudTrail  ── logs the call in YOUR account (global events on)
   ▼
EventBridge ── rule matches the canary's accessKeyId
   ▼
Lambda      ── extracts IP / UA / key / principal
   ▼
SNS email  +  DynamoDB trip log

Every component sits inside AWS Always-Free at demo volume: Lambda (1M req/mo), SNS (1k emails/mo), DynamoDB (25GB), CloudTrail (first trail mgmt-events free), IAM (free).

Prerequisites

  • Terraform installed (terraform -version)
  • AWS CLI installed and authenticated. If aws sts get-caller-identity doesn't return your account, run aws configure first (Access Key ID, Secret, region, output format).
  • An IAM user/role with enough permissions to create the stack. This deploys across several services, so a tightly-scoped user will fail mid-apply with AccessDenied and leave a half-built stack. You need create/delete rights on: iam (user + access key + role + role policy), cloudtrail, s3, events (EventBridge), lambda, sns, dynamodb. An admin user works; otherwise scope a policy to those actions. If apply fails partway, run ./destroy.sh to clean up before retrying.

Deploy

git clone https://github.com/longmun/aws-canary aws-canary && cd aws-canary
./deploy.sh you@example.com us-east-1

The script checks prereqs, runs Terraform, applies an S3 log-expiry lifecycle, and prints your decoy key. Then confirm the SNS email AWS sends you — no confirmation, no alerts.

Test it

AWS_ACCESS_KEY_ID=<canary_id> AWS_SECRET_ACCESS_KEY=<canary_secret> \
  aws sts get-caller-identity

Alert email arrives in ~1–5 min (CloudTrail delivery latency).

Tear down

./destroy.sh you@example.com us-east-1

Proof it works

A live trip, captured end to end:

ALERT published for <attacker_ip> using AKIA-XXXXXXXXXXXX

DynamoDB trip record:

field value
event GetCallerIdentity
ip <attacker_ip>
key AKIA-XXXXXXXXXXXX
time 2026-06-01T10:23:21Z

The alert email carried timestamp, API call, source IP, User-Agent, access key, and principal.

Lessons learned (the interesting part)

1. The User-Agent leaks more than the IP. My test trip's alert exposed the full caller environment — aws-cli/2.x … kali-amd64 … python/3.x … command#sts.get-caller-identity. For a defender that's gold: UA is harder to spoof than an IP and attackers rarely bother. For a red-teamer, it's a warning — your reflexive get-caller-identity from a stock Kali box brands you instantly to any canary. The tool that catches attackers also documents the exact mistake attackers make.

2. Detection ≠ prevention. CloudTrail's 1–5 min lag means by the time you're alerted, the key's already been used. This is a tripwire, not a blocker. Know its job: it tells you you're breached.

3. The IP is a lead, not proof. My test logged my own egress IP. A real attacker uses the key from behind a VPN/Tor/throwaway cloud box. Weight the "key was used at all" signal far above the source IP for attribution.

4. Terraform state holds secrets in plaintext. terraform.tfstate contains the canary's secret key. The .gitignore here blocks it — the most common credential-leak-on-GitHub mistake.

5. Cost guardrails are not circuit breakers. An AWS budget alert lags actual spend by hours and only notifies; it never prevents a charge. The only true hard stop is tearing resources down.

Cost safety

  • All serverless → no idle cost
  • 30-day S3 lifecycle caps log growth (auto-applied by deploy.sh)
  • A zero-spend budget alert is recommended (see RUNBOOK)

Repo layout

deploy.sh            one-shot deploy (prereq checks + terraform + lifecycle)
destroy.sh           teardown
terraform/main.tf    all infra (IAM, CloudTrail, EventBridge, Lambda, SNS, DynamoDB)
lambda/handler.py     alert handler (SNS publish + DynamoDB log)
docs/RUNBOOK.md       operational runbook

License

MIT — do whatever, no warranty. This is a learning/portfolio project.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors