chore: standardize ecosystem to Go 1.25 and add Go sensor template, finished rewriting sensors in go

This commit is contained in:
andreicscs
2026-04-07 17:04:07 +02:00
parent 80cf418483
commit 1bd6d7c226
32 changed files with 825 additions and 491 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
module github.com/honeywire/sdk-go
go 1.22.1
go 1.25.0
+44
View File
@@ -0,0 +1,44 @@
# HoneyWire Official Sensor: File Canary
The File Canary acts as both a Honeypot and a File Integrity Monitor (FIM). It watches a specified directory or file on the host machine. If an attacker modifies, deletes, or drops a file into the watched area, the sensor immediately fires an alert to the HoneyWire Hub.
## Features
* **Zero-Setup SDK Integration:** Natively built on the HoneyWire Go SDK.
* **Dual-Mode Operation:** Can monitor highly sensitive, real system files (FIM) or act as a standalone honeypot directory (Trap).
* **Safe Permissions Handling:** Uses Access Control Lists (ACLs) to securely read target directories without altering their original host ownership.
* **Failsafe Mounts:** Designed to halt deployment if the target directory doesn't exist, preventing false-positive monitoring.
## Configuration
Configuration is managed through an `.env` file located in the same directory as the `docker-compose.yml`.
### Core Ecosystem Variables
| Variable | Description | Example |
|---|---|---|
| `HW_HUB_ENDPOINT` | The URL of your central HoneyWire Hub. | `http://127.0.0.1:8080` |
| `HW_HUB_KEY` | The shared secret API key to authenticate with the Hub. | `super_secret_key_123` |
| `HW_SENSOR_ID` | A unique identifier for this specific trap. | `file-canary-01` |
| `HW_SEVERITY` | Alert severity sent to the Hub (`info` to `critical`). | `critical` |
### Sensor-Specific Variables
| Variable | Description | Default |
|---|---|---|
| `TRAP_PATH` | The physical path on the host machine to monitor. | `./trap_directory` or `/etc/passwd` |
## Deployment
It is highly recommended to deploy this sensor using the provided `docker-compose.yml` configuration and the provided `Dockerfile` (if building from source). The compose file orchestrates the required permission fixers and read-only mounts automatically.
```bash
docker compose up -d
```
## Security Architecture
This sensor is architected for extreme resilience against exploits. By utilizing a minimal attack surface and enforcing strict container sandboxing, it ensures the host filesystem remains protected.
**Core Defense-in-Depth Measures:**
* **Unprivileged Execution:** Runs entirely as a non-root user (`UID 65532`), preventing system-level modifications even in the event of a container breach.
* **Read-Only Mounts:** The target directory is mounted with strict `read_only: true` flags, ensuring the container cannot write to or modify the host files it is monitoring under any circumstances.
* **ACL Integration:** Instead of changing host file ownership, a temporary initialization container uses `setfacl` to grant the non-root user specific, read-only traverse rights, keeping your original host permissions completely intact.
* **Kernel Capability Stripping:** Drops all default Linux kernel capabilities (`cap_drop: ALL`) via the Docker Compose configuration, neutralizing advanced kernel exploitation techniques.
* **Distroless Isolation:** Built on a statically-linked Distroless image. It completely lacks a shell (`/bin/sh`), package managers, or standard Linux utilities, leaving attackers with zero tools to pivot to the host.
*Recommendation: For optimal security, always deploy this sensor using the official `docker-compose.yml` and `Dockerfile` to ensure these sandbox protections are strictly enforced by the container runtime.*
@@ -1,3 +1,5 @@
version: '3.8'
services:
# 1. THE FIM SETTER: Only runs if the path exists. Simply grants the read-only ACL pass.
permission-fixer:
@@ -17,6 +19,9 @@ services:
dockerfile: Sensors/official/FileCanary-GO/Dockerfile
restart: unless-stopped
# Ensures uniform communication with a locally hosted Hub
network_mode: "host"
depends_on:
permission-fixer:
condition: service_completed_successfully
+42
View File
@@ -0,0 +1,42 @@
# HoneyWire Official Sensor: ICMP Canary
The ICMP Canary (Ping Canary) is a simple, highly effective network tripwire. It listens for ICMP Echo Requests (pings) directed at the host machine. It is best deployed on isolated IPs, darknets, or unused subnets where any inbound ICMP traffic is inherently suspicious.
## Features
* **Zero-Setup SDK Integration:** Natively built on the HoneyWire Go SDK.
* **Raw Socket Listening:** Uses pure Go to listen directly for protocol 1 (ICMP) packets without external C-dependencies.
* **Low Overhead:** Requires minimal CPU and RAM to operate, making it ideal for widespread deployment.
* **Distroless Container:** Compiled as a statically-linked binary running inside a minimal Docker image.
## Configuration
Configuration is managed through Environment Variables.
### Core Ecosystem Variables
| Variable | Description | Example |
|---|---|---|
| `HW_HUB_ENDPOINT` | The URL of your central HoneyWire Hub. | `http://127.0.0.1:8080` |
| `HW_HUB_KEY` | The shared secret API key to authenticate with the Hub. | `super_secret_key_123` |
| `HW_SENSOR_ID` | A unique identifier for this specific trap. | `ping-canary-01` |
| `HW_SEVERITY` | Alert severity sent to the Hub (`info` to `critical`). | `high` |
## Deployment
It is highly recommended to deploy this sensor using the provided `docker-compose.yml` configuration and the provided `Dockerfile` (if building from source). The compose file automatically applies the strict network and kernel capability rules required for safe execution.
```bash
docker compose up -d
```
## Security Architecture
This sensor is architected for extreme resilience against exploits. By utilizing a minimal attack surface and enforcing strict container sandboxing, it safely intercepts raw ICMP traffic.
**Core Defense-in-Depth Measures:**
* **Raw Socket Isolation:** Bypasses heavy NIDS frameworks by interacting directly with network packets in pure Go, eliminating external C-library vulnerabilities.
* **Least Privilege Execution:** Runs as container root strictly to bind the raw socket, relying on container boundaries to limit system access.
* **Kernel Capability Stripping:** Drops all default Linux kernel capabilities (`cap_drop: ALL`) and only adds back `NET_RAW`, ensuring the sensor can intercept pings but cannot modify the host filesystem or OS.
* **Distroless Isolation:** Built on a statically-linked Distroless image. It completely lacks a shell (`/bin/sh`), package managers, or standard Linux utilities, leaving attackers with zero tools to execute secondary payloads.
* **In-Memory Operation:** Processes all packet data exclusively in memory, ensuring zero malicious disk I/O operations occur on the host system.
*Recommendation: For optimal security, always deploy this sensor using the official `docker-compose.yml` and `Dockerfile` to ensure these sandbox protections are strictly enforced by the container runtime.*
@@ -6,6 +6,9 @@ services:
dockerfile: Sensors/official/IcmpCanary-GO/Dockerfile
restart: unless-stopped
# Binds directly to the host machine to capture the true Source IPs
network_mode: "host"
# ---------------------------------------------------------
# SECURITY HARDENING: Principle of Least Privilege
# ---------------------------------------------------------
@@ -15,7 +18,7 @@ services:
- NET_RAW
environment:
- HW_HUB_ENDPOINT=http://172.17.0.1:8080
- HW_HUB_ENDPOINT=http://127.0.0.1:8080
- HW_HUB_KEY=change_this_to_a_secure_random_string
- HW_SENSOR_ID=ping-canary-01
- HW_TEST_MODE=false
@@ -0,0 +1,49 @@
# HoneyWire Official Sensor: Network Scan Detector
The Network Scan Detector is a low-overhead network sensor designed to silently detect horizontal port scans. By monitoring raw SYN packets directly on the network interface, it identifies scanning activity aimed at closed or unused ports before it ever reaches a firewall or application log.
## Features
* **Zero-Setup SDK Integration:** Natively built on the HoneyWire Go SDK.
* **In-Memory Parsing:** Analyzes raw TCP headers directly in memory.
* **Configurable Thresholds:** Easily adjust how many unique ports must be hit within a specific time window to trigger an alert.
* **Distroless Container:** Compiled as a statically-linked binary running inside a minimal Docker image.
## Configuration
All configuration is handled via Environment Variables.
### Core Ecosystem Variables
| Variable | Description | Example |
|---|---|---|
| `HW_HUB_ENDPOINT` | The URL of your central HoneyWire Hub. | `http://127.0.0.1:8080` |
| `HW_HUB_KEY` | The shared secret API key to authenticate with the Hub. | `super_secret_key_123` |
| `HW_SENSOR_ID` | A unique identifier for this specific trap. | `scan-detector-01` |
| `HW_SEVERITY` | Alert severity sent to the Hub (`info` to `critical`). | `critical` |
### Sensor-Specific Variables
| Variable | Description | Default |
|---|---|---|
| `HW_SCAN_THRESHOLD` | Number of unique ports that must be hit to trigger an alert. | `5` |
| `HW_SCAN_WINDOW` | The time window (in seconds) to track the threshold. | `5` |
| `HW_IGNORE_PORTS` | Comma-separated ports to ignore (e.g., actual open services). | `80,443` |
## Deployment
It is highly recommended to deploy this sensor using the provided docker-compose.yml configuration and the provided Dockerfile (if building from source). The compose file automatically applies the strict network and kernel capability rules required for safe execution.
```bash
docker compose up -d
```
## Security Architecture
This sensor is architected for extreme resilience against exploits. By utilizing a minimal attack surface and enforcing strict container sandboxing, it safely handles raw network traffic.
**Core Defense-in-Depth Measures:**
* **Raw Socket Isolation:** Bypasses heavy NIDS frameworks by interacting directly with network packets in pure Go, eliminating external C-library vulnerabilities.
* **Least Privilege Execution:** Runs as container root strictly to bind the raw socket, but relies on capability dropping to prevent privilege escalation.
* **Kernel Capability Stripping:** Drops all default Linux kernel capabilities (`cap_drop: ALL`) and only adds back `NET_RAW`, ensuring the sensor can read packets but cannot modify the system.
* **Distroless Isolation:** Built on a statically-linked Distroless image. It completely lacks a shell (`/bin/sh`), package managers, or standard Linux utilities, leaving attackers with zero tools to pivot to the host network.
* **In-Memory Operation:** Processes all payload data exclusively in memory, ensuring zero malicious disk I/O operations occur on the host system.
*Recommendation: For optimal security, always deploy this sensor using the official `docker-compose.yml` and `Dockerfile` to ensure these sandbox protections are strictly enforced by the container runtime.*
@@ -6,6 +6,9 @@ services:
dockerfile: Sensors/official/NetworkScanDetector-GO/Dockerfile
restart: unless-stopped
# Binds directly to the host machine to accurately capture true Source IPs
network_mode: "host"
# ---------------------------------------------------------
# SECURITY HARDENING
# ---------------------------------------------------------
@@ -15,7 +18,7 @@ services:
- NET_RAW
environment:
- HW_HUB_ENDPOINT=http://172.17.0.1:8080
- HW_HUB_ENDPOINT=http://127.0.0.1:8080
- HW_HUB_KEY=change_this_to_a_secure_random_string
- HW_SENSOR_ID=scan-detector-01
- HW_TEST_MODE=false
+1 -1
View File
@@ -1,6 +1,6 @@
module github.com/honeywire/sensors/networkscandetector
go 1.22.1
go 1.25.0
replace github.com/honeywire/sdk-go => ../../../SDKs/go-honeywire
+21
View File
@@ -0,0 +1,21 @@
# STAGE 1: Build
FROM golang:1.25 AS builder
WORKDIR /src
COPY SDKs/go-honeywire ./SDKs/go-honeywire
COPY Sensors/official/TcpTarpit-GO ./Sensors/official/TcpTarpit-GO
WORKDIR /src/Sensors/official/TcpTarpit-GO
RUN go mod download
# Pure Go, fully static binary
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o tcp-tarpit ./main.go
# STAGE 2: Distroless
# Back to the fully unprivileged nonroot container!
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /src/Sensors/official/TcpTarpit-GO/tcp-tarpit /app/tcp-tarpit
USER 65532
CMD ["/app/tcp-tarpit"]
+54
View File
@@ -0,0 +1,54 @@
# HoneyWire Official Sensor: TCP Tarpit
The TCP Tarpit is a high-fidelity, low-interaction honeypot designed to detect network reconnaissance and brute-force attempts. It can act as a "Tarpit," binding to decoy ports and intentionally stalling attackers to waste their time while silently extracting their IP and payload data to report to the HoneyWire Hub, or instantly close the connection and report the IP to the Hub.
## Features
* **Zero-Setup SDK Integration:** Natively built on the HoneyWire Go SDK.
* **Massive Concurrency:** Powered by Go routines and channels, capable of trapping thousands of automated bots simultaneously with microscopic memory overhead.
* **Tarpit Modes:** Supports `hold` (silent stall), `echo` (repeat data back), or `close` (immediate drop).
* **Forensic Capture:** Safely buffers up to 10 lines of payload data without risking memory exhaustion.
* **Distroless Container:** Compiled as a statically-linked binary running inside a hardened, unprivileged `:nonroot` Distroless Docker image to prevent container breakouts.
## Configuration
All configuration is handled via Environment Variables. Copy the `.env.example` file to `.env` before running.
### Core Ecosystem Variables (Required)
| Variable | Description | Example |
|---|---|---|
| `HW_HUB_ENDPOINT` | The URL of your central HoneyWire Hub. | `http://127.0.0.1:8080` |
| `HW_HUB_KEY` | The shared secret API key to authenticate with the Hub. | `super_secret_key_123` |
| `HW_SENSOR_ID` | A unique identifier for this specific trap. | `ssh-tarpit-01` |
| `HW_SEVERITY` | Alert severity sent to the Hub (`info` to `critical`). | `high` |
### Sensor-Specific Variables
| Variable | Description | Default |
|---|---|---|
| `HW_DECOY_PORTS` | Comma-separated list of TCP ports to monitor. | `2222,3306` |
| `HW_TARPIT_MODE` | The behavior of the trap: `hold`, `echo`, or `close`. | `hold` |
| `HW_TARPIT_BANNER` | (Optional) A fake service banner to send on connect. | `SSH-2.0-OpenSSH_8.2p1\r\n` |
## Tarpit Modes Explained
* **`hold` (Default):** The sensor accepts the connection but sends nothing. It holds the TCP socket open as long as possible (up to 1 hour), dripping empty bytes to drain the attacker's resources and slow down automated scanners like Nmap or brute-force tools.
* **`echo`:** The sensor acts as an echo server, repeating whatever the attacker sends back to them. Useful for confusing automated scripts.
* **`close`:** The sensor logs the connection, captures the initial payload, and forcefully closes the socket.
## Deployment
It is highly recommended to deploy this sensor using the provided docker-compose.yml configuration and the provided Dockerfile (if building from source). The compose file automatically applies the strict network and kernel capability rules required for safe execution.
```Bash
docker compose up -d
```
## Security Architecture
This sensor is architected for extreme resilience against exploitation. By adhering to the principle of least privilege and enforcing strict resource limits.
**Core Defense-in-Depth Measures:**
* **Kernel Capability Stripping:** Drops all Linux kernel capabilities (`cap_drop: ALL`) via the Docker Compose configuration, neutralizing advanced kernel exploitation techniques.
* **Distroless Isolation:** Built on a statically-linked Distroless image. It completely lacks a shell (`/bin/sh`), package managers, or common Linux utilities (like `curl` or `wget`), leaving attackers with zero tools to pivot if they achieve Remote Code Execution.
* **Concurrency Capping:** Utilizes a native Go buffered channel (semaphore) to strictly cap concurrent connections at `1000`. This prevents attackers from launching a Denial of Service (DoS) attack designed to exhaust the host machine's File Descriptors or RAM.
* **In-Memory Operation:** Processes all payload data exclusively in memory, ensuring zero malicious disk I/O operations occur on the host system.
*Recommendation: For optimal security, always deploy this sensor using the official `docker-compose.yml` and `Dockerfile` to ensure these sandbox protections are strictly enforced by the container runtime.*
@@ -0,0 +1,32 @@
services:
tcp-tarpit:
container_name: hw-tcp-tarpit
build:
context: ../../..
dockerfile: Sensors/official/TcpTarpit-GO/Dockerfile
restart: unless-stopped
# ---------------------------------------------------------
# NETWORKING: Host Mode
# ---------------------------------------------------------
# Binds directly to the host machine.
# 1. Preserves the real Source IP of the attacker.
# 2. Automatically exposes ports defined in HW_DECOY_PORTS.
network_mode: "host"
# ---------------------------------------------------------
# SECURITY HARDENING
# ---------------------------------------------------------
cap_drop:
- ALL
environment:
# Since we are on the host network, use the host's perspective to reach the Hub
- HW_HUB_ENDPOINT=http://127.0.0.1:8080
- HW_HUB_KEY=change_this_to_a_secure_random_string
- HW_SENSOR_ID=tcp-tarpit-01
- HW_TEST_MODE=false
- HW_DECOY_PORTS=2222,3306
- HW_TARPIT_MODE=hold
- HW_TARPIT_BANNER=SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.4\r\n
+7
View File
@@ -0,0 +1,7 @@
module github.com/honeywire/sensors/tcptarpit
go 1.25.0
replace github.com/honeywire/sdk-go => ../../../SDKs/go-honeywire
require github.com/honeywire/sdk-go v0.0.0-00010101000000-000000000000
+186
View File
@@ -0,0 +1,186 @@
package main
import (
"fmt"
"log"
"net"
"os"
"strconv"
"strings"
"time"
"github.com/honeywire/sdk-go"
)
var (
decoyPorts = parsePorts(getEnv("HW_DECOY_PORTS", "2222,3306"))
tarpitMode = strings.ToLower(getEnv("HW_TARPIT_MODE", "hold"))
banner = parseBanner(getEnv("HW_TARPIT_BANNER", ""))
concurrency = 1000
maxBytes = 50 * 1024 // 50KB
maxLines = 10
maxDuration = 3600 * time.Second
)
func main() {
// 1. Initialize SDK
hw := sdk.NewSensor("tarpit")
hw.Start()
log.Printf("[*] HoneyWire Tarpit | Mode: %s", strings.ToUpper(tarpitMode))
if banner != "" {
log.Printf("[*] Banner loaded: %s", strconv.Quote(banner))
}
// 2. Concurrency Limiter (Semaphore)
semaphore := make(chan struct{}, concurrency)
errCh := make(chan error)
// 3. Start a TCP Listener for each Decoy Port
for _, port := range decoyPorts {
go startListener(hw, port, semaphore, errCh)
}
// Block main thread and watch for fatal listener errors
log.Fatal(<-errCh)
}
func startListener(hw *sdk.Sensor, port int, semaphore chan struct{}, errCh chan error) {
addr := fmt.Sprintf("0.0.0.0:%d", port)
listener, err := net.Listen("tcp", addr)
if err != nil {
errCh <- fmt.Errorf("[!] FATAL: Failed to bind to port %d: %v", port, err)
return
}
defer listener.Close()
log.Printf("[+] Tarpit listening on port %d", port)
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("[-] Accept error on port %d: %v", port, err)
continue
}
// Acquire semaphore slot (blocks if 1000 connections are active)
semaphore <- struct{}{}
// Spawn a lightweight goroutine for the attacker
go func(c net.Conn) {
defer func() { <-semaphore }() // Release slot when done
handleConnection(hw, c, port)
}(conn)
}
}
func handleConnection(hw *sdk.Sensor, conn net.Conn, port int) {
defer conn.Close()
start := time.Now()
// Extract the IP without the attacker's ephemeral port
remoteAddr := conn.RemoteAddr().String()
srcIP, _, err := net.SplitHostPort(remoteAddr)
if err != nil {
srcIP = remoteAddr
}
var payload []string
consumedBytes := 0
// 1. Send Fake Banner (e.g., pretending to be SSH)
if banner != "" && tarpitMode != "close" {
conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
conn.Write([]byte(banner))
}
// 2. The Tarpit Trap Loop
if tarpitMode != "close" {
buf := make([]byte, 1024)
for consumedBytes < maxBytes && time.Since(start) < maxDuration {
// Wait up to 5 minutes for the attacker to send data
conn.SetReadDeadline(time.Now().Add(300 * time.Second))
n, err := conn.Read(buf)
if err != nil {
// If we timed out waiting for them, send a Null byte to keep the connection alive!
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
conn.Write([]byte{0})
continue
}
break // Attacker dropped the connection or sent EOF
}
if n > 0 {
consumedBytes += n
// Safely decode to string and log up to maxLines
if len(payload) < maxLines {
text := strings.TrimSpace(strings.ToValidUTF8(string(buf[:n]), "?"))
if text != "" {
payload = append(payload, text)
}
}
// If in echo mode, reflect their attack back at them
if tarpitMode == "echo" {
conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
conn.Write(buf[:n])
}
// The Tar: Slow down our processing to waste their time
time.Sleep(500 * time.Millisecond)
}
}
}
duration := time.Since(start).Seconds()
// 3. Dispatch the Event via the SDK
hw.ReportEvent(
"tcp_connection",
"high",
map[string]any{
"duration_sec": duration,
"payload": payload,
},
tarpitMode,
srcIP,
fmt.Sprintf("Port %d", port),
)
}
// --- Helper Functions ---
func parsePorts(raw string) []int {
var ports []int
for _, p := range strings.Split(raw, ",") {
p = strings.TrimSpace(p)
if p == "" { continue }
if val, err := strconv.Atoi(p); err == nil {
ports = append(ports, val)
} else {
log.Printf("[!] Invalid port in HW_DECOY_PORTS: %s", p)
}
}
if len(ports) == 0 {
return []int{2222, 3306}
}
return ports
}
// Replaces "\n" string literals from env vars into actual newlines
func parseBanner(raw string) string {
raw = strings.ReplaceAll(raw, "\\r", "\r")
raw = strings.ReplaceAll(raw, "\\n", "\n")
return raw
}
func getEnv(key, fallback string) string {
if val, exists := os.LookupEnv(key); exists {
return val
}
return fallback
}
-19
View File
@@ -1,19 +0,0 @@
# ==========================================
# HONEYWIRE SENSOR: TCP TARPIT
# ==========================================
# --- CORE ECOSYSTEM SETTINGS (Required) ---
HW_HUB_ENDPOINT=http://192.168.1.100:8080
HW_HUB_KEY=change_this_to_a_secure_random_string
HW_SENSOR_ID=TripWire-01
HW_SEVERITY=high
# --- SENSOR SPECIFIC SETTINGS ---
# The fake ports this agent will listen on (Comma-separated)
HW_DECOY_PORTS=21,2222,3306
# Modes: 'echo' (repeat data), 'hold' (do nothing, keep open), 'close' (drop immediately)
HW_TARPIT_MODE=hold
# The fake service banner to send upon connection
HW_TARPIT_BANNER=SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.1\r\n
-35
View File
@@ -1,35 +0,0 @@
# --- Stage 1: Builder ---
FROM python:3.11-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
# 1. Copy the SDK source
COPY SDKs/python-honeywire /tmp/sdk_src
# 2. Build a WHEEL (a static redistribution file) instead of installing directly.
# This ensures all code is physically packaged and not symlinked.
RUN pip install --no-cache-dir wheel && \
pip wheel /tmp/sdk_src --wheel-dir /tmp/wheels
# 3. Install the wheel and the requirements into a specific folder
# We use --target to ensure everything is in one flat, movable directory.
COPY Sensors/official/TcpTripWire/requirements.txt .
RUN pip install --no-cache-dir --target /app/lib /tmp/wheels/honeywire-*.whl -r requirements.txt
# --- Stage 2: Distroless (Production) ---
FROM gcr.io/distroless/python3-debian12
WORKDIR /app
# 4. Copy the FLAT library folder. No symlinks, just code.
COPY --from=builder /app/lib /app/lib
# 5. Copy the sensor script
COPY Sensors/official/TcpTripWire/TcpTripWire.py .
# 6. Tell Python to look in our lib folder first.
ENV PYTHONPATH="/app/lib"
ENV PYTHONUNBUFFERED=1
# Distroless runs /usr/bin/python3 by default
CMD ["TcpTripWire.py"]
-70
View File
@@ -1,70 +0,0 @@
# HoneyWire Official Sensor: TCP TripWire 🕸️
The TCP TripWire is a high-fidelity, low-interaction honeypot designed to detect network reconnaissance and brute-force attempts. It acts as a "Tarpit," binding to decoy ports and intentionally stalling attackers to waste their time while silently extracting their IP and payload data to report to the HoneyWire Hub.
## Features
* **Zero-Setup SDK Integration:** Natively built on the HoneyWire Python SDK.
* **Tarpit Modes:** Supports `hold` (silent stall), `echo` (repeat data back), or `close` (immediate drop).
* **Forensic Capture:** Safely buffers up to 10 lines of payload data without risking memory exhaustion.
* **Distroless Container:** Runs as a hardened, Distroless Docker image to prevent container breakouts.
## Configuration
All configuration is handled via Environment Variables. Copy the `.env.example` file to `.env` before running.
### Core Ecosystem Variables (Required)
| Variable | Description | Example |
|---|---|---|
| `HW_HUB_ENDPOINT` | The URL of your central HoneyWire Hub. | `http://192.168.1.100:8080` |
| `HW_HUB_KEY` | The shared secret API key to authenticate with the Hub. | `super_secret_key_123` |
| `HW_SENSOR_ID` | A unique identifier for this specific trap. | `dmz-ssh-tarpit-01` |
### Sensor-Specific Variables
| Variable | Description | Default |
|---|---|---|
| `HW_DECOY_PORTS` | Comma-separated list of TCP ports to monitor. | `2222,3306` |
| `HW_TARPIT_MODE` | The behavior of the trap: `hold`, `echo`, or `close`. | `hold` |
| `HW_SEVERITY` | Alert severity sent to the Hub (`info` to `critical`). | `high` |
| `HW_TARPIT_BANNER` | (Optional) A fake service banner to send on connect. | `SSH-2.0-OpenSSH_8.2p1` |
## Tarpit Modes Explained
* **`hold` (Default):** The sensor accepts the connection but sends nothing. It holds the TCP socket open as long as possible, draining the attacker's resources and slowing down automated scanners like Nmap.
* **`echo`:** The sensor acts as an echo server, repeating whatever the attacker sends back to them. Useful for confusing automated scripts.
* **`close`:** The sensor logs the connection, captures the initial payload, and forcefully closes the socket.
## Deployment
The easiest way to deploy this sensor is via Docker Compose. Note that if you intend to bind to privileged ports (any port under 1024, like Port 22 or 80), the container must run as `root` (which is the default).
### 1. Using Docker Compose
Create a `docker-compose.yml` file:
```yaml
services:
tcp-tripwire:
image: ghcr.io/andreicscs/honeywire-tcptripwire:latest
container_name: hw-tcp-tripwire
restart: unless-stopped
# CRITICAL: Binds directly to the host machine's network.
# 1. Preserves the real source IP of the attacker.
# 2. Automatically exposes whatever ports are set in HW_DECOY_PORTS.
network_mode: "host"
env_file:
- .env
```
Run it in the background:
```Bash
docker-compose up -d
```
### 2. Testing Locally (Build from Source)
If you are developing or testing locally:
```Bash
docker build -t honeywire-tcptripwire .
docker run --rm --network host --env-file .env honeywire-tcptripwire
```
## Security Note
This sensor uses asyncio with strict memory limits (50KB or 10 lines per connection) and a global semaphore to limit concurrent connections. This ensures the sensor cannot be easily DOS'd or used to exhaust the host machine's File Descriptors.
-149
View File
@@ -1,149 +0,0 @@
"""
HoneyWire Tarpit Agent (Standard Port Tripwire)
Sensor type: tarpit
Custom Environment variables:
HW_DECOY_PORTS Comma-separated ports (default: 2222,3306)
HW_TARPIT_MODE hold | echo | close (default: hold)
HW_TARPIT_BANNER Optional service banner
"""
import asyncio
import os
import time
from typing import List, Optional
from honeywire import HoneyWireSensor
DEFAULT_DECOY_PORTS = [2222, 3306]
DEFAULT_TARPIT_MODE = "hold"
DEFAULT_MAX_BYTES = 1024 * 50
DEFAULT_MAX_LINES = 10
DEFAULT_MAX_DURATION = 3600
DEFAULT_CONCURRENCY = 1000
class TcpTarpitSensor(HoneyWireSensor):
def __init__(self):
super().__init__(sensor_type="tarpit")
self.tarpit_mode = os.getenv("HW_TARPIT_MODE", DEFAULT_TARPIT_MODE).lower()
self.decoy_ports = self._parse_ports(os.getenv("HW_DECOY_PORTS", ",".join(map(str, DEFAULT_DECOY_PORTS))))
self.tarpit_banner = self._load_banner(os.getenv("HW_TARPIT_BANNER", ""))
self.max_bytes = DEFAULT_MAX_BYTES
self.max_lines = DEFAULT_MAX_LINES
self.max_duration = DEFAULT_MAX_DURATION
self.semaphore = asyncio.Semaphore(DEFAULT_CONCURRENCY)
@staticmethod
def _parse_ports(raw_ports: str) -> List[int]:
ports = []
for item in raw_ports.split(","):
item = item.strip()
if not item:
continue
try:
ports.append(int(item))
except ValueError:
print(f"[!] Invalid port in HW_DECOY_PORTS: {item}")
return ports or DEFAULT_DECOY_PORTS
@staticmethod
def _load_banner(raw_banner: str) -> bytes:
if not raw_banner:
return b""
return raw_banner.encode("utf-8").decode("unicode_escape").encode("utf-8")
async def _capture_connection(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, port: int):
peer = writer.get_extra_info("peername")
source_ip = peer[0] if peer else "Unknown"
start_time = time.time()
captured_payload = []
consumed_bytes = 0
try:
if self.tarpit_banner and self.tarpit_mode != "close":
writer.write(self.tarpit_banner)
await writer.drain()
if self.tarpit_mode != "close":
while consumed_bytes < self.max_bytes and (time.time() - start_time) < self.max_duration:
try:
timeout = 300.0
data = await asyncio.wait_for(reader.read(1024), timeout=timeout)
if not data:
break
decoded = data.decode("utf-8", errors="replace").strip()
if decoded and len(captured_payload) < self.max_lines:
captured_payload.append(decoded)
consumed_bytes += len(data)
if self.tarpit_mode == "echo":
writer.write(data)
await writer.drain()
await asyncio.sleep(0.5)
except asyncio.TimeoutError:
if not writer.is_closing():
writer.write(b"\0")
await writer.drain()
except Exception as exc:
print(f"[!] Connection processing error from {source_ip}:{port} - {exc}")
finally:
if not writer.is_closing():
try:
writer.close()
await writer.wait_closed()
except Exception:
pass
duration = time.time() - start_time
event_payload = {
"duration_sec": round(duration, 2),
"payload": captured_payload,
}
await asyncio.to_thread(
self.report_event,
event_type="tcp_connection",
severity=self.severity,
details=event_payload,
action_taken=self.tarpit_mode,
source=source_ip,
target=f"Port {port}",
)
async def _serve_connection(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, port: int):
async with self.semaphore:
await self._capture_connection(reader, writer, port)
async def monitor(self):
print(f"[*] HoneyWire Agent | Mode: {self.tarpit_mode.upper()} | Severity: {self.severity}")
listeners = []
for port in self.decoy_ports:
listener = await asyncio.start_server(
lambda r, w, p=port: self._serve_connection(r, w, p),
"0.0.0.0",
port,
)
listeners.append(listener.serve_forever())
print(f"[+] Tarpit listening on port {port}")
await asyncio.gather(*listeners)
if __name__ == "__main__":
sensor = TcpTarpitSensor()
try:
asyncio.run(sensor.start())
except KeyboardInterrupt:
print("\n[*] Shutting down HoneyWire Tarpit...")
@@ -1,18 +0,0 @@
services:
tcp-tripwire:
# Option A: Pull the pre-built image from GHCR (Uncomment lines below to use)
# image: ghcr.io/andreicscs/honeywire-tcptripwire:latest
# Option B: Build locally from source (Comment lines below if you want to use the pre-built image)
build:
context: ../../../
dockerfile: Sensors/official/TcpTripWire/Dockerfile
container_name: hw-tcp-tripwire
restart: unless-stopped
# CRITICAL: Binds directly to the host machine's network to capture real source IPs
network_mode: "host"
env_file:
- .env
@@ -1,3 +0,0 @@
# requirements.txt
aiohttp==3.13.3
requests==2.31.0
@@ -1,9 +0,0 @@
# Core HoneyWire configuration
HW_HUB_ENDPOINT=http://192.168.1.100:8080
HW_HUB_KEY=change_this_to_a_secure_random_string
HW_SENSOR_ID=fake-router-admin-01
HW_SEVERITY=critical
# Router deception settings
HW_BIND_PORT=8080
HW_ROUTER_BRAND=Netgear
+14 -21
View File
@@ -1,27 +1,20 @@
# --- Stage 1: Builder ---
FROM python:3.11-slim AS builder
WORKDIR /app
# STAGE 1: Build
FROM golang:1.25 AS builder
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
WORKDIR /src
COPY SDKs/go-honeywire ./SDKs/go-honeywire
COPY Sensors/official/WebRouterDecoy-GO ./Sensors/official/WebRouterDecoy-GO
# 1. Copy the SDK source from the repository root.
COPY SDKs/python-honeywire /tmp/sdk_src
WORKDIR /src/Sensors/official/WebRouterDecoy-GO
RUN go mod download
# 2. Build a wheel for the SDK.
RUN pip install --no-cache-dir wheel && \
pip wheel /tmp/sdk_src --wheel-dir /tmp/wheels
# Pure Go, fully static binary
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o web-decoy ./main.go
# 3. Install the wheel and sensor-specific dependencies.
COPY Sensors/official/WebRouterDecoy/requirements.txt .
RUN pip install --no-cache-dir --target /app/lib /tmp/wheels/honeywire-*.whl -r requirements.txt
# STAGE 2: Distroless
FROM gcr.io/distroless/static-debian12:nonroot
# --- Stage 2: Distroless ---
FROM gcr.io/distroless/python3-debian12
WORKDIR /app
COPY --from=builder /app/lib /app/lib
COPY Sensors/official/WebRouterDecoy/WebRouterDecoy.py .
COPY --from=builder /src/Sensors/official/WebRouterDecoy-GO/web-decoy /app/web-decoy
ENV PYTHONPATH="/app/lib"
ENV PYTHONUNBUFFERED=1
CMD ["WebRouterDecoy.py"]
USER 65532
CMD ["/app/web-decoy"]
+31 -40
View File
@@ -1,55 +1,46 @@
# HoneyWire Official Sensor: Web Router Decoy
The Web Router Decoy sensor emulates a consumer router login panel and captures credentials when an attacker (or curious insider) submits the form. It returns an HTTP 401 response to keep the attacker guessing.
The Web Router Decoy is a web honeypot designed to detect credential stuffing, automated web scanners, and targeted administrative panel attacks. It serves a deceptive, router login page. When an attacker attempts to log in, the sensor captures their IP, user agent, and attempted credentials, silently reports them to the HoneyWire Hub, and safely returns a "401 Unauthorized" response to keep them guessing.
## Features
* **Web canary:** Low-resource trap that looks like a router admin page.
* **Credential capture:** Logs attempted username and password.
* **Deceptive response:** Always returns `401 Unauthorized` to preserve the illusion.
* **HoneyWire-native reporting:** Sends standardized JSON events to the Hub.
* **Zero-Setup SDK Integration:** Natively built on the HoneyWire Go SDK.
* **Dynamic Brand Variable:** Automatically injects the specified router brand (e.g., Cisco, Netgear, ASUS) directly into the HTML template to make the trap more convincing.
* **Distroless Container:** Compiled as a statically-linked binary running inside a hardened, unprivileged `:nonroot` Distroless Docker image to prevent container breakouts.
## Configuration
Copy `.env.example` to `.env` and update the core HoneyWire settings.
All configuration is handled via Environment Variables. Copy the `.env.example` file to `.env` before running.
### Core Ecosystem Variables (Required)
| Variable | Description | Example |
|---|---|---|
| `HW_HUB_ENDPOINT` | The URL of your central HoneyWire Hub. | `http://127.0.0.1:8080` |
| `HW_HUB_KEY` | The shared secret API key to authenticate with the Hub. | `super_secret_key_123` |
| `HW_SENSOR_ID` | A unique identifier for this specific trap. | `web-decoy-01` |
| `HW_SEVERITY` | Alert severity sent to the Hub (`info` to `critical`). | `critical` |
### Sensor-Specific Variables
| Variable | Description | Default |
|---|---|---|
| `HW_HUB_ENDPOINT` | HoneyWire Hub API endpoint | `http://192.168.1.100:8080` |
| `HW_HUB_KEY` | HoneyWire API key | `super_secret_key_123` |
| `HW_SENSOR_ID` | Unique sensor identifier | `web-router-decoy-01` |
| `HW_SEVERITY` | Alert severity | `critical` |
| `HW_BIND_PORT` | Container port to listen on | `8080` |
| `HW_ROUTER_BRAND` | Display name for the fake router UI | `Netgear` |
| `HW_BIND_PORT` | The TCP port the fake web server will listen on. | `8080` |
| `HW_ROUTER_BRAND` | The brand name injected into the fake login page. | `Netgear` |
## Deployment
This sensor can use host networking for direct access to the network interface. When using host mode, set HW_BIND_PORT to 80 to listen on the standard web port. Alternatively, use standard Docker bridging and map host port 80 to container port 8080.
Example `docker-compose.yml` (host mode):
```yaml
services:
web-router-decoy:
build:
context: ../../../
dockerfile: Sensors/official/WebRouterDecoy/Dockerfile
container_name: hw-web-router-decoy
network_mode: "host"
env_file:
- .env
It is highly recommended to deploy this sensor using the provided docker-compose.yml configuration and the provided Dockerfile (if building from source). The compose file automatically applies the strict network and kernel capability rules required for safe execution.
```Bash
docker compose up -d
```
Ensure HW_BIND_PORT=80 in your .env for host mode.
## Security Architecture
## Event Example
```json
{
"event_type": "web_login_attempt",
"severity": "critical",
"source": "192.168.1.15",
"target": "Web Interface",
"details": {
"user_agent": "Mozilla/5.0...",
"attempted_username": "admin",
"attempted_password": "password123"
}
}
```
This sensor is architected for extreme resilience against web-based exploits. By utilizing a minimal attack surface and enforcing strict container sandboxing.
**Core Defense-in-Depth Measures:**
* **Framework-Free Execution:** Built purely on Go's native `net/http` library, eliminating the massive attack surface and supply-chain risks associated with heavy third-party web frameworks (like FastAPI, Flask, or Express).
* **Unprivileged Execution:** Runs entirely as a non-root user (`UID 65532`), preventing system-level modifications even in the event of a container breach.
* **Kernel Capability Stripping:** Drops all Linux kernel capabilities (`cap_drop: ALL`) via the Docker Compose configuration, neutralizing advanced kernel exploitation techniques.
* **Distroless Isolation:** Built on a statically-linked Distroless image. It completely lacks a shell (`/bin/sh`), package managers, or standard Linux utilities (like `curl` or `wget`), leaving attackers with zero tools to download secondary payloads or pivot to the host network.
* **In-Memory Operation:** Processes all payload data exclusively in memory, ensuring zero malicious disk I/O operations occur on the host system.
*Recommendation: For optimal security, always deploy this sensor using the official `docker-compose.yml` and `Dockerfile` to ensure these sandbox protections are strictly enforced by the container runtime.*
@@ -1,113 +0,0 @@
"""
HoneyWire Official Sensor: Web Router Decoy
Sensor type: web_honeypot
This sensor serves a deceptive router administration login page and captures credentials when an attacker submits the form.
"""
import asyncio
import os
from typing import Dict
from fastapi import FastAPI, Form, Request, status
from fastapi.responses import HTMLResponse, PlainTextResponse
from fastapi.middleware.cors import CORSMiddleware
from honeywire import HoneyWireSensor
import uvicorn
LOGIN_PAGE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Router Login</title>
<style>
body { background: #f1f5f9; color: #111827; font-family: Inter, system-ui, sans-serif; }
.card { max-width: 420px; margin: 6rem auto; padding: 2rem; background: white; box-shadow: 0 24px 80px rgba(15,23,42,0.08); border-radius: 1rem; }
h1 { margin-bottom: 1.25rem; font-size: 1.75rem; letter-spacing: -.03em; }
label { display: block; margin-top: 1rem; font-size: 0.9rem; color: #374151; }
input { width: 100%; margin-top: 0.5rem; padding: 0.9rem 1rem; border: 1px solid #d1d5db; border-radius: 0.75rem; }
button { width: 100%; margin-top: 1.75rem; padding: 0.95rem 1rem; background: #0f766e; color: white; border: none; border-radius: 0.75rem; font-weight: 700; cursor: pointer; }
.footer { margin-top: 1.5rem; font-size: 0.85rem; color: #6b7280; }
</style>
</head>
<body>
<div class="card">
<h1>Router Login</h1>
<p>Sign in to the router administration panel.</p>
<form method="post" action="/login">
<label>Username</label>
<input name="username" type="text" autocomplete="username" value="admin" />
<label>Password</label>
<input name="password" type="password" autocomplete="current-password" />
<button type="submit">Login</button>
</form>
<div class="footer">If credentials are invalid, please try again.</div>
</div>
</body>
</html>
"""
class WebRouterDecoy(HoneyWireSensor):
def __init__(self):
super().__init__(sensor_type="web_honeypot")
self.port = int(os.getenv("HW_BIND_PORT", "8080"))
self.router_brand = os.getenv("HW_ROUTER_BRAND", "Netgear")
self.app = FastAPI()
self.app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["GET", "POST"],
allow_headers=["*"]
)
self._register_routes()
def _register_routes(self):
@self.app.get("/", response_class=HTMLResponse)
async def root(request: Request):
return HTMLResponse(LOGIN_PAGE)
@self.app.post("/login", response_class=HTMLResponse)
async def login(request: Request, username: str = Form(""), password: str = Form("")):
user_agent = request.headers.get("user-agent", "Unknown")
source_ip = request.client.host if request.client else "Unknown"
details = {
"user_agent": user_agent,
"attempted_username": username,
"attempted_password": password,
}
await asyncio.to_thread(
self.report_event,
event_type="web_login_attempt",
severity="critical",
details=details,
action_taken="logged",
source=source_ip,
target="Web Interface",
)
return HTMLResponse(
content="<h1>401 Unauthorized</h1><p>Invalid Username or Password.</p>",
status_code=status.HTTP_401_UNAUTHORIZED,
)
async def monitor(self):
print(f"[*] HoneyWire Web Router Decoy Sensor | Port {self.port} | Severity: {self.severity}")
config = uvicorn.Config(self.app, host="0.0.0.0", port=self.port, log_level="warning")
server = uvicorn.Server(config)
await server.serve()
if __name__ == "__main__":
sensor = WebRouterDecoy()
try:
asyncio.run(sensor.start())
except KeyboardInterrupt:
print("\n[*] Shutting down Web Router Decoy sensor...")
@@ -1,9 +1,25 @@
services:
web-router-decoy:
web-decoy:
container_name: hw-web-decoy
build:
context: ../../../
dockerfile: Sensors/official/WebRouterDecoy/Dockerfile
container_name: hw-web-router-decoy
context: ../../..
dockerfile: Sensors/official/WebRouterDecoy-GO/Dockerfile
restart: unless-stopped
# Binds directly to the host machine to preserve Source IPs
network_mode: "host"
env_file:
- .env
# SECURITY HARDENING
user: "65532:65532"
cap_drop:
- ALL
environment:
- HW_HUB_ENDPOINT=http://127.0.0.1:8080
- HW_HUB_KEY=change_this_to_a_secure_random_string
- HW_SENSOR_ID=production-web-decoy-01
- HW_TEST_MODE=false
# Web Honeypot Configuration
- HW_BIND_PORT=8081
- HW_ROUTER_BRAND=Cisco
+7
View File
@@ -0,0 +1,7 @@
module github.com/honeywire/sensors/webrouterdecoy
go 1.25.0
replace github.com/honeywire/sdk-go => ../../../SDKs/go-honeywire
require github.com/honeywire/sdk-go v0.0.0-00010101000000-000000000000
+134
View File
@@ -0,0 +1,134 @@
package main
import (
"fmt"
"log"
"net"
"net/http"
"os"
"github.com/honeywire/sdk-go"
)
var (
port = getEnv("HW_BIND_PORT", "8080")
routerBrand = getEnv("HW_ROUTER_BRAND", "Netgear")
hw *sdk.Sensor
)
// The HTML template with %s placeholders for the router brand
const loginHTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>%s Administration</title>
<style>
body { background: #f1f5f9; color: #111827; font-family: Inter, system-ui, sans-serif; }
.card { max-width: 420px; margin: 6rem auto; padding: 2rem; background: white; box-shadow: 0 24px 80px rgba(15,23,42,0.08); border-radius: 0.5rem; }
h1 { margin-bottom: 1.25rem; font-size: 1.75rem; letter-spacing: -.03em; }
label { display: block; margin-top: 1rem; font-size: 0.9rem; color: #374151; }
input { width: 100%%; margin-top: 0.5rem; padding: 0.9rem 1rem; border: 1px solid #d1d5db; border-radius: 0.25rem; box-sizing: border-box; }
button { width: 100%%; margin-top: 1.75rem; padding: 0.95rem 1rem; background: #0f766e; color: white; border: none; border-radius: 0.25rem; font-weight: 700; cursor: pointer; }
.footer { margin-top: 1.5rem; font-size: 0.85rem; color: #6b7280; }
</style>
</head>
<body>
<div class="card">
<h1>%s Router</h1>
<p>Sign in to the administration panel.</p>
<form method="post" action="/login">
<label>Username</label>
<input name="username" type="text" autocomplete="username" value="admin" />
<label>Password</label>
<input name="password" type="password" autocomplete="current-password" />
<button type="submit">Login</button>
</form>
<div class="footer">If credentials are invalid, please try again.</div>
</div>
</body>
</html>`
func main() {
// 1. Initialize SDK
hw = sdk.NewSensor("web_honeypot")
hw.Start()
log.Printf("[*] HoneyWire Web Router Decoy | Brand: %s | Port: %s", routerBrand, port)
// 2. Register Routes
http.HandleFunc("/", handleRoot)
http.HandleFunc("/login", handleLogin)
// 3. Start the Server
addr := fmt.Sprintf("0.0.0.0:%s", port)
if err := http.ListenAndServe(addr, nil); err != nil {
log.Fatalf("[!] FATAL: Web server failed: %v", err)
}
}
func handleRoot(w http.ResponseWriter, r *http.Request) {
// Only respond to GET requests on the root path
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
// Inject the brand name into the HTML
fmt.Fprintf(w, loginHTML, routerBrand, routerBrand)
}
func handleLogin(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
// Parse the submitted form data
if err := r.ParseForm(); err != nil {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
username := r.FormValue("username")
password := r.FormValue("password")
userAgent := r.UserAgent()
if userAgent == "" {
userAgent = "Unknown"
}
// Extract the real IP (strip the ephemeral port)
srcIP := r.RemoteAddr
if ip, _, err := net.SplitHostPort(srcIP); err == nil {
srcIP = ip
}
log.Printf("[!] Login attempt from %s (User: %s)", srcIP, username)
// Dispatch the Event via the SDK
hw.ReportEvent(
"web_login_attempt",
"critical",
map[string]any{
"user_agent": userAgent,
"attempted_username": username,
"attempted_password": password,
},
"logged",
srcIP,
"Web Interface",
)
// Always return 401 Unauthorized to keep them guessing
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprint(w, "<h1>401 Unauthorized</h1><p>Invalid Username or Password.</p>")
}
func getEnv(key, fallback string) string {
if val, exists := os.LookupEnv(key); exists {
return val
}
return fallback
}
@@ -1,4 +0,0 @@
# Sensor dependencies
fastapi
uvicorn[standard]
python-multipart
+27
View File
@@ -0,0 +1,27 @@
# STAGE 1: Build
FROM golang:1.25 AS builder
WORKDIR /src
# 1. Copy the shared SDK into the builder
COPY SDKs/go-honeywire ./SDKs/go-honeywire
# 2. Copy the specific sensor code (Change 'custom/MySensor' to your folder name)
COPY Sensors/custom/MySensor ./Sensors/custom/MySensor
# 3. Move into the sensor folder to build
WORKDIR /src/Sensors/custom/MySensor
RUN go mod download
# Compile the pure Go, fully static binary
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o custom-sensor ./main.go
# STAGE 2: Distroless Sandbox
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /src/Sensors/custom/MySensor/custom-sensor /app/custom-sensor
# Run as an unprivileged user
USER 65532
CMD ["/app/custom-sensor"]
+39
View File
@@ -0,0 +1,39 @@
# HoneyWire Go Sensor Template
Welcome to the HoneyWire ecosystem! This template contains everything you need to build a blazing-fast, strictly sandboxed custom Go security sensor that natively reports to the HoneyWire Hub.
## How to Build Your Sensor
1. **Copy this folder** to your desired location (e.g., `Sensors/custom/MyNewSensor`).
2. **Initialize the Go Module**: Link your new sensor to the local HoneyWire SDK by running these commands inside your new folder:
```bash
go mod init github.com/honeywire/sensors/mynewsensor
go mod edit -replace github.com/honeywire/sdk-go=../../../SDKs/go-honeywire
go mod tidy
```
3. **Write your logic** inside `main.go`. The file is heavily commented and shows you exactly where to put your detection loops.
4. **Update `docker-compose.yml` and `Dockerfile`**: Ensure the paths point to your newly named directory.
## Deployment
It is highly recommended to deploy this sensor using the provided `docker-compose.yml` configuration and the provided `Dockerfile` (if building from source). The compose file orchestrates the required security capabilities, user permissions, and network modes automatically.
```bash
docker compose up -d
```
## Security Architecture
This template is architected for extreme resilience against exploits out-of-the-box. By utilizing a minimal attack surface and enforcing strict container sandboxing, it ensures your custom detection logic cannot be weaponized against the host.
**Core Defense-in-Depth Measures:**
* **Unprivileged Execution:** Defaults to running entirely as a non-root user (`UID 65532`), preventing system-level modifications even in the event of a container breach.
* **Kernel Capability Stripping:** Drops all Linux kernel capabilities (`cap_drop: ALL`) via the Docker Compose configuration, neutralizing advanced kernel exploitation techniques.
* **Distroless Isolation:** Built on a statically-linked Distroless image. It completely lacks a shell (`/bin/sh`), package managers, or standard Linux utilities (like `curl` or `wget`), leaving attackers with zero tools to download secondary payloads or pivot to the host network.
* **In-Memory Operation:** Written in pure Go, encouraging in-memory packet or log processing without the need for vulnerable C-bindings or heavy framework dependencies.
*Recommendation: For optimal security, always deploy this sensor using the official `docker-compose.yml` and `Dockerfile` to ensure these sandbox protections are strictly enforced by the container runtime. If your sensor doesn't follow this security standard there should be a clear reason or a feature that requires otherwise.*
## CI/CD Requirement
To ensure your sensor works, our GitHub Actions will run it with `HW_TEST_MODE=true`. The HoneyWire Go SDK handles this automatically! When `hw.Start()` is called in that mode, it fires a synthetic payload to the Hub and gracefully exits. You do not need to write any custom testing logic.
@@ -0,0 +1,36 @@
version: '3.8'
services:
custom-sensor:
container_name: hw-custom-sensor
build:
# Step back to the repo root so Docker can see both the Sensor and the SDK
context: ../../..
dockerfile: Sensors/custom/MySensor/Dockerfile
restart: unless-stopped
# Uncomment if you need to capture real Source IPs or bind to host ports
# network_mode: "host"
# ---------------------------------------------------------
# SECURITY HARDENING
# ---------------------------------------------------------
# Non-root user for the Distroless container
user: "65532:65532"
# Drops all kernel capabilities for maximum security
cap_drop:
- ALL
# cap_add:
# - NET_RAW # Uncomment ONLY if your Go code requires raw network sockets
environment:
- HW_HUB_ENDPOINT=http://127.0.0.1:8080
- HW_HUB_KEY=change_this_to_a_secure_random_string
- HW_SENSOR_ID=custom-sensor-01
- HW_TEST_MODE=false
- HW_SEVERITY=medium
# Custom Variables
- HW_CUSTOM_TARGET=/tmp/honey
+7
View File
@@ -0,0 +1,7 @@
module github.com/honeywire/sensors/custom-template
go 1.25.0
replace github.com/honeywire/sdk-go => ../../../SDKs/go-honeywire
require github.com/honeywire/sdk-go v0.0.0-00010101000000-000000000000
+58
View File
@@ -0,0 +1,58 @@
package main
import (
"log"
"os"
"time"
"github.com/honeywire/sdk-go"
)
var (
// Load custom configuration from environment variables
severity = getEnv("HW_SEVERITY", "medium")
target = getEnv("HW_CUSTOM_TARGET", "/tmp/honey")
)
func main() {
// 1. Initialize the HoneyWire SDK
// Change 'custom' to whatever category fits your sensor (e.g., 'network', 'file', 'auth')
hw := sdk.NewSensor("custom")
// Start the SDK (This automatically handles Hub syncing and HW_TEST_MODE for CI/CD)
hw.Start()
log.Printf("[*] Starting Custom Go Sensor | Target: %s | Severity: %s", target, severity)
// 2. --- YOUR SENSOR LOGIC GOES HERE ---
// This is the main loop of your sensor. Do not let the main function exit!
for {
// Example: Waiting for an event...
time.Sleep(60 * time.Second)
// Assume an attack happened!
log.Printf("[!] Attack detected! Gathering forensics...")
sourceIP := "192.168.1.100" // Replace with actual extracted data
// 3. Send the alert to the Hub using the SDK's built-in method
hw.ReportEvent(
"custom_anomaly_detected", // Event Type
severity, // Severity
map[string]any{ // Details (Custom JSON payload)
"attack_type": "example_probe",
"raw_payload": "GET /etc/passwd HTTP/1.1",
},
"logged", // Action Taken
sourceIP, // Source
"Custom Trap", // Target
)
}
}
// Helper function to easily grab environment variables with fallbacks
func getEnv(key, fallback string) string {
if val, exists := os.LookupEnv(key); exists {
return val
}
return fallback
}