What's new

Firewall configuration

  • SNBForums Code of Conduct

    SNBForums is a community for everyone, no matter what their level of experience.

    Please be tolerant and patient of others, especially newcomers. We are all here to share and learn!

    The rules are simple: Be patient, be nice, be helpful or be gone!

mma

Occasional Visitor
New user here; GNU/Linux desktop user since 1993, some experience with iptables (and its predecessor ipchains) and setting up routers, firewalls or VPN on commodity PC hardware in the late 1990s. For the past 20 years, I have used closed-source routers, nothing fancier than setting up some NAT rules for making some TCP/IP ports on the LAN accessible from the Internet.

Yesterday I bought a second-hand RT-AC86U. I immediately upgraded it to AsusWRT-Merlin 386.7 and installed Entware, mainly for rsync and prosody (XMPP), which I migrated from a Raspberry Pi 2.
Everything works well so far, except:
  • I did not figure out how to create persistent Linux user accounts for running services, so I reused the pre-existing user and group "nat" for Prosody. The paths were a bit different from what I had on Raspbian or Debian, but it was not hard to adjust the files.
  • Enabling the firewall in the web UI affects the output of iptables -L, but the inbound rule for TCP port 5222 (XMPP client-to-server connections) does not. That is, if I enable the firewall, Prosody can only receive connections from the LAN, not WAN. How to do this properly in the web UI, or via the command line? I did not find anything relevant in the wiki.
  • It would be nice to maintain a LetsEncrypt certificate when not using any DDNS provider, and have it available to all SSL based software that runs on the router (such as Prosody). I get the public IPv4 address via DHCP, but it is basically static as long as I will not shut down the router for some longer time. Thus, I would prefer to do the DDNS part "manually", just specify the name to LetsEncrypt.
I think that I should be able to contribute some pull requests (I already cloned the repository), but I would need some hints to get started.
 
New user here; GNU/Linux desktop user since 1993, some experience with iptables (and its predecessor ipchains) and setting up routers, firewalls or VPN on commodity PC hardware in the late 1990s. For the past 20 years, I have used closed-source routers, nothing fancier than setting up some NAT rules for making some TCP/IP ports on the LAN accessible from the Internet.

Yesterday I bought a second-hand RT-AC86U. I immediately upgraded it to AsusWRT-Merlin 386.7 and installed Entware, mainly for rsync and prosody (XMPP), which I migrated from a Raspberry Pi 2.
Everything works well so far, except:
  • I did not figure out how to create persistent Linux user accounts for running services, so I reused the pre-existing user and group "nat" for Prosody. The paths were a bit different from what I had on Raspbian or Debian, but it was not hard to adjust the files.
  • Enabling the firewall in the web UI affects the output of iptables -L, but the inbound rule for TCP port 5222 (XMPP client-to-server connections) does not. That is, if I enable the firewall, Prosody can only receive connections from the LAN, not WAN. How to do this properly in the web UI, or via the command line? I did not find anything relevant in the wiki.
  • It would be nice to maintain a LetsEncrypt certificate when not using any DDNS provider, and have it available to all SSL based software that runs on the router (such as Prosody). I get the public IPv4 address via DHCP, but it is basically static as long as I will not shut down the router for some longer time. Thus, I would prefer to do the DDNS part "manually", just specify the name to LetsEncrypt.
I think that I should be able to contribute some pull requests (I already cloned the repository), but I would need some hints to get started.

Coming from a standard Linux Distro to a embedded Linux distro like BusyBox (what Asus is built on) will be a big shift for you.

I think most of what you want to do, you can use custom scripts. Certainly for the firewall changes (firewall-start and nat-start). Looking through the postconf scripts, it looks like use may be able to add users/groups.

Topic for another thread, but have a look here for a starter;

 
I did not figure out how to create persistent Linux user accounts for running services, so I reused the pre-existing user and group "nat" for Prosody. The paths were a bit different from what I had on Raspbian or Debian, but it was not hard to adjust the files.

As pointed out by @Jeffrey Young, the router is a very different beast than your standard Linux distro. A lot of things you probably have become accustomed to, don't even exist in these embedded systems due to resource limits. In this case, the adduser utility typically doesn't exist in third-party firmware because it's considered unnecessary and expendable. The *intent* is to provide administrative (root) privileges exclusively.

