]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/PVE/Firewall.pm
birectionnal macros cleanups
[pve-firewall.git] / src / PVE / Firewall.pm
index bd17ee6d4b9663892c81e617a1a3fec19d67b583..dae867d8fd23fb9d4f6177b20e4bd5eb53ec2df4 100644 (file)
@@ -98,7 +98,6 @@ use Data::Dumper;
 my $nodename = PVE::INotify::nodename();
 
 my $pve_fw_lock_filename = "/var/lock/pvefw.lck";
-my $pve_fw_status_filename = "/var/lib/pve-firewall/pvefw.status";
 
 my $default_log_level = 'info';
 
@@ -158,9 +157,8 @@ my $pve_fw_macros = {
        { action => 'PARAM', proto => 'tcp', dport => '6277' },
     ],
     'DHCPfwd' => [
-       "Forwarded DHCP traffic (bidirectional)",
+       "Forwarded DHCP traffic",
        { action => 'PARAM', proto => 'udp', dport => '67:68', sport => '67:68' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => 'udp', dport => '67:68', sport => '67:68' },
     ],
     'DNS' => [
        "Domain Name System traffic (upd and tcp)",
@@ -187,9 +185,8 @@ my $pve_fw_macros = {
        { action => 'PARAM', proto => 'udp', dport => '1080' },
     ],
     'GRE' => [
-       "Generic Routing Encapsulation tunneling protocol (bidirectional)",
+       "Generic Routing Encapsulation tunneling protocol",
        { action => 'PARAM', proto => '47' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => '47' },
     ],
     'Git' => [
        "Git distributed revision control traffic",
@@ -224,32 +221,24 @@ my $pve_fw_macros = {
        { action => 'PARAM', proto => 'tcp', dport => '993' },
     ],
     'IPIP' => [
-       "IPIP capsulation traffic (bidirectional)",
+       "IPIP capsulation traffic",
        { action => 'PARAM', proto => '94' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => '94' },
     ],
     'IPsec' => [
-       "IPsec traffic (bidirectional)",
+       "IPsec traffic",
        { action => 'PARAM', proto => 'udp', dport => '500', sport => '500' },
        { action => 'PARAM', proto => '50' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => 'udp', dport => '500', sport => '500' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => '50' },
     ],
     'IPsecah' => [
-       "IPsec authentication (AH) traffic (bidirectional)",
+       "IPsec authentication (AH) traffic",
        { action => 'PARAM', proto => 'udp', dport => '500', sport => '500' },
        { action => 'PARAM', proto => '51' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => 'udp', dport => '500', sport => '500' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => '51' },
     ],
     'IPsecnat' => [
-       "IPsec traffic and Nat-Traversal (bidirectional)",
+       "IPsec traffic and Nat-Traversal",
        { action => 'PARAM', proto => 'udp', dport => '500' },
        { action => 'PARAM', proto => 'udp', dport => '4500' },
        { action => 'PARAM', proto => '50' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => 'udp', dport => '500' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => 'udp', dport => '4500' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => '50' },
     ],
     'IRC' => [
        "Internet Relay Chat traffic",
@@ -262,7 +251,6 @@ my $pve_fw_macros = {
     'L2TP' => [
        "Layer 2 Tunneling Protocol traffic",
        { action => 'PARAM', proto => 'udp', dport => '1701' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => 'udp', dport => '1701' },
     ],
     'LDAP' => [
        "Lightweight Directory Access Protocol traffic",
@@ -306,11 +294,6 @@ my $pve_fw_macros = {
        "Network Time Protocol (ntpd)",
        { action => 'PARAM', proto => 'udp', dport => '123' },
     ],
-    'NTPbi' => [
-       "Bi-directional NTP (for NTP peers)",
-       { action => 'PARAM', proto => 'udp', dport => '123' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => 'udp', dport => '123' },
-    ],
     'OSPF' => [
        "OSPF multicast traffic",
        { action => 'PARAM', proto => '89' },
@@ -335,7 +318,6 @@ my $pve_fw_macros = {
     'PPtP' => [
        "Point-to-Point Tunneling Protocol",
        { action => 'PARAM', proto => '47' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => '47' },
        { action => 'PARAM', proto => 'tcp', dport => '1723' },
     ],
     'Ping' => [
@@ -354,10 +336,9 @@ my $pve_fw_macros = {
        "Microsoft Remote Desktop Protocol traffic",
        { action => 'PARAM', proto => 'tcp', dport => '3389' },
     ],
-    'RIPbi' => [
+    'RIP' => [
        "Routing Information Protocol (bidirectional)",
        { action => 'PARAM', proto => 'udp', dport => '520' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => 'udp', dport => '520' },
     ],
     'RNDC' => [
        "BIND remote management protocol",
@@ -386,17 +367,6 @@ my $pve_fw_macros = {
        { action => 'PARAM', proto => 'udp', dport => '1024:65535', sport => '137' },
        { action => 'PARAM', proto => 'tcp', dport => '135,139,445' },
     ],
-    'SMBBI' => [
-       "Microsoft SMB traffic (bidirectional)",
-       { action => 'PARAM', proto => 'udp', dport => '135,445' },
-       { action => 'PARAM', proto => 'udp', dport => '137:139' },
-       { action => 'PARAM', proto => 'udp', dport => '1024:65535', sport => '137' },
-       { action => 'PARAM', proto => 'tcp', dport => '135,139,445' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => 'udp', dport => '135,445' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => 'udp', dport => '137:139' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => 'udp', dport => '1024:65535', sport => '137' },
-       { action => 'PARAM', source => 'DEST', dest => 'SOURCE', proto => 'tcp', dport => '135,139,445' },
-    ],
     'SMBswat' => [
        "Samba Web Administration Tool",
        { action => 'PARAM', proto => 'tcp', dport => '901' },
@@ -1001,7 +971,10 @@ sub verify_rule {
        raise_param_exc({ type => "unknown rule type '$type'"});
     }
 
-    # fixme: verify $rule->{iface}?
+    if ($rule->{iface}) {
+       eval { PVE::JSONSchema::pve_verify_iface($rule->{iface}); };
+       raise_param_exc({ iface => $@ }) if $@;
+    }  
 
     if ($rule->{macro}) {
        my $preferred_name = $pve_fw_preferred_macro_names->{lc($rule->{macro})};
@@ -1563,8 +1536,6 @@ sub ruleset_generate_vm_ipsrules {
 sub generate_venet_rules_direction {
     my ($ruleset, $cluster_conf, $hostfw_conf, $vmfw_conf, $vmid, $ip, $direction) = @_;
 
-    parse_address_list($ip); # make sure we have a valid $ip list
-
     my $lc_direction = lc($direction);
 
     my $rules = $vmfw_conf->{rules};
@@ -1728,10 +1699,14 @@ sub enable_host_firewall {
 
 sub generate_group_rules {
     my ($ruleset, $cluster_conf, $group) = @_;
-    die "no such security group '$group'\n" if !$cluster_conf->{groups}->{$group};
 
     my $rules = $cluster_conf->{groups}->{$group};
 
+    if (!$rules) {
+       warn "no such security group '$group'\n";
+       $rules = []; # create empty chain
+    }
+
     my $chain = "GROUP-${group}-IN";
 
     ruleset_create_chain($ruleset, $chain);
@@ -1763,9 +1738,11 @@ for (my $i = 0; $i < $MAX_NETS; $i++)  {
 }
 
 sub parse_fw_rule {
-    my ($line, $need_iface, $allow_groups) = @_;
+    my ($line, $allow_iface, $allow_groups) = @_;
 
-    my ($type, $action, $iface, $source, $dest, $proto, $dport, $sport);
+    my ($type, $action, $macro, $iface, $source, $dest, $proto, $dport, $sport);
+
+    chomp $line;
 
     # we can add single line comments to the end of the rule
     my $comment = decode('utf8', $1) if $line =~ s/#\s*(.*?)\s*$//;
@@ -1775,22 +1752,11 @@ sub parse_fw_rule {
 
     $enable = 0 if $line =~ s/^\|//;
 
-    my @data = split(/\s+/, $line);
-    my $expected_elements = $need_iface ? 8 : 7;
-
-    die "wrong number of rule elements\n" if scalar(@data) > $expected_elements;
-
-    if ($need_iface) {
-       ($type, $action, $iface, $source, $dest, $proto, $dport, $sport) = @data
-    } else {
-       ($type, $action, $source, $dest, $proto, $dport, $sport) =  @data;
-    }
-
-    die "incomplete rule\n" if ! ($type && $action);
-
-    my $macro;
-
-    $type = lc($type);
+    $line =~ s/^(\S+)\s+(\S+)\s*// ||
+       die "unable to parse rule: $line\n";
+    
+    $type = lc($1);
+    $action = $2;
 
     if ($type eq  'in' || $type eq 'out') {
        if ($action =~ m/^(ACCEPT|DROP|REJECT)$/) {
@@ -1804,32 +1770,52 @@ sub parse_fw_rule {
            die "unknown action '$action'\n";
        }
     } elsif ($type eq 'group') {
-       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_name_pattern}$/;
     } else {
        die "unknown rule type '$type'\n";
     }
 
-    if ($need_iface) {
-       $iface = undef if $iface && $iface eq '-';
-    }
-
-    $proto = undef if $proto && $proto eq '-';
-    pve_fw_verify_protocol_spec($proto) if $proto;
+    while (length($line)) {
+       if ($line =~ s/^-i (\S+)\s*//) {
+           die "parameter -i not allowed\n" if !$allow_iface;
+           $iface = $1;
+           PVE::JSONSchema::pve_verify_iface($iface);
+           next;
+       }
 
-    $source = undef if $source && $source eq '-';
-    $dest = undef if $dest && $dest eq '-';
+       last if $type eq 'group';
 
-    $dport = undef if $dport && $dport eq '-';
-    $sport = undef if $sport && $sport eq '-';
+       if ($line =~ s/^-p (\S+)\s*//) {
+           $proto = $1;
+           pve_fw_verify_protocol_spec($proto);
+           next;
+       }
+       if ($line =~ s/^-dport (\S+)\s*//) {
+           $dport = $1;
+           parse_port_name_number_or_range($dport);
+           next;
+       }
+       if ($line =~ s/^-sport (\S+)\s*//) {
+           $sport = $1;
+           parse_port_name_number_or_range($sport);
+           next;
+       }
+       if ($line =~ s/^-source (\S+)\s*//) {
+           $source = $1;
+           parse_address_list($source);
+           next;
+       }
+       if ($line =~ s/^-dest (\S+)\s*//) {
+           $dest = $1;
+           parse_address_list($dest);
+           next;
+       }
 
-    parse_port_name_number_or_range($dport) if defined($dport);
-    parse_port_name_number_or_range($sport) if defined($sport);
+       last;
+    }
 
-    parse_address_list($source) if $source;
-    parse_address_list($dest) if $dest;
+    die "unable to parse rule parameters: $line\n" if length($line);
 
     return {
        type => $type,
@@ -2217,7 +2203,7 @@ sub load_vmfw_conf {
 }
 
 my $format_rules = sub {
-    my ($rules, $need_iface) = @_;
+    my ($rules, $allow_iface) = @_;
 
     my $raw = '';
 
@@ -2230,14 +2216,16 @@ my $format_rules = sub {
            } else {
                $raw .= " " . $rule->{action};
            }
-           $raw .= " " . ($rule->{iface} || '-') if $need_iface;
+           if ($allow_iface && $rule->{iface}) {
+               $raw .= " -i $rule->{iface}";
+           }
 
            if ($rule->{type} ne  'group')  {
-               $raw .= " " . ($rule->{source} || '-');
-               $raw .= " " . ($rule->{dest} || '-');
-               $raw .= " " . ($rule->{proto} || '-');
-               $raw .= " " . ($rule->{dport} || '-');
-               $raw .= " " . ($rule->{sport} || '-');
+               $raw .= " -source $rule->{source}" if $rule->{source};
+               $raw .= " -dest $rule->{dest}" if $rule->{dest};
+               $raw .= " -p $rule->{proto}" if $rule->{proto};
+               $raw .= " -dport $rule->{dport}" if $rule->{dport};
+               $raw .= " -sport $rule->{sport}" if $rule->{sport};
            }
 
            $raw .= " # " . encode('utf8', $rule->{comment})
@@ -2449,30 +2437,6 @@ sub round_powerof2 {
     return ++$int;
 }
 
-sub save_pvefw_status {
-    my ($status) = @_;
-
-    die "unknown status '$status' - internal error"
-       if $status !~ m/^(stopped|active)$/;
-
-    mkdir dirname($pve_fw_status_filename);
-    PVE::Tools::file_set_contents($pve_fw_status_filename, $status);
-}
-
-sub read_pvefw_status {
-
-    my $status = 'unknown';
-
-    return 'stopped' if ! -f $pve_fw_status_filename;
-
-    eval {
-       $status = PVE::Tools::file_get_contents($pve_fw_status_filename);
-    };
-    warn $@ if $@;
-
-    return $status;
-}
-
 sub load_clusterfw_conf {
     my ($filename) = @_;
 
@@ -2588,8 +2552,7 @@ sub compile {
     }
 
 
-    my $ipset_ruleset = {};
-    generate_ipset_chains($ipset_ruleset, $cluster_conf);
+    $cluster_conf->{ipset}->{venet0} = [];
 
     my $ruleset = {};
 
@@ -2606,8 +2569,8 @@ sub compile {
     ruleset_chain_add_conn_filters($ruleset, "PVEFW-FORWARD", "ACCEPT");
 
     ruleset_create_chain($ruleset, "PVEFW-VENET-OUT");
-    ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i venet0 -j PVEFW-VENET-OUT");
-    ruleset_addrule($ruleset, "PVEFW-INPUT", "-i venet0 -j PVEFW-VENET-OUT");
+    ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i venet0 -m set --match-set PVEFW-venet0 src -j PVEFW-VENET-OUT");
+    ruleset_addrule($ruleset, "PVEFW-INPUT", "-i venet0 -m set --match-set PVEFW-venet0 src -j PVEFW-VENET-OUT");
 
     ruleset_create_chain($ruleset, "PVEFW-FWBR-IN");
     ruleset_chain_add_input_filters($ruleset, "PVEFW-FWBR-IN", $hostfw_options, $cluster_conf, $loglevel);
@@ -2620,7 +2583,7 @@ sub compile {
     ruleset_create_chain($ruleset, "PVEFW-VENET-IN");
     ruleset_chain_add_input_filters($ruleset, "PVEFW-VENET-IN", $hostfw_options, $cluster_conf, $loglevel);
 
-    ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o venet0 -j PVEFW-VENET-IN");
+    ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o venet0 -m set --match-set PVEFW-venet0 dst -j PVEFW-VENET-IN");
 
     generate_std_chains($ruleset, $hostfw_options);
 
@@ -2628,7 +2591,7 @@ sub compile {
 
     enable_host_firewall($ruleset, $hostfw_conf, $cluster_conf) if $hostfw_enable;
 
-    ruleset_addrule($ruleset, "PVEFW-OUTPUT", "-o venet0 -j PVEFW-VENET-IN");
+    ruleset_addrule($ruleset, "PVEFW-OUTPUT", "-o venet0 -m set --match-set PVEFW-venet0 dst -j PVEFW-VENET-IN");
 
     # generate firewall rules for QEMU VMs
     foreach my $vmid (keys %{$vmdata->{qemu}}) {
@@ -2662,6 +2625,16 @@ sub compile {
        if ($conf->{ip_address} && $conf->{ip_address}->{value}) {
            my $ip = $conf->{ip_address}->{value};
            $ip =~ s/\s+/,/g;
+           parse_address_list($ip); # make sure we have a valid $ip list
+
+           my @ips = split(',', $ip);
+
+           foreach my $singleip (@ips) {
+               my $venet0ipset = {};
+               $venet0ipset->{cidr} = $singleip;
+               push @{$cluster_conf->{ipset}->{venet0}}, $venet0ipset;
+           }
+
            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');
        }
@@ -2685,6 +2658,9 @@ sub compile {
        ruleset_insertrule($ruleset, "PVEFW-FORWARD", "-m conntrack --ctstate RELATED,ESTABLISHED -j PVEFW-IPS");
     }
 
+    my $ipset_ruleset = {};
+    generate_ipset_chains($ipset_ruleset, $cluster_conf);
+
     return ($ruleset, $ipset_ruleset);
 }
 
@@ -2955,7 +2931,7 @@ sub remove_pvefw_chains {
 }
 
 sub update {
-    my ($start, $verbose) = @_;
+    my ($verbose) = @_;
 
     my $code = sub {
 
@@ -2964,9 +2940,7 @@ sub update {
 
        my $enable = $cluster_options->{enable};
 
-       my $status = read_pvefw_status();
-
-       die "Firewall is disabled - cannot start\n" if !$enable && $start;
+       die "Firewall is disabled - cannot start\n" if !$enable;
 
        if (!$enable) {
            PVE::Firewall::remove_pvefw_chains();
@@ -2978,14 +2952,7 @@ sub update {
 
        my ($ruleset, $ipset_ruleset) = compile($cluster_conf, $hostfw_conf);
 
-       if ($start || $status eq 'active') {
-
-           save_pvefw_status('active') if ($status ne 'active');
-
-           apply_ruleset($ruleset, $hostfw_conf, $ipset_ruleset, $verbose);
-       } else {
-           print "Firewall not active (status = $status)\n" if $verbose;
-       }
+       apply_ruleset($ruleset, $hostfw_conf, $ipset_ruleset, $verbose);
     };
 
     run_locked($code);