fwtool: initial ipv6 support and puppet integration
authorClinton Ebadi <clinton@unknownlamer.org>
Thu, 19 Apr 2018 05:27:08 +0000 (01:27 -0400)
committerClinton Ebadi <clinton@unknownlamer.org>
Thu, 19 Apr 2018 05:27:08 +0000 (01:27 -0400)
Not the prettiest, but it works.

Just duplicates the firewall between ipv4 and ipv6, making sure to
filter out any hostnames that aren't resolvable in each domain.

ProxiedServer doesn't work over IPv6 yet due to nodes not having that
information, will need to be fixed for proxied web services to work.

domtool-publish has a new action, firewallpuppet, that will reload the
firewall for our new setup (and fall back to just reloading ferm on
the current one). Further work is required for puppet; we are purging
unmanaged chains and will need to move all rules into a single chain
instead of jumping to a different chain per user.

configDefault/firewall.cfg
configDefault/firewall.csg
scripts/domtool-publish
src/plugins/firewall.sml

index 5cb781b..6ed751b 100644 (file)
@@ -5,6 +5,7 @@ val firewallRules = ConfigCore.sharedRoot ^ "/firewall/user.rules"
 val firewallDir = ConfigCore.localRoot ^ "/firewall/"
 val firewallNodes = ["bog", "navajos", "quag"]
 
-val reload = ConfigCore.sudo ^ " " ^ ConfigCore.installPrefix ^ "/sbin/domtool-publish firewall"
+val reload = ConfigCore.sudo ^ " " ^ ConfigCore.installPrefix ^ "/sbin/domtool-publish firewallpuppet"
+val dig = "/usr/bin/dig"
 
 end
index af23b1c..d80e393 100644 (file)
@@ -6,4 +6,5 @@ signature FIREWALL_CONFIG = sig
     val firewallRules : string (* Rules file *)
 
     val reload : string (* Command to reload configuration *)
+    val dig : string (* Path to dig from dnsutils *)
 end
