flowchart TD
ATK(["Attacker"])
subgraph HWZ["HONEYWALL ZONE"]
NFT["nftables<br/>packet filter + log"]
SUR["Suricata<br/>NIDS"]
AUTH["auth logs"]
end
subgraph SENS["SENSOR ZONE"]
COW["Cowrie<br/>SSH, Telnet"]
DIO["Dionaea<br/>HTTP, MySQL…"]
end
subgraph CLZ["COLLECTOR ZONE"]
subgraph STREAM["Vector (all zones)"]
GEO["GeoIP<br/>MaxMind GeoLite2"] --> NRM["OCSF normalization"]
end
BRZ[("🥉 BRONZE<br/>raw, GeoIP, OCSF")]
subgraph BATCH["D+1"]
IOC["IOC extraction"] --> CACHE[("cache")]
CACHE -- "miss" --> EXT["VirusTotal, GreyNoise…"]
EXT -- "store" --> CACHE
end
SLV[("🥈 SILVER<br/>enriched logs")]
GLD[("🥇 GOLD<br/>top IPs, credentials, geo…")]
NRM --> BRZ --> IOC
CACHE -- "enriched" --> SLV --> GLD
end
ATK -. "attack traffic" .-> NFT
NFT -. "attack traffic" .-> SENS
NFT & SUR & AUTH -- "logs" --> STREAM
COW & DIO -- "commands, credentials, files…" --> STREAM
As someone who had deployed two major SIEMs, IBM QRadar and Google Chronicle, I wanted to reason about the log processing pipeline end-to-end, from ingestion to data extraction. However, I wanted to avoid the burden of creating test log sources to focus solely on the pipeline. At the same time, I wanted to create something beyond a simple laboratory; something other people could use too.
As the person primarily responsible for the Adversary Engagement program at work, the idea of using honeypots as data sources clicked quickly. Although this field has a solid option for this purpose, T-Pot, it relies on the Elastic Stack to process data and focuses more on the number of decoys than on their customization.
It was then that the idea of Lantana emerged: fewer honeypots with more convincing narratives for better data collection, dual-stack (IPv4 and IPv6), fully deployed via Infrastructure as Code (IaC), a Honeypot as Code platform. On top of that, I’d be able to learn modern ways of deploying Linux servers (the last one I had deployed was in 2018, with legacy tools like iptables), and implement the data pipeline as I wanted. The logs would be produced by attackers. No need for extra VMs. Also, as a threat detection engineer, I’d be able to implement things I had always wanted from Cyber Intelligence teams.
The Foundation
I started by analyzing T-Pot and other open-source honeypots to sketch out the project. It was early December 2025. As I mentioned, T-Pot is a great project, but its stack is too bloated for my purposes: more than 20 honeypots supported, the default installation pays no attention to customizing the narrative (something extremely important to create convincing decoys that attract more than automated scanners) and most importantly, the data pipeline is entirely built on the Elastic Stack (Elasticsearch, Logstash, and Kibana — ELK). It works, but it’s heavy, requiring more powerful servers, and the data processing is hidden behind ELK.
However, T-Pot had a few decisions I liked: it creates a dummy NIC on the server to segregate network traffic, it implements Suricata (an IDS) to supplement honeypot data, and containerizing honeypots proved to be a great isolation approach. All things I wanted to replicate.
After some research, I decided on the foundational stack:
- Target OS: Debian 13. Open-source, widely supported, with plenty of tools and hardening options. A good balance between up-to-date packages and tested software.
- Deployment method: Ansible. I won’t deny it: in the early days, I started with shell scripts to configure the server, just like years ago. But it didn’t take long to realize that was against my goal of using modern methods. Ansible stood out as the best choice: idempotent, widely used, Red Hat-maintained, stable, and secure.
- Honeypots: Cowrie and Dionaea. After reviewing T-Pot’s supported honeypots, these two stood out as the most recommended and widely used. Together they cover most of the protocols I had in scope.
- Data pipeline: Python, OCSF, Polars, and Streamlit. Python offers the best balance between speed and usability for this kind of work. Rust would offer better performance, but Python has the right trade-off. Polars and Streamlit followed naturally: I had read comparisons praising Polars for its speed over Pandas, and this Streamlit demo convinced me it was the right tool for delivering a dashboard rather than a notebook for data exploration. As a Google Chronicle user, I initially considered parsing logs to UDM format, but preferred an open-source, community-driven alternative: OCSF.
With this foundation defined, I started testing and experimenting with the tools while building the early version of Lantana. It was during this phase that I chose the name. I wanted a poisonous plant, common across the world, easy to propagate, and beautiful. Lantana 🏵️ grows everywhere in my region, so it was an easy choice.
Early Days of Development
Between December 2025 and January 2026, I was experimenting with this basic environment. The first major blocker came when the server was ready and I had to deploy the first honeypot. I decided to deploy only one at that point, since one would generate enough data to validate the rest of the configuration. Once validated, I’d add the second. Since Cowrie had more documentation and community resources, I went with it.
I added Docker to the stack and deployed a containerized Cowrie instance — another nod to T-Pot. No big problems so far. At this point, I realized I’d need a security tool to monitor the server, and since I was already working with Falco at work, I chose it. It was during the first security checks that I noticed Docker was bypassing many of my firewall rules. I was using iptables at the time, and making the two work together was painful.
After about two stressful development weeks and many parallel research threads (not full-time), I found I’d have to disable Docker’s iptables integration entirely to move forward. It was around then that I discovered Podman. It doesn’t configure the firewall by default and is praised for being daemonless. Part of my security concern with Docker centered on container escape: even with containers configured to run as unprivileged users, Docker’s daemon runs as root, leaving a privilege escalation path open on the host. Podman’s rootless mode closes that gap, as the entire runtime runs as a regular user.
It led to the first major change in the project: Docker out, Podman in. The Docker/iptables conflict also made it clear I was using a legacy tool. nftables is the modern standard for Linux firewalls. On top of that, I realized I needed to modernize my approach to Linux server management: adopted iproute2 and systemd, both default in Debian. No more ad hoc commands to configure the server, no more scripts left scattered across the environment; only standardized, predictable deployments.
At this point, I finally started touching the pipeline. Dealing with logs is tricky because they’re always coming, like a stream. That’s why ETL systems often work with D+1 data availability: rotate the logs at midnight, then process the rotated file since it won’t be updated anymore. After some research, I discovered Vector, an open-source data pipeline tool maintained by Datadog. Vector can tail log files and process them in near real-time. At first, I planned to use it for basic enrichment (GeoIP) and OCSF parsing, but at this stage it would just collect and forward log data to the data lake.
The data lake: at last, the core of my work. I had never built one before, so I had to read up on how to design a simple one. I settled on the Medallion architecture. In short, it divides data processing into bronze, silver, and gold layers: from raw data to business-ready aggregations. Vector would drop logs with minimal enrichment, plus OCSF parsing, into the bronze layer, and I’d build Python scripts from there.
Finally, during this period, I realized I could replace Falco with Suricata and monitor the host at a different level. But I’d postpone that until I had a working system. With all these changes, I reached an MVP by early January. I deployed it on my VPS, and when the first logs started arriving, I was genuinely proud. I had deployed a customized Cowrie instance following the narrative I’d defined, on a public server, with a single command. In under ten minutes after hitting enter. Honeypot as Code. 🪄
I didn’t elaborate on the importance of narrative here because it’s out of scope for this post — this is about Lantana’s engineering. If you want to learn more about it, read Adversary Engagement 101.
Full Architecture Review
I managed to deliver that MVP right before my vacation and was confident I’d finish the full project within a month. During my time off, I read Virtual Honeypots (Provos & Holz, 2007) and it proved to be a game-changer. The book made it clear my architecture needed to be more modular and support both single-node and multi-node deployments, multi-node being essential for high-interaction honeypots, whether for my own use or for anyone else running Lantana.
I took two steps back and fully reviewed what I had built. It turned out that some parts of the MVP were quick shortcuts that really needed a rework. The revised architecture looked like this:
- Debian 13 properly configured, especially SSH. Until then, I was configuring SSH via Ansible — it was painful. The better approach is to use preparation scripts or, better yet, IaC tools like Terraform for initial provisioning.
- Terraform. The IaC of choice for machine provisioning.
- Ansible. Now with revised tasks and no longer burdened with SSH configuration.
- nftables, iproute2, systemd, logrotate, and cron. Core Linux tooling. iproute2 creates the dummy
ltn0interface, nftables defines firewall rules, and systemd orchestrates services and ensures configurations survive restarts. logrotate handles daily log rotation and removes old files to keep the disk clean. cron executes periodic routines — fundamental to keeping the pipeline running. - Podman. The container runtime for honeypots — daemonless, all containers hardened.
- Suricata. Network visibility where honeypots can’t reach, plus host monitoring at the network layer — better data across the board.
- Vector. Stream data processing.
- Data Enrichment: GeoLite2, VirusTotal, Shodan, AbuseIPDB, GreyNoise, Phishstats.
- Python, Polars, and Streamlit. The data processing layer, yet to be developed.
- Notifications: Discord.
- Multiple zones, inspired by Virtual Honeypots:
- Honeywall. The security perimeter — internet-facing, with packet filtering and full logging.
- Sensor zone. The Podman domain where honeypots run.
- Collector zone. Where all data is directed, processed, and stored.
- Two modes of operation: single-node and multi-node. In single-node, all zones run on the same server, using the dummy
ltn0interface for network segregation. In multi-node, each zone can run on a dedicated server; additional collector nodes can be deployed manually, enabling high-interaction honeypot scenarios. - Full data utilization. From the honeywall to the honeypots, almost all data the server touches flows into the pipeline.
It took one or two weeks to come up with this design. Then I implemented what I call Alpha version — functionally equivalent to the first MVP, but on a proper foundation. The overall architecture was like this:
In this diagram, dotted edges represent attack traffic (the thing being studied) and solid edges represent log/data flow (the output). Although the pipeline was yet to be implemented, its design was already complete. Note the D+1 subgraph in the Collector zone. Here’s the breakdown:
- Logs are ingested and written to disk from the honeywall and sensor zone.
- Vector reads this data in streaming mode, adding geolocation for IP addresses through MaxMind’s GeoLite2 MMDB files. It also parses logs into OCSF format, saving the results in the bronze layer of the data lake.
- On D+1, a bit after midnight, the enrichment process is triggered, fetching threat intelligence from supported services. A local cache minimizes redundant API calls. The results are saved in the silver layer.
- Some time after that, the final process is started. It analyzes the enriched data and creates aggregated data with CTI insights. This layer produces a Markdown report, a Streamlit dashboard, and a STIX file for automation.
- Once this process is finished, a message is sent to a Discord channel with a run summary and the Markdown report attached.
IPv4 and IPv6 were requirements from the start, but I hadn’t anticipated how poor IPv6 support is in local development tooling. I spent many hours trying to get IPv6 working on Mac with VMware Fusion and UTM — no success. I only managed to test it properly once I moved the code to my server. IPv6 support in local dev tooling is still surprisingly poor in 2026, but adversaries routinely abuse it to bypass IPv4-focused defenses. Having it in the stack was non-negotiable.
AI’s Help
By November 2025, Google, OpenAI and Anthropic shipped significant updates that substantially changed how AI fits into a development workflow. Basically, they improved their architectures to unlock “System 2” thinking—allowing the AI to pause, plan, and execute deep reasoning across complex, multi-hour tasks. This supercharged existing tools like Anthropic’s Claude Code, transforming them from helpful terminal assistants into autonomous agents that could navigate massive codebases on their own — read more here. However, I wouldn’t experiment with them until late March 2026, after Alpha was done.
Between December and early March, I used Google Gemini Pro the same way we used to work 84 years ago, in 2025: open a chat, share as much context as you can, and interact with the model. Sometimes I’d open Gemini CLI and try to extract something useful from it, but most of the time it would break things.
I’d create a new thread with proper context on a specific topic (the trade-offs of switching from Docker to Podman, for instance) and discuss it with Gemini as if it were a fellow practitioner. On the technical side, I’d open threads to build Ansible playbooks or configuration files like logrotate configs. Most of the time, the answers were partially correct and I had to fix small but important details.
Compared to how I work with AI now, in June 2026, my workflow back then feels prehistoric. The models were sloppy, prone to hallucination, and demanded constant supervision. Lacking proper context window control, I constantly battled context saturation; a frustrating loop that frequently ended in pure prompt rage. Still, despite the friction, the AI genuinely helped me reason through critical design decisions. The proof is in the result: the foundation I settled on by early March held up perfectly, fully validated by the smarter models that came later.
After delivering Alpha, I decided to step back from Lantana and spend some time studying the new AI landscape. The buzz was real: something had fundamentally changed. Since my employer was also pushing in this direction, I adopted Claude, and it was the best decision. Claude and this new way of working with AI would help me accelerate Lantana big time.
What Comes Next
Alpha version was done by early March 2026. The foundation was solid, but the data pipeline was still unwritten — and the AI tooling I’d be using to build it was about to change completely.
In the next part, I’ll pick up in April, when I returned to Lantana with Claude Code. That’s where the real pipeline work happened: Vector transforms, Polars processing across the three data layers, Suricata wired into the stack, and all the integration friction that only surfaces when you start connecting everything. The post ends with Lantana v1 live.
Lantana Series
- Lantana 1: Honeypot as code
- Lantana 2: TBW
- Lantana 3: TBW
Reuse
Citation
@online{lopes2026,
author = {Lopes, Joe},
title = {Lantana 1: {Honeypot} as {Code}},
date = {2026-06-23},
url = {https://lopes.id/log/lantana-1-honeypot-as-code/},
langid = {en}
}