firewall and routing of port 80 to 8080

- create nftables in install.py
- routing of port 80 to 8080 if boxweb is using 8080
- modified change_firewall in utils.py used also by router.py
+ automatically start desktop and web browser when boxweb is
  configured
This commit is contained in:
2025-10-14 16:28:23 +02:00
parent 0547a2a503
commit 63373404e6
6 changed files with 144 additions and 81 deletions

View File

@@ -0,0 +1,12 @@
[Unit]
Description = Running Web Browser for boxweb
Requires=labwc.service
Requires=boxweb.service
[Service]
Type=simple
ExecStart = /home/l_samenv/boxweb/start_browser.py
[Install]
WantedBy = default.target

11
desktop/labwc.service Normal file
View File

@@ -0,0 +1,11 @@
[Unit]
Description=LabWC Wayland compositor
[Service]
User=l_samenv
Type=simple
ExecStart=/usr/bin/labwc
Restart=on-failure
[Install]
WantedBy=default.target

View File

@@ -47,6 +47,9 @@ except ImportError:
if serial is None:
exit()
main_ports = {22} # ssh
router_ports = set()
TOOLS = BoxInfo.TOOLS
BOX_TYPES = {'ionopi', 'ionopimax', 'control-box', 'bare-apu', 'dual-eth-rpi'}
@@ -198,6 +201,18 @@ def do_cmd(*command, sudo=True):
show.dirty = True
def do_copy(src, dst):
"""copy or delete (src=None)"""
as_root = not dst.is_relative_to('/home/l_samenv')
if src is None:
if dst.exists():
do_cmd('rm', dst, sudo=as_root)
return
if dst.exists() and getmtime(src) <= getmtime(dst):
return
do_cmd('cp', src, dst, sudo=as_root)
def frappy(cfg=None, port=None, requirements='', **kwds):
if not cfg:
return False, None
@@ -209,27 +224,18 @@ def frappy(cfg=None, port=None, requirements='', **kwds):
return False, FRAPPY_SERVICE
def router(firewall=False, **opts):
if firewall:
active, enabled = check_service('nftables')
ports = {22}
if opts:
for port in firewall.split(','):
ports.add(int(port))
for key in opts:
try:
ports.add(int(key))
except ValueError:
pass
ports.update((1110, 1111, 1112))
if change_firewall(True, ports, doit):
show.dirty = True
elif change_firewall(False, set(), doit):
show.dirty = True
def router(**opts):
active, enabled = check_service('nftables')
if opts:
for key in opts:
try:
router_ports.add(int(key))
except ValueError:
pass
router_ports.update((1110, 1111, 1112))
if not opts:
return True, None
try:
os.remove(TO_SYSTEM[0] / 'etc/nftables.conf')
with open(TOOLS / 'requirements.txt') as f:
pip_requirements['root']['tools'] = f.read()
except FileNotFoundError:
@@ -281,8 +287,21 @@ def pip():
def boxweb(port=8080, page=None):
port = int(port)
servicecfg = None if page is None else BOXWEB_SERVICE.format(port=port, page=page)
if page is None:
servicecfg = None
else:
port = int(port)
main_ports.add(port)
if port == 8080:
main_ports.add(80)
servicecfg = BOXWEB_SERVICE.format(port=port, page=page)
for file, dest in (('labwc.service', '/etc/systemd/system'),
('boxweb-browser.service', '/home/l_samenv/.config/systemd/user')):
src = TOOLS / 'desktop' / file
dst = Path(dest) / file
if not page:
src = None # -> remove
do_copy(src, dst)
return port <= 1024, servicecfg
@@ -697,6 +716,7 @@ def handle_config():
if servicecfg and to_start.get('service') is None:
to_start[service] = 'restart', as_root
change_firewall(router_ports, main_ports, doit)
if 'dialout' not in unix_cmd('id l_samenv'):
do_cmd('usermod -a -G dialout l_samenv')
pip()

View File

@@ -1,25 +0,0 @@
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0;
# accept any localhost traffic
iif lo accept
# accept traffic originated from us
ct state established,related accept
# activate the following line to accept common local services
tcp dport { 22 } ct state new accept
# ICMPv6 packets which must not be dropped, see https://tools.ietf.org/html/rfc4890#section-4.4.1
meta nfproto ipv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-reply, echo-request, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, 148, 149 } accept
ip6 saddr fe80::/10 icmpv6 type { 130, 131, 132, 143, 151, 152, 153 } accept
# count and drop any other traffic
counter drop
}
}

View File

