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(); + + + @@ -274,6 +280,9 @@ $bridgedEnabled = getBridgedState(); case "/torproxy_conf": DisplayTorProxyConfig(); break; + case "/firewall_conf": + DisplayFirewallConfig(); + break; case "/auth_conf": DisplayAuthConfig($config['admin_user'], $config['admin_pass']); break; diff --git a/installers/install_feature_firewall.sh b/installers/install_feature_firewall.sh new file mode 100644 index 00000000..a2eecdc7 --- /dev/null +++ b/installers/install_feature_firewall.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# +# RaspAP feature installation: Firewall +# to be sources by the RaspAP installer script +# Author: @zbchristian +# Author URI: https://github.com/zbchristian/ +# License: GNU General Public License v3.0 +# License URI: https://github.com/raspap/raspap-webgui/blob/master/LICENSE + +function _install_feature_firewall() { + name="feature firewall" + + _install_log "Install $name" + # create config dir + sudo mkdir "$raspap_network/firewall" || _install_status 1 "Unable to create firewall config directory" + # copy firewall configuration + sudo cp "$webroot_dir/config/iptables_rules.json" "$raspap_network/firewall/" || _install_status 1 "Unable to copy iptables templates" + sudo chown $raspap_user:$raspap_user -R "$raspap_network/firewall" || _install_status 1 "Unable to change ownership of firewall directory and files " + _install_status 0 +} diff --git a/installers/raspap.sudoers b/installers/raspap.sudoers index 7ce40f28..c7bbac5c 100644 --- a/installers/raspap.sudoers +++ b/installers/raspap.sudoers @@ -62,3 +62,9 @@ www-data ALL=(ALL) NOPASSWD:/bin/cat /etc/wireguard/*.conf www-data ALL=(ALL) NOPASSWD:/bin/cat /etc/wireguard/wg-*.key www-data ALL=(ALL) NOPASSWD:/bin/rm /etc/wireguard/*.conf www-data ALL=(ALL) NOPASSWD:/bin/rm /etc/wireguard/wg-*.key +www-data ALL=(ALL) NOPASSWD:/tmp/iptables_raspap.sh +www-data ALL=(ALL) NOPASSWD:/tmp/ip6tables_raspap.sh +www-data ALL=(ALL) NOPASSWD:/usr/sbin/iptables-save +www-data ALL=(ALL) NOPASSWD:/usr/sbin/ip6tables-save +www-data ALL=(ALL) NOPASSWD:/usr/bin/tee /etc/iptables/rules.v4 +www-data ALL=(ALL) NOPASSWD:/usr/bin/tee /etc/iptables/rules.v6 diff --git a/installers/update_firewall.sh b/installers/update_firewall.sh new file mode 100644 index 00000000..2a7d9212 --- /dev/null +++ b/installers/update_firewall.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# include the raspap helper functions +source /usr/local/sbin/raspap_helpers.sh + +_getWebRoot + +echo -n "Update firewall ... " + +cat << EOF > /tmp/updateFirewall.php + +EOF + +sudo php -d include_path=$raspap_webroot /tmp/updateFirewall.php +rm /tmp/updateFirewall.php +echo "done." diff --git a/locale/en_US/LC_MESSAGES/messages.po b/locale/en_US/LC_MESSAGES/messages.po index a28fb4ec..c56747ac 100644 --- a/locale/en_US/LC_MESSAGES/messages.po +++ b/locale/en_US/LC_MESSAGES/messages.po @@ -1140,3 +1140,74 @@ msgstr "WireGuard configuration updated successfully" msgid "WireGuard configuration failed to be updated" msgstr "WireGuard configuration failed to be updated" +#: templates/firewall.php + +msgid "Client Firewall" +msgstr "Client Firewall" + +msgid "Firewall is ENABLED" +msgstr "Firewall is ENABLED" + +msgid "Firewall is OFF" +msgstr "Firewall is OFF" + +msgid "The default firewall will only allow outgoing and already established traffic." +msgstr "The default firewall will only allow outgoing and already established traffic." + +msgid "No incoming UDP traffic is allowed." +msgstr "No incoming UDP traffic is allowed." + +msgid "There are no restrictions for the access point %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 @@ +
+
+
+
+
+
+ +
+
+
+
+ showMessages(); ?> +

+ + + + + +
+
+

+ +
+
+ %s."), $ap_device); ?> +
+

+
+
+
+ +
+
+
+
+ > + +
+
+ > + +
+

+ +

+
+
+
+
+
+ + " aria-describedby="exclusion-description" > +

+ +
+ %s"), $str_clients); ?>
+ %s is per default excluded."), $ap_device); ?> +
+

+
+
+
+
+
+ + " aria-describedby="excl-ips-description" > +

+ +
+
+ %s"), $vpn_ips); ?> +
+

+
+
+ + " name="apply-firewall" /> + " name="firewall-disable" data-toggle="modal" data-target="#firewallModal"/> + + " name="save-firewall" /> + " name="firewall-enable" data-toggle="modal" data-target="#firewallModal"/> + +
+
+ +
+
+
+ + + + diff --git a/templates/torproxy.php b/templates/torproxy.php old mode 100644 new mode 100755 diff --git a/templates/wireguard.php b/templates/wireguard.php old mode 100644 new mode 100755