A live SSH honeypot, visualized.

An SSH honeypot captures real attacker traffic. A serverless AWS pipeline ingests, classifies, and aggregates every login attempt. A React dashboard renders the result in near-real time — at dashboard.dram-soc.org.

dashboard.dram-soc.org live

Loading live data…

What this is

An SSH honeypot's traffic, plumbed end-to-end on AWS.

A Cowrie SSH honeypot accepts every login attempt the public internet throws at it and records the username, the password, the source IP, the timing, and any post-login activity. Honeypots see attacker traffic almost exclusively — most legitimate users never hit one — so the data is unusually clean signal.

The pipeline ships those events as gzipped JSON to S3. A Python Lambda validates each event with a strict Pydantic schema, classifies passwords against a bundled known-bad dictionary (ADR-005), attaches GeoIP enrichment, and writes a single-table DynamoDB row. A second Lambda consumes DynamoDB Streams to produce SUMMARY and RANK rollups idempotently.

A read-only API Lambda serves a TypeScript-strict React + Recharts SPA over CloudFront, behind Cloudflare's free WAF (ADR-007). The whole stack is Terraformed and runs at ~$2.60/month.

Architecture

Three paths: ingest, read, render.

System architecture: Cowrie SSH honeypot on Raspberry Pi → S3 raw bucket → ingest Lambda (password classifier, GeoIP) → DynamoDB single-table; aggregator Lambda consumes DDB Streams to write SUMMARY/RANK rollups; API Lambda serves read-only routes via API Gateway; S3 frontend bundle → CloudFront → Cloudflare proxy → browser.
Solid teal lines mark the ingest and origin-fetch paths; dashed teal marks the API read path the browser walks every 30 seconds. Two security boundaries are explicit: the password-classifier inside the ingest Lambda (ADR-005) splits dictionary attempts from raw plaintext; Cloudflare's proxy (ADR-007) is the public WAF, replacing AWS WAF entirely.

Tech stack

Python 3.13 TypeScript (strict) AWS Lambda DynamoDB API Gateway CloudFront S3 + OAC ACM CloudWatch Pydantic v2 boto3 Terraform React 18 Vite TanStack Query Recharts Tailwind CSS Cloudflare Cowrie

Engineering decisions

Four trade-offs the project owns explicitly.

ADR-005

Attempted-password dictionary filtering

Cowrie captures every plaintext password tried. Publishing them verbatim risks surfacing real victims' credentials. The ingest Lambda checks each attempt against a bundled known-bad dictionary; matches are public, non-matches are length-redacted. password_raw never leaves the ingest path.

Read full ADR →
ADR-007

Cloudflare proxy as edge WAF (no AWS WAF)

AWS WAF runs ~$5.40/mo on this volume — the largest line item on the bill. Cloudflare's free tier covers DDoS, managed WAF rules, bot management, and TLS at the edge. Decision: orange-cloud the dashboard behind Cloudflare; CloudFront origin sits behind it.

Read full ADR →
ADR-003

DynamoDB single-table design

Five distinct entity shapes — events, hourly aggregates, rank items, daily summaries, heartbeats — share one table with overloaded keys and three GSIs. Every access pattern resolves to a Query, never a Scan. Costs ~$0.55/mo; alternative (Aurora Serverless) would alone breach the cap.

Read full ADR →
ADR-009

Captured-malware policy: SHA + URL only

Cowrie can store the binaries attackers try to drop. The dashboard does not — only the SHA-256 and source URL are kept; the binary itself is dropped at the Pi. Eliminates the binary-custody liability from the cloud surface and keeps the AWS storage policy boring.

Read full ADR →