index cd5e196..3450703 100755 (executable)
@@ -95,6 +95,26 @@ case $1 in
               # rules?
               /bin/cp /var/domtool/firewall/*.conf /etc/ferm/
               /etc/init.d/ferm reload
+              ;;
+       firewallpuppet)
+              # new firewall publishing method that integrates with puppet (sort of)
+              /bin/cp /var/domtool/firewall/*.conf /etc/ferm/
+              if [ ! -d /etc/puppetlabs ]; then
+                  # legacy node
+                  /etc/init.d/ferm reload
+                  exit
+              fi
+              if ( /usr/sbin/ferm --noexec /etc/ferm/ferm.conf ); then
+                  for chain in FERM-INPUT FERM-OUTPUT; do
+                      /sbin/iptables -F $chain
+                      /sbin/ip6tables -F $chain
+
+                      /usr/sbin/ferm --domain ip  --noexec --lines /etc/ferm/ferm.conf | iptables-restore --noflush
+                              /usr/sbin/ferm --domain ip6 --noexec --lines /etc/ferm/ferm.conf | ip6tables-restore --noflush
+                  done
+              else
+                  echo "firewall: ferm failed, aborting regeneration."
+              fi
        ;;
        *)
                echo "Usage: domtool-publish [apache|bind|courier|exim|mailman|smtplog STRING|users|firewall]"
index e6f92b2..2561f1c 100644 (file)
@@ -1,6 +1,6 @@
 (* HCoop Domtool (http://hcoop.sourceforge.net/)
  * Copyright (c) 2006-2007, Adam Chlipala
- * Copyright (c) 2011,2012,2013,2014 Clinton Ebadi
+ * Copyright (c) 2011,2012,2013,2014,2018 Clinton Ebadi
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -24,7 +24,7 @@
 structure Firewall :> FIREWALL = struct
 
 datatype user = User of string
-                   
+
 datatype fwnode = FirewallNode of string
 
 datatype fwrule = Client of int list * string list
@@ -34,6 +34,9 @@ datatype fwrule = Client of int list * string list
 
 type firewall_rules = (user * fwnode * fwrule) list
 
+datatype fwip = FwIPv4
+             | FwIPv6
+
 structure StringMap = DataStructures.StringMap
 
 fun parseRules () =
@@ -85,32 +88,59 @@ fun query (node, uname) =
            (List.filter (fn (User u, FirewallNode n, _) => u = uname andalso n = node) rules)
     end
 
+fun dnsExists dnsRR dnsRecord =
+    let
+       val dnsRR_string = case dnsRR of
+                              FwIPv6 => "AAAA"
+                            | FwIPv4 => "A"
+    in
+       (* timeout chosen arbitrarilty, shorter is better if it's reliable *)
+       (* dig outputs true even if the lookup fails, but no output in short mode should work *)
+       case Slave.runOutput (Config.Firewall.dig, ["+short", "+timeout=3", "-t", dnsRR_string, dnsRecord]) of
+           (_, SOME s) => (case Domain.validDomain (substring (s, 0, size s - 2)) of (* delete trailing . from cname *)
+                               true => dnsExists dnsRR s (* dig will return CNAME, must recurse *)
+                             | false => true) (* maybe also double check ip? use size s - 1 if so! *)
+
+         | (_, NONE) => false
+    end
+
+fun filterHosts (hosts, ipv6) =
+    List.filter (fn host => if (Domain.validIpv6 host orelse Domain.validIp host)
+                           then
+                               true
+                           else
+                               dnsExists ipv6 host)
+               hosts
+
+
 fun formatPorts ports = "(" ^ String.concatWith " " (map Int.toString ports) ^ ")"
-fun formatHosts hosts = "(" ^ String.concatWith " " hosts ^ ")"
+fun formatHosts (hosts, ipv6) = "(" ^ String.concatWith " " (filterHosts (hosts, ipv6)) ^ ")"
 
-fun formatOutputRule (Client (ports, hosts)) = "dport " ^ formatPorts ports ^ (case hosts of 
+fun formatOutputRule (Client (ports, hosts), ipv6) = "dport " ^ formatPorts ports ^ (case hosts of
                                                                                 [] => ""
-                                                                              | _ => " daddr " ^ formatHosts hosts) ^ " ACCEPT;"
+                                                                              | _ => " daddr " ^ formatHosts (hosts, ipv6)) ^ " ACCEPT;"
   | formatOutputRule _ = ""
 
-fun formatInputRule (Server (ports, hosts)) = "dport " ^ formatPorts ports ^ (case hosts of 
+fun formatInputRule (Server (ports, hosts), ipv6) = "dport " ^ formatPorts ports ^ (case hosts of
                                                                                  [] => ""
-                                                                               | _ => " saddr " ^ formatHosts hosts) ^ " ACCEPT;"
+                                                                               | _ => " saddr " ^ formatHosts (hosts, ipv6)) ^ " ACCEPT;"
   | formatInputRule _ = ""
 
 type ferm_lines = { input_rules : (string list) DataStructures.StringMap.map,
-                   output_rules : (string list) DataStructures.StringMap.map } 
+                   output_rules : (string list) DataStructures.StringMap.map }
 
-fun generateNodeFermRules rules  = 
+fun generateNodeFermRules rules  =
     let
        fun filter_node_rules rules =
-           List.filter (fn (uname, FirewallNode node, rule) => node = Slave.hostname () orelse case rule of 
+           List.filter (fn (uname, FirewallNode node, rule) => node = Slave.hostname () orelse case rule of
                                                                                                    ProxiedServer _ => List.exists (fn (h,_) => h = Slave.hostname ()) Config.Apache.webNodes_all
                                                                                     | _ => false)
                        rules
 
        val inputLines = ref StringMap.empty
        val outputLines = ref StringMap.empty
+       val inputLines_v6 = ref StringMap.empty
+       val outputLines_v6 = ref StringMap.empty
 
        fun confLine r (User uname, line) =
            let
@@ -122,15 +152,17 @@ fun generateNodeFermRules rules  =
                r := StringMap.insert (!r, uname, line :: lines)
            end
 
-       fun confLine_in (uname, rule) = confLine inputLines (uname, formatInputRule rule)
-       fun confLine_out (uname, rule) = confLine outputLines (uname, formatOutputRule rule)
+       fun confLine_in (uname, rule) = confLine inputLines (uname, formatInputRule (rule, FwIPv4))
+       fun confLine_out (uname, rule) = confLine outputLines (uname, formatOutputRule (rule, FwIPv4))
+       fun confLine_in_v6 (uname, rule) = confLine inputLines_v6 (uname, formatInputRule (rule, FwIPv6))
+       fun confLine_out_v6 (uname, rule) = confLine outputLines_v6 (uname, formatOutputRule (rule, FwIPv6))
 
        fun insertConfLine (uname, ruleNode, rule) =
-           case rule of 
-               Client (ports, hosts) => confLine_out (uname, rule)
-             | Server (ports, hosts) => confLine_in (uname, rule)
-             | LocalServer ports => (insertConfLine (uname, ruleNode, Client (ports, ["127.0.0.1/8"]));
-                                     insertConfLine (uname, ruleNode, Server (ports, ["127.0.0.1/8"])))
+           case rule of
+               Client (ports, hosts) => (confLine_out (uname, rule); confLine_out_v6 (uname, rule))
+             | Server (ports, hosts) => (confLine_in (uname, rule); confLine_in_v6 (uname, rule))
+             | LocalServer ports => (insertConfLine (uname, ruleNode, Client (ports, ["127.0.0.1/8", ":::1"]));
+                                     insertConfLine (uname, ruleNode, Server (ports, ["127.0.0.1/8", ":::1"])))
              | ProxiedServer ports => if (fn FirewallNode r => r) ruleNode = Slave.hostname () then
                                           (insertConfLine (uname, ruleNode, Server (ports, ["$WEBNODES"]));
                                            insertConfLine (uname, ruleNode, Client (ports, [(fn FirewallNode r => r) ruleNode])))
@@ -141,7 +173,9 @@ fun generateNodeFermRules rules  =
        val _ = map insertConfLine (filter_node_rules rules)
     in
        { input_rules = !inputLines,
-         output_rules = !outputLines }
+         output_rules = !outputLines,
+          input6_rules = !inputLines_v6,
+         output6_rules = !outputLines_v6 }
 
 
     end
@@ -155,53 +189,66 @@ fun generateFirewallConfig rules =
        val users_tcp_in_conf = TextIO.openOut (Config.Firewall.firewallDir ^ "/users_tcp_in.conf")
        val user_chains_conf = TextIO.openOut (Config.Firewall.firewallDir ^ "/user_chains.conf")
 
+       val users_tcp6_out_conf = TextIO.openOut (Config.Firewall.firewallDir ^ "/users_tcp6_out.conf")
+       val users_tcp6_in_conf = TextIO.openOut (Config.Firewall.firewallDir ^ "/users_tcp6_in.conf")
+       val user_chains6_conf = TextIO.openOut (Config.Firewall.firewallDir ^ "/user_chains6.conf")
+
        val nodeFermRules = generateNodeFermRules rules
-               
-       fun write_tcp_in_conf_preamble outf = 
-           TextIO.output (outf, String.concat ["@def $WEBNODES = (",
-                                               (String.concatWith " " (List.map (fn (_, ip) => ip) 
+
+       fun write_tcp_in_conf_preamble outf =
+           (* no ipv6 support yet, but use @ipfilter() in ferm to prepare *)
+           TextIO.output (outf, String.concat ["@def $WEBNODES = @ipfilter((",
+                                               (String.concatWith " " (List.map (fn (_, ip) => ip)
                                                                                 (List.filter (fn (node, _) => List.exists (fn (n) => n = node) (List.map (fn (node, _) => node) (Config.Apache.webNodes_all @ Config.Apache.webNodes_admin)))
                                                                                              Config.nodeIps))),
-                                               ");\n\n"])
+                                               "));\n\n"])
 
-       fun writeUserInRules (uname, lines) = 
+       fun writeUserInRules tcp_inf (uname, lines) =
            (* We can't match the user when listening; SELinux or
               similar would let us manage this with better
               granularity.*)
            let
                val _ = SysWord.toInt (Posix.ProcEnv.uidToWord (Posix.SysDB.Passwd.uid (Posix.SysDB.getpwnam uname)))
            in
-               TextIO.output (users_tcp_in_conf, "proto tcp {\n");
-               TextIO.output (users_tcp_in_conf, concat lines);
-               TextIO.output (users_tcp_in_conf, "\n}\n\n")
-           end handle OS.SysErr _ => print "Invalid user in firewall config, skipping.\n" (* no sense in opening ports for bad users *)                
+               TextIO.output (tcp_inf, "proto tcp {\n");
+               TextIO.output (tcp_inf, concat lines);
+               TextIO.output (tcp_inf, "\n}\n\n")
+           end handle OS.SysErr _ => print "Invalid user in firewall config, skipping.\n" (* no sense in opening ports for bad users *)
 
-       fun writeUserOutRules (uname, lines) =
+       fun writeUserOutRules tcp_outf chains_outf (uname, lines) =
            let
                val uid = SysWord.toInt (Posix.ProcEnv.uidToWord (Posix.SysDB.Passwd.uid (Posix.SysDB.getpwnam uname)))
            in
-               TextIO.output (users_tcp_out_conf, "mod owner uid-owner " ^ (Int.toString uid)
+               TextIO.output (tcp_outf, "mod owner uid-owner " ^ (Int.toString uid)
                                                   ^ " { jump user_" ^ uname ^ "_tcp_out"
                                                   ^ "; DROP; }\n");
 
-               TextIO.output (user_chains_conf, "chain user_" ^ uname ^ "_tcp_out"
+               TextIO.output (chains_outf, "chain user_" ^ uname ^ "_tcp_out"
                                                 ^ " proto tcp {\n");
-               TextIO.output (user_chains_conf, concat lines);
-               TextIO.output (user_chains_conf, "\n}\n\n")
+               TextIO.output (chains_outf, concat lines);
+               TextIO.output (chains_outf, "\n}\n\n")
            end handle OS.SysErr _ => print "Invalid user in firewall config, skipping.\n"
-           
+
     in
        write_tcp_in_conf_preamble (users_tcp_in_conf);
-       StringMap.appi (writeUserOutRules) (#output_rules nodeFermRules);
-       StringMap.appi (writeUserInRules) (#input_rules nodeFermRules);
+       StringMap.appi (writeUserOutRules users_tcp_out_conf user_chains_conf) (#output_rules nodeFermRules);
+       StringMap.appi (writeUserInRules users_tcp_in_conf) (#input_rules nodeFermRules);
+
+       write_tcp_in_conf_preamble (users_tcp6_in_conf);
+       StringMap.appi (writeUserOutRules users_tcp6_out_conf user_chains6_conf) (#output6_rules nodeFermRules);
+       StringMap.appi (writeUserInRules users_tcp6_in_conf) (#input6_rules nodeFermRules);
 
        TextIO.closeOut user_chains_conf;
        TextIO.closeOut users_tcp_out_conf;
        TextIO.closeOut users_tcp_in_conf;
 
+       TextIO.closeOut user_chains6_conf;
+       TextIO.closeOut users_tcp6_out_conf;
+       TextIO.closeOut users_tcp6_in_conf;
+
        true
     end
 
-fun publishConfig _ = 
+fun publishConfig _ =
     Slave.shell [Config.Firewall.reload]
 end