diff --git a/config/config.php b/config/config.php index 5c4a298b..96808363 100755 --- a/config/config.php +++ b/config/config.php @@ -24,12 +24,14 @@ define('RASPI_OPENVPN_CLIENT_CONFIG', '/etc/openvpn/client/client.conf'); define('RASPI_OPENVPN_CLIENT_LOGIN', '/etc/openvpn/client/login.conf'); define('RASPI_WIREGUARD_PATH', '/etc/wireguard/'); define('RASPI_WIREGUARD_CONFIG', RASPI_WIREGUARD_PATH.'wg0.conf'); +define('RASPI_FIREWALL_CONF', RASPI_CONFIG.'/networking/firewall/firewall.conf'); +define('RASPI_IPTABLES_CONF', RASPI_CONFIG.'/networking/firewall/iptables_rules.json'); define('RASPI_TORPROXY_CONFIG', '/etc/tor/torrc'); define('RASPI_LIGHTTPD_CONFIG', '/etc/lighttpd/lighttpd.conf'); define('RASPI_ACCESS_CHECK_IP', '1.1.1.1'); define('RASPI_ACCESS_CHECK_DNS', 'one.one.one.one'); -define('RASPI_CLIENT_CONFIG_PATH', '/etc/raspap/networking/client_udev_prototypes.json'); -define('RASPI_MOBILEDATA_CONFIG', '/etc/raspap/networking/mobiledata.ini'); +define('RASPI_CLIENT_CONFIG_PATH', RASPI_CONFIG.'/networking/client_udev_prototypes.json'); +define('RASPI_MOBILEDATA_CONFIG', RASPI_CONFIG.'/networking/mobiledata.ini'); define('RASPI_CLIENT_SCRIPT_PATH', '/usr/local/sbin'); // Constant for the 5GHz wireless regulatory domain @@ -44,6 +46,7 @@ define('RASPI_DHCP_ENABLED', true); define('RASPI_ADBLOCK_ENABLED', false); define('RASPI_OPENVPN_ENABLED', false); define('RASPI_WIREGUARD_ENABLED', false); +define('RASPI_FIREWALL_ENABLED', true); define('RASPI_TORPROXY_ENABLED', false); define('RASPI_CONFAUTH_ENABLED', true); define('RASPI_CHANGETHEME_ENABLED', true); diff --git a/config/iptables_rules.json b/config/iptables_rules.json new file mode 100644 index 00000000..d9b6f5f9 --- /dev/null +++ b/config/iptables_rules.json @@ -0,0 +1,205 @@ +{ + "info": "IPTABLES rules. $...$ expressions will be replaces automatically ($INTERFACE$, $PORT$, $IPADDRESS$)", + "rules_v4_file": "/etc/iptables/rules.v4", + "rules_v6_file": "/etc/iptables/rules.v6", + "order": [ "pre_rules", "restriction_rules", "main_rules", "exception_rules" ], + "pre_rules": [ + { + "name": "firewall policies", + "fw-state": true, + "comment": "Policy rules (firewall)", + "rules": [ + "-P INPUT DROP", + "-P FORWARD ACCEPT", + "-P OUTPUT ACCEPT", + "-t nat -P PREROUTING ACCEPT", + "-t nat -P POSTROUTING ACCEPT", + "-t nat -P INPUT ACCEPT", + "-t nat -P OUTPUT ACCEPT" + ] + }, + { + "name": "policies", + "fw-state": false, + "comment": "Policy rules", + "rules": [ + "-P INPUT ACCEPT", + "-P FORWARD ACCEPT", + "-P OUTPUT ACCEPT", + "-t nat -P PREROUTING ACCEPT", + "-t nat -P POSTROUTING ACCEPT", + "-t nat -P INPUT ACCEPT", + "-t nat -P OUTPUT ACCEPT" + ] + }, + { + "name": "loopback", + "fw-state": true, + "comment": "allow loopback device", + "rules": [ + "-A INPUT -i lo -j ACCEPT", + "-A OUTPUT -o lo -j ACCEPT" + ] + }, + { + "name": "ping", + "fw-state": true, + "ip-version": 4, + "comment": "allow ping request and echo", + "rules": [ + "-A INPUT -p icmp --icmp-type 8/0 -j ACCEPT", + "-A INPUT -p icmp --icmp-type 0/0 -j ACCEPT" + ] + }, + { + "name": "ping IPv6", + "fw-state": true, + "ip-version": 6, + "comment": "allow ping request and echo for IPv6", + "rules": [ + "-A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT", + "-A INPUT -p icmpv6 --icmpv6-type echo-reply -j ACCEPT" + ] + }, + { + "name": "ntp", + "fw-state": true, + "comment": "allow ntp request via udp (tcp should work w/o rule)", + "rules": [ + "-A INPUT -p udp --sport 123 -j ACCEPT" + ] + }, + { + "name": "dns", + "fw-state": true, + "comment": "allow dns request via tcp and udp", + "rules": [ + "-A INPUT -p udp -m multiport --sport 53,853 -j ACCEPT", + "-A INPUT -p tcp -m multiport --sport 53,853 -j ACCEPT" + ] + } + ], + "main_rules": [ + { + "name": "accesspoint", + "fw-state": true, + "comment": "Access point interface by default no restrictions", + "dependson": [ + { "var": "ap-device", "type": "string", "replace": "$INTERFACE$" } + ], + "rules": [ + "-A INPUT -i $INTERFACE$ -j ACCEPT", + "-A OUTPUT -o $INTERFACE$ -j ACCEPT" + ] + }, + { + "name": "NAT for access point", + "comment": "Masquerading needed for access point", + "rules": [ + "-t nat -A POSTROUTING -j MASQUERADE" + ] + }, + { + "name": "clients", + "fw-state": true, + "comment": "Rules for client interfaces (includes tun device)", + "rules": [ + "-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT" + ] + }, + { + "name": "openvpn", + "comment": "Rules for tunnel device (tun)", + "ip-version": 4, + "dependson": [ + { "var": "openvpn-enable", "type": "bool" }, + { "var": "openvpn-serverip", "type": "string", "replace": "$IPADDRESS$" }, + { "var": "ap-device", "type": "string", "replace": "$INTERFACE$" } + ], + "rules": [ + "-A INPUT -p udp -s $IPADDRESS$ -j ACCEPT", + "-A FORWARD -i tun+ -o $INTERFACE$ -m state --state RELATED,ESTABLISHED -j ACCEPT", + "-A FORWARD -i $INTERFACE$ -o tun+ -j ACCEPT", + "-t nat -A POSTROUTING -o tun+ -j MASQUERADE" + ] + }, + { + "name": "wireguard", + "comment": "Rules for wireguard device (wg)", + "ip-version": 4, + "dependson": [ + { "var": "wireguard-enable", "type": "bool" }, + { "var": "wireguard-serverip", "type": "string", "replace": "$IPADDRESS$" }, + { "var": "client-device", "type": "string", "replace": "$INTERFACE$" } + ], + "rules": [ + "-A INPUT -p udp -s $IPADDRESS$ -j ACCEPT", + "-A FORWARD -i wg+ -j ACCEPT", + "-t nat -A POSTROUTING -o $INTERFACE$ -j MASQUERADE" + ] + } + ], + "exception_rules": [ + { + "name": "ssh", + "fw-state": true, + "comment": "Allow ssh access to RaspAP on port 22", + "dependson": [ + { "var": "ssh-enable", "type": "bool" } + ], + "rules": [ + "-A INPUT -p tcp --dport 22 -j ACCEPT" + ] + }, + { + "name": "http", + "fw-state": true, + "comment": "Allow access to RaspAP GUI (https)", + "dependson": [ + { "var": "http-enable", "type": "bool" } + ], + "rules": [ + "-A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT" + ] + }, + { + "name": "interface", + "fw-state": true, + "comment": "Exclude interface from firewall", + "dependson": [ + { "var": "excl-devices", "type": "list", "replace": "$INTERFACE$" } + ], + "rules": [ + "-A INPUT -i $INTERFACE$ -j ACCEPT", + "-A OUTPUT -o $INTERFACE$ -j ACCEPT" + ] + }, + { + "name": "ipaddress", + "fw-state": true, + "ip-version": 4, + "comment": "allow access from/to IP", + "dependson": [ + { "var": "excluded-ips", "type": "list", "replace": "$IPADDRESS$" } + ], + "rules": [ + "-A INPUT -s $IPADDRESS$ -j ACCEPT", + "-A INPUT -d $IPADDRESS$ -j ACCEPT" + ] + } + ], + "restriction_rules": [ + { + "name": "ipaddress", + "fw-state": true, + "ip-version": 4, + "dependson": [ + { "var": "restricted-ips", "type": "list", "replace": "$IPADDRESS$" } + ], + "comment": "Block access from IP-address", + "rules": [ + "-A INPUT -s $IPADDRESS$ -j DROP" + ] + } + ] +} diff --git a/includes/firewall.php b/includes/firewall.php new file mode 100644 index 00000000..f44833c3 --- /dev/null +++ b/includes/firewall.php @@ -0,0 +1,368 @@ + $sect ) { + if (isRuleEnabled($sect, $conf) ) { + $str_rules= createRuleStr($sect, $conf); + if (!empty($str_rules) ) { + if (isIPv4($sect) ) { file_put_contents(RASPAP_IPTABLES_SCRIPT, $str_rules, FILE_APPEND); + } + if (isIPv6($sect) ) { file_put_contents(RASPAP_IP6TABLES_SCRIPT, $str_rules, FILE_APPEND); + } + ++$count; + } + } + } + } + } + if ($count > 0 ) { + exec("chmod +x ".RASPAP_IPTABLES_SCRIPT); + exec("sudo ".RASPAP_IPTABLES_SCRIPT); + exec("sudo iptables-save | sudo tee /etc/iptables/rules.v4"); + unlink(RASPAP_IPTABLES_SCRIPT); + exec("chmod +x ".RASPAP_IP6TABLES_SCRIPT); + exec("sudo ".RASPAP_IP6TABLES_SCRIPT); + exec("sudo ip6tables-save | sudo tee /etc/iptables/rules.v6"); + unlink(RASPAP_IP6TABLES_SCRIPT); + } + return ($count > 0); +} + +/** + * + * @param array $conf + * @return string $ret + */ +function WriteFirewallConf($conf) +{ + $ret = false; + if (is_array($conf) ) { write_php_ini($conf, RASPI_FIREWALL_CONF); + } + return $ret; +} + +/** + * + * @return array $conf + */ +function ReadFirewallConf() +{ + $conf = array(); + if (file_exists(RASPI_FIREWALL_CONF) ) { + $conf = parse_ini_file(RASPI_FIREWALL_CONF); + } + if ( !isset($conf["firewall-enable"]) ) { + $conf["firewall-enable"] = false; + $conf["ssh-enable"] = false; + $conf["http-enable"] = false; + $conf["excl-devices"] = ""; + $conf["excluded-ips"] = ""; + $conf["ap-device"] = ""; + $conf["client-device"] = ""; + $conf["restricted-ips"] = ""; + } + exec('ifconfig | grep -E -i "^tun[0-9]"', $ret); + $conf["openvpn-enable"] = !empty($ret); + unset($ret); + exec('ifconfig | grep -E -i "^wg[0-9]"', $ret); + $conf["wireguard-enable"] = !empty($ret); + return $conf; +} + +/** + * + * @return string $ips + */ +function getVPN_IPs() +{ + $ips = ""; + // get openvpn and wireguard server IPs + if (RASPI_OPENVPN_ENABLED && ($fconf = glob(RASPI_OPENVPN_CLIENT_PATH ."/*.conf")) !== false && !empty($fconf) ) { + foreach ( $fconf as $f ) { + unset($result); + exec('cat '.$f.' | sed -rn "s/^remote\s*([a-z0-9\.\-\_:]*)\s*([0-9]*)\s*$/\1 \2/ip" ', $result); + if (!empty($result) ) { + $result = explode(" ", $result[0]); + $ip = (isset($result[0])) ? $result[0] : ""; + $port = (isset($result[1])) ? $result[1] : ""; + if (!empty($ip) ) { + $ip = gethostbyname($ip); + if (filter_var($ip, FILTER_VALIDATE_IP) && strpos($ips, $ip) === false ) { $ips .= " $ip"; + } + } + } + } + } + // get wireguard server IPs + if (RASPI_WIREGUARD_ENABLED && ($fconf = glob(RASPI_WIREGUARD_PATH ."/*.conf")) !== false && !empty($fconf) ) { + foreach ( $fconf as $f ) { + unset($result); + exec('sudo /bin/cat '.$f.' | sed -rn "s/^endpoint\s*=\s*\[?([a-z0-9\.\-\_:]*)\]?:([0-9]*)\s*$/\1 \2/ip" ', $result); + if (!empty($result) ) { + $result = explode(" ", $result[0]); + $ip = (isset($result[0])) ? $result[0] : ""; + $port = (isset($result[1])) ? $result[1] : ""; + if (!empty($ip) ) { + $ip = gethostbyname($ip); + if (filter_var($ip, FILTER_VALIDATE_IP) && strpos($ips, $ip) === false ) { $ips .= " $ip"; + } + } + } + } + } + return trim($ips); +} + +/** + * + * @return array $fw_conf + */ +function getFirewallConfiguration() +{ + $fw_conf = ReadFirewallConf(); + + $json = file_get_contents(RASPI_IPTABLES_CONF); + getWifiInterface(); + $ap_device = $_SESSION['ap_interface']; + $clients = getClients(); + $str_clients = ""; + foreach( $clients["device"] as $dev ) { + if (!$dev["isAP"] ) { + if (!empty($str_clients) ) { $str_clients .= ", "; + } + $str_clients .= $dev["name"]; + } + } + $fw_conf["ap-device"] = $ap_device; + $fw_conf["client-list"] = $str_clients; + $id=findCurrentClientIndex($clients); + if ($id >= 0 ) { $fw_conf["client-device"] = $clients["device"][$id]["name"]; + } + return $fw_conf; +} + +/** + * + */ +function updateFirewall() +{ + $fw_conf = getFirewallConfiguration(); + if ( isset($fw_conf["firewall-enable"]) ) { + WriteFirewallConf($fw_conf); + configureFirewall(); + } + return; +} + +/** + * + */ +function DisplayFirewallConfig() +{ + $status = new StatusMessages(); + + $fw_conf = getFirewallConfiguration(); + $ap_device = $fw_conf["ap-device"]; + $str_clients = $fw_conf["client-list"]; + + if (!empty($_POST)) { + $fw_conf["ssh-enable"] = isset($_POST['ssh-enable']); + $fw_conf["http-enable"] = isset($_POST['http-enable']); + $fw_conf["firewall-enable"] = isset($_POST['firewall-enable']) || isset($_POST['apply-firewall']); + if (isset($_POST['firewall-enable']) ) { $status->addMessage(_('Firewall is now enabled'), 'success'); + } + if (isset($_POST['apply-firewall']) ) { $status->addMessage(_('Firewall settings changed'), 'success'); + } + if (isset($_POST['firewall-disable']) ) { $status->addMessage(_('Firewall is now disabled'), 'warning'); + } + if (isset($_POST['save-firewall']) ) { $status->addMessage(_('Firewall settings saved. Firewall is still disabled.'), 'success'); + } + if (isset($_POST['excl-devices']) ) { + $excl = filter_var($_POST['excl-devices'], FILTER_SANITIZE_STRING); + $excl = str_replace(',', ' ', $excl); + $excl = trim(preg_replace('/\s+/', ' ', $excl)); + if ($fw_conf["excl-devices"] != $excl ) { + $status->addMessage(_('Exclude devices '. $excl), 'success'); + $fw_conf["excl-devices"] = $excl; + } + } + if (isset($_POST['excluded-ips']) ) { + $excl = filter_var($_POST['excluded-ips'], FILTER_SANITIZE_STRING); + $excl = str_replace(',', ' ', $excl); + $excl = trim(preg_replace('/\s+/', ' ', $excl)); + if (!empty($excl) ) { + $excl = explode(' ', $excl); + $str_excl = ""; + foreach ( $excl as $ip ) { + if (filter_var($ip, FILTER_VALIDATE_IP) ) { $str_excl .= "$ip "; + } else { $status->addMessage(_('Exclude IP address '. $ip . ' failed - not a valid IP address'), 'warning'); + } + } + } + $str_excl = trim($str_excl); + if ($fw_conf["excluded-ips"] != $str_excl ) { + $status->addMessage(_('Exclude IP address(es) '. $str_excl), 'success'); + $fw_conf["excluded-ips"] = $str_excl; + } + } + WriteFirewallConf($fw_conf); + configureFirewall(); + } + $vpn_ips = getVPN_IPs(); + echo renderTemplate( + "firewall", compact( + "status", + "ap_device", + "str_clients", + "fw_conf", + "vpn_ips" + ) + ); +} + diff --git a/index.php b/index.php index ec6819a1..06f0b45a 100755 --- a/index.php +++ b/index.php @@ -41,6 +41,7 @@ require_once 'includes/system.php'; require_once 'includes/sysstats.php'; require_once 'includes/configure_client.php'; require_once 'includes/networking.php'; +require_once 'includes/firewall.php'; require_once 'includes/themes.php'; require_once 'includes/data_usage.php'; require_once 'includes/about.php'; @@ -175,6 +176,11 @@ $bridgedEnabled = getBridgedState();
%s
."
+msgstr "There are no restrictions for the access point %s
."
+
+msgid "Exception: Service"
+msgstr "Exception: Service"
+
+msgid "allow SSH access on port 22"
+msgstr "allow SSH access on port 22"
+
+msgid "allow access to the RaspAP GUI on port 80 or 443"
+msgstr "allow access to the RaspAP GUI on port 80 or 443"
+
+msgid "Allow incoming connections for some services from the internet side."
+msgstr "Allow incoming connections for some services from the internet side."
+
+msgid "Exception: network device"
+msgstr "Exception: network device"
+
+msgid "Exclude device(s)"
+msgstr "Exclude device(s)"
+
+msgid "Exclude the given network device(s) (separated by a blank or comma) from firewall rules."
+msgstr "Exclude the given network device(s) (separated by a blank or comma) from firewall rules."
+
+msgid "Current client devices: %s
"
+msgstr "Current client devices: %s
"
+
+msgid "The access point %s
is per default excluded."
+msgstr "The access point %s
is per default excluded."
+
+msgid "Exception: IP-Address"
+msgstr "Exception: IP-Address"
+
+msgid "Allow incoming connections from"
+msgstr "Allow incoming connections from"
+
+msgid "For the given IP-addresses (separated by a blank or comma) the incoming connection (via TCP and UDP) is accepted."
+msgstr "For the given IP-addresses (separated by a blank or comma) the incoming connection (via TCP and UDP) is accepted."
+
+msgid "This is required for an OpenVPN via UDP or Wireguard connection."
+msgstr "This is required for an OpenVPN via UDP or Wireguard connection."
+
+msgid "The list of configured VPN server IP addresses: %s
"
+msgstr "The list of configured VPN server IP addresses: %s
"
+
+msgid "Disable Firewall"
+msgstr "Disable Firewall"
+
+msgid "Enable Firewall"
+msgstr "Enable Firewall"
+
+msgid "Apply changes"
+msgstr "Apply changes"
+:
diff --git a/templates/firewall.php b/templates/firewall.php
new file mode 100755
index 00000000..70210346
--- /dev/null
+++ b/templates/firewall.php
@@ -0,0 +1,110 @@
+
+
+
+
+ %s."), $ap_device); ?>
+
+