]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/PVE/Firewall.pm
ruleset_generate_vm_rule: avoid multiple calls to generate_nfqueue()
[pve-firewall.git] / src / PVE / Firewall.pm
index 6ec314c299cfbcd034b54897ca9b2f45662f21ec..01de542d3305e8dc397ad15110eab2d5775bd0b3 100644 (file)
@@ -15,7 +15,7 @@ use File::Basename;
 use File::Path;
 use IO::File;
 use Net::IP;
-use PVE::Tools qw(run_command lock_file);
+use PVE::Tools qw(run_command lock_file dir_glob_foreach);
 use Encode;
 
 my $hostfw_conf_filename = "/etc/pve/local/host.fw";
@@ -63,12 +63,13 @@ PVE::JSONSchema::register_standard_option('pve-fw-loglevel' => {
     optional => 1,
 });
 
-my $security_group_pattern = '[A-Za-z][A-Za-z0-9\-\_]+';
+my $security_group_name_pattern = '[A-Za-z][A-Za-z0-9\-\_]+';
+my $ip_alias_pattern = '[A-Za-z][A-Za-z0-9\-\_]+';
 
 PVE::JSONSchema::register_standard_option('pve-security-group-name', {
     description => "Security Group name.",
     type => 'string',
-    pattern => $security_group_pattern,
+    pattern => $security_group_name_pattern,
     minLength => 2,
     maxLength => 20,                             
 });