Even the scripting capabilities are stripped down, to the point there are many other missing utilities, and of those that are included, their options are stripped down as well. Heck, on DD-WRT years ago, they were so short of needed flash to store the firmware, they had to remove error messages! Imagine having to deal w/ a utility that just returns a non zero return code and says NOTHING.

Even persistence is handled differently, which is why those links above were provided.

  • Enabling the firewall in the web UI affects the output of iptables -L, but the inbound rule for TCP port 5222 (XMPP client-to-server connections) does not. That is, if I enable the firewall, Prosody can only receive connections from the LAN, not WAN. How to do this properly in the web UI, or via the command line? I did not find anything relevant in the wiki.

For remote access purposes, if the target of that service is running on the router itself, you just open the port on the WAN using iptables. If the target of that remote access is elsewhere on the LAN, then you use the port forwarding feature of the GUI (or iptables if you prefer).

For the former, you need to make sure the service is actually *listening* on the WAN network interface. As it happens, most services will default to ALL network interfaces anyway (check its config file). But if it happens to be specifically bound to the LAN, then you're going to have issues until you either include ALL, or include the WAN, or create your own port forwarding using iptables so you can redirect that traffic from the WAN to the LAN (something the port forwarding feature in the GUI can NOT do).
 
Last edited:
As pointed out by @Jeffrey Young, the router is a very different beast than your standard Linux distro. A lot of things you probably have become accustomed to, don't even exist in these embedded systems due to resource limits. In this case, the adduser utility typically doesn't exist in third-party firmware because it's considered unnecessary and expendable. The *intent* is to provide administrative (root) privileges exclusively.
I added a line for a new user and group to each of passwd, group, shadow, gshadow in /etc. It worked, but after the router was accidentally power-cycled (a bad contact between the power supply and the wall plug adapter, which I think I was able to fix later), the changes were gone, and "ls" would only display the numeric UID and GID that I had chosen. I did not want to run the service as the root user, hence I ended up reusing the "nat" user and group for it. I was pleased to find out that the Prosody service is automatically starting when the router is powered on.

I played with embedded Linux back when the 2.4 kernel was still being maintained. I managed to cram a kernel (with all required drivers built in, no support for modules or initramfs) and some self-written statically linked user-space program running as the init process, on a 1.44MB 3.5" floppy disk of a 80486 system that might have had some 4 MB of RAM. I do not remember if BusyBox was available back then, but uClibc definitely was. For the VPN or router setup, I could have used a hard disk. Compared to that, the RT-AC88U is a storage monster.
For remote access purposes, if the target of that service is running on the router itself, you just open the port on the WAN using iptables. If the target of that remote access is elsewhere on the LAN, then you use the port forwarding feature of the GUI (or iptables if you prefer).
I think that I will try the direct iptables approach, once I have figured out how the configuration files work.
For the former, you need to make sure the service is actually *listening* on the WAN network interface. As it happens, most services will default to ALL network interfaces anyway (check its config file).
In the output of netstat -natp, there were a few services listening to all addresses (0.0.0.0). I did not check for any interface limitations yet. I would guess that by default, the service would listen to all addresses on all interfaces. I will investigate further and report back later.
 
According to its documentation, Prosody is listening on all interfaces by default.

The following did the trick for me. I was first trying to use the other2wan chain, until I understood that probably is for filtering LAN-to-WAN traffic.
Bash:
cat > /jffs/scripts/firewall-start << EOF
#!/bin/sh
iptables -I INPUT -p tcp -m tcp --dport 5222 --jump ACCEPT
EOF
chmod +rx /jffs/scripts/firewall-start
This rule is basically a copy of an existing rule that allows connections from the WAN to the SSH port (22).

I got a little closer to having an auto-generated LetsEncrypt certificate. In WAN/DDNS:
  1. Enable the DDNS Client: Yes
  2. Server: Custom
  3. Host Name: (fill in)
I created a dummy script:
Bash:
cat > /jffs/scripts/ddns-start << EOF
#!/bin/sh
exec /sbin/ddns_custom_updated 1
EOF
chmod +rx /jffs/scripts/ddns-start
The "Server Certificate" got to Status: Authorizing but not beyond that. I also tried explicitly enabling ports 80 and 443 in my firewall-start script above. Any hints?
 
