Collaborators

Opnsense, ELK, sysmon, and more

1. OPNsense

  1. To install OPNsense, initiate the login sequence by entering option 0 with the following administrative credentials:

login: installer
password: opnsense

  1. Select the preferred regional language layout for the system keyboard.

  1. Install the primary file system storage architecture, selecting ZFS for this deployment.

  1. Select the virtual storage device pool structure. In this scenario, we will not deploy a RAID system due to the availability of only a single virtual disk.

  1. Select the physical disk drive targeted for the OPNsense system installation.

  1. Select the confirmation option "yes". Note that this operation wipes all preexisting data blocks on the selected disk drive or array.

  1. Modify the default system root administrative password by selecting the "Root Password" configuration option.

  1. Input the new secure administrative password string.

  1. Select the "Complete Install" directive and click OK once the installation process finishes processing.

  1. Select the "Reboot now" operation and click OK to restart the system environment.

  1. Following the system reboot and initialization of OPNsense, the platform dynamically assigns the primary WAN and LAN network boundaries. We can now connect to the OPNsense graphical user interface (GUI) to provision our remaining internal networks.

  1. If adjustments are required for the automated network interface boundaries mapped by OPNsense, they must be reassigned directly through the command-line interface (CLI). Otherwise, configuration can continue natively through the web GUI.

  1. When opting for the CLI deployment method, a configuration wizard initiates to guide the user systematically through the interface mapping workflow.

  1. Following the successful configuration of the WAN network zone, we proceed with the mapping parameter definitions for the local LAN zone.

  1. Once the core WAN and LAN interfaces are properly mapped, commit and apply the pending system changes.

  1. Following the exact logical sequence deployed for the WAN gateway mapping, assign the remaining structural interfaces to their respective network zones.

  1. Once the WAN and LAN parameters are provisioned, connect to the web GUI using an endpoint workstation configured with a valid static IP address within the LAN segment, as the internal DHCP service is currently offline.

    Default Access Credentials: * Username: root
    Password: 1234*

  1. The initial administrative phase requires mapping the hardware network interfaces to logical identifiers. To do this, navigate to Interfaces / Assignments.

  1. Inside the configuration page, map the network interface cards to their respective logical designations, which represent the 3 isolated LAN segments of our infrastructure.

  1. Next, enable the structural network interfaces and provision the primary IP gateway addresses for each LAN segment. Navigate to Interfaces and select the target LAN instance.

    Activate the interface by selecting the Enable Interface checkbox. To configure the static gateway IP address, modify the IPv4 Configuration Type dropdown field to Static IPv4.

  1. Under the Static IPv4 configuration parameters, define the interface gateway IP address and click Save. Duplicate these steps sequentially for all remaining internal LAN interfaces.

  1. To configure the dynamic addressing services across our internal networks, navigate to Services / ISC DHCPv4, and select the specific LAN zone where the DHCP service needs to be enabled.

  2. After choosing the target LAN zone, check the Enable DHCP server on the work interface checkbox. Define the range of dynamic IP allocations reserved for local network hosts in the Range fields.

  1. Input the interface's local gateway address into the designated Gateway configuration field.

  1. Scroll to the bottom of the management dashboard page and click Save.

  1. Restart the system service daemon.

