一个使用 Terraform 部署在 DigitalOcean 云服务器上的 SSH/Telnet 蜜罐堆栈。攻击行为由 Cowrie 捕获,存储在 Loki 中,并在 Grafana 中通过攻击来源的实时世界地图进行可视化。
honeypot/
├── cowrie/
│ └── etc/
│ ├── cowrie.cfg # Cowrie honeypot configuration
│ └── userdb.txt # Accepted and rejected fake credentials
├── geoip/
│ └── .gitkeep # Placeholder - MMDB files are gitignored
├── grafana/
│ ├── provisioning/
│ │ ├── dashboards/
│ │ │ ├── dashboards.yml # Provisioning: dashboard provider
│ │ │ └── honeypot-dashboard.json # Pre-built Attack Monitor dashboard
│ │ └── datasources/
│ │ └── loki.yml # Auto-provisioned Loki datasource
│ └── grafana.ini # Grafana server settings
├── loki/
│ └── config.yml # Loki single-binary config and retention
├── promtail/
│ └── config.yml # Promtail scrape and GeoIP pipeline
├── scripts/
│ ├── geoip-update.sh # Download or refresh GeoIP database
│ └── setup-firewall.sh # Host UFW rules for honeypot ports
├── terraform/
│ ├── templates/
│ │ ├── cloud-init.yaml.tftpl # Droplet first-boot script (Terraform-templated)
│ │ └── env.tftpl # .env lines embedded via Terraform
│ ├── backend.tf # Terraform backend config
│ ├── main.tf # Droplet, SSH key, firewall, cloud-init
│ ├── outputs.tf # IPs and helpful post-apply values
│ ├── terraform.tfvars.example # Example variable values (copy to terraform.tfvars)
│ ├── variables.tf # Terraform input variables
│ └── versions.tf # Terraform and provider version constraints
├── .env.example # Example environment file for manual setup
├── .gitignore # Ignored paths and files
├── .gitlab-ci.yml # CI/CD pipeline: fmt, validate, Checkov
├── docker-compose.yml # Docker Compose file for the honeypot stack
├── LICENSE # GPLv2 license
├── manual-deployment.sh # Legacy VM bootstrap without Terraform
└── README.md # Project documentation
## 硬件需求
> 注意:这些要求基于 DigitalOcean 截至 2026 年 5 月的 Basic Droplet 方案,是运行该项目的最低要求。
- 提供商:DigitalOcean
- 方案:Basic Droplet - 1 个 Intel vCPU
- 内存:1 GB(+2 GB 交换空间)
- 存储:35 GB NVMe SSD
- 操作系统:Ubuntu 24.04 LTS
## 安装与设置
>注意:安装后,建议打开一个新终端,验证在关闭原始会话之前仍能通过 `ADMIN_SSH_PORT`(默认:2022)进行 SSH 连接。
### Terraform 部署(推荐)
1. 创建一个具有读写权限的 DigitalOcean API 令牌:
- 登录 DigitalOcean,导航至 Account > API > Tokens > Generate New Token。
- 为令牌命名(例如 "Cowrie Honeypot")并选择 "Full Access" 权限。
- 点击 "Generate Token",将令牌值复制到安全位置(之后将无法再次查看)。
2. 在本地机器上创建一个 SSH 密钥对,并复制公钥路径。
3. 进入 `terraform` 目录,然后复制并编辑变量文件:
> 注意:在应用之前,`terraform.tfvars` 中至少必须配置 `do_token`、`ssh_public_key_path` 和 `grafana_admin_password`。```bash
cd terraform
cp terraform.tfvars.example terraform.tfvars
注意:本地机器上必须安装 Terraform。请访问 Terraform 安装指南 获取说明。```bash terraform init # Initialize Terraform and download providers terraform apply
5. 如果测试 - 使用替换重新部署:```bash
terraform apply -replace="digitalocean_droplet.honeypot"
### 手动部署(旧版)
1. 创建一个满足上述硬件要求并包含你的 SSH 密钥的 DigitalOcean droplet。
2. SSH 登录到该 droplet 或使用 DigitalOcean 网页控制台,然后运行以下命令:```bash
ssh root@<your-droplet-ip> # If using DigitalOcean web console, skip this command
git clone https://gitlab.com/Oseguera12/cowrie-honeypot-digitalocean.git /opt/honeypot
cd /opt/honeypot
cp .env.example .env
nano .env
bash manual-deployment.sh
注意:默认情况下,Terraform 将状态写入本地机器的
terraform/terraform.tfstate。该文件包含敏感的输出值(Droplet IP、Grafana 密码、SSH 密钥指纹),绝不能提交到版本控制中。.gitignore已涵盖*.tfstate和*.tfstate.*。
本地状态的风险:
apply 运行可能损坏文件对于任何超出个人实验的范围,请切换到远程后端。terraform/backend.tf 包含一个注释掉的 DigitalOcean Spaces 配置(兼容 S3)。
启用远程后端的方法:
terraform.tfvars 中): ```bash
export AWS_ACCESS_KEY_ID=
export AWS_SECRET_ACCESS_KEY=
terraform/backend.tf 中取消注释 backend "s3" 块,并填写你的存储桶名称和区域端点。terraform init -migrate-state 将现有的本地状态迁移到 Spaces。.gitlab-ci.yml 在每次推送时,在单一的 validate 阶段中运行三个任务:
注意:
terraform:fmt和terraform:validate在失败时会阻塞流水线。checkov:scan设置为allow_failure: true,因为某些发现是有意为之的权衡。对于可接受的特定发现,请使用内联的# checkov:skip=CKXXX注释来抑制,而不是完全禁用该任务。
CKV_DIO_4(“确保防火墙入站规则不是完全开放”)针对整个 digitalocean_firewall 资源触发一次。通过在 terraform/main.tf 的资源块内放置一个 # checkov:skip=CKV_DIO_4: 注释来抑制它。下表记录了每个开放端口为何是有意为之。
flowchart TB internet((Internet))
subgraph tf["Terraform"] fw[DigitalOcean Cloud Firewall] droplet[Ubuntu 24.04 droplet] fw --> droplet end
subgraph compose["Docker Compose on droplet"] cowrie["Cowrie honeypot — SSH on port 22, Telnet on port 23"] promtail[Promtail with GeoIP labels] loki[Loki log store] grafana["Grafana — host :3000"] cowrie -->|JSON logs from ./data/cowrie-logs| promtail --> loki --> grafana end
internet -->|22/23 honeypot| fw internet -->|Admin SSH :2022| fw internet -->|Grafana :3000| fw droplet --> compose
## Observations
Data captured over 5 days of live deployment (2026-05-05 to 2026-05-09):
| Metric | Value |
|---------------------------|--------|
| Total connections | 41,700 |
| Login attempts | 15,400 |
| Successful logins | 868 |
| Commands executed | 836 |
| Files downloaded | 6 |
| Unique source countries | 106 |
### Top attacking countries
| Country | Connections |
|-----------------|-------------|
| Germany | 9,768 |
| Netherlands | 9,110 |
| United States | 7,706 |
| United Kingdom | 3,858 |
| Singapore | 1,770 |
| Belgium | 1,422 |
### Credential patterns
The most common username attempted was `root` with 3,866 attempts, followed by
`admin` (728) and `user` (494), reflecting automated scanners targeting
default credentials and known service accounts. The most common password was
`123456` (1,480 attempts) followed by `123` and `12345`, consistent with
dictionary-based brute-force tooling.
### Attacker behavior inside the fake shell
868 login attempts succeeded against the fake credential set. Of those sessions,
836 commands were executed. The most common command was `uname -s -v -n -r -m`
(360 executions) — a standard system fingerprinting command run by automated
post-exploitation scripts to identify the target OS and architecture before
deploying a payload. Other observed commands included `export HISTFILE=/dev/null`
to disable shell history logging, and `export HISTSAVE=/dev/null`, indicating
attackers actively attempting to cover their tracks even inside what they believed
was a compromised system.
### Key takeaway
The volume of automated scanning traffic — 41,700 connections in 5 days from 106
countries — confirms that any publicly accessible SSH service faces constant
brute-force attempts within hours of exposure. The behavioral pattern of
immediately running `uname` followed by history-suppression commands is consistent
with automated post-exploitation frameworks operating with minimal human
involvement.
## Usage
### Using Grafana Dashboard
> Note: Dashboard Credentials are set in terraform.tfvars or .env (for manual deployment)
Dashboard can be accessed at:```
http://<your-droplet-ip>:3000
使用 admin / <GRAFANA_ADMIN_PASSWORD> 登录。
cowrie/etc/userdb.txt - 包含蜜罐可接受的凭证。
编辑此文件以调整蜜罐凭证
登录成功后攻击者进入伪 shell,所有命令均被记录
被拒绝的条目记录为失败尝试
本节详细介绍了开发本项目期间所做的决策和遇到的问题。它反映了项目的开发过程和经验教训。如果只想了解如何使用该项目,请跳至安全注意事项。
为何选择云托管?
为何选择 DigitalOcean?
为何选择 Terraform?
manual-deployment.sh 适用于不熟悉 Terraform 或不愿使用它的用户。该路径需要预配 Droplet、连接、克隆仓库并运行脚本。Terraform 允许更快迭代:编辑代码库并重新部署,而无需从本地机器重复每个手动步骤。它还简化了基础设施的拆除和重新开始。如果项目从单个蜜罐扩展到蜜网,Terraform 的扩展性优于手动配置或仅使用旧脚本。为何选择 Cowrie?
为何选择 Loki?
为何选择 Grafana?
为何选择 Promtail?
为何选择 GeoIP?
scripts/geoip-update.sh 使用 DB-IP City Lite:无需账户或 API 密钥,从而简化了任何克隆仓库用户的部署。Promtail 内置的 GeoIP 阶段读取本地 MMDB 文件并为 Grafana Geomap 面板添加标签。使用 Terraform 或手动部署脚本设置蜜罐时发生的步骤概述。
在您的机器上:
terraform/terraform.tfvars:API 令牌、SSH 公钥路径、Grafana 密码、仓库 URL/分支、可选 manage_do_firewallterraform init:本地安装 DigitalOcean 提供商terraform apply:Terraform 构建计划,然后创建或更新资源在 DigitalOcean 中:
droplet_image、droplet_size、region 等user_data 设置为渲染后的 cloud-init:首次启动时,VM 获得管理 SSH 端口、git repo_url / repo_branch 以及生成的 .env 的 base64 编码副本(来自 env.tftpl 的 Grafana 密码和 ADMIN_SSH_PORT)manage_do_firewall 为 true 时):
terraform/main.tf)在 Droplet 上,cloud-init 自动运行:
package_update / package_upgrade,然后安装列出的软件包(curl、git、ufw 等)。引导早期,write_files 创建 /root/honeypot.env(Terraform 渲染的 env.tftpl 作为 base64:Grafana 密码、ADMIN_SSH_PORT 等)runcmd 引导脚本(在 terraform/templates/cloud-init.yaml.tftpl 中的顺序):将 iptables / ip6tables 设置为传统后端/swapfile、fstab、swappiness)在 Docker Compose 内部:
/ready 端口 3100;主机上仅 127.0.0.1:3100)docker-compose.yml 中的 depends_on),然后启动data/cowrie-logs 供 Promtail 使用用户创建 VM,SSH 登录,然后从克隆的仓库运行脚本。
manual-deployment.sh 执行以下操作:
.envapt update & apt upgradeufw 和 iptablessshd 在 ADMIN_SSH_PORT 并禁用 ssh.socketscripts/setup-firewall.shscripts/geoip-update.sh 和每月 crondocker compose pull / up -d,然后脚本等待 Loki 和 Grafana 健康检查并打印摘要考虑的解决方案:
解决方法:
权衡:
经验教训:
docker compose up 失败,因为端口 22 已被占用,无法供 Cowrie 绑定。考虑的解决方案:
sshd 配置错误或监听在错误的端口解决方法:
sshd 移动到 ADMIN_SSH_PORT(默认 2022)并重启,然后停止并禁用 ssh.socket 以释放端口 22 供 Docker 绑定给 Cowrie。两步完成后应用 UFW 规则以允许新管理端口。权衡:
经验教训:
ssh.socket 是一个 systemd socket 单元,它保留端口 22 用于按需 SSH 激活。必须在 Docker 绑定主机端口 22 给 Cowrie 之前停止并禁用它。如果它仍处于活动状态,docker compose up 将以"地址已在使用中"失败。GeoIP 功能
Grafana HTTPS
代理服务器功能
GNU 通用公共许可证版本 2.0(GPLv2) 请参阅 LICENSE 文件中的完整许可证文本。
| 任务 | 工具 | 用途 |
|---|
terraform:fmt | hashicorp/terraform:1.8 | 格式化:如果任何文件需要 terraform fmt 则失败 |
terraform:validate | hashicorp/terraform:1.8 | 配置有效性(不联系 DigitalOcean) |
checkov:scan | bridgecrew/checkov:latest | 检查 Terraform 代码中的 IaC 配置错误 |
| 检查 ID | 资源 | 发现 | 决定 |
|---|
CKV_DIO_4 | digitalocean_firewall.honeypot — 管理 SSH 入站 | 管理 SSH 端口对 0.0.0.0/0 开放 | 接受 — 为便携式实验室固定 IP 不现实;建议在生产环境中限制 |
CKV_DIO_4 | digitalocean_firewall.honeypot — 端口 22 入站 | 端口 22 对 0.0.0.0/0 开放 | 有意为之 — 这是 SSH 蜜罐暴露面;限制来源会破坏目的 |
CKV_DIO_4 | digitalocean_firewall.honeypot — 端口 23 入站 | 端口 23 对 0.0.0.0/0 开放 | 有意为之 — Telnet 蜜罐暴露面;理由同端口 22 |
CKV_DIO_4 | digitalocean_firewall.honeypot — 端口 3000 入站 | Grafana HTTP 暴露给 0.0.0.0/0 | 为实验室可访问性接受 — 已知限制已在安全注意事项中记录;生产部署应限制为已知 IP 或通过 HTTPS 在端口 443 上进行代理 |
| 面板 | 描述 |
|---|
| 攻击世界地图 | 地理地图,包含热力图+标记层,显示每个连接的来源位置 |
| 总连接数 | 选定时间范围内入站蜜罐会话的数量 |
| 登录尝试 | 凭证暴力破解尝试总数 |
| 成功登录 | 攻击者匹配 userdb.txt 凭证的次数 |
| 执行命令 | 在伪 shell 中执行的 shell 命令 |
| 下载文件 | 通过 wget/curl 捕获的恶意软件/脚本 |
| 连接速率 | 时间序列:连接/秒、失败登录/秒、成功登录/秒 |
| 热门用户名 | 尝试次数最多的 SSH 用户名 |
| 热门密码 | 尝试次数最多的密码 |
| 热门命令 | 执行次数最多的 shell 命令 |
| 按国家统计的攻击 | 国家级别连接表 |
| 近期事件 | 最新事件的实时日志流 |
| 文件下载 | 攻击者尝试拉取的每个文件的表格 |
systemctl enable --now dockersshd 在 ADMIN_SSH_PORT 上;禁用 ssh.socket 以释放主机端口 22 供 Cowrie 使用repo_url / repo_branch 执行 git clone 到 /opt/honeypot(必须包含 docker-compose.yml 和配置文件).env:将 /root/honeypot.env 移到 /opt/honeypot/.env(模式 600)scripts/setup-firewall.sh(UFW 用于管理 SSH、22、23、3000)scripts/geoip-update.sh(DB-IP City Lite 放入 geoip/);为同一脚本添加每月 cron 行data/cowrie-logs 和 data/cowrie-dl,权限符合 Compose 预期/opt/honeypot 执行 docker compose pull 然后 docker compose up -d