Signed-off-by: Stefan Hanreich <s.hanre...@proxmox.com> Reviewed-by: Wolfgang Bumiller <w.bumil...@proxmox.com> Tested-by: Hannes Dürr <h.du...@proxmox.com> --- src/PVE/Firewall.pm | 127 ++++++++++++++++++++++++++++++++++-- src/PVE/Firewall/Helpers.pm | 12 ++++ 2 files changed, 132 insertions(+), 7 deletions(-)
diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index bfaa33a..ec7a4e6 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -29,6 +29,7 @@ use PVE::RS::Firewall::SDN; my $pvefw_conf_dir = "/etc/pve/firewall"; my $clusterfw_conf_filename = "$pvefw_conf_dir/cluster.fw"; +my $vnetfw_conf_dir = "/etc/pve/sdn/firewall"; # dynamically include PVE::QemuServer and PVE::LXC # to avoid dependency problems @@ -1290,6 +1291,12 @@ our $cluster_option_properties = { optional => 1, enum => ['ACCEPT', 'REJECT', 'DROP'], }, + policy_forward => { + description => "Forward policy.", + type => 'string', + optional => 1, + enum => ['ACCEPT', 'DROP'], + }, log_ratelimit => { description => "Log ratelimiting settings", type => 'string', format => { @@ -1329,6 +1336,8 @@ our $host_option_properties = { description => "Log level for incoming traffic." }), log_level_out => get_standard_option('pve-fw-loglevel', { description => "Log level for outgoing traffic." }), + log_level_forward => get_standard_option('pve-fw-loglevel', { + description => "Log level for forwarded traffic." }), tcp_flags_log_level => get_standard_option('pve-fw-loglevel', { description => "Log level for illegal tcp flags filter." }), smurf_log_level => get_standard_option('pve-fw-loglevel', { @@ -1476,6 +1485,23 @@ our $vm_option_properties = { }; +our $vnet_option_properties = { + enable => { + description => "Enable/disable firewall rules.", + type => 'boolean', + default => 0, + optional => 1, + }, + policy_forward => { + description => "Forward policy.", + type => 'string', + optional => 1, + enum => ['ACCEPT', 'DROP'], + }, + log_level_forward => get_standard_option('pve-fw-loglevel', { + description => "Log level for forwarded traffic." }), +}; + my $addr_list_descr = "This can refer to a single IP address, an IP set ('+ipsetname') or an IP alias definition. You can also specify an address range like '20.34.101.207-201.3.9.99', or a list of IP addresses and networks (entries are separated by comma). Please do not mix IPv4 and IPv6 addresses inside such lists."; @@ -1493,7 +1519,7 @@ my $rule_properties = { description => "Rule type.", type => 'string', optional => 1, - enum => ['in', 'out', 'group'], + enum => ['in', 'out', 'forward', 'group'], }, action => { description => "Rule action ('ACCEPT', 'DROP', 'REJECT') or security group name.", @@ -1651,10 +1677,20 @@ my $rule_env_iface_lookup = { 'ct' => 1, 'vm' => 1, 'group' => 0, + 'vnet' => 0, 'cluster' => 1, 'host' => 1, }; +my $rule_env_direction_lookup = { + 'ct' => ['in', 'out', 'group'], + 'vm' => ['in', 'out', 'group'], + 'group' => ['in', 'out', 'forward'], + 'cluster' => ['in', 'out', 'forward', 'group'], + 'host' => ['in', 'out', 'forward', 'group'], + 'vnet' => ['forward', 'group'], +}; + sub verify_rule { my ($rule, $cluster_conf, $fw_conf, $rule_env, $noerr) = @_; @@ -1726,8 +1762,17 @@ sub verify_rule { &$add_error('action', "missing property") if !$action; if ($type) { - if ($type eq 'in' || $type eq 'out') { - &$add_error('action', "unknown action '$action'") + my $valid_types = $rule_env_direction_lookup->{$rule_env} + or die "unknown rule_env '$rule_env'\n"; + + $add_error->('type', "invalid rule type '$type' for rule_env '$rule_env'") + if !(grep { $_ eq $type } @$valid_types); + + if ($type eq 'in' || $type eq 'out' || $type eq 'forward') { + $add_error->('action', 'cannot define REJECT rules on forward chain') + if $type eq 'forward' && $action eq 'REJECT'; + + $add_error->('action', "unknown action '$action'") if $action && ($action !~ m/^(ACCEPT|DROP|REJECT)$/); } elsif ($type eq 'group') { &$add_error('type', "security groups not allowed") @@ -2830,7 +2875,7 @@ sub parse_fw_rule { $rule->{type} = lc($1); $rule->{action} = $2; - if ($rule->{type} eq 'in' || $rule->{type} eq 'out') { + if ($rule->{type} eq 'in' || $rule->{type} eq 'out' || $rule->{type} eq 'forward') { if ($rule->{action} =~ m/^(\S+)\((ACCEPT|DROP|REJECT)\)$/) { $rule->{macro} = $1; $rule->{action} = $2; @@ -2944,7 +2989,7 @@ sub parse_hostfw_option { 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) { + } elsif ($line =~ m/^(log_level_(?:in|out|forward)|tcp_flags_log_level|smurf_log_level):\s*(($loglevels)\s*)?$/i) { $opt = lc($1); $value = $2 ? lc($3) : ''; } elsif ($line =~ m/^(nf_conntrack_helpers):\s*(((\S+)[,]?)+)\s*$/i) { @@ -2975,7 +3020,7 @@ sub parse_clusterfw_option { } elsif ($line =~ m/^(ebtables):\s*(0|1)\s*$/i) { $opt = lc($1); $value = int($2); - } elsif ($line =~ m/^(policy_(in|out)):\s*(ACCEPT|DROP|REJECT)\s*$/i) { + } elsif ($line =~ m/^(policy_(in|out|forward)):\s*(ACCEPT|DROP|REJECT)\s*$/i) { $opt = lc($1); $value = uc($3); } elsif ($line =~ m/^(log_ratelimit):\s*(\S+)\s*$/) { @@ -2988,6 +3033,24 @@ sub parse_clusterfw_option { return ($opt, $value); } +sub parse_vnetfw_option { + my ($line) = @_; + + my ($opt, $value); + + if ($line =~ m/^(enable):\s*(\d+)\s*$/i) { + $opt = lc($1); + $value = int($2); + } elsif ($line =~ m/^(policy_forward):\s*(ACCEPT|DROP)\s*$/i) { + $opt = lc($1); + $value = uc($2); + } else { + die "can't parse option '$line'\n" + } + + return ($opt, $value); +} + sub resolve_alias { my ($clusterfw_conf, $fw_conf, $cidr, $scope) = @_; @@ -3154,6 +3217,8 @@ sub generic_fw_config_parser { ($opt, $value) = parse_clusterfw_option($line); } elsif ($rule_env eq 'host') { ($opt, $value) = parse_hostfw_option($line); + } elsif ($rule_env eq 'vnet') { + ($opt, $value) = parse_vnetfw_option($line); } else { ($opt, $value) = parse_vmfw_option($line); } @@ -3293,6 +3358,10 @@ sub lock_vmfw_conf { return PVE::Firewall::Helpers::lock_vmfw_conf(@_); } +sub lock_vnetfw_conf { + return PVE::Firewall::Helpers::lock_vnetfw_conf(@_); +} + sub load_vmfw_conf { my ($cluster_conf, $rule_env, $vmid, $dir) = @_; @@ -3319,7 +3388,7 @@ my $format_rules = sub { my $raw = ''; foreach my $rule (@$rules) { - if ($rule->{type} eq 'in' || $rule->{type} eq 'out' || $rule->{type} eq 'group') { + if (grep { $_ eq $rule->{type} } qw(in out forward group)) { $raw .= '|' if defined($rule->{enable}) && !$rule->{enable}; $raw .= uc($rule->{type}); if ($rule->{macro}) { @@ -3797,6 +3866,50 @@ sub save_hostfw_conf { } } +sub load_vnetfw_conf { + my ($cluster_conf, $rule_env, $vnet, $dir) = @_; + + $rule_env = 'vnet' if !defined($rule_env); + + my $filename = "$vnetfw_conf_dir/$vnet.fw"; + + my $empty_conf = { + rules => [], + options => {}, + }; + + my $vnetfw_conf = generic_fw_config_parser($filename, $cluster_conf, $empty_conf, $rule_env); + $vnetfw_conf->{vnet} = $vnet; + + return $vnetfw_conf; +} + +sub save_vnetfw_conf { + my ($vnet, $conf) = @_; + + my $filename = "$vnetfw_conf_dir/$vnet.fw"; + + my $raw = ''; + + my $options = $conf->{options}; + $raw .= &$format_options($options) if $options && scalar(keys %$options); + + my $rules = $conf->{rules}; + if ($rules && scalar(@$rules)) { + $raw .= "[RULES]\n\n"; + $raw .= &$format_rules($rules, 1); + $raw .= "\n"; + } + + mkdir($vnetfw_conf_dir, 0755) if !-d $vnetfw_conf_dir; + + if ($raw) { + PVE::Tools::file_set_contents($filename, $raw); + } else { + unlink $filename; + } +} + sub compile { my ($cluster_conf, $hostfw_conf, $vmdata, $corosync_conf) = @_; diff --git a/src/PVE/Firewall/Helpers.pm b/src/PVE/Firewall/Helpers.pm index 7dcbca3..0b465ae 100644 --- a/src/PVE/Firewall/Helpers.pm +++ b/src/PVE/Firewall/Helpers.pm @@ -32,6 +32,18 @@ sub lock_vmfw_conf { return $res; } +sub lock_vnetfw_conf { + my ($vnet, $timeout, $code, @param) = @_; + + die "can't lock vnet firewall config for undefined vnet\n" + if !defined($vnet); + + my $res = PVE::Cluster::cfs_lock_firewall("vnet-$vnet", $timeout, $code, @param); + die $@ if $@; + + return $res; +} + sub remove_vmfw_conf { my ($vmid) = @_; -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel