diff --git a/config/config.php b/config/config.php index c02ef442..997a3990 100755 --- a/config/config.php +++ b/config/config.php @@ -23,6 +23,8 @@ 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('RASPAP_FIREWALL_CONF',"/etc/raspap/networking/firewall/firewall.conf"); +define('RASPAP_IPTABLES_CONF',"/etc/raspap/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'); @@ -43,6 +45,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..1f05c9fe --- /dev/null +++ b/includes/firewall.php @@ -0,0 +1,259 @@ + $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 > /etc/iptables/rules.v4"); +// unlink(RASPAP_IPTABLES_SCRIPT); + exec("chmod +x ".RASPAP_IP6TABLES_SCRIPT); + exec("sudo ".RASPAP_IP6TABLES_SCRIPT); +// exec("sudo iptables-save > /etc/iptables/rules.v6"); +// unlink(RASPAP_IP6TABLES_SCRIPT); + } + return ($count > 0); +} + +function WriteFirewallConf($conf) { + $ret = false; + if ( is_array($conf) ) write_php_ini($conf,RASPAP_FIREWALL_CONF); + return $ret; +} + + +function ReadFirewallConf() { + if ( file_exists(RASPAP_FIREWALL_CONF) ) { + $conf = parse_ini_file(RASPAP_FIREWALL_CONF); + } else { + $conf = array(); + $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; +} + +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); +} + + +function DisplayFirewallConfig() +{ + + $status = new StatusMessages(); + + $json = file_get_contents(RASPAP_IPTABLES_CONF); + $ipt_rules = json_decode($json, true); + 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 = ReadFirewallConf(); + $fw_conf["ap-device"] = $ap_device; + $id=findCurrentClientIndex($clients); + if ( $id >= 0 ) $fw_conf["client-device"] = $clients["device"][$id]["name"]; + 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", + "ipt_rules", + "vpn_ips") + ); +} diff --git a/index.php b/index.php index 3e6e9ff6..496fafd4 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();
No incoming UDP traffic is allowed.
There are no restrictions for the access point $ap_device
.") ?>