Re-enable querying user firewall rules
[hcoop/domtool2.git] / src / plugins / firewall.sml
CommitLineData
f9548f16
AC
1(* HCoop Domtool (http://hcoop.sourceforge.net/)
2 * Copyright (c) 2006-2007, Adam Chlipala
599a99d3 3 * Copyright (c) 2011,2012,2013 Clinton Ebadi
f9548f16
AC
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 *)
19
ec95f39f
CE
20(* Firewall management *)
21
22(* Contains portions from Fwtool Copyright (C) 2005 Adam Chlipala, GPL v2 or later *)
f9548f16
AC
23
24structure Firewall :> FIREWALL = struct
25
1a03ee46
CE
26datatype user = User of string
27
28datatype fwnode = FirewallNode of string
29
30datatype fwrule = Client of int list * string list
31 | Server of int list * string list
32 | ProxiedServer of int list
33 | LocalServer of int list
34
35type firewall_rules = (user * fwnode * fwrule) list
73b95423 36
ec95f39f
CE
37structure StringMap = DataStructures.StringMap
38
9a8de137 39fun parseRules () =
ec95f39f
CE
40 let
41 val inf = TextIO.openIn Config.Firewall.firewallRules
ec95f39f
CE
42
43 fun parsePorts ports =
1a03ee46
CE
44 List.mapPartial Int.fromString (String.fields (fn ch => ch = #",") ports)
45 (* Just drop bad ports for now *)
ec95f39f 46
1a03ee46 47 fun loop parsedRules =
ec95f39f 48 case TextIO.inputLine inf of
1a03ee46 49 NONE => parsedRules
ec95f39f
CE
50 | SOME line =>
51 case String.tokens Char.isSpace line of
9a8de137 52 node :: uname :: rest =>
ec95f39f 53 (case rest of
1a03ee46
CE
54 "Client" :: ports :: hosts => loop ((User uname, FirewallNode node, Client (parsePorts ports, hosts)) :: parsedRules)
55 | "Server" :: ports :: hosts => loop ((User uname, FirewallNode node, Server (parsePorts ports, hosts)) :: parsedRules)
56 | ["ProxiedServer", ports] => loop ((User uname, FirewallNode node, ProxiedServer (parsePorts ports)) :: parsedRules)
57 | ["LocalServer", ports] => loop ((User uname, FirewallNode node, LocalServer (parsePorts ports)) :: parsedRules)
58 | _ => (print "Invalid config line\n"; loop parsedRules))
59 | _ => loop parsedRules
ec95f39f 60 in
1a03ee46 61 loop []
ec95f39f
CE
62 end
63
167cffff
CE
64fun formatQueryRule (Client (ports, hosts)) =
65 "Client " ^ String.concatWith "," (map Int.toString ports) ^ " " ^ String.concatWith " " hosts
66 | formatQueryRule (Server (ports, hosts)) =
67 "Server " ^ String.concatWith "," (map Int.toString ports) ^ " " ^ String.concatWith " " hosts
68 | formatQueryRule (ProxiedServer ports) =
69 "ProxiedServer " ^ String.concatWith "," (map Int.toString ports)
70 | formatQueryRule (LocalServer ports) =
71 "LocalServer " ^ String.concatWith "," (map Int.toString ports)
72
73fun query (node, uname) =
1a03ee46 74 (* completely broken *)
f9548f16 75 let
ec95f39f 76 val rules = parseRules ()
f9548f16 77 in
167cffff
CE
78 map (fn (_, _, r) => formatQueryRule r)
79 (List.filter (fn (User u, FirewallNode n, _) => u = uname andalso n = node) rules)
ec95f39f
CE
80 end
81
1a03ee46
CE
82fun formatPorts ports = "(" ^ String.concatWith " " (map Int.toString ports) ^ ")"
83fun formatHosts hosts = "(" ^ String.concatWith " " hosts ^ ")"
84
85fun formatOutputRule (Client (ports, hosts)) = "dport " ^ formatPorts ports ^ (case hosts of
86 [] => ""
87 | _ => " daddr " ^ formatHosts hosts) ^ " ACCEPT;"
88
89fun formatInputRule (Server (ports, hosts)) = "dport " ^ formatPorts ports ^ (case hosts of
90 [] => ""
91 | _ => " saddr " ^ formatHosts hosts) ^ " ACCEPT;"
92
93type ferm_lines = { input_rules : (string list) DataStructures.StringMap.map,
94 output_rules : (string list) DataStructures.StringMap.map }
95
96fun generateNodeFermRules rules =
97 let
98 fun filter_node_rules rules =
99 List.filter (fn (uname, FirewallNode node, rule) => node = Slave.hostname () orelse case rule of
100 ProxiedServer _ => List.exists (fn (h,_) => h = Slave.hostname ()) Config.Apache.webNodes_all
101 | _ => false)
102 rules
f9548f16 103
1a03ee46
CE
104 val inputLines = ref StringMap.empty
105 val outputLines = ref StringMap.empty
106
107 fun confLine r (User uname, line) =
108 let
109 val line = "\t" ^ line ^ "\n"
110 val lines = case StringMap.find (!r, uname) of
111 NONE => []
112 | SOME lines => lines
113 in
114 r := StringMap.insert (!r, uname, line :: lines)
115 end
116
117 fun confLine_in (uname, rule) = confLine inputLines (uname, formatInputRule rule)
118 fun confLine_out (uname, rule) = confLine outputLines (uname, formatOutputRule rule)
119
120 fun insertConfLine (uname, ruleNode, rule) =
121 case rule of
122 Client (ports, hosts) => confLine_out (uname, rule)
123 | Server (ports, hosts) => confLine_in (uname, rule)
124 | LocalServer ports => (insertConfLine (uname, ruleNode, Client (ports, ["127.0.0.1/8"]));
125 insertConfLine (uname, ruleNode, Server (ports, ["127.0.0.1/8"])))
126 | ProxiedServer ports => if (fn FirewallNode r => r) ruleNode = Slave.hostname () then
127 (insertConfLine (uname, ruleNode, Server (ports, ["$WEBNODES"]));
128 insertConfLine (uname, ruleNode, Client (ports, [(fn FirewallNode r => r) ruleNode])))
129 else (* we are a web server *)
130 (insertConfLine (uname, ruleNode, Client (ports, [(fn FirewallNode r => r) ruleNode]));
131 insertConfLine (User "www-data", ruleNode, Client (ports, [(fn FirewallNode r => r) ruleNode])))
132
133 val _ = map insertConfLine (filter_node_rules rules)
134 in
135 { input_rules = !inputLines,
136 output_rules = !outputLines }
137
138
139 end
140
141fun generateFirewallConfig rules =
9a8de137
CE
142 (* rule generation must happen on the node (mandating the even
143 service users be pts users would make it possible to do on the
144 server, but that's not happening any time soon) *)
ec95f39f 145 let
ec95f39f
CE
146 val users_tcp_out_conf = TextIO.openOut (Config.Firewall.firewallDir ^ "/users_tcp_out.conf")
147 val users_tcp_in_conf = TextIO.openOut (Config.Firewall.firewallDir ^ "/users_tcp_in.conf")
1a03ee46
CE
148 val user_chains_conf = TextIO.openOut (Config.Firewall.firewallDir ^ "/user_chains.conf")
149
150 val nodeFermRules = generateNodeFermRules rules
151
152 fun write_tcp_in_conf_preamble outf =
153 TextIO.output (outf, String.concat ["@def $WEBNODES = (",
154 (String.concatWith " " (List.map (fn (_, ip) => ip)
155 (List.filter (fn (node, _) => List.exists (fn (n) => n = node) (List.map (fn (node, _) => node) (Config.Apache.webNodes_all @ Config.Apache.webNodes_admin)))
156 Config.nodeIps))),
157 ");\n\n"])
158
159 fun writeUserInRules (uname, lines) =
160 (* We can't match the user when listening; SELinux or
161 similar would let us manage this with better
162 granularity.*)
787bd6a4
CE
163 let
164 val _ = SysWord.toInt (Posix.ProcEnv.uidToWord (Posix.SysDB.Passwd.uid (Posix.SysDB.getpwnam uname)))
165 in
166 TextIO.output (users_tcp_in_conf, "proto tcp {\n");
167 TextIO.output (users_tcp_in_conf, concat lines);
168 TextIO.output (users_tcp_in_conf, "\n}\n\n")
169 end handle OS.SysErr _ => print "Invalid user in firewall config, skipping.\n" (* no sense in opening ports for bad users *)
1a03ee46
CE
170
171 fun writeUserOutRules (uname, lines) =
acef55cc 172 let
1a03ee46 173 val uid = SysWord.toInt (Posix.ProcEnv.uidToWord (Posix.SysDB.Passwd.uid (Posix.SysDB.getpwnam uname)))
acef55cc 174 in
1a03ee46
CE
175 TextIO.output (users_tcp_out_conf, "mod owner uid-owner " ^ (Int.toString uid)
176 ^ " { jump user_" ^ uname ^ "_tcp_out"
177 ^ "; DROP; }\n");
178
179 TextIO.output (user_chains_conf, "chain user_" ^ uname ^ "_tcp_out"
180 ^ " proto tcp {\n");
181 TextIO.output (user_chains_conf, concat lines);
182 TextIO.output (user_chains_conf, "\n}\n\n")
183 end handle OS.SysErr _ => print "Invalid user in firewall config, skipping.\n"
184
ec95f39f 185 in
1a03ee46
CE
186 write_tcp_in_conf_preamble (users_tcp_in_conf);
187 StringMap.appi (writeUserOutRules) (#output_rules nodeFermRules);
188 StringMap.appi (writeUserInRules) (#input_rules nodeFermRules);
f9548f16 189
1a03ee46 190 TextIO.closeOut user_chains_conf;
ec95f39f 191 TextIO.closeOut users_tcp_out_conf;
73b95423
CE
192 TextIO.closeOut users_tcp_in_conf;
193
194 true
ec95f39f 195 end
73b95423
CE
196
197fun publishConfig _ =
198 Slave.shell [Config.Firewall.reload]
f9548f16 199end