I found this acme.sh wrapper script by @Jeffrey Young. That thread also says that the ASUS version of acme.sh had better be replaced with one from upstream. Would that also be case with asuswrt-merlin 386.7? In the source code, I found a number of precompiled libraries libletsencrypt.so that appear to lack source code. If I understood it correctly, one of those libraries would invoke acme.sh. I did not figure out where any output from that script would go. There was nothing in /jffs/syslog.log.

In the wiki, there is no page about LetsEncrypt yet. I would be happy to contribute that, if I get this to work first.
 
I found this acme.sh wrapper script by @Jeffrey Young. That thread also says that the ASUS version of acme.sh had better be replaced with one from upstream. Would that also be case with asuswrt-merlin 386.7? In the source code, I found a number of precompiled libraries libletsencrypt.so that appear to lack source code. If I understood it correctly, one of those libraries would invoke acme.sh. I did not figure out where any output from that script would go. There was nothing in /jffs/syslog.log.

In the wiki, there is no page about LetsEncrypt yet. I would be happy to contribute that, if I get this to work first.

The acme.sh script in 386.5_2 was too old and would not support everything I wanted to do. In addition to the certificate for the router, I also use a wildcard certificate for my Active Directory, Nextcloud (LDAPS) and NGINX reverse proxy.

Basically, I downloaded the acme.sh script from github onto the USB key of the router, ran the upgrade/install functions to get everything setup. Then, to make sure I never inadvertently run the firmware version of acme.sh, I use use a bind mount in the init-start script to replace the firmware version with the current github version. I have another script that gets called twice daily to check for certificate renewal and, if renewed, copies required files to all my varies machines (DC, Nextcloud, LDAPS, etc.)

Also, should mention, I don't use LetsEncrypt. I use ZeroSSL, which is the current default for the current version of acme.sh

As for 386.7, could not say. I have no plans to upgrade to it. I am currently working on replacing my AC86U with a RasPi4 running Ubuntu and use the AC86U as a mere AP.
 
Report back once you’re done, please.

The RasPi is actually ready to go. Except for some fine tuning and running new cabling to the upstairs where I want the new AP to go. My big problem now is 1. It's summer and better things to do and 2. Getting a half day to full day of no internet to swap things around is damn near impossible with the family. It is hard for me even to get AC86U for an hour to test the defaults of how the guest is set up in AP. I am told the default AP setup of the Guest network is VLAN 501. But I have yet to the time to test that.

My RasPi is setup as follows;

1. Ubuntu 22.04 Server for RasPi
2. DNSMASQ for DHCP (I just know it better that ics-dhcp)
3. AdGuardHome for DNS (currently tweaking for main subnet is filtered, and guest subnet is not - bit of a learning curve)
4. VNSTAT for use monitoring
5. My own adaptation of Jack's SpdMerlin (just rewrote my SpdWrt script a bit)
6. I modelled the firewall after the Default AC86U firewall - cleaning it in spots to make it more friendly
7. Acme.sh to manage all my SSL certificate requirements across my network
8. Chrony for Network time (also use iptables to force RaspPi being the time server)
9. I use Network-Manager as opposed to NetworkD as the network monitor scripts (event handlers) are more diverse and useful in Network-Manager
10. My own mail scripts to send my info emails/SMS, when required
11. NUT for UPS management (and to signal shutdowns to my machines).

I am using a TP-Link managed switch (TP 108, I think). I was using the single network port on the Pi for all network (LAN, WAN, GUEST) using VLANs for the WAN/Guest, however, I could not get the network services monitor to detect network changes in a VLAN (ethernet up/down). As this was important to me for signaling DDNS changes and to monitor when the ISP goes down, I switched to using a separate USB Network dongle for the WAN.

If I ever get the time to go all out with this, I will certainly keep you in the loop @dave14305. After all, you were the one that inspired this little build!
 