1.1. OPNsense Firewall

  1. The next engineering phase involves provisioning our firewall access control rules. For this deployment, security policy dictates that the internal network zone ("inter") host sandbox machines containing unverified code repositories; therefore, this zone must remain completely isolated. The "inter" network zone must only receive inbound SSH traffic initiated explicitly from the DMZ zone. No workloads inside the "inter" zone are permitted to initiate outbound requests to other local networks.

    To configure these access control policies, navigate to Firewall -> Rules -> <network>. For this segment, choose the inter interface policies.

  2. Create a dedicated firewall rule on the "inter" interface that exclusively allows inbound SSH sessions originating from the DMZ subnet. Click the + action icon to append a new rule definition.

  3. Set the administrative action of this policy rule to Pass, allowing traffic matching the criteria defined below to pass through the packet filter.

  4. Set the target interface constraint to the specific hardware network card mapped to the inter zone.

  5. Set the traffic direction parameter to in. By default, OPNsense firewall rule logic filters inbound traffic vectors on a given interface. Outbound configuration directions are rarely necessary; the core firewall uses a default-deny posture. If only an inbound rule exists without an explicitly corresponding outbound rule, the firewall automatically blocks all traffic paths that do not match the inbound rule criteria.

  6. Set the acceptable layer 4 transmission protocol to TCP, which matches the transport standard used by SSH.

  1. Set the packet source context to the DMZ net object and define the destination target as the inter net object.

  2. Set the destination port scope constraint exclusively to SSH. Leave all remaining extended structural configurations at their default settings and click Save.

  1. Create an explicit ANY rule on the DMZ interface network profile. This policy allows DMZ nodes to send and receive any traffic standard, including outbound access to the public internet. Because OPNsense defaults to a strict default-deny model, omitting this permissive definition would result in total isolation of the DMZ network zone.

  2. The next phase involves validating our access control rules. For validation testing, we deploy a Kali Linux instance running an active openssh-server instance within the isolated "inter" network zone, along with a Windows workstation acting as the testing node.

    As verified by the terminal capture, the Windows client successfully initiates and establishes a remote SSH session to the Kali Linux target host.

  3. Next, we test the outbound network isolation of the "inter" zone by attempting to forge outbound requests from the Kali Linux host to external networks.

    The terminal capture confirms that the Kali Linux instance cannot resolve public web requests or reach adjacent protected networks (validated via a failed curl request hitting a T-Pot honeypot node listening on the DMZ network segment).

2. ELK

To build our centralized logging environment, we deploy 3 discrete server nodes: one dedicated to Elasticsearch, another hosting the Kibana visual portal, and a third running Logstash. We configured isolated SSH sessions on each server node to improve text readability and optimize our command pipeline. The deployment logs and installation steps are detailed below:

2.1. Elasticsearch

  1. First, we must scale the system kernel variable vm.max_map_count to a minimum value of 262144. The default operating system values are insufficient for Elasticsearch memory allocations and will trigger runtime initialization failures.
echo "vm.max\_map\_count=262144" | sudo tee \-a /etc/sysctl.confsudo sysctl \-p 

  1. Next, install the official Elasticsearch repository architecture along with the corresponding cryptographic signing keys into the host operating system instance:
sudo apt install apt-transport-https ca-certificates curl gnupg \-ycurl \-fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg \--dearmor \-o /usr/share/keyrings/elastic.gpgecho "deb \[signed-by=/usr/share/keyrings/elastic.gpg\] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.listsudo apt update
  1. Once the package index is synchronized, install the application package using the native package management utility. Because our target nodes run Ubuntu Server, we use the apt engine.

Crucial Operational Note: During this automated package setup phase, the installation script dynamically generates and outputs a temporary superuser credential for the root account. This string must be securely documented.

Administrative Notice: The root administrative password string recorded for this lab deployment is: zBlLm2UOlj_rCerTDwqJ

  1. Once the package installation finishes, configure the system service manager to enable the service at boot, start the daemon, and audit its runtime operational status:
sudo systemctl enable –now elasticsearchsudo systemctl status elasticsearch

  1. Verify API accessibility by querying the local Elasticsearch engine endpoint using the secure credentials recorded during the package configuration phase in Step 3.

  1. After authenticating against the endpoint, the API indicates normal operation by returning a JSON confirmation payload structured like the capture below:

  1. Next, generate a cryptographic cluster enrollment token string to securely pair our Kibana visual node to the backend Elasticsearch API layer:
sudo /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token \-s kibana

Generated Enrollment Token String: eyJ2ZXIiOiI4LjE0LjAiLCJhZHIiOlsiMTkyLjE2OC4zMS41MDo5MjAwIl0sImJnciI6IjQ4YjdlMTkzZjgzZTkxOTNkZThjYmRkMjMwMzY5ZDg4NTJmZGFlNDQ5OGY5YzgwMWYzZjhiNjg2ODRmOGQzMzkiLCJrZXkiOiJuOGo5dnB3QjAzT2swSUlKXzhweDptaGtybGtBeGlXUUViZXNPRW9wVGN3In0=

This generated security token string will be used later during the Kibana service enrollment setup to link the log analytics layers.

2.2. Kibana

  1. Begin by importing the standard Elastic repository dependencies and the cryptographic repository signature definitions onto the secondary visualization server node:
sudo apt install apt-transport-https ca-certificates curl gnupg \-ycurl \-fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg \--dearmor \-o /usr/share/keyrings/elastic.gpgecho "deb \[signed-by=/usr/share/keyrings/elastic.gpg\] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.listsudo apt update
  1. Once repository synchronization completes, install the Kibana software application package via apt:
