Protecting Devices with CDP Studio and UFW
Protecting Devices with CDP Studio and UFW
This guide explains how CDP Studio helps you set up firewall rules for your control system applications using Uncomplicated Firewall (UFW).
Devices should follow the IEC 62443-3-3 standard to be more resilient against cyberattacks. Firewalls play a crucial role in achieving this by controlling incoming and outgoing traffic.
CDP Studio simplifies firewall configuration for your applications. It generates UFW scripts based on each application's specific configuration. These scripts act as a starting point and can be run on Linux systems to set up the firewall. For Windows systems, you can use the generated scripts as a reference to configure the Windows Firewall. The generated scripts can only be run on Linux devices that have UFW installed. To verify that your device has UFW installed, you can run the following command on the device:
sudo ufw status
If the command outputs 'Status: active' or 'Status: inactive', then UFW is installed, and you should be able to apply the generated UFW scripts on the device.
Note:
- UFW Firewall generation is only available in CDP Studio systems with CDP version 5.0 or later
- To generate UFW Firewall scripts, Python 3 must be installed and available in the path on the development PC that runs CDP Studio
- The lxml python library and dependecies for the CDP Configurator must be installed; they can typically be installed on Windows by opening 'cmd'exe' and typing:
python3 -m pip install lxml wexpect ipaddress
For Linux, open a terminal and type:
python3 -m pip install lxml pexpect ipaddress
Generating UFW Firewall Rules
- In CDP Studio Configure mode, first make sure the system to generate rules for is selected, either by selecting the system itself, or by selecting an application in it. Note that if a library is selected, then the 'Generate Firewall Rules' action below will fail.
- Then, in the CDP Studio Menu, click Tools External Generate Firewall Rules.
- This action scans all applications in your system and uses knowledge from each library to create a base set of UFW rules for each application instance.
- The generated UFW scripts are saved in the <CDPStudioWorkspace>/<System>/<Application> folder with the filename apply_ufw_rules.sh.
- You can use the Run Custom Script functionality within CDP Studio to install the firewall rules on your target device. This feature allows you to upload the script containing the firewall rules and execute it directly on the device. The script itself will then update the firewall (UFW) with the new rules, which take effect immediately.
Note:
- Before applying the generated scripts, it's critical to have them reviewed and potentially modified by a qualified professional.
- UFW applies rules sequentially, so ensure the order is correct for the desired behavior.
How CDP Studio UFW Script Generators Work
The Generate Firewall Rules does the following:
- Scans the default (native) toolkit.
- Scans all user-created libraries associated with that toolkit.
- Searches for files named 'script/ufw.py' within these locations.
- for each application, it
- Maps in all .ui files for that application
- Executes each discovered 'ufw.py' for the application
The executed scripts have access to helper functions provided by the parent UFW generator script (see UFW Script Helper Functions).
Understanding 'ufw.py' Scripts
Any library in CDP Studio, including user-made libraries, can contain a 'scripts' folder. If this folder includes a 'ufw.py' file, it runs whenever the Generate Firewall Rules action is triggered. This script is typically found only in libraries that involve network I/O and require firewall configuration based on that I/O setup.
Adding Custom UFW Rules
If you have custom components or widgets that require firewall rules, you can create your own 'ufw.py' scripts and add them to your CDP Studio libraries. Refer to the following sections for more details on this process.
How to Create a Custom CDP Studio UFW Script
Let's say you have a library named CommunicationLib with a component called TCPServer. This component communicates with various external targets on different ports. The port range is stored as LowerPortRange and UpperPortRange properties in the TCPServer configuration. The communication happens on the ETH0 IP address as defined in the Application configuration, and one of the ports in the port range may be used to serve data.
Here's how to create UFW rules for your TCPComm component instances:
- Inside your library's top-level folder, create a folder named scripts and add a file named 'ufw.py' inside it
- See UFW Script Helper Functions for a description of available functions to use in the script
- The provided code snippet below shows a basic example of a 'ufw.py' script for this component
Example TCPComm UFW Generator
local_ip = get_app_ip("ETH0") def generate_rule_for_tcpcomm_port_range(component_name, model_name): if debug_printouts is True: print ("Generating UFW rules for TCPComm: '"+component_name+"'") app.cd(component_name) lower_port_range = get_child_value(component_name, "LowerPortRange") upper_port_range = get_child_value(component_name, "UpperPortRange") append_ufw_profile(f"allow in from any to {local_ip} port {lower_port_range}:{upper_port_range} proto tcp", f"{component_name} (Model {model_name})") # Make sure above function is called for all instances found: for_each_object_of_model("CommunicationLib.TCPServer", generate_rule_for_tcpcomm_port_range)
Note: The library that contains the 'ufw.py' script has to be built at least once for CDP Studio to be able to use the 'ufw.py' script. Each time the script is changed, the library that contains it must be re-built.
A typical string to append to the UFW profile is shown in the example above, and it generally has the format:
allow <in/out> from <ip/any> to {ip/any} port <port number/port_range_start:port_range_end> proto <udp/tcp>
Note: By default, UFW allows outgoing traffic, and blocks incoming traffic. For this reason, only rules for incoming requests are generated. If needed, the complexity of handling outgoing rules is left solely to the user.
A link to the UFW documentation and other helpful references are provided in Further Resources below
Generating UFW Rules From GUI Widgets
The 'ufw.py' script can also extract information from the Graphical User Interfaces in the system. The functions for_each_ui_class and get_ui_widget_property simplify the work of retrieving information from the .ui-files in the system. See the UFW Script Helper Functions below for more information about these functions.
For instance, assume a CommWidget contains a Url property that we want to generate a UFW rule for. To generate a rule for that, we can add the following 'ufw.py' script (or extend the previous one):
Example CommWidget UFW Generator
def handle_comm_widget_url(name, widget, ui_filename): if debug_printouts is True: print(f"Processing {ui_filename}, widget name='{name}'") comm_url = get_ui_widget_property(widget, "Url") prefix, url, port = split_url(comm_url) if port is None and prefix.lower() == "https": port = 443 if port is not None: portstr = f"port {port} " else: portstr = " " append_ufw_profile(f"allow in to {url} {portstr}proto tcp", f"ui-widget {name}, {comm_url}") for_each_ui_class("CommWidget", handle_comm_widget_url)
We use the functions for_each_ui_class and get_ui_widget_property to extract information from the UI. To extract other information from the widget Element, see the Python lxml documentation.
UFW Script Helper Functions
The following table explains the various functions that can be called from your 'ufw.py' script.
Name | Parameters | Description |
---|---|---|
append_ufw_profile | ufw_command, ufw_comment | Appends a line containing 'ufw <ufw_command> comment <ufw_comment>' to the apply_ufw_rules.sh. |
get_app_name | Returns the application executable name. | |
get_app_ip | network_interface_name | Returns the ip address that matches network_interface_name from the Application configuration. Example: get_app_ip("ETH0") |
get_broadcast_ip | network_interface_name | Returns the broadcast ip address that matches network_interface_name from the Application configuration. Example: get_broadcast_ip("ETH0") |
split_url | url | Split url string into a tuple (prefx, url, port). Example http://www.mysite.com:8080 is split to (http, mysite.com, 8080) |
for_each_object_of_model | model, function | Calls function(component_name, model_name) for all children at the current 'app' position (as selected by app.cd() ) that have basemodel model |
get_children_by_model | parent, model_name | Returns a list of children of parent that derives from the (base)model_name. The list contains tuples of (name , model). |
get_child_value | parent, property_name | Returns the value of parent.property_name |
get_value | name | Returns the value of the current element (as selected by app.cd() ) |
get_ui_widget_property | widget_element, property_name | Returns the value of property_name from the widget, or None if not found. |
for_each_ui_class | class_name, function | Calls function(name, widget, ui_filename) for all widgets that have class <class_name>. 'widget' is an lxml Element, see the python lxml documentation for more information on retrieving data from lxml Elements. |
app.<api> | Use the CDPStudioConfigurator Python API such as get_value(), cd(), etc. as specified in the CDPConfigurator. |
Further Resources
- For detailed information on creating UFW rules, refer to the UFW documentation.
- To understand the CDPConfigurator Python API better, consult the CDPConfigurator Python API Help documentation.
- For information on the lxml python library, see https://lxml.de/
- For a defence-in-depth security approach, see IEC 62443-3-3 Cybersecurity Requirements.
- To run scripts on a device, see Run Script.
Get started with CDP Studio today
Let us help you take your great ideas and turn them into the products your customer will love.