Basically, I downloaded the acme.sh script from github onto the USB key of the router, ran the upgrade/install functions to get everything setup. Then, to make sure I never inadvertently run the firmware version of acme.sh, I use use a bind mount in the init-start script to replace the firmware version with the current github version. I have another script that gets called twice daily to check for certificate renewal and, if renewed, copies required files to all my varies machines (DC, Nextcloud, LDAPS, etc.)
Thank you. Do you know which web server the built-in acme.sh is using? Also NginX?
As for 386.7, could not say. I have no plans to upgrade to it. I am currently working on replacing my AC86U with a RasPi4 running Ubuntu and use the AC86U as a mere AP.
I see, we are basically going in opposite directions. I had LetsEncrypt (with lightttpd) and Prosody running on the Raspberry Pi 2 B, with NAT port forwarding from the old router. Now both are offline. If I wanted to occasionally get better WLAN coverage upstairs or in the back yard, I could connect the old router in bridge AP mode.
I basically like to tinker a little with the router. It already satisfies one of my goals: no more than 1 always-on device in my LAN.
 
Thank you. Do you know which web server the built-in acme.sh is using? Also NginX?

I don't know how Asus uses the acme.sh script internally. I am assuming it is for the Let's Encrypt feature in the GUI. Asus, I think uses a light weight web server of some kind (httpd maybe).

I use NGINX via Entware on my AC86U as a reverse proxy to the family Nextcloud server and OnlyOffice server. The built in acme.sh would not handle a custom call to issue wildcard certificates as well as to use the GoDaddy DNS plug ins. Hence why I decided to use the github version. The fact I was able to secure the Asus GUI was just a bonus for me.
 
The third line of the acme.sh in 386.7 says VER=2.8.3. The file is almost identical to the 2.8.3 release tag in the upstream repository. It should be safe to assume that the LetsEncrypt thingy from the web UI is not worth spending time on. Yesterday I found this unresolved ticket from 2018. Version 2.8.3 was released in September 2019, so whenever ASUSWRT was finally updated to that, it is possible that it would have fixed that issue from 2018. I don’t think that the 2.8.3 version can possibly work nowadays. Something in the protocol was changed rather recently, because I had to upgrade the script on my Raspberry Pi before I could renew my certificate at the start of July.
 
I just realized that I had used lighttpd and dehydrated (another shell script) instead of acme.sh on the Raspberry Pi. A search of the output of strings libletsencrypt.so revealed that the web UI is using acme.sh in standalone mode (letting the script implement a web server for communicating with the certificate server).
The following command worked for me, on the plain acme.sh script straight from the repository, without any "installation":
Bash:
acme.sh --issue -d myhostname.example.com --server letsencrypt --standalone
For debugging, you may additionally specify --log. To be exact, the command only worked after I set Administration/System: HTTP LAN port: 8080. The httpd process was listening on port 80 even though I had chosen Authentication Method: HTTPS (not HTTP or BOTH). I wanted to avoid killing and restarting the httpd process myself, especially because the program appears to include some closed-source components,

I explicitly used LetsEncrypt, because the default server (ZeroSSL) requires that an email address be specified. I do not need or want any automated renewal; manually updating the certificate for Prosody is enough for me. The output would appear in /root/.acme.sh/myhostname.example.com/. I will try installing the certificate later, maybe tomorrow. It looks like dehydrated uses the .pem suffix for the files while acme.sh would use the .cer suffix.
 
I just realized that I had used lighttpd and dehydrated (another shell script) instead of acme.sh on the Raspberry Pi. A search of the output of strings libletsencrypt.so revealed that the web UI is using acme.sh in standalone mode (letting the script implement a web server for communicating with the certificate server).
The following command worked for me, on the plain acme.sh script straight from the repository, without any "installation":
Bash:
acme.sh --issue -d myhostname.example.com --server letsencrypt --standalone
For debugging, you may additionally specify --log. To be exact, the command only worked after I set Administration/System: HTTP LAN port: 8080. The httpd process was listening on port 80 even though I had chosen Authentication Method: HTTPS (not HTTP or BOTH). I wanted to avoid killing and restarting the httpd process myself, especially because the program appears to include some closed-source components,

I explicitly used LetsEncrypt, because the default server (ZeroSSL) requires that an email address be specified. I do not need or want any automated renewal; manually updating the certificate for Prosody is enough for me. The output would appear in /root/.acme.sh/myhostname.example.com/. I will try installing the certificate later, maybe tomorrow. It looks like dehydrated uses the .pem suffix for the files while acme.sh would use the .cer suffix.

