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";
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', {
my ($str) = @_;
return if $str =~ m/^(\+)(\S+)$/; # ipset ref
+ return if $str =~ m/^${security_group_pattern}$/; # aliases
my $count = 0;
my $iprange = 0;
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;
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";
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";
}
}
}
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} || {};
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
}
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");
}
}
}
- if ($options->{tcpflags}) {
+ if ($host_options->{tcpflags}) {
ruleset_addrule($ruleset, $chain, "-p tcp -j PVEFW-tcpflags");
}
}
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
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);
}
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);
# 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");
}
}
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";
# 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);
}
# 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);
}
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) {
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) = @_;
next;
}
+ if ($line =~ m/^\[aliases\]$/i) {
+ $section = 'aliases';
+ next;
+ }
+
if ($line =~ m/^\[group\s+(\S+)\]\s*(?:#\s*(.*?)\s*)?$/i) {
$section = 'groups';
$group = lc($1);
$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); };
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 };
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) = @_;
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) {
# 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;
}
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);
$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, $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');
}
}
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}) {
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};
- 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');
}
}