This repository contains documentation and source code for the Network and Computer Security (SIRS) project.
The REPORT document provides a detailed overview of the key technical decisions and various components of the implemented project. It offers insights into the rationale behind these choices, the project's architecture, and the impact of these decisions on the overall functionality and performance of the system.
This document presents installation and demonstration instructions.
To see the project in action, it is necessary to setup a virtual environment, with 3 networks and 7 machines.
The following diagram shows the networks and machines:
All the virtual machines are based on Kali Linux 64-bit.
Follow the official SIRS guide to install VirtualBox / VMware Fusion and set up a Kali base VM (KALI):
The KALI VM itself is never used directly, it acts as a template for all other machines.
For each machine, there is an initialization script named init.sh located in the folder corresponding to that machine. The script installs all the necessary packages and makes all required configurations.
To avoid repeating some steps of the setup common to every VM, do these next steps on a single virtual machine that you will then clone for every component in our project.
Start by cloning this repository:
NOTE: You may need to create and add a ssh key to your github profile in order to clone the repository. Follow this guide.
$ git clone https://github.com/tecnico-sec/A57-DeathNodeRun the common-init.sh script that performs all common setup tasks required on all machines.
Note: If any pop up appears, choose
YES.
$ cd A57-DeathNode
$ sudo ./common-init.shAfter running common-init.sh, the script will shut down the KALI VM. There is no need to boot it up again, proceed to the next step.
After preparing an initial VM linked clone it n times, once for each of the following components: DATABASE, API, VIGILANT, GATEWAY, CLIENT1, CLIENT2 and CLIENT3
Custom networks will have to be created for the project to work as intended. In VMWare Fusion, create it by editing /Library/Preferences/VMware\ Fusion/networking, and add the following lines to the file:
answer VNET_2_HOSTONLY_NETMASK 255.255.255.0
answer VNET_2_HOSTONLY_SUBNET 192.168.0.0
answer VNET_3_HOSTONLY_NETMASK 255.255.255.0
answer VNET_3_HOSTONLY_SUBNET 192.168.1.0
answer VNET_4_HOSTONLY_NETMASK 255.255.255.0
answer VNET_4_HOSTONLY_SUBNET 192.168.2.0
- You can add them right after the last
answer VNET_1...line. - Very important: do NOT edit anything else on this file. VMWare reserves
vmnet1andvmnet8for its own use, and you should not change those. - Save and exit;
- Re-open VMWare Fusion;
The VMs will have to have the following adapters (each associated with a specific network):
| VM | ADAPTER TYPE | IP ADDRESS |
|---|---|---|
| DATABASE | CUSTOM (vmnet4) | 192.168.2.10/24 |
| API | CUSTOM (vmnet4) | 192.168.2.250/24 |
| CUSTOM (vmnet3) | 192.168.1.10/24 | |
| VIGILANT | CUSTOM (vmnet4) | 192.168.2.240/24 |
| CUSTOM (vmnet3) | 192.168.1.20/24 | |
| GATEWAY | CUSTOM (vmnet3) | 192.168.1.250/24 |
| CUSTOM (vmnet2) | 192.168.0.250/24 | |
| SHARED WITH THE HOST | - | |
| CLIENT 1 | CUSTOM (vmnet2) | 192.168.0.2/24 |
| CLIENT 2 | CUSTOM (vmnet2) | 192.168.0.3/24 |
| CLIENT 3 | CUSTOM (vmnet2) | 192.168.0.4/24 |
After adding the adapters to each VM, you can boot all of them.
Proceed to fill the GATEWAY's, API's and VIGILANT's init.sh interface MAC variables in the beginning of the file, with the corresponding interface MAC addresses of each connected interface/adapter.
Note: (The MAC addresses can be found in each VM settings, on the adapter section.) The 'SHARED WITH THE HOST' NIC/adapter has the tendency to change its MAC address after the VM boots, be careful.``
The init.sh will set up the VMs with the required interface and firewall configurations, additionally, it also installs some needed packages specific to the current VM.
- On the GATEWAY VM, inside
A57-DeathNode/gateway/, runsudo ./init.sh. - On the API VM, inside
A57-DeathNode/api/, runsudo ./init.sh. - On the VIGILANT VM, inside
A57-DeathNode/vigilant/, runsudo ./init.sh. - On the DATABASE VM, inside
A57-DeathNode/database/, runsudo ./init.sh. - On the CLIENT VMs, inside
A57-DeathNode/client/, runsudo ./init.shand choose a different IP for each, additionally, create your own 'local.env' file based on the template.
Note: Follow this exact order, the gateway and other VMs close to the gateway forward internet traffic from inside the network. If not, e.g. Postgres will not be installed on the Database VM.
- On the API VM, run
./start.sh. - On the Vigilant VM, run
./start.sh. - On each Client VM run
./start.sh
This machine runs a PostgreSQL 17 and will store the reports that were submitted to the network, as well as which clients reported which reports as invalid, it also tracks which clients are currently banned from using the network.
Verify the following:
$ systemctl status postgresql
$ tail -f /var/log/postgresql/postgresql-17-main.log- The first command should report that
postgresql.serviceisactiveandenabled. - The second command should report:
LOG: listening on IPv4 address "0.0.0.0", port 5432
LOG: listening on IPv6 address "::", port 5432
LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
LOG: database system was shut down at 2025-12-20 15:12:01 WET
LOG: database system is ready to accept connections
Additionally, run:
$ sudo -u postgres psql
# Type the password, 'postgresql'
postgres=# \c deathnode;
postgres=# \dt;We expect the follwing output:
You are now connected to database "deathnode" as user "postgres".
...
List of relations
Schema | Name | Type | Owner
--------+---------------------------+-------+----------
public | ban | table | postgres
public | peer_report_invalidations | table | postgres
public | report | table | postgres
(3 rows)
This machine runs a Quarkus 3.30.3 REST API, it allows clients to submit, invalidate and pull new reports.
After running ./start.sh verify that no JDBC or any other type of errors pop up in quarkus logs. The following should appear:
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
...
(...) Listening on: https://0.0.0.0:443
When it comes to network interfaces, you should have two configured, one in which the assigned IP is 192.168.1.10 (to the Gateway) and another in which the assigned IP is 192.168.2.250 (to the database).
This machine runs a Quarkus 3.30.3 REST API, it allows client requests to be authenticated by using the TLS certificate used to open the TLS TCP session with the gateway.
After running ./start.sh verify that no JDBC or any other type of errors pop up in quarkus logs. The following should appear:
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
...
(...) Listening on: https://0.0.0.0:443
When it comes to network interfaces, you should have two configured, one in which the assigned IP is 192.168.1.20 (to the Gateway) and another in which the assigned IP is 192.168.2.240 (to the database).
This machine runs NGINX, it redirects client requests to be authenticated first (using the Vigilant API), if needed, and also redirects requests to the Server API.
Verify the following:
$ systemctl status nginx
$ tail -f /var/log/nginx/error.log
- The first command should report that
nginx.serviceisactiveandenabled. - The second command should not print any errors.
When it comes to network interfaces, you should have three configured, one in which the assigned IP is 192.168.1.250 (connected to the API and Vigilant), another in which the assigned IP is 192.168.0.250 (to the clients) and another configured with DHCP, used to install the needed software.
This machine runs a Java CLI which is able to communicate with the Server API to push newly created reports or pull new reports from it.
Verify the follwing after running ./start.sh:
DocumentTool> syncWe expect no errors in the output, it should return the following:
Pulling new reports from server...
Sync complete.
When it comes to network interfaces, you should have a single one configured on each VM, in which the assigned IP was the one chosen in the init.sh script (to the Gateway).
Now that all the networks and machines are up and running, we can start creating reports and syncing with the Server.
After running ./start.sh on a client, to see the available commands run help, you should see:
We can start by creating a report, run create-report and fill the fields:
We can now list the reports we created but are not yet synced with the Server, run list-reports:
Next we will sync with the Server, run sync. A playload is sent containing the reports we created:
Example:
{
"reports": [
{
"id": "00bde5e2-736d-48c2-9592-4c0686990d1f",
"version": 1,
"timestamp": "2025-12-20T23:15:02.587246887Z",
"dek": "np0/NqKxaA72VLEAFMqWXnVaCpBZtgXw52S39x+pqvQ=",
"historyHash": "l2BMXuiB2VGVM+eRRFaiiYGVxx4SindPt59qMAr6ooYHjw7rKoGPKhNHpF1gK5SfjEh492lAOnuzNGgjZEpDdw==",
"prevHash": "LVEEdSQPuNleihLT4l5yDxJ64Fh1DYem9ryq+pJJejPgveWy61X3dv582eHg7wlACcgaz52LXkhXI8W1Ax+ZSA==",
"inner": "ZSQV9fPi+rmK0aGqx9TsrCqKmTkAdhEsYzMjZDqSAoFZDq7zRNX8Zyl50tMev/LafxNcnao9AM2AA9+pf2whxfJ2PiuzNlpGCLyOl8Lr9L7+6X84SpBiIKfP23wNkMHiwxQhBK0hn/MpH903Aq9LKtMShq9Ctpg6F1+G5D9S4GAkKchAJz98DeDTHfu3eUTv8UauPkAP1cs0AH3o7I0wV0xlAbJwY5NguCmApRwOJGyhUJ1un7jKKPtZ15mVpyxK18P74LXI1p3Ob6RS5ecKZPm7iRGiHuqvU8z1NafArqfyUGo6+PLOjCfJjvA1qp4OGXiPf0MzoafuaQYv61nD4OomSbYlsWfiBoesf4BTrhb3T6PF1Rr2sbot7FTEloun88QfcSRYUnde0w2JeBnKpg==",
"signature": "lhwhP8oaDw7HvD4rnHWTofa5d32SwulQCMI5YNDMDs08Iz6qznvvD9kzODCZ4Sflv/C+lT5XPsB0QqjT+pJNkV/a12ZaCcP/uT68pg0SZR1qIktcCgMrjpEfl9/Qi1Y9pzbqkiH9TrDzDKylY2Q7zKf1jTvKas5gzPnmQkp9yVJWgP1AGSqwH9uXBBPX0j5tn82TVmV4+ZvThnNpG1ZQ+H886Dui/ufYkzWpKFMLNFzHb4ajZYZMjR8svuDAyKXo00zJewxNytDSXFPoBSSZZ6V+hcpNzoYPeHI0ojL60uWOB8y85d4joIdbrMMQ7MF2JgSJ9AtRuxaSthgg9nQcFFalBkmsRsnFuYE3GFBGntJkTQVz3rTmwObKFXXsXpjovXRBEmtP2xDZyZoM67EbxvuGkqDicm4ux7m0zQhH7whbnLYx/3Pe8QgW+QXSm1ObpRUANbZ/2NBjHu0vTGv9Nrc9AcApu6ep07KsttSoAah8yoEAPSssZ9G2cSjD3szJ5LFbImnnN9UfSKmOcCMCERviSx+SDuWaeE+Sr4KPBMpJILH7kemHF10Put02T/4rC+GSCrKd/ulealai0N2I+gwrRMU/2oNto6KX4h3+rGr0yOsSbyoenePdZtMorLFAtv7E3f4Tl4kLWPuzjuOL+31xek0c32vMaAqY6V5lCjI="
}
]
}We can also verify that our report has been moved to the already synced reports:
On another client, running sync will make it pull all the not synced reports from the Server:
Example payload:
[
{
"id": "1cfa3cbc-c68c-4539-8e88-0b61a5f9d850",
"version": 1,
"timestamp": "2025-12-20T22:15:20.407189Z",
"dek": "0XCQnqlw8PIdKN2pQbwCy6/QpsrH0jB1DvsmpzuLRYs=",
"historyHash": "PPQSEohVmKLur/wQV39pZ0pFj/tvAPUD2tEPIz/sKZXMFtfxZavMflfGpYk+7vGXAGvluPRjgvbOwP8ZLW/Lsg==",
"prevHash": "bgrOEsoknsdNguILeRQ1shFr1R1BJHnVBoNezuqfflxhxDpEJoGSDeRMfF0zvhcfynSr1GNWSyfIcfRPi+nhfQ==",
"inner": "A1NFF0l3YcdyO6l2WckAi8SKg8d/7qfpj1xUETREAHWpQBtNskKWTlE2eNqcVgEfBkGTIbp3/hZfnVDgMYDWDVeN4aQpn3QjI+tukWaXXkDbneU+VyIq3yxhTzeEjR+HRa5b7FODULlzjeYHitimuEgWo166xnDdYLXMznEtYDtP/yb1L4pxJJrw2fEtC2LvcYDjyFbIiwSIYI9fEDG88/ETjPuU6pEagkM9iyMFhCIVrzw1f4KqraOhj+NxR+kIt9AR7F7mA4rEFX3bn9JcXjLYMCzdpxZW3YXx214+0c7Z8Sskh8se+xKMGo1M4ZGDGgAnXyVEpjhKQrUovpZm1qhyxhs0Zc8AxbXee199XiqyVioc157f4jw+XPPk2eX04sJaLdSu02IVhu78xRhGEg==",
"signature": "YbSjylBmnFUK3l2O/9gNXRsTmtq3+1Enq3LpGM/OH87SXeHLbSS0xfBRcbGkicf3vtv6KheVXAlOwe0T2AGTnHYizJdocMXT+9TVa0zhXV/nuNXLJhaUKLE1m2mfaERir0F1A8KbVNXjZNS3MadI6XVlYMryo2x2WMOT3E1DWIH4QcXVnHysnVCCtt2QyuilduTn7E7WylrsGtMYSo1GycG98AlTfaOVLnuMl2IpiSfe31xdLA4AAkctg++MsrQa42+MZ/kZiN4Hd1Tw8OBEqhR/qpSU5+DjH2huDAW6exkv12QbLlOr3HswvAAt6I8f1ZN8qlXeBMdtNKxpyudM6dxgYrdTY0uwm3iETKSLfIPrPGTYUbvbWez3xB/yguoja4DDxmMUEjhYKunXoi4tc9IUbTdjDYwAWX0J1YqVF1Q9zFT7X6anVbpl9sIi97RxGw94kQPykE3jhNyjMcIn6fFCx7J4LGIn1NVg0/4adiy3IbtMtACPxrSHGAY0b3Fbz11InIEHTo7DQ4wg1X1VWL2x0/Fk66C5L4JF3oYChDbpfDZL9+2FJP0y+WVwNU2970g7ube8CNgflnjTFgz4oQ32AjeRwKOV9u17gUJXlHJam+N8WXUqG6dg6US5IkY9f0jI23ISU4tgVD2uWLC3JapsthTsdGM8OZUlbk/IEa4="
}
]As can also be seen in the image above, through wireshark, the request and responses are ciphered since we use TLS, an attacker would not be able to read whatever is sent or received by the client over the network regardless of where he stands in our network.
All communication is protected between all components, CLIENTS <-> NGINX, NGINX <-> API|VIGILANT and API|VIGILANT <-> DB.
Apart from these commands, we can also update an existing report:
The report will be added to our reports that are not yet synced with the Server. We can then sync:
And pull the new version from another client:
Moreover, we can add a new client whose reports will all be invalid to demonstrate how bans work.
To do so, the simplest way is to remove this client's certificate from the certificates the clients have of one another.
These are present in the clientX/certs/ directory. Do it for the two clients that are not the "bad" clients.
After that we can create some reports for this new client and sync them:
After they are synced, other clients can pull them. They will try to validate the reports but fail.
Posteriorly, the validating clients report the invalid reports back to the Server (using the /reports endpoints).
Example Payload:
[
{"id": "7c69094c-2859-447e-85f9-70d915873b73", "version": 4},
{"id": "ee1d1a26-ca93-44a1-9c51-d7538c138448", "version": 23}
]The report table then tracks how many times the report has been invalidated.
(We make sure clients can't report the same report twice, by tracking which client reported which reports in an extra table called peer_report_invalidations.
To know which client called the /reports endpoint, we added a custom header, X-SSL-Client-Cert , to the request by configuring NGINX. It contains the certificate used by the client during the TLS handshake.)
If a majority of clients have invalidated a report, it will be tagged as invalid.
When it is tagged as invalid, there is a new table, ban (in which we store how many invalid reports each client has sent) that gets updated.
We increment the report's client invalid_report_count.
When this number is between (and including) 2 to 5, the client is banned once temporarily, for 1 minute.
During this time he can only pull reports from the server with sync, he can't push or report reports from other clients as invalid.
If more than 5 reports have been tagged as invalid, the client is banned permanently.
This authorization mechanism was achieved by configuring NGINX to make an additional request to our Vigilant API.
This API accesses the database and queries the ban table, in which we track the number of invalid reports per client (also using the TLS Handshake certificate to identify the client).
This concludes the demonstration.
- Java 17
- Maven 3.9.5
- PostgreSQL 17
- Quarkus 3.30.3
- JUnit 5.11.0
- JLine 3.30.0
- Kali Linux 2025.3
- VMware Fusion
We use GIT for versioning.
This project is licensed under the MIT License - see the LICENSE.txt for details.