@@ -421,10 +421,6 @@ class Service:
@classmethod
def run(cls, routes):
firewall = routes.pop('firewall', None)
firewall_on = firewall is not None
if firewall_on:
cls.firewall_ports = set(int(r) for r in firewall.split(',') if r.strip())
Service(1110, None, InfoHandler, 5, handler_args=(log.DEBUG,))
Service(1111, None, InfoHandler, 5, handler_args=(log.INFO,))
Service(1112, None, InfoHandler, 5, handler_args=(log.WARN,))
@@ -439,7 +435,7 @@ class Service:
else:
remoteport = port
Service(port, (host, remoteport), TcpHandler)
change_firewall(firewall_on, cls.firewall_ports)
change_firewall(cls.firewall_ports, None)
while True:
try:
# log.debug('select %r', list(cls.readers))
@@ -476,3 +472,5 @@ if __name__ == '__main__':
routercfg = BoxInfo().read_config('ROUTER')
if routercfg:
Service.run(routercfg)
else:
print('router is not configured')

109
utils.py
View File

@@ -2,6 +2,7 @@ import os
import socket
import threading
import re
import tempfile
from glob import glob
from pathlib import Path
from configparser import ConfigParser
@@ -10,6 +11,48 @@ from subprocess import Popen, PIPE
FIREWALL_CONF = '/etc/nftables.conf'
FIREWALL_MAIN = Path('~/.config/firewall').expanduser()
NFTABLES_CONF = """#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0;
# accept any localhost traffic
iif lo accept
# accept traffic originated from us
ct state established,related accept
# activate the following line to accept common local services
tcp dport { %s } ct state new accept
# ICMPv6 packets which must not be dropped, see https://tools.ietf.org/html/rfc4890#section-4.4.1
meta nfproto ipv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-reply, echo-request, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, 148, 149 } accept
ip6 saddr fe80::/10 icmpv6 type { 130, 131, 132, 143, 151, 152, 153 } accept
# count and drop any other traffic
counter drop
}
}
%s
"""
ROUTE80 = """
table ip nat {
chain prerouting {
type nat hook prerouting priority 0; policy accept;
tcp dport 80 redirect to 8080
}
chain postrouting {
type nat hook postrouting priority 0; policy accept;
}
}
"""
if os.geteuid():
@@ -255,45 +298,49 @@ def check_service(service, set_on=None, execute=None, as_root=True):
return active, enabled
def change_firewall(set_on, ports, execute=None):
ports.add(22) # always add ssh
active, enabled = check_service('nftables')
if not set_on:
check_service('nftables', False, execute)
return active or enabled
pattern = re.compile('(tcp dport ({.*}) ct state new accept)', re.MULTILINE | re.DOTALL)
for filename in FIREWALL_CONF, BoxInfo.TOOLS / 'nftables.conf':
with open(filename) as f:
content = f.read()
def change_firewall(router_ports, main_ports, execute=None):
"""configure the firewall
router_ports: the ports needed for the router
main_ports: other ports needed to be open. when None, read it from FIREWALL_MAIN
execute: None: do it quietly, True: do it, False: display only
"""
if main_ports is None:
try:
((prevline, prevports),) = pattern.findall(content)
break
except (TypeError, ValueError):
pass
with open(FIREWALL_MAIN) as fil:
main_ports = set(int(p) for p in f.read().split())
except FileNotFoundError:
main_ports = {22}
else:
print(f'{FIREWALL_CONF} does not contain expected pattern for open ports - firewall off?')
return False
main_ports.add(22) # always add ssh
if execute:
with open(FIREWALL_MAIN, 'w') as fil:
fil.write(' '.join(str(p) for p in sorted(main_ports)))
fil.write('\n')
# parse previous port set
prevportset = {int(p) for p in prevports[1:-1].split(',')}
line = prevline.replace(prevports, '{ %s }' % ', '.join((str(p) for p in sorted(ports))))
if prevportset == ports:
ports = main_ports | router_ports
active, enabled = check_service('nftables')
with open(FIREWALL_CONF) as f:
oldcontent = f.read()
portstr = ', '.join(str(p) for p in sorted(ports))
content = NFTABLES_CONF % (portstr, ROUTE80 if {80, 8080} <= ports else '')
if content == oldcontent:
if active and enabled:
return False
check_service('nftables', True, execute)
return True
if os.geteuid() == 0:
if execute is not None:
print(f'change firewall ports to {ports}')
if execute != 0:
with open('f{FIREWALL_CONF}.tmp', 'w') as f:
f.write(content.replace(prevline, line))
os.rename('f{FIREWALL_CONF}.tmp', FIREWALL_CONF)
if execute is not None:
print(f'set firewall ports to {portstr}')
if execute != 0:
with tempfile.NamedTemporaryFile('w') as fil:
fil.write(content)
fil.flush()
unix_cmd('cp', fil.name, FIREWALL_CONF, execute=execute)
unix_cmd('chmod 0644', FIREWALL_CONF, execute=execute)
unix_cmd('systemctl restart nftables', execute=execute)
unix_cmd('systemctl enable nftables', execute=execute)
elif ports - prevportset:
print('need sudo rights to modify firewall')
return True