sudo apt install kibana 
  1. Register and start the Kibana initialization service using the system control daemon:
sudo systemctl enable \--now kibana
  1. Open a web browser interface and browse to the Kibana management server port (5601). Paste the secure cluster enrollment token generated on the Elasticsearch server node during Step 7.

  1. The portal interface will prompt for a secondary security confirmation code string. Generate this localized authentication code by executing the native binary kibana-verification-code on the host console:

  1. Input the generated numerical confirmation code string directly into the portal authentication prompt.

  1. Upon code submission, Kibana starts its internal cluster connection mapping routines.

  1. Once the login interface processes, authenticate using the superuser identifier elastic and the secure operational password string recorded during Step 3 of the Elasticsearch setup phase.

  1. To provision initial ingestion channels directly, select the Add integrations control option.

  1. Successful access to the primary Kibana monitoring dashboard interface confirms that the visualization service deployment is complete.

2.3. Logstash

  1. First, import the standard deployment repositories and target GPG security signatures onto the third pipeline log server node:
sudo apt install apt-transport-https ca-certificates curl gnupg \-ycurl \-fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg \--dearmor \-o /usr/share/keyrings/elastic.gpgecho "deb \[signed-by=/usr/share/keyrings/elastic.gpg\] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.listsudo apt update
  1. Execute the installation pipeline for the Logstash routing engine using the native package manager:
sudo apt install logstash \-y
  1. Register and enable the Logstash data processing service daemon:
sudo systemctl enable \--now logstash
  1. Provision a custom configuration profile to orchestrate traffic pipelines and forward parsed log telemetry records to the Elasticsearch backend cluster instance:
sudo nano /etc/logstash/conf.d/elk-lab.conf
input {
    stdin {}
}

filter {
  # Add future filters here (grok, mutate, date...)
}

output {
  elasticsearch {
    hosts => ["[https://192.168.31.50:9200](https://192.168.31.50:9200)"]
    user => "elastic"
    password => "zBlLm2UOlj_rCerTDwqJ"
    ssl => true
    ssl_certificate_verification => false
    index => "logs-lab-%{+YYYY.MM.dd}"
  }

  stdout { codec => rubydebug }
}

  1. Run the Logstash binary engine explicitly with the custom configuration pipeline file we just created:
sudo /usr/share/logstash/bin/logstash \-f /etc/logstash/conf.d/elk-lab.conf

  1. For ingestion testing, the stdin {} input block captures arbitrary text strings typed into the terminal window and routes them as standard log structures directly to the Elasticsearch API endpoint.

  1. Navigate to the Discover module workspace inside the Kibana interface to interact with the inbound event data stream logs.

  1. Inspect the interface log records inside Kibana to verify that the arbitrary test log entries match the structures processed through the Elasticsearch API.

  1. Modify the ingestion rules by adding a dedicated listener port to capture log streams sent over the Beats communication protocol from decentralized Filebeat agents across the architecture:
input {
    beats {
        port => 5044
        host => "0.0.0.0"  
    }
}

  1. Restart the Logstash engine process using the system control utility to apply the new pipeline configuration parameters:
sudo systemctl restart logstash

2.4. Filebeats

  1. First, download the core application repository definitions onto the targeted client workstation node:
curl \-fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg \--dearmor \-o /usr/share/keyrings/elastic.gpgecho "deb \[signed-by=/usr/share/keyrings/elastic.gpg\] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.list sudo apt update
  1. Install the localized log shipper agent package via apt:
sudo apt install filebeat \-y
  1. Open the primary configuration file located at /etc/filebeat/filebeat.yml. Assign a unique host identifier to the node and enable the Filebeat tracking engine.

  1. Comment out the default direct Elasticsearch output configuration blocks. Our network design routes all log collections through Logstash for parsing before indexing.

  1. Scroll down to populate custom endpoint metadata parameters and operational classification tags. This step is optional but helps with categorization within the central SIEM index.

  1. Configure the active output parameters to point to the remote Logstash server host IP address and the dedicated Beats tracking port configured earlier.

  1. Enable the native operating system monitoring modules within the Filebeat framework:
sudo filebeat modules enable system

  1. Run the built-in validation test tool to ensure the file syntax adheres to standard YAML formatting guidelines:
