From bb01cf269d9b2cc40db65f194470585964da0977 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Mon, 24 Nov 2025 11:39:00 +0100 Subject: [PATCH 01/32] ignore vagrant files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index f1c4ea203..c29b42995 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ testing/unit/services/output/ make/DEBIAN/postinst testrun.log + +# Ignore vagrant files +test_vm/.vagrant/ From 0823b47ad160d13634ebc25db741ea5359d4c104 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Mon, 24 Nov 2025 20:55:23 +0100 Subject: [PATCH 02/32] protocol compliant --- test_vm/Vagrantfile | 4 +- ...{provision.yml => provision-compliant.yml} | 81 ++++++++++++++++--- test_vm/python/src/modbus_server.py | 18 +++++ 3 files changed, 92 insertions(+), 11 deletions(-) rename test_vm/{provision.yml => provision-compliant.yml} (57%) create mode 100644 test_vm/python/src/modbus_server.py diff --git a/test_vm/Vagrantfile b/test_vm/Vagrantfile index da820d783..2865f6eab 100644 --- a/test_vm/Vagrantfile +++ b/test_vm/Vagrantfile @@ -10,9 +10,9 @@ Vagrant.configure("2") do |config| libvirt.cpus = 2 libvirt.graphics_type = "none" end - if ENV['USE_ANSIBLE'] == '1' + if ENV['DEVICE'] == 'COMPLIANT' config.vm.provision "ansible" do |ansible| - ansible.playbook = "provision.yml" + ansible.playbook = "provision-compliant.yml" end end end diff --git a/test_vm/provision.yml b/test_vm/provision-compliant.yml similarity index 57% rename from test_vm/provision.yml rename to test_vm/provision-compliant.yml index ed666c0f1..d827eb3a7 100644 --- a/test_vm/provision.yml +++ b/test_vm/provision-compliant.yml @@ -2,6 +2,19 @@ - hosts: all become: true tasks: + - name: Update apt cache + apt: + update_cache: yes + + - name: Install Python3, pip, venv, and setcap utility + apt: + name: + - python3 + - python3-pip + - python3-venv + - libcap2-bin + state: present + - name: Install DHCP client apt: name: isc-dhcp-client @@ -30,20 +43,17 @@ - libpcap-dev state: present update_cache: yes - tags: bacnet - name: Clone BACnet stack repository git: repo: https://github.com/bacnet-stack/bacnet-stack.git dest: /opt/bacnet-stack version: master - tags: bacnet - name: Build BACnet stack make: chdir: /opt/bacnet-stack target: all - tags: bacnet - name: Copy bacserv-wrapper.sh copy: @@ -60,7 +70,6 @@ fi sleep 2 done - tags: bacnet - name: Create systemd unit for bacserv copy: @@ -80,7 +89,6 @@ [Install] WantedBy=multi-user.target - tags: bacnet - name: Reload systemd and enable bacserv service systemd: @@ -88,18 +96,15 @@ enabled: yes state: restarted daemon_reload: yes - tags: bacnet - name: Check if bacserv process is running shell: pgrep -fl bacserv register: bacserv_process changed_when: false - tags: bacnet - name: Show information about bacserv process debug: var: bacserv_process.stdout_lines - tags: bacnet - name: Show last lines of bacserv log shell: tail -n 20 /opt/bacnet-stack/bacserv.log @@ -110,4 +115,62 @@ - name: Display bacserv log debug: var: bacserv_log.stdout_lines - tags: bacnet \ No newline at end of file + + - name: Create venv for modbus + become: no + command: python3 -m venv /home/vagrant/modbus-venv + args: + creates: /home/vagrant/modbus-venv + + - name: Create venv for modbus + become: no + command: python3 -m venv /home/vagrant/modbus-venv + args: + creates: /home/vagrant/modbus-venv + + - name: Install pyModbusTCP in venv + become: no + command: /home/vagrant/modbus-venv/bin/pip install pyModbusTCP + + - name: Find real python binary path + shell: readlink -f /home/vagrant/modbus-venv/bin/python + register: real_python_path + + - name: Allow system python to bind to port 502 + become: true + command: setcap 'cap_net_bind_service=+ep' {{ real_python_path.stdout }} + + - name: Copy Modbus TCP server script (pyModbusTCP) + copy: + src: ./python/src/modbus_server.py + dest: /home/vagrant/modbus_server.py + owner: vagrant + group: vagrant + mode: '0644' + + - name: Ensure modbus server is running (systemd) + copy: + dest: /etc/systemd/system/modbus-server.service + content: | + [Unit] + Description=Modbus TCP Server (pyModbusTCP) + After=network.target + + [Service] + Type=simple + User=vagrant + ExecStart=/home/vagrant/modbus-venv/bin/python /home/vagrant/modbus_server.py + Restart=always + + [Install] + WantedBy=multi-user.target + + - name: Reload systemd + systemd: + daemon_reload: yes + + - name: Enable and start modbus-server + systemd: + name: modbus-server + enabled: yes + state: started \ No newline at end of file diff --git a/test_vm/python/src/modbus_server.py b/test_vm/python/src/modbus_server.py new file mode 100644 index 000000000..b0ecd5bf1 --- /dev/null +++ b/test_vm/python/src/modbus_server.py @@ -0,0 +1,18 @@ +from pyModbusTCP.server import ModbusServer, DataBank + +if __name__ == "__main__": + # Создаем сервер на порту 502 (или 5020, если нет прав) + server = ModbusServer(host="0.0.0.0", port=502, no_block=True) + server.start() + print("Modbus TCP server started on port 502") + + # Пример: инициализация регистров + DataBank.set_words(0, [0]*100) # Holding registers + DataBank.set_bits(0, [0]*100) # Coils + + try: + while True: + pass # Сервер работает в основном потоке + except KeyboardInterrupt: + print("Stopping server...") + server.stop() \ No newline at end of file From 27dedb7b39f129472ad46757d99e2cf6699734d7 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Tue, 25 Nov 2025 11:04:51 +0100 Subject: [PATCH 03/32] change device mac address --- test_vm/Vagrantfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_vm/Vagrantfile b/test_vm/Vagrantfile index 2865f6eab..784d66637 100644 --- a/test_vm/Vagrantfile +++ b/test_vm/Vagrantfile @@ -4,7 +4,7 @@ Vagrant.configure("2") do |config| config.vm.network "private_network", libvirt__network_name: "hostonly-noip", auto_config: false, - mac: "52:54:00:12:34:56" + mac: "f0:d4:e2:f2:f5:41" config.vm.provider :libvirt do |libvirt| libvirt.memory = 2048 libvirt.cpus = 2 From f8192a50399906ed8d375b9f209fd207f51997a7 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Tue, 25 Nov 2025 18:52:38 +0100 Subject: [PATCH 04/32] connection module --- test_vm/README.MD | 9 +++++++-- test_vm/mock_ethtool.sh | 29 +++++++++++++++++++++++++++++ test_vm/restore_ethtool.sh | 22 ++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100755 test_vm/mock_ethtool.sh create mode 100755 test_vm/restore_ethtool.sh diff --git a/test_vm/README.MD b/test_vm/README.MD index 9a69f91e1..e95d4b3bd 100644 --- a/test_vm/README.MD +++ b/test_vm/README.MD @@ -60,6 +60,8 @@ sudo ip link delete enbr99 type bridge ### Create and start the network: ```bash +sudo virsh net-destroy hostonly-noip || true +sudo virsh net-undefine hostonly-noip || true sudo virsh net-define /tmp/hostonly-noip.xml sudo virsh net-start hostonly-noip sudo virsh net-autostart hostonly-noip @@ -78,6 +80,9 @@ You should see hostonly-noip with status active. ```bash -vagrant up --provider=libvirt +./mock_ethtool.sh +hash -r +DEVICE=COMPLIANT vagrant up --provider=libvirt ``` -If you see "Permission denied to /var/run/libvirt/libvirt-sock", check your libvirt group membership and restart your session. \ No newline at end of file +If you see "Permission denied to /var/run/libvirt/libvirt-sock", check your libvirt group membership and restart your session. + diff --git a/test_vm/mock_ethtool.sh b/test_vm/mock_ethtool.sh new file mode 100755 index 000000000..10110a3a4 --- /dev/null +++ b/test_vm/mock_ethtool.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +sudo tee /usr/local/bin/ethtool > /dev/null <<'EOF' +#!/bin/bash +if [[ "$1" == "enbr99" ]]; then + cat < Date: Wed, 26 Nov 2025 11:39:05 +0100 Subject: [PATCH 05/32] ntp module comliant --- test_vm/provision-compliant.yml | 52 ++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/test_vm/provision-compliant.yml b/test_vm/provision-compliant.yml index d827eb3a7..82902af8d 100644 --- a/test_vm/provision-compliant.yml +++ b/test_vm/provision-compliant.yml @@ -2,6 +2,7 @@ - hosts: all become: true tasks: + # Common setup tasks - name: Update apt cache apt: update_cache: yes @@ -13,8 +14,10 @@ - python3-pip - python3-venv - libcap2-bin + - net-tools state: present + # Set up networking on eth1 - name: Install DHCP client apt: name: isc-dhcp-client @@ -26,12 +29,7 @@ async: 0 poll: 0 - - name: Install netstat for network troubleshooting - apt: - name: net-tools - state: present - update_cache: yes - + # BACnet stack setup - name: Install BACnet dependencies apt: name: @@ -116,6 +114,8 @@ debug: var: bacserv_log.stdout_lines + # Modbus TCP server setup + - name: Create venv for modbus become: no command: python3 -m venv /home/vagrant/modbus-venv @@ -173,4 +173,42 @@ systemd: name: modbus-server enabled: yes - state: started \ No newline at end of file + state: started + + # NTP client setup + + - name: Install chrony + apt: + name: chrony + state: present + update_cache: yes + + - name: Remove static NTP servers from chrony.conf + lineinfile: + path: /etc/chrony/chrony.conf + regexp: '^(server|pool)' + state: absent + + - name: Ensure sourcedir for DHCP is present + lineinfile: + path: /etc/chrony/chrony.conf + line: 'sourcedir /run/chrony-dhcp' + state: present + + - name: Restart chrony + service: + name: chrony + state: restarted + enabled: yes + + - name: Wait for chrony to sync sources + .wait_for: + timeout: 10 + + - name: Show current chrony sources + command: chronyc sources + register: chrony_sources + + - name: Print chrony sources + ansible.builtin.debug: + var: chrony_sources.stdout \ No newline at end of file From d373200264cb67b70ce4c22b3f59029a3fe85bd9 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Wed, 26 Nov 2025 12:45:44 +0100 Subject: [PATCH 06/32] changes in readme --- test_vm/README.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_vm/README.MD b/test_vm/README.MD index e95d4b3bd..9283c1b57 100644 --- a/test_vm/README.MD +++ b/test_vm/README.MD @@ -10,7 +10,7 @@ This guide will help you set up a Vagrant VM with two network interfaces: ```bash sudo apt-get update -sudo apt-get install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager \ +sudo apt-get install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils \ ruby ruby-dev libvirt-dev zlib1g-dev ebtables dnsmasq-base vagrant ``` From 7eb954fe1bc80f28aad83dbf8572cef7b50ddc0e Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Thu, 27 Nov 2025 13:31:38 +0100 Subject: [PATCH 07/32] dns module --- test_vm/provision-compliant.yml | 84 ++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/test_vm/provision-compliant.yml b/test_vm/provision-compliant.yml index 82902af8d..c47c09a97 100644 --- a/test_vm/provision-compliant.yml +++ b/test_vm/provision-compliant.yml @@ -202,7 +202,7 @@ enabled: yes - name: Wait for chrony to sync sources - .wait_for: + wait_for: timeout: 10 - name: Show current chrony sources @@ -211,4 +211,84 @@ - name: Print chrony sources ansible.builtin.debug: - var: chrony_sources.stdout \ No newline at end of file + var: chrony_sources.stdout + + # DNS request setup + + - name: Create systemd service for curl + ansible.builtin.copy: + dest: /etc/systemd/system/curl-google.service + content: | + [Unit] + Description=Curl google.com + + [Service] + Type=oneshot + ExecStart=/usr/bin/curl -s -L -o /dev/null -w "%{http_code}\n" https://www.google.com + + - name: Create systemd timer for curl + ansible.builtin.copy: + dest: /etc/systemd/system/curl-google.timer + content: | + [Unit] + Description=Run curl google.com every 5 seconds + + [Timer] + OnBootSec=5 + OnUnitActiveSec=5 + + [Install] + WantedBy=timers.target + + - name: Reload systemd daemon + ansible.builtin.systemd: + daemon_reload: yes + + - name: Enable and start curl-google.timer + ansible.builtin.systemd: + name: curl-google.timer + enabled: yes + state: started + + # mDNS traffic setup + + - name: Ensure avahi-utils is installed + ansible.builtin.apt: + name: avahi-utils + state: present + update_cache: yes + + - name: Create systemd service for mDNS traffic + ansible.builtin.copy: + dest: /etc/systemd/system/mdns-traffic.service + content: | + [Unit] + Description=Generate mDNS traffic using avahi-publish + + [Service] + Type=oneshot + ExecStart=/usr/bin/avahi-publish -s "TestService" _http._tcp 8080 + + - name: Create systemd timer for mDNS traffic + ansible.builtin.copy: + dest: /etc/systemd/system/mdns-traffic.timer + content: | + [Unit] + Description=Run mDNS traffic generator every 5 seconds + + [Timer] + OnBootSec=5 + OnUnitActiveSec=5 + + [Install] + WantedBy=timers.target + + - name: Reload systemd daemon + ansible.builtin.systemd: + daemon_reload: yes + + - name: Enable and start mdns-traffic.timer + ansible.builtin.systemd: + name: mdns-traffic.timer + enabled: yes + state: started \ No newline at end of file From 01a2dec5b2d936e50a875c5e88494e4c703022d4 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Fri, 28 Nov 2025 21:32:18 +0100 Subject: [PATCH 08/32] tls outbound complient --- test_vm/README.MD | 8 ++++- test_vm/provision-compliant.yml | 64 +++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/test_vm/README.MD b/test_vm/README.MD index 9283c1b57..c160dc21a 100644 --- a/test_vm/README.MD +++ b/test_vm/README.MD @@ -47,7 +47,13 @@ sudo ip link delete enbr99 type bridge ## 6. Create an Isolated libvirt Network Without IP and DHCP -### Create the file /tmp/hostonly-noip.xml: +### Create the file /tmp/hostonly-noip.xml + +```bash +nano /tmp/hostonly-noip.xml +``` + +file content ```xml diff --git a/test_vm/provision-compliant.yml b/test_vm/provision-compliant.yml index c47c09a97..2aa00cfa9 100644 --- a/test_vm/provision-compliant.yml +++ b/test_vm/provision-compliant.yml @@ -291,4 +291,68 @@ ansible.builtin.systemd: name: mdns-traffic.timer enabled: yes + state: started + + # Outbound TLS traffic setup + + - name: Ensure openssl is installed + ansible.builtin.apt: + name: openssl + state: present + update_cache: yes + + - name: Copy TLS emulation script and make it executable + ansible.builtin.copy: + dest: /usr/local/bin/tls_emulate.sh + mode: '0755' + content: | + #!/bin/bash + # Ждём, пока eth1 не получит IP-адрес + while true; do + IP=$(ip -4 addr show eth1 | awk '/inet / {print $2}' | cut -d/ -f1) + if [[ -n "$IP" ]]; then + break + fi + sleep 1 + done + # TLS 1.0 + openssl s_client -connect tls-v1-0.badssl.com:1010 -tls1 -bind "$IP" + # TLS 1.2 + openssl s_client -connect tls-v1-2.badssl.com:1012 -tls1_2 -bind "$IP" + # TLS 1.3 + openssl s_client -connect tls13.badssl.com:443 -tls1_3 -bind "$IP" + + - name: Create systemd service for TLS emulation + ansible.builtin.copy: + dest: /etc/systemd/system/tls-emulate.service + content: | + [Unit] + Description=Emulate outgoing TLS connections via eth1 + + [Service] + Type=oneshot + ExecStart=/usr/local/bin/tls_emulate.sh + + - name: Create systemd timer for TLS emulation + ansible.builtin.copy: + dest: /etc/systemd/system/tls-emulate.timer + content: | + [Unit] + Description=Run TLS emulation every 5 seconds + + [Timer] + OnBootSec=5 + OnUnitActiveSec=5 + + [Install] + WantedBy=timers.target + + - name: Reload systemd daemon + ansible.builtin.systemd: + daemon_reload: yes + + - name: Enable and start tls-emulate.timer + ansible.builtin.systemd: + name: tls-emulate.timer + enabled: yes state: started \ No newline at end of file From b7c4f11380d7d4a8e36a6732e71cb174d7ff3bd4 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Sun, 30 Nov 2025 18:55:10 +0100 Subject: [PATCH 09/32] tls compliant server --- .gitignore | 8 +++ test_vm/create_certificate.sh | 124 ++++++++++++++++++++++++++++++++ test_vm/provision-compliant.yml | 15 +++- 3 files changed, 146 insertions(+), 1 deletion(-) create mode 100755 test_vm/create_certificate.sh diff --git a/.gitignore b/.gitignore index c29b42995..9d6cc2acc 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,11 @@ testrun.log # Ignore vagrant files test_vm/.vagrant/ + +# Ingnore TLS test certificates +test_vm/certs/ +test_vm/*.cnf +test_vm/*.csr +test_vm/*.pem +test_vm/*.key +test_vm/*.crt \ No newline at end of file diff --git a/test_vm/create_certificate.sh b/test_vm/create_certificate.sh new file mode 100755 index 000000000..26b283491 --- /dev/null +++ b/test_vm/create_certificate.sh @@ -0,0 +1,124 @@ +#!/bin/bash +set -e + +if [ -z "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +# Save the initial working directory +WORKDIR="$(pwd)" + +# Check and install sshpass if not present +if ! command -v sshpass &> /dev/null; then + echo "sshpass not found. Installing..." + if [ -x "$(command -v apt)" ]; then + sudo apt update + sudo apt install -y sshpass + elif [ -x "$(command -v yum)" ]; then + sudo yum install -y epel-release + sudo yum install -y sshpass + elif [ -x "$(command -v brew)" ]; then + brew install hudochenkov/sshpass/sshpass + else + echo "Please install sshpass manually." + exit 1 + fi +fi + +VM_USER=vagrant +VM_PASS=vagrant +VM_IP="$1" +CA_DIR=~/myCA + +SSHPASS="sshpass -p $VM_PASS" + +# 1. Generate key and CSR on VM via ssh +$SSHPASS ssh -o StrictHostKeyChecking=no ${VM_USER}@${VM_IP} "cat > /home/vagrant/openssl_ip.cnf" < /etc/nginx/sites-available/default < Date: Sun, 30 Nov 2025 20:38:29 +0100 Subject: [PATCH 10/32] firewall --- test_vm/provision-compliant.yml | 58 ++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/test_vm/provision-compliant.yml b/test_vm/provision-compliant.yml index 4fefaf443..da23972c0 100644 --- a/test_vm/provision-compliant.yml +++ b/test_vm/provision-compliant.yml @@ -368,4 +368,60 @@ service: name: nginx state: started - enabled: yes \ No newline at end of file + enabled: yes + + # Install firewall + + - name: Install UFW firewall + apt: + name: ufw + state: present + update_cache: yes + + - name: Reset UFW to default + command: ufw --force reset + + - name: Set default UFW policy to deny incoming + command: ufw default deny incoming + + - name: Set default UFW policy to allow outgoing + command: ufw default allow outgoing + + - name: Allow all incoming traffic on eth0 + command: ufw allow in on eth0 + + - name: Allow all outgoing traffic on eth0 + command: ufw allow out on eth0 + + - name: Allow 443/tcp incoming on eth1 + command: ufw allow in on eth1 to any port 443 proto tcp + + - name: Allow all outgoing traffic on eth1 + command: ufw allow out on eth1 + + - name: Allow BACnet UDP port 47808 incoming on eth1 + command: ufw allow in on eth1 to any port 47808 proto udp + + - name: Allow BACnet UDP port 47808 outgoing on eth1 + command: ufw allow out on eth1 to any port 47808 proto udp + + - name: Allow BACnet UDP broadcast on eth1 + command: ufw allow in on eth1 to 255.255.255.255 port 47808 proto udp + ignore_errors: yes + + - name: Allow Modbus TCP port 502 incoming on eth1 + command: ufw allow in on eth1 to any port 502 proto tcp + + - name: Allow Modbus TCP port 502 outgoing on eth1 + command: ufw allow out on eth1 to any port 502 proto tcp + + - name: Enable UFW + command: ufw --force enable + + - name: Show UFW status + command: ufw status verbose + register: ufw_status + + - name: Print UFW status + debug: + var: ufw_status.stdout \ No newline at end of file From abd8401acdb47decf3301384b9016ed38732c616 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Tue, 2 Dec 2025 20:43:31 +0100 Subject: [PATCH 11/32] intermidiate certificate --- test_vm/README.MD | 4 + test_vm/create_cert_chain.sh | 135 ++++++++++++++++++++++++++++++++++ test_vm/create_certificate.sh | 12 ++- 3 files changed, 150 insertions(+), 1 deletion(-) create mode 100755 test_vm/create_cert_chain.sh diff --git a/test_vm/README.MD b/test_vm/README.MD index c160dc21a..342cfa6cc 100644 --- a/test_vm/README.MD +++ b/test_vm/README.MD @@ -89,6 +89,10 @@ You should see hostonly-noip with status active. ./mock_ethtool.sh hash -r DEVICE=COMPLIANT vagrant up --provider=libvirt +DEVICE=COMPLIANT vagrant provision +./create_certificate.sh ``` If you see "Permission denied to /var/run/libvirt/libvirt-sock", check your libvirt group membership and restart your session. +## 9. Start Testrun + diff --git a/test_vm/create_cert_chain.sh b/test_vm/create_cert_chain.sh new file mode 100755 index 000000000..a9107ee7d --- /dev/null +++ b/test_vm/create_cert_chain.sh @@ -0,0 +1,135 @@ +#!/bin/bash +set -e + +if [ -z "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +VM_USER=vagrant +VM_PASS=vagrant +VM_IP="$1" +SSHPASS="sshpass -p $VM_PASS" + +BASE_DIR="$(pwd)" +WORKDIR="$BASE_DIR/multi_ca_demo" +CERTS_DIR="$BASE_DIR/certs" +NGINX_CN="$VM_IP" + +mkdir -p "$WORKDIR" +mkdir -p "$CERTS_DIR" +cd "$WORKDIR" + +# Create CA extension config for intermediates +cat > ca_ext.cnf < openssl_nginx.cnf < nginx_fullchain.pem + +echo "All certificates generated." + +# 7. Copy certs and key to VM +$SSHPASS scp -o StrictHostKeyChecking=no nginx.key ${VM_USER}@${VM_IP}:/home/vagrant/nginx.key +$SSHPASS scp -o StrictHostKeyChecking=no nginx_fullchain.pem ${VM_USER}@${VM_IP}:/home/vagrant/nginx_fullchain.pem +$SSHPASS scp -o StrictHostKeyChecking=no int2CA.pem ${VM_USER}@${VM_IP}:/home/vagrant/int2CA.pem +$SSHPASS scp -o StrictHostKeyChecking=no int1CA.pem ${VM_USER}@${VM_IP}:/home/vagrant/int1CA.pem +$SSHPASS scp -o StrictHostKeyChecking=no rootCA.pem ${VM_USER}@${VM_IP}:/home/vagrant/rootCA.pem + +# 8. Move certs to correct locations and configure nginx on VM +$SSHPASS ssh -o StrictHostKeyChecking=no ${VM_USER}@${VM_IP} "sudo mv /home/vagrant/nginx.key /etc/ssl/private/nginx.key" +$SSHPASS ssh -o StrictHostKeyChecking=no ${VM_USER}@${VM_IP} "sudo mv /home/vagrant/nginx_fullchain.pem /etc/ssl/certs/nginx_fullchain.pem" +$SSHPASS ssh -o StrictHostKeyChecking=no ${VM_USER}@${VM_IP} "sudo mv /home/vagrant/int2CA.pem /etc/ssl/certs/int2CA.pem" +$SSHPASS ssh -o StrictHostKeyChecking=no ${VM_USER}@${VM_IP} "sudo mv /home/vagrant/int1CA.pem /etc/ssl/certs/int1CA.pem" +$SSHPASS ssh -o StrictHostKeyChecking=no ${VM_USER}@${VM_IP} "sudo mv /home/vagrant/rootCA.pem /etc/ssl/certs/rootCA.pem" + +$SSHPASS ssh -o StrictHostKeyChecking=no ${VM_USER}@${VM_IP} "sudo bash -c 'cat > /etc/nginx/sites-available/default < Date: Tue, 2 Dec 2025 21:01:31 +0100 Subject: [PATCH 12/32] pylint --- test_vm/python/requirements.txt | 1 + test_vm/python/src/modbus_server.py | 28 +++++++++++++++------------- 2 files changed, 16 insertions(+), 13 deletions(-) create mode 100644 test_vm/python/requirements.txt diff --git a/test_vm/python/requirements.txt b/test_vm/python/requirements.txt new file mode 100644 index 000000000..8e7a8b454 --- /dev/null +++ b/test_vm/python/requirements.txt @@ -0,0 +1 @@ +pyModbusTCP==0.3.0 \ No newline at end of file diff --git a/test_vm/python/src/modbus_server.py b/test_vm/python/src/modbus_server.py index b0ecd5bf1..cc47e019c 100644 --- a/test_vm/python/src/modbus_server.py +++ b/test_vm/python/src/modbus_server.py @@ -1,18 +1,20 @@ +"""Modbus TCP server implementation.""" from pyModbusTCP.server import ModbusServer, DataBank if __name__ == "__main__": - # Создаем сервер на порту 502 (или 5020, если нет прав) - server = ModbusServer(host="0.0.0.0", port=502, no_block=True) - server.start() - print("Modbus TCP server started on port 502") + # Run server on port 502 - # Пример: инициализация регистров - DataBank.set_words(0, [0]*100) # Holding registers - DataBank.set_bits(0, [0]*100) # Coils + server = ModbusServer(host="0.0.0.0", port=502, no_block=True) + server.start() + print("Modbus TCP server started on port 502") - try: - while True: - pass # Сервер работает в основном потоке - except KeyboardInterrupt: - print("Stopping server...") - server.stop() \ No newline at end of file + # Initialize data bank with zeros + DataBank.set_words(0, [0]*100) # Holding registers + DataBank.set_bits(0, [0]*100) # Coils + + try: + while True: + pass # Keep the server running + except KeyboardInterrupt: + print("Stopping server...") + server.stop() From 059783bb62fed7037fe73f7ee6203e5d4be61f2e Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Fri, 5 Dec 2025 15:56:26 +0100 Subject: [PATCH 13/32] add ansible --- test_vm/README.MD | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test_vm/README.MD b/test_vm/README.MD index 342cfa6cc..860064e3d 100644 --- a/test_vm/README.MD +++ b/test_vm/README.MD @@ -8,10 +8,14 @@ This guide will help you set up a Vagrant VM with two network interfaces: ## 1. Install Required Packages and Dependencies + + ```bash +wget -O- https://apt.releases.hashicorp.com/gpg | sudo apt-key add - +sudo add-apt-repository -y "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main" sudo apt-get update sudo apt-get install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils \ - ruby ruby-dev libvirt-dev zlib1g-dev ebtables dnsmasq-base vagrant + ruby ruby-dev libvirt-dev zlib1g-dev ebtables dnsmasq-base vagrant ansible ``` @@ -94,5 +98,6 @@ DEVICE=COMPLIANT vagrant provision ``` If you see "Permission denied to /var/run/libvirt/libvirt-sock", check your libvirt group membership and restart your session. + ## 9. Start Testrun From 5d5c0d0fc1d512ac1da69edb074fc0a3372e77bc Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Sun, 7 Dec 2025 13:49:19 +0100 Subject: [PATCH 14/32] modbus non-compliant --- test_vm/Vagrantfile | 5 ++ test_vm/provision-non-compliant.yml | 73 +++++++++++++++++++ .../python/src/modbus_server_non_compliant.py | 60 +++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 test_vm/provision-non-compliant.yml create mode 100644 test_vm/python/src/modbus_server_non_compliant.py diff --git a/test_vm/Vagrantfile b/test_vm/Vagrantfile index 784d66637..3319ee554 100644 --- a/test_vm/Vagrantfile +++ b/test_vm/Vagrantfile @@ -15,4 +15,9 @@ Vagrant.configure("2") do |config| ansible.playbook = "provision-compliant.yml" end end + if ENV['DEVICE'] == 'NON-COMPLIANT' + config.vm.provision "ansible" do |ansible| + ansible.playbook = "provision-non-compliant.yml" + end + end end diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml new file mode 100644 index 000000000..40df9ef22 --- /dev/null +++ b/test_vm/provision-non-compliant.yml @@ -0,0 +1,73 @@ +--- +- hosts: all + become: true + tasks: + # Common setup tasks + - name: Update apt cache + apt: + update_cache: yes + + - name: Install Python3, pip, venv, and setcap utility + apt: + name: + - python3 + - python3-pip + - python3-venv + - libcap2-bin + - net-tools + state: present + + # Set up networking on eth1 + - name: Install DHCP client + apt: + name: isc-dhcp-client + state: present + update_cache: yes + + - name: Run dhclient on eth1 (in background) + shell: nohup dhclient eth1 & + async: 0 + poll: 0 + + # Custom Modbus TCP server NON-COMPLIANT + + - name: Copy Modbus TCP server script (pyModbusTCP) + copy: + src: ./python/src/modbus_server_non_compliant.py + dest: /home/vagrant/modbus_server_non_compliant.py + owner: vagrant + group: vagrant + mode: '0644' + + - name: Create systemd unit for custom Modbus server + copy: + dest: /etc/systemd/system/custom-modbus-server.service + mode: '0644' + content: | + [Unit] + Description=Custom Modbus TCP Error Server + After=network.target + + [Service] + Type=simple + ExecStart=/usr/bin/python3 /home/vagrant/modbus_server_non_compliant.py + Restart=always + + [Install + WantedBy=multi-user.target + + - name: Reload systemd + systemd: + daemon_reload: yes + + - name: Enable and start custom Modbus server + systemd: + name: custom-modbus-server + enabled: yes + state: started + + - name: Show custom Modbus server status + systemd: + name: custom-modbus-server + register: modbus_status + changed_when: false \ No newline at end of file diff --git a/test_vm/python/src/modbus_server_non_compliant.py b/test_vm/python/src/modbus_server_non_compliant.py new file mode 100644 index 000000000..698d0ec85 --- /dev/null +++ b/test_vm/python/src/modbus_server_non_compliant.py @@ -0,0 +1,60 @@ +""" +test_vm.python.src.modbus_server_non_compliant +""" + +import socket +import struct + + +MODBUS_PORT = 502 +MODBUS_EXCEPTION_CODE = 0x02 # Illegal Data Address + + +def parse_mbap_header(data): + if len(data) < 7: + return None + tid, pid, length, uid = struct.unpack(">HHHB", data[:7]) + return tid, pid, length, uid + + +def build_exception_response(request, function_code, exception_code): + tid, pid, _, uid = parse_mbap_header(request) + resp_fc = function_code | 0x80 + pdu = struct.pack("BB", resp_fc, exception_code) + mbap = struct.pack(">HHHB", tid, pid, len(pdu) + 1, uid) + return mbap + pdu + + +def handle_request(conn, data): + if len(data) < 8: + return + # tid, pid, length, uid = parse_mbap_header(data) + function_code = data[7] + response = build_exception_response( + data, function_code, MODBUS_EXCEPTION_CODE) + conn.sendall(response) + + +def run_server(): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(("0.0.0.0", MODBUS_PORT)) + sock.listen(5) + print(f"Modbus server started on port {MODBUS_PORT}") + while True: + conn, addr = sock.accept() + print(f"Connection from {addr}") + try: + while True: + data = conn.recv(1024) + if not data: + break + handle_request(conn, data) + except Exception as e: + print(f"Error: {e}") + finally: + conn.close() + + +if __name__ == "__main__": + run_server() From 0ed92d45ca5b0945d85d926a817c2c1bd17e45ae Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Mon, 8 Dec 2025 15:23:02 +0100 Subject: [PATCH 15/32] fix modbus error --- test_vm/provision-non-compliant.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml index 40df9ef22..7dd87543e 100644 --- a/test_vm/provision-non-compliant.yml +++ b/test_vm/provision-non-compliant.yml @@ -53,7 +53,7 @@ ExecStart=/usr/bin/python3 /home/vagrant/modbus_server_non_compliant.py Restart=always - [Install + [Install] WantedBy=multi-user.target - name: Reload systemd From cb14d015cb67df4fdd1755e7146ae2cea3ff63be Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Mon, 8 Dec 2025 20:49:53 +0100 Subject: [PATCH 16/32] ftp ssh non-compiant --- test_vm/provision-non-compliant.yml | 136 ++++++++++++++++++++++++++-- 1 file changed, 129 insertions(+), 7 deletions(-) diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml index 7dd87543e..6e4e738b2 100644 --- a/test_vm/provision-non-compliant.yml +++ b/test_vm/provision-non-compliant.yml @@ -3,6 +3,10 @@ become: true tasks: # Common setup tasks + + - name: Gather facts + setup: + - name: Update apt cache apt: update_cache: yes @@ -15,15 +19,13 @@ - python3-venv - libcap2-bin - net-tools + - netcat-openbsd + - isc-dhcp-client + - vsftpd state: present # Set up networking on eth1 - - name: Install DHCP client - apt: - name: isc-dhcp-client - state: present - update_cache: yes - + - name: Run dhclient on eth1 (in background) shell: nohup dhclient eth1 & async: 0 @@ -70,4 +72,124 @@ systemd: name: custom-modbus-server register: modbus_status - changed_when: false \ No newline at end of file + changed_when: false + + # FTP server + + - name: Ensure vsftpd is running and enabled + service: + name: vsftpd + state: started + enabled: yes + + - name: Copy custom vsftpd config (optional) + copy: + src: ./vsftpd.conf + dest: /etc/vsftpd.conf + owner: root + group: root + mode: 0644 + notify: Restart vsftpd + when: vsftpd.conf is defined + + # SSH server configuration + + - name: Gather facts + setup: + + - name: Set eth0_ip from Ansible facts + set_fact: + eth0_ip: "{{ ansible_eth0.ipv4.address | default('') }}" + + - name: Fail if eth0_ip is not set + fail: + msg: "eth0_ip is not set! Please check network configuration." + when: eth0_ip == '' + + - name: Debug eth0 IP + debug: + msg: "eth0 IP is {{ eth0_ip }}" + + - name: Stop and disable ssh.socket (prevents auto-start on all interfaces) + service: + name: ssh.socket + state: stopped + enabled: no + ignore_errors: yes + + - name: Ensure only eth0 is in ListenAddress for sshd + lineinfile: + path: /etc/ssh/sshd_config + regexp: '^ListenAddress' + line: "ListenAddress {{ eth0_ip }}" + state: present + insertafter: '^#Port' + + - name: Remove all other ListenAddress lines from sshd_config + lineinfile: + path: /etc/ssh/sshd_config + regexp: '^ListenAddress (?!{{ eth0_ip }})' + state: absent + + - name: Restart sshd + service: + name: ssh + state: restarted + + - name: Show what is listening on port 22 + shell: ss -tlnp | grep :22 || netstat -tlnp | grep :22 || true + register: port22 + changed_when: false + + - debug: + var: port22.stdout_lines + + - name: Install netcat if not present + apt: + name: netcat-openbsd + state: present + update_cache: yes + + - name: Create fake SSH banner systemd service for eth1:22 + copy: + dest: /etc/systemd/system/fake-ssh-banner.service + mode: '0644' + content: | + [Unit] + Description=Fake SSH Banner on eth1:22 + After=network-online.target + Wants=network-online.target + + [Service] + Type=simple + ExecStart=/bin/bash -c '\ + while true; do \ + ETH1_IP=$(ip -4 addr show eth1 | grep -oP "(?<=inet\s)\d+(\.\d+){3}"); \ + if [[ -n "$ETH1_IP" ]]; then \ + echo "eth1 IP detected: $ETH1_IP"; \ + break; \ + fi; \ + echo "Waiting for eth1 IP..."; \ + sleep 2; \ + done; \ + while true; do echo -e "SSH-1.99-OpenSSH_1.99\r\n" | nc -l -s $ETH1_IP -p 22; done' + Restart=always + + [Install] + WantedBy=multi-user.target + + - name: Reload systemd + systemd: + daemon_reload: yes + + - name: Restart fake-ssh-banner service + systemd: + name: fake-ssh-banner.service + state: restarted + enabled: yes + + handlers: + - name: Restart vsftpd + service: + name: vsftpd + state: restarted \ No newline at end of file From 0d56b8cf33c279da9c682088e34b746b35772c39 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Tue, 9 Dec 2025 12:31:48 +0100 Subject: [PATCH 17/32] smtp imamp pop --- test_vm/provision-non-compliant.yml | 68 ++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml index 6e4e738b2..f4fcdd9aa 100644 --- a/test_vm/provision-non-compliant.yml +++ b/test_vm/provision-non-compliant.yml @@ -7,6 +7,11 @@ - name: Gather facts setup: + - name: Ensure main and universe repos are enabled + apt_repository: + repo: "deb http://archive.ubuntu.com/ubuntu {{ ansible_distribution_release }} main universe" + state: present + - name: Update apt cache apt: update_cache: yes @@ -22,6 +27,9 @@ - netcat-openbsd - isc-dhcp-client - vsftpd + - netcat-openbsd + - inetutils-inetd + - inetutils-telnetd state: present # Set up networking on eth1 @@ -144,11 +152,6 @@ - debug: var: port22.stdout_lines - - name: Install netcat if not present - apt: - name: netcat-openbsd - state: present - update_cache: yes - name: Create fake SSH banner systemd service for eth1:22 copy: @@ -188,6 +191,61 @@ state: restarted enabled: yes + # Telnet server setup + + - name: Ensure telnet service is enabled in /etc/inetd.conf + lineinfile: + path: /etc/inetd.conf + regexp: '^#?telnet\s' + line: 'telnet stream tcp nowait root /usr/sbin/tcpd /usr/sbin/in.telnetd' + state: present + + - name: Restart inetutils-inetd + service: + name: inetutils-inetd + state: restarted + + # SMTP POP IMAP server setup + + - name: Install Postfix + apt: + name: postfix + state: present + update_cache: yes + + - name: Install Dovecot + apt: + name: + - dovecot-pop3d + - dovecot-imapd + state: present + + - name: Ensure submission and smtps are enabled in Postfix master.cf + blockinfile: + path: /etc/postfix/master.cf + block: | + submission inet n - y - - smtpd + -o syslog_name=postfix/submission + -o smtpd_tls_security_level=encrypt + -o smtpd_sasl_auth_enable=yes + -o smtpd_client_restrictions=permit_sasl_authenticated,reject + smtps inet n - y - - smtpd + -o syslog_name=postfix/smtps + -o smtpd_tls_wrappermode=yes + -o smtpd_sasl_auth_enable=yes + -o smtpd_client_restrictions=permit_sasl_authenticated,reject + + - name: Restart Postfix + service: + name: postfix + state: restarted + + - name: Restart Dovecot + service: + name: dovecot + state: restarted + + handlers: - name: Restart vsftpd service: From c76589b97b4ec93f3b45322adc932a3ac3431405 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Tue, 9 Dec 2025 14:10:21 +0100 Subject: [PATCH 18/32] http 80 port --- test_vm/provision-non-compliant.yml | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml index f4fcdd9aa..ecf5c8476 100644 --- a/test_vm/provision-non-compliant.yml +++ b/test_vm/provision-non-compliant.yml @@ -30,6 +30,10 @@ - netcat-openbsd - inetutils-inetd - inetutils-telnetd + - postfix + - dovecot-pop3d + - dovecot-imapd + - nginx state: present # Set up networking on eth1 @@ -207,19 +211,6 @@ # SMTP POP IMAP server setup - - name: Install Postfix - apt: - name: postfix - state: present - update_cache: yes - - - name: Install Dovecot - apt: - name: - - dovecot-pop3d - - dovecot-imapd - state: present - - name: Ensure submission and smtps are enabled in Postfix master.cf blockinfile: path: /etc/postfix/master.cf @@ -245,6 +236,13 @@ name: dovecot state: restarted + # HTTP server setup + + - name: Ensure Nginx is running and enabled + service: + name: nginx + state: started + enabled: yes handlers: - name: Restart vsftpd From e4dc3d0e4144d1b05704724e7a31f301ab8ebbd6 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Tue, 9 Dec 2025 15:00:42 +0100 Subject: [PATCH 19/32] snmp server --- test_vm/provision-non-compliant.yml | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml index ecf5c8476..c47db9d94 100644 --- a/test_vm/provision-non-compliant.yml +++ b/test_vm/provision-non-compliant.yml @@ -34,6 +34,7 @@ - dovecot-pop3d - dovecot-imapd - nginx + - snmpd state: present # Set up networking on eth1 @@ -244,6 +245,42 @@ state: started enabled: yes + # SNMP server + + - name: Copy SNMP eth1 binding script + copy: + dest: /usr/local/bin/snmpd-eth1.sh + mode: '0755' + content: | + #!/bin/bash + while true; do + ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ETH1_IP" ]]; then + echo "eth1 IP detected: $ETH1_IP" + break + fi + echo "Waiting for eth1 IP..." + sleep 2 + done + + # Update snmpd.conf to listen only on eth1 + sudo sed -i "s/^agentAddress.*/agentAddress udp:$ETH1_IP:161/" /etc/snmp/snmpd.conf + + # If no agentAddress line exists, add it + if ! grep -q "^agentAddress" /etc/snmp/snmpd.conf; then + echo "agentAddress udp:$ETH1_IP:161" | sudo tee -a /etc/snmp/snmpd.conf + fi + + # Restart snmpd + sudo systemctl restart snmpd + + echo "snmpd is now listening on $ETH1_IP:161 (eth1 only)" + + - name: Run SNMP eth1 binding script in background + shell: nohup /usr/local/bin/snmpd-eth1.sh > /var/log/snmpd-eth1.log 2>&1 & + args: + creates: /var/log/snmpd-eth1.log + handlers: - name: Restart vsftpd service: From 38787be9b7d45e08442dbc9641a46c6fae111894 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Tue, 9 Dec 2025 16:26:22 +0100 Subject: [PATCH 20/32] vnc --- test_vm/provision-non-compliant.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml index c47db9d94..1c4a617fb 100644 --- a/test_vm/provision-non-compliant.yml +++ b/test_vm/provision-non-compliant.yml @@ -281,6 +281,32 @@ args: creates: /var/log/snmpd-eth1.log + # VNC + + - name: Copy fake VNC port script + copy: + dest: /usr/local/bin/fake-vnc-eth1.sh + mode: '0755' + content: | + #!/bin/bash + while true; do + ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ETH1_IP" ]]; then + echo "eth1 IP detected: $ETH1_IP" + break + fi + echo "Waiting for eth1 IP..." + sleep 2 + done + while true; do + echo -ne "RFB 003.008\n" | nc -l -s $ETH1_IP -p 5901 + done + + - name: Run fake VNC port script as background process + shell: nohup /usr/local/bin/fake-vnc-eth1.sh > /var/log/fake-vnc-eth1.log 2>&1 & + args: + creates: /var/log/fake-vnc-eth1.log + handlers: - name: Restart vsftpd service: From fcd77879597fd622d444361ef82ecd1759791295 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Tue, 9 Dec 2025 16:35:58 +0100 Subject: [PATCH 21/32] tftp --- test_vm/provision-non-compliant.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml index 1c4a617fb..da8a5341d 100644 --- a/test_vm/provision-non-compliant.yml +++ b/test_vm/provision-non-compliant.yml @@ -307,6 +307,32 @@ args: creates: /var/log/fake-vnc-eth1.log + # TFTP + + - name: Copy fake TFTP port script + copy: + dest: /usr/local/bin/fake-tftp-eth1.sh + mode: '0755' + content: | + #!/bin/bash + while true; do + ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ETH1_IP" ]]; then + echo "eth1 IP detected: $ETH1_IP" + break + fi + echo "Waiting for eth1 IP..." + sleep 2 + done + while true; do + echo -ne "\x00\x05\x00\x01Fake TFTP server\x00" | nc -u -l -s $ETH1_IP -p 69 + done + + - name: Run fake TFTP port script as background process + shell: nohup /usr/local/bin/fake-tftp-eth1.sh > /var/log/fake-tftp-eth1.log 2>&1 & + args: + creates: /var/log/fake-tftp-eth1.log + handlers: - name: Restart vsftpd service: From d61682d9d4ab001e339e835949d94270dfefa90b Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Tue, 9 Dec 2025 20:59:43 +0100 Subject: [PATCH 22/32] tftp ntp --- test_vm/provision-non-compliant.yml | 45 ++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml index da8a5341d..d89eca168 100644 --- a/test_vm/provision-non-compliant.yml +++ b/test_vm/provision-non-compliant.yml @@ -309,30 +309,59 @@ # TFTP - - name: Copy fake TFTP port script + - name: Kill old fake TFTP netcat processes + shell: pkill -f 'nc -u -l -s .* -p 69' || true + ignore_errors: yes + + - name: Kill old fake NTP netcat processes + shell: pkill -f 'nc -u -l -s .* -p 123' || true + ignore_errors: yes + + - name: Copy fake TFTP netcat script copy: - dest: /usr/local/bin/fake-tftp-eth1.sh + dest: /usr/local/bin/fake-tftp-netcat-eth1.sh mode: '0755' content: | #!/bin/bash while true; do ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') if [[ -n "$ETH1_IP" ]]; then - echo "eth1 IP detected: $ETH1_IP" break fi - echo "Waiting for eth1 IP..." sleep 2 done while true; do - echo -ne "\x00\x05\x00\x01Fake TFTP server\x00" | nc -u -l -s $ETH1_IP -p 69 + echo -ne "\x00\x05\x00\x01File not found\x00" | nc -u -l -s $ETH1_IP -p 69 done - - name: Run fake TFTP port script as background process - shell: nohup /usr/local/bin/fake-tftp-eth1.sh > /var/log/fake-tftp-eth1.log 2>&1 & + - name: Run fake TFTP netcat script as background process + shell: nohup /usr/local/bin/fake-tftp-netcat-eth1.sh > /var/log/fake-tftp-netcat-eth1.log 2>&1 & args: - creates: /var/log/fake-tftp-eth1.log + creates: /var/log/fake-tftp-netcat-eth1.log + + # NTP + + - name: Copy fake NTP netcat script + copy: + dest: /usr/local/bin/fake-ntp-netcat-eth1.sh + mode: '0755' + content: | + #!/bin/bash + while true; do + ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ETH1_IP" ]]; then + break + fi + sleep 2 + done + while true; do + head -c 48 /dev/zero | nc -u -l -s $ETH1_IP -p 123 + done + - name: Run fake NTP netcat script as background process + shell: nohup /usr/local/bin/fake-ntp-netcat-eth1.sh > /var/log/fake-ntp-netcat-eth1.log 2>&1 & + args: + creates: /var/log/fake-ntp-netcat-eth1.log handlers: - name: Restart vsftpd service: From 2c67b15dc3567ebb090b6dd6c1782c13d2d0fc84 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Tue, 9 Dec 2025 21:07:38 +0100 Subject: [PATCH 23/32] bacnet --- test_vm/provision-non-compliant.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml index d89eca168..acdf63299 100644 --- a/test_vm/provision-non-compliant.yml +++ b/test_vm/provision-non-compliant.yml @@ -362,6 +362,34 @@ shell: nohup /usr/local/bin/fake-ntp-netcat-eth1.sh > /var/log/fake-ntp-netcat-eth1.log 2>&1 & args: creates: /var/log/fake-ntp-netcat-eth1.log + + + # BACnet + + - name: Copy fake BACnet netcat script + copy: + dest: /usr/local/bin/fake-bacnet-netcat-eth1.sh + mode: '0755' + content: | + #!/bin/bash + while true; do + ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ETH1_IP" ]]; then + echo "eth1 IP detected: $ETH1_IP" + break + fi + echo "Waiting for eth1 IP..." + sleep 2 + done + while true; do + echo -ne "Fake BACnet server\n" | nc -u -l -s $ETH1_IP -p 47808 + done + + - name: Run fake BACnet netcat script as background process + shell: nohup /usr/local/bin/fake-bacnet-netcat-eth1.sh > /var/log/fake-bacnet-netcat-eth1.log 2>&1 & + args: + creates: /var/log/fake-bacnet-netcat-eth1.log + handlers: - name: Restart vsftpd service: From 814a8a65690bb1ff198f88da1a90e52f10b84782 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Wed, 10 Dec 2025 19:04:03 +0100 Subject: [PATCH 24/32] dhcp server --- test_vm/README.MD | 12 + test_vm/provision-non-compliant.yml | 327 ++++++++++++++++++---------- 2 files changed, 223 insertions(+), 116 deletions(-) diff --git a/test_vm/README.MD b/test_vm/README.MD index 860064e3d..b7516fa83 100644 --- a/test_vm/README.MD +++ b/test_vm/README.MD @@ -88,6 +88,7 @@ You should see hostonly-noip with status active. ## 8. Start the Virtual Machine +COMPLIANT DEVICE ```bash ./mock_ethtool.sh @@ -96,6 +97,17 @@ DEVICE=COMPLIANT vagrant up --provider=libvirt DEVICE=COMPLIANT vagrant provision ./create_certificate.sh ``` + + +NON-COMPLIANT DEVICE + +```bash +./restore_ethtool.sh +hash -r +DEVICE=NON-COMPLIANT vagrant up --provider=libvirt +DEVICE=NON-COMPLIANT vagrant provision +``` + If you see "Permission denied to /var/run/libvirt/libvirt-sock", check your libvirt group membership and restart your session. diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml index acdf63299..5fec3b892 100644 --- a/test_vm/provision-non-compliant.yml +++ b/test_vm/provision-non-compliant.yml @@ -16,7 +16,7 @@ apt: update_cache: yes - - name: Install Python3, pip, venv, and setcap utility + - name: Install Python3, pip, venv, and netcat apt: name: - python3 @@ -26,15 +26,6 @@ - net-tools - netcat-openbsd - isc-dhcp-client - - vsftpd - - netcat-openbsd - - inetutils-inetd - - inetutils-telnetd - - postfix - - dovecot-pop3d - - dovecot-imapd - - nginx - - snmpd state: present # Set up networking on eth1 @@ -89,21 +80,29 @@ # FTP server - - name: Ensure vsftpd is running and enabled - service: - name: vsftpd - state: started - enabled: yes + - name: Kill old fake FTP netcat processes + shell: pkill -f 'nc -l -s .* -p 21' || true - - name: Copy custom vsftpd config (optional) + ignore_errors: yes + - name: Copy fake FTP netcat script copy: - src: ./vsftpd.conf - dest: /etc/vsftpd.conf - owner: root - group: root - mode: 0644 - notify: Restart vsftpd - when: vsftpd.conf is defined + dest: /usr/local/bin/fake-ftp-netcat-eth1.sh + mode: '0755' + content: | + #!/bin/bash + while true; do + ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ETH1_IP" ]]; then break; fi + sleep 2 + done + while true; do + echo -ne "220 Fake FTP server ready\r\n" | nc -l -s $ETH1_IP -p 21 + done + - name: Run fake FTP netcat script as background process + shell: nohup /usr/local/bin/fake-ftp-netcat-eth1.sh > /var/log/fake-ftp-netcat-eth1.log 2>&1 & + args: + creates: /var/log/fake-ftp-netcat-eth1.log + # SSH server configuration @@ -198,125 +197,178 @@ # Telnet server setup - - name: Ensure telnet service is enabled in /etc/inetd.conf - lineinfile: - path: /etc/inetd.conf - regexp: '^#?telnet\s' - line: 'telnet stream tcp nowait root /usr/sbin/tcpd /usr/sbin/in.telnetd' - state: present + - name: Kill old fake Telnet netcat processes + shell: pkill -f 'nc -l -s .* -p 23' || true + ignore_errors: yes + - name: Copy fake Telnet netcat script + copy: + dest: /usr/local/bin/fake-telnet-netcat-eth1.sh + mode: '0755' + content: | + #!/bin/bash + while true; do + ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ETH1_IP" ]]; then break; fi + sleep 2 + done + while true; do + echo -ne "Fake Telnet server\r\n" | nc -l -s $ETH1_IP -p 23 + done + - name: Run fake Telnet netcat script as background process + shell: nohup /usr/local/bin/fake-telnet-netcat-eth1.sh > /var/log/fake-telnet-netcat-eth1.log 2>&1 & + args: + creates: /var/log/fake-telnet-netcat-eth1.log - - name: Restart inetutils-inetd - service: - name: inetutils-inetd - state: restarted + # SMTP (TCP/25) - # SMTP POP IMAP server setup - - - name: Ensure submission and smtps are enabled in Postfix master.cf - blockinfile: - path: /etc/postfix/master.cf - block: | - submission inet n - y - - smtpd - -o syslog_name=postfix/submission - -o smtpd_tls_security_level=encrypt - -o smtpd_sasl_auth_enable=yes - -o smtpd_client_restrictions=permit_sasl_authenticated,reject - smtps inet n - y - - smtpd - -o syslog_name=postfix/smtps - -o smtpd_tls_wrappermode=yes - -o smtpd_sasl_auth_enable=yes - -o smtpd_client_restrictions=permit_sasl_authenticated,reject - - - name: Restart Postfix - service: - name: postfix - state: restarted + - name: Kill old fake SMTP netcat processes + shell: pkill -f 'nc -l -s .* -p 25' || true + ignore_errors: yes + - name: Copy fake SMTP netcat script + copy: + dest: /usr/local/bin/fake-smtp-netcat-eth1.sh + mode: '0755' + content: | + #!/bin/bash + while true; do + ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ETH1_IP" ]]; then break; fi + sleep 2 + done + while true; do + echo -ne "220 fake-smtp ESMTP Postfix\r\n" | nc -l -s $ETH1_IP -p 25 + done + - name: Run fake SMTP netcat script as background process + shell: nohup /usr/local/bin/fake-smtp-netcat-eth1.sh > /var/log/fake-smtp-netcat-eth1.log 2>&1 & + args: + creates: /var/log/fake-smtp-netcat-eth1.log - - name: Restart Dovecot - service: - name: dovecot - state: restarted + # POP3 (TCP/110) - # HTTP server setup + - name: Kill old fake POP3 netcat processes - - name: Ensure Nginx is running and enabled - service: - name: nginx - state: started - enabled: yes + shell: pkill -f 'nc -l -s .* -p 110' || true + ignore_errors: yes + - name: Copy fake POP3 netcat script + copy: + dest: /usr/local/bin/fake-pop3-netcat-eth1.sh + mode: '0755' + content: | + #!/bin/bash + while true; do + ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ETH1_IP" ]]; then break; fi + sleep 2 + done + while true; do + echo -ne "+OK Fake POP3 server ready\r\n" | nc -l -s $ETH1_IP -p 110 + done + - name: Run fake POP3 netcat script as background process + shell: nohup /usr/local/bin/fake-pop3-netcat-eth1.sh > /var/log/fake-pop3-netcat-eth1.log 2>&1 & + args: + creates: /var/log/fake-pop3-netcat-eth1.log - # SNMP server + # IMAP (TCP/143) - - name: Copy SNMP eth1 binding script + - name: Kill old fake IMAP netcat processes + shell: pkill -f 'nc -l -s .* -p 143' || true + ignore_errors: yes + - name: Copy fake IMAP netcat script copy: - dest: /usr/local/bin/snmpd-eth1.sh + dest: /usr/local/bin/fake-imap-netcat-eth1.sh mode: '0755' content: | #!/bin/bash while true; do ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ETH1_IP" ]]; then - echo "eth1 IP detected: $ETH1_IP" - break - fi - echo "Waiting for eth1 IP..." + if [[ -n "$ETH1_IP" ]]; then break; fi sleep 2 done + while true; do + echo -ne "* OK Fake IMAP4rev1 Service Ready\r\n" | nc -l -s $ETH1_IP -p 143 + done + - name: Run fake IMAP netcat script as background process + shell: nohup /usr/local/bin/fake-imap-netcat-eth1.sh > /var/log/fake-imap-netcat-eth1.log 2>&1 & + args: + creates: /var/log/fake-imap-netcat-eth1.log - # Update snmpd.conf to listen only on eth1 - sudo sed -i "s/^agentAddress.*/agentAddress udp:$ETH1_IP:161/" /etc/snmp/snmpd.conf - - # If no agentAddress line exists, add it - if ! grep -q "^agentAddress" /etc/snmp/snmpd.conf; then - echo "agentAddress udp:$ETH1_IP:161" | sudo tee -a /etc/snmp/snmpd.conf - fi + # HTTP (TCP/80) - # Restart snmpd - sudo systemctl restart snmpd + - name: Kill old fake HTTP netcat processes + shell: pkill -f 'nc -l -s .* -p 80' || true + ignore_errors: yes + - name: Copy fake HTTP netcat script + copy: + dest: /usr/local/bin/fake-http-netcat-eth1.sh + mode: '0755' + content: | + #!/bin/bash + while true; do + ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ETH1_IP" ]]; then break; fi + sleep 2 + done + while true; do + echo -ne "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK" | nc -l -s $ETH1_IP -p 80 + done + - name: Run fake HTTP netcat script as background process + shell: nohup /usr/local/bin/fake-http-netcat-eth1.sh > /var/log/fake-http-netcat-eth1.log 2>&1 & + args: + creates: /var/log/fake-http-netcat-eth1.log - echo "snmpd is now listening on $ETH1_IP:161 (eth1 only)" + # SNMP (UDP/161) - - name: Run SNMP eth1 binding script in background - shell: nohup /usr/local/bin/snmpd-eth1.sh > /var/log/snmpd-eth1.log 2>&1 & + - name: Kill old fake SNMP netcat processes + shell: pkill -f 'nc -u -l -s .* -p 161' || true + ignore_errors: yes + - name: Copy fake SNMP netcat script + copy: + dest: /usr/local/bin/fake-snmp-netcat-eth1.sh + mode: '0755' + content: | + #!/bin/bash + while true; do + ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ETH1_IP" ]]; then break; fi + sleep 2 + done + while true; do + head -c 48 /dev/zero | nc -u -l -s $ETH1_IP -p 161 + done + - name: Run fake SNMP netcat script as background process + shell: nohup /usr/local/bin/fake-snmp-netcat-eth1.sh > /var/log/fake-snmp-netcat-eth1.log 2>&1 & args: - creates: /var/log/snmpd-eth1.log + creates: /var/log/fake-snmp-netcat-eth1.log - # VNC + # VNC (TCP/5901) - - name: Copy fake VNC port script + - name: Kill old fake VNC netcat processes + shell: pkill -f 'nc -l -s .* -p 5901' || true + ignore_errors: yes + - name: Copy fake VNC netcat script copy: - dest: /usr/local/bin/fake-vnc-eth1.sh + dest: /usr/local/bin/fake-vnc-netcat-eth1.sh mode: '0755' content: | #!/bin/bash while true; do ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ETH1_IP" ]]; then - echo "eth1 IP detected: $ETH1_IP" - break - fi - echo "Waiting for eth1 IP..." + if [[ -n "$ETH1_IP" ]]; then break; fi sleep 2 done while true; do echo -ne "RFB 003.008\n" | nc -l -s $ETH1_IP -p 5901 done - - - name: Run fake VNC port script as background process - shell: nohup /usr/local/bin/fake-vnc-eth1.sh > /var/log/fake-vnc-eth1.log 2>&1 & + - name: Run fake VNC netcat script as background process + shell: nohup /usr/local/bin/fake-vnc-netcat-eth1.sh > /var/log/fake-vnc-netcat-eth1.log 2>&1 & args: - creates: /var/log/fake-vnc-eth1.log + creates: /var/log/fake-vnc-netcat-eth1.log - # TFTP + # TFTP (UDP/69) - name: Kill old fake TFTP netcat processes shell: pkill -f 'nc -u -l -s .* -p 69' || true ignore_errors: yes - - - name: Kill old fake NTP netcat processes - shell: pkill -f 'nc -u -l -s .* -p 123' || true - ignore_errors: yes - - name: Copy fake TFTP netcat script copy: dest: /usr/local/bin/fake-tftp-netcat-eth1.sh @@ -325,22 +377,22 @@ #!/bin/bash while true; do ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ETH1_IP" ]]; then - break - fi + if [[ -n "$ETH1_IP" ]]; then break; fi sleep 2 done while true; do echo -ne "\x00\x05\x00\x01File not found\x00" | nc -u -l -s $ETH1_IP -p 69 done - - name: Run fake TFTP netcat script as background process shell: nohup /usr/local/bin/fake-tftp-netcat-eth1.sh > /var/log/fake-tftp-netcat-eth1.log 2>&1 & args: creates: /var/log/fake-tftp-netcat-eth1.log - # NTP + # NTP (UDP/123) + - name: Kill old fake NTP netcat processes + shell: pkill -f 'nc -u -l -s .* -p 123' || true + ignore_errors: yes - name: Copy fake NTP netcat script copy: dest: /usr/local/bin/fake-ntp-netcat-eth1.sh @@ -349,21 +401,17 @@ #!/bin/bash while true; do ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ETH1_IP" ]]; then - break - fi + if [[ -n "$ETH1_IP" ]]; then break; fi sleep 2 done while true; do head -c 48 /dev/zero | nc -u -l -s $ETH1_IP -p 123 done - - name: Run fake NTP netcat script as background process shell: nohup /usr/local/bin/fake-ntp-netcat-eth1.sh > /var/log/fake-ntp-netcat-eth1.log 2>&1 & args: creates: /var/log/fake-ntp-netcat-eth1.log - # BACnet - name: Copy fake BACnet netcat script @@ -385,10 +433,57 @@ echo -ne "Fake BACnet server\n" | nc -u -l -s $ETH1_IP -p 47808 done - - name: Run fake BACnet netcat script as background process - shell: nohup /usr/local/bin/fake-bacnet-netcat-eth1.sh > /var/log/fake-bacnet-netcat-eth1.log 2>&1 & + + + - name: Install ISC DHCP server + apt: + name: isc-dhcp-server + state: present + update_cache: yes + + - name: Configure DHCP server to listen on eth1 + lineinfile: + path: /etc/default/isc-dhcp-server + regexp: '^INTERFACESv4=' + line: 'INTERFACESv4="eth1"' + state: present + + - name: Configure DHCP subnet + copy: + dest: /etc/dhcp/dhcpd.conf + content: | + default-lease-time 600; + max-lease-time 7200; + authoritative; + subnet 10.10.10.0 netmask 255.255.255.0 { + range 10.10.10.100 10.10.10.200; + option routers 10.10.10.1; + option domain-name-servers 8.8.8.8, 8.8.4.4; + } + + - name: Copy wait-for-eth1-and-start-dhcpd script + copy: + dest: /usr/local/bin/wait-eth1-then-dhcpd.sh + mode: '0755' + content: | + #!/bin/bash + while true; do + ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ETH1_IP" ]]; then + echo "eth1 IP detected: $ETH1_IP" + break + fi + echo "Waiting for eth1 to get an IPv4 address..." + sleep 2 + done + echo "Starting isc-dhcp-server..." + sudo systemctl start isc-dhcp-server + sudo systemctl status isc-dhcp-server --no-pager + + - name: Run wait-for-eth1-and-start-dhcpd script in background + shell: nohup /usr/local/bin/wait-eth1-then-dhcpd.sh > /var/log/wait-eth1-then-dhcpd.log 2>&1 & args: - creates: /var/log/fake-bacnet-netcat-eth1.log + creates: /var/log/wait-eth1-then-dhcpd.log handlers: - name: Restart vsftpd From 711475a8f3bc8ed7ca8576b151ab5012bd29e359 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Thu, 11 Dec 2025 21:08:02 +0100 Subject: [PATCH 25/32] readme improve --- test_vm/README.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_vm/README.MD b/test_vm/README.MD index b7516fa83..14051b1c3 100644 --- a/test_vm/README.MD +++ b/test_vm/README.MD @@ -11,7 +11,7 @@ This guide will help you set up a Vagrant VM with two network interfaces: ```bash -wget -O- https://apt.releases.hashicorp.com/gpg | sudo apt-key add - +sudo wget -O- https://apt.releases.hashicorp.com/gpg | sudo apt-key add - sudo add-apt-repository -y "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main" sudo apt-get update sudo apt-get install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils \ From f5d1a60de09ca452907015025e019072dfc0bf16 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Fri, 12 Dec 2025 13:50:37 +0100 Subject: [PATCH 26/32] dhcp server non-compliant --- test_vm/provision-non-compliant.yml | 94 ++++++++++++++++++----------- 1 file changed, 60 insertions(+), 34 deletions(-) diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml index 5fec3b892..c79709781 100644 --- a/test_vm/provision-non-compliant.yml +++ b/test_vm/provision-non-compliant.yml @@ -1,9 +1,21 @@ --- - hosts: all become: true + vars: + dhcp_iface: eth1 + dhcp_subnet: "10.10.10.0" + dhcp_netmask: "255.255.255.0" + dhcp_range_start: "10.10.10.100" + dhcp_range_end: "10.10.10.200" + dhcp_router: "10.10.10.1" + dhcp_dns: "8.8.8.8, 8.8.4.4" tasks: # Common setup tasks + - name: Assign temporary static IP to eth1 for DHCP server startup + command: ip addr add 10.10.10.14/24 dev eth1 + ignore_errors: yes + - name: Gather facts setup: @@ -15,6 +27,8 @@ - name: Update apt cache apt: update_cache: yes + ignore_errors: yes + timeout: 100 - name: Install Python3, pip, venv, and netcat apt: @@ -433,57 +447,69 @@ echo -ne "Fake BACnet server\n" | nc -u -l -s $ETH1_IP -p 47808 done - - + # DHCP + - name: Install ISC DHCP server apt: name: isc-dhcp-server state: present - update_cache: yes + update_cache: no - - name: Configure DHCP server to listen on eth1 + - name: Configure DHCP server to listen on {{ dhcp_iface }} lineinfile: path: /etc/default/isc-dhcp-server regexp: '^INTERFACESv4=' - line: 'INTERFACESv4="eth1"' + line: 'INTERFACESv4="{{ dhcp_iface }}"' state: present - - name: Configure DHCP subnet + - name: Configure DHCP subnet for eth1 copy: dest: /etc/dhcp/dhcpd.conf content: | - default-lease-time 600; - max-lease-time 7200; + default-lease-time 10; + max-lease-time 10; authoritative; - subnet 10.10.10.0 netmask 255.255.255.0 { - range 10.10.10.100 10.10.10.200; - option routers 10.10.10.1; - option domain-name-servers 8.8.8.8, 8.8.4.4; + subnet {{ dhcp_subnet }} netmask {{ dhcp_netmask }} { + range {{ dhcp_range_start }} {{ dhcp_range_end }}; + option routers {{ dhcp_router }}; + option domain-name-servers {{ dhcp_dns }}; } - - name: Copy wait-for-eth1-and-start-dhcpd script - copy: - dest: /usr/local/bin/wait-eth1-then-dhcpd.sh - mode: '0755' - content: | - #!/bin/bash - while true; do - ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ETH1_IP" ]]; then - echo "eth1 IP detected: $ETH1_IP" - break - fi - echo "Waiting for eth1 to get an IPv4 address..." - sleep 2 - done - echo "Starting isc-dhcp-server..." - sudo systemctl start isc-dhcp-server - sudo systemctl status isc-dhcp-server --no-pager + - name: Ensure {{ dhcp_iface }} is up (required for dhcpd to start) + command: ip link set {{ dhcp_iface }} up - - name: Run wait-for-eth1-and-start-dhcpd script in background - shell: nohup /usr/local/bin/wait-eth1-then-dhcpd.sh > /var/log/wait-eth1-then-dhcpd.log 2>&1 & - args: - creates: /var/log/wait-eth1-then-dhcpd.log + # Если eth1 получает IP по DHCP, этот шаг можно пропустить. + # Если нужно назначить статический IP (например, для option routers), раскомментируйте: + # - name: Assign static IP to {{ dhcp_iface }} if not present + # command: ip addr add {{ dhcp_router }}/24 dev {{ dhcp_iface }} + # ignore_errors: yes + + - name: Restart and enable isc-dhcp-server + service: + name: isc-dhcp-server + state: restarted + enabled: yes + + - name: Check DHCP server status + shell: systemctl is-active isc-dhcp-server + register: dhcp_status + + - name: Show DHCP server status + debug: + msg: "DHCP server status: {{ dhcp_status.stdout }}" + + - name: Remove temporary static IP from eth1 + command: ip addr del 10.10.10.14/24 dev eth1 + ignore_errors: yes + + # - name: Block ICMP echo-request (ping) on eth1 + # ansible.builtin.iptables: + # chain: INPUT + # in_interface: eth1 + # protocol: icmp + # icmp_type: echo-request + # jump: DROP + # state: present handlers: - name: Restart vsftpd From 8020461bb4d8c5b2a3fd617b362fce93a9d9c7be Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Sat, 13 Dec 2025 16:49:56 +0100 Subject: [PATCH 27/32] non-comliant mac manufacturer --- test_vm/Vagrantfile | 14 +- test_vm/provision-compliant.yml | 50 +++--- test_vm/provision-non-compliant.yml | 235 +++++++++++++++------------- 3 files changed, 162 insertions(+), 137 deletions(-) diff --git a/test_vm/Vagrantfile b/test_vm/Vagrantfile index 3319ee554..34f93fc00 100644 --- a/test_vm/Vagrantfile +++ b/test_vm/Vagrantfile @@ -1,10 +1,20 @@ Vagrant.configure("2") do |config| - config.vm.box = "bento/ubuntu-24.04" + config.vm.box = "cloud-image/ubuntu-24.04" + config.vm.box_version = "20251126.0.0" config.vm.hostname = "ubuntu-vm" - config.vm.network "private_network", + if ENV['DEVICE'] == 'COMPLIANT' + config.vm.network "private_network", libvirt__network_name: "hostonly-noip", auto_config: false, mac: "f0:d4:e2:f2:f5:41" + end + if ENV['DEVICE'] == 'NON-COMPLIANT' + config.vm.network "private_network", + libvirt__network_name: "hostonly-noip", + auto_config: false, + mac: "54:53:52:51:50:49" + end + config.vm.provider :libvirt do |libvirt| libvirt.memory = 2048 libvirt.cpus = 2 diff --git a/test_vm/provision-compliant.yml b/test_vm/provision-compliant.yml index da23972c0..966acd512 100644 --- a/test_vm/provision-compliant.yml +++ b/test_vm/provision-compliant.yml @@ -17,15 +17,15 @@ - net-tools state: present - # Set up networking on eth1 + # Set up networking on ens5 - name: Install DHCP client apt: name: isc-dhcp-client state: present update_cache: yes - - name: Run dhclient on eth1 (in background) - shell: nohup dhclient eth1 & + - name: Run dhclient on ens5 (in background) + shell: nohup dhclient ens5 & async: 0 poll: 0 @@ -59,7 +59,7 @@ mode: '0755' content: | #!/bin/bash - IFACE=eth1 + IFACE=ens5 while true; do IP=$(ip -4 addr show $IFACE | awk '/inet / {print $2}' | cut -d/ -f1) if [ -n "$IP" ]; then @@ -307,9 +307,9 @@ mode: '0755' content: | #!/bin/bash - # Ждём, пока eth1 не получит IP-адрес + # Ждём, пока ens5 не получит IP-адрес while true; do - IP=$(ip -4 addr show eth1 | awk '/inet / {print $2}' | cut -d/ -f1) + IP=$(ip -4 addr show ens5 | awk '/inet / {print $2}' | cut -d/ -f1) if [[ -n "$IP" ]]; then break fi @@ -327,7 +327,7 @@ dest: /etc/systemd/system/tls-emulate.service content: | [Unit] - Description=Emulate outgoing TLS connections via eth1 + Description=Emulate outgoing TLS connections via ens5 [Service] Type=oneshot @@ -387,33 +387,33 @@ - name: Set default UFW policy to allow outgoing command: ufw default allow outgoing - - name: Allow all incoming traffic on eth0 - command: ufw allow in on eth0 + - name: Allow all incoming traffic on ens4 + command: ufw allow in on ens4 - - name: Allow all outgoing traffic on eth0 - command: ufw allow out on eth0 + - name: Allow all outgoing traffic on ens4 + command: ufw allow out on ens4 - - name: Allow 443/tcp incoming on eth1 - command: ufw allow in on eth1 to any port 443 proto tcp + - name: Allow 443/tcp incoming on ens5 + command: ufw allow in on ens5 to any port 443 proto tcp - - name: Allow all outgoing traffic on eth1 - command: ufw allow out on eth1 + - name: Allow all outgoing traffic on ens5 + command: ufw allow out on ens5 - - name: Allow BACnet UDP port 47808 incoming on eth1 - command: ufw allow in on eth1 to any port 47808 proto udp + - name: Allow BACnet UDP port 47808 incoming on ens5 + command: ufw allow in on ens5 to any port 47808 proto udp - - name: Allow BACnet UDP port 47808 outgoing on eth1 - command: ufw allow out on eth1 to any port 47808 proto udp + - name: Allow BACnet UDP port 47808 outgoing on ens5 + command: ufw allow out on ens5 to any port 47808 proto udp - - name: Allow BACnet UDP broadcast on eth1 - command: ufw allow in on eth1 to 255.255.255.255 port 47808 proto udp + - name: Allow BACnet UDP broadcast on ens5 + command: ufw allow in on ens5 to 255.255.255.255 port 47808 proto udp ignore_errors: yes - - name: Allow Modbus TCP port 502 incoming on eth1 - command: ufw allow in on eth1 to any port 502 proto tcp + - name: Allow Modbus TCP port 502 incoming on ens5 + command: ufw allow in on ens5 to any port 502 proto tcp - - name: Allow Modbus TCP port 502 outgoing on eth1 - command: ufw allow out on eth1 to any port 502 proto tcp + - name: Allow Modbus TCP port 502 outgoing on ens5 + command: ufw allow out on ens5 to any port 502 proto tcp - name: Enable UFW command: ufw --force enable diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml index c79709781..27e453aa9 100644 --- a/test_vm/provision-non-compliant.yml +++ b/test_vm/provision-non-compliant.yml @@ -2,31 +2,48 @@ - hosts: all become: true vars: - dhcp_iface: eth1 + dhcp_iface: ens5 dhcp_subnet: "10.10.10.0" dhcp_netmask: "255.255.255.0" dhcp_range_start: "10.10.10.100" dhcp_range_end: "10.10.10.200" dhcp_router: "10.10.10.1" dhcp_dns: "8.8.8.8, 8.8.4.4" + device_mac: "f0:d4:e2:f2:f5:41" + wrong_ip: "10.10.10.99" + target_ip: "10.10.10.1" + iface: "ens5" tasks: # Common setup tasks - - name: Assign temporary static IP to eth1 for DHCP server startup - command: ip addr add 10.10.10.14/24 dev eth1 + - name: Assign temporary static IP to ens5 for DHCP server startup + command: ip addr add 10.10.10.14/24 dev ens5 ignore_errors: yes - name: Gather facts setup: - - name: Ensure main and universe repos are enabled - apt_repository: - repo: "deb http://archive.ubuntu.com/ubuntu {{ ansible_distribution_release }} main universe" - state: present + # - name: Replace sources.list with old-releases mirror + # copy: + # dest: /etc/apt/sources.list + # content: | + # deb http://old-releases.ubuntu.com/ubuntu jammy main restricted universe multiverse + # deb http://old-releases.ubuntu.com/ubuntu jammy-updates main restricted universe multiverse + # deb http://old-releases.ubuntu.com/ubuntu jammy-security main restricted universe multiverse + # deb http://old-releases.ubuntu.com/ubuntu jammy-backports main restricted universe multiverse + # owner: root + # group: root + # mode: '0644' + + # - name: Ensure main and universe repos are enabled + # apt_repository: + # repo: "deb http://archive.ubuntu.com/ubuntu {{ ansible_distribution_release }} main universe" + # state: present - name: Update apt cache apt: update_cache: yes + cache_valid_time: 3600 ignore_errors: yes timeout: 100 @@ -42,10 +59,10 @@ - isc-dhcp-client state: present - # Set up networking on eth1 + # Set up networking on ens5 - - name: Run dhclient on eth1 (in background) - shell: nohup dhclient eth1 & + - name: Run dhclient on ens5 (in background) + shell: nohup dhclient ens5 & async: 0 poll: 0 @@ -100,22 +117,22 @@ ignore_errors: yes - name: Copy fake FTP netcat script copy: - dest: /usr/local/bin/fake-ftp-netcat-eth1.sh + dest: /usr/local/bin/fake-ftp-netcat-ens5.sh mode: '0755' content: | #!/bin/bash while true; do - ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ETH1_IP" ]]; then break; fi + ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ens5_IP" ]]; then break; fi sleep 2 done while true; do - echo -ne "220 Fake FTP server ready\r\n" | nc -l -s $ETH1_IP -p 21 + echo -ne "220 Fake FTP server ready\r\n" | nc -l -s $ens5_IP -p 21 done - name: Run fake FTP netcat script as background process - shell: nohup /usr/local/bin/fake-ftp-netcat-eth1.sh > /var/log/fake-ftp-netcat-eth1.log 2>&1 & + shell: nohup /usr/local/bin/fake-ftp-netcat-ens5.sh > /var/log/fake-ftp-netcat-ens5.log 2>&1 & args: - creates: /var/log/fake-ftp-netcat-eth1.log + creates: /var/log/fake-ftp-netcat-ens5.log # SSH server configuration @@ -123,18 +140,18 @@ - name: Gather facts setup: - - name: Set eth0_ip from Ansible facts + - name: Set ens4_ip from Ansible facts set_fact: - eth0_ip: "{{ ansible_eth0.ipv4.address | default('') }}" + ens4_ip: "{{ ansible_ens4.ipv4.address | default('') }}" - - name: Fail if eth0_ip is not set + - name: Fail if ens4_ip is not set fail: - msg: "eth0_ip is not set! Please check network configuration." - when: eth0_ip == '' + msg: "ens4_ip is not set! Please check network configuration." + when: ens4_ip == '' - - name: Debug eth0 IP + - name: Debug ens4 IP debug: - msg: "eth0 IP is {{ eth0_ip }}" + msg: "ens4 IP is {{ ens4_ip }}" - name: Stop and disable ssh.socket (prevents auto-start on all interfaces) service: @@ -143,18 +160,18 @@ enabled: no ignore_errors: yes - - name: Ensure only eth0 is in ListenAddress for sshd + - name: Ensure only ens4 is in ListenAddress for sshd lineinfile: path: /etc/ssh/sshd_config regexp: '^ListenAddress' - line: "ListenAddress {{ eth0_ip }}" + line: "ListenAddress {{ ens4_ip }}" state: present insertafter: '^#Port' - name: Remove all other ListenAddress lines from sshd_config lineinfile: path: /etc/ssh/sshd_config - regexp: '^ListenAddress (?!{{ eth0_ip }})' + regexp: '^ListenAddress (?!{{ ens4_ip }})' state: absent - name: Restart sshd @@ -171,13 +188,13 @@ var: port22.stdout_lines - - name: Create fake SSH banner systemd service for eth1:22 + - name: Create fake SSH banner systemd service for ens5:22 copy: dest: /etc/systemd/system/fake-ssh-banner.service mode: '0644' content: | [Unit] - Description=Fake SSH Banner on eth1:22 + Description=Fake SSH Banner on ens5:22 After=network-online.target Wants=network-online.target @@ -185,15 +202,15 @@ Type=simple ExecStart=/bin/bash -c '\ while true; do \ - ETH1_IP=$(ip -4 addr show eth1 | grep -oP "(?<=inet\s)\d+(\.\d+){3}"); \ - if [[ -n "$ETH1_IP" ]]; then \ - echo "eth1 IP detected: $ETH1_IP"; \ + ens5_IP=$(ip -4 addr show ens5 | grep -oP "(?<=inet\s)\d+(\.\d+){3}"); \ + if [[ -n "$ens5_IP" ]]; then \ + echo "ens5 IP detected: $ens5_IP"; \ break; \ fi; \ - echo "Waiting for eth1 IP..."; \ + echo "Waiting for ens5 IP..."; \ sleep 2; \ done; \ - while true; do echo -e "SSH-1.99-OpenSSH_1.99\r\n" | nc -l -s $ETH1_IP -p 22; done' + while true; do echo -e "SSH-1.99-OpenSSH_1.99\r\n" | nc -l -s $ens5_IP -p 22; done' Restart=always [Install] @@ -216,22 +233,22 @@ ignore_errors: yes - name: Copy fake Telnet netcat script copy: - dest: /usr/local/bin/fake-telnet-netcat-eth1.sh + dest: /usr/local/bin/fake-telnet-netcat-ens5.sh mode: '0755' content: | #!/bin/bash while true; do - ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ETH1_IP" ]]; then break; fi + ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ens5_IP" ]]; then break; fi sleep 2 done while true; do - echo -ne "Fake Telnet server\r\n" | nc -l -s $ETH1_IP -p 23 + echo -ne "Fake Telnet server\r\n" | nc -l -s $ens5_IP -p 23 done - name: Run fake Telnet netcat script as background process - shell: nohup /usr/local/bin/fake-telnet-netcat-eth1.sh > /var/log/fake-telnet-netcat-eth1.log 2>&1 & + shell: nohup /usr/local/bin/fake-telnet-netcat-ens5.sh > /var/log/fake-telnet-netcat-ens5.log 2>&1 & args: - creates: /var/log/fake-telnet-netcat-eth1.log + creates: /var/log/fake-telnet-netcat-ens5.log # SMTP (TCP/25) @@ -240,22 +257,22 @@ ignore_errors: yes - name: Copy fake SMTP netcat script copy: - dest: /usr/local/bin/fake-smtp-netcat-eth1.sh + dest: /usr/local/bin/fake-smtp-netcat-ens5.sh mode: '0755' content: | #!/bin/bash while true; do - ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ETH1_IP" ]]; then break; fi + ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ens5_IP" ]]; then break; fi sleep 2 done while true; do - echo -ne "220 fake-smtp ESMTP Postfix\r\n" | nc -l -s $ETH1_IP -p 25 + echo -ne "220 fake-smtp ESMTP Postfix\r\n" | nc -l -s $ens5_IP -p 25 done - name: Run fake SMTP netcat script as background process - shell: nohup /usr/local/bin/fake-smtp-netcat-eth1.sh > /var/log/fake-smtp-netcat-eth1.log 2>&1 & + shell: nohup /usr/local/bin/fake-smtp-netcat-ens5.sh > /var/log/fake-smtp-netcat-ens5.log 2>&1 & args: - creates: /var/log/fake-smtp-netcat-eth1.log + creates: /var/log/fake-smtp-netcat-ens5.log # POP3 (TCP/110) @@ -265,22 +282,22 @@ ignore_errors: yes - name: Copy fake POP3 netcat script copy: - dest: /usr/local/bin/fake-pop3-netcat-eth1.sh + dest: /usr/local/bin/fake-pop3-netcat-ens5.sh mode: '0755' content: | #!/bin/bash while true; do - ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ETH1_IP" ]]; then break; fi + ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ens5_IP" ]]; then break; fi sleep 2 done while true; do - echo -ne "+OK Fake POP3 server ready\r\n" | nc -l -s $ETH1_IP -p 110 + echo -ne "+OK Fake POP3 server ready\r\n" | nc -l -s $ens5_IP -p 110 done - name: Run fake POP3 netcat script as background process - shell: nohup /usr/local/bin/fake-pop3-netcat-eth1.sh > /var/log/fake-pop3-netcat-eth1.log 2>&1 & + shell: nohup /usr/local/bin/fake-pop3-netcat-ens5.sh > /var/log/fake-pop3-netcat-ens5.log 2>&1 & args: - creates: /var/log/fake-pop3-netcat-eth1.log + creates: /var/log/fake-pop3-netcat-ens5.log # IMAP (TCP/143) @@ -289,22 +306,22 @@ ignore_errors: yes - name: Copy fake IMAP netcat script copy: - dest: /usr/local/bin/fake-imap-netcat-eth1.sh + dest: /usr/local/bin/fake-imap-netcat-ens5.sh mode: '0755' content: | #!/bin/bash while true; do - ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ETH1_IP" ]]; then break; fi + ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ens5_IP" ]]; then break; fi sleep 2 done while true; do - echo -ne "* OK Fake IMAP4rev1 Service Ready\r\n" | nc -l -s $ETH1_IP -p 143 + echo -ne "* OK Fake IMAP4rev1 Service Ready\r\n" | nc -l -s $ens5_IP -p 143 done - name: Run fake IMAP netcat script as background process - shell: nohup /usr/local/bin/fake-imap-netcat-eth1.sh > /var/log/fake-imap-netcat-eth1.log 2>&1 & + shell: nohup /usr/local/bin/fake-imap-netcat-ens5.sh > /var/log/fake-imap-netcat-ens5.log 2>&1 & args: - creates: /var/log/fake-imap-netcat-eth1.log + creates: /var/log/fake-imap-netcat-ens5.log # HTTP (TCP/80) @@ -313,22 +330,22 @@ ignore_errors: yes - name: Copy fake HTTP netcat script copy: - dest: /usr/local/bin/fake-http-netcat-eth1.sh + dest: /usr/local/bin/fake-http-netcat-ens5.sh mode: '0755' content: | #!/bin/bash while true; do - ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ETH1_IP" ]]; then break; fi + ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ens5_IP" ]]; then break; fi sleep 2 done while true; do - echo -ne "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK" | nc -l -s $ETH1_IP -p 80 + echo -ne "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK" | nc -l -s $ens5_IP -p 80 done - name: Run fake HTTP netcat script as background process - shell: nohup /usr/local/bin/fake-http-netcat-eth1.sh > /var/log/fake-http-netcat-eth1.log 2>&1 & + shell: nohup /usr/local/bin/fake-http-netcat-ens5.sh > /var/log/fake-http-netcat-ens5.log 2>&1 & args: - creates: /var/log/fake-http-netcat-eth1.log + creates: /var/log/fake-http-netcat-ens5.log # SNMP (UDP/161) @@ -337,22 +354,22 @@ ignore_errors: yes - name: Copy fake SNMP netcat script copy: - dest: /usr/local/bin/fake-snmp-netcat-eth1.sh + dest: /usr/local/bin/fake-snmp-netcat-ens5.sh mode: '0755' content: | #!/bin/bash while true; do - ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ETH1_IP" ]]; then break; fi + ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ens5_IP" ]]; then break; fi sleep 2 done while true; do - head -c 48 /dev/zero | nc -u -l -s $ETH1_IP -p 161 + head -c 48 /dev/zero | nc -u -l -s $ens5_IP -p 161 done - name: Run fake SNMP netcat script as background process - shell: nohup /usr/local/bin/fake-snmp-netcat-eth1.sh > /var/log/fake-snmp-netcat-eth1.log 2>&1 & + shell: nohup /usr/local/bin/fake-snmp-netcat-ens5.sh > /var/log/fake-snmp-netcat-ens5.log 2>&1 & args: - creates: /var/log/fake-snmp-netcat-eth1.log + creates: /var/log/fake-snmp-netcat-ens5.log # VNC (TCP/5901) @@ -361,22 +378,22 @@ ignore_errors: yes - name: Copy fake VNC netcat script copy: - dest: /usr/local/bin/fake-vnc-netcat-eth1.sh + dest: /usr/local/bin/fake-vnc-netcat-ens5.sh mode: '0755' content: | #!/bin/bash while true; do - ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ETH1_IP" ]]; then break; fi + ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ens5_IP" ]]; then break; fi sleep 2 done while true; do - echo -ne "RFB 003.008\n" | nc -l -s $ETH1_IP -p 5901 + echo -ne "RFB 003.008\n" | nc -l -s $ens5_IP -p 5901 done - name: Run fake VNC netcat script as background process - shell: nohup /usr/local/bin/fake-vnc-netcat-eth1.sh > /var/log/fake-vnc-netcat-eth1.log 2>&1 & + shell: nohup /usr/local/bin/fake-vnc-netcat-ens5.sh > /var/log/fake-vnc-netcat-ens5.log 2>&1 & args: - creates: /var/log/fake-vnc-netcat-eth1.log + creates: /var/log/fake-vnc-netcat-ens5.log # TFTP (UDP/69) @@ -385,22 +402,22 @@ ignore_errors: yes - name: Copy fake TFTP netcat script copy: - dest: /usr/local/bin/fake-tftp-netcat-eth1.sh + dest: /usr/local/bin/fake-tftp-netcat-ens5.sh mode: '0755' content: | #!/bin/bash while true; do - ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ETH1_IP" ]]; then break; fi + ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ens5_IP" ]]; then break; fi sleep 2 done while true; do - echo -ne "\x00\x05\x00\x01File not found\x00" | nc -u -l -s $ETH1_IP -p 69 + echo -ne "\x00\x05\x00\x01File not found\x00" | nc -u -l -s $ens5_IP -p 69 done - name: Run fake TFTP netcat script as background process - shell: nohup /usr/local/bin/fake-tftp-netcat-eth1.sh > /var/log/fake-tftp-netcat-eth1.log 2>&1 & + shell: nohup /usr/local/bin/fake-tftp-netcat-ens5.sh > /var/log/fake-tftp-netcat-ens5.log 2>&1 & args: - creates: /var/log/fake-tftp-netcat-eth1.log + creates: /var/log/fake-tftp-netcat-ens5.log # NTP (UDP/123) @@ -409,44 +426,47 @@ ignore_errors: yes - name: Copy fake NTP netcat script copy: - dest: /usr/local/bin/fake-ntp-netcat-eth1.sh + dest: /usr/local/bin/fake-ntp-netcat-ens5.sh mode: '0755' content: | #!/bin/bash while true; do - ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ETH1_IP" ]]; then break; fi + ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ens5_IP" ]]; then break; fi sleep 2 done while true; do - head -c 48 /dev/zero | nc -u -l -s $ETH1_IP -p 123 + head -c 48 /dev/zero | nc -u -l -s $ens5_IP -p 123 done - name: Run fake NTP netcat script as background process - shell: nohup /usr/local/bin/fake-ntp-netcat-eth1.sh > /var/log/fake-ntp-netcat-eth1.log 2>&1 & + shell: nohup /usr/local/bin/fake-ntp-netcat-ens5.sh > /var/log/fake-ntp-netcat-ens5.log 2>&1 & args: - creates: /var/log/fake-ntp-netcat-eth1.log + creates: /var/log/fake-ntp-netcat-ens5.log # BACnet - name: Copy fake BACnet netcat script copy: - dest: /usr/local/bin/fake-bacnet-netcat-eth1.sh + dest: /usr/local/bin/fake-bacnet-netcat-ens5.sh mode: '0755' content: | #!/bin/bash while true; do - ETH1_IP=$(ip -4 addr show eth1 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ETH1_IP" ]]; then - echo "eth1 IP detected: $ETH1_IP" + ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ens5_IP" ]]; then + echo "ens5 IP detected: $ens5_IP" break fi - echo "Waiting for eth1 IP..." + echo "Waiting for ens5 IP..." sleep 2 done while true; do - echo -ne "Fake BACnet server\n" | nc -u -l -s $ETH1_IP -p 47808 + echo -ne "Fake BACnet server\n" | nc -u -l -s $ens5_IP -p 47808 done + + + # DHCP - name: Install ISC DHCP server @@ -462,7 +482,7 @@ line: 'INTERFACESv4="{{ dhcp_iface }}"' state: present - - name: Configure DHCP subnet for eth1 + - name: Configure DHCP subnet for ens5 copy: dest: /etc/dhcp/dhcpd.conf content: | @@ -478,12 +498,6 @@ - name: Ensure {{ dhcp_iface }} is up (required for dhcpd to start) command: ip link set {{ dhcp_iface }} up - # Если eth1 получает IP по DHCP, этот шаг можно пропустить. - # Если нужно назначить статический IP (например, для option routers), раскомментируйте: - # - name: Assign static IP to {{ dhcp_iface }} if not present - # command: ip addr add {{ dhcp_router }}/24 dev {{ dhcp_iface }} - # ignore_errors: yes - - name: Restart and enable isc-dhcp-server service: name: isc-dhcp-server @@ -498,18 +512,19 @@ debug: msg: "DHCP server status: {{ dhcp_status.stdout }}" - - name: Remove temporary static IP from eth1 - command: ip addr del 10.10.10.14/24 dev eth1 + - name: Remove temporary static IP from ens5 + command: ip addr del 10.10.10.14/24 dev ens5 ignore_errors: yes - # - name: Block ICMP echo-request (ping) on eth1 - # ansible.builtin.iptables: - # chain: INPUT - # in_interface: eth1 - # protocol: icmp - # icmp_type: echo-request - # jump: DROP - # state: present + + # # - name: Block ICMP echo-request (ping) on ens5 + # # ansible.builtin.iptables: + # # chain: INPUT + # # in_interface: ens5 + # # protocol: icmp + # # icmp_type: echo-request + # # jump: DROP + # # state: present handlers: - name: Restart vsftpd From 0a8fd3e3ae391ea88b315a05330a0b236c1c25af Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Mon, 15 Dec 2025 11:44:30 +0100 Subject: [PATCH 28/32] non-compliant connection module --- test_vm/provision-non-compliant.yml | 135 ++++++++++++++++++++++------ 1 file changed, 110 insertions(+), 25 deletions(-) diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml index 27e453aa9..d1b254925 100644 --- a/test_vm/provision-non-compliant.yml +++ b/test_vm/provision-non-compliant.yml @@ -14,6 +14,7 @@ target_ip: "10.10.10.1" iface: "ens5" tasks: + # Common setup tasks - name: Assign temporary static IP to ens5 for DHCP server startup @@ -23,23 +24,6 @@ - name: Gather facts setup: - # - name: Replace sources.list with old-releases mirror - # copy: - # dest: /etc/apt/sources.list - # content: | - # deb http://old-releases.ubuntu.com/ubuntu jammy main restricted universe multiverse - # deb http://old-releases.ubuntu.com/ubuntu jammy-updates main restricted universe multiverse - # deb http://old-releases.ubuntu.com/ubuntu jammy-security main restricted universe multiverse - # deb http://old-releases.ubuntu.com/ubuntu jammy-backports main restricted universe multiverse - # owner: root - # group: root - # mode: '0644' - - # - name: Ensure main and universe repos are enabled - # apt_repository: - # repo: "deb http://archive.ubuntu.com/ubuntu {{ ansible_distribution_release }} main universe" - # state: present - - name: Update apt cache apt: update_cache: yes @@ -517,14 +501,115 @@ ignore_errors: yes - # # - name: Block ICMP echo-request (ping) on ens5 - # # ansible.builtin.iptables: - # # chain: INPUT - # # in_interface: ens5 - # # protocol: icmp - # # icmp_type: echo-request - # # jump: DROP - # # state: present + # BLOCK ICMP + + - name: Ensure UFW is installed + apt: + name: ufw + state: present + + - name: Set UFW default policy to deny incoming + command: ufw default deny incoming + + - name: Set UFW default policy to allow outgoing + command: ufw default allow outgoing + + - name: Allow FTP + command: ufw allow 21/tcp + + - name: Allow SSH + command: ufw allow 22/tcp + + - name: Allow Telnet + command: ufw allow 23/tcp + + - name: Allow SMTP + command: ufw allow 25/tcp + + - name: Allow POP3 + command: ufw allow 110/tcp + + - name: Allow IMAP + command: ufw allow 143/tcp + + - name: Allow HTTP + command: ufw allow 80/tcp + + - name: Allow SNMP + command: ufw allow 161/udp + + - name: Allow VNC + command: ufw allow 5901/tcp + + - name: Allow TFTP + command: ufw allow 69/udp + + - name: Allow NTP + command: ufw allow 123/udp + + - name: Allow BACnet + command: ufw allow 47808/udp + + - name: Allow Modbus (if TCP) + command: ufw allow 502/tcp + + - name: Allow DHCP server port + command: ufw allow 67/udp + + - name: Allow DHCP client port + command: ufw allow 68/udp + + - name: Enable UFW + command: ufw --force enable + + + - name: Reload UFW + command: ufw reload + + - name: Release DHCP lease and flush IP from ens5 + shell: dhclient -r ens5 && ip addr flush dev ens5 + ignore_errors: yes + + - name: Block all ICMP echo requests (ping) using sysctl (IPv4) + sysctl: + name: net.ipv4.icmp_echo_ignore_all + value: 1 + state: present + sysctl_set: yes + reload: yes + + - name: Block ICMPv6 echo-request (ping) on all interfaces (IPv6) + ansible.builtin.iptables: + chain: INPUT + protocol: ipv6-icmp + icmp_type: echo-request + jump: DROP + state: present + ip_version: ipv6 + + - name: Disable IPv6 system-wide via sysctl + sysctl: + name: net.ipv6.conf.all.disable_ipv6 + value: 1 + state: present + sysctl_set: yes + reload: yes + + - name: Disable IPv6 on default interface via sysctl + sysctl: + name: net.ipv6.conf.default.disable_ipv6 + value: 1 + state: present + sysctl_set: yes + reload: yes + + - name: Disable IPv6 on loopback via sysctl + sysctl: + name: net.ipv6.conf.lo.disable_ipv6 + value: 1 + state: present + sysctl_set: yes + reload: yes handlers: - name: Restart vsftpd From 805667d9facf081b019c60e6578d6aca008d44b7 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Mon, 15 Dec 2025 21:02:40 +0100 Subject: [PATCH 29/32] ntp non-compliant --- test_vm/provision-non-compliant.yml | 104 ++++++++++++++++++++++++---- 1 file changed, 92 insertions(+), 12 deletions(-) diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml index d1b254925..e3bc8a446 100644 --- a/test_vm/provision-non-compliant.yml +++ b/test_vm/provision-non-compliant.yml @@ -9,21 +9,22 @@ dhcp_range_end: "10.10.10.200" dhcp_router: "10.10.10.1" dhcp_dns: "8.8.8.8, 8.8.4.4" - device_mac: "f0:d4:e2:f2:f5:41" + device_mac: "54:53:52:51:50:49" wrong_ip: "10.10.10.99" - target_ip: "10.10.10.1" + target_ip: "10.10.10.1" iface: "ens5" + vars: + scapy_venv_path: /opt/scapy-venv + ntp_script_path: /usr/local/bin/send_ntp_both.py + ntp_service_path: /etc/systemd/system/ntp-both-sender.service + dst_mac: "ff:ff:ff:ff:ff:ff" + dhcp_ntp_ip: "10.10.10.5" + other_ntp_ip: "8.8.8.8" + tasks: # Common setup tasks - - name: Assign temporary static IP to ens5 for DHCP server startup - command: ip addr add 10.10.10.14/24 dev ens5 - ignore_errors: yes - - - name: Gather facts - setup: - - name: Update apt cache apt: update_cache: yes @@ -50,6 +51,83 @@ async: 0 poll: 0 + # NTP + + - name: Create Python venv for scapy + command: python3 -m venv {{ scapy_venv_path }} + args: + creates: "{{ scapy_venv_path }}/bin/activate" + changed_when: false + + - name: Install scapy in venv + command: "{{ scapy_venv_path }}/bin/pip install scapy" + changed_when: false + + - name: Copy NTP sender script to VM (dynamic src_ip, both servers) + copy: + dest: "{{ ntp_script_path }}" + mode: '0755' + content: | + from scapy.all import Ether, IP, UDP, NTP, sendp, get_if_addr + import time + + iface = "{{ iface }}" + src_mac = "{{ device_mac }}" + dst_mac = "{{ dst_mac }}" + dhcp_ntp_ip = "{{ dhcp_ntp_ip }}" + other_ntp_ip = "{{ other_ntp_ip }}" + + while True: + src_ip = get_if_addr(iface) + # Send to DHCP-provided NTP server + pkt1 = Ether(src=src_mac, dst=dst_mac) / \ + IP(src=src_ip, dst=dhcp_ntp_ip) / \ + UDP(sport=123, dport=123) / \ + NTP(version=3) + sendp(pkt1, iface=iface, verbose=False) + print(f"Sent NTPv4 packet to DHCP NTP server {dhcp_ntp_ip} from {src_ip}") + + # Send to non-DHCP NTP server + pkt2 = Ether(src=src_mac, dst=dst_mac) / \ + IP(src=src_ip, dst=other_ntp_ip) / \ + UDP(sport=123, dport=123) / \ + NTP(version=3) + sendp(pkt2, iface=iface, verbose=False) + print(f"Sent NTPv4 packet to non-DHCP NTP server {other_ntp_ip} from {src_ip}") + + time.sleep(1) + + - name: Create systemd unit for continuous NTP both sender + copy: + dest: "{{ ntp_service_path }}" + mode: '0644' + content: | + [Unit] + Description=Continuous NTP Packet Sender (DHCP and non-DHCP) + After=network.target + + [Service] + Type=simple + ExecStart={{ scapy_venv_path }}/bin/python {{ ntp_script_path }} + Restart=always + + [Install] + WantedBy=multi-user.target + + - name: Reload systemd for NTP both sender + systemd: + daemon_reload: yes + + - name: Enable and start NTP both sender service + systemd: + name: ntp-both-sender + enabled: yes + state: started + + - name: Assign temporary static IP to ens5 for DHCP server startup + command: ip addr add 10.10.10.14/24 dev ens5 + ignore_errors: yes + # Custom Modbus TCP server NON-COMPLIANT - name: Copy Modbus TCP server script (pyModbusTCP) @@ -496,9 +574,6 @@ debug: msg: "DHCP server status: {{ dhcp_status.stdout }}" - - name: Remove temporary static IP from ens5 - command: ip addr del 10.10.10.14/24 dev ens5 - ignore_errors: yes # BLOCK ICMP @@ -611,6 +686,11 @@ sysctl_set: yes reload: yes + + - name: Remove temporary static IP from ens5 + command: ip addr del 10.10.10.14/24 dev ens5 + ignore_errors: yes + handlers: - name: Restart vsftpd service: From 2645bfb57c6280ef70b41f6e360bd0f66c2e3dcf Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Wed, 17 Dec 2025 20:58:09 +0100 Subject: [PATCH 30/32] tls non-compliant --- test_vm/README.MD | 11 +- test_vm/Vagrantfile | 11 ++ test_vm/provision-compliant.yml | 2 +- test_vm/provision-non-compliant-tls.yml | 178 ++++++++++++++++++++++++ 4 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 test_vm/provision-non-compliant-tls.yml diff --git a/test_vm/README.MD b/test_vm/README.MD index 14051b1c3..4261de94a 100644 --- a/test_vm/README.MD +++ b/test_vm/README.MD @@ -99,7 +99,7 @@ DEVICE=COMPLIANT vagrant provision ``` -NON-COMPLIANT DEVICE +NON-COMPLIANT DEVICE (WITHOUT TLS) ```bash ./restore_ethtool.sh @@ -108,6 +108,15 @@ DEVICE=NON-COMPLIANT vagrant up --provider=libvirt DEVICE=NON-COMPLIANT vagrant provision ``` +NON-COMPLIANT DEVICE (TLS ONLY) + +```bash +./restore_ethtool.sh +hash -r +DEVICE=NON-COMPLIANT-TLS vagrant up --provider=libvirt +DEVICE=NON-COMPLIANT-TLS vagrant provision +``` + If you see "Permission denied to /var/run/libvirt/libvirt-sock", check your libvirt group membership and restart your session. diff --git a/test_vm/Vagrantfile b/test_vm/Vagrantfile index 34f93fc00..ac7895a2b 100644 --- a/test_vm/Vagrantfile +++ b/test_vm/Vagrantfile @@ -8,6 +8,12 @@ Vagrant.configure("2") do |config| auto_config: false, mac: "f0:d4:e2:f2:f5:41" end + if ENV['DEVICE'] == 'NON-COMPLIANT-TLS' + config.vm.network "private_network", + libvirt__network_name: "hostonly-noip", + auto_config: false, + mac: "f0:d4:e2:f2:f5:42" + end if ENV['DEVICE'] == 'NON-COMPLIANT' config.vm.network "private_network", libvirt__network_name: "hostonly-noip", @@ -30,4 +36,9 @@ Vagrant.configure("2") do |config| ansible.playbook = "provision-non-compliant.yml" end end + if ENV['DEVICE'] == 'NON-COMPLIANT-TLS' + config.vm.provision "ansible" do |ansible| + ansible.playbook = "provision-non-compliant-tls.yml" + end + end end diff --git a/test_vm/provision-compliant.yml b/test_vm/provision-compliant.yml index 966acd512..a02b028de 100644 --- a/test_vm/provision-compliant.yml +++ b/test_vm/provision-compliant.yml @@ -307,7 +307,7 @@ mode: '0755' content: | #!/bin/bash - # Ждём, пока ens5 не получит IP-адрес + # Wait for IP while true; do IP=$(ip -4 addr show ens5 | awk '/inet / {print $2}' | cut -d/ -f1) if [[ -n "$IP" ]]; then diff --git a/test_vm/provision-non-compliant-tls.yml b/test_vm/provision-non-compliant-tls.yml new file mode 100644 index 000000000..dc9388202 --- /dev/null +++ b/test_vm/provision-non-compliant-tls.yml @@ -0,0 +1,178 @@ +--- +- name: Non-compliant TLS handshakes from 10.10.10.14 via ens5 every 2s (with pause and logging) + hosts: all + become: yes + vars: + fail_tls_script: /usr/local/bin/fail_tls_handshake.sh + bind_ip: "10.10.10.14" + iface: "ens5" + target_host: "www.google.com" + target_port: "443" + ssl_cert: /etc/ssl/certs/nginx-selfsigned.crt + ssl_key: /etc/ssl/private/nginx-selfsigned.key + + tasks: + - name: Ensure openssl, dhclient, and nginx are installed + apt: + name: + - openssl + - isc-dhcp-client + - nginx + state: present + update_cache: yes + + - name: Run dhclient on ens5 (in background) + shell: nohup dhclient {{ iface }} & + async: 0 + poll: 0 + + # NGINX + + - name: Ensure openssl, dhclient, and nginx are installed + apt: + name: + - openssl + - isc-dhcp-client + - nginx + state: present + update_cache: yes + + - name: Generate self-signed SSL certificate for nginx + command: > + openssl req -x509 -nodes -days 365 -newkey rsa:2048 + -keyout {{ ssl_key }} -out {{ ssl_cert }} + -subj "/CN=localhost" + args: + creates: "{{ ssl_cert }}" + + - name: Create nginx HTTPS config + copy: + dest: /etc/nginx/sites-available/ssl_default + content: | + server { + listen 443 ssl; + server_name _; + ssl_certificate {{ ssl_cert }}; + ssl_certificate_key {{ ssl_key }}; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + location / { + return 200 'nginx HTTPS is up\n'; + } + } + server { + listen 80; + server_name _; + location / { + return 200 'nginx HTTP is up\n'; + } + } + + - name: Enable nginx HTTPS config + file: + src: /etc/nginx/sites-available/ssl_default + dest: /etc/nginx/sites-enabled/ssl_default + state: link + force: yes + + - name: Remove default nginx config (if exists) + file: + path: /etc/nginx/sites-enabled/default + state: absent + + - name: Ensure nginx is started and enabled + systemd: + name: nginx + state: restarted + enabled: yes + + + - name: Copy fail_tls_handshake.sh script + copy: + dest: "{{ fail_tls_script }}" + mode: '0755' + content: | + #!/bin/bash + iface="{{ iface }}" + ip_addr="{{ bind_ip }}" + host="{{ target_host }}" + port="{{ target_port }}" + LOG="/tmp/tls_test.log" + echo "=== $(date) ===" >> $LOG + echo "IP on $iface:" >> $LOG + ip -4 addr show dev $iface >> $LOG + echo "Routing table:" >> $LOG + ip route >> $LOG + # Wait for IP to appear on ens5 + for i in {1..60}; do + if ip -4 addr show dev $iface | grep -q "$ip_addr"; then + break + fi + sleep 2 + done + if ! ip -4 addr show dev $iface | grep -q "$ip_addr"; then + echo "No IP $ip_addr on $iface" >> $LOG + exit 1 + fi + # TLS 1.2 + echo "Running TLS 1.2 handshake..." >> $LOG + openssl s_client -connect $host:$port -tls1_2 -cipher "CAMELLIA256-SHA256" -bind $ip_addr -ign_eof < /dev/null >> $LOG 2>&1 + + - name: Create systemd service for failing TLS handshake + copy: + dest: /etc/systemd/system/fail-tls-handshake.service + content: | + [Unit] + Description=Fail TLS handshake via ens5 from {{ bind_ip }} + + [Service] + Type=oneshot + ExecStart={{ fail_tls_script }} + + - name: Create systemd timer for failing TLS handshake + copy: + dest: /etc/systemd/system/fail-tls-handshake.timer + content: | + [Unit] + Description=Run fail TLS handshake every 2 seconds + + [Timer] + OnBootSec=5 + OnUnitActiveSec=2 + + [Install] + WantedBy=timers.target + + - name: Reload systemd + shell: systemctl daemon-reload + + - name: Enable and start fail-tls-handshake.timer + systemd: + name: fail-tls-handshake.timer + enabled: yes + state: started + + # BLOCK ICMP + + - name: Ensure UFW is installed + apt: + name: ufw + state: present + + - name: Set UFW default policy to deny incoming + command: ufw default deny incoming + + - name: Set UFW default policy to allow outgoing + command: ufw default allow outgoing + + - name: Allow SSH + command: ufw allow 22/tcp + + - name: Allow HTTP + command: ufw allow 80/tcp + + - name: Allow SNMP + command: ufw allow 443/tcp + + - name: Reload UFW + command: ufw reload From 0a6972299db64726edeae11509b5362a270759c7 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Thu, 18 Dec 2025 15:14:11 +0100 Subject: [PATCH 31/32] feature not detected config --- test_vm/README.MD | 9 ++++ test_vm/Vagrantfile | 11 ++++ test_vm/provision-not-detected.yml | 82 ++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 test_vm/provision-not-detected.yml diff --git a/test_vm/README.MD b/test_vm/README.MD index 4261de94a..4ab0b130e 100644 --- a/test_vm/README.MD +++ b/test_vm/README.MD @@ -117,6 +117,15 @@ DEVICE=NON-COMPLIANT-TLS vagrant up --provider=libvirt DEVICE=NON-COMPLIANT-TLS vagrant provision ``` +NOT-DETECTED DEVICE + +```bash +./mock_ethtool.sh +hash -r +DEVICE=NOT-DETECTED vagrant up --provider=libvirt +DEVICE=NOT-DETECTED vagrant provision +``` + If you see "Permission denied to /var/run/libvirt/libvirt-sock", check your libvirt group membership and restart your session. diff --git a/test_vm/Vagrantfile b/test_vm/Vagrantfile index ac7895a2b..8d01960f4 100644 --- a/test_vm/Vagrantfile +++ b/test_vm/Vagrantfile @@ -20,6 +20,12 @@ Vagrant.configure("2") do |config| auto_config: false, mac: "54:53:52:51:50:49" end + if ENV['DEVICE'] == 'NOT-DETECTED' + config.vm.network "private_network", + libvirt__network_name: "hostonly-noip", + auto_config: false, + mac: "f0:d4:e2:f2:f5:43" + end config.vm.provider :libvirt do |libvirt| libvirt.memory = 2048 @@ -41,4 +47,9 @@ Vagrant.configure("2") do |config| ansible.playbook = "provision-non-compliant-tls.yml" end end + if ENV['DEVICE'] == 'NOT-DETECTED' + config.vm.provision "ansible" do |ansible| + ansible.playbook = "provision-not-detected.yml" + end + end end diff --git a/test_vm/provision-not-detected.yml b/test_vm/provision-not-detected.yml new file mode 100644 index 000000000..4f9f6472c --- /dev/null +++ b/test_vm/provision-not-detected.yml @@ -0,0 +1,82 @@ +--- +- hosts: all + become: true + tasks: + + # Set up networking on ens5 + - name: Install dependencies + apt: + name: + - isc-dhcp-client + - ufw + state: present + update_cache: yes + + - name: Run dhclient on ens5 (in background) + shell: nohup dhclient ens5 & + async: 0 + poll: 0 + + # ARP packet control + - name: Copy arp_control.sh script + copy: + dest: /usr/local/bin/arp_control.sh + mode: '0755' + content: | + #!/bin/bash + iface="ens5" + ip_addr="" + while true; do + ip_addr=$(ip -4 addr show dev $iface | awk '/inet / {print $2}' | cut -d/ -f1) + if [[ -n "$ip_addr" ]]; then + ip link set dev $iface arp off + echo "$(date): IP $ip_addr detected, ARP disabled on $iface" + else + ip link set dev $iface arp on + echo "$(date): No IP on $iface, ARP enabled" + fi + sleep 2 + done + + - name: Run arp_control.sh in background + shell: nohup /usr/local/bin/arp_control.sh & + async: 0 + poll: 0 + + # DNS request setup + + - name: Create systemd service for curl + ansible.builtin.copy: + dest: /etc/systemd/system/curl-google.service + content: | + [Unit] + Description=Curl google.com + + [Service] + Type=oneshot + ExecStart=/usr/bin/curl -s -L -o /dev/null -w "%{http_code}\n" https://www.google.com + + - name: Create systemd timer for curl + ansible.builtin.copy: + dest: /etc/systemd/system/curl-google.timer + content: | + [Unit] + Description=Run curl google.com every 5 seconds + + [Timer] + OnBootSec=5 + OnUnitActiveSec=5 + + [Install] + WantedBy=timers.target + + - name: Reload systemd daemon + ansible.builtin.systemd: + daemon_reload: yes + + - name: Enable and start curl-google.timer + ansible.builtin.systemd: + name: curl-google.timer + enabled: yes + state: started + From 4a58e8b749a83b28a116849276108e3101117a53 Mon Sep 17 00:00:00 2001 From: Aliaksandr Nikitsin Date: Fri, 19 Dec 2025 10:58:57 +0100 Subject: [PATCH 32/32] refactor compliant config --- test_vm/provision-compliant.yml | 133 ++++---------------------------- 1 file changed, 13 insertions(+), 120 deletions(-) diff --git a/test_vm/provision-compliant.yml b/test_vm/provision-compliant.yml index a02b028de..4a6716576 100644 --- a/test_vm/provision-compliant.yml +++ b/test_vm/provision-compliant.yml @@ -2,10 +2,8 @@ - hosts: all become: true tasks: + # Common setup tasks - - name: Update apt cache - apt: - update_cache: yes - name: Install Python3, pip, venv, and setcap utility apt: @@ -15,14 +13,20 @@ - python3-venv - libcap2-bin - net-tools + - isc-dhcp-client + - git + - gcc + - make + - bison + - flex + - libpcap-dev + - chrony + - openssl + - nginx state: present + update_cache: yes # Set up networking on ens5 - - name: Install DHCP client - apt: - name: isc-dhcp-client - state: present - update_cache: yes - name: Run dhclient on ens5 (in background) shell: nohup dhclient ens5 & @@ -30,17 +34,6 @@ poll: 0 # BACnet stack setup - - name: Install BACnet dependencies - apt: - name: - - git - - gcc - - make - - bison - - flex - - libpcap-dev - state: present - update_cache: yes - name: Clone BACnet stack repository git: @@ -95,25 +88,6 @@ state: restarted daemon_reload: yes - - name: Check if bacserv process is running - shell: pgrep -fl bacserv - register: bacserv_process - changed_when: false - - - name: Show information about bacserv process - debug: - var: bacserv_process.stdout_lines - - - name: Show last lines of bacserv log - shell: tail -n 20 /opt/bacnet-stack/bacserv.log - register: bacserv_log - changed_when: false - ignore_errors: true - - - name: Display bacserv log - debug: - var: bacserv_log.stdout_lines - # Modbus TCP server setup - name: Create venv for modbus @@ -177,12 +151,6 @@ # NTP client setup - - name: Install chrony - apt: - name: chrony - state: present - update_cache: yes - - name: Remove static NTP servers from chrony.conf lineinfile: path: /etc/chrony/chrony.conf @@ -205,14 +173,6 @@ wait_for: timeout: 10 - - name: Show current chrony sources - command: chronyc sources - register: chrony_sources - - - name: Print chrony sources - ansible.builtin.debug: - var: chrony_sources.stdout - # DNS request setup - name: Create systemd service for curl @@ -295,12 +255,6 @@ # Outbound TLS traffic setup - - name: Ensure openssl is installed - ansible.builtin.apt: - name: openssl - state: present - update_cache: yes - - name: Copy TLS emulation script and make it executable ansible.builtin.copy: dest: /usr/local/bin/tls_emulate.sh @@ -359,69 +313,8 @@ # Install and start nginx for HTTP traffic - - name: Install nginx - apt: - name: nginx - state: present - - name: Ensure nginx is running service: name: nginx state: started - enabled: yes - - # Install firewall - - - name: Install UFW firewall - apt: - name: ufw - state: present - update_cache: yes - - - name: Reset UFW to default - command: ufw --force reset - - - name: Set default UFW policy to deny incoming - command: ufw default deny incoming - - - name: Set default UFW policy to allow outgoing - command: ufw default allow outgoing - - - name: Allow all incoming traffic on ens4 - command: ufw allow in on ens4 - - - name: Allow all outgoing traffic on ens4 - command: ufw allow out on ens4 - - - name: Allow 443/tcp incoming on ens5 - command: ufw allow in on ens5 to any port 443 proto tcp - - - name: Allow all outgoing traffic on ens5 - command: ufw allow out on ens5 - - - name: Allow BACnet UDP port 47808 incoming on ens5 - command: ufw allow in on ens5 to any port 47808 proto udp - - - name: Allow BACnet UDP port 47808 outgoing on ens5 - command: ufw allow out on ens5 to any port 47808 proto udp - - - name: Allow BACnet UDP broadcast on ens5 - command: ufw allow in on ens5 to 255.255.255.255 port 47808 proto udp - ignore_errors: yes - - - name: Allow Modbus TCP port 502 incoming on ens5 - command: ufw allow in on ens5 to any port 502 proto tcp - - - name: Allow Modbus TCP port 502 outgoing on ens5 - command: ufw allow out on ens5 to any port 502 proto tcp - - - name: Enable UFW - command: ufw --force enable - - - name: Show UFW status - command: ufw status verbose - register: ufw_status - - - name: Print UFW status - debug: - var: ufw_status.stdout \ No newline at end of file + enabled: yes \ No newline at end of file