]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/PVE/Firewall.pm
add aliases feature
[pve-firewall.git] / src / PVE / Firewall.pm
index d5d4950b6aaa4be45c796d37c084dc599452976c..7ef38fa514be9b0c35cf2ba3425845ef95472649 100644 (file)
@@ -56,6 +56,13 @@ PVE::JSONSchema::register_standard_option('ipset-name', {
     maxLength => 20,                     
 });
 
+PVE::JSONSchema::register_standard_option('pve-fw-loglevel' => {
+    description => "Log level.",
+    type => 'string', 
+    enum => ['emerg', 'alert', 'crit', 'err', 'warning', 'notice', 'info', 'debug', 'nolog'],
+    optional => 1,
+});
+
 my $security_group_pattern = '[A-Za-z][A-Za-z0-9\-\_]+';
 
 PVE::JSONSchema::register_standard_option('pve-security-group-name', {
@@ -705,6 +712,7 @@ sub parse_address_list {
     my ($str) = @_;
 
     return if $str =~ m/^(\+)(\S+)$/; # ipset ref
+    return if $str =~ m/^${security_group_pattern}$/; # aliases
 
     my $count = 0;
     my $iprange = 0;
@@ -1201,6 +1209,10 @@ sub ruleset_generate_cmdstr {
            die "no such ipset $2" if !$cluster_conf->{ipset}->{$2};
            push @cmd, "-m set --match-set PVEFW-$2 src";
 
+       } elsif ($source =~ m/^${security_group_pattern}$/){
+           die "no such alias $source" if !$cluster_conf->{aliases}->{$source};
+           push @cmd, "-s $cluster_conf->{aliases}->{$source}";
+
         } elsif ($source =~ m/\-/){
            push @cmd, "-m iprange --src-range $source";
 
@@ -1214,11 +1226,15 @@ sub ruleset_generate_cmdstr {
            die "no such ipset $2" if !$cluster_conf->{ipset}->{$2};
            push @cmd, "-m set --match-set PVEFW-$2 dst";
 
+       } elsif ($dest =~ m/^${security_group_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";
 
        } else {
-           push @cmd, "-s $dest";
+           push @cmd, "-d $dest";
         }
     }
 
@@ -1418,12 +1434,12 @@ sub ruleset_add_chain_policy {
 }
 
 sub ruleset_create_vm_chain {
-    my ($ruleset, $chain, $options, $macaddr, $direction) = @_;
+    my ($ruleset, $chain, $options, $host_options, $macaddr, $direction) = @_;
 
     ruleset_create_chain($ruleset, $chain);
     my $accept = generate_nfqueue($options);
 
-    if (!(defined($options->{nosmurfs}) && $options->{nosmurfs} == 0)) {
+    if (!(defined($host_options->{nosmurfs}) && $host_options->{nosmurfs} == 0)) {
        ruleset_addrule($ruleset, $chain, "-m conntrack --ctstate INVALID,NEW -j PVEFW-smurfs");
     }
 
@@ -1437,7 +1453,7 @@ sub ruleset_create_vm_chain {
        }
     }
 
-    if ($options->{tcpflags}) {
+    if ($host_options->{tcpflags}) {
        ruleset_addrule($ruleset, $chain, "-p tcp -j PVEFW-tcpflags");
     }
 
@@ -1531,7 +1547,7 @@ sub ruleset_generate_vm_ipsrules {
 }
 
 sub generate_venet_rules_direction {
-    my ($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, $direction) = @_;
+    my ($ruleset, $cluster_conf, $hostfw_conf, $vmfw_conf, $vmid, $ip, $direction) = @_;
 
     parse_address_list($ip); # make sure we have a valid $ip list
 
@@ -1540,11 +1556,12 @@ sub generate_venet_rules_direction {
     my $rules = $vmfw_conf->{rules};
 
     my $options = $vmfw_conf->{options};
+    my $hostfw_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, $options, undef, $direction);
+    ruleset_create_vm_chain($ruleset, $chain, $options, $hostfw_options, undef, $direction);
 
     ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $chain, 'venet', $direction);
 
@@ -1586,18 +1603,19 @@ sub generate_venet_rules_direction {
 }
 
 sub generate_tap_rules_direction {
-    my ($ruleset, $cluster_conf, $iface, $netid, $macaddr, $vmfw_conf, $vmid, $bridge, $direction) = @_;
+    my ($ruleset, $cluster_conf, $hostfw_conf, $iface, $netid, $macaddr, $vmfw_conf, $vmid, $bridge, $direction) = @_;
 
     my $lc_direction = lc($direction);
 
     my $rules = $vmfw_conf->{rules};
 
     my $options = $vmfw_conf->{options};
+    my $hostfw_options = $hostfw_conf->{options};
     my $loglevel = get_option_log_level($options, "log_level_${lc_direction}");
 
     my $tapchain = "$iface-$direction";
 
-    ruleset_create_vm_chain($ruleset, $tapchain, $options, $macaddr, $direction);
+    ruleset_create_vm_chain($ruleset, $tapchain, $options, $hostfw_options, $macaddr, $direction);
 
     ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $tapchain, $netid, $direction, $options);
 
@@ -1629,11 +1647,10 @@ sub generate_tap_rules_direction {
 sub enable_host_firewall {
     my ($ruleset, $hostfw_conf, $cluster_conf) = @_;
 
-    # fixme: allow security groups
-
     my $options = $hostfw_conf->{options};
     my $cluster_options = $cluster_conf->{options};
     my $rules = $hostfw_conf->{rules};
+    my $cluster_rules = $cluster_conf->{rules};
 
     # host inbound firewall
     my $chain = "PVEFW-HOST-IN";
@@ -1659,7 +1676,8 @@ sub enable_host_firewall {
     # we use RETURN because we need to check also tap rules
     my $accept_action = 'RETURN';
 
-    foreach my $rule (@$rules) {
+    # add host rules first, so that cluster wide rules can be overwritten
+    foreach my $rule (@$rules, @$cluster_rules) {
        next if $rule->{type} ne 'in';
        ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" }, undef, $cluster_conf);
     }
@@ -1684,7 +1702,8 @@ sub enable_host_firewall {
     # we use RETURN because we may want to check other thigs later
     $accept_action = 'RETURN';
 
-    foreach my $rule (@$rules) {
+    # add host rules first, so that cluster wide rules can be overwritten
+    foreach my $rule (@$rules, @$cluster_rules) {
        next if $rule->{type} ne 'out';
        ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" }, undef, $cluster_conf);
     }
@@ -1824,7 +1843,7 @@ sub parse_vmfw_option {
 
     my $loglevels = "emerg|alert|crit|err|warning|notice|info|debug|nolog";
 
-    if ($line =~ m/^(enable|dhcp|macfilter|nosmurfs|tcpflags|ips):\s*(0|1)\s*$/i) {
+    if ($line =~ m/^(enable|dhcp|macfilter|ips):\s*(0|1)\s*$/i) {
        $opt = lc($1);
        $value = int($2);
     } elsif ($line =~ m/^(log_level_in|log_level_out):\s*(($loglevels)\s*)?$/i) {
@@ -1887,6 +1906,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) = @_;
 
@@ -2010,6 +2048,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);
@@ -2046,6 +2089,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); };
@@ -2070,12 +2119,14 @@ sub parse_cluster_fw_rules {
            my $nomatch = $1;
            my $cidr = $2;
 
-           $cidr =~ s|/32$||;
+           if($cidr !~ m/^${security_group_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 }; 
@@ -2277,6 +2328,7 @@ sub generate_std_chains {
 
     # same as shorewall smurflog.
     my $chain = 'PVEFW-smurflog';
+    $pve_std_chains->{$chain} = [];
 
     push @{$pve_std_chains->{$chain}}, get_log_rule_base($chain, 0, "DROP: ", $loglevel) if $loglevel;
     push @{$pve_std_chains->{$chain}}, "-j DROP";
@@ -2284,6 +2336,8 @@ sub generate_std_chains {
     # same as shorewall logflags action.
     $loglevel = get_option_log_level($options, 'tcp_flags_log_level');
     $chain = 'PVEFW-logflags';
+    $pve_std_chains->{$chain} = [];
+
     # fixme: is this correctly logged by pvewf-logger? (ther is no --log-ip-options for NFLOG)
     push @{$pve_std_chains->{$chain}}, get_log_rule_base($chain, 0, "DROP: ", $loglevel) if $loglevel;
     push @{$pve_std_chains->{$chain}}, "-j DROP";
@@ -2304,12 +2358,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) {
@@ -2323,6 +2377,12 @@ sub generate_ipset {
     # remove duplicates
     my $nethash = {};
     foreach my $entry (@$options) {
+       my $cidr = $entry->{cidr};
+       #check aliases
+       if ($cidr =~ m/^${security_group_pattern}$/){
+           die "no such alias $cidr" if !$aliases->{$cidr};
+           $entry->{cidr} = $aliases->{$cidr};
+       }
        $nethash->{$entry->{cidr}} = $entry;
     }
 
@@ -2493,7 +2553,6 @@ sub compile {
 
     my $routing_table = read_proc_net_route();
 
-
     my $ipset_ruleset = {};
     generate_ipset_chains($ipset_ruleset, $cluster_conf);
 
@@ -2533,9 +2592,9 @@ sub compile {
            generate_bridge_chains($ruleset, $hostfw_conf, $bridge, $routing_table);
 
            my $macaddr = $net->{macaddr};
-           generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,
+           generate_tap_rules_direction($ruleset, $cluster_conf, $hostfw_conf, $iface, $netid, $macaddr,
                                         $vmfw_conf, $vmid, $bridge, 'IN');
-           generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,
+           generate_tap_rules_direction($ruleset, $cluster_conf, $hostfw_conf, $iface, $netid, $macaddr,
                                         $vmfw_conf, $vmid, $bridge, 'OUT');
        }
     }
@@ -2550,8 +2609,8 @@ sub compile {
 
        if ($conf->{ip_address} && $conf->{ip_address}->{value}) {
            my $ip = $conf->{ip_address}->{value};
-           generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, 'IN');
-           generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, 'OUT');
+           generate_venet_rules_direction($ruleset, $cluster_conf, $hostfw_conf, $vmfw_conf, $vmid, $ip, 'IN');
+           generate_venet_rules_direction($ruleset, $cluster_conf, $hostfw_conf, $vmfw_conf, $vmid, $ip, 'OUT');
        }
 
        if ($conf->{netif} && $conf->{netif}->{value}) {
@@ -2568,9 +2627,9 @@ sub compile {
 
                my $macaddr = $d->{mac};
                my $iface = $d->{host_ifname};
-               generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,
+               generate_tap_rules_direction($ruleset, $cluster_conf, $hostfw_conf, $iface, $netid, $macaddr,
                                             $vmfw_conf, $vmid, $bridge, 'IN');
-               generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,
+               generate_tap_rules_direction($ruleset, $cluster_conf, $hostfw_conf, $iface, $netid, $macaddr,
                                             $vmfw_conf, $vmid, $bridge, 'OUT');
            }
        }