Skip to content

Exposing a Web Management Interface Through Simple Firewall

Overview

In this tutorial, you'll create a simple web management interface and configure your firewall to expose that service only through the ethernet interface while keeping the default firewall policy intact.

What you'll learn:

  • Create a package providing a minimal web page
  • Host it using a lightweight web server in your Yocto image
  • Configure the firewall using Welma sfw to allow HTTP traffic on a specific network interface
  • Validate connectivity from a client machine

What you'll need:

  • A workstation with a Linux operating system
  • A Raspberry Pi 4B device
  • The device is reachable from you workstation through serial port and network
  • WiFi Hotspot

Step 1: initial setup

Create your working directories on your workstation:

ROOT=...               # your root working directory
mkdir $ROOT/build      # your Yocto build directory
mkdir $ROOT/sources    # your Yocto layers

Download Welma into $ROOT/sources:

cd $ROOT/sources

MACHINE=raspberrypi4-64-welma
WELMA_REF=scarthgap-next
WELMA_GIT_NAMESPACE=witekio/rnd/theembeddedkit/welma

git clone git@gitlab.com:$WELMA_GIT_NAMESPACE/welma-manifest.git
welma-manifest/setup-download welma-manifest/$WELMA_REF/manifest-$MACHINE.txt

Set up your Yocto build directory in $ROOT/build.

Terminal-1: Yocto build environment
cd $ROOT
source sources/meta-welma/setup/setup-build-env \
       sources/meta-welma-raspberrypi/conf/templates/$MACHINE

If you did not fuse the PBKH on your device, do not try to do it now, but be sure to add WELMA_SECURE_BOOT="0" in your conf/local.conf.

Build the development image:

Terminal-1: Yocto build environment
bitbake welma-image-minimal-dev
Install the image on you device:

  • Insert the SD card in your workstation.

  • Copy the Welma image to the SD card (be careful about the destination /dev/mmcblk0, your milage may vary):

Terminal-1: Yocto build environment
gunzip --stdout \
    tmp/deploy/images/$MACHINE/welma-image-minimal-dev-$MACHINE.wic.gz |
    sudo dd of="/dev/mmcblk0" bs=10M
sync

Remove the SD card from your workstation, and insert it into your device. Do the cabling so as to get console access and network access to your device. Then start your device.

You should now see the device booting on the console, something like this:

Console: /dev/ttyUSB0
  0.51 RPi: BOOTSYS release VERSION:69471177 DATE: 2025/05/08 TIME: 16:21:35
...
  2.53 RPi: BOOTLOADER release VERSION:69471177 DATE: 2025/05/08 TIME: 16:21:35
...
U-Boot 2024.01 (Jan 08 2024 - 15:37:48 +0000)
...
Starting kernel ...
...
[    0.000000] Linux version 6.6.63-v8 (oe-user@oe-host) (aarch64-poky-linux-gcc (GCC) 13.4.0, GNU ld (GNU Binutils) 2.42.0.20240723) #1 SMP PREEMPT Fri Dec  6 10:10:05 UTC 2024
...
Welma by Witekio 1.6.1 raspberrypi4-64-welma ttyS0

raspberrypi4-64-welma login:

Step 2: create a Yocto layer

Create a dedicated directory $ROOT/sources/meta-management, in which you create the following:

meta-management/conf/layer.conf
# Add layer to BBPATH
# We have a conf and classes directory, add to BBPATH
BBPATH .= ":${LAYERDIR}"

# We have recipes-* directories, add to BBFILES
BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \
            ${LAYERDIR}/recipes-*/*/*.bbappend"

BBFILE_COLLECTIONS += "management-layer"
BBFILE_PATTERN_management-layer = "^${LAYERDIR}/"
BBFILE_PRIORITY_management-layer = "1000"

LAYERSERIES_COMPAT_management-layer = "scarthgap"

IMAGE_INSTALL:append = " management-if wlan-config"

This configuration adds the management-if application created in step 5 and a minimal WiFi configuration created in step 4 to the compiled image.

Step 3: enable webserver in BusyBox

We will use BusyBox httpd as a lightweight embedded web server. To enable httpd configuration, create the following files:

recipes-core
└── busybox
    ├── busybox_%.bbappend
    └── files
        └── httpd.cfg

The httpd.cfg fragment should contain the following httpd configuration:

meta-management/recipes-core/busybox/files/httpd.cfg
CONFIG_HTTPD=y
CONFIG_FEATURE_HTTPD_PORT_DEFAULT=80

This configuration is appended to BusyBox recipe through the busybox_%.bbappend file:

meta-management/recipes-core/busybox/busybox_%.bbappend
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"

SRC_URI += "file://httpd.cfg"

Step 4: configure WLAN network interface

To verify that the management interface is accessible only through the eth0 Ethernet interface, you need to configure the Wi-Fi interface and ensure that the web server does not respond when accessed via its Wi-Fi IP address. To proceed, create the following files:

recipes-network
└── wlan-config
    ├── files
    │   ├── 01-wlan0.network
    │   └── wpa_supplicant-wlan0.conf
    └── wlan-config.bb
meta-management/recipes-network/wlan-config/wlan-config.bb
SUMMARY = "Minimal wlan interface configuration"
LICENSE = "CLOSED"

SRC_URI += " \
    file://01-wlan0.network \
    file://wpa_supplicant-wlan0.conf \
    "

RDEPENDS:${PN} += "wpa-supplicant"

do_install() {
    install -d ${D}${systemd_unitdir}/network
    install -d ${D}${sysconfdir}/wpa_supplicant

    install -m 0644 ${WORKDIR}/01-wlan0.network ${D}${systemd_unitdir}/network
    install -m 0600 ${WORKDIR}/wpa_supplicant-wlan0.conf ${D}${sysconfdir}/wpa_supplicant
}

FILES:${PN} += "\
    ${systemd_unitdir}/network/01-wlan0.network \
    ${sysconfdir}/wpa_supplicant/wpa_supplicant-wlan0.conf \
"

This recipe will install a minimal configuration for the Wi-Fi interface. To do this, the Wi-Fi network credentials will be provided by the wpa_supplicant-wlan0.conf file, its content could be generated using the following command:

$  wpa_passphrase '<your-ssid>' '<your-password>' > wpa_supplicant-wlan0.conf

Here is an example of wpa_supplicant-wlan0.conf with an wifi-ssid as SSID and password as a password:

meta-management/recipes-network/wlan-config/files/wpa_supplicant-wlan0.conf
network={
        ssid="wifi-ssid"
        #psk="password"
        psk=af83b4c658cbb0c9ee5f9f660fd71c68e613fd00630992f9efb416a3c12e38f7
}

In order for the wlan0 interface to be configured correctly by systemd-networkd, the configuration file 01-wlan0.network is installed with the following content:

meta-management/recipes-network/wlan-config/files/01-wlan0.network
[Match]
Type=wlan
Name=*0
Name=!dummy*

[Network]
DHCP=yes

Step 5: add web management application

We're now creating a minimal application, called management-if, that provides a web management interface with web server configuration.

In meta-management, create the following files and directories:

recipes-management
└── management-if
    ├── management-if.bb
    └── files
        └── httpd.conf
        └── management-if.html
        └── management-if.service

We start by writing a simple HTML page that will act as the management interface:

meta-management/recipes-management/management-if/files/management-if.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Simple Firewall - Management Interface</title>

  <style>
    body {
      font-family: Arial, Helvetica, sans-serif;
      background-color: #f4f6f8;
      margin: 0;
      padding: 0;
    }

    header {
      background-color: #1f2d3d;
      color: #ffffff;
      padding: 20px;
      text-align: center;
    }

    header h1 {
      margin: 0;
    }

    header p {
      margin: 5px 0;
    }

    main {
      display: flex;
      justify-content: center;
      margin-top: 60px;
    }

    .login-box {
      background-color: #ffffff;
      padding: 30px;
      width: 320px;
      border-radius: 6px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }

    .login-box h2 {
      margin-top: 0;
      text-align: center;
    }

    label {
      font-size: 14px;
      font-weight: bold;
    }

    input[type="text"],
    input[type="password"] {
      width: 100%;
      padding: 8px;
      margin-top: 5px;
      margin-bottom: 15px;
      border: 1px solid #ccc;
      border-radius: 4px;
      box-sizing: border-box;
    }

    button {
      width: 100%;
      padding: 10px;
      background-color: #2c7be5;
      color: #ffffff;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 14px;
    }

    button:hover {
      background-color: #1a68d1;
    }

    footer {
      text-align: center;
      margin-top: 80px;
      padding: 15px;
      font-size: 12px;
      color: #666;
    }
  </style>
</head>

<body>

  <header>
    <h1>Device Management Interface</h1>
    <p><strong>Simple Firewall Tutorial</strong></p>
    <p>Software Version: 1.1.0</p>
  </header>

  <main>
    <div class="login-box">
      <h2>Login</h2>

      <form method="post" action="/login">
        <label for="username">Username</label>
        <input type="text" id="username" name="username" required />

        <label for="password">Password</label>
        <input type="password" id="password" name="password" required />

        <button type="submit">Login</button>
      </form>
    </div>
  </main>

  <footer>
    Simple Firewall
  </footer>

</body>
</html>

This minimal page is sufficient to validate:

  • Web server operation
  • Network exposure
  • Firewall filtering

To use management-if.html as the index, httpd should be started with the provided httpd.conf configuration file:

meta-management/recipes-management/management-if/files/httpd.conf
I:management-if.html

To start the web server automatically at boot, we create a dedicated systemd service, called management-if.service

meta-management/recipes-management/management-if/files/management-if.service
[Unit]
Description=Web Management Interface
After=network.target
Requires=network.target

[Service]
ExecStart=/usr/bin/busybox httpd -f -h /var/www -c /etc/management-if/httpd.conf
Restart=always

[Install]
WantedBy=multi-user.target

The management-if.bb will install the web management interface under /var/www:

meta-management/recipes-management/management-if/management-if.bb
SUMMARY = "Minimal Web Management interface"
LICENSE = "CLOSED"

SRC_URI += " \
  file://management-if.html \
  file://management-if.service \
  file://httpd.conf \
  "

S = "${WORKDIR}"

inherit systemd

SYSTEMD_SERVICE:${PN} = "management-if.service"
SYSTEMD_AUTO_ENABLE = "enable"

do_install() {
    install -d ${D}/var/www
    install -d ${D}${sysconfdir}/management-if/
    install -d ${D}${systemd_system_unitdir}

    install -m 0644 ${WORKDIR}/management-if.service ${D}${systemd_system_unitdir}
    install -m 0644 ${WORKDIR}/management-if.html ${D}/var/www
    install -m 0644 ${WORKDIR}/httpd.conf ${D}${sysconfdir}/management-if
}

Step 6: enable http packets through simple firewall

Create the following directories and files:

recipes-filter
└── simple-firewall
    ├── files
    │   └── 80-http.sfw
    └── simple-firewall%.bbappend

To enable HTTP packets on eth0 network interface only, 80-http.sfw contains the following:

meta-management/recipes-filter/simple-firewall/files/80-http.sfw
INPUT
    protocol=tcp dport=http if=eth0

This allows only HTTP packets to pass through the eth0 interface; all other packets will be handled by the default simple-firewall configuration, which will drop them.

This rule should be installed by simple-firewall%.bbappend under /etc/simple-firewall.d/:

meta-management/recipes-filter/simple-firewall/simple-firewall%.bbappend
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"

SRC_URI += "file://80-http.sfw"

do_install:append() {
    install -m 0644 ${WORKDIR}/80-http.sfw ${D}/${sysconfdir}/simple-firewall.d
}

Step 7: build the image

Add meta-management to the Yocto environement and build the image:

Terminal-1: Yocto build environment
$ cat << EOF >> conf/bblayers.conf
BBLAYERS =+ "$ROOT/sources/meta-management"
EOF
$ bitbake welma-image-minimal-dev

Install the resulting image tmp/deploy/images/$MACHINE/welma-image-minimal-dev-$MACHINE.wic.gz onto your device and boot it. You can access the web server using the eht0 ip address either:

  • From the host machine terminal:

    Terminal-1: Yocto build environment
    $ curl http://192.168.1.105/management-if.html
    <!DOCTYPE html>
    <html>
    <head>
        <title>Device Web Management</title>
    </head>
    <body>
        <h1>Welcome</h1>
        <p>This is the device management interface.</p>
    </body>
    </html>
    

  • From a web browser: Web Browser

Step 8: test the web management interface through WLAN

To test the uniqueness of access via the Ethernet interface, you will need to connect the wlan0 interface to the Wi-Fi network.

Warning

when two network interfaces are on the same L2 layer, by default, Linux uses a weak host model for ARP, which mean that it may answer ARP requests for any of its local IP addresses on any interface, not strictly the interface that owns the address.

In our case, if both wlan0 and eth0 interfaces are on the same network, Linux (in the Raspberry Pi) will respond to the ARP request to resolve the IP address of wlan0 with the MAC address of eth0, and in this case, consulting the web server via the IP address of wlan0 actually routes the packets to the MAC address of eth0, authorized by the firewall.

In your host machine, you can check the MAC address of both ip addresses of your Raspberry pi using the following command:

Terminal-1: Yocto build environment
$ arp -na
? (192.168.1.105) at dc:a6:32:c2:ce:70 [ether] on enp0s31f6
? (192.168.1.106) at dc:a6:32:c2:ce:70 [ether] on enp0s31f6
Since both nics share the same dc:a6:32:c2:ce:70 MAC address of eth0, WiFi traffic for 192.168.1.106 address will be also routed to eth0 interface.

Since we are using the same network, we need to stop the eth0 interface and run the wlan0 interface only during this test using the following command on the Raspberry pi serial port:

Terminal-2: /dev/ttyUSB0
raspberrypi4-64-welma:~# ip link set eth0 down
raspberrypi4-64-welma:~# systemctl restart wpa_supplicant@wlan0
After few second, an IP address should be attributed to wlan0

Terminal-2: /dev/ttyUSB0
raspberrypi4-64-welma:~# ip a show wlan0
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether dc:a6:32:c2:ce:71 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.106/24 metric 1024 brd 192.168.1.255 scope global dynamic wlan0
       valid_lft 86390sec preferred_lft 86390sec
    inet6 fe80::dea6:32ff:fec2:ce71/64 scope link proto kernel_ll 
       valid_lft forever preferred_lft forever

With this IP address, we can still ping the Raspberry Pi:

Terminal-1: Yocto build environment
$ ping 192.168.1.106 -c 2
PING 192.168.1.106 (192.168.1.106) 56(84) bytes of data.
64 bytes from 192.168.1.106: icmp_seq=1 ttl=64 time=55.9 ms
64 bytes from 192.168.1.106: icmp_seq=2 ttl=64 time=7.25 ms

--- 192.168.1.106 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 7.254/31.565/55.877/24.311 ms

But the Web application is not reachable:

Terminal-1: Yocto build environment
$ curl http://192.168.1.106/management-if.html
curl: (28) Failed to connect to 192.168.1.106 port 80 after 135972 ms: Connection timed out 

Dropped wlan0 packets are logged on the Raspberry Pi buffer:

Terminal-2: /dev/ttyUSB0
[simple-firewall] droped packet: IN=wlan0 OUT= MAC...

Summary

In this tutorial, you created a minimal web management interface page and integrated it into your Yocto image. You then deployed a lightweight web server configured to start automatically at boot and serve the management-if.html page.

Finally, you validated that the interface was accessible while all other inbound traffic remained blocked, demonstrating how to securely expose a device service without weakening the firewall baseline.