@@ -712,6 +713,7 @@ sub parse_address_list {
     my ($str) = @_;
 
     return if $str =~ m/^(\+)(\S+)$/; # ipset ref
+    return if $str =~ m/^${ip_alias_pattern}$/;
 
     my $count = 0;
     my $iprange = 0;
@@ -854,7 +856,7 @@ my $rule_properties = {
        description => "Rule action ('ACCEPT', 'DROP', 'REJECT') or security group name.",
        type => 'string',
        optional => 1,
-       pattern => $security_group_pattern,
+       pattern => $security_group_name_pattern,
        maxLength => 20,
        minLength => 2,
     },
@@ -986,7 +988,7 @@ sub verify_rule {
        raise_param_exc({ type => "security groups not allowed"}) 
            if !$allow_groups;
        raise_param_exc({ action => "invalid characters in security group name"}) 
-           if $rule->{action} !~ m/^${security_group_pattern}$/;
+           if $rule->{action} !~ m/^${security_group_name_pattern}$/;
     } else {
        raise_param_exc({ type => "unknown rule type '$type'"});
     }
@@ -1092,7 +1094,7 @@ sub iptables_get_chains {
 
        return 1 if $name =~ m/^venet0-\d+-(:?IN|OUT)$/;
 
-       return 1 if $name =~ m/^vmbr\d+-(:?FW|IN|OUT|IPS)$/;
+       return 1 if $name =~ m/^vmbr\d+(v\d+)?-(:?FW|IN|OUT|IPS)$/;
        return 1 if $name =~ m/^GROUP-(:?[^\s\-]+)-(:?IN|OUT)$/;
 
        return undef;
@@ -1204,9 +1206,16 @@ sub ruleset_generate_cmdstr {
     my $dest = $rule->{dest};
 
     if ($source) {
-        if ($source =~ m/^(\+)(\S+)$/) {
-           die "no such ipset $2" if !$cluster_conf->{ipset}->{$2};
-           push @cmd, "-m set --match-set PVEFW-$2 src";
+        if ($source =~ m/^\+/) {
+           if ($source =~ m/^\+(${security_group_name_pattern})$/) {
+               die "no such ipset '$1'\n" if !$cluster_conf->{ipset}->{$1};
+               push @cmd, "-m set --match-set PVEFW-$1 src";
+           } else {
+               die "invalid security group name '$source'\n";
+           }
+       } elsif ($source =~ m/^${ip_alias_pattern}$/){
+           die "no such alias $source\n" if !$cluster_conf->{aliases}->{$source};
+           push @cmd, "-s $cluster_conf->{aliases}->{$source}";
 
         } elsif ($source =~ m/\-/){
            push @cmd, "-m iprange --src-range $source";
@@ -1217,9 +1226,16 @@ sub ruleset_generate_cmdstr {
     }
 
     if ($dest) {
-        if ($dest =~ m/^(\+)(\S+)$/) {
-           die "no such ipset $2" if !$cluster_conf->{ipset}->{$2};
-           push @cmd, "-m set --match-set PVEFW-$2 dst";
+        if ($dest =~ m/^\+/) {
+           if ($dest =~ m/^\+(${security_group_name_pattern})$/) {
+               die "no such ipset '$1'\n" if !$cluster_conf->{ipset}->{$1};
+               push @cmd, "-m set --match-set PVEFW-$1 dst";
+           } else {
+               die "invalid security group name '$dest'\n";
+           }
+       } elsif ($dest =~ m/^${ip_alias_pattern}$/){
+           die "no such alias $dest" if !$cluster_conf->{aliases}->{$dest};
+           push @cmd, "-d $cluster_conf->{aliases}->{$dest}";
 
         } elsif ($dest =~ m/^(\d+)\.(\d+).(\d+).(\d+)\-(\d+)\.(\d+).(\d+).(\d+)$/){
            push @cmd, "-m iprange --dst-range $dest";
@@ -1293,11 +1309,18 @@ sub ruleset_generate_rule {
        $rules = [ $rule ];
     }
 
+    # update all or nothing
+
+    my @cmds = ();
     foreach my $tmp (@$rules) {
        if (my $cmdstr = ruleset_generate_cmdstr($ruleset, $chain, $tmp, $actions, $goto, $cluster_conf)) {
-           ruleset_addrule($ruleset, $chain, $cmdstr);
+           push @cmds, $cmdstr;
        }
     }
+
+    foreach my $cmdstr (@cmds) {
+       ruleset_addrule($ruleset, $chain, $cmdstr);
+    }
 }
 
 sub ruleset_generate_rule_insert {
@@ -1369,7 +1392,7 @@ sub ruleset_addlog {
 }
 
 sub generate_bridge_chains {
-    my ($ruleset, $hostfw_conf, $bridge, $routing_table) = @_;
+    my ($ruleset, $hostfw_conf, $bridge, $routing_table, $bridges_config) = @_;
 
     my $options = $hostfw_conf->{options} || {};
 
@@ -1384,12 +1407,26 @@ sub generate_bridge_chains {
 
     if (!ruleset_chain_exist($ruleset, "$bridge-OUT")) {
        ruleset_create_chain($ruleset, "$bridge-OUT");
+
+       if($options->{optimize}){
+           foreach my $interface (@{$bridges_config->{$bridge}}) {
+               ruleset_addrule($ruleset, "$bridge-OUT", "-m physdev --physdev-is-bridged --physdev-in $interface -g PVEFW-SET-ACCEPT-MARK");
+           }
+       }
+
        ruleset_addrule($ruleset, "$bridge-FW", "-m physdev --physdev-is-in -j $bridge-OUT");
        ruleset_insertrule($ruleset, "PVEFW-INPUT", "-i $bridge -m physdev --physdev-is-in -j $bridge-OUT");
     }
 
     if (!ruleset_chain_exist($ruleset, "$bridge-IN")) {
        ruleset_create_chain($ruleset, "$bridge-IN");
+
+       if($options->{optimize}){
+           foreach my $interface (@{$bridges_config->{$bridge}}) {
+               ruleset_addrule($ruleset, "$bridge-IN", "-m physdev --physdev-is-bridged --physdev-out $interface -j ACCEPT");
+           }
+       }
+
        ruleset_addrule($ruleset, "$bridge-FW", "-m physdev --physdev-is-out -j $bridge-IN");
        ruleset_addrule($ruleset, "$bridge-FW", "-m mark --mark 1 -j ACCEPT");
        # accept traffic to unmanaged bridge ports
@@ -1468,6 +1505,8 @@ sub ruleset_generate_vm_rules {
 
     my $lc_direction = lc($direction);
 
+    my $in_accept = generate_nfqueue($options);
+
     foreach my $rule (@$rules) {
        next if $rule->{iface} && $rule->{iface} ne $netid;
        next if !$rule->{enable};
@@ -1490,8 +1529,7 @@ sub ruleset_generate_vm_rules {
                ruleset_generate_rule($ruleset, $chain, $rule,
                                      { ACCEPT => "PVEFW-SET-ACCEPT-MARK", REJECT => "PVEFW-reject" }, undef, $cluster_conf);
            } else {
-               my $accept = generate_nfqueue($options);
-               ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $accept , REJECT => "PVEFW-reject" }, undef, $cluster_conf);
+               ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $in_accept , REJECT => "PVEFW-reject" }, undef, $cluster_conf);
            }
        }
     }
@@ -1500,22 +1538,20 @@ sub ruleset_generate_vm_rules {
 sub generate_nfqueue {
     my ($options) = @_;
 
-    my $action = "";
-    if($options->{ips}){
-       $action = "NFQUEUE";
-       if($options->{ips_queues} && $options->{ips_queues} =~ m/^(\d+)(:(\d+))?$/) {
-           if(defined($3) && defined($1)) {
+    if ($options->{ips}) {
+       my $action = "NFQUEUE";
+       if ($options->{ips_queues} && $options->{ips_queues} =~ m/^(\d+)(:(\d+))?$/) {
+           if (defined($3) && defined($1)) {
                $action .= " --queue-balance $1:$3";
-           }elsif (defined($1)) {
+           } elsif (defined($1)) {
                $action .= " --queue-num $1";
            }
        }
        $action .= " --queue-bypass" if $feature_ipset_nomatch; #need kernel 3.10
-    }else{
-       $action = "ACCEPT";
+       return $action;
+    } else {
+       return "ACCEPT";
     }
-
-    return $action;
 }
 
 sub ruleset_generate_vm_ipsrules {
@@ -1627,10 +1663,10 @@ sub generate_tap_rules_direction {
 
     # plug the tap chain to bridge chain
     if ($direction eq 'IN') {
-       ruleset_insertrule($ruleset, "$bridge-IN",
+       ruleset_addrule($ruleset, "$bridge-IN",
                           "-m physdev --physdev-is-bridged --physdev-out $iface -j $tapchain");
     } else {
-       ruleset_insertrule($ruleset, "$bridge-OUT",
+       ruleset_addrule($ruleset, "$bridge-OUT",
                           "-m physdev --physdev-in $iface -j $tapchain");
     }
 }
@@ -1788,7 +1824,7 @@ sub parse_fw_rule {
        die "wrong number of rule elements\n" if scalar(@data) != 3;
        die "groups disabled\n" if !$allow_groups;
 
-       die "invalid characters in group name\n" if $action !~ m/^${security_group_pattern}$/;
+       die "invalid characters in group name\n" if $action !~ m/^${security_group_name_pattern}$/;
     } else {
        die "unknown rule type '$type'\n";
     }
@@ -1897,6 +1933,25 @@ sub parse_clusterfw_option {
     return ($opt, $value);
 }
 
+sub parse_clusterfw_alias {
+    my ($line) = @_;
+
+    my ($opt, $value);
+    if ($line =~ m/^(\S+)\s(\S+)$/) {
+       $opt = lc($1);
+       if($2){
+           $2 =~ s|/32$||;
+           pve_verify_ipv4_or_cidr($2) if $2;
+           $value = $2;
+       }
+    } else {
+       chomp $line;
+       die "can't parse option '$line'\n";
+    }
+
+    return ($opt, $value);
+}
+
 sub parse_vm_fw_rules {
     my ($filename, $fh) = @_;
 
@@ -2020,6 +2075,11 @@ sub parse_cluster_fw_rules {
            next;
        }
 
+       if ($line =~ m/^\[aliases\]$/i) {
+           $section = 'aliases';
+           next;
+       }
+
        if ($line =~ m/^\[group\s+(\S+)\]\s*(?:#\s*(.*?)\s*)?$/i) {
            $section = 'groups';
            $group = lc($1);
@@ -2056,6 +2116,12 @@ sub parse_cluster_fw_rules {
                $res->{options}->{$opt} = $value;
            };
            warn "$prefix: $@" if $@;
+       } elsif ($section eq 'aliases') {
+           eval {
+               my ($opt, $value) = parse_clusterfw_alias($line);
+               $res->{aliases}->{$opt} = $value;
+           };
+           warn "$prefix: $@" if $@;
        } elsif ($section eq 'rules') {
            my $rule;
            eval { $rule = parse_fw_rule($line, 1, 1); };
@@ -2080,12 +2146,14 @@ sub parse_cluster_fw_rules {
            my $nomatch = $1;
            my $cidr = $2;
 
-           $cidr =~ s|/32$||;
+           if($cidr !~ m/^${ip_alias_pattern}$/) {
+               $cidr =~ s|/32$||;
            
-           eval { pve_verify_ipv4_or_cidr($cidr); };
-           if (my $err = $@) {
-               warn "$prefix: $cidr - $err";
-               next;
+               eval { pve_verify_ipv4_or_cidr($cidr); };
+               if (my $err = $@) {
+                   warn "$prefix: $cidr - $err";
+                   next;
+               }
            }
 
            my $entry = { cidr => $cidr }; 
@@ -2147,6 +2215,22 @@ sub read_local_vm_config {
     return $vmdata;
 };
 
+sub read_bridges_config {
+
+    my $bridgehash = {};
+
+    dir_glob_foreach('/sys/class/net', 'vmbr(\d+)', sub {
+        my ($bridge) = @_;
+
+       dir_glob_foreach("/sys/class/net/$bridge/brif", '((eth|bond)(\d+)(\.(\d+))?)', sub {
+           my ($interface) = @_;
+           push @{$bridgehash->{$bridge}}, $interface;
+       });
+    });
+
+    return $bridgehash;
+};
+
 sub load_vmfw_conf {
     my ($vmid) = @_;
 
@@ -2317,12 +2401,12 @@ sub generate_ipset_chains {
     my ($ipset_ruleset, $fw_conf) = @_;
 
     foreach my $ipset (keys %{$fw_conf->{ipset}}) {
-       generate_ipset($ipset_ruleset, "PVEFW-$ipset", $fw_conf->{ipset}->{$ipset});
+       generate_ipset($ipset_ruleset, "PVEFW-$ipset", $fw_conf->{ipset}->{$ipset}, $fw_conf->{aliases});
     }
 }
 
 sub generate_ipset {
-    my ($ipset_ruleset, $name, $options) = @_;
+    my ($ipset_ruleset, $name, $options, $aliases) = @_;
 
     my $hashsize = scalar(@$options);
     if ($hashsize <= 64) {
@@ -2336,6 +2420,14 @@ sub generate_ipset {
     # remove duplicates
     my $nethash = {};
     foreach my $entry (@$options) {
+       my $cidr = $entry->{cidr};
+       if ($cidr =~ m/^${ip_alias_pattern}$/) {
+           if ($aliases->{$cidr}) {
+               $entry->{cidr} = $aliases->{$cidr};
+           } else {
+               warn "no such alias '$cidr'\n" if !$aliases->{$cidr};
+           }
+       }
        $nethash->{$entry->{cidr}} = $entry;
     }
 
@@ -2505,6 +2597,8 @@ sub compile {
     my $vmfw_configs = read_vm_firewall_configs($vmdata);
 
     my $routing_table = read_proc_net_route();
+    
+    my $bridges_config = read_bridges_config();
 
     my $ipset_ruleset = {};
     generate_ipset_chains($ipset_ruleset, $cluster_conf);
@@ -2542,7 +2636,7 @@ sub compile {
 
            $bridge .= "v$net->{tag}" if $net->{tag};
 
-           generate_bridge_chains($ruleset, $hostfw_conf, $bridge, $routing_table);
+           generate_bridge_chains($ruleset, $hostfw_conf, $bridge, $routing_table, $bridges_config);
 
            my $macaddr = $net->{macaddr};
            generate_tap_rules_direction($ruleset, $cluster_conf, $hostfw_conf, $iface, $netid, $macaddr,
@@ -2576,7 +2670,7 @@ sub compile {
                    next; # fixme?
                }
 
-               generate_bridge_chains($ruleset, $hostfw_conf, $bridge, $routing_table);
+               generate_bridge_chains($ruleset, $hostfw_conf, $bridge, $routing_table, $bridges_config);
 
                my $macaddr = $d->{mac};
                my $iface = $d->{host_ifname};
@@ -2778,10 +2872,6 @@ sub apply_ruleset {
 
     enable_bridge_firewall();
 
-    update_nf_conntrack_max($hostfw_conf);
-
-    update_nf_conntrack_tcp_timeout_established($hostfw_conf);
-
     my ($ipset_create_cmdlist, $ipset_delete_cmdlist, $ipset_changes) =
        get_ipset_cmdlist($ipset_ruleset, undef, $verbose);
 
@@ -2820,6 +2910,11 @@ sub apply_ruleset {
     }
 
     die "unable to apply firewall changes\n" if $errors;
+
+    update_nf_conntrack_max($hostfw_conf);
+
+    update_nf_conntrack_tcp_timeout_established($hostfw_conf);
+
 }
 
 sub update_nf_conntrack_max {