sudo filebeat test config

  1. Validate the output connectivity parameters to verify that communication channels to the remote Logstash ingestion interface are open:
sudo filebeat test output

  1. Once all diagnostic verification passes return clean status results, start and register the Filebeat collection daemon:
sudo systemctl enable \--now filebeat
  1. Monitor the live ingestion process stream on the Logstash server console using the system journal tracking tool to verify successful log delivery:
sudo journalctl \-u logstash \-f

  1. Run an administrative index query against the active cluster node to verify that all data flows are tracking correctly.

2.5. Sysmon

  1. System Monitor (Sysmon) is a Windows system service and device driver that remains resident across system reboots. It monitors and logs system activity directly to the Windows Event Log, providing detailed tracking data on process creation, network connections, and file modification timestamps.

  2. Documentation link: https://learn.microsoft.com/en-us/sysinternals/downloads/sysmon

  3. The software package can be deployed using multiple administrative methods depending on environment constraints.

  4. Execute the core installation sequence via the command line with the following argument:

  5. sysmon -i

3. T-POT Honeypot + External ELK Integration

  1. To install the T-Pot framework, clone the official codebase repository out of GitHub into your local server workspace:

Default Credentials:
Username: admin
Password: 1234

  1. Navigate into the cloned repository directory. Elevate file execution permissions on the install.sh shell script and run the installation script. Enter the confirmation flag y to proceed with the T-Pot automated setup.

Prerequisite requirement: The local host must have an active instance of the openssh-server service preinstalled.

  1. For our environment deployment, the target Elastic Stack instance is distributed across separate processing servers operating on the same local subnet.

  2. Once the setup script finishes processing, reboot the system. Hardening the SSH daemon configuration afterward is recommended.

  3. Before running the sensor deployment routines, generate a cryptographic SSH keypair on the administrative system for the user account assigned to manage the remote sensor array over SSH.

Generate the key structure with: ssh-keygen

  1. Export the generated public SSH key string directly onto the target honeypot sensor interface using the standard network key utility:
ssh-copy-id \-p \<port\> \<user\>@\<IP\_T-Pot\>
  1. Generate the required Nginx server layer cryptographic keys before continuing the setup phase.

  2. Once the key pair generation completes, rename the generated certificate extension identifier from .cert to .crt.

  3. With the prerequisite configurations complete, run the primary automated T-Pot deployment script: deploy.sh. Follow the prompts provided by the command-line interface.

  4. Once the deployment process wraps up, run the standard container diagnostic tool to verify that all T-Pot container services are active:

docker ps

T-Pot deploys an array of integrated honeypots alongside a Suricata IDS monitoring layer. While the ecosystem supports an automated local ELK deployment option, we omitted it here to route logs to our dedicated external ELK stack instead.

  1. To verify honeypot accessibility, open a web browser and connect to the IP address of the T-Pot honeypot host on the designated interface port to view the decoy landing page.

3.1. Installing and Configuring Filebeat on T-Pot

  1. Download and deploy the localized Filebeat installation package onto the T-Pot machine host:

  1. Open the primary tracking configuration profile filebeat.yml for editing:

  1. Define the explicit destination endpoint IP address pointing toward our remote Logstash parsing instance.

  1. Because the T-Pot application files reside within the home path of the administrative user account (/home/admin/), update the source log paths to point precisely to the directory where the honeypot container runtime logs are generated.

  1. Comment out the standard ingestion modules section. For this sub-system node, we are exclusively tracking the specialized log schemas produced by T-Pot.

  1. Run the file configuration diagnostic verification tool to ensure there are no configuration syntax errors:

  2. Run an operational connection validation test against the destination Logstash endpoint to verify communication paths are open:

  3. Verify the live Logstash ingestion console stream to confirm that telemetry logs from T-Pot's integrated Suricata instance are successfully arriving and parsing client connection attempts hitting our decoy honeypot ports.

4. SAI / UPS (Uninterruptible Power Supply)

  1. For the system application deployment phase, install the application packages via the native Debian automated package engine utility:
apt install nut
  1. Install the low-level USB driver libraries and developer client dependency extensions required for data synchronization:
apt install libusb-1.0-0-dev libupsclient-dev

After installation, configure the application files located within the primary system path directory /etc/nut/.

  1. Provision the hardware driver parameters for the targeted UPS device architecture inside the hardware definition file ups.conf:

  1. Select the dummy-ups driver configuration to implement a virtualized testing environment.

