Introduction
This is the part 2 of the MTD serie and it presents what happens behind the scenes in the PoC introduced in the part 1.
How to use nftables from python?
The title of this section is the same as this post’s title. The post describes the key point in terms of the PoC implementation – the ability to apply nftables rules using pure python. As you can read the python3-nftables package is all you need to implement your own solution.
PoC implementation
The core configuration of the PoC is based on the files in this directory. Actually, only three out of four are important, we don’t care about the configuration_schema.json file – this one only validates the configuration.json file. So, let’s describe the remaining files.
configuration.json
The first file is the configuration.json. This is the custom file designed by myself and it has all the necessary configuration the PoC reads during the startup. There are three JSON objects:
- redis_connection_configuration,
- redis_subscriber_configuration,
- nftables_service_configuration.
redis_connection_configuration and redis_subscriber_configuration are related only to the Redis communication whereas nftables_service_configuration consists of the configuration closely related to the nftables rules the PoC applies in the runtime – so let’s focus on this one. There are following objects:
- nft_startup_commands_path,
- nft_address_rules_path,
- max_port_number,
- tcp_ports,
- watched_addresses.
nft_startup_commands_path and nft_address_rules_path point to the JSON files that consist of nftables commands – we will discuss these two files later on but for now it is worth to mention that the files’ content is based on the JSON schema that is well documented here (it is also mentioned in the aforementioned post).
max_port_number is the maximum port number that the program can choose when choosing redirection ports.
tcp_ports is a list of port numbers that are in use on the host machine.
watched_addresses is a list of objects in which each of them describes watched address
i.e. an IP address (ip field) and a list of ports (tcp_ignore field) that we want to exclude from the whole redirection process.
nft_startup_commands.json
In this section (and in the next one) I won’t describe in details the file content because it’s not the point. The nft_startup_commands.json file was also created by myself but its content is based on the previously mentioned schema. The file contains six nftables commands that do the following:
- removes all tables, chains and generated rules (first command – flush),
- creates the nat table of the ip family and creates the prerouting chain within the table (second and third commands – add table and add chain).
- creates the raw table of the ip family and creates the prerouting and output chains within the table (fourth, fifth and sixth commands – add table, add chain and add chain).
The commands don’t rely on the configuration.json file, i.e. they are always the same. The main purpose of them is to remove all nftables-related rules and structures (tables and chains) created so far and prepare empty chains for the newly generated rules (described in the next section).
nft_address_rules.json
The nft_address_rules.json file contains nftables commands that create redirection rules. The nft_address_rules.json file was also created by myself and its content is also based on the previously mentioned schema. The file content is not 100% valid because it contains
4 custom tokens that are replaced in the runtime:
- {{SOURCE_ADDRESS}},
- {{DESTINATION_PORT}},
- {{REDIRECTED_PORT}},
- {{PROTOCOL}}.
For each in the watched_addresses list from the configuration.json file and for each in the tcp_ports list excluding tcp_ignore list the program generates rules according to the nft_address_rules.json file. In other words, the nft_address_rules.json file is a template for
a batch of rules for a particular pair of a watched IP address and a port (not ignored one).
Example
The foregoing description might be a little involved but let me try to simplify it by an example. As the code on the GitHub repository might change in the future let’s assume that the following example uses the configuration.json, nft_startup_commands.json and nft_address_rules.json files from this commit.
Let’s focus for now on the README file section. The nftables container is the one that has the PoC up and running. Now let’s move to this README section and to the following part “Once you send the message, you should see that the nftables
container received the message (first terminal). Receiving the message means that the nftables rules has been applied.” What does it mean “the nftables rules has been applied”? Let me break it down by describing the whole rules application process in the following steps:
- The PoC is listening on the test_channel_1 on Redis, IP address 10.0.0.5, port 6379 (configuraion.json file).
- The message is sent to the test_channel_1.
- The PoC is notified about the message.
- The PoC starts to process the message.
- The PoC starts to generate nftables commands:
- The PoC starts to prepare the nftables commands (there is no commands at this time) by appending the ones from nft_startup_commands.json file.
- The PoC continues to prepare the nftables commands by adding the nftables rules commands:
- For each in watched_addresses and for each in tcp_ports excluding tcp_ignore a batch of rules based on the nft_address_rules.json is created. In this example that would be 3 batches of rules (each unique pair of an IP and a port has its own batch of rules):
- first batch of rules – IP 10.0.0.3, port 3 – the {{SOURCE_ADDRESS}} token is replaced by “10.0.0.3”, {{DESTINATION_PORT}} by “3”, {{REDIRECTED_PORT}} by
a random number from the range [1..20] (20 is the max_port_number) excluding [1, 2, 3, 4, 5] (tcp_ports) and {{PROTOCOL}} by “tcp”. - second batch of rules – IP 10.0.0.3, port 4 – the {{SOURCE_ADDRESS}} token is replaced by “10.0.0.3”, {{DESTINATION_PORT}} by “4”, {{REDIRECTED_PORT}} by
a random number from the range [1..20] (20 is the max_port_number) excluding [1, 2, 3, 4, 5] (tcp_ports) and excluding [NumberChosenForPort3] and {{PROTOCOL}} by “tcp”. - third batch of rules – IP 10.0.0.3, port 5 – the {{SOURCE_ADDRESS}} token is replaced by “10.0.0.3”, {{DESTINATION_PORT}} by 3, {{REDIRECTED_PORT}} by a random number from the range [1..20] (20 is the max_port_number) excluding [1, 2, 3, 4, 5] (tcp_ports) and excluding [NumberChosenForPort3, NumberChosenForPort4] and {{PROTOCOL}} by “tcp”.
- first batch of rules – IP 10.0.0.3, port 3 – the {{SOURCE_ADDRESS}} token is replaced by “10.0.0.3”, {{DESTINATION_PORT}} by “3”, {{REDIRECTED_PORT}} by
- For each in watched_addresses and for each in tcp_ports excluding tcp_ignore a batch of rules based on the nft_address_rules.json is created. In this example that would be 3 batches of rules (each unique pair of an IP and a port has its own batch of rules):
- The PoC finishes the nftables commands preparation, appends them to the commands added from the nft_startup_commands.json and is ready to validate and apply the commands.
- The PoC validates the prepared commands.
- The PoC applies the nftables commands and validates the output.
- If the rules application succeed, then the previously applied rules are removed and the network traffic is redirected according to the newly applied batches of rules.
- The PoC starts to generate nftables commands:
- The PoC finishes the message processing.
Summary
I hope the explanation helps at least a little in understanding the presented PoC. There is one more thing that might help you to understand the concept. If you run the PoC according to the README you can list all applied nftables commands by the PoC on the host machine (i.e. nftables container). All you have to do is to run the following command inside the nftables container: nft list ruleset
.
Have a nice day, bye!
Be First to Comment