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