X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=src%2FPVE%2FFirewall.pm;h=95e00bddc55f9f972c49511f6631f64ad8169312;hb=180da76c1e2cabea4e737fe2320f877c38c6d268;hp=634ff90c8ab7f216af72a3e4b0f9590cf4c220ce;hpb=044409e5b479a4bbff6147dab3d8350fab156e58;p=pve-firewall.git diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index 634ff90..95e00bd 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -20,6 +20,7 @@ use IO::File; use Net::IP; use PVE::Tools qw(run_command lock_file dir_glob_foreach); use Encode; +use Storable qw(dclone); my $hostfw_conf_filename = "/etc/pve/local/host.fw"; my $pvefw_conf_dir = "/etc/pve/firewall"; @@ -145,14 +146,15 @@ my $log_level_hash = { # %rule # # name => optional +# enable => [0|1] # action => # proto => -# sport => -# dport => +# sport => port[,port[,port]].. or port:port +# dport => port[,port[,port]].. or port:port # log => optional, loglevel # logmsg => optional, logmsg - overwrites default -# iface_in -# iface_out +# iface_in => incomin interface +# iface_out => outgoing interface # match => optional, overwrites generation of match # target => optional, overwrites action @@ -548,7 +550,8 @@ my $FWACCEPTMARK_ON = "0x80000000/0x80000000"; my $FWACCEPTMARK_OFF = "0x00000000/0x80000000"; my $pve_std_chains = {}; -$pve_std_chains->{4} = { +my $pve_std_chains_conf = {}; +$pve_std_chains_conf->{4} = { 'PVEFW-SET-ACCEPT-MARK' => [ { target => "-j MARK --set-mark $FWACCEPTMARK_ON" }, ], @@ -584,10 +587,10 @@ $pve_std_chains->{4} = { # Drop packets with INVALID state { action => 'DROP', match => '-m conntrack --ctstate INVALID', }, # Drop Microsoft SMB noise - { action => 'DROP', proto => 'udp', dport => '135,445', nbdport => 2 }, - { action => 'DROP', proto => 'udp', dport => '137:139'}, + { action => 'DROP', proto => 'udp', dport => '135,445' }, + { action => 'DROP', proto => 'udp', dport => '137:139' }, { action => 'DROP', proto => 'udp', dport => '1024:65535', sport => 137 }, - { action => 'DROP', proto => 'tcp', dport => '135,139,445', nbdport => 3 }, + { action => 'DROP', proto => 'tcp', dport => '135,139,445' }, { action => 'DROP', proto => 'udp', dport => 1900 }, # UPnP # Drop new/NotSyn traffic so that it doesn't get logged { action => 'DROP', match => '-p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN' }, @@ -607,10 +610,10 @@ $pve_std_chains->{4} = { # Drop packets with INVALID state { action => 'DROP', match => '-m conntrack --ctstate INVALID', }, # Drop Microsoft SMB noise - { action => 'PVEFW-reject', proto => 'udp', dport => '135,445', nbdport => 2 }, + { action => 'PVEFW-reject', proto => 'udp', dport => '135,445' }, { action => 'PVEFW-reject', proto => 'udp', dport => '137:139'}, { action => 'PVEFW-reject', proto => 'udp', dport => '1024:65535', sport => 137 }, - { action => 'PVEFW-reject', proto => 'tcp', dport => '135,139,445', nbdport => 3 }, + { action => 'PVEFW-reject', proto => 'tcp', dport => '135,139,445' }, { action => 'DROP', proto => 'udp', dport => 1900 }, # UPnP # Drop new/NotSyn traffic so that it doesn't get logged { action => 'DROP', match => '-p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN' }, @@ -641,7 +644,7 @@ $pve_std_chains->{4} = { ], }; -$pve_std_chains->{6} = { +$pve_std_chains_conf->{6} = { 'PVEFW-SET-ACCEPT-MARK' => [ { target => "-j MARK --set-mark $FWACCEPTMARK_ON" }, ], @@ -680,10 +683,10 @@ $pve_std_chains->{6} = { # Drop packets with INVALID state { action => 'DROP', match => '-m conntrack --ctstate INVALID', }, # Drop Microsoft SMB noise - { action => 'DROP', proto => 'udp', dport => '135,445', nbdport => 2 }, + { action => 'DROP', proto => 'udp', dport => '135,445' }, { action => 'DROP', proto => 'udp', dport => '137:139'}, { action => 'DROP', proto => 'udp', dport => '1024:65535', sport => 137 }, - { action => 'DROP', proto => 'tcp', dport => '135,139,445', nbdport => 3 }, + { action => 'DROP', proto => 'tcp', dport => '135,139,445' }, { action => 'DROP', proto => 'udp', dport => 1900 }, # UPnP # Drop new/NotSyn traffic so that it doesn't get logged { action => 'DROP', match => '-p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN' }, @@ -704,10 +707,10 @@ $pve_std_chains->{6} = { # Drop packets with INVALID state { action => 'DROP', match => '-m conntrack --ctstate INVALID', }, # Drop Microsoft SMB noise - { action => 'PVEFW-reject', proto => 'udp', dport => '135,445', nbdport => 2 }, - { action => 'PVEFW-reject', proto => 'udp', dport => '137:139'}, + { action => 'PVEFW-reject', proto => 'udp', dport => '135,445' }, + { action => 'PVEFW-reject', proto => 'udp', dport => '137:139' }, { action => 'PVEFW-reject', proto => 'udp', dport => '1024:65535', sport => 137 }, - { action => 'PVEFW-reject', proto => 'tcp', dport => '135,139,445', nbdport => 3 }, + { action => 'PVEFW-reject', proto => 'tcp', dport => '135,139,445' }, { action => 'DROP', proto => 'udp', dport => 1900 }, # UPnP # Drop new/NotSyn traffic so that it doesn't get logged { action => 'DROP', match => '-p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN' }, @@ -1797,6 +1800,158 @@ sub ipset_get_chains { return $res; } +# substitude action of rule according to action hash +sub rule_substitude_action { + my ($rule, $actions) = @_; + + if (my $action = $rule->{action}) { + $rule->{action} = $actions->{$action} if defined($actions->{$action}); + } +} + +# generate a src or dst match +# $dir(ection) is either d or s +sub ipt_gen_src_or_dst_match { + my ($adr, $dir, $ipversion, $cluster_conf, $fw_conf) = @_; + + my $srcdst; + if ($dir eq 's') { + $srcdst = "src"; + } elsif ($dir eq 'd') { + $srcdst = "dst"; + } else { + die "ipt_gen_src_or_dst_match: invalid direction $dir \n"; + } + + my $match; + if ($adr =~ m/^\+/) { + if ($adr =~ m/^\+(${ipset_name_pattern})$/) { + my $name = $1; + my $ipset_chain; + if ($fw_conf && $fw_conf->{ipset}->{$name}) { + $ipset_chain = compute_ipset_chain_name($fw_conf->{vmid}, $name, $ipversion); + } elsif ($cluster_conf && $cluster_conf->{ipset}->{$name}) { + $ipset_chain = compute_ipset_chain_name(0, $name, $ipversion); + } else { + die "no such ipset '$name'\n"; + } + $match = "-m set --match-set ${ipset_chain} ${srcdst}"; + } else { + die "invalid security group name '$adr'\n"; + } + } elsif ($adr =~ m/^${ip_alias_pattern}$/){ + my $alias = lc($adr); + my $e = $fw_conf ? $fw_conf->{aliases}->{$alias} : undef; + $e = $cluster_conf->{aliases}->{$alias} if !$e && $cluster_conf; + die "no such alias '$adr'\n" if !$e; + $match = "-${dir} $e->{cidr}"; + } elsif ($adr =~ m/\-/){ + $match = "-m iprange --${srcdst}-range $adr"; + } else { + $match = "-${dir} $adr"; + } + + return $match; +} + +# convert a %rule to an array of iptables commands +sub ipt_rule_to_cmds { + my ($rule, $chain, $ipversion, $cluster_conf, $fw_conf, $vmid) = @_; + + die "ipt_rule_to_cmds unable to handle macro" if $rule->{macro}; #should not happen + + my @match = (); + + if (defined $rule->{match}) { + push @match, $rule->{match}; + } else { + push @match, "-i $rule->{iface_in}" if $rule->{iface_in}; + push @match, "-o $rule->{iface_out}" if $rule->{iface_out}; + + if ($rule->{source}) { + push @match, ipt_gen_src_or_dst_match($rule->{source}, 's', $ipversion, $cluster_conf, $fw_conf); + } + if ($rule->{dest}) { + push @match, ipt_gen_src_or_dst_match($rule->{dest}, 'd', $ipversion, $cluster_conf, $fw_conf); + } + + if (my $proto = $rule->{proto}) { + push @match, "-p $proto"; + + my $nbdport = defined($rule->{dport}) ? parse_port_name_number_or_range($rule->{dport}, 1) : 0; + my $nbsport = defined($rule->{sport}) ? parse_port_name_number_or_range($rule->{sport}, 0) : 0; + + my $multiport = 0; + $multiport++ if $nbdport > 1; + $multiport++ if $nbsport > 1; + + push @match, "--match multiport" if $multiport; + + die "multiport: option '--sports' cannot be used together with '--dports'\n" + if ($multiport == 2) && ($rule->{dport} ne $rule->{sport}); + + if ($rule->{dport}) { + if ($proto eq 'icmp') { + # Note: we use dport to store --icmp-type + die "unknown icmp-type '$rule->{dport}'\n" + if $rule->{dport} !~ /^\d+$/ && !defined($icmp_type_names->{$rule->{dport}}); + push @match, "-m icmp --icmp-type $rule->{dport}"; + } elsif ($proto eq 'icmpv6') { + # Note: we use dport to store --icmpv6-type + die "unknown icmpv6-type '$rule->{dport}'\n" + if $rule->{dport} !~ /^\d+$/ && !defined($icmpv6_type_names->{$rule->{dport}}); + push @match, "-m icmpv6 --icmpv6-type $rule->{dport}"; + } elsif (!$PROTOCOLS_WITH_PORTS->{$proto}) { + die "protocol $proto does not have ports\n"; + } else { + if ($nbdport > 1) { + if ($multiport == 2) { + push @match, "--ports $rule->{dport}"; + } else { + push @match, "--dports $rule->{dport}"; + } + } else { + push @match, "--dport $rule->{dport}"; + } + } + } + + if ($rule->{sport}) { + die "protocol $proto does not have ports\n" + if !$PROTOCOLS_WITH_PORTS->{$proto}; + if ($nbsport > 1) { + push @match, "--sports $rule->{sport}" if $multiport != 2; + } else { + push @match, "--sport $rule->{sport}"; + } + } + } elsif ($rule->{dport} || $rule->{sport}) { + die "destination port '$rule->{dport}', but no protocol specified\n" if $rule->{dport}; + die "source port '$rule->{sport}', but no protocol specified\n" if $rule->{sport}; + } + + push @match, "-m addrtype --dst-type $rule->{dsttype}" if $rule->{dsttype}; + } + my $matchstr = scalar(@match) ? join(' ', @match) : ""; + + my $targetstr; + if (defined $rule->{target}) { + $targetstr = $rule->{target}; + } else { + my $action = (defined $rule->{action}) ? $rule->{action} : ""; + my $goto = 1 if $action eq 'PVEFW-SET-ACCEPT-MARK'; + $targetstr = ($goto) ? "-g $action" : "-j $action"; + } + + my @iptcmds; + if (defined $rule->{log} && $rule->{log}) { + my $logaction = get_log_rule_base($chain, $vmid, $rule->{logmsg}, $rule->{log}); + push @iptcmds, "-A $chain $matchstr $logaction"; + } + push @iptcmds, "-A $chain $matchstr $targetstr"; + return @iptcmds; +} + sub ruleset_generate_match { my ($ruleset, $chain, $ipversion, $rule, $actions, $goto, $cluster_conf, $fw_conf) = @_; @@ -1818,63 +1973,8 @@ sub ruleset_generate_match { my $source = $rule->{source}; my $dest = $rule->{dest}; - if ($source) { - if ($source =~ m/^\+/) { - if ($source =~ m/^\+(${ipset_name_pattern})$/) { - my $name = $1; - if ($fw_conf && $fw_conf->{ipset}->{$name}) { - my $ipset_chain = compute_ipset_chain_name($fw_conf->{vmid}, $name, $ipversion); - push @cmd, "-m set --match-set ${ipset_chain} src"; - } elsif ($cluster_conf && $cluster_conf->{ipset}->{$name}) { - my $ipset_chain = compute_ipset_chain_name(0, $name, $ipversion); - push @cmd, "-m set --match-set ${ipset_chain} src"; - } else { - die "no such ipset '$name'\n"; - } - } else { - die "invalid security group name '$source'\n"; - } - } elsif ($source =~ m/^${ip_alias_pattern}$/){ - my $alias = lc($source); - my $e = $fw_conf ? $fw_conf->{aliases}->{$alias} : undef; - $e = $cluster_conf->{aliases}->{$alias} if !$e && $cluster_conf; - die "no such alias '$source'\n" if !$e; - push @cmd, "-s $e->{cidr}"; - } elsif ($source =~ m/\-/){ - push @cmd, "-m iprange --src-range $source"; - } else { - push @cmd, "-s $source"; - } - } - - if ($dest) { - if ($dest =~ m/^\+/) { - if ($dest =~ m/^\+(${ipset_name_pattern})$/) { - my $name = $1; - if ($fw_conf && $fw_conf->{ipset}->{$name}) { - my $ipset_chain = compute_ipset_chain_name($fw_conf->{vmid}, $name, $ipversion); - push @cmd, "-m set --match-set ${ipset_chain} dst"; - } elsif ($cluster_conf && $cluster_conf->{ipset}->{$name}) { - my $ipset_chain = compute_ipset_chain_name(0, $name, $ipversion); - push @cmd, "-m set --match-set ${ipset_chain} dst"; - } else { - die "no such ipset '$name'\n"; - } - } else { - die "invalid security group name '$dest'\n"; - } - } elsif ($dest =~ m/^${ip_alias_pattern}$/){ - my $alias = lc($dest); - my $e = $fw_conf ? $fw_conf->{aliases}->{$alias} : undef; - $e = $cluster_conf->{aliases}->{$alias} if !$e && $cluster_conf; - die "no such alias '$dest'\n" if !$e; - push @cmd, "-d $e->{cidr}"; - } elsif ($dest =~ m/^(\d+)\.(\d+).(\d+).(\d+)\-(\d+)\.(\d+).(\d+).(\d+)$/){ - push @cmd, "-m iprange --dst-range $dest"; - } else { - push @cmd, "-d $dest"; - } - } + push @cmd, ipt_gen_src_or_dst_match($source, 's', $ipversion, $cluster_conf, $fw_conf) if $source; + push @cmd, ipt_gen_src_or_dst_match($dest, 'd', $ipversion, $cluster_conf, $fw_conf) if $dest; if (my $proto = $rule->{proto}) { push @cmd, "-p $proto"; @@ -1949,17 +2049,6 @@ sub ruleset_generate_action { return scalar(@cmd) ? join(' ', @cmd) : undef; } -sub ruleset_generate_cmdstr { - my ($ruleset, $chain, $ipversion, $rule, $actions, $goto, $cluster_conf, $fw_conf) = @_; - my $match = ruleset_generate_match($ruleset, $chain, $ipversion, $rule, $actions, $goto, $cluster_conf, $fw_conf); - my $action = ruleset_generate_action($ruleset, $chain, $ipversion, $rule, $actions, $goto, $cluster_conf, $fw_conf); - - return undef if !(defined($match) or defined($action)); - my $ret = defined($match) ? $match : ""; - $ret = "$ret $action" if defined($action); - return $ret; -} - sub ruleset_generate_rule { my ($ruleset, $chain, $ipversion, $rule, $actions, $goto, $cluster_conf, $fw_conf) = @_; @@ -1972,25 +2061,12 @@ sub ruleset_generate_rule { } # update all or nothing - - # fixme: lots of temporary ugliness - my @mstrs = (); - my @astrs = (); - my @logging = (); - my @logmsg = (); - foreach my $tmp (@$rules) { - my $m = ruleset_generate_match($ruleset, $chain, $ipversion, $tmp, $actions, $goto, $cluster_conf, $fw_conf); - my $a = ruleset_generate_action($ruleset, $chain, $ipversion, $tmp, $actions, $goto, $cluster_conf, $fw_conf); - if (defined $m or defined $a) { - push @mstrs, defined($m) ? $m : ""; - push @astrs, defined($a) ? $a : ""; - push @logging, $tmp->{log}; - push @logmsg, $tmp->{logmsg}; - } + my @ipt_rule_cmds; + foreach my $r (@$rules) { + push @ipt_rule_cmds, ipt_rule_to_cmds($r, $chain, $ipversion, $cluster_conf, $fw_conf); } - - for my $i (0 .. $#mstrs) { - ruleset_addrule($ruleset, $chain, $mstrs[$i], $astrs[$i], $logging[$i], $logmsg[$i]); + foreach my $c (@ipt_rule_cmds) { + ruleset_add_ipt_cmd($ruleset, $chain, $c); } } @@ -2023,6 +2099,15 @@ sub ruleset_chain_exist { return $ruleset->{$chain} ? 1 : undef; } +# add an iptables command (like generated by ipt_rule_to_cmds) to a chain +sub ruleset_add_ipt_cmd { + my ($ruleset, $chain, $iptcmd) = @_; + + die "no such chain '$chain'\n" if !$ruleset->{$chain}; + + push @{$ruleset->{$chain}}, $iptcmd; +} + sub ruleset_addrule { my ($ruleset, $chain, $match, $action, $log, $logmsg, $vmid) = @_; @@ -2060,8 +2145,9 @@ sub ruleset_add_chain_policy { if ($policy eq 'ACCEPT') { - ruleset_generate_rule($ruleset, $chain, $ipversion, { action => 'ACCEPT' }, - { ACCEPT => $accept_action}); + my $rule = { action => 'ACCEPT' }; + rule_substitude_action($rule, { ACCEPT => $accept_action}); + ruleset_generate_rule($ruleset, $chain, $ipversion, $rule); } elsif ($policy eq 'DROP') { @@ -2207,12 +2293,12 @@ sub ruleset_generate_vm_rules { next if $rule->{type} ne $lc_direction; eval { if ($direction eq 'OUT') { - ruleset_generate_rule($ruleset, $chain, $ipversion, $rule, - { ACCEPT => "PVEFW-SET-ACCEPT-MARK", REJECT => "PVEFW-reject" }, + rule_substitude_action($rule, { ACCEPT => "PVEFW-SET-ACCEPT-MARK", REJECT => "PVEFW-reject" }); + ruleset_generate_rule($ruleset, $chain, $ipversion, $rule, undef, undef, $cluster_conf, $vmfw_conf); } else { - ruleset_generate_rule($ruleset, $chain, $ipversion, $rule, - { ACCEPT => $in_accept , REJECT => "PVEFW-reject" }, + rule_substitude_action($rule, { ACCEPT => $in_accept , REJECT => "PVEFW-reject" }); + ruleset_generate_rule($ruleset, $chain, $ipversion, $rule, undef, undef, $cluster_conf, $vmfw_conf); } }; @@ -2341,8 +2427,8 @@ sub enable_host_firewall { if ($rule->{type} eq 'group') { ruleset_add_group_rule($ruleset, $cluster_conf, $chain, $rule, 'IN', $accept_action, $ipversion); } elsif ($rule->{type} eq 'in') { - ruleset_generate_rule($ruleset, $chain, $ipversion, $rule, - { ACCEPT => $accept_action, REJECT => "PVEFW-reject" }, + rule_substitude_action($rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" }); + ruleset_generate_rule($ruleset, $chain, $ipversion, $rule, undef, undef, $cluster_conf, $hostfw_conf); } }; @@ -2398,8 +2484,8 @@ sub enable_host_firewall { if ($rule->{type} eq 'group') { ruleset_add_group_rule($ruleset, $cluster_conf, $chain, $rule, 'OUT', $accept_action, $ipversion); } elsif ($rule->{type} eq 'out') { - ruleset_generate_rule($ruleset, $chain, $ipversion, - $rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" }, + rule_substitude_action($rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" }); + ruleset_generate_rule($ruleset, $chain, $ipversion, $rule, undef, undef, $cluster_conf, $hostfw_conf); } }; @@ -2445,9 +2531,8 @@ sub generate_group_rules { foreach my $rule (@$rules) { next if $rule->{type} ne 'in'; next if $rule->{ipversion} && $rule->{ipversion} ne $ipversion; - ruleset_generate_rule($ruleset, $chain, $ipversion, $rule, - { ACCEPT => "PVEFW-SET-ACCEPT-MARK", REJECT => "PVEFW-reject" }, - undef, $cluster_conf); + rule_substitude_action($rule, { ACCEPT => "PVEFW-SET-ACCEPT-MARK", REJECT => "PVEFW-reject" }); + ruleset_generate_rule($ruleset, $chain, $ipversion, $rule, undef, undef, $cluster_conf); } $chain = "GROUP-${group}-OUT"; @@ -2460,8 +2545,8 @@ sub generate_group_rules { next if $rule->{ipversion} && $rule->{ipversion} ne $ipversion; # we use PVEFW-SET-ACCEPT-MARK (Instead of ACCEPT) because we need to # check also other tap rules later - ruleset_generate_rule($ruleset, $chain, $ipversion, $rule, - { ACCEPT => 'PVEFW-SET-ACCEPT-MARK', REJECT => "PVEFW-reject" }, + rule_substitude_action($rule, { ACCEPT => 'PVEFW-SET-ACCEPT-MARK', REJECT => "PVEFW-reject" }); + ruleset_generate_rule($ruleset, $chain, $ipversion, $rule, undef, undef, $cluster_conf); } } @@ -3354,6 +3439,9 @@ sub compile { my $vmfw_configs; + # fixme: once we read standard chains from config this needs to be put in test/standard cases below + $pve_std_chains = dclone($pve_std_chains_conf); + if ($vmdata) { # test mode my $testdir = $vmdata->{testdir} || die "no test directory specified"; my $filename = "$testdir/cluster.fw";