From 291bca0818e98457bc0de7db58bdbfa0ce446c17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Mon, 9 Dec 2024 11:49:08 +0100 Subject: [PATCH 01/14] firewall.py complete revamp --- conf/yunohost/firewall.yml | 57 +- conf/yunohost/services.yml | 2 +- debian/control | 4 +- share/actionsmap.yml | 89 ++- src/firewall.py | 808 ++++++++++++------------- src/migrations/0032_firewall_config.py | 57 ++ src/tests/test_app_resources.py | 6 +- src/tools.py | 4 +- src/utils/resources.py | 6 +- 9 files changed, 562 insertions(+), 471 deletions(-) create mode 100644 src/migrations/0032_firewall_config.py diff --git a/conf/yunohost/firewall.yml b/conf/yunohost/firewall.yml index 64c6b93264..7a4de86208 100644 --- a/conf/yunohost/firewall.yml +++ b/conf/yunohost/firewall.yml @@ -1,12 +1,45 @@ -uPnP: - enabled: false - TCP: [22, 25, 80, 443, 587, 993, 5222, 5269] - UDP: [] - TCP_TO_CLOSE: [] - UDP_TO_CLOSE: [] -ipv4: - TCP: [22, 25, 53, 80, 443, 587, 993, 5222, 5269] - UDP: [53, 5353] -ipv6: - TCP: [22, 25, 53, 80, 443, 587, 993, 5222, 5269] - UDP: [53, 5353] +router_forwarding_upnp: false + +tcp: + 22: + open: true + upnp: true + comment: Default SSH port + 25: + open: true + upnp: true + comment: SMTP email server (postfix) + 80: + open: true + upnp: true + comment: HTTP server (nginx) + 443: + open: true + upnp: true + comment: HTTPS server (nginx) + 587: + open: true + upnp: true + comment: SMTP MSA email server (postfix) + 993: + open: true + upnp: true + comment: IMAP email server (dovecot) + +udp: + 53: + open: true + upnp: false + comment: DNS server (dnsmasq) + 1900: + open: true + upnp: false + comment: UPnP services + 5353: + open: true + upnp: false + comment: mDNS (yunomdns) + 55354: + open: true + upnp: false + comment: YunoHost UPnP firewall configurator diff --git a/conf/yunohost/services.yml b/conf/yunohost/services.yml index 6d5f6746ea..55e94bb6d8 100644 --- a/conf/yunohost/services.yml +++ b/conf/yunohost/services.yml @@ -55,7 +55,7 @@ yunohost-api: category: admin yunohost-firewall: need_lock: true - test_status: iptables -S | grep "^-A INPUT" | grep " --dport" | grep -q ACCEPT + test_status: nft list chain ip filter input | grep "dport" | grep -q "accept" category: security yunomdns: category: mdns diff --git a/debian/control b/debian/control index fe5cb5a20c..5eced681f0 100644 --- a/debian/control +++ b/debian/control @@ -19,7 +19,7 @@ Depends: python3-all (>= 3.11), , python-is-python3, python3-pydantic, python3-email-validator , nginx, nginx-extras (>=1.22) , apt, apt-transport-https, apt-utils, aptitude, dirmngr - , openssh-server, iptables, fail2ban, bind9-dnsutils + , openssh-server, nftables, fail2ban, bind9-dnsutils , openssl, ca-certificates, netcat-openbsd, iproute2 , slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd, libpam-ldapd , dnsmasq, resolvconf, libnss-myhostname @@ -44,7 +44,7 @@ Conflicts: iptables-persistent , slapd (>= 2.6) , dovecot-core (>= 1:2.4) , fail2ban (>= 1.1) - , iptables (>= 1.8.10) + , nftables (>= 1.1) Description: manageable and configured self-hosting server YunoHost aims to make self-hosting accessible to everyone. It configures an email, Web and IM server alongside a LDAP base. It also provides diff --git a/share/actionsmap.yml b/share/actionsmap.yml index e1e9cb01ba..ed5e10b891 100755 --- a/share/actionsmap.yml +++ b/share/actionsmap.yml @@ -1396,13 +1396,83 @@ firewall: full: --raw help: Return the complete YAML dict action: store_true - -i: - full: --by-ip-version - help: List rules by IP version - action: store_true + protocol: + help: "If not raw, protocol type to list (tcp/udp)" + choices: + - tcp + - udp + nargs: "?" -f: - full: --list-forwarded - help: List forwarded ports with UPnP + full: --forwarded + help: If not raw, list UPnP forwarded ports instead of open ports + action: store_true + + ### firewall_open() + open: + action_help: Allow connections on a port + api: PUT /firewall//open/ + arguments: + port: + help: Port or range of ports to open + extra: + pattern: &pattern_port_or_range + - !!str ((^|(?!\A):)([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){1,2}?$ + - "pattern_port_or_range" + protocol: + help: "Protocol type (tcp/udp)" + choices: + - tcp + - udp + default: tcp + comment: + help: A reason for the port to be open (like the app's name) + default: "" + --upnp: + help: Add forwarding of this port with UPnP + action: store_true + --no-reload: + help: Do not reload firewall rules + action: store_true + + ### firewall_close() + close: + action_help: Disallow connections on a port + api: PUT /firewall//close/ + arguments: + port: + help: Port or range of ports to close + extra: + pattern: *pattern_port_or_range + protocol: + help: "Protocol type (tcp/udp)" + choices: + - tcp + - udp + default: tcp + --upnp-only: + help: Only remove forwarding of this port with UPnP + action: store_true + --no-reload: + help: Do not reload firewall rules + action: store_true + + ### firewall_delete() + delete: + action_help: Unregister a port from YunoHost + api: PUT /firewall//delete/ + arguments: + port: + help: Port or range of ports to delete + extra: + pattern: *pattern_port_or_range + protocol: + help: "Protocol type (tcp/udp)" + choices: + - tcp + - udp + default: tcp + --no-reload: + help: Do not reload firewall rules action: store_true ### firewall_allow() @@ -1420,9 +1490,7 @@ firewall: port: help: Port or range of ports to open extra: - pattern: &pattern_port_or_range - - !!str ((^|(?!\A):)([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){1,2}?$ - - "pattern_port_or_range" + pattern: *pattern_port_or_range -4: full: --ipv4-only help: Only add a rule for IPv4 connections @@ -1479,7 +1547,6 @@ firewall: - enable - disable - status - - reload nargs: "?" default: status --no-refresh: @@ -1497,7 +1564,7 @@ firewall: ### firewall_stop() stop: - action_help: Stop iptables and ip6tables + action_help: Remove all the firewall rules diff --git a/src/firewall.py b/src/firewall.py index 9e751399ae..5a0ce6360b 100644 --- a/src/firewall.py +++ b/src/firewall.py @@ -19,510 +19,463 @@ # import os +import shutil +from typing import Any +from pathlib import Path from logging import getLogger import miniupnpc import yaml from moulinette import m18n -from moulinette.utils import process from yunohost.utils.error import YunohostError, YunohostValidationError +from yunohost.regenconf import regen_conf -FIREWALL_FILE = "/etc/yunohost/firewall.yml" -UPNP_CRON_JOB = "/etc/cron.d/yunohost-firewall-upnp" logger = getLogger("yunohost.firewall") -def firewall_allow( - protocol, - port, - ipv4_only=False, - ipv6_only=False, - no_upnp=False, - no_reload=False, - reload_only_if_change=False, -): +class YunoFirewall: + FIREWALL_FILE = Path("/etc/yunohost/firewall.yml") + + def __init__(self) -> None: + self.need_reload = False + + # This is a workaround for when we need to actively close UPnP ports + self.upnp_to_close: list[tuple[str, int | str]] = [] + + self.read() + + def read(self) -> None: + self.config = yaml.safe_load(self.FIREWALL_FILE.read_text()) + + def write(self) -> None: + old_file = self.FIREWALL_FILE.parent / (self.FIREWALL_FILE.name + ".old") + shutil.copyfile(self.FIREWALL_FILE, old_file) + self.FIREWALL_FILE.write_text(yaml.dump(self.config)) + + def list(self, protocol: str, forwarded: bool = False) -> list[int]: + protocol, _ = self._validate_port(protocol, 0) + return [ + port + for port, status in self.config[protocol].items() + if (status["forwarded"] if forwarded else status["open"]) + ] + + @staticmethod + def _validate_port(protocol: str, port: int | str) -> tuple[str, int | str]: + if isinstance(port, str) and ":" not in port: + port = int(port) + if protocol not in ["tcp", "udp"]: + raise ValueError(f"protocol should be tcp or udp, not {protocol}") + return protocol, port + + def open_port( + self, protocol: str, port: int | str, comment: str, upnp: bool = False + ) -> None: + protocol, port = self._validate_port(protocol, port) + + if port not in self.config[protocol]: + self.config[protocol][port] = { + "open": False, + "upnp": False, + "comment": comment, + } + + # Keep existing comment if the one passed is empty + if comment: + self.config[protocol][port]["comment"] = comment + + if not self.config[protocol][port]["open"]: + self.config[protocol][port]["open"] = True + self.need_reload = True + + if self.config[protocol][port]["upnp"] != upnp: + self.config[protocol][port]["upnp"] = upnp + self.need_reload = True + self.write() + + def close_port( + self, protocol: str, port: int | str, upnp_only: bool = False + ) -> None: + protocol, port = self._validate_port(protocol, port) + + if port not in self.config[protocol]: + return + + if self.config[protocol][port]["upnp"]: + self.config[protocol][port]["upnp"] = False + # not need_reload, it's only upnp + self.upnp_to_close.append((protocol, port)) + + if upnp_only: + self.write() + return + + if self.config[protocol][port]["open"]: + self.config[protocol][port]["open"] = False + self.need_reload = True + self.write() + + def delete_port(self, protocol: str, port: int | str) -> None: + protocol, port = self._validate_port(protocol, port) + + if port not in self.config[protocol]: + return + + self.close_port(protocol, port, False) + + del self.config[protocol][port] + self.need_reload = True + self.write() + + def apply(self, upnp: bool = True) -> None: + # Just leverage regen_conf that will regen the nftables files, reload nftables + regen_conf(["nftables"], force=True) + self.need_reload = False + + # Refresh port forwarding with UPnP + if self.config["router_forwarding_upnp"] and upnp: + YunoUPnP(self).refresh(self) + + def clear(self) -> None: + os.system("systemctl stop nftables") + + +class YunoUPnP: + UPNP_PORT = 55354 # Picked at random, this port has no real meaning + UPNP_PORT_COMMENT = "YunoHost UPnP firewall configurator" + UPNP_CRON_JOB = Path("/etc/cron.d/yunohost-firewall-upnp") + + def __init__(self, firewall: "YunoFirewall") -> None: + self.firewall = firewall + self.upnpc: miniupnpc.UPnP | None = None + + def enabled(self, new_status: bool | None = None) -> bool: + if new_status is not None: + self.firewall.config["router_forwarding_upnp"] = new_status + self.firewall.write() + return self.firewall.config["router_forwarding_upnp"] + + def ensure_listen_port(self) -> None: + self.firewall.open_port("udp", self.UPNP_PORT, self.UPNP_PORT_COMMENT) + + def find_gid(self) -> bool: + upnpc = miniupnpc.UPnP() + upnpc.localport = self.UPNP_PORT + upnpc.discoverdelay = 3000 + # Discover UPnP device(s) + logger.debug("discovering UPnP devices...") + try: + nb_dev = upnpc.discover() + except Exception: + logger.warning("Failed to find any UPnP device on the network") + nb_dev = -1 + if nb_dev < 1: + logger.error(m18n.n("upnp_dev_not_found")) + return False + logger.debug("found %d UPnP device(s)", int(nb_dev)) + try: + # Select UPnP device + upnpc.selectigd() + except Exception: + logger.debug("unable to select UPnP device", exc_info=1) + return False + return True + + def open_port(self, protocol: str, port: int | str, comment: str) -> bool: + if self.upnpc is None: + self.find_gid() + assert self.upnpc is not None + + # FIXME : how should we handle port ranges ? + if not isinstance(port, int): + logger.warning("Can't use UPnP to open '%s'" % port) + return False + + # Clean the mapping of this port + if self.upnpc.getspecificportmapping(port, protocol): + try: + self.upnpc.deleteportmapping(port, protocol) + except Exception: + return False + + # Add new port mapping + desc = f"yunohost firewall: port {port} {comment}" + try: + self.upnpc.addportmapping( + port, protocol, self.upnpc.lanaddr, port, desc, "" + ) + except Exception: + logger.debug("unable to add port %d using UPnP", port, exc_info=1) + return False + return True + + def close_port(self, protocol: str, port: int | str) -> bool: + if self.upnpc is None: + self.find_gid() + assert self.upnpc is not None + + if self.upnpc.getspecificportmapping(port, protocol): + try: + self.upnpc.deleteportmapping(port, protocol) + except Exception: + return False + return True + + def refresh(self, firewall: "YunoFirewall") -> bool: + if not self.find_gid(): + return False + + status = True + for protocol, port in firewall.upnp_to_close: + status = status or self.close_port(protocol, port) + + for protocol, ports in firewall.config.items(): + for port, info in ports.items(): + if self.enabled(): + status = status or self.open_port(protocol, port, info["comment"]) + else: + status = status or self.close_port(protocol, port) + + return status + + def enable(self) -> None: + if not self.enabled(): + # Add cron job + self.UPNP_CRON_JOB.write_text( + "*/50 * * * * root /usr/bin/yunohost firewall upnp status >>/dev/null\n" + ) + self.enabled(True) + + def disable(self) -> None: + if self.enabled(): + # Remove cron job + self.UPNP_CRON_JOB.unlink(missing_ok=True) + self.enabled(False) + + +def firewall_open( + port: int | str, + protocol: str, + comment: str, + upnp: bool = False, + no_reload: bool = False, + reload_if_changed: bool = False, +) -> None: """ Allow connections on a port Keyword arguments: - protocol -- Protocol type to allow (TCP/UDP/Both) port -- Port or range of ports to open - ipv4_only -- Only add a rule for IPv4 connections - ipv6_only -- Only add a rule for IPv6 connections + protocol -- Protocol type to allow (tcp/udp) + comment -- A reason for the port to be open no_upnp -- Do not add forwarding of this port with UPnP no_reload -- Do not reload firewall rules - """ - firewall = firewall_list(raw=True) - - # Validate port - if not isinstance(port, int) and ":" not in port: - port = int(port) - - # Validate protocols - protocols = ["TCP", "UDP"] - if protocol != "Both" and protocol in protocols: - protocols = [ - protocol, - ] - - # Validate IP versions - ipvs = ["ipv4", "ipv6"] - if ipv4_only and not ipv6_only: - ipvs = [ - "ipv4", - ] - elif ipv6_only and not ipv4_only: - ipvs = [ - "ipv6", - ] + firewall = YunoFirewall() + + # Add a readable comment if none was passed but we're handling an app + app_id = os.environ.get("YNH_APP_ID", "") + if not comment: + if app_id: + if port == 53: + comment = f"DNS for {app_id}" + elif port == 67: + comment = f"DHCP for {app_id}" + elif port == 445: + comment = f"SMB for {app_id}" + elif port == 1900: + comment = f"UPnP for {app_id}" + else: + comment = f"For {app_id}" + else: + comment = "Manually set without comment" - changed = False + firewall.open_port(protocol, port, comment, upnp) + if not reload_if_changed and not firewall.need_reload: + logger.warning(m18n.n("port_already_opened", port=port)) - for p in protocols: - # Iterate over IP versions to add port - for i in ipvs: - if port not in firewall[i][p]: - firewall[i][p].append(port) - changed = True - else: - ipv = "IPv%s" % i[3] - if not reload_only_if_change: - logger.warning( - m18n.n("port_already_opened", port=port, ip_version=ipv) - ) - # Add port forwarding with UPnP - if not no_upnp and port not in firewall["uPnP"][p]: - firewall["uPnP"][p].append(port) - if ( - p + "_TO_CLOSE" in firewall["uPnP"] - and port in firewall["uPnP"][p + "_TO_CLOSE"] - ): - firewall["uPnP"][p + "_TO_CLOSE"].remove(port) - - # Update and reload firewall - _update_firewall_file(firewall) - if (not reload_only_if_change and not no_reload) or ( - reload_only_if_change and changed + if (firewall.need_reload and reload_if_changed) or ( + not no_reload and not reload_if_changed ): - return firewall_reload() + firewall.apply() -def firewall_disallow( - protocol, - port, - ipv4_only=False, - ipv6_only=False, - upnp_only=False, - no_reload=False, - reload_only_if_change=False, -): +def firewall_close( + port: int | str, + protocol: str, + upnp_only: bool = False, + no_reload: bool = False, + reload_if_changed: bool = False, +) -> None: """ Disallow connections on a port Keyword arguments: - protocol -- Protocol type to disallow (TCP/UDP/Both) port -- Port or range of ports to close - ipv4_only -- Only remove the rule for IPv4 connections - ipv6_only -- Only remove the rule for IPv6 connections + protocol -- Protocol type to disallow (tcp/udp) upnp_only -- Only remove forwarding of this port with UPnP no_reload -- Do not reload firewall rules + """ + firewall = YunoFirewall() + + firewall.close_port(protocol, port, upnp_only=upnp_only) + if not firewall.need_reload and not reload_if_changed: + logger.warning(m18n.n("port_already_closed", port=port)) + + if (firewall.need_reload and reload_if_changed) or ( + not no_reload and not reload_if_changed + ): + firewall.apply() + + +# Legacy APIs +def firewall_allow( + protocol: str, + port: int | str, + ipv4_only: bool = False, + ipv6_only: bool = False, + no_upnp: bool = False, + no_reload: bool = False, + reload_only_if_change: bool = False, +) -> None: + return firewall_open( + port, protocol.lower(), "", not no_upnp, no_reload, reload_only_if_change + ) + +def firewall_disallow( + protocol: str, + port: int | str, + ipv4_only: bool = False, + ipv6_only: bool = False, + upnp_only: bool = False, + no_reload: bool = False, + reload_only_if_change: bool = False, +) -> None: + firewall_close(port, protocol.lower(), upnp_only, no_reload, reload_only_if_change) + + if os.environ.get("YNH_APP_ACTION", "") == "remove": + ports_to_keep = [53, 1900] + if port not in ports_to_keep: + firewall_delete(port, protocol.lower(), no_reload, reload_only_if_change) + + +def firewall_delete( + port: int | str, + protocol: str, + no_reload: bool = False, + reload_if_changed: bool = False, +) -> None: """ - firewall = firewall_list(raw=True) + Delete a port from YunoHost's config - # Validate port - if not isinstance(port, int) and ":" not in port: - port = int(port) + Keyword arguments: + protocol -- Protocol type to disallow (tcp/udp) + port -- Port or range of ports to close + no_reload -- Do not reload firewall rules + """ + firewall = YunoFirewall() + firewall.delete_port(protocol, port) - # Validate protocols - protocols = ["TCP", "UDP"] - if protocol != "Both" and protocol in protocols: - protocols = [ - protocol, - ] + if not firewall.need_reload and not reload_if_changed: + logger.warning(m18n.n("port_already_closed", port=port)) - # Validate IP versions and UPnP - ipvs = ["ipv4", "ipv6"] - upnp = True - if ipv4_only and ipv6_only: - upnp = True # automatically disallow UPnP - elif ipv4_only: - ipvs = [ - "ipv4", - ] - upnp = upnp_only - elif ipv6_only: - ipvs = [ - "ipv6", - ] - upnp = upnp_only - elif upnp_only: - ipvs = [] - - changed = False - - for p in protocols: - # Iterate over IP versions to remove port - for i in ipvs: - if port in firewall[i][p]: - firewall[i][p].remove(port) - changed = True - else: - ipv = "IPv%s" % i[3] - if not reload_only_if_change: - logger.warning( - m18n.n("port_already_closed", port=port, ip_version=ipv) - ) - # Remove port forwarding with UPnP - if upnp and port in firewall["uPnP"][p]: - firewall["uPnP"][p].remove(port) - if p + "_TO_CLOSE" not in firewall["uPnP"]: - firewall["uPnP"][p + "_TO_CLOSE"] = [] - firewall["uPnP"][p + "_TO_CLOSE"].append(port) - - # Update and reload firewall - _update_firewall_file(firewall) - if (not reload_only_if_change and not no_reload) or ( - reload_only_if_change and changed + if (firewall.need_reload and reload_if_changed) or ( + not no_reload and not reload_if_changed ): - return firewall_reload() + firewall.apply() -def firewall_list(raw=False, by_ip_version=False, list_forwarded=False): +def firewall_list( + raw: bool = False, protocol: str = "tcp", forwarded: bool = False +) -> dict[str, Any]: """ List all firewall rules Keyword arguments: raw -- Return the complete YAML dict - by_ip_version -- List rules by IP version - list_forwarded -- List forwarded ports with UPnP - + tcp -- If not raw, list TCP ports + udp -- If not raw, list UDP ports + forwarded -- If not raw, list UPnP forwarded ports instead of open ports """ - with open(FIREWALL_FILE) as f: - firewall = yaml.safe_load(f) - if raw: - return firewall - - # Retrieve all ports for IPv4 and IPv6 - ports = {} - for i in ["ipv4", "ipv6"]: - f = firewall[i] - # Combine TCP and UDP ports - ports[i] = sorted( - set(f["TCP"]) | set(f["UDP"]), - key=lambda p: int(p.split(":")[0]) if isinstance(p, str) else p, - ) + firewall = YunoFirewall() + return firewall.config if raw else {protocol: firewall.list(protocol, forwarded)} - if not by_ip_version: - # Combine IPv4 and IPv6 ports - ports = sorted( - set(ports["ipv4"]) | set(ports["ipv6"]), - key=lambda p: int(p.split(":")[0]) if isinstance(p, str) else p, - ) - # Format returned dict - ret = {"opened_ports": ports} - if list_forwarded: - # Combine TCP and UDP forwarded ports - ret["forwarded_ports"] = sorted( - set(firewall["uPnP"]["TCP"]) | set(firewall["uPnP"]["UDP"]), - key=lambda p: int(p.split(":")[0]) if isinstance(p, str) else p, - ) - return ret - - -def firewall_reload(skip_upnp=False): +def firewall_reload(skip_upnp: bool = False) -> None: """ Reload all firewall rules Keyword arguments: skip_upnp -- Do not refresh port forwarding using UPnP - """ - from yunohost.hook import hook_callback + # from yunohost.hook import hook_callback from yunohost.service import _run_service_command - reloaded = False - errors = False + firewall = YunoFirewall() # Check if SSH port is allowed - ssh_port = _get_ssh_port() - if ssh_port not in firewall_list()["opened_ports"]: - firewall_allow("TCP", ssh_port, no_reload=True) - - # Retrieve firewall rules and UPnP status - firewall = firewall_list(raw=True) - upnp = firewall_upnp()["enabled"] if not skip_upnp else False - - # IPv4 - try: - process.check_output("iptables -n -w -L") - except process.CalledProcessError as e: - logger.debug( - "iptables seems to be not available, it outputs:\n%s", - e.output.decode().strip(), - ) - logger.warning(m18n.n("iptables_unavailable")) - else: - rules = [ - "iptables -w -F", - "iptables -w -X", - "iptables -w -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT", - ] - # Iterate over ports and add rule - for protocol in ["TCP", "UDP"]: - for port in firewall["ipv4"][protocol]: - rules.append( - "iptables -w -A INPUT -p %s --dport %s -j ACCEPT" - % (protocol, process.quote(str(port))) - ) - rules += [ - "iptables -w -A INPUT -i lo -j ACCEPT", - "iptables -w -A INPUT -p icmp -j ACCEPT", - "iptables -w -P INPUT DROP", - ] - - # Execute each rule - if process.run_commands(rules, callback=_on_rule_command_error): - errors = True - reloaded = True - - # IPv6 - try: - process.check_output("ip6tables -n -L") - except process.CalledProcessError as e: - logger.debug( - "ip6tables seems to be not available, it outputs:\n%s", - e.output.decode().strip(), - ) - logger.warning(m18n.n("ip6tables_unavailable")) - else: - rules = [ - "ip6tables -w -F", - "ip6tables -w -X", - "ip6tables -w -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT", - ] - # Iterate over ports and add rule - for protocol in ["TCP", "UDP"]: - for port in firewall["ipv6"][protocol]: - rules.append( - "ip6tables -w -A INPUT -p %s --dport %s -j ACCEPT" - % (protocol, process.quote(str(port))) - ) - rules += [ - "ip6tables -w -A INPUT -i lo -j ACCEPT", - "ip6tables -w -A INPUT -p icmpv6 -j ACCEPT", - "ip6tables -w -P INPUT DROP", - ] - - # Execute each rule - if process.run_commands(rules, callback=_on_rule_command_error): - errors = True - reloaded = True - - if not reloaded: - raise YunohostError("firewall_reload_failed") - - hook_callback( - "post_iptable_rules", args=[upnp, os.path.exists("/proc/net/if_inet6")] - ) - - if upnp: - # Refresh port forwarding with UPnP - firewall_upnp(no_refresh=False) - + firewall.open_port("tcp", _get_ssh_port(), "SSH port", upnp=True) + firewall.apply(upnp=not skip_upnp) _run_service_command("restart", "fail2ban") + # FIXME: how to get errors from regen_conf? + errors = False if errors: logger.warning(m18n.n("firewall_rules_cmd_failed")) else: logger.success(m18n.n("firewall_reloaded")) - return firewall_list() -def firewall_upnp(action="status", no_refresh=False): +def firewall_upnp(action: str = "status", no_refresh: bool = False) -> dict[str, bool]: """ Manage port forwarding using UPnP - Note: 'reload' action is deprecated and will be removed in the near - future. You should use 'status' instead - which retrieve UPnP status - and automatically refresh port forwarding if 'no_refresh' is False. + Available actions are status, enable, disable. + All actions will refresh port forwarding unless 'no_refresh' is False. Keyword argument: action -- Action to perform no_refresh -- Do not refresh port forwarding - """ - firewall = firewall_list(raw=True) - enabled = firewall["uPnP"]["enabled"] - - # Compatibility with previous version - if action == "reload": - logger.debug("'reload' action is deprecated and will be removed") - try: - # Remove old cron job - os.remove("/etc/cron.d/yunohost-firewall") - except Exception: - pass - action = "status" - no_refresh = False - - if action == "status" and no_refresh: - # Only return current state - return {"enabled": enabled} - elif action == "enable" or (enabled and action == "status"): - # Add cron job - with open(UPNP_CRON_JOB, "w+") as f: - f.write( - "*/50 * * * * root " - "/usr/bin/yunohost firewall upnp status >>/dev/null\n" - ) - # Open port 1900 to receive discovery message - if 1900 not in firewall["ipv4"]["UDP"]: - firewall_allow("UDP", 1900, no_upnp=True, no_reload=True) - if not enabled: - firewall_reload(skip_upnp=True) - enabled = True - elif action == "disable" or (not enabled and action == "status"): - try: - # Remove cron job - os.remove(UPNP_CRON_JOB) - except Exception: - pass - enabled = False - if action == "status": - no_refresh = True - else: + if action not in ["status", "enable", "disable"]: raise YunohostValidationError("action_invalid", action=action) - # Refresh port mapping using UPnP - if not no_refresh: - upnpc = miniupnpc.UPnP(localport=1) - upnpc.discoverdelay = 3000 + firewall = YunoFirewall() + upnp = YunoUPnP(firewall) - # Discover UPnP device(s) - logger.debug("discovering UPnP devices...") - try: - nb_dev = upnpc.discover() - except Exception: - logger.warning("Failed to find any UPnP device on the network") - nb_dev = -1 - enabled = False + if action == "enable": + upnp.enable() + if action == "disable": + upnp.disable() + if no_refresh: + # Only return current state + return {"enabled": upnp.enabled()} - logger.debug("found %d UPnP device(s)", int(nb_dev)) - if nb_dev < 1: - logger.error(m18n.n("upnp_dev_not_found")) - enabled = False - else: - try: - # Select UPnP device - upnpc.selectigd() - except Exception: - logger.debug("unable to select UPnP device", exc_info=1) - enabled = False - else: - # Iterate over ports - for protocol in ["TCP", "UDP"]: - if protocol + "_TO_CLOSE" in firewall["uPnP"]: - for port in firewall["uPnP"][protocol + "_TO_CLOSE"]: - if not isinstance(port, int): - # FIXME : how should we handle port ranges ? - logger.warning("Can't use UPnP to close '%s'" % port) - continue - - # Clean the mapping of this port - if upnpc.getspecificportmapping(port, protocol): - try: - upnpc.deleteportmapping(port, protocol) - except Exception: - pass - firewall["uPnP"][protocol + "_TO_CLOSE"] = [] - - for port in firewall["uPnP"][protocol]: - if not isinstance(port, int): - # FIXME : how should we handle port ranges ? - logger.warning("Can't use UPnP to open '%s'" % port) - continue - - # Clean the mapping of this port - if upnpc.getspecificportmapping(port, protocol): - try: - upnpc.deleteportmapping(port, protocol) - except Exception: - pass - if not enabled: - continue - try: - # Add new port mapping - upnpc.addportmapping( - port, - protocol, - upnpc.lanaddr, - port, - "yunohost firewall: port %d" % port, - "", - ) - except Exception: - logger.debug( - "unable to add port %d using UPnP", port, exc_info=1 - ) - enabled = False - - _update_firewall_file(firewall) - - if enabled != firewall["uPnP"]["enabled"]: - firewall = firewall_list(raw=True) - firewall["uPnP"]["enabled"] = enabled - - _update_firewall_file(firewall) - - if not no_refresh: - # Display success message if needed - if action == "enable" and enabled: - logger.success(m18n.n("upnp_enabled")) - elif action == "disable" and not enabled: - logger.success(m18n.n("upnp_disabled")) - # Make sure to disable UPnP - elif action != "disable" and not enabled: - firewall_upnp("disable", no_refresh=True) - - if not enabled and (action == "enable" or 1900 in firewall["ipv4"]["UDP"]): - # Close unused port 1900 - firewall_disallow("UDP", 1900, no_reload=True) - if not no_refresh: - firewall_reload(skip_upnp=True) - - if action == "enable" and not enabled: + if upnp.refresh(firewall): + # Display success message if needed + logger.success( + m18n.n("upnp_enabled") if upnp.enabled() else m18n.n("upnp_disabled") + ) + else: + # FIXME: Do not update the config file to let a refresh handle the failure? raise YunohostError("upnp_port_open_failed") - return {"enabled": enabled} - -def firewall_stop(): - """ - Stop iptables and ip6tables + return {"enabled": upnp.enabled()} +def firewall_stop() -> None: """ - - if os.system("iptables -w -P INPUT ACCEPT") != 0: + Stop nftables + """ + if os.system("nft list ruleset") != 0: raise YunohostError("iptables_unavailable") + YunoFirewall().clear() - os.system("iptables -w -F") - os.system("iptables -w -X") - - if os.path.exists("/proc/net/if_inet6"): - os.system("ip6tables -P INPUT ACCEPT") - os.system("ip6tables -F") - os.system("ip6tables -X") - - if os.path.exists(UPNP_CRON_JOB): - firewall_upnp("disable") - -def _get_ssh_port(default=22): +def _get_ssh_port(default: int = 22) -> int: """Return the SSH port to use Retrieve the SSH port from the sshd_config file or used the default @@ -537,22 +490,3 @@ def _get_ssh_port(default=22): except Exception: pass return default - - -def _update_firewall_file(rules): - """Make a backup and write new rules to firewall file""" - os.system("cp {0} {0}.old".format(FIREWALL_FILE)) - with open(FIREWALL_FILE, "w") as f: - yaml.safe_dump(rules, f, default_flow_style=False) - - -def _on_rule_command_error(returncode, cmd, output): - """Callback for rules commands error""" - # Log error and continue commands execution - logger.debug( - '"%s" returned non-zero exit status %d:\n%s', - cmd, - returncode, - output.decode().strip(), - ) - return True diff --git a/src/migrations/0032_firewall_config.py b/src/migrations/0032_firewall_config.py new file mode 100644 index 0000000000..2c92a9d528 --- /dev/null +++ b/src/migrations/0032_firewall_config.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2024 YunoHost Contributors +# +# This file is part of YunoHost (see https://yunohost.org) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +import logging +from typing import Any + +import yaml + +from yunohost.tools import Migration + +from yunohost.firewall import YunoFirewall + +logger = logging.getLogger("yunohost.migration") + + +class MyMigration(Migration): + "Rework the firewall configuration" + + mode = "auto" + + def firewall_file_migrate(self) -> None: + old_data = yaml.safe_load(YunoFirewall.FIREWALL_FILE.open("r", encoding="utf-8")) + + new_data: dict[str, Any] = { + "router_forwarding_upnp": old_data["uPnP"]["enabled"], + "tcp": {}, + "udp": {}, + } + for proto in ["TCP", "UDP"]: + new_data[proto.lower()] = { + port: { + "open": True, + "upnp": port in old_data["uPnP"][proto], + } + for port in set(old_data["ipv4"][proto] + old_data["ipv6"][proto]) + } + yaml.dump(new_data, YunoFirewall.FIREWALL_FILE.open("w", encoding="utf-8")) + + def run(self): + self.firewall_file_migrate() diff --git a/src/tests/test_app_resources.py b/src/tests/test_app_resources.py index 011172fc97..afa0dfab4c 100644 --- a/src/tests/test_app_resources.py +++ b/src/tests/test_app_resources.py @@ -275,17 +275,17 @@ def test_resource_ports_firewall(): r(conf, "testapp").provision_or_update() - assert 12345 not in firewall_list()["opened_ports"] + assert 12345 not in firewall_list(protocol="tcp")["tcp"] conf = {"main": {"default": 12345, "exposed": "TCP"}} r(conf, "testapp").provision_or_update() - assert 12345 in firewall_list()["opened_ports"] + assert 12345 in firewall_list(protocol="tcp")["tcp"] r(conf, "testapp").deprovision() - assert 12345 not in firewall_list()["opened_ports"] + assert 12345 not in firewall_list(protocol="tcp")["tcp"] def test_resource_database(): diff --git a/src/tools.py b/src/tools.py index e3e6a3c3ee..10012b044e 100644 --- a/src/tools.py +++ b/src/tools.py @@ -230,9 +230,9 @@ def tools_postinstall( else: raise YunohostValidationError("dyndns_unavailable", domain=domain) - if os.system("iptables -V >/dev/null 2>/dev/null") != 0: + if os.system("nft -V >/dev/null 2>/dev/null") != 0: raise YunohostValidationError( - "iptables/nftables does not seems to be working on your setup. You may be in a container or your kernel does have the proper modules loaded. Sometimes, rebooting the machine may solve the issue.", + "nftables does not seems to be working on your setup. You may be in a container or your kernel does have the proper modules loaded. Sometimes, rebooting the machine may solve the issue.", raw_msg=True, ) diff --git a/src/utils/resources.py b/src/utils/resources.py index ef96f2e795..5f1ffc1128 100644 --- a/src/utils/resources.py +++ b/src/utils/resources.py @@ -1407,10 +1407,10 @@ def provision_or_update(self, context: Dict = {}): self.set_setting(setting_name, port_value) if infos["exposed"]: - firewall_allow(infos["exposed"], port_value, reload_only_if_change=True) + firewall_allow(infos["exposed"], port_value, reload_if_change=True) else: firewall_disallow( - infos["exposed"], port_value, reload_only_if_change=True + infos["exposed"], port_value, reload_if_change=True ) def deprovision(self, context: Dict = {}): @@ -1422,7 +1422,7 @@ def deprovision(self, context: Dict = {}): self.delete_setting(setting_name) if value and str(value).strip(): firewall_disallow( - infos["exposed"], int(value), reload_only_if_change=True + infos["exposed"], int(value), reload_if_change=True ) From a9e2519d7cfd463c31ad1d2ad52dc7b98bfa91a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 11 Dec 2024 23:34:05 +0100 Subject: [PATCH 02/14] Remove conflict between yunohost-firewall and nftables --- hooks/conf_regen/01-yunohost | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/hooks/conf_regen/01-yunohost b/hooks/conf_regen/01-yunohost index 59bb66bdfe..0fd7abc07b 100755 --- a/hooks/conf_regen/01-yunohost +++ b/hooks/conf_regen/01-yunohost @@ -278,16 +278,6 @@ ConditionVirtualization=!container EOF fi - # Make nftable conflict with yunohost-firewall - mkdir -p ${pending_dir}/etc/systemd/system/nftables.service.d/ - cat > ${pending_dir}/etc/systemd/system/nftables.service.d/ynh-override.conf << EOF -[Unit] -# yunohost-firewall and nftables conflict with each other -Conflicts=yunohost-firewall.service -ConditionFileIsExecutable=!/etc/init.d/yunohost-firewall -ConditionPathExists=!/etc/systemd/system/multi-user.target.wants/yunohost-firewall.service -EOF - # Don't suspend computer on LidSwitch mkdir -p ${pending_dir}/etc/systemd/logind.conf.d/ cat > ${pending_dir}/etc/systemd/logind.conf.d/ynh-override.conf << EOF @@ -361,7 +351,6 @@ do_post_regen() { } fi - [[ ! "$regen_conf_files" =~ "nftables.service.d/ynh-override.conf" ]] || systemctl daemon-reload [[ ! "$regen_conf_files" =~ "login.conf.d/ynh-override.conf" ]] || { systemctl daemon-reload systemctl restart systemd-logind From 2dd68e5761d8905683e2cdd99e1e7218e6e2525c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 11 Dec 2024 20:08:01 +0100 Subject: [PATCH 03/14] Fix mypy: it doesn't detect the proper logging class --- src/firewall.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/firewall.py b/src/firewall.py index 5a0ce6360b..11136fea25 100644 --- a/src/firewall.py +++ b/src/firewall.py @@ -32,7 +32,7 @@ from yunohost.regenconf import regen_conf -logger = getLogger("yunohost.firewall") +logger: Any = getLogger("yunohost.firewall") class YunoFirewall: From a206c6173455b65ac9a8e476ffd343b10e299d99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Mon, 9 Dec 2024 14:49:11 +0100 Subject: [PATCH 04/14] iptables to nftables: Update translations --- locales/ca.json | 9 ++++----- locales/de.json | 7 +++---- locales/en.json | 10 +++++----- locales/eo.json | 7 +++---- locales/es.json | 7 +++---- locales/eu.json | 15 +++++++++++---- locales/fa.json | 7 +++---- locales/fr.json | 9 ++++----- locales/gl.json | 9 ++++----- locales/id.json | 7 +++---- locales/it.json | 7 +++---- locales/ja.json | 7 +++---- locales/nl.json | 4 ++-- locales/oc.json | 7 +++---- locales/pt.json | 2 +- locales/ru.json | 7 +++---- locales/uk.json | 7 +++---- locales/zh_Hans.json | 7 +++---- src/firewall.py | 2 +- 19 files changed, 65 insertions(+), 72 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index f7ed146f9c..5f495b4a5c 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -143,8 +143,7 @@ "hook_list_by_invalid": "Aquesta propietat no es pot utilitzar per llistar els hooks", "hook_name_unknown": "Nom de script « {name} » desconegut", "installation_complete": "Instal·lació completada", - "ip6tables_unavailable": "No podeu modificar les ip6tables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció", - "iptables_unavailable": "No podeu modificar les iptables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció", + "nftables_unavailable": "No podeu modificar les nftables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció", "log_corrupted_md_file": "El fitxer de metadades YAML associat amb els registres està malmès: « {md_file} »\nError: {error}", "log_link_to_log": "El registre complet d'aquesta operació: «{desc}»", "log_help_to_get_log": "Per veure el registre de l'operació « {desc} », utilitzeu l'ordre « yunohost log show {name} »", @@ -203,8 +202,8 @@ "pattern_port_or_range": "Ha de ser un número de port vàlid (i.e. 0-65535) o un interval de ports (ex. 100:200)", "pattern_username": "Ha d'estar compost per caràcters alfanumèrics en minúscula i guió baix exclusivament", "pattern_password_app": "Les contrasenyes no poden de tenir els següents caràcters: {forbidden_chars}", - "port_already_closed": "El port {port} ja està tancat per les connexions {ip_version}", - "port_already_opened": "El port {port} ja està obert per les connexions {ip_version}", + "port_already_closed": "El port {port} ja està tancat", + "port_already_opened": "El port {port} ja està obert", "regenconf_file_backed_up": "S'ha guardat una còpia de seguretat del fitxer de configuració «{conf}» a «{backup}»", "regenconf_file_copy_failed": "No s'ha pogut copiar el nou fitxer de configuració «{new}» a «{conf}»", "regenconf_file_kept_back": "S'espera que el fitxer de configuració «{conf}» sigui suprimit per regen-conf (categoria {category}) però s'ha mantingut.", @@ -749,7 +748,7 @@ "migration_0029_postgresql_15_not_installed": "PostgreSQL 13 està instal·lat, però no PostgreSQL 15!? Alguna cosa estranya podria haver passat al vostre sistema :(…", "service_description_yunohost-portal-api": "Gestiona les interaccions entre les diferents interfícies web del portal d'usuaris i el sistema", "global_settings_setting_tls_passthrough_enabled_help": "Aquesta és una característica avançada de proxy invers d'un domini sencer a una altra màquina *sense* desxifrar el trànsit. Útil quan voleu exposar diverses màquines darrere de la mateixa IP, però encara permeteu que cada màquina gestioni la terminació SSL.", - "global_settings_setting_tls_passthrough_explain": "Aquesta característica és AVANÇADA i EXPERIMENTAL i provocarà canvis importants en la configuració nginx d'aquest servidor. Si us plau, NO l'utilitzeu si no saps què estàs fent! En particular, heu de tenir en compte que fail2ban no es pot implementar al servidor intermediari (iptables no pot prohibir el trànsit maliciós ja que tots els paquets IP semblen procedents del servidor frontal). A més, de moment, la configuració nginx del servidor intermediari s'ha d'ajustar manualment per acceptar el `proxy_protocol`.", + "global_settings_setting_tls_passthrough_explain": "Aquesta característica és AVANÇADA i EXPERIMENTAL i provocarà canvis importants en la configuració nginx d'aquest servidor. Si us plau, NO l'utilitzeu si no saps què estàs fent! En particular, heu de tenir en compte que fail2ban no es pot implementar al servidor intermediari (nftables no pot prohibir el trànsit maliciós ja que tots els paquets IP semblen procedents del servidor frontal). A més, de moment, la configuració nginx del servidor intermediari s'ha d'ajustar manualment per acceptar el `proxy_protocol`.", "migration_0029_postgresql_13_not_installed": "PostgreSQL no s'ha instal·lat al vostre sistema. Res a fer.", "tos_dyndns_acknowledgement": "Heu triat registrar un domini DynDNS que és un servei que ofereix el projecte YunoHost. Tenint en compte que els noms de domini són un aspecte important dels serveis digitals a llarg termini, us recordem que llegiu atentament les Condicions dels Serveis corresponents, en particular la secció relativa a aquests noms de domini gratuïts: .", "migration_0030_rebuild_python_venv_in_bookworm_disclaimer_base": "Després de l'actualització a Debian Bookworm, algunes aplicacions de Python s'han de reconstruir parcialment per convertir-se a la nova versió de Python distribuida amb Debian (en termes tècnics: cal recrear el que s'anomena 'virtualenv'). Mentrestant, és possible que aquestes aplicacions de Python no funcionin. YunoHost pot intentar reconstruir el virtualenv per a alguns d'ells, tal com es detalla a continuació. Per a altres aplicacions, o si l'intent de reconstrucció falla, haureu de forçar manualment una actualització d'aquestes aplicacions.", diff --git a/locales/de.json b/locales/de.json index 8211a57e7a..b9b73f8f59 100644 --- a/locales/de.json +++ b/locales/de.json @@ -55,8 +55,7 @@ "hook_list_by_invalid": "Dieser Wert kann nicht verwendet werden, um Hooks anzuzeigen", "hook_name_unknown": "Hook '{name}' ist nicht bekannt", "installation_complete": "Installation vollständig", - "ip6tables_unavailable": "ip6tables kann nicht verwendet werden. Du befindest dich entweder in einem Container oder es wird nicht vom Kernel unterstützt", - "iptables_unavailable": "iptables kann nicht verwendet werden. Du befindest dich entweder in einem Container oder es wird nicht vom Kernel unterstützt", + "nftables_unavailable": "nftables kann nicht verwendet werden. Du befindest dich entweder in einem Container oder es wird nicht vom Kernel unterstützt", "mail_alias_remove_failed": "Konnte E-Mail-Alias '{mail}' nicht entfernen", "mail_domain_unknown": "Die Domäne '{domain}' dieser E-Mail-Adresse ist ungültig. Wähle bitte eine Domäne, welche durch diesen Server verwaltet wird.", "mail_forward_remove_failed": "Die Weiterleitungs-E-Mail '{mail}' konnte nicht gelöscht werden", @@ -69,8 +68,8 @@ "pattern_password": "Muss mindestens drei Zeichen lang sein", "pattern_port_or_range": "Muss ein valider Port (z.B. 0-65535) oder ein Bereich (z.B. 100:200) sein", "pattern_username": "Darf nur aus klein geschriebenen alphanumerischen Zeichen und Unterstrichen bestehen", - "port_already_closed": "Der Port {port} wurde bereits für {ip_version} Verbindungen geschlossen", - "port_already_opened": "Der Port {port} wird bereits von {ip_version} benutzt", + "port_already_closed": "Der Port {port} wurde bereits geschlossen", + "port_already_opened": "Der Port {port} wird bereits benutzt", "restore_already_installed_app": "Eine Applikation mit der ID '{app}' ist bereits installiert", "restore_cleaning_failed": "Das temporäre Dateiverzeichnis für die Systemwiederherstellung konnte nicht gelöscht werden", "restore_complete": "Vollständig wiederhergestellt", diff --git a/locales/en.json b/locales/en.json index 4dd59bd919..2735b48688 100644 --- a/locales/en.json +++ b/locales/en.json @@ -498,7 +498,7 @@ "global_settings_setting_ssh_port_help": "A port lower than 1024 is preferred to prevent usurpation attempts by non-administrator services on the remote machine. You should also avoid using a port already in use, such as 80 or 443.", "global_settings_setting_tls_passthrough_enabled": "Enable TLS-passthrough / SNI-based forwarding", "global_settings_setting_tls_passthrough_enabled_help": "This is an advanced feature to reverse-proxy an entire domain to another machine *without* decrypting the traffic. Useful when you want to expose several machines behind the same IP but still allow each machine to handle the SSL termination.", - "global_settings_setting_tls_passthrough_explain": "This feature is ADVANCED and EXPERIMENTAL and will trigger major changes in the nginx configuration of this server. Please DO NOT use it if you don't know what you are doing! In particular, you must be aware that fail2ban cannot be implemented on the proxied server (iptables cannot ban malicious traffic as all IP packets appear as coming from the front server). In addition, for now the proxied server's nginx configuration needs to manually tweaked to accept the `proxy_protocol`.", + "global_settings_setting_tls_passthrough_explain": "This feature is ADVANCED and EXPERIMENTAL and will trigger major changes in the nginx configuration of this server. Please DO NOT use it if you don't know what you are doing! In particular, you must be aware that fail2ban cannot be implemented on the proxied server (nftables cannot ban malicious traffic as all IP packets appear as coming from the front server). In addition, for now the proxied server's nginx configuration needs to manually tweaked to accept the `proxy_protocol`.", "global_settings_setting_tls_passthrough_list": "List of forwarding", "global_settings_setting_tls_passthrough_list_help": "Should be a list of DOMAIN;DESTINATION;PORT, such as domain.tld;192.168.1.42;443 or domain.tld;server.local;8123", "global_settings_setting_tls_passthrough_name": "TLS-passthrough / SNI-based forwarding", @@ -544,8 +544,7 @@ "invalid_password": "Invalid password", "invalid_regex": "Invalid regex:'{regex}'", "invalid_shell": "Invalid shell: {shell}", - "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", - "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", + "nftables_unavailable": "You cannot play with nftables here. You are either in a container or your kernel does not support it", "ldap_attribute_already_exists": "LDAP attribute '{attribute}' already exists with value '{value}'", "ldap_server_down": "Unable to reach LDAP server", "ldap_server_is_down_restart_it": "The LDAP service is down, attempt to restart it…", @@ -640,6 +639,7 @@ "migration_description_0029_postgresql_13_to_15": "Migrate databases from PostgreSQL 13 to 15", "migration_description_0030_rebuild_python_venv_in_bookworm": "Repair Python app after bookworm migration", "migration_description_0031_terms_of_services": "Terms of services", + "migration_description_0032_firewall_config": "Internal firewall config file migration", "migration_ldap_backup_before_migration": "Creating a backup of LDAP database and apps settings prior to the actual migration.", "migration_ldap_can_not_backup_before_migration": "The backup of the system could not be completed before the migration failed. Error: {error}", "migration_ldap_migration_failed_trying_to_rollback": "Could not migrate… trying to roll back the system.", @@ -697,8 +697,8 @@ "permission_require_account": "Permission {permission} only makes sense for users having an account, and therefore cannot be enabled for visitors.", "permission_update_failed": "Could not update permission '{permission}': {error}", "permission_updated": "Permission '{permission}' updated", - "port_already_closed": "Port {port} is already closed for {ip_version} connections", - "port_already_opened": "Port {port} is already opened for {ip_version} connections", + "port_already_closed": "Port {port} is already closed", + "port_already_opened": "Port {port} is already opened", "postinstall_low_rootfsspace": "The root filesystem has a total space less than 10 GB, which is quite worrisome! You will likely run out of disk space very quickly! It's recommended to have at least 16GB for the root filesystem. If you want to install YunoHost despite this warning, re-run the postinstall with --force-diskspace", "pydantic_type_error": "Invalid type.", "pydantic_type_error_none_not_allowed": "Value is required.", diff --git a/locales/eo.json b/locales/eo.json index d9701f3e00..17da4b5ca1 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -151,7 +151,6 @@ "permission_already_exist": "Permesita '{permission}' jam ekzistas", "domain_created": "Domajno kreita", "log_user_create": "Aldonu uzanton '{}'", - "ip6tables_unavailable": "Vi ne povas ludi kun ip6tabloj ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin", "mail_unavailable": "Ĉi tiu retpoŝta adreso estas rezervita kaj aŭtomate estos atribuita al la unua uzanto", "certmanager_domain_dns_ip_differs_from_public_ip": "La DNS 'A' rekordo por la domajno '{domain}' diferencas de la IP de ĉi tiu servilo. Se vi lastatempe modifis vian A-registron, bonvolu atendi ĝin propagandi (iuj DNS-disvastigaj kontroliloj estas disponeblaj interrete). (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)", "log_remove_on_failed_install": "Forigu '{}' post malsukcesa instalado", @@ -159,11 +158,11 @@ "regenconf_would_be_updated": "La agordo estus aktualigita por la kategorio '{category}'", "certmanager_cert_install_success_selfsigned": "Mem-subskribita atestilo nun instalita por la domajno '{domain}'", "regenconf_file_backed_up": "Agordodosiero '{conf}' estis rezervita al '{backup}'", - "iptables_unavailable": "Vi ne povas ludi kun iptables ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin", + "nftables_unavailable": "Vi ne povas ludi kun nftables ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin", "service_added": "La servo '{service}' estis aldonita", "upnp_disabled": "UPnP malŝaltis", "service_started": "Servo '{service}' komenciĝis", - "port_already_opened": "Haveno {port} estas jam malfermita por {ip_version} rilatoj", + "port_already_opened": "Haveno {port} estas jam malfermita", "upgrading_packages": "Ĝisdatigi pakojn…", "custom_app_url_required": "Vi devas provizi URL por altgradigi vian kutimon app {app}", "service_reload_failed": "Ne povis reŝargi la servon '{service}'\n\nLastatempaj servaj protokoloj: {logs}", @@ -215,7 +214,7 @@ "upnp_port_open_failed": "Ne povis malfermi havenon per UPnP", "log_app_upgrade": "Ĝisdatigu la aplikon '{}'", "log_help_to_get_failed_log": "La operacio '{desc}' ne povis finiĝi. Bonvolu dividi la plenan ŝtipon de ĉi tiu operacio per la komando 'yunohost log share {name}' por akiri helpon", - "port_already_closed": "Haveno {port} estas jam fermita por {ip_version} rilatoj", + "port_already_closed": "Haveno {port} estas jam fermita", "hook_name_unknown": "Nekonata hoko-nomo '{name}'", "restore_nothings_done": "Nenio estis restarigita", "log_tools_postinstall": "Afiŝu vian servilon YunoHost", diff --git a/locales/es.json b/locales/es.json index 7725258bb0..b18c423cef 100644 --- a/locales/es.json +++ b/locales/es.json @@ -62,8 +62,7 @@ "hook_list_by_invalid": "Esta propiedad no se puede usar para enumerar ganchos («hooks»)", "hook_name_unknown": "Nombre de hook desconocido '{name}'", "installation_complete": "Instalación finalizada", - "ip6tables_unavailable": "No puede modificar ip6tables aquí. O bien está en un 'container' o su kernel no soporta esta opción", - "iptables_unavailable": "No puede modificar iptables aquí. O bien está en un 'container' o su kernel no soporta esta opción", + "nftables_unavailable": "No puede modificar nftables aquí. O bien está en un 'container' o su kernel no soporta esta opción", "mail_alias_remove_failed": "No se pudo eliminar el alias de correo «{mail}»", "mail_domain_unknown": "Dirección de correo no válida para el dominio «{domain}». Use un dominio administrado por este servidor.", "mail_forward_remove_failed": "No se pudo eliminar el reenvío de correo «{mail}»", @@ -77,8 +76,8 @@ "pattern_password": "Debe contener al menos 3 caracteres", "pattern_port_or_range": "Debe ser un número de puerto válido (es decir entre 0-65535) o un intervalo de puertos (por ejemplo 100:200)", "pattern_username": "Solo puede contener caracteres alfanuméricos o el guión bajo", - "port_already_closed": "El puerto {port} ya está cerrado para las conexiones {ip_version}", - "port_already_opened": "El puerto {port} ya está abierto para las conexiones {ip_version}", + "port_already_closed": "El puerto {port} ya está cerrado", + "port_already_opened": "El puerto {port} ya está abierto", "restore_already_installed_app": "Una aplicación con el ID «{app}» ya está instalada", "app_restore_failed": "No se pudo restaurar la aplicación «{app}»: {error}", "restore_cleaning_failed": "No se pudo limpiar el directorio temporal de restauración", diff --git a/locales/eu.json b/locales/eu.json index 018071bff0..b429e9827c 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -271,7 +271,7 @@ "group_updated": "'{group}' taldea eguneratu da", "group_update_failed": "Ezinezkoa izan da '{group}' taldea eguneratzea: {error}", "diagnosis_rootfstotalspace_warning": "'root' fitxategi-sistemak {space} baino ez ditu. Agian ez da arazorik egongo, baina kontuz ibili edo memoriarik gabe gera zaitezke laster… 'root' fitxategi-sistemak gutxienez 16GB erabilgarri izatea da gomendioa.", - "iptables_unavailable": "Ezin dituzu iptaulak hemen moldatu; edukiontzi bat erabiltzen ari zara edo kernelak ez du aukera hau onartzen", + "nftables_unavailable": "Ezin dituzu iptaulak hemen moldatu; edukiontzi bat erabiltzen ari zara edo kernelak ez du aukera hau onartzen", "log_permission_delete": "Ezabatu '{}' baimena", "group_already_exist": "{group} taldea existitzen da lehendik ere", "group_user_not_in_group": "{user} erabiltzailea ez dago {group} taldean", @@ -397,7 +397,6 @@ "installation_complete": "Instalazioa amaitu da", "hook_exec_failed": "Ezinezkoa izan da agindua exekutatzea: {path}", "hook_json_return_error": "Ezin izan da {path} aginduaren erantzuna irakurri. Errorea: {msg}. Jatorrizko edukia: {raw_content}", - "ip6tables_unavailable": "Ezin dituzu ip6taulak hemen moldatu; edukiontzi bat erabiltzen ari zara edo kernelak ez du aukera hau onartzen", "log_link_to_log": "Eragiketa honen erregistro osoa: '{desc}'", "log_operation_unit_unclosed_properly": "Eragiketa ez da modu egokian itxi", "log_backup_restore_app": "Lehengoratu '{}' babeskopia fitxategi bat erabiliz", @@ -508,7 +507,7 @@ "migrations_no_such_migration": "Ez dago '{id}' izeneko migraziorik", "migrations_not_pending_cant_skip": "Migrazio hauek ez daude exekutatzeke eta, beraz, ez dago saihesteko aukerarik: {ids}", "regex_with_only_domain": "Ezin duzu regex domeinuetarako erabili; bideetarako bakarrik", - "port_already_closed": "{port}. ataka itxita dago lehendik ere {ip_version} konexioetarako", + "port_already_closed": "{port}. ataka itxita dago lehendik", "regenconf_file_copy_failed": "Ezinezkoa izan da '{new}' konfigurazio fitxategi berria '{conf}'-(e)n kopiatzea", "regenconf_file_remove_failed": "Ezinezkoa izan da '{conf}' konfigurazio fitxategia ezabatzea", "server_shutdown_confirm": "Zerbitzaria berehala itzaliko da, ziur al zaude? [{answers}]", @@ -560,7 +559,7 @@ "permission_already_up_to_date": "Baimena ez da eguneratu egindako eskaria egungo egoerarekin bat datorrelako.", "permission_protected": "'{permission}' baimena babestuta dago. Ezin duzu bisitarien taldea baimen honetara gehitu / baimen honetatik kendu.", "permission_update_failed": "Ezinezkoa izan da '{permission}' baimena aldatzea: {error}", - "port_already_opened": "{port}. ataka lehendik ere irekita dago {ip_version} konexioetarako", + "port_already_opened": "{port}. ataka lehendik ere irekita", "user_home_creation_failed": "Ezin izan da erabiltzailearentzat '{home}' direktorioa sortu", "user_unknown": "Erabiltzaile ezezaguna: {user}", "yunohost_postinstall_end_tip": "Instalazio ondorengo prozesua amaitu da! Sistemaren konfigurazioa bukatzeko:\n- erabili 'Diagnostikoak' gunea ohiko arazoei aurre hartzeko. Abiarazi administrazio-gunean edo exekutatu 'yunohost diagnosis run';\n- irakurri 'Finalizing your setup' eta 'Getting to know YunoHost' atalak. Dokumentazioan aurki ditzakezu: https://yunohost.org/admindoc.", @@ -764,8 +763,16 @@ "global_settings_setting_tls_passthrough_enabled_help": "Funtzio aurreratua da reverse-proxy erabiliz domeinu oso bat beste makina batera desbideratzeko *trafikoa deszifratu gabe*. Erabilgarria da IP berarekin makina bat baino gehiago zerbitzatu nahi dituzunean, makina bakoitzak dagozkion SSL amaierak kudeatzeko aukera mantenduz.", "global_settings_setting_tls_passthrough_list": "Birbidalketen zerrenda", "global_settings_setting_tls_passthrough_list_help": "Zerrendak DOMEINUA;HELBURUA;ATAKA egitura izan behar du. Adibidez: domeinua.eus;192.168.1.42;443 edo domeinua.eus;zerbitzaria.local;8123", +<<<<<<< HEAD "global_settings_setting_tls_passthrough_explain": "Ezaugarri hau AURRERATUA eta ESPERIMENTALA da, eta aldaketa handiak eragingo ditu zerbitzari honen nginx konfigurazioan. EZ erabili ez badakizu zertan ari zaren! Kontuan izan fail2ban ezin dela inplementatu proxy atzeko zerbitzarian (iptables-ek ezin dute trafiko maltzurra debekatu, IP pakete guztiak zerbitzari nagusitik datozelaren itxura dutelako). Horrez gain, oraingoz, proxy atzeko zerbitzariaren nginx konfigurazioa eskuz moldatu behar da `proxy_protocol` onartzeko.", "confirm_tos_acknowledgement": "Zerbitzu-baldintzak irakurri eta ulertzen ditut [{answers}]", +||||||| parent of 0c756e32 (iptables to nftables: Update translations) + "global_settings_setting_tls_passthrough_explain": "Ezaugarri hau AURRERATUA eta ESPERIMENTALA da, eta aldaketa handiak eragingo ditu zerbitzari honen nginx konfigurazioan. EZ erabili ez badakizu zertan ari zaren! Kontuan izan fail2ban ezin dela inplementatu proxy atzeko zerbitzarian (iptables-ek ezin dute trafiko maltzurra debekatu, IP pakete guztiak zerbitzari nagusitik datozelaren itxura dutelako). Horrez gain, oraingoz, proxy atzeko zerbitzariaren nginx konfigurazioa eskuz moldatu behar da `proxy_protocol` onartzeko.", + "confirm_tos_acknowledgement": "Zerbitzuen Baldintzak irakurri eta ulertzen ditut [{answers}]", +======= + "global_settings_setting_tls_passthrough_explain": "Ezaugarri hau AURRERATUA eta ESPERIMENTALA da, eta aldaketa handiak eragingo ditu zerbitzari honen nginx konfigurazioan. EZ erabili ez badakizu zertan ari zaren! Kontuan izan fail2ban ezin dela inplementatu proxy atzeko zerbitzarian (nftables-ek ezin dute trafiko maltzurra debekatu, IP pakete guztiak zerbitzari nagusitik datozelaren itxura dutelako). Horrez gain, oraingoz, proxy atzeko zerbitzariaren nginx konfigurazioa eskuz moldatu behar da `proxy_protocol` onartzeko.", + "confirm_tos_acknowledgement": "Zerbitzuen Baldintzak irakurri eta ulertzen ditut [{answers}]", +>>>>>>> 0c756e32 (iptables to nftables: Update translations) "domain_config_cert_name": "Ziurtagiria", "domain_config_custom_css": "CSS estilo-orri pertsonalizatua", "domain_config_dns_name": "DNSa", diff --git a/locales/fa.json b/locales/fa.json index a964318da7..dfd4f48055 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -421,8 +421,7 @@ "log_corrupted_md_file": "فایل فوق داده YAML مربوط به گزارش ها آسیب دیده است: '{md_file}\nخطا: {error} '", "ldap_server_is_down_restart_it": "سرویس LDAP خاموش است ، سعی کنید آن را دوباره راه اندازی کنید…", "ldap_server_down": "دسترسی به سرور LDAP امکان پذیر نیست", - "iptables_unavailable": "در اینجا نمی توانید با iptables بازی کنید. شما یا در ظرفی هستید یا هسته شما آن را پشتیبانی نمی کند", - "ip6tables_unavailable": "در اینجا نمی توانید با جدول های ipv6 کار کنید. شما یا در کانتینتر هستید یا هسته شما آن را پشتیبانی نمی کند", + "nftables_unavailable": "در اینجا نمی توانید با nftables بازی کنید. شما یا در ظرفی هستید یا هسته شما آن را پشتیبانی نمی کند", "invalid_regex": "عبارت منظم نامعتبر: '{regex}'", "yunohost_postinstall_end_tip": "پس از نصب کامل شد! برای نهایی کردن تنظیمات خود ، لطفاً موارد زیر را در نظر بگیرید:\n - تشخیص مشکلات احتمالی از طریق بخش \"عیب یابی\" webadmin (یا 'yunohost diagnosis run' در خط فرمان) ؛\n - خواندن قسمت های \"نهایی کردن راه اندازی خود\" و \"آشنایی با YunoHost\" در اسناد مدیریت: https://yunohost.org/admindoc.", "yunohost_not_installed": "YunoHost به درستی نصب نشده است. لطفا 'yunohost tools postinstall' را اجرا کنید", @@ -532,8 +531,8 @@ "regenconf_file_copy_failed": "فایل پیکربندی جدید '{new}' در '{conf}' کپی نشد", "regenconf_file_backed_up": "فایل پیکربندی '{conf}' در '{backup}' پشتیبان گیری شد", "postinstall_low_rootfsspace": "فضای فایل سیستم اصلی کمتر از 10 گیگابایت است که بسیار نگران کننده است! به احتمال زیاد خیلی زود فضای دیسک شما تمام می شود! توصیه می شود حداقل 16 گیگابایت برای سیستم فایل ریشه داشته باشید. اگر می خواهید YunoHost را با وجود این هشدار نصب کنید ، فرمان نصب را مجدد با این آپشن --force-diskspace اجرا کنید", - "port_already_opened": "پورت {port} قبلاً برای اتصالات {ip_version} باز شده است", - "port_already_closed": "پورت {port} قبلاً برای اتصالات {ip_version} بسته شده است", + "port_already_opened": "پورت {port} قبلاً باز است", + "port_already_closed": "درگاه {port} قبلاً بسته شده است", "permission_require_account": "مجوز {permission} فقط برای کاربران دارای حساب کاربری منطقی است و بنابراین نمی تواند برای بازدیدکنندگان فعال شود.", "permission_protected": "مجوز {permission} محافظت می شود. شما نمی توانید گروه بازدیدکنندگان را از/به این مجوز اضافه یا حذف کنید.", "permission_updated": "مجوز '{permission}' به روز شد", diff --git a/locales/fr.json b/locales/fr.json index 384f2bbbf2..6e0c79cab6 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -64,8 +64,7 @@ "hook_list_by_invalid": "Propriété invalide pour lister les actions par celle-ci", "hook_name_unknown": "Nom de l'action '{name}' inconnu", "installation_complete": "Installation terminée", - "ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", - "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", + "nftables_unavailable": "Vous ne pouvez pas jouer avec nftables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", "mail_alias_remove_failed": "Impossible de supprimer l'alias mail '{mail}'", "mail_domain_unknown": "Le domaine '{domain}' de cette adresse email n'est pas valide. Merci d'utiliser un domaine administré par ce serveur.", "mail_forward_remove_failed": "Impossible de supprimer l'email de transfert '{mail}'", @@ -79,8 +78,8 @@ "pattern_password": "Doit être composé d'au moins 3 caractères", "pattern_port_or_range": "Doit être un numéro de port valide compris entre 0 et 65535, ou une gamme de ports (exemple : 100 :200)", "pattern_username": "Doit être composé uniquement de caractères alphanumériques minuscules et de tirets bas (aussi appelé tiret du 8 ou underscore)", - "port_already_closed": "Le port {port} est déjà fermé pour les connexions {ip_version}", - "port_already_opened": "Le port {port} est déjà ouvert pour les connexions {ip_version}", + "port_already_closed": "Le port {port} est déjà fermé", + "port_already_opened": "Le port {port} est déjà ouvert", "restore_already_installed_app": "Une application est déjà installée avec l'identifiant '{app}'", "app_restore_failed": "Impossible de restaurer {app} : {error}", "restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration", @@ -764,7 +763,7 @@ "global_settings_setting_tls_passthrough_list": "Liste des transferts", "global_settings_setting_tls_passthrough_list_help": "Doit être une liste de ce type DOMAIN;DESTINATION;PORT, telle que domaine.tld;192.168.1.42;443 ou domaine.tld;serveur.local;8123", "global_settings_setting_tls_passthrough_enabled": "Activer le flux TLS-passthrough/transfert basé sur les SNI", - "global_settings_setting_tls_passthrough_explain": "Cette fonctionnalité est AVANCÉE et EXPÉRIMENTALE et entraînera des changements majeurs dans la configuration NGINX de votre serveur. Veuillez NE PAS l'utiliser si vous ne savez pas ce que vous faites ! En particulier, vous devez savoir que Fail2Ban ne peut pas être mis en œuvre sur le serveur proxy (iptables ne peut pas bannir le trafic malveillant car tous les paquets IP semblent provenir du serveur principal). De plus, pour l'instant, la configuration NGINX du serveur proxy doit être modifiée manuellement pour accepter le `proxy_protocol`.", + "global_settings_setting_tls_passthrough_explain": "Cette fonctionnalité est AVANCÉE et EXPÉRIMENTALE et entraînera des changements majeurs dans la configuration NGINX de votre serveur. Veuillez NE PAS l'utiliser si vous ne savez pas ce que vous faites ! En particulier, vous devez savoir que Fail2Ban ne peut pas être mis en œuvre sur le serveur proxy (nftables ne peut pas bannir le trafic malveillant car tous les paquets IP semblent provenir du serveur principal). De plus, pour l'instant, la configuration NGINX du serveur proxy doit être modifiée manuellement pour accepter le `proxy_protocol`.", "service_description_opendkim": "Signe les e-mails sortants à l'aide de DKIM afin qu'ils soient moins susceptibles d'être signalés comme spam", "pydantic_value_error_url_extra": "URL non valide, caractères supplémentaires trouvés après une URL valide : '{extra}'", "global_settings_setting_root_access_name": "Changer le mot de passe root", diff --git a/locales/gl.json b/locales/gl.json index 4f83c60900..dce494b4e8 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -317,8 +317,7 @@ "log_help_to_get_log": "Para ver o rexistro completo da operación '{desc}', usa o comando 'yunohost log show {name}'", "log_link_to_log": "Rexistro completo desta operación: '{desc}'", "log_corrupted_md_file": "O ficheiro YAML con metadatos asociado aos rexistros está danado: '{md_file}\nErro: {error}'", - "iptables_unavailable": "Non podes andar remexendo en iptables aquí. Ou ben estás nun contedor ou o teu kernel non ten soporte para isto", - "ip6tables_unavailable": "Non podes remexer en ip6tables aquí. Ou ben estás nun contedor ou o teu kernel non ten soporte para isto", + "nftables_unavailable": "Non podes andar remexendo en nftables aquí. Ou ben estás nun contedor ou o teu kernel non ten soporte para isto", "invalid_regex": "Regex non válido: '{regex}'", "installation_complete": "Instalación completa", "hook_name_unknown": "Nome descoñecido do gancho '{name}'", @@ -430,8 +429,8 @@ "regenconf_file_copy_failed": "Non se puido copiar o novo ficheiro de configuración '{new}' a '{conf}'", "regenconf_file_backed_up": "Ficheiro de configuración '{conf}' copiado a '{backup}'", "postinstall_low_rootfsspace": "O sistema de ficheiros raiz ten un espazo total menor de 10GB, que é pouco! Probablemente vas quedar sen espazo moi pronto! É recomendable ter polo menos 16GB para o sistema raíz. Se queres instalar YunoHost obviando este aviso, volve a executar a postinstalación con --force-diskspace", - "port_already_opened": "O porto {port} xa está aberto para conexións {ip_version}", - "port_already_closed": "O porto {port} xa está pechado para conexións {ip_version}", + "port_already_opened": "O porto {port} xa está aberto", + "port_already_closed": "O porto {port} xa está pechado", "permission_require_account": "O permiso {permission} só ten sentido para usuarias cunha conta, e por tanto non pode concederse a visitantes.", "permission_protected": "O permiso {permission} está protexido. Non podes engadir ou eliminar o grupo visitantes a/de este permiso.", "permission_updated": "Permiso '{permission}' actualizado", @@ -771,7 +770,7 @@ "global_settings_setting_tls_passthrough_list": "Lista de reenvío", "mail_alias_unauthorized": "Non tes autorización para engadir alias para o dominio '{domain}'", "global_settings_setting_tls_passthrough_enabled_help": "Esta característica avanzada para facer proxy-inverso a un dominio completo cara outra máquina *sen* descifrar o tráfico. É útil cando queres expoñer varias máquinas detrás do mesmo IP pero permitir a cada máquina xestionar a súa conexión SSL.", - "global_settings_setting_tls_passthrough_explain": "Esta ferramenta é AVANZADA e EXPERIMENTAL e provoca grandes cambios na configuración de nginx neste servidor. NON A USES se non sabes o que estás a facer! En particular, tes que ter en conta que fail2ban non se pode implementar no servidor adicional (iptables non pode vetar tráfico daniño xa que todos os paquetes parecen proceder do servidor principal). Adicionalmente, por agora o nginx do servidor engadido ten que modificarse manualmente para aceptar o `proxy_protocol`.", + "global_settings_setting_tls_passthrough_explain": "Esta ferramenta é AVANZADA e EXPERIMENTAL e provoca grandes cambios na configuración de nginx neste servidor. NON A USES se non sabes o que estás a facer! En particular, tes que ter en conta que fail2ban non se pode implementar no servidor adicional (nftables non pode vetar tráfico daniño xa que todos os paquetes parecen proceder do servidor principal). Adicionalmente, por agora o nginx do servidor engadido ten que modificarse manualmente para aceptar o `proxy_protocol`.", "global_settings_setting_tls_passthrough_list_help": "Debería ser unha lista tipo DOMINIO;DESTINO;PORTO, como domain.tld;192.168.1.42;443 ou dominio.tld;server.local;8123", "mail_already_exists": "Xa existe o enderezo '{mail}'", "migration_0029_not_enough_space": "Ter espazo suficiente en {path} para realizar a migración.", diff --git a/locales/id.json b/locales/id.json index d199ba1056..71accb633f 100644 --- a/locales/id.json +++ b/locales/id.json @@ -300,7 +300,7 @@ "domain_config_cert_renew": "Perbarui sertifikat Let's Encrypt", "domain_config_cert_summary": "Status sertifikat", "domain_config_cert_summary_expired": "PENTING: Sertifikat saat ini tidak valid! HTTPS tidak akan bekerja sama sekali!", - "port_already_opened": "Porta {port} telah dibuka untuk koneksi {ip_version}", + "port_already_opened": "Porta {port} telah dibuka", "migrations_success_forward": "Migrasi {id} selesai", "not_enough_disk_space": "Ruang kosong tidak cukup di '{path}'", "password_too_long": "Pilih kata sandi yang lebih pendek dari 127 karakter", @@ -326,7 +326,7 @@ "password_too_simple_2": "Kata sandi harus terdiri dari minimal 8 karakter dan berisi karakter angka, besar, dan kecil", "password_too_simple_3": "Kata sandi harus terdiri dari minimal 8 karakter dan berisi karakter angka, besar, kecil dan khusus", "password_too_simple_4": "Panjang kata sandi harus paling tidak 12 karakter dan mengandung digit, huruf kapital, huruf kecil, dan karakter khusus", - "port_already_closed": "Porta {port} telah ditutup untuk koneksi {ip_version}", + "port_already_closed": "Porta {port} telah ditutup", "service_description_yunomdns": "Membuat Anda bisa menemukan peladen Anda menggunakan 'yunohost.local' di jaringan lokal Anda", "regenconf_file_copy_failed": "Tidak dapat menyalin berkas konfigurasi baru '{new}' ke '{conf}'", "regenconf_file_kept_back": "Berkas konfigurasi '{conf}' seharusnya dihapus oleh regen-conf (kategori {category}) tapi tidak jadi.", @@ -615,7 +615,7 @@ "global_settings_setting_dns_exposure_help": "NB: Ini hanya mempengaruhi konfigurasi DNS yang disarankan dan pemeriksaan diagnosis. Ini tidak mempengaruhi konfigurasi sistem.", "global_settings_setting_nginx_compatibility": "Kompatibilitas NGINX", "global_settings_setting_security_experimental_enabled": "Fitur keamanan eksperimental", - "iptables_unavailable": "Anda tidak dapat menggunakan iptables di sini. Anda berada dalam sebuah penampungan atau kernel Anda yang tidak mendukungnya", + "nftables_unavailable": "Anda tidak dapat menggunakan nftables di sini. Anda berada dalam sebuah penampungan atau kernel Anda yang tidak mendukungnya", "ldap_attribute_already_exists": "Atribut LDAP '{attribute}' sudah ada dengan nilai '{value}'", "log_does_exists": "Tidak ada log operasi dengan nama '{log}', gunakan 'yunohost log list' untuk melihat semua log operasi yang tersedia", "log_dyndns_unsubscribe": "Berhenti berlangganan subdomain YunoHost '{}'", @@ -696,7 +696,6 @@ "global_settings_setting_webadmin_allowlist_help": "Alamat IP diizinkan untuk mengakses webadmin. Notasi CIDR diperbolehkan.", "global_settings_setting_ssh_port_help": "Port kurang dari 1024 lebih dianjurkan untuk mencegah upaya kudeta oleh layanan non-administrator pada mesin jarak jauh. Anda juga sebaiknya menghindari penggunaan port yang sudah digunakan, seperti 80 atau 443.", "invalid_regex": "Regex tidak valid:'{regex}'", - "ip6tables_unavailable": "Anda tidak dapat menggunakan ip6tables di sini. Anda berada dalam sebuah penampungan atau kernel Anda yang tidak mendukungnya", "global_settings_setting_ssh_compatibility_help": "Kompatibilitas versus kompromi keamanan untuk server SSH. Mempengaruhi kerahasiaan (dan aspek terkait keamanan lainnya). Lihat https://infosec.mozilla.org/guidelines/openssh untuk informasi lebih lanjut.", "global_settings_setting_webadmin_allowlist": "Daftar IP pengelola web yang diizinkan", "global_settings_setting_webadmin_allowlist_enabled_help": "Izinkan hanya beberapa IP untuk mengakses webadmin.", diff --git a/locales/it.json b/locales/it.json index a1ba813ece..73a640acc9 100644 --- a/locales/it.json +++ b/locales/it.json @@ -11,7 +11,7 @@ "domain_exists": "Il dominio esiste già", "pattern_email": "L'indirizzo email deve essere valido, senza simboli '+' (es. tizio@dominio.com)", "pattern_mailbox_quota": "La dimensione deve avere un suffisso b/k/M/G/T o 0 per disattivare la quota", - "port_already_opened": "La porta {port} è già aperta per {ip_version} connessioni", + "port_already_opened": "La porta {port} è già aperta", "service_add_failed": "Impossibile aggiungere il servizio '{service}'", "service_cmd_exec_failed": "Impossibile eseguire il comando '{command}'", "service_disabled": "Il servizio '{service}' non partirà più al boot di sistema.", @@ -76,8 +76,7 @@ "hook_exec_not_terminated": "Los script non è stato eseguito correttamente: {path}", "hook_name_unknown": "Nome di hook '{name}' sconosciuto", "installation_complete": "Installazione completata", - "ip6tables_unavailable": "Non puoi giocare con ip6tables qui. O sei in un container o il tuo kernel non lo supporta", - "iptables_unavailable": "Non puoi giocare con iptables qui. O sei in un container o il tuo kernel non lo supporta", + "nftables_unavailable": "Non puoi giocare con nftables qui. O sei in un container o il tuo kernel non lo supporta", "mail_alias_remove_failed": "Impossibile rimuovere l'alias mail '{mail}'", "mail_domain_unknown": "Indirizzo mail non valido per il dominio '{domain}'. Usa un dominio gestito da questo server.", "mail_forward_remove_failed": "Impossibile rimuovere la mail inoltrata '{mail}'", @@ -90,7 +89,7 @@ "pattern_password": "Deve contenere almeno 3 caratteri", "pattern_port_or_range": "Deve essere un numero di porta valido (es. 0-65535) o una fascia di porte valida (es. 100:200)", "pattern_username": "Caratteri minuscoli alfanumerici o trattini bassi soli", - "port_already_closed": "La porta {port} è già chiusa per le connessioni {ip_version}", + "port_already_closed": "La porta {port} è già chiusa", "restore_already_installed_app": "Un'applicazione con l'ID '{app}' è già installata", "app_restore_failed": "Impossibile ripristinare l'applicazione '{app}': {error}", "restore_cleaning_failed": "Impossibile pulire la directory temporanea di ripristino", diff --git a/locales/ja.json b/locales/ja.json index 0a35c50808..989c9df6a0 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -494,7 +494,6 @@ "group_already_exist_on_system_but_removing_it": "グループ{group}はすでにシステムグループに存在しますが、YunoHostはそれを削除します…", "group_cannot_edit_all_users": "グループ 'all_users' は手動で編集できません。これは、YunoHostに登録されているすべてのユーザーを含むことを目的とした特別なグループです", "invalid_shell": "無効なシェル: {shell}", - "ip6tables_unavailable": "ここではip6tablesを使うことはできません。あなたはコンテナ内にいるか、カーネルがサポートしていません", "group_cannot_edit_primary_group": "グループ '{group}' を手動で編集することはできません。これは、特定のユーザーを 1 人だけ含むためのプライマリ グループです。", "group_cannot_edit_visitors": "グループの'訪問者'を手動で編集することはできません。匿名の訪問者を代表する特別なグループです", "group_creation_failed": "グループ '{group}' を作成できませんでした: {error}", @@ -511,7 +510,7 @@ "invalid_credentials": "無効なパスワードまたはユーザー名", "invalid_number": "数値にする必要があります", "invalid_regex": "無効な正規表現: '{regex}'", - "iptables_unavailable": "ここではiptablesを使うことはできません。あなたはコンテナ内にいるか、カーネルがサポートしていません", + "nftables_unavailable": "ここではnftablesを使うことはできません。あなたはコンテナ内にいるか、カーネルがサポートしていません", "ldap_attribute_already_exists": "LDAP 属性 '{attribute}' は、値 '{value}' で既に存在します。", "ldap_server_down": "LDAP サーバーに到達できません", "ldap_server_is_down_restart_it": "LDAP サービスがダウンしています。再起動を試みます…", @@ -618,8 +617,8 @@ "permission_protected": "アクセス許可{permission}は保護されています。このアクセス許可に対して訪問者グループを追加または削除することはできません。", "permission_require_account": "権限{permission}は、アカウントを持つユーザーに対してのみ意味があるため、訪問者に対して有効にすることはできません。", "permission_update_failed": "アクセス許可 '{permission}' を更新できませんでした: {error}", - "port_already_closed": "ポート {port} は既に{ip_version}接続のために閉じられています", - "port_already_opened": "ポート {port} は既に{ip_version}接続用に開かれています", + "port_already_closed": "ポート {port} はすでに閉じられている", + "port_already_opened": "ポート {port} はすでに開いている", "postinstall_low_rootfsspace": "ルートファイルシステムの総容量は10GB未満で、かなり気になります。ディスク容量がすぐに不足する可能性があります。ルートファイルシステム用に少なくとも16GBを用意することをお勧めします。この警告にもかかわらずYunoHostをインストールする場合は、--force-diskspaceを使用してポストインストールを再実行してください", "regenconf_dry_pending_applying": "カテゴリ '{category}' に適用された保留中の構成を確認しています…", "regenconf_failed": "カテゴリの設定を再生成できませんでした: {categories}", diff --git a/locales/nl.json b/locales/nl.json index 2a2af25ad5..265301e9e6 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -37,8 +37,8 @@ "pattern_email": "Moet een geldig e-mailadres bevatten, zonder '+' symbool er in (bv. abc@example.org)", "pattern_mailbox_quota": "Mailbox quota moet een waarde bevatten met b/k/M/G/T erachter of 0 om geen quota in te stellen", "pattern_password": "Wachtwoord moet tenminste 3 karakters lang zijn", - "port_already_closed": "Poort {port} is al gesloten voor {ip_version} verbindingen", - "port_already_opened": "Poort {port} is al open voor {ip_version} verbindingen", + "port_already_closed": "Poort {port} is al gesloten", + "port_already_opened": "Poort {port} is al open", "app_restore_failed": "De app '{app}' kon niet worden terug gezet: {error}", "restore_hook_unavailable": "De herstel-hook '{part}' is niet beschikbaar op dit systeem", "service_add_failed": "Kan service '{service}' niet toevoegen", diff --git a/locales/oc.json b/locales/oc.json index 5a1fe12dee..070a8e801e 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -119,8 +119,8 @@ "pattern_email": "Deu èsser una adreça electronica valida (ex : escais@domeni.org)", "pattern_password": "Deu conténer almens 3 caractèrs", "pattern_port_or_range": "Deu èsser un numèro de pòrt valid (ex : 0-65535) o un interval de pòrt (ex : 100:200)", - "port_already_closed": "Lo pòrt {port} es ja tampat per las connexions {ip_version}", - "port_already_opened": "Lo pòrt {port} es ja dubèrt per las connexions {ip_version}", + "port_already_closed": "Lo pòrt {port} es ja tampat", + "port_already_opened": "Lo pòrt {port} es ja dubèrt", "restore_already_installed_app": "Una aplicacion es ja installada amb l’id « {app} »", "app_restore_failed": "Impossible de restaurar l’aplicacion « {app} »: {error}", "backup_ask_for_copying_if_needed": "Volètz far una salvagarda en utilizant {size} Mo temporàriament ? (Aqueste biais de far es emplegat perque unes fichièrs an pas pogut èsser preparats amb un metòde mai eficaç.)", @@ -196,8 +196,7 @@ "service_description_ssh": "vos permet de vos connectar a distància a vòstre servidor via un teminal (protocòl SSH)", "service_description_yunohost-api": "permet las interaccions entre l’interfàcia web de YunoHost e le sistèma", "service_description_yunohost-firewall": "gerís los pòrts de connexion dobèrts e tampats als servicis", - "ip6tables_unavailable": "Podètz pas jogar amb ip6tables aquí. Siá sèts dins un contenedor, siá vòstre nuclèu es pas compatible amb aquela opcion", - "iptables_unavailable": "Podètz pas jogar amb iptables aquí. Siá sèts dins un contenedor, siá vòstre nuclèu es pas compatible amb aquela opcion", + "nftables_unavailable": "Podètz pas jogar amb nftables aquí. Siá sèts dins un contenedor, siá vòstre nuclèu es pas compatible amb aquela opcion", "mail_alias_remove_failed": "Supression impossibla de l’alias de corrièl « {mail} »", "mail_forward_remove_failed": "Supression impossibla del corrièl de transferiment « {mail} »", "migrations_migration_has_failed": "La migracion {id} a pas capitat, abandon. Error : {exception}", diff --git a/locales/pt.json b/locales/pt.json index c2e1cfe945..a8a7adcb2c 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -34,7 +34,7 @@ "field_invalid": "Campo inválido '{}'", "firewall_reloaded": "Firewall recarregada com êxito", "installation_complete": "Instalação concluída", - "iptables_unavailable": "Não pode alterar aqui a iptables. Ou o seu kernel não o suporta ou está num espaço reservado", + "nftables_unavailable": "Não pode alterar aqui a nftables. Ou o seu kernel não o suporta ou está num espaço reservado", "mail_alias_remove_failed": "Não foi possível remover a etiqueta de correio '{mail}'", "mail_domain_unknown": "Domínio de endereço de correio '{domain}' inválido. Por favor, usa um domínio administrado per esse servidor.", "mail_forward_remove_failed": "Não foi possível remover o reencaminhamento de correio '{mail}'", diff --git a/locales/ru.json b/locales/ru.json index 50d280bac8..18e0f2b3a9 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -228,7 +228,7 @@ "regenconf_file_updated": "Файл конфигурации '{conf}' обновлен", "regenconf_now_managed_by_yunohost": "Конфигурационный файл '{conf}' теперь управляется YunoHost (категория {category}).", "migrations_to_be_ran_manually": "Миграция {id} должна быть запущена вручную. Пожалуйста, перейдите в раздел Инструменты → Миграции на вэб-странице администратора или выполните команду `yunohost tools migrations run`.", - "port_already_opened": "Порт {port} уже открыт для {ip_version} подключений", + "port_already_opened": "Порт {port} уже открыт", "postinstall_low_rootfsspace": "Общий размер корневой файловой системы составляет менее 10 ГБ, что вызывает беспокойство! Скорее всего, свободное место очень быстро закончится! Рекомендуется иметь не менее 16 ГБ для корневой файловой системы. Если вы хотите установить YunoHost, несмотря на это предупреждение, повторно запустите пост-установку с параметром --force-diskspace", "diagnosis_services_running": "Служба {service} запущена!", "diagnosis_swap_none": "Система вообще не имеет свопа. Вы должны рассмотреть возможность добавления по крайней мере {recommended} объема подкачки, чтобы избежать ситуаций, когда в системе заканчивается память.", @@ -279,8 +279,7 @@ "diagnosis_services_conf_broken": "Конфигурация нарушена для службы {service}!", "diagnosis_sshd_config_inconsistent": "Похоже, что порт SSH был вручную изменен в /etc/ssh/sshd_config. Начиная с версии YunoHost 4.2, доступен новый глобальный параметр 'security.ssh.port', позволяющий избежать ручного редактирования конфигурации.", "hook_exec_not_terminated": "Скрипт не завершился должным образом: {path}", - "ip6tables_unavailable": "Вы не можете играть с ip6tables здесь. Либо Вы находитесь в контейнере, либо ваше ядро это не поддерживает", - "iptables_unavailable": "Вы не можете играть с ip6tables здесь. Либо Вы находитесь в контейнере, либо ваше ядро это не поддерживает", + "nftables_unavailable": "Вы не можете играть с nftables здесь. Либо Вы находитесь в контейнере, либо ваше ядро это не поддерживает", "log_corrupted_md_file": "Файл метаданных YAML, связанный с логами, поврежден: '{md_file}\nОшибка: {error}'", "log_does_exists": "Нет логов с именем '{log}', используйте 'yunohost log list' для просмотра всех доступных логов", "log_app_change_url": "Измените URL приложения '{}'", @@ -292,7 +291,7 @@ "pattern_email": "Должен быть правильный адрес электронной почты, без символа \"+\" (например, someone@example.com)", "pattern_port_or_range": "Должен быть корректный номер порта (т.е. 0-65535) или диапазон портов (например, 100:200)", "pattern_password_app": "Извините, пароли не могут содержать следующие символы: {forbidden_chars}", - "port_already_closed": "Порт {port} уже закрыт для подключений {ip_version}", + "port_already_closed": "Порт {port} уже закрыт", "user_update_failed": "Не удалось обновить пользователя {user}: {error}", "migrations_success_forward": "Миграция {id} завершена", "pattern_mailbox_quota": "Должен быть размер с суффиксом b/k/M/G/T или 0, что значит без ограничений", diff --git a/locales/uk.json b/locales/uk.json index a4148600a1..6edca06969 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -95,8 +95,8 @@ "regenconf_file_copy_failed": "Не вдалося скопіювати новий файл конфігурації '{new}' в '{conf}'", "regenconf_file_backed_up": "Конфігураційний файл '{conf}' збережено в '{backup}'", "postinstall_low_rootfsspace": "Загальне місце кореневої файлової системи становить менше 10 ГБ, що викликає занепокоєння! Швидше за все, дисковий простір закінчиться дуже скоро! Рекомендовано мати не менше 16 ГБ для кореневої файлової системи. Якщо ви хочете встановити YunoHost попри це попередження, повторно запустіть післявстановлення з параметром --force-diskspace", - "port_already_opened": "Порт {port} вже відкрито для з'єднань {ip_version}", - "port_already_closed": "Порт {port} вже закрито для з'єднань {ip_version}", + "port_already_opened": "Порт {port} вже відкрито", + "port_already_closed": "Порт {port} вже закрито", "permission_require_account": "Дозвіл {permission} має зміст тільки для користувачів, що мають обліковий запис, і тому не може бути увімкненим для відвідувачів.", "permission_protected": "Дозвіл {permission} захищено. Ви не можете додавати або вилучати групу відвідувачів до/з цього дозволу.", "permission_updated": "Дозвіл '{permission}' оновлено", @@ -202,8 +202,7 @@ "log_help_to_get_log": "Щоб переглянути журнал операції '{desc}', використовуйте команду 'yunohost log show {name}'", "log_link_to_log": "Повний журнал цієї операції: '{desc}'", "log_corrupted_md_file": "Файл метаданих YAML, пов'язаний з журналами, пошкоджено: '{md_file}\nПомилка: {error}'", - "iptables_unavailable": "Ви не можете відтворювати з iptables тут. Ви перебуваєте або в контейнері, або ваше ядро не підтримує його", - "ip6tables_unavailable": "Ви не можете відтворювати з ip6tables тут. Ви перебуваєте або в контейнері, або ваше ядро не підтримує його", + "nftables_unavailable": "Ви не можете відтворювати з nftables тут. Ви перебуваєте або в контейнері, або ваше ядро не підтримує його", "invalid_regex": "Неприпустимий regex: '{regex}'", "installation_complete": "Установлення завершено", "hook_name_unknown": "Невідома назва хука '{name}'", diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index dc89c574da..d367c8af2d 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -149,8 +149,8 @@ "app_action_cannot_be_ran_because_required_services_down": "这些必需的服务应该正在运行以执行以下操作:{services},尝试重新启动它们以继续操作(考虑调查为什么它们出现故障)。", "already_up_to_date": "无事可做。一切都已经是最新的了。", "postinstall_low_rootfsspace": "根文件系统的总空间小于10 GB,这非常令人担忧!您可能很快就会用完磁盘空间!建议根文件系统至少有16GB, 如果尽管出现此警告仍要安装YunoHost,请使用--force-diskspace重新运行postinstall", - "port_already_opened": "{ip_version}个连接的端口 {port} 已打开", - "port_already_closed": "{ip_version}个连接的端口 {port} 已关闭", + "port_already_opened": "端口 {port} 已打开", + "port_already_closed": "端口 {port} 已关闭", "permission_require_account": "权限{permission}只对有账户的用户有意义,因此不能对访客启用。", "permission_protected": "权限{permission}是受保护的。您不能向/从这个权限添加或删除访问者组。", "permission_updated": "权限 '{permission}' 已更新", @@ -455,8 +455,7 @@ "log_help_to_get_log": "要查看操作'{desc}'的日志,请使用命令'yunohost log show {name}'", "log_link_to_log": "此操作的完整日志: '{desc}'", "log_corrupted_md_file": "与日志关联的YAML元数据文件已损坏: '{md_file}\n错误: {error}'", - "iptables_unavailable": "您不能在这里使用iptables。您要么在一个容器中,要么您的内核不支持它", - "ip6tables_unavailable": "您不能在这里使用ip6tables。您要么在一个容器中,要么您的内核不支持它", + "nftables_unavailable": "您不能在这里使用nftables。您要么在一个容器中,要么您的内核不支持它", "log_regen_conf": "重新生成系统配置'{}'", "log_letsencrypt_cert_renew": "续订'{}'的“Let's Encrypt”证书", "log_selfsigned_cert_install": "在 '{}'域上安装自签名证书", diff --git a/src/firewall.py b/src/firewall.py index 11136fea25..094df1ad8f 100644 --- a/src/firewall.py +++ b/src/firewall.py @@ -471,7 +471,7 @@ def firewall_stop() -> None: Stop nftables """ if os.system("nft list ruleset") != 0: - raise YunohostError("iptables_unavailable") + raise YunohostError("nftables_unavailable") YunoFirewall().clear() From df4e759d8a8ea581b05639e16b45d4078a297f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Mon, 9 Dec 2024 14:49:45 +0100 Subject: [PATCH 05/14] Use nftables instead of iptables for fail2ban --- conf/fail2ban/jail.conf | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/conf/fail2ban/jail.conf b/conf/fail2ban/jail.conf index 8b6a2ec6db..8d12bb70f2 100644 --- a/conf/fail2ban/jail.conf +++ b/conf/fail2ban/jail.conf @@ -44,19 +44,19 @@ before = paths-debian.conf # MISCELLANEOUS OPTIONS # -# "bantime.increment" allows to use database for searching of previously banned ip's to increase a +# "bantime.increment" allows to use database for searching of previously banned ip's to increase a # default ban time using special formula, default it is banTime * 1, 2, 4, 8, 16, 32... #bantime.increment = true -# "bantime.rndtime" is the max number of seconds using for mixing with random time +# "bantime.rndtime" is the max number of seconds using for mixing with random time # to prevent "clever" botnets calculate exact time IP can be unbanned again: -#bantime.rndtime = +#bantime.rndtime = # "bantime.maxtime" is the max number of seconds using the ban time can reach (doesn't grow further) -#bantime.maxtime = +#bantime.maxtime = # "bantime.factor" is a coefficient to calculate exponent growing of the formula or common multiplier, -# default value of factor is 1 and with default value of formula, the ban time +# default value of factor is 1 and with default value of formula, the ban time # grows by 1, 2, 4, 8, 16 ... #bantime.factor = 1 @@ -69,14 +69,14 @@ before = paths-debian.conf # "bantime.multipliers" used to calculate next value of ban time instead of formula, corresponding # previously ban count and given "bantime.factor" (for multipliers default is 1); -# following example grows ban time by 1, 2, 4, 8, 16 ... and if last ban count greater as multipliers count, +# following example grows ban time by 1, 2, 4, 8, 16 ... and if last ban count greater as multipliers count, # always used last multiplier (64 in example), for factor '1' and original ban time 600 - 10.6 hours #bantime.multipliers = 1 2 4 8 16 32 64 # following example can be used for small initial ban time (bantime=60) - it grows more aggressive at begin, # for bantime=60 the multipliers are minutes and equal: 1 min, 5 min, 30 min, 1 hour, 5 hour, 12 hour, 1 day, 2 day #bantime.multipliers = 1 5 30 60 300 720 1440 2880 -# "bantime.overalljails" (if true) specifies the search of IP in the database will be executed +# "bantime.overalljails" (if true) specifies the search of IP in the database will be executed # cross over all jails, if false (default), only current jail of the ban IP will be searched #bantime.overalljails = false @@ -205,8 +205,8 @@ fail2ban_agent = Fail2Ban/%(fail2ban_version)s # iptables-multiport, shorewall, etc) It is used to define # action_* variables. Can be overridden globally or per # section within jail.local file -banaction = iptables-multiport -banaction_allports = iptables-allports +banaction = nftables-multiport +banaction_allports = nftables-allports # The simplest action to take: ban only action_ = %(banaction)s[port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] @@ -242,11 +242,11 @@ action_cf_mwl = cloudflare[cfuser="%(cfemail)s", cftoken="%(cfapikey)s"] %(mta)s-whois-lines[sender="%(sender)s", dest="%(destemail)s", logpath="%(logpath)s", chain="%(chain)s"] # Report block via blocklist.de fail2ban reporting service API -# +# # See the IMPORTANT note in action.d/blocklist_de.conf for when to use this action. # Specify expected parameters in file action.d/blocklist_de.local or if the interpolation # `action_blocklist_de` used for the action, set value of `blocklist_de_apikey` -# in your `jail.local` globally (section [DEFAULT]) or per specific jail section (resp. in +# in your `jail.local` globally (section [DEFAULT]) or per specific jail section (resp. in # corresponding jail.d/my-jail.local file). # action_blocklist_de = blocklist_de[email="%(sender)s", service="%(__name__)s", apikey="%(blocklist_de_apikey)s", agent="%(fail2ban_agent)s"] @@ -378,7 +378,7 @@ logpath = /opt/openhab/logs/request.log port = http,https logpath = %(nginx_error_log)s -# To use 'nginx-limit-req' jail you should have `ngx_http_limit_req_module` +# To use 'nginx-limit-req' jail you should have `ngx_http_limit_req_module` # and define `limit_req` and `limit_req_zone` as described in nginx documentation # http://nginx.org/en/docs/http/ngx_http_limit_req_module.html # or for example see in 'config/filter.d/nginx-limit-req.conf' @@ -839,7 +839,7 @@ backend = %(syslog_backend)s [xinetd-fail] -banaction = iptables-multiport-log +banaction = nftables-multiport-log logpath = %(syslog_daemon)s backend = %(syslog_backend)s maxretry = 2 From 8b0425d2b3a97ddae684fe0238d5ce42c5984064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Mon, 9 Dec 2024 21:27:20 +0100 Subject: [PATCH 06/14] Add nftables config and conf-regen --- conf/nftables/nftables.conf | 20 ++++++ conf/nftables/nftables.d/fail2ban.conf | 1 + .../nftables.d/yunohost-firewall.tpl.conf | 21 ++++++ debian/control | 3 +- hooks/conf_regen/01-yunohost | 4 ++ hooks/conf_regen/40-nftables | 69 +++++++++++++++++++ 6 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 conf/nftables/nftables.conf create mode 100644 conf/nftables/nftables.d/fail2ban.conf create mode 100644 conf/nftables/nftables.d/yunohost-firewall.tpl.conf create mode 100644 hooks/conf_regen/40-nftables diff --git a/conf/nftables/nftables.conf b/conf/nftables/nftables.conf new file mode 100644 index 0000000000..5483be1ffe --- /dev/null +++ b/conf/nftables/nftables.conf @@ -0,0 +1,20 @@ +#!/usr/sbin/nft -f + +flush ruleset + +table inet filter { + chain input { + type filter hook input priority filter; + } + chain forward { + type filter hook forward priority filter; + } + chain output { + type filter hook output priority filter; + } +} + +## Above is the standard nftables.conf +## Below is to include YunoHost configuration + +include "/etc/nftables.d/*.conf" diff --git a/conf/nftables/nftables.d/fail2ban.conf b/conf/nftables/nftables.d/fail2ban.conf new file mode 100644 index 0000000000..f13fc73d9c --- /dev/null +++ b/conf/nftables/nftables.d/fail2ban.conf @@ -0,0 +1 @@ +# FIXME: diff --git a/conf/nftables/nftables.d/yunohost-firewall.tpl.conf b/conf/nftables/nftables.d/yunohost-firewall.tpl.conf new file mode 100644 index 0000000000..3cbf9f2604 --- /dev/null +++ b/conf/nftables/nftables.d/yunohost-firewall.tpl.conf @@ -0,0 +1,21 @@ +#!/usr/sbin/nft -f + +{% set tcp_ports = tcp_ports.strip().split(' ') -%} +{% set udp_ports = udp_ports.strip().split(' ') -%} + +table inet filter { + chain input { + ct state related,established counter accept; + + {% for port in tcp_ports %} + tcp dport {{port}} counter accept; + {%- endfor %} + + {% for port in udp_ports %} + udp dport {{port}} counter accept; + {%- endfor %} + + iifname "lo" counter accept; + ip protocol icmp counter accept; + } +} diff --git a/debian/control b/debian/control index 5eced681f0..d101501300 100644 --- a/debian/control +++ b/debian/control @@ -34,8 +34,7 @@ Recommends: yunohost-admin, yunohost-portal (>= 12.0) , bash-completion, rsyslog , unattended-upgrades , libdbd-ldap-perl, libnet-dns-perl -Conflicts: iptables-persistent - , apache2 +Conflicts: apache2 , bind9 , openresolv , systemd-resolved diff --git a/hooks/conf_regen/01-yunohost b/hooks/conf_regen/01-yunohost index 0fd7abc07b..24c94b6d36 100755 --- a/hooks/conf_regen/01-yunohost +++ b/hooks/conf_regen/01-yunohost @@ -278,6 +278,10 @@ ConditionVirtualization=!container EOF fi + # Delete legacy conflict between yunohost and nftables + mkdir -p ${pending_dir}/etc/systemd/system/nftables.service.d/ + touch ${pending_dir}/etc/systemd/system/nftables.service.d/ynh-override.conf + # Don't suspend computer on LidSwitch mkdir -p ${pending_dir}/etc/systemd/logind.conf.d/ cat > ${pending_dir}/etc/systemd/logind.conf.d/ynh-override.conf << EOF diff --git a/hooks/conf_regen/40-nftables b/hooks/conf_regen/40-nftables new file mode 100644 index 0000000000..f73ba32f71 --- /dev/null +++ b/hooks/conf_regen/40-nftables @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2024 YunoHost Contributors +# +# This file is part of YunoHost (see https://yunohost.org) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +set -e + +. /usr/share/yunohost/helpers + + +PY_LIST_PORTS_OF=" +import os +import yaml +file = os.environ['FILE'] +proto = os.environ['PROTO'] +data = yaml.safe_load(open(file, 'r')) +ports = [str(port) for port, info in data[proto].items() if info['open']] +print(' '.join(ports)) +" + +do_pre_regen() { + pending_dir=$1 + + firewall_file="/etc/yunohost/firewall.yml" + + tcp_ports=$(FILE=$firewall_file PROTO=tcp python3 -c "$PY_LIST_PORTS_OF") + udp_ports=$(FILE=$firewall_file PROTO=udp python3 -c "$PY_LIST_PORTS_OF") + export tcp_ports udp_ports + + # # Support different strategy for security configurations + # export compatibility="$(jq -r '.ssh_compatibility' <<< "$YNH_SETTINGS")" + # export port="$(jq -r '.ssh_port' <<< "$YNH_SETTINGS")" + # export password_authentication="$(jq -r '.ssh_password_authentication' <<< "$YNH_SETTINGS" | int_to_bool)" + # export ssh_keys=$(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key 2> /dev/null || true) + + cd /usr/share/yunohost/conf/nftables + mkdir -p "${pending_dir}/etc/nftables.d" + cp nftables.conf "${pending_dir}/etc/nftables.conf" + ynh_render_template nftables.d/yunohost-firewall.tpl.conf "${pending_dir}/etc/nftables.d/yunohost-firewall.conf" +} + +do_post_regen() { + regen_conf_files=$1 + + if ls -l /etc/nftables.d/*.conf; then + chown root:root /etc/nftables.d/*.conf + chmod 644 /etc/nftables.d/*.conf + fi + + [[ -z "$regen_conf_files" ]] \ + || systemctl reload-or-restart nftables +} + +do_$1_regen ${@:2} From a0d017908ea6dcc7b352c94a5c072064bad6c870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Thu, 12 Dec 2024 19:45:17 +0100 Subject: [PATCH 07/14] resources: rewrite port resource for the new firewall API firewall: handle resources --- src/utils/resources.py | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/utils/resources.py b/src/utils/resources.py index 5f1ffc1128..4c756446b5 100644 --- a/src/utils/resources.py +++ b/src/utils/resources.py @@ -1335,6 +1335,7 @@ class PortsResource(AppResource): "default": None, "exposed": False, # or True(="Both"), "TCP", "UDP" "fixed": False, + "upnp": False, } ports: Dict[str, Dict[str, Any]] @@ -1376,7 +1377,8 @@ def _port_is_used(self, port): return used_by_process or used_by_app or used_by_self_provisioning def provision_or_update(self, context: Dict = {}): - from yunohost.firewall import firewall_allow, firewall_disallow + from yunohost.firewall import YunoFirewall + firewall = YunoFirewall() for name, infos in self.ports.items(): setting_name = f"port_{name}" if name != "main" else "port" @@ -1407,23 +1409,40 @@ def provision_or_update(self, context: Dict = {}): self.set_setting(setting_name, port_value) if infos["exposed"]: - firewall_allow(infos["exposed"], port_value, reload_if_change=True) + if infos["exposed"].lower() == "both": + protos = ["tcp", "udp"] + else: + protos = [infos["exposed"].lower()] + + comment = f"{self.app} {name}" + for proto in protos: + firewall.open_port(proto, port_value, comment, infos["upnp"]) + else: - firewall_disallow( - infos["exposed"], port_value, reload_if_change=True - ) + for proto in ["tcp", "udp"]: + firewall.close_port(proto, port_value) + + if firewall.need_reload: + firewall.apply() def deprovision(self, context: Dict = {}): - from yunohost.firewall import firewall_disallow + from yunohost.firewall import YunoFirewall + firewall = YunoFirewall() for name, infos in self.ports.items(): setting_name = f"port_{name}" if name != "main" else "port" value = self.get_setting(setting_name) self.delete_setting(setting_name) if value and str(value).strip(): - firewall_disallow( - infos["exposed"], int(value), reload_if_change=True - ) + if infos["exposed"].lower() == "both": + protos = ["tcp", "udp"] + else: + protos = [infos["exposed"].lower()] + for proto in protos: + firewall.delete_port(proto, value) + + if firewall.need_reload: + firewall.apply() class DatabaseAppResource(AppResource): From 4aa4bc76ca62a162f4b5516cc2d14c30d6fe9489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Fri, 13 Dec 2024 18:52:28 +0100 Subject: [PATCH 08/14] firewall: Positional protocol argument --- share/actionsmap.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/share/actionsmap.yml b/share/actionsmap.yml index ed5e10b891..2f096f36e8 100755 --- a/share/actionsmap.yml +++ b/share/actionsmap.yml @@ -1396,7 +1396,8 @@ firewall: full: --raw help: Return the complete YAML dict action: store_true - protocol: + -p: + full: --protocol help: "If not raw, protocol type to list (tcp/udp)" choices: - tcp @@ -1418,7 +1419,8 @@ firewall: pattern: &pattern_port_or_range - !!str ((^|(?!\A):)([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){1,2}?$ - "pattern_port_or_range" - protocol: + -p: + full: --protocol help: "Protocol type (tcp/udp)" choices: - tcp @@ -1443,7 +1445,8 @@ firewall: help: Port or range of ports to close extra: pattern: *pattern_port_or_range - protocol: + -p: + full: --protocol help: "Protocol type (tcp/udp)" choices: - tcp @@ -1465,7 +1468,8 @@ firewall: help: Port or range of ports to delete extra: pattern: *pattern_port_or_range - protocol: + -p: + full: --protocol help: "Protocol type (tcp/udp)" choices: - tcp From 574ae0855debdace957442f9a0e2de6d83d5d444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Fri, 13 Dec 2024 23:01:44 +0100 Subject: [PATCH 09/14] Add command firewall is-open -p for apps --- share/actionsmap.yml | 23 ++++++++++++++++++++--- src/firewall.py | 15 +++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/share/actionsmap.yml b/share/actionsmap.yml index 2f096f36e8..914d1bf0dd 100755 --- a/share/actionsmap.yml +++ b/share/actionsmap.yml @@ -1408,6 +1408,25 @@ firewall: help: If not raw, list UPnP forwarded ports instead of open ports action: store_true + ### firewall_is_open() + is-open: + action_help: Returns whether the port is open or not. + api: GET /firewall// + arguments: + port: + help: Port or range of ports to close + extra: + pattern: &pattern_port_or_range + - !!str ((^|(?!\A):)([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){1,2}?$ + - "pattern_port_or_range" + -p: + full: --protocol + help: "Protocol type (tcp/udp)" + choices: + - tcp + - udp + default: tcp + ### firewall_open() open: action_help: Allow connections on a port @@ -1416,9 +1435,7 @@ firewall: port: help: Port or range of ports to open extra: - pattern: &pattern_port_or_range - - !!str ((^|(?!\A):)([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){1,2}?$ - - "pattern_port_or_range" + pattern: *pattern_port_or_range -p: full: --protocol help: "Protocol type (tcp/udp)" diff --git a/src/firewall.py b/src/firewall.py index 094df1ad8f..7ec7073d77 100644 --- a/src/firewall.py +++ b/src/firewall.py @@ -255,6 +255,21 @@ def disable(self) -> None: self.enabled(False) +def firewall_is_open( + port: int | str, + protocol: str, +) -> bool: + """ + Returns whether the specified port is open. + + Keyword arguments: + port -- Port or range of ports to open + protocol -- Protocol type to allow (tcp/udp) + + """ + return port in firewall_list(raw=False, protocol=protocol, forwarded=False) + + def firewall_open( port: int | str, protocol: str, From f666cc1f4ea8ec516d9932cbb3fdeba75727d0be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Sun, 15 Dec 2024 18:07:39 +0100 Subject: [PATCH 10/14] Replace yunohost-firewall.service with an override on nftables.service to call yunohost hooks on start/reload --- conf/yunohost/yunohost-firewall.service | 14 -------------- .../yunohost/yunohost-nftables-hooks-override.conf | 8 ++++++++ hooks/conf_regen/01-yunohost | 8 ++++---- 3 files changed, 12 insertions(+), 18 deletions(-) delete mode 100644 conf/yunohost/yunohost-firewall.service create mode 100644 conf/yunohost/yunohost-nftables-hooks-override.conf diff --git a/conf/yunohost/yunohost-firewall.service b/conf/yunohost/yunohost-firewall.service deleted file mode 100644 index 1dd46f4777..0000000000 --- a/conf/yunohost/yunohost-firewall.service +++ /dev/null @@ -1,14 +0,0 @@ -[Unit] -Description=YunoHost Firewall -Requires=network.target -After=network.target - -[Service] -Type=oneshot -ExecStart=/usr/bin/yunohost firewall reload -ExecReload=/usr/bin/yunohost firewall reload -ExecStop=/usr/bin/yunohost firewall stop -RemainAfterExit=yes - -[Install] -WantedBy=multi-user.target diff --git a/conf/yunohost/yunohost-nftables-hooks-override.conf b/conf/yunohost/yunohost-nftables-hooks-override.conf new file mode 100644 index 0000000000..dac1dc45a5 --- /dev/null +++ b/conf/yunohost/yunohost-nftables-hooks-override.conf @@ -0,0 +1,8 @@ +# This override config calls yunohost hooks when nftables is started/reloaded + +[Service] +ExecStart=yunohost hook callback post_nftables +ExecReload=yunohost hook callback post_nftables +# This one is legacy, apps should use the new post_nftables hook +ExecStart=yunohost hook callback post_iptable_rules +ExecReload=yunohost hook callback post_iptable_rules diff --git a/hooks/conf_regen/01-yunohost b/hooks/conf_regen/01-yunohost index 24c94b6d36..c5dca53de3 100755 --- a/hooks/conf_regen/01-yunohost +++ b/hooks/conf_regen/01-yunohost @@ -195,7 +195,6 @@ do_init_regen() { # YunoHost services cp yunohost-api.service /etc/systemd/system/yunohost-api.service cp yunohost-portal-api.service /etc/systemd/system/yunohost-portal-api.service - cp yunohost-firewall.service /etc/systemd/system/yunohost-firewall.service cp yunoprompt.service /etc/systemd/system/yunoprompt.service systemctl daemon-reload @@ -278,8 +277,9 @@ ConditionVirtualization=!container EOF fi - # Delete legacy conflict between yunohost and nftables mkdir -p ${pending_dir}/etc/systemd/system/nftables.service.d/ + cp yunohost-nftables-hooks-override.conf ${pending_dir}/etc/systemd/system/nftables.service.d/yunohost-nftables-hooks.conf + # Delete legacy conflict between yunohost and nftables touch ${pending_dir}/etc/systemd/system/nftables.service.d/ynh-override.conf # Don't suspend computer on LidSwitch @@ -293,9 +293,10 @@ EOF cp yunohost-api.service ${pending_dir}/etc/systemd/system/yunohost-api.service cp yunohost-portal-api.service ${pending_dir}/etc/systemd/system/yunohost-portal-api.service - cp yunohost-firewall.service ${pending_dir}/etc/systemd/system/yunohost-firewall.service cp yunoprompt.service ${pending_dir}/etc/systemd/system/yunoprompt.service cp proc-hidepid.service ${pending_dir}/etc/systemd/system/proc-hidepid.service + # Delete legacy yunohost-firewall service + touch ${pending_dir}/etc/systemd/system/yunohost-firewall.service mkdir -p ${pending_dir}/etc/dpkg/origins/ cp dpkg-origins ${pending_dir}/etc/dpkg/origins/yunohost @@ -359,7 +360,6 @@ do_post_regen() { systemctl daemon-reload systemctl restart systemd-logind } - [[ ! "$regen_conf_files" =~ "yunohost-firewall.service" ]] || systemctl daemon-reload [[ ! "$regen_conf_files" =~ "yunohost-api.service" ]] || systemctl daemon-reload [[ ! "$regen_conf_files" =~ "yunohost-portal-api.service" ]] || systemctl daemon-reload From 7bb4779dc9e91f2ffb8820e06574c295140c960a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Wed, 11 Dec 2024 21:51:46 +0100 Subject: [PATCH 11/14] DONOTMERGE apt update before install --- .gitlab/ci/install.gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab/ci/install.gitlab-ci.yml b/.gitlab/ci/install.gitlab-ci.yml index bbe4042d80..e725b8a3f2 100644 --- a/.gitlab/ci/install.gitlab-ci.yml +++ b/.gitlab/ci/install.gitlab-ci.yml @@ -16,6 +16,7 @@ upgrade: extends: .install-stage image: "core-tests" script: + - apt update - DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ${CI_PROJECT_DIR}/*.deb @@ -23,5 +24,6 @@ install-postinstall: extends: .install-stage image: "before-install" script: + - apt update - DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ${CI_PROJECT_DIR}/*.deb - yunohost tools postinstall -d domain.tld -u syssa -F 'Syssa Mine' -p the_password --ignore-dyndns --force-diskspace From 6802b91e3af527be93b9ab2f55aff14b16973091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Tue, 17 Dec 2024 16:01:04 +0100 Subject: [PATCH 12/14] firewall: Add a custom script that run-parts the hooks instead of calling yunohost hook that takes lock --- .../yunohost-nftables-hooks-override.conf | 6 +----- share/yunohost-nftables-hooks | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) create mode 100755 share/yunohost-nftables-hooks diff --git a/conf/yunohost/yunohost-nftables-hooks-override.conf b/conf/yunohost/yunohost-nftables-hooks-override.conf index dac1dc45a5..17713c1ccb 100644 --- a/conf/yunohost/yunohost-nftables-hooks-override.conf +++ b/conf/yunohost/yunohost-nftables-hooks-override.conf @@ -1,8 +1,4 @@ # This override config calls yunohost hooks when nftables is started/reloaded [Service] -ExecStart=yunohost hook callback post_nftables -ExecReload=yunohost hook callback post_nftables -# This one is legacy, apps should use the new post_nftables hook -ExecStart=yunohost hook callback post_iptable_rules -ExecReload=yunohost hook callback post_iptable_rules +ExecStart=/usr/share/yunohost/yunohost-nftables-hooks diff --git a/share/yunohost-nftables-hooks b/share/yunohost-nftables-hooks new file mode 100755 index 0000000000..85e1dfc372 --- /dev/null +++ b/share/yunohost-nftables-hooks @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +HOOK_FOLDER="/usr/share/yunohost/hooks/" +CUSTOM_HOOK_FOLDER="/etc/yunohost/hooks.d/" + +run_parts() { + dir="$1" + if [ -d "$dir" ]; then + run-parts "$dir" + fi +} + +run_parts "$HOOK_FOLDER/post_nftables" +run_parts "$CUSTOM_HOOK_FOLDER/post_nftables" + +# This one is legacy, apps should use the new post_nftables hook +run_parts "$HOOK_FOLDER/post_iptable_rules" +run_parts "$CUSTOM_HOOK_FOLDER/post_iptable_rules" From 43666cf7b477fc2157394295e0b7c0bc5df05676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Fri, 20 Dec 2024 20:38:06 +0100 Subject: [PATCH 13/14] app resources: Fix firewall, exposed can be a bool too --- src/utils/resources.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/utils/resources.py b/src/utils/resources.py index 4c756446b5..3e9f7e6d99 100644 --- a/src/utils/resources.py +++ b/src/utils/resources.py @@ -1376,6 +1376,13 @@ def _port_is_used(self, port): return used_by_process or used_by_app or used_by_self_provisioning + def _exposed_to_protos(self, exposed: str | bool) -> list[str]: + if isinstance(exposed, bool): + return ["tcp"] if exposed else [] + if exposed.lower() == "both": + return ["tcp", "udp"] + return [exposed.lower()] + def provision_or_update(self, context: Dict = {}): from yunohost.firewall import YunoFirewall firewall = YunoFirewall() @@ -1408,16 +1415,10 @@ def provision_or_update(self, context: Dict = {}): self.ports_used_by_self.append(port_value) self.set_setting(setting_name, port_value) + comment = f"{self.app} {name}" if infos["exposed"]: - if infos["exposed"].lower() == "both": - protos = ["tcp", "udp"] - else: - protos = [infos["exposed"].lower()] - - comment = f"{self.app} {name}" - for proto in protos: + for proto in self._exposed_to_protos(infos["exposed"]): firewall.open_port(proto, port_value, comment, infos["upnp"]) - else: for proto in ["tcp", "udp"]: firewall.close_port(proto, port_value) @@ -1434,11 +1435,7 @@ def deprovision(self, context: Dict = {}): value = self.get_setting(setting_name) self.delete_setting(setting_name) if value and str(value).strip(): - if infos["exposed"].lower() == "both": - protos = ["tcp", "udp"] - else: - protos = [infos["exposed"].lower()] - for proto in protos: + for proto in self._exposed_to_protos(infos["exposed"]): firewall.delete_port(proto, value) if firewall.need_reload: From a6a217e80904250e6046b8b88f6d04e5c2ae6ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Sat, 21 Dec 2024 12:39:42 +0100 Subject: [PATCH 14/14] Improve firewall migration: add comment for port opened for an app --- src/migrations/0032_firewall_config.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/migrations/0032_firewall_config.py b/src/migrations/0032_firewall_config.py index 2c92a9d528..2ba9f7c98e 100644 --- a/src/migrations/0032_firewall_config.py +++ b/src/migrations/0032_firewall_config.py @@ -24,6 +24,7 @@ import yaml from yunohost.tools import Migration +from yunohost.app import app_list from yunohost.firewall import YunoFirewall @@ -35,8 +36,28 @@ class MyMigration(Migration): mode = "auto" + apps = app_list(full=True) + + def _app_comment_of_port(self, port: int) -> str: + for app in self.apps["apps"]: + settings = app["settings"] + port_keys: list[str] = [ + key + for key in settings.keys() + if key.startswith("port_") or key == "port" + ] + for key in port_keys: + if settings[key] not in [port, str(port)]: + continue + port_name = "main" if key == "port" else key.removeprefix("port_") + return f"App {app['id']}: {port_name} port" + + return "" + def firewall_file_migrate(self) -> None: - old_data = yaml.safe_load(YunoFirewall.FIREWALL_FILE.open("r", encoding="utf-8")) + old_data = yaml.safe_load( + YunoFirewall.FIREWALL_FILE.open("r", encoding="utf-8") + ) new_data: dict[str, Any] = { "router_forwarding_upnp": old_data["uPnP"]["enabled"], @@ -48,6 +69,7 @@ def firewall_file_migrate(self) -> None: port: { "open": True, "upnp": port in old_data["uPnP"][proto], + "comment": self._app_comment_of_port(port), } for port in set(old_data["ipv4"][proto] + old_data["ipv6"][proto]) }