
Honeypot de interação média SSH/Telnet construído com Cowrie, Loki, Promtail e Grafana - provisionado na DigitalOcean via Terraform com um pipeline de validação GitLab CI.
Uma pilha de honeypot SSH/Telnet implantada em um droplet da DigitalOcean usando Terraform. Os ataques são capturados pelo Cowrie, armazenados no Loki e visualizados no Grafana com um mapa-múndi ao vivo das origens dos ataques.
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
## Requisitos de Hardware
> Nota: Estes requisitos são baseados no plano Basic Droplet da DigitalOcean a partir de maio de 2026 e são os requisitos mínimos para executar o projeto.
- Provedor: DigitalOcean
- Plano: Basic Droplet - 1 vCPU Intel
- RAM: 1 GB (+2 GB de swap)
- Armazenamento: 35 GB SSD NVMe
- SO: Ubuntu 24.04 LTS
## Instalação e Configuração
>Nota: Após a instalação, é recomendado abrir um novo terminal e verificar se ainda é possível fazer SSH na porta `ADMIN_SSH_PORT` (Padrão: 2022) antes de fechar sua sessão original.
### Implantação com Terraform (Recomendado)
1. Crie um token da API do DigitalOcean com permissões de leitura/escrita:
- Faça login no DigitalOcean e navegue até Account > API > Tokens > Generate New Token.
- Nomeie seu token (exemplo: "Cowrie Honeypot") e selecione permissões "Full Access".
- Clique em "Generate Token" e copie o valor do token para um local seguro (não será possível vê-lo novamente).
2. Crie um par de chaves SSH em sua máquina local e copie o caminho da chave pública.
3. Vá para o diretório `terraform` e copie e edite o arquivo de variáveis:
> Nota: `do_token`, `ssh_public_key_path` e `grafana_admin_password` devem ser configurados no mínimo em `terraform.tfvars` antes de aplicar.```bash
cd terraform
cp terraform.tfvars.example terraform.tfvars
Nota: O Terraform deve estar instalado na sua máquina local. Visite Guia de Instalação do Terraform para obter instruções.```bash terraform init # Initialize Terraform and download providers terraform apply
5. Se Teste - Reimplantar com substituição:```bash
terraform apply -replace="digitalocean_droplet.honeypot"
### Implantação Manual (Legado)
1. Crie um droplet na DigitalOcean com os requisitos de hardware acima e sua chave SSH.
2. Faça SSH no droplet ou use o console web da DigitalOcean e execute os seguintes comandos:```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
Nota: Por padrão, o Terraform escreve o estado em
terraform/terraform.tfstatena sua máquina local. Este arquivo contém valores de saída sensíveis (IP do droplet, senha do Grafana, impressão digital da chave SSH) e nunca deve ser commitado. O.gitignorecobre*.tfstatee*.tfstate.*.
Riscos do estado local:
apply concorrentes podem corromper o arquivoPara qualquer coisa além de um laboratório pessoal, mude para um backend remoto. O terraform/backend.tf contém uma configuração comentada do DigitalOcean Spaces (compatível com S3).
Para ativar o backend remoto:
terraform.tfvars): ```bash
export AWS_ACCESS_KEY_ID=
export AWS_SECRET_ACCESS_KEY=
backend "s3" em terraform/backend.tf e preencha o nome do seu bucket e o endpoint da região.terraform init -migrate-state para mover o estado local existente para o Spaces.O arquivo .gitlab-ci.yml executa três jobs a cada push em um único estágio validate:
Nota:
terraform:fmteterraform:validatebloqueiam o pipeline em caso de falha.checkov:scanestá configurado comoallow_failure: trueporque alguns achados são compromissos intencionais. Suprima achados específicos aceitáveis com comentários inline# checkov:skip=CKXXXem vez de desabilitar o job inteiramente.
CKV_DIO_4 ("Garanta que o ingress do firewall não esteja totalmente aberto") dispara uma vez contra todo o recurso digitalocean_firewall. Ele é suprimido com um único comentário # checkov:skip=CKV_DIO_4: colocado dentro do bloco do recurso em terraform/main.tf. A tabela abaixo documenta por que cada porta aberta é intencional.
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
## Observações
Dados capturados durante 5 dias de implantação ao vivo (2026-05-05 a 2026-05-09):
| Métrica | Valor |
|---------------------------|--------|
| Total de conexões | 41.700 |
| Tentativas de login | 15.400 |
| Logins bem-sucedidos | 868 |
| Comandos executados | 836 |
| Arquivos baixados | 6 |
| Países de origem únicos | 106 |
### Principais países atacantes
| País | Conexões |
|-----------------|----------|
| Alemanha | 9.768 |
| Países Baixos | 9.110 |
| Estados Unidos | 7.706 |
| Reino Unido | 3.858 |
| Singapura | 1.770 |
| Bélgica | 1.422 |
### Padrões de credenciais
O nome de usuário mais comum tentado foi `root` com 3.866 tentativas, seguido por
`admin` (728) e `user` (494), refletindo scanners automatizados que visam
credenciais padrão e contas de serviço conhecidas. A senha mais comum foi
`123456` (1.480 tentativas) seguida por `123` e `12345`, consistente com
ferramentas de força bruta baseadas em dicionário.
### Comportamento do atacante dentro do shell falso
868 tentativas de login foram bem-sucedidas contra o conjunto de credenciais falsas. Dessas sessões,
836 comandos foram executados. O comando mais comum foi `uname -s -v -n -r -m`
(360 execuções) — um comando padrão de identificação de sistema executado por scripts
automatizados de pós-exploração para identificar o SO e a arquitetura alvo antes de
implantar um payload. Outros comandos observados incluíram `export HISTFILE=/dev/null`
para desabilitar o registro do histórico do shell, e `export HISTSAVE=/dev/null`, indicando
que os atacantes estavam ativamente tentando encobrir seus rastros mesmo dentro do que acreditavam
ser um sistema comprometido.
### Principal conclusão
O volume de tráfego de varredura automatizada — 41.700 conexões em 5 dias de 106
países — confirma que qualquer serviço SSH publicamente acessível enfrenta constantes
tentativas de força bruta dentro de horas após a exposição. O padrão comportamental de
executar imediatamente `uname` seguido de comandos de supressão de histórico é consistente
com frameworks automatizados de pós-exploração operando com envolvimento humano mínimo.
## Uso
### Usando o Dashboard do Grafana
> Nota: As credenciais do dashboard são definidas em terraform.tfvars ou .env (para implantação manual)
O dashboard pode ser acessado em:```
http://<your-droplet-ip>:3000
Faça login com admin / <GRAFANA_ADMIN_PASSWORD>.
cowrie/etc/userdb.txt - contém as credenciais aceitas para o honeypot.
Edite este arquivo para ajustar as credenciais do honeypot
Logins aceitos colocam o atacante em um shell falso onde todos os comandos são registrados
Entradas rejeitadas são registradas como tentativas falhas
Esta seção detalha as decisões tomadas e os problemas encontrados durante o desenvolvimento deste projeto. Serve como uma reflexão sobre o processo de desenvolvimento do projeto e as lições aprendidas. Pule para Considerações de Segurança se você só quiser saber como usar o projeto.
Por que Hospedagem em Nuvem?
Por que DigitalOcean?
Por que Terraform?
manual-deployment.sh, é para usuários que não têm experiência com Terraform ou preferem não usá-lo. Esse caminho requer provisionar um droplet, conectar-se a ele, clonar o repositório e executar o script. O Terraform permite iteração mais rápida: edite a base de código e reimplante sem repetir cada etapa manual a partir da sua máquina local. Também facilita a desmontagem da infraestrutura e o recomeço. Se o projeto crescer de um único honeypot para uma honeynet, o Terraform escala de forma mais limpa do que o provisionamento manual ou o script legado sozinho.Por que Cowrie?
Por que Loki?
Por que Grafana?
Por que Promtail?
Por que GeoIP?
scripts/geoip-update.sh: sem conta ou chave de API, o que mantém a implantação simples para qualquer pessoa que clone o repositório. O estágio GeoIP embutido do Promtail lê o arquivo MMDB local e adiciona rótulos para o painel Geomap do Grafana.Visão geral passo a passo do que ocorre quando o honeypot é configurado usando Terraform ou o script de implantação manual.
Na sua máquina:
terraform/terraform.tfvars: token da API, caminho da chave pública SSH, senha do Grafana, URL/ramo do repositório, manage_do_firewall opcionalterraform init: instala o provedor DigitalOcean localmenteterraform apply: Terraform constrói um plano e depois cria ou atualiza recursosNo DigitalOcean:
droplet_image, droplet_size, region, etc.user_data é definido como o cloud-init renderizado: na primeira inicialização, a VM obtém a porta SSH de administração, git repo_url / repo_branch e uma cópia codificada em base64 do .env gerado (senha do Grafana e ADMIN_SSH_PORT de env.tftpl)manage_do_firewall é verdadeiro):
terraform/main.tf)No droplet, o cloud-init é executado automaticamente:
package_update / package_upgrade, depois instala os pacotes listados (curl, git, ufw, …). No início da inicialização, write_files cria /root/honeypot.env (o env.tftpl renderizado pelo Terraform como base64: senha do Grafana, ADMIN_SSH_PORT, etc.)runcmd (ordem em terraform/templates/cloud-init.yaml.tftpl): define iptables / ip6tables para backends legados/swapfile, fstab, swappiness)Dentro do Docker Compose:
/ready na porta 3100 dentro do contêiner; no host apenas 127.0.0.1:3100)depends_on em docker-compose.yml) e depois iniciamdata/cowrie-logs para o PromtailO usuário cria a VM, faz SSH e executa o script a partir do repositório clonado.
manual-deployment.sh faz o seguinte:
.envapt update e apt upgradeufw e iptablessshd na ADMIN_SSH_PORT e desativa ssh.socketscripts/setup-firewall.shscripts/geoip-update.sh e cron mensaldocker compose pull / up -d, então o script aguarda verificações de saúde do Loki e Grafana e imprime um resumoSoluções Consideradas:
Resolução:
Trade-offs:
Lições Aprendidas:
docker compose up falhava porque a porta 22 já estava em uso e não podia ser vinculada pelo Cowrie.Soluções Consideradas:
sshd estava mal configurado ou ouvindo na porta erradaResolução:
sshd é movido para ADMIN_SSH_PORT (padrão 2022) e reiniciado primeiro, depois ssh.socket é parado e desativado para liberar a porta 22 para o Docker vincular para Cowrie. As regras UFW são aplicadas após ambas as etapas para permitir a nova porta de administração.Trade-offs:
Lições Aprendidas:
ssh.socket é uma unidade de socket systemd que mantém a porta 22 reservada para ativação SSH sob demanda. Ela deve ser parada e desativada antes que o Docker possa vincular a porta 22 do host para Cowrie. Se ainda estiver ativa, docker compose up falha com "address already in use".Funcionalidade GeoIP
Grafana HTTPS
Funcionalidade de Servidor Proxy
GNU General Public License Version 2.0 (GPLv2) Veja o texto completo da licença no arquivo LICENSE.
| Job | Tool | Purpose |
|---|
terraform:fmt | hashicorp/terraform:1.8 | Formatação: falha se algum arquivo precisar de terraform fmt |
terraform:validate | hashicorp/terraform:1.8 | Validade da configuração sem contactar a DigitalOcean |
checkov:scan | bridgecrew/checkov:latest | Má configurações de IaC no código Terraform |
| ID de Verificação | Recurso | Achado | Decisão |
|---|
CKV_DIO_4 | digitalocean_firewall.honeypot — admin SSH inbound | Porta SSH admin aberta para 0.0.0.0/0 | Aceito — restringir a um IP fixo é impraticável para um laboratório portátil; recomendado em produção |
CKV_DIO_4 | digitalocean_firewall.honeypot — porta 22 inbound | Porta 22 aberta para 0.0.0.0/0 | Intencional — esta é a superfície do honeypot SSH; restringir a origem anula o propósito |
CKV_DIO_4 | digitalocean_firewall.honeypot — porta 23 inbound | Porta 23 aberta para 0.0.0.0/0 | Intencional — superfície do honeypot Telnet; mesma lógica da porta 22 |
CKV_DIO_4 | digitalocean_firewall.honeypot — porta 3000 inbound | HTTP do Grafana exposto para 0.0.0.0/0 | Aceito para acessibilidade de laboratório — limitação conhecida documentada em Considerações de Segurança; implantações em produção devem restringir a um IP conhecido ou fazer proxy via HTTPS na porta 443 |
| Painel | Descrição |
|---|
| Mapa Mundial de Ataques | Geomapa com camadas de mapa de calor + marcadores mostrando a origem de cada conexão |
| Total de Conexões | Contagem de sessões do honeypot recebidas no intervalo de tempo selecionado |
| Tentativas de Login | Total de tentativas de força bruta de credenciais |
| Logins Bem-sucedidos | Atacantes que corresponderam às credenciais de userdb.txt |
| Comandos Executados | Comandos de shell executados dentro do shell falso |
| Arquivos Baixados | Malware/scripts capturados via wget/curl |
| Taxa de Conexão | Série temporal: conexões/s, logins falhos/s, sucessos/s |
| Principais Nomes de Usuário | Nomes de usuário SSH mais tentados |
| Principais Senhas | Senhas mais tentadas |
| Principais Comandos | Comandos de shell mais executados |
| Ataques por País | Tabela de conexões a nível de país |
| Eventos Recentes | Fluxo de logs ao vivo dos eventos mais recentes |
| Downloads de Arquivos | Tabela de cada arquivo que um atacante tentou baixar |
systemctl enable --now dockersshd na ADMIN_SSH_PORT; desativa ssh.socket para que a porta 22 do host fique livre para Cowriegit clone de repo_url / repo_branch em /opt/honeypot (deve incluir docker-compose.yml e configurações).env no disco: move /root/honeypot.env para /opt/honeypot/.env (modo 600)scripts/setup-firewall.sh (UFW para SSH de administração, 22, 23, 3000)scripts/geoip-update.sh (DB-IP City Lite em geoip/); anexa uma linha cron mensal para o mesmo scriptdata/cowrie-logs e data/cowrie-dl com permissões que o Compose espera/opt/honeypot, docker compose pull e depois docker compose up -d