1 (* HCoop
Domtool (http
://hcoop
.sourceforge
.net
/)
2 * Copyright (c
) 2006-2007, Adam Chlipala
3 * Copyright (c
) 2011,2012,2013 Clinton Ebadi
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
.
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
.
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
.
20 (* Firewall management
*)
22 (* Contains portions from Fwtool
Copyright (C
) 2005 Adam Chlipala
, GPL v2 or later
*)
24 structure Firewall
:> FIREWALL
= struct
26 datatype user
= User
of string
28 datatype fwnode
= FirewallNode
of string
30 datatype fwrule
= Client
of int list
* string list
31 | Server
of int list
* string list
32 | ProxiedServer
of int list
33 | LocalServer
of int list
35 type firewall_rules
= (user
* fwnode
* fwrule
) list
37 structure StringMap
= DataStructures
.StringMap
41 val inf
= TextIO.openIn Config
.Firewall
.firewallRules
43 fun parsePorts ports
=
44 List.mapPartial
Int.fromString (String.fields (fn ch
=> ch
= #
",") ports
)
45 (* Just drop bad ports for now
*)
47 fun loop parsedRules
=
48 case TextIO.inputLine inf
of
51 case String.tokens
Char.isSpace line
of
52 node
:: uname
:: rest
=>
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
65 (* completely broken
*)
67 val rules
= parseRules ()
69 (* map (fn (_
, FirewallNode n
, r
) => (n
, r
)) (List.filter (fn (User u
, _
, _
) => u
= uname
) rules
) *)
73 fun formatPorts ports
= "(" ^
String.concatWith
" " (map
Int.toString ports
) ^
")"
74 fun formatHosts hosts
= "(" ^
String.concatWith
" " hosts ^
")"
76 fun formatOutputRule (Client (ports
, hosts
)) = "dport " ^ formatPorts ports ^
(case hosts
of
78 | _
=> " daddr " ^ formatHosts hosts
) ^
" ACCEPT;"
80 fun formatInputRule (Server (ports
, hosts
)) = "dport " ^ formatPorts ports ^
(case hosts
of
82 | _
=> " saddr " ^ formatHosts hosts
) ^
" ACCEPT;"
84 type ferm_lines
= { input_rules
: (string list
) DataStructures
.StringMap
.map
,
85 output_rules
: (string list
) DataStructures
.StringMap
.map
}
87 fun generateNodeFermRules rules
=
89 fun filter_node_rules rules
=
90 List.filter (fn (uname
, FirewallNode node
, rule
) => node
= Slave
.hostname () orelse case rule
of
91 ProxiedServer _
=> List.exists (fn (h
,_
) => h
= Slave
.hostname ()) Config
.Apache
.webNodes_all
95 val inputLines
= ref StringMap
.empty
96 val outputLines
= ref StringMap
.empty
98 fun confLine
r (User uname
, line
) =
100 val line
= "\t" ^ line ^
"\n"
101 val lines
= case StringMap
.find (!r
, uname
) of
103 | SOME lines
=> lines
105 r
:= StringMap
.insert (!r
, uname
, line
:: lines
)
108 fun confLine_in (uname
, rule
) = confLine
inputLines (uname
, formatInputRule rule
)
109 fun confLine_out (uname
, rule
) = confLine
outputLines (uname
, formatOutputRule rule
)
111 fun insertConfLine (uname
, ruleNode
, rule
) =
113 Client (ports
, hosts
) => confLine_out (uname
, rule
)
114 |
Server (ports
, hosts
) => confLine_in (uname
, rule
)
115 | LocalServer ports
=> (insertConfLine (uname
, ruleNode
, Client (ports
, ["127.0.0.1/8"]));
116 insertConfLine (uname
, ruleNode
, Server (ports
, ["127.0.0.1/8"])))
117 | ProxiedServer ports
=> if (fn FirewallNode r
=> r
) ruleNode
= Slave
.hostname () then
118 (insertConfLine (uname
, ruleNode
, Server (ports
, ["$WEBNODES"]));
119 insertConfLine (uname
, ruleNode
, Client (ports
, [(fn FirewallNode r
=> r
) ruleNode
])))
120 else (* we are a web server
*)
121 (insertConfLine (uname
, ruleNode
, Client (ports
, [(fn FirewallNode r
=> r
) ruleNode
]));
122 insertConfLine (User
"www-data", ruleNode
, Client (ports
, [(fn FirewallNode r
=> r
) ruleNode
])))
124 val _
= map
insertConfLine (filter_node_rules rules
)
126 { input_rules
= !inputLines
,
127 output_rules
= !outputLines
}
132 fun generateFirewallConfig rules
=
133 (* rule generation must happen on the
node (mandating the even
134 service users be pts users would make it possible to
do on the
135 server
, but that
's not happening any time soon
) *)
137 val users_tcp_out_conf
= TextIO.openOut (Config
.Firewall
.firewallDir ^
"/users_tcp_out.conf")
138 val users_tcp_in_conf
= TextIO.openOut (Config
.Firewall
.firewallDir ^
"/users_tcp_in.conf")
139 val user_chains_conf
= TextIO.openOut (Config
.Firewall
.firewallDir ^
"/user_chains.conf")
141 val nodeFermRules
= generateNodeFermRules rules
143 fun write_tcp_in_conf_preamble outf
=
144 TextIO.output (outf
, String.concat
["@def $WEBNODES = (",
145 (String.concatWith
" " (List.map (fn (_
, ip
) => ip
)
146 (List.filter (fn (node
, _
) => List.exists (fn (n
) => n
= node
) (List.map (fn (node
, _
) => node
) (Config
.Apache
.webNodes_all @ Config
.Apache
.webNodes_admin
)))
150 fun writeUserInRules (uname
, lines
) =
151 (* We can
't match the user when listening
; SELinux or
152 similar would
let us manage this
with better
155 val _
= SysWord
.toInt (Posix
.ProcEnv
.uidToWord (Posix
.SysDB
.Passwd
.uid (Posix
.SysDB
.getpwnam uname
)))
157 TextIO.output (users_tcp_in_conf
, "proto tcp {\n");
158 TextIO.output (users_tcp_in_conf
, concat lines
);
159 TextIO.output (users_tcp_in_conf
, "\n}\n\n")
160 end handle OS
.SysErr _
=> print
"Invalid user in firewall config, skipping.\n" (* no sense
in opening ports for bad users
*)
162 fun writeUserOutRules (uname
, lines
) =
164 val uid
= SysWord
.toInt (Posix
.ProcEnv
.uidToWord (Posix
.SysDB
.Passwd
.uid (Posix
.SysDB
.getpwnam uname
)))
166 TextIO.output (users_tcp_out_conf
, "mod owner uid-owner " ^
(Int.toString uid
)
167 ^
" { jump user_" ^ uname ^
"_tcp_out"
170 TextIO.output (user_chains_conf
, "chain user_" ^ uname ^
"_tcp_out"
172 TextIO.output (user_chains_conf
, concat lines
);
173 TextIO.output (user_chains_conf
, "\n}\n\n")
174 end handle OS
.SysErr _
=> print
"Invalid user in firewall config, skipping.\n"
177 write_tcp_in_conf_preamble (users_tcp_in_conf
);
178 StringMap
.appi (writeUserOutRules
) (#output_rules nodeFermRules
);
179 StringMap
.appi (writeUserInRules
) (#input_rules nodeFermRules
);
181 TextIO.closeOut user_chains_conf
;
182 TextIO.closeOut users_tcp_out_conf
;
183 TextIO.closeOut users_tcp_in_conf
;
188 fun publishConfig _
=
189 Slave
.shell
[Config
.Firewall
.reload
]