]> git.proxmox.com Git - pve-firewall.git/blame - src/PVE/Service/pve_firewall.pm
make verbose a global state
[pve-firewall.git] / src / PVE / Service / pve_firewall.pm
CommitLineData
0c32b7fb
DM
1package PVE::Service::pve_firewall;
2
3use strict;
4use warnings;
5use PVE::SafeSyslog;
6use PVE::Daemon;
7
8use Time::HiRes qw (gettimeofday);
9use PVE::Tools qw(dir_glob_foreach file_read_firstline);
10use PVE::ProcFSTools;
11use PVE::INotify;
12use PVE::Cluster qw(cfs_read_file);
13use PVE::RPCEnvironment;
14use PVE::CLIHandler;
15use PVE::Firewall;
16use PVE::FirewallSimulator;
17use Data::Dumper;
18
19use base qw(PVE::Daemon);
20
21my $cmdline = [$0, @ARGV];
22
23my %daemon_options = (restart_on_error => 5, stop_wait_time => 5);
24
25my $daemon = __PACKAGE__->new('pve-firewall', $cmdline, %daemon_options);
26
27my $nodename = PVE::INotify::nodename();
28
29sub init {
30
31 PVE::Cluster::cfs_update();
32
33 PVE::Firewall::init();
34}
35
36my $restart_request = 0;
37my $next_update = 0;
38
39my $cycle = 0;
40my $updatetime = 10;
41
42my $initial_memory_usage;
43
44sub shutdown {
45 my ($self) = @_;
46
47 syslog('info' , "server closing");
48
49 # wait for children
50 1 while (waitpid(-1, POSIX::WNOHANG()) > 0);
51
52 syslog('info' , "clear firewall rules");
53
54 eval { PVE::Firewall::remove_pvefw_chains(); };
55 warn $@ if $@;
56
57 $self->exit_daemon(0);
58}
59
60sub hup {
61 my ($self) = @_;
62
63 $restart_request = 1;
64}
65
66sub run {
67 my ($self) = @_;
68
69 local $SIG{'__WARN__'} = 'IGNORE'; # do not fill up logs
70
71 for (;;) { # forever
72
73 $next_update = time() + $updatetime;
74
75 my ($ccsec, $cusec) = gettimeofday ();
76 eval {
77 PVE::Cluster::cfs_update();
78 PVE::Firewall::update();
79 };
80 my $err = $@;
81
82 if ($err) {
83 syslog('err', "status update error: $err");
84 }
85
86 my ($ccsec_end, $cusec_end) = gettimeofday ();
87 my $cptime = ($ccsec_end-$ccsec) + ($cusec_end - $cusec)/1000000;
88
89 syslog('info', sprintf("firewall update time (%.3f seconds)", $cptime))
90 if ($cptime > 5);
91
92 $cycle++;
93
94 my $mem = PVE::ProcFSTools::read_memory_usage();
95
96 if (!defined($initial_memory_usage) || ($cycle < 10)) {
97 $initial_memory_usage = $mem->{resident};
98 } else {
99 my $diff = $mem->{resident} - $initial_memory_usage;
100 if ($diff > 5*1024*1024) {
101 syslog ('info', "restarting server after $cycle cycles to " .
102 "reduce memory usage (free $mem->{resident} ($diff) bytes)");
103 $self->restart_daemon();
104 }
105 }
106
107 my $wcount = 0;
108 while ((time() < $next_update) &&
109 ($wcount < $updatetime) && # protect against time wrap
110 !$restart_request) { $wcount++; sleep (1); };
111
112 $self->restart_daemon() if $restart_request;
113 }
114}
115
116$daemon->register_start_command("Start the Proxmox VE firewall service.");
117$daemon->register_restart_command(1, "Restart the Proxmox VE firewall service.");
118$daemon->register_stop_command("Stop firewall. This removes all Proxmox VE " .
119 "related iptable rules. " .
120 "The host is unprotected afterwards.");
121
122__PACKAGE__->register_method ({
123 name => 'status',
124 path => 'status',
125 method => 'GET',
126 description => "Get firewall status.",
127 parameters => {
128 additionalProperties => 0,
129 properties => {},
130 },
131 returns => {
132 type => 'object',
133 additionalProperties => 0,
134 properties => {
135 status => {
136 type => 'string',
137 enum => ['unknown', 'stopped', 'running'],
138 },
139 enable => {
140 description => "Firewall is enabled (in 'cluster.fw')",
141 type => 'boolean',
142 },
143 changes => {
144 description => "Set when there are pending changes.",
145 type => 'boolean',
146 optional => 1,
147 }
148 },
149 },
150 code => sub {
151 my ($param) = @_;
152
153 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
154
155 my $code = sub {
156
157 my $status = $daemon->running() ? 'running' : 'stopped';
158
159 my $res = { status => $status };
160
40af93c4
TL
161 PVE::Firewall::set_verbose(1); # show syntax errors
162
163 my $cluster_conf = PVE::Firewall::load_clusterfw_conf(undef);
0c32b7fb
DM
164 $res->{enable} = $cluster_conf->{options}->{enable} ? 1 : 0;
165
166 if ($status eq 'running') {
167
40af93c4 168 my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE::Firewall::compile($cluster_conf, undef, undef);
0c32b7fb 169
40af93c4
TL
170 PVE::Firewall::set_verbose(0); # do not show iptables details
171 my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset);
172 my ($test, $ruleset_changes) = PVE::Firewall::get_ruleset_cmdlist($ruleset);
173 my (undef, $ruleset_changesv6) = PVE::Firewall::get_ruleset_cmdlist($rulesetv6, "ip6tables");
174 my (undef, $ebtables_changes) = PVE::Firewall::get_ebtables_cmdlist($ebtables_ruleset);
0c32b7fb 175
151c209e 176 $res->{changes} = ($ipset_changes || $ruleset_changes || $ruleset_changesv6 || $ebtables_changes) ? 1 : 0;
0c32b7fb
DM
177 }
178
179 return $res;
180 };
181
182 return PVE::Firewall::run_locked($code);
183 }});
184
185__PACKAGE__->register_method ({
186 name => 'compile',
187 path => 'compile',
188 method => 'GET',
189 description => "Compile and print firewall rules. This is useful for testing.",
190 parameters => {
191 additionalProperties => 0,
192 properties => {},
193 },
194 returns => { type => 'null' },
195
196 code => sub {
197 my ($param) = @_;
198
199 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
200
201 my $code = sub {
202
40af93c4 203 PVE::Firewall::set_verbose(1);
0c32b7fb 204
40af93c4
TL
205 my $cluster_conf = PVE::Firewall::load_clusterfw_conf(undef);
206 my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE::Firewall::compile($cluster_conf, undef, undef);
0c32b7fb
DM
207
208 print "ipset cmdlist:\n";
40af93c4 209 my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset);
0c32b7fb
DM
210
211 print "\niptables cmdlist:\n";
40af93c4 212 my (undef, $ruleset_changes) = PVE::Firewall::get_ruleset_cmdlist($ruleset);
0c32b7fb
DM
213
214 print "\nip6tables cmdlist:\n";
40af93c4 215 my (undef, $ruleset_changesv6) = PVE::Firewall::get_ruleset_cmdlist($rulesetv6, "ip6tables");
0c32b7fb 216
151c209e 217 print "\nebtables cmdlist:\n";
40af93c4 218 my (undef, $ebtables_changes) = PVE::Firewall::get_ebtables_cmdlist($ebtables_ruleset);
151c209e
AD
219
220 if ($ipset_changes || $ruleset_changes || $ruleset_changesv6 || $ebtables_changes) {
0c32b7fb
DM
221 print "detected changes\n";
222 } else {
223 print "no changes\n";
224 }
225 if (!$cluster_conf->{options}->{enable}) {
226 print "firewall disabled\n";
227 }
228
229 };
230
231 PVE::Firewall::run_locked($code);
232
233 return undef;
234 }});
235
236__PACKAGE__->register_method ({
237 name => 'localnet',
238 path => 'localnet',
239 method => 'GET',
240 description => "Print information about local network.",
241 parameters => {
242 additionalProperties => 0,
243 properties => {},
244 },
245 returns => { type => 'null' },
246 code => sub {
247 my ($param) = @_;
248
249 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
250
251 my $nodename = PVE::INotify::nodename();
252 print "local hostname: $nodename\n";
253
254 my $ip = PVE::Cluster::remote_node_ip($nodename);
255 print "local IP address: $ip\n";
256
257 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
258
259 my $localnet = PVE::Firewall::local_network() || '127.0.0.0/8';
260 print "network auto detect: $localnet\n";
261 if ($cluster_conf->{aliases}->{local_network}) {
262 print "using user defined local_network: $cluster_conf->{aliases}->{local_network}->{cidr}\n";
263 } else {
264 print "using detected local_network: $localnet\n";
265 }
266
267 return undef;
268 }});
269
270__PACKAGE__->register_method ({
271 name => 'simulate',
272 path => 'simulate',
273 method => 'GET',
274 description => "Simulate firewall rules. This does not simulate kernel 'routing' table. Instead, this simply assumes that routing from source zone to destination zone is possible.",
275 parameters => {
276 additionalProperties => 0,
277 properties => {
278 verbose => {
279 description => "Verbose output.",
280 type => 'boolean',
281 optional => 1,
282 default => 0,
283 },
284 from => {
285 description => "Source zone.",
286 type => 'string',
287 pattern => '(host|outside|vm\d+|ct\d+|vmbr\d+/\S+)',
288 optional => 1,
289 default => 'outside',
290 },
291 to => {
292 description => "Destination zone.",
293 type => 'string',
294 pattern => '(host|outside|vm\d+|ct\d+|vmbr\d+/\S+)',
295 optional => 1,
296 default => 'host',
297 },
298 protocol => {
299 description => "Protocol.",
300 type => 'string',
301 pattern => '(tcp|udp)',
302 optional => 1,
303 default => 'tcp',
304 },
305 dport => {
306 description => "Destination port.",
307 type => 'integer',
308 minValue => 1,
309 maxValue => 65535,
310 optional => 1,
311 },
312 sport => {
313 description => "Source port.",
314 type => 'integer',
315 minValue => 1,
316 maxValue => 65535,
317 optional => 1,
318 },
319 source => {
320 description => "Source IP address.",
321 type => 'string', format => 'ipv4',
322 optional => 1,
323 },
324 dest => {
325 description => "Destination IP address.",
326 type => 'string', format => 'ipv4',
327 optional => 1,
328 },
329 },
330 },
331 returns => { type => 'null' },
332 code => sub {
333 my ($param) = @_;
334
335 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
336
40af93c4
TL
337 PVE::Firewall::set_verbose($param->{verbose});
338
339 my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE::Firewall::compile();
0c32b7fb 340
40af93c4 341 PVE::FirewallSimulator::debug();
0c32b7fb
DM
342
343 my $host_ip = PVE::Cluster::remote_node_ip($nodename);
344
345 PVE::FirewallSimulator::reset_trace();
346 print Dumper($ruleset) if $param->{verbose};
347
348 my $test = {
349 from => $param->{from},
350 to => $param->{to},
351 proto => $param->{protocol} || 'tcp',
352 source => $param->{source},
353 dest => $param->{dest},
354 dport => $param->{dport},
355 sport => $param->{sport},
356 };
357
358 if (!defined($test->{to})) {
359 $test->{to} = 'host';
360 PVE::FirewallSimulator::add_trace("Set Zone: to => '$test->{to}'\n");
361 }
362 if (!defined($test->{from})) {
363 $test->{from} = 'outside',
364 PVE::FirewallSimulator::add_trace("Set Zone: from => '$test->{from}'\n");
365 }
366
367 my $vmdata = PVE::Firewall::read_local_vm_config();
368
369 print "Test packet:\n";
370
371 foreach my $k (qw(from to proto source dest dport sport)) {
372 printf(" %-8s: %s\n", $k, $test->{$k}) if defined($test->{$k});
373 }
374
375 $test->{action} = 'QUERY';
376
377 my $res = PVE::FirewallSimulator::simulate_firewall($ruleset, $ipset_ruleset,
378 $host_ip, $vmdata, $test);
379
380 print "ACTION: $res\n";
381
382 return undef;
383 }});
384
385our $cmddef = {
386 start => [ __PACKAGE__, 'start', []],
387 restart => [ __PACKAGE__, 'restart', []],
388 stop => [ __PACKAGE__, 'stop', []],
389 compile => [ __PACKAGE__, 'compile', []],
390 simulate => [ __PACKAGE__, 'simulate', []],
391 localnet => [ __PACKAGE__, 'localnet', []],
392 status => [ __PACKAGE__, 'status', [], undef, sub {
393 my $res = shift;
394 my $status = ($res->{enable} ? "enabled" : "disabled") . '/' . $res->{status};
395
396 if ($res->{changes}) {
397 print "Status: $status (pending changes)\n";
398 } else {
399 print "Status: $status\n";
400 }
401 }],
402 };
403
4041;