Testing Configuration Note: Because we are implementing the mock dummy driver engine, we must configure a virtual system data file port. This allows us to write mock status indicators to simulate the real telemetry returned by a physical UPS array.

  1. Define an administrative master user profile with full system control privileges inside the user management database configuration file upsd.users:

  1. Open and provision the central daemon connection listener parameters inside the file upsd.conf:

  1. Register and map our UPS system instances within the monitoring process tracking configuration file upsmon.conf:

  1. Create an automated bash execution script designed to intercept UPS event signals and trigger actions based on battery capacity changes:
#!/bin/bash
case "${NOTIFYTYPE}" in
     ONLINE)
          _notifymessage="🟢 UPS ${UPSNAME} | Power restored ⚡ System is running on normal utility power."
          /usr/bin/python3 /etc/nut/bot.py "${_notifymessage}"
     ;;
     ONBATT)
          _notifymessage="🔴 UPS ${UPSNAME} | Power outage ⚠️ The system is currently running on battery."
          /usr/bin/python3 /etc/nut/bot.py "${_notifymessage}"
     ;;
     LOWBATT)
          _notifymessage="🪫 UPS ${UPSNAME} | Low battery warning ⚠️ The UPS battery level is critically low."
          /usr/bin/python3 /etc/nut/bot.py "${_notifymessage}"
     ;;
     FSD)
          _notifymessage="⛔ UPS ${UPSNAME} | Forced shutdown. The system is forcing shutdown to protect the equipment."
          /usr/bin/python3 /etc/nut/bot.py "${_notifymessage}"
     ;;
     COMMOK)
          _notifymessage="📡 UPS ${UPSNAME} | Communication restored ✅ Connection with the UPS has been successfully established."
          /usr/bin/python3 /etc/nut/bot.py "${_notifymessage}"
     ;;
     COMMBAD)
          _notifymessage="❌ UPS ${UPSNAME} | Communication lost. Unable to communicate with the UPS."
          /usr/bin/python3 /etc/nut/bot.py "${_notifymessage}"
     ;;
     SHUTDOWN)
          _notifymessage="🔌 System shutdown in progress. Logging out and powering off the system."
          /usr/bin/python3 /etc/nut/bot.py "${_notifymessage}"
     ;;
     REPLBATT)
          _notifymessage="🔋 UPS ${UPSNAME} | Battery replacement required ⚠️ The UPS battery needs to be replaced."
          /usr/bin/python3 /etc/nut/bot.py "${_notifymessage}"
     ;;
     NOCOMM)
          _notifymessage="⚠️ UPS ${UPSNAME} | UPS not available. The UPS device is currently unreachable."
          /usr/bin/python3 /etc/nut/bot.py "${_notifymessage}"
     ;;
     NOPARENT)
          _notifymessage="🚨 Automatic shutdown failed. Administrator intervention is required."
          /usr/bin/python3 /etc/nut/bot.py "${_notifymessage}"
     ;;
     *)
          /usr/bin/python3 /etc/nut/bot.py "❓ UPS | Unknown event detected."
     ;;
esac

With the handling automation script ready, start the service daemon components.

  1. As an extension to the alert infrastructure, we built a Python script to handle automated Telegram notification deliveries:
import requests
import sys

bot_token = "BOT_KEY" # Replace with your production bot token API string
chat_id = "CHATID"

def main():
    url = f"[https://api.telegram.org/bot](https://api.telegram.org/bot){bot_token}/sendMessage"
    message = sys.argv[1]
    payload = {"chat_id": chat_id, "text": message}
    response = requests.post(url, json=payload)

    if response.status_code != 200:
        print('ERROR')

if __name__ == "__main__":
    main()
  1. This integration dynamically routes live status change updates from our UPS monitoring engine directly into our designated Telegram channel.

sudo apt-get install -y software-properties-common wget  
sudo mkdir -p /etc/apt/keyrings/  
wget -q -O - [https://apt.grafana.com/gpg.key](https://apt.grafana.com/gpg.key) | sudo tee /etc/apt/keyrings/grafana.asc  
echo "deb [signed-by=/etc/apt/keyrings/grafana.asc] [https://apt.grafana.com](https://apt.grafana.com) stable main" | sudo tee /etc/apt/sources.list.d/grafana.list  
sudo systemctl enable --now grafana-server

Default credentials: admin:admin