X-Git-Url: https://git.proxmox.com/?p=pve-firewall.git;a=blobdiff_plain;f=src%2FPVE%2FFirewall.pm;h=ae124c2d665f81405000f999df4ac351887837d3;hp=2f876ce5f8d6c3cd9fd9423829cdda7207d4230e;hb=a42e97637036df459c695eb0df693311a7fa11bb;hpb=b3d75afb1ac1b4577efa9f73e2c18c3c0d1ecb39 diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index 2f876ce..ae124c2 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -23,7 +23,7 @@ my $hostfw_conf_filename = "/etc/pve/local/host.fw"; my $pvefw_conf_dir = "/etc/pve/firewall"; my $clusterfw_conf_filename = "$pvefw_conf_dir/cluster.fw"; -# dynamically include PVE::QemuServer and PVE::OpenVZ +# dynamically include PVE::QemuServer and PVE::LXC # to avoid dependency problems my $have_qemu_server; eval { @@ -31,12 +31,13 @@ eval { $have_qemu_server = 1; }; -my $have_pve_manager; +my $have_lxc; eval { - require PVE::OpenVZ; - $have_pve_manager = 1; + require PVE::LXC; + $have_lxc = 1; }; + my $pve_fw_status_dir = "/var/lib/pve-firewall"; mkdir $pve_fw_status_dir; # make sure this exists @@ -141,6 +142,9 @@ my $pve_ipv6fw_macros = { { action => 'PARAM', proto => 'icmpv6', dport => 'neighbor-solicitation' }, { action => 'PARAM', proto => 'icmpv6', dport => 'neighbor-advertisement' }, ], + 'DHCPv6' => [ + { action => 'PARAM', proto => 'udp', dport => '546:547', sport => '546:547' }, + ], 'Trcrt' => [ { action => 'PARAM', proto => 'udp', dport => '33434:33524' }, { action => 'PARAM', proto => 'icmpv6', dport => 'echo-request' }, @@ -591,7 +595,7 @@ $pve_std_chains->{4} = { 'PVEFW-smurfs' => [ # same as shorewall smurfs action # Filter packets for smurfs (packets with a broadcast address as the source). - "-s 0.0.0.0/32 -j RETURN", + "-s 0.0.0.0/32 -j RETURN", # allow DHCP "-m addrtype --src-type BROADCAST -g PVEFW-smurflog", "-s 224.0.0.0/4 -g PVEFW-smurflog", ], @@ -899,14 +903,20 @@ sub local_network { my $testip = Net::IP->new($ip); - my $routes = PVE::ProcFSTools::read_proc_net_route(); + my $isv6 = $testip->version == 6; + my $routes = $isv6 ? PVE::ProcFSTools::read_proc_net_ipv6_route() + : PVE::ProcFSTools::read_proc_net_route(); foreach my $entry (@$routes) { - my $mask = $ipv4_mask_hash_localnet->{$entry->{mask}}; - next if !defined($mask); - return if $mask eq '0.0.0.0'; + my $mask; + if ($isv6) { + $mask = $entry->{prefix}; + } else { + $mask = $ipv4_mask_hash_localnet->{$entry->{mask}}; + next if !defined($mask); + } my $cidr = "$entry->{dest}/$mask"; my $testnet = Net::IP->new($cidr); - if ($testnet->overlaps($testip)) { + if ($testnet->overlaps($testip) == $Net::IP::IP_B_IN_A_OVERLAP) { $__local_network = $cidr; return; } @@ -1191,7 +1201,9 @@ my $apply_macro = sub { } # skip macros which are specific to another ipversion - return if ($ipversion//0) != ($pve_fw_macro_ipversion->{$macro_name}//0); + if ($ipversion && (my $required = $pve_fw_macro_ipversion->{$macro_name})) { + return if $ipversion != $required; + } my $rules = []; @@ -1325,12 +1337,9 @@ sub verify_rule { if !$allow_iface; eval { PVE::JSONSchema::pve_verify_iface($rule->{iface}); }; &$add_error('iface', $@) if $@; - if ($rule_env eq 'vm') { + if ($rule_env eq 'vm' || $rule_env eq 'ct') { &$add_error('iface', "value does not match the regex pattern 'net\\d+'") if $rule->{iface} !~ m/^net(\d+)$/; - } elsif ($rule_env eq 'ct') { - &$add_error('iface', "value does not match the regex pattern '(venet|eth\\d+)'") - if $rule->{iface} !~ m/^(venet|eth(\d+))$/; } } @@ -1429,7 +1438,7 @@ sub rules_modify_permissions { return { check => ['perm', '/', [ 'Sys.Modify' ]], }; - } elsif ($rule_env eq 'vm' || $rule_env eq 'ct') { + } elsif ($rule_env eq 'vm' || $rule_env eq 'ct') { return { check => ['perm', '/vms/{vmid}', [ 'VM.Config.Network' ]], } @@ -1449,7 +1458,7 @@ sub rules_audit_permissions { return { check => ['perm', '/', [ 'Sys.Audit' ]], }; - } elsif ($rule_env eq 'vm' || $rule_env eq 'ct') { + } elsif ($rule_env eq 'vm' || $rule_env eq 'ct') { return { check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]], } @@ -1507,14 +1516,12 @@ sub iptables_get_chains { return 1 if $name =~ m/^PVEFW-\S+$/; - return 1 if $name =~ m/^tap\d+i\d+-(:?IN|OUT)$/; + return 1 if $name =~ m/^tap\d+i\d+-(?:IN|OUT)$/; - return 1 if $name =~ m/^veth\d+.\d+-(:?IN|OUT)$/; # fixme: dev name is configurable + return 1 if $name =~ m/^veth\d+i\d+-(?:IN|OUT)$/; - return 1 if $name =~ m/^venet0-\d+-(:?IN|OUT)$/; - - return 1 if $name =~ m/^fwbr\d+(v\d+)?-(:?FW|IN|OUT|IPS)$/; - return 1 if $name =~ m/^GROUP-(:?[^\s\-]+)-(:?IN|OUT)$/; + return 1 if $name =~ m/^fwbr\d+(v\d+)?-(?:FW|IN|OUT|IPS)$/; + return 1 if $name =~ m/^GROUP-(?:$security_group_name_pattern)-(?:IN|OUT)$/; return undef; }; @@ -1698,11 +1705,13 @@ sub ruleset_generate_cmdstr { if ($rule->{dport}) { if ($rule->{proto} && $rule->{proto} eq 'icmp') { # Note: we use dport to store --icmp-type - die "unknown icmp-type '$rule->{dport}'\n" if !defined($icmp_type_names->{$rule->{dport}}); + die "unknown icmp-type '$rule->{dport}'\n" + if $rule->{dport} !~ /^\d+$/ && !defined($icmp_type_names->{$rule->{dport}}); push @cmd, "-m icmp --icmp-type $rule->{dport}"; } elsif ($rule->{proto} && $rule->{proto} eq 'icmpv6') { # Note: we use dport to store --icmpv6-type - die "unknown icmpv6-type '$rule->{dport}'\n" if !defined($icmpv6_type_names->{$rule->{dport}}); + die "unknown icmpv6-type '$rule->{dport}'\n" + if $rule->{dport} !~ /^\d+$/ && !defined($icmpv6_type_names->{$rule->{dport}}); push @cmd, "-m icmpv6 --icmpv6-type $rule->{dport}"; } else { if ($nbdport > 1) { @@ -1898,15 +1907,28 @@ sub ruleset_create_vm_chain { my $accept = generate_nfqueue($options); if (!(defined($options->{dhcp}) && $options->{dhcp} == 0)) { - if ($direction eq 'OUT') { - ruleset_generate_rule($ruleset, $chain, $ipversion, - { action => 'PVEFW-SET-ACCEPT-MARK', - proto => 'udp', sport => 68, dport => 67 }); - } else { - ruleset_generate_rule($ruleset, $chain, $ipversion, - { action => 'ACCEPT', - proto => 'udp', sport => 67, dport => 68 }); + if ($ipversion == 4) { + if ($direction eq 'OUT') { + ruleset_generate_rule($ruleset, $chain, $ipversion, + { action => 'PVEFW-SET-ACCEPT-MARK', + proto => 'udp', sport => 68, dport => 67 }); + } else { + ruleset_generate_rule($ruleset, $chain, $ipversion, + { action => 'ACCEPT', + proto => 'udp', sport => 67, dport => 68 }); + } + } elsif ($ipversion == 6) { + if ($direction eq 'OUT') { + ruleset_generate_rule($ruleset, $chain, $ipversion, + { action => 'PVEFW-SET-ACCEPT-MARK', + proto => 'udp', sport => 546, dport => 547 }); + } else { + ruleset_generate_rule($ruleset, $chain, $ipversion, + { action => 'ACCEPT', + proto => 'udp', sport => 547, dport => 546 }); + } } + } if ($direction eq 'OUT') { @@ -2006,48 +2028,6 @@ sub ruleset_generate_vm_ipsrules { } } -sub generate_venet_rules_direction { - my ($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, $direction, $ipversion) = @_; - - my $lc_direction = lc($direction); - - my $rules = $vmfw_conf->{rules}; - - my $options = $vmfw_conf->{options}; - my $loglevel = get_option_log_level($options, "log_level_${lc_direction}"); - - my $chain = "venet0-$vmid-$direction"; - - ruleset_create_vm_chain($ruleset, $chain, $ipversion, $options, undef, undef, $direction); - - ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $vmfw_conf, $chain, 'venet', $direction, undef, $ipversion); - - # implement policy - my $policy; - - if ($direction eq 'OUT') { - $policy = $options->{policy_out} || 'ACCEPT'; # allow everything by default - } else { - $policy = $options->{policy_in} || 'DROP'; # allow nothing by default - } - - my $accept = generate_nfqueue($options); - my $accept_action = $direction eq 'OUT' ? "PVEFW-SET-ACCEPT-MARK" : $accept; - ruleset_add_chain_policy($ruleset, $chain, $ipversion, $vmid, $policy, $loglevel, $accept_action); - - if ($direction eq 'OUT') { - ruleset_generate_rule_insert($ruleset, "PVEFW-VENET-OUT", $ipversion, { - action => $chain, - source => $ip, - iface_in => 'venet0'}); - } else { - ruleset_generate_rule($ruleset, "PVEFW-VENET-IN", $ipversion, { - action => $chain, - dest => $ip, - iface_out => 'venet0'}); - } -} - sub generate_tap_rules_direction { my ($ruleset, $cluster_conf, $iface, $netid, $macaddr, $vmfw_conf, $vmid, $direction, $ipversion) = @_; @@ -2678,10 +2658,10 @@ sub run_locked { sub read_local_vm_config { - my $openvz = {}; my $qemu = {}; + my $lxc = {}; - my $vmdata = { openvz => $openvz, qemu => $qemu }; + my $vmdata = { qemu => $qemu, lxc => $lxc }; my $vmlist = PVE::Cluster::get_vmlist(); return $vmdata if !$vmlist || !$vmlist->{ids}; @@ -2692,21 +2672,21 @@ sub read_local_vm_config { my $d = $ids->{$vmid}; next if !$d->{node} || $d->{node} ne $nodename; next if !$d->{type}; - if ($d->{type} eq 'openvz') { - if ($have_pve_manager) { - my $cfspath = PVE::OpenVZ::cfs_config_path($vmid); - if (my $conf = PVE::Cluster::cfs_read_file($cfspath)) { - $openvz->{$vmid} = $conf; - } - } - } elsif ($d->{type} eq 'qemu') { + if ($d->{type} eq 'qemu') { if ($have_qemu_server) { my $cfspath = PVE::QemuServer::cfs_config_path($vmid); if (my $conf = PVE::Cluster::cfs_read_file($cfspath)) { $qemu->{$vmid} = $conf; } } - } + } elsif ($d->{type} eq 'lxc') { + if ($have_lxc) { + my $cfspath = PVE::LXC::cfs_config_path($vmid); + if (my $conf = PVE::Cluster::cfs_read_file($cfspath)) { + $lxc->{$vmid} = $conf; + } + } + } } return $vmdata; @@ -2857,6 +2837,29 @@ sub save_vmfw_conf { PVE::Tools::file_set_contents($filename, $raw); } +sub remove_vmfw_conf { + my ($vmid) = @_; + + my $vmfw_conffile = "$pvefw_conf_dir/$vmid.fw"; + + unlink $vmfw_conffile; +} + +sub clone_vmfw_conf { + my ($vmid, $newid) = @_; + + my $sourcevm_conffile = "$pvefw_conf_dir/$vmid.fw"; + my $clonevm_conffile = "$pvefw_conf_dir/$newid.fw"; + + if (-f $clonevm_conffile) { + unlink $clonevm_conffile; + } + if (-f $sourcevm_conffile) { + my $data = PVE::Tools::file_get_contents($sourcevm_conffile); + PVE::Tools::file_set_contents($clonevm_conffile, $data); + } +} + sub read_vm_firewall_configs { my ($cluster_conf, $vmdata, $dir, $verbose) = @_; @@ -2867,10 +2870,10 @@ sub read_vm_firewall_configs { next if !$vmfw_conf->{options}; # skip if file does not exists $vmfw_configs->{$vmid} = $vmfw_conf; } - foreach my $vmid (keys %{$vmdata->{openvz}}) { - my $vmfw_conf = load_vmfw_conf($cluster_conf, 'ct', $vmid, $dir, $verbose); - next if !$vmfw_conf->{options}; # skip if file does not exists - $vmfw_configs->{$vmid} = $vmfw_conf; + foreach my $vmid (keys %{$vmdata->{lxc}}) { + my $vmfw_conf = load_vmfw_conf($cluster_conf, 'ct', $vmid, $dir, $verbose); + next if !$vmfw_conf->{options}; # skip if file does not exists + $vmfw_configs->{$vmid} = $vmfw_conf; } return $vmfw_configs; @@ -2952,8 +2955,7 @@ sub generate_ipset_chains { } #http://backreference.org/2013/03/01/ipv6-address-normalization/ if ($ver == 6) { - my $ipv6 = inet_pton(AF_INET6, lc($cidr)); - $cidr = inet_ntop(AF_INET6, $ipv6); + $cidr = lc(Net::IP::ip_compress_address($cidr, 6)); $cidr =~ s|/128$||; } else { $cidr =~ s|/32$||; @@ -3120,9 +3122,6 @@ sub compile { sub compile_iptables_filter { my ($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, $ipversion, $verbose) = @_; - $cluster_conf->{ipset}->{venet0} = []; - my $venet0_ipset_chain = compute_ipset_chain_name(0, 'venet0', $ipversion); - my $localnet; if ($cluster_conf->{aliases}->{local_network}) { $localnet = $cluster_conf->{aliases}->{local_network}->{cidr}; @@ -3152,11 +3151,6 @@ sub compile_iptables_filter { ruleset_chain_add_conn_filters($ruleset, "PVEFW-FORWARD", "ACCEPT"); - - ruleset_create_chain($ruleset, "PVEFW-VENET-OUT"); - ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i venet0 -m set --match-set ${venet0_ipset_chain} src -j PVEFW-VENET-OUT"); - ruleset_addrule($ruleset, "PVEFW-INPUT", "-i venet0 -m set --match-set ${venet0_ipset_chain} src -j PVEFW-VENET-OUT"); - ruleset_create_chain($ruleset, "PVEFW-FWBR-IN"); ruleset_chain_add_input_filters($ruleset, "PVEFW-FWBR-IN", $ipversion, $hostfw_options, $cluster_conf, $loglevel); @@ -3165,11 +3159,6 @@ sub compile_iptables_filter { ruleset_create_chain($ruleset, "PVEFW-FWBR-OUT"); ruleset_addrule($ruleset, "PVEFW-FORWARD", "-m physdev --physdev-is-bridged --physdev-out fwln+ -j PVEFW-FWBR-OUT"); - ruleset_create_chain($ruleset, "PVEFW-VENET-IN"); - ruleset_chain_add_input_filters($ruleset, "PVEFW-VENET-IN", $ipversion, $hostfw_options, $cluster_conf, $loglevel); - - ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o venet0 -m set --match-set ${venet0_ipset_chain} dst -j PVEFW-VENET-IN"); - generate_std_chains($ruleset, $hostfw_options, $ipversion); my $hostfw_enable = !(defined($hostfw_options->{enable}) && ($hostfw_options->{enable} == 0)); @@ -3181,8 +3170,6 @@ sub compile_iptables_filter { warn $@ if $@; # just to be sure - should not happen } - ruleset_addrule($ruleset, "PVEFW-OUTPUT", "-o venet0 -m set --match-set ${venet0_ipset_chain} dst -j PVEFW-VENET-IN"); - # generate firewall rules for QEMU VMs foreach my $vmid (keys %{$vmdata->{qemu}}) { eval { @@ -3208,53 +3195,30 @@ sub compile_iptables_filter { warn $@ if $@; # just to be sure - should not happen } - # generate firewall rules for OpenVZ containers - foreach my $vmid (keys %{$vmdata->{openvz}}) { - eval { - my $conf = $vmdata->{openvz}->{$vmid}; - - my $vmfw_conf = $vmfw_configs->{$vmid}; - return if !$vmfw_conf; - - generate_ipset_chains($ipset_ruleset, $cluster_conf, $vmfw_conf); - - if ($vmfw_conf->{options}->{enable}) { - if ($conf->{ip_address} && $conf->{ip_address}->{value}) { - my $ip = $conf->{ip_address}->{value}; - $ip =~ s/\s+/,/g; - - my @ips = (); - - foreach my $singleip (split(',', $ip)) { - my $singleip_ver = parse_address_list($singleip); # make sure we have a valid $ip list - push @{$cluster_conf->{ipset}->{venet0}}, { cidr => $singleip }; - push @ips, $singleip if $singleip_ver == $ipversion; - } - - if (scalar(@ips)) { - my $ip_list = join(',', @ips); - generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip_list, 'IN', $ipversion); - generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip_list, 'OUT', $ipversion); - } - } - } - - if ($conf->{netif} && $conf->{netif}->{value}) { - my $netif = PVE::OpenVZ::parse_netif($conf->{netif}->{value}); - foreach my $netid (keys %$netif) { - my $d = $netif->{$netid}; - my $bridge = $d->{bridge}; - next if !$bridge || $bridge !~ m/^vmbr\d+(v(\d+))?f$/; # firewall enabled ? - my $macaddr = $d->{mac}; - my $iface = $d->{host_ifname}; - generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr, - $vmfw_conf, $vmid, 'IN', $ipversion); - generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr, - $vmfw_conf, $vmid, 'OUT', $ipversion); + # generate firewall rules for LXC containers + foreach my $vmid (keys %{$vmdata->{lxc}}) { + eval { + my $conf = $vmdata->{lxc}->{$vmid}; + my $vmfw_conf = $vmfw_configs->{$vmid}; + return if !$vmfw_conf; + + generate_ipset_chains($ipset_ruleset, $cluster_conf, $vmfw_conf); + + if ($vmfw_conf->{options}->{enable}) { + foreach my $netid (keys %$conf) { + next if $netid !~ m/^net(\d+)$/; + my $net = PVE::LXC::parse_lxc_network($conf->{$netid}); + next if !$net->{firewall}; + my $iface = "veth${vmid}i$1"; + my $macaddr = $net->{hwaddr}; + generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr, + $vmfw_conf, $vmid, 'IN', $ipversion); + generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr, + $vmfw_conf, $vmid, 'OUT', $ipversion); } - } - }; - warn $@ if $@; # just to be sure - should not happen + } + }; + warn $@ if $@; # just to be sure - should not happen } if(ruleset_chain_exist($ruleset, "PVEFW-IPS")){