
중간 상호작용 SSH/Telnet 허니팟 - Cowrie, Loki, Promtail, Grafana로 구축, Terraform을 통해 DigitalOcean에 프로비저닝, GitLab CI 검증 파이프라인 포함
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
## 하드웨어 요구 사항
> 참고: 이러한 요구 사항은 2026년 5월 기준 DigitalOcean Basic Droplet 플랜을 기반으로 하며, 프로젝트를 실행하기 위한 최소 요구 사항입니다.
- 제공자: DigitalOcean
- 플랜: Basic Droplet - 1 Intel vCPU
- RAM: 1 GB (+2 GB 스왑)
- 스토리지: 35 GB NVMe SSD
- OS: 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. 위의 하드웨어 요구 사항을 충족하는 DigitalOcean 드롭렛을 생성하고 SSH 키를 추가합니다.
2. 드롭렛에 SSH로 접속하거나 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에는 (S3 호환) DigitalOcean Spaces 설정이 주석 처리되어 있습니다.
원격 백엔드를 활성화하려면:
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 - 허니팟에 대해 수락된 자격 증명이 포함되어 있습니다.
이 파일을 편집하여 허니팟 자격 증명을 조정하세요
수락된 로그인은 공격자를 모든 명령어가 기록되는 가짜 셸로 떨어뜨립니다
거부된 항목은 실패한 시도로 기록됩니다
이 섹션에서는 이 프로젝트 개발 중 내린 결정과 발생한 문제를 자세히 설명합니다. 프로젝트 개발 과정과 교훈을 반영합니다. 프로젝트 사용 방법만 알고 싶다면 보안 고려 사항으로 건너뛰세요.
왜 클라우드 호스팅인가?
왜 DigitalOcean인가?
왜 Terraform인가?
manual-deployment.sh는 Terraform에 경험이 없거나 사용을 선호하지 않는 사용자를 위한 것입니다. 해당 경로는 드롭릿을 프로비저닝하고, 연결하고, 리포지토리를 복제하고, 스크립트를 실행해야 합니다. 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 인코딩 사본(Grafana 비밀번호 및 env.tftpl의 ADMIN_SSH_PORT)을 가져옵니다manage_do_firewall이 true인 경우만):
terraform/main.tf 참조)드롭릿에서 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; 호스트에서는 127.0.0.1:3100만)docker-compose.yml에서 depends_on) 시작합니다data/cowrie-logs 아래에 JSON 로그를 씁니다사용자가 VM을 생성하고, SSH로 접속한 후, 클론된 리포지토리에서 스크립트를 실행합니다.
manual-deployment.sh는 다음을 수행합니다:
.env 로드 및 검증apt update 및 apt upgradeufw 및 iptablessshd를 ADMIN_SSH_PORT에서 실행하고 ssh.socket 비활성화scripts/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을 중지하고 비활성화하여 Docker가 Cowrie를 위해 포트 22를 바인딩할 수 있도록 함. UFW 규칙은 두 단계 후에 새 관리 포트를 허용하도록 적용됩니다.트레이드오프:
교훈:
ssh.socket은 주문형 SSH 활성화를 위해 포트 22를 예약하는 systemd 소켓 유닛입니다. Docker가 호스트 포트 22를 Cowrie에 바인딩하려면 먼저 중지하고 비활성화해야 합니다. 여전히 활성화되어 있으면 docker compose up이 "address already in use" 오류와 함께 실패합니다.GeoIP 기능
Grafana HTTPS
프록시 서버 기능
GNU General Public License 버전 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에 열려 있음 | 의도적 — 텔넷 허니팟 표면입니다. 22번 포트와 동일한 이유 |
CKV_DIO_4 | digitalocean_firewall.honeypot — 3000번 포트 인바운드 | Grafana HTTP가 0.0.0.0/0에 노출됨 | 랩 접근성을 위해 수용됨 — 보안 고려 사항에 문서화된 알려진 한계; 프로덕션 배포 시 알려진 IP로 제한하거나 443번 포트의 HTTPS를 통해 프록시해야 함 |
| 패널 | 설명 |
|---|
| 공격 세계 지도 | 모든 연결의 출처를 보여주는 히트맵 + 마커 레이어가 있는 지오맵 |
| 총 연결 수 | 선택한 시간 범위 내 인바운드 허니팟 세션 수 |
| 로그인 시도 | 총 자격 증명 무작위 대입 공격 시도 |
| 성공적인 로그인 | userdb.txt 자격 증명과 일치한 공격자 |
| 실행된 명령어 | 가짜 셸 내부에서 실행된 셸 명령어 |
| 다운로드된 파일 | wget/curl을 통해 캡처된 악성코드/스크립트 |
| 연결 속도 | 시계열: 연결/초, 실패한 로그인/초, 성공/초 |
| 최다 사용자 이름 | 가장 많이 시도된 SSH 사용자 이름 |
| 최다 비밀번호 | 가장 많이 시도된 비밀번호 |
| 최다 명령어 | 가장 많이 실행된 셸 명령어 |
| 국가별 공격 | 국가 수준 연결 테이블 |
| 최근 이벤트 | 최신 이벤트의 실시간 로그 스트림 |
| 파일 다운로드 | 공격자가 가져오려고 시도한 모든 파일의 테이블 |
systemctl enable --now dockersshd를 ADMIN_SSH_PORT에서 실행; ssh.socket을 비활성화하여 호스트 포트 22를 Cowrie가 사용할 수 있도록 함git clone을 repo_url / repo_branch에서 /opt/honeypot로 (docker-compose.yml 및 구성 포함해야 함).env: /root/honeypot.env를 /opt/honeypot/.env로 이동(모드 600)scripts/setup-firewall.sh (관리 SSH, 22, 23, 3000에 대한 UFW)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