Glad that you got it working for you. I did forget to mention that I can not use standalone mode. I am behind a CGNAT where my ISP has been kind enough to assign a static 172.x.x.x address and port forward my a block of ports. I then use redirection on GoDaddy to redirect my public s nextcloud FQDN to the correct internal FQDN and port to get through my ISP. Bit of a pain, but my ISP is the only show in town. I need to use acme.sh with the GoDaddy plug in so that the certificate challenge is directed to GoDaddy as opposed to my own internal web server (which can not be reached via port 80 or 443 in my case).
 
I decided to give the web UI one more chance.

The mount --bind option was something new to me. Using it, I replaced the /usr/sbin/acme.sh, but I do not see it being invoked by the web UI. My edited version of the file starts with the following:
Bash:
#!/usr/bin/env sh

echo acme: "$@" >> /jffs/syslog.log

VER=3.0.4
I only see messages about ddns or httpd restart, but nothing generated by this script. Based on what @garycnew wrote in his wrapper script comments, I would have expected a directory /jffs/.le to be created.

The following steps would work for me, using the latest revision of acme.sh.
Bash:
FQDN=myhostname.example.com
mkdir -m 750 /opt/etc/prosody/certs
acme.sh --certhome /opt/etc/prosody/certs --fullchain-file /opt/etc/prosody/certs/$FQDN/fullchain.pem --key-file /opt/etc/prosody/certs/$FQDN/privkey.pem --issue -d $FQDN --server letsencrypt --standalone
chown -R nas.nas /opt/etc/prosody/certs
prosodyctl reload
I verified in an XMPP client (Gajim) that the server certificate was freshly created.
 
I created the wiki pages Prosody and LetsEncrypt based on my setup. If someone knows how the libletsencrypt.so of the web UI can be made to work, edits to the latter wiki page are welcome.

For what it is worth, in my attempt to run acme.sh release 3.0.4 from the web UI, I reverted the default to LetsEncrypt V2:
Diff:
diff --git a/acme.sh b/acme.sh
index 2517ca0..3fc6358 100755
--- a/acme.sh
+++ b/acme.sh
@@ -37,7 +37,7 @@ CA_SSLCOM_ECC="https://acme.ssl.com/sslcom-dv-ecc"
 CA_GOOGLE="https://dv.acme-v02.api.pki.goog/directory"
 CA_GOOGLE_TEST="https://dv.acme-v02.test-api.pki.goog/directory"
 
-DEFAULT_CA=$CA_ZEROSSL
+DEFAULT_CA=$CA_LETSENCRYPT_V2
 DEFAULT_STAGING_CA=$CA_LETSENCRYPT_V2_TEST
 
 CA_NAMES="
The default had been changed in the 3.0.0 release. I also tried to apply the changes that existed between 2.8.3 and the /usr/sbin/acme.sh in the router:
Diff:
diff --git a/acme.sh b/acme.sh
index b958a02..2517ca0 100755
--- a/acme.sh
+++ b/acme.sh
@@ -1646,7 +1646,8 @@ _stat() {
 #keyfile
 _isRSA() {
   keyfile=$1
-  if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || ${ACME_OPENSSL_BIN:-openssl} rsa -in "$keyfile" -noout -text | grep "^publicExponent:" >/dev/null 2>&1; then
+  if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1 ||
+     grep "BEGIN PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || ${ACME_OPENSSL_BIN:-openssl} rsa -in "$keyfile" -noout -text | grep "^publicExponent:" >/dev/null 2>&1; then
     return 0
   fi
   return 1
@@ -4943,8 +4944,10 @@ $_authorizations_map"
         _debug2 errordetail "$errordetail"
         if [ "$errordetail" ]; then
           _err "$d:Verify error:$errordetail"
+          echo -n "$errordetail" > /tmp/acme_err
         else
           _err "$d:Verify error:$error"
+          echo -n "$error" > /tmp/acme_err
         fi
         if [ "$DEBUG" ]; then
           if [ "$vtype" = "$VTYPE_HTTP" ]; then
For my command line based approach, I simply used the latest acme.sh without any modifications.
 

Similar threads

Latest threads

Sign Up For SNBForums Daily Digest

Get an update of what's new every day delivered to your mailbox. Sign up here!
Top