X-Git-Url: https://git.proxmox.com/?p=pve-firewall.git;a=blobdiff_plain;f=src%2FPVE%2FFirewall.pm;h=92ea33d6ca8e6ffd84dbba7adbba7227a0496a17;hp=ff18de042a9972b960abd8d06e8bb8dd15d1661f;hb=HEAD;hpb=856de23adb4129351026bbeefeff0534edddfe6f diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index ff18de0..09544ba 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -85,7 +85,7 @@ PVE::JSONSchema::register_format('IPorCIDRorAlias', \&pve_verify_ip_or_cidr_or_a sub pve_verify_ip_or_cidr_or_alias { my ($cidr, $noerr) = @_; - return if $cidr =~ m/^(?:$ip_alias_pattern)$/; + return if $cidr =~ m@^(dc/|guest/)?(?:$ip_alias_pattern)$@; return pve_verify_ip_or_cidr($cidr, $noerr); } @@ -1067,7 +1067,7 @@ sub parse_address_list { return; } - if ($str =~ m/^${ip_alias_pattern}$/) { + if ($str =~ m@^(dc/|guest/)?${ip_alias_pattern}$@) { die "alias name too long\n" if length($str) > $max_alias_name_length; return; } @@ -1408,6 +1408,12 @@ our $host_option_properties = { default => 0, optional => 1 }, + nftables => { + description => "Enable nftables based firewall (tech preview)", + type => 'boolean', + default => 0, + optional => 1, + }, }; our $vm_option_properties = { @@ -1683,19 +1689,26 @@ sub verify_rule { if (my $value = $rule->{$name}) { if ($value =~ m/^\+/) { - if ($value =~ m@^\+(vm/|dc/)?(${ipset_name_pattern})$@) { + if ($value =~ m@^\+(guest/|dc/)?(${ipset_name_pattern})$@) { &$add_error($name, "no such ipset '$2'") if !($cluster_conf->{ipset}->{$2} || ($fw_conf && $fw_conf->{ipset}->{$2})); } else { &$add_error($name, "invalid ipset name '$value'"); } - } elsif ($value =~ m/^${ip_alias_pattern}$/){ - my $alias = lc($value); + } elsif ($value =~ m@^(guest/|dc/)?(${ip_alias_pattern})$@){ + my $scope = $1 // ""; + my $alias = lc($2); &$add_error($name, "no such alias '$value'") if !($cluster_conf->{aliases}->{$alias} || ($fw_conf && $fw_conf->{aliases}->{$alias})); - my $e = $fw_conf ? $fw_conf->{aliases}->{$alias} : undef; - $e = $cluster_conf->{aliases}->{$alias} if !$e && $cluster_conf; + + my $e; + if ($scope ne 'dc/' && $fw_conf) { + $e = $fw_conf->{aliases}->{$alias}; + } + if ($scope ne 'guest/' && !$e && $cluster_conf) { + $e = $cluster_conf->{aliases}->{$alias}; + } &$set_ip_version($e->{ipversion}); } @@ -2095,13 +2108,13 @@ sub ipt_gen_src_or_dst_match { my $match; if ($adr =~ m/^\+/) { - if ($adr =~ m@^\+(vm/|dc/)?(${ipset_name_pattern})$@) { - my $scope = $1; + if ($adr =~ m@^\+(guest/|dc/)?(${ipset_name_pattern})$@) { + my $scope = $1 // ""; my $name = $2; my $ipset_chain; if ($scope ne 'dc/' && $fw_conf && $fw_conf->{ipset}->{$name}) { $ipset_chain = compute_ipset_chain_name($fw_conf->{vmid}, $name, $ipversion); - } elsif ($scope ne 'vm/' && $cluster_conf && $cluster_conf->{ipset}->{$name}) { + } elsif ($scope ne 'guest/' && $cluster_conf && $cluster_conf->{ipset}->{$name}) { $ipset_chain = compute_ipset_chain_name(0, $name, $ipversion); } else { die "no such ipset '$name'\n"; @@ -2110,10 +2123,16 @@ sub ipt_gen_src_or_dst_match { } 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; + } elsif ($adr =~ m@^(dc/|guest/)?(${ip_alias_pattern})$@){ + my $scope = $1 // ""; + my $alias = lc($2); + my $e; + if ($scope ne 'dc/' && $fw_conf) { + $e = $fw_conf->{aliases}->{$alias}; + } + if ($scope ne 'guest/' && !$e && $cluster_conf) { + $e = $cluster_conf->{aliases}->{$alias}; + } die "no such alias '$adr'\n" if !$e; $match = "-${dir} $e->{cidr}"; } elsif ($adr =~ m/\-/){ @@ -2916,7 +2935,7 @@ sub parse_hostfw_option { my $loglevels = "emerg|alert|crit|err|warning|notice|info|debug|nolog"; - if ($line =~ m/^(enable|nosmurfs|tcpflags|ndp|log_nf_conntrack|nf_conntrack_allow_invalid|protection_synflood):\s*(0|1)\s*$/i) { + if ($line =~ m/^(enable|nosmurfs|tcpflags|ndp|log_nf_conntrack|nf_conntrack_allow_invalid|protection_synflood|nftables):\s*(0|1)\s*$/i) { $opt = lc($1); $value = int($2); } elsif ($line =~ m/^(log_level_in|log_level_out|tcp_flags_log_level|smurf_log_level):\s*(($loglevels)\s*)?$/i) { @@ -2964,11 +2983,26 @@ sub parse_clusterfw_option { } sub resolve_alias { - my ($clusterfw_conf, $fw_conf, $cidr) = @_; + my ($clusterfw_conf, $fw_conf, $cidr, $scope) = @_; + + # When we're on the cluster level, the cluster config only gets + # saved into fw_conf, so we need some extra handling here (to + # stay consistent) + my ($cluster_config, $local_config); + if (!$clusterfw_conf) { + ($cluster_config, $local_config) = ($fw_conf, undef); + } else { + ($cluster_config, $local_config) = ($clusterfw_conf, $fw_conf); + } my $alias = lc($cidr); - my $e = $fw_conf ? $fw_conf->{aliases}->{$alias} : undef; - $e = $clusterfw_conf->{aliases}->{$alias} if !$e && $clusterfw_conf; + my $e; + if ($scope ne 'dc/' && $local_config) { + $e = $local_config->{aliases}->{$alias}; + } + if ($scope ne 'guest/' && !$e && $cluster_config) { + $e = $cluster_config->{aliases}->{$alias}; + } die "no such alias '$cidr'\n" if !$e;; @@ -3156,8 +3190,10 @@ sub generic_fw_config_parser { } eval { - if ($cidr =~ m/^${ip_alias_pattern}$/) { - resolve_alias($cluster_conf, $res, $cidr); # make sure alias exists + if ($cidr =~ m@^(dc/|guest/)?(${ip_alias_pattern}$)@) { + my $scope = $1 // ""; + my $alias = $2; + resolve_alias($cluster_conf, $res, $alias, $scope); # make sure alias exists } else { $cidr = parse_ip_or_cidr($cidr); } @@ -3330,7 +3366,7 @@ my $format_aliases = sub { my $raw = ''; $raw .= "[ALIASES]\n\n"; - foreach my $k (keys %$aliases) { + foreach my $k (sort keys %$aliases) { my $e = $aliases->{$k}; $raw .= "$e->{name} $e->{cidr}"; $raw .= " # " . encode('utf8', $e->{comment}) @@ -3508,8 +3544,10 @@ sub generate_ipset_chains { next if $entry->{errors}; # skip entries with errors eval { my ($cidr, $ver); - if ($entry->{cidr} =~ m/^${ip_alias_pattern}$/) { - ($cidr, $ver) = resolve_alias($clusterfw_conf, $fw_conf, $entry->{cidr}); + if ($entry->{cidr} =~ m@^(dc/|guest/)?(${ip_alias_pattern})$@) { + my $scope = $1 // ""; + my $alias = $2; + ($cidr, $ver) = resolve_alias($clusterfw_conf, $fw_conf, $alias, $scope); } else { ($cidr, $ver) = parse_ip_or_cidr($entry->{cidr}); } @@ -4641,12 +4679,51 @@ sub remove_pvefw_chains_ebtables { ebtables_restore_cmdlist(get_ebtables_cmdlist({})); } -sub init { - my $cluster_conf = load_clusterfw_conf(); - my $cluster_options = $cluster_conf->{options}; - my $enable = $cluster_options->{enable}; +sub is_nftables { + my ($cluster_conf, $host_conf) = @_; - return if !$enable; + if (!-x "/usr/libexec/proxmox/proxmox-firewall") { + return 0; + } + + $cluster_conf = load_clusterfw_conf() if !defined($cluster_conf); + $host_conf = load_hostfw_conf($cluster_conf) if !defined($host_conf); + + return $host_conf->{options}->{nftables}; +} + +my sub update_force_nftables_disable_flag { + my ($cluster_firewall_enabled, $is_nftables) = @_; + + # This is checked in proxmox-firewall to avoid log-spam due to failing to parse the config + my $FORCE_NFT_DISABLE_FLAG_FILE = "/run/proxmox-nftables-firewall-force-disable"; + + if (!($cluster_firewall_enabled && $is_nftables)) { + if (! -e $FORCE_NFT_DISABLE_FLAG_FILE) { + open(my $_fh, '>', $FORCE_NFT_DISABLE_FLAG_FILE) + or warn "failed to create flag file '$FORCE_NFT_DISABLE_FLAG_FILE' – $!\n"; + } + } else { + unlink($FORCE_NFT_DISABLE_FLAG_FILE) + or $!{ENOENT} or warn "failed to unlink flag file '$FORCE_NFT_DISABLE_FLAG_FILE' - $!\n"; + } +} + +sub is_enabled_and_not_nftables { + my ($cluster_conf, $host_conf) = @_; + + $cluster_conf = load_clusterfw_conf() if !defined($cluster_conf); + $host_conf = load_hostfw_conf($cluster_conf) if !defined($host_conf); + + my $is_nftables = is_nftables($cluster_conf, $host_conf); + + update_force_nftables_disable_flag($cluster_conf->{options}->{enable}, $is_nftables); + + return $cluster_conf->{options}->{enable} && !$is_nftables; +} + +sub init { + return if !is_enabled_and_not_nftables(); # load required modules here } @@ -4655,15 +4732,13 @@ sub update { my $code = sub { my $cluster_conf = load_clusterfw_conf(); - my $cluster_options = $cluster_conf->{options}; + my $hostfw_conf = load_hostfw_conf($cluster_conf); - if (!$cluster_options->{enable}) { + if (!is_enabled_and_not_nftables($cluster_conf, $hostfw_conf)) { PVE::Firewall::remove_pvefw_chains(); return; } - my $hostfw_conf = load_hostfw_conf($cluster_conf); - my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = compile($cluster_conf, $hostfw_conf); apply_ruleset($ruleset, $hostfw_conf, $ipset_ruleset, $rulesetv6, $ebtables_ruleset);