From 3a616aa0ae97f3dc0fd3faa0361d5c4e8b3b312f Mon Sep 17 00:00:00 2001 From: Alexandre Derumier Date: Fri, 7 Feb 2014 16:22:27 +0100 Subject: [PATCH 1/1] basic bridge iptables implementation ./pvefw enabletaprules -netid net0 -vmid 110 ./pvefw disabletaprules -netid net0 -vmid 110 sample firewall config file --------------------------- [IN] ACCEPT net0 - - tcp 22 - ACCEPT net0 - - icmp - - GROUP-securityname1 net0 - - - - - #apply security group rules GROUP-securityname2 net0 - - icmp - - #apply security group rules on icmp only [OUT] ACCEPT net0 - - icmp - - ACCEPT net0 - - tcp 80 - GROUP-securityname2 net0 - - - - - #apply security group rules Signed-off-by: Alexandre Derumier --- PVE/Firewall.pm | 216 +++++++++++++++++++++++++++++++++++++++++++++++- pvefw | 61 ++++++++++++++ 2 files changed, 276 insertions(+), 1 deletion(-) diff --git a/PVE/Firewall.pm b/PVE/Firewall.pm index 6a3f225..4bf4284 100644 --- a/PVE/Firewall.pm +++ b/PVE/Firewall.pm @@ -8,10 +8,13 @@ use PVE::QemuServer; use File::Path; use IO::File; use Net::IP; +use PVE::Tools qw(run_command); use Data::Dumper; my $macros; +my @ruleset = (); + sub get_shorewall_macros { return $macros if $macros; @@ -56,6 +59,7 @@ sub get_etc_services { $etc_services = $services; + return $etc_services; } @@ -120,6 +124,216 @@ sub parse_port_name_number_or_range { my $rule_format = "%-15s %-30s %-30s %-15s %-15s %-15s\n"; +sub iptables { + my ($cmd) = @_; + + run_command("/sbin/iptables $cmd", outfunc => sub {}, errfunc => sub {}); +} + +sub iptables_restore { + + unshift (@ruleset, '*filter'); + push (@ruleset, 'COMMIT'); + + my $cmdlist = join("\n", @ruleset); + + run_command("echo '$cmdlist' | /sbin/iptables-restore -n", outfunc => sub {}); +} + +sub iptables_addrule { + my ($rule) = @_; + + push (@ruleset, $rule); +} + +sub iptables_chain_exist { + my ($chain) = @_; + + eval{ + iptables("-n --list $chain"); + }; + return undef if $@; + + return 1; +} + +sub iptables_rule_exist { + my ($rule) = @_; + + eval{ + iptables("-C $rule"); + }; + return undef if $@; + + return 1; +} + +sub iptables_generate_rule { + my ($chain, $rule) = @_; + + my $cmd = "-A $chain"; + + $cmd .= " -s $rule->{source}" if $rule->{source}; + $cmd .= " -d $rule->{dest}" if $rule->{destination}; + $cmd .= " -p $rule->{proto}" if $rule->{proto}; + $cmd .= " --dport $rule->{dport}" if $rule->{dport}; + $cmd .= " --sport $rule->{sport}" if $rule->{sport}; + $cmd .= " -j $rule->{action}" if $rule->{action}; + + iptables_addrule($cmd); + +} + +sub generate_bridge_rules { + my ($bridge) = @_; + + if(!iptables_chain_exist("BRIDGEFW-OUT")){ + iptables_addrule(":BRIDGEFW-OUT - [0:0]"); + } + + if(!iptables_chain_exist("BRIDGEFW-IN")){ + iptables_addrule(":BRIDGEFW-IN - [0:0]"); + } + + if(!iptables_chain_exist("proxmoxfw-FORWARD")){ + iptables_addrule(":proxmoxfw-FORWARD - [0:0]"); + iptables_addrule("-I FORWARD -j proxmoxfw-FORWARD"); + iptables_addrule("-A proxmoxfw-FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT"); + iptables_addrule("-A proxmoxfw-FORWARD -m physdev --physdev-is-in --physdev-is-bridged -j BRIDGEFW-OUT"); + iptables_addrule("-A proxmoxfw-FORWARD -m physdev --physdev-is-out --physdev-is-bridged -j BRIDGEFW-IN"); + + } + + generate_proxmoxfwinput(); + + if(!iptables_chain_exist("$bridge-IN")){ + iptables_addrule(":$bridge-IN - [0:0]"); + iptables_addrule("-A proxmoxfw-FORWARD -i $bridge -j DROP"); #disable interbridge routing + iptables_addrule("-A BRIDGEFW-IN -j $bridge-IN"); + iptables_addrule("-A $bridge-IN -j ACCEPT"); + + } + + if(!iptables_chain_exist("$bridge-OUT")){ + iptables_addrule(":$bridge-OUT - [0:0]"); + iptables_addrule("-A proxmoxfw-FORWARD -o $bridge -j DROP"); # disable interbridge routing + iptables_addrule("-A BRIDGEFW-OUT -j $bridge-OUT"); + + } + +} + + +sub generate_tap_rules_direction { + my ($iface, $netid, $rules, $bridge, $direction) = @_; + + my $tapchain = "$iface-$direction"; + + iptables_addrule(":$tapchain - [0:0]"); + + iptables_addrule("-A $tapchain -m state --state INVALID -j DROP"); + iptables_addrule("-A $tapchain -m state --state RELATED,ESTABLISHED -j ACCEPT"); + + if (scalar(@$rules)) { + foreach my $rule (@$rules) { + next if $rule->{iface} && $rule->{iface} ne $netid; + if($rule->{action} =~ m/^(GROUP-(\S+))$/){ + $rule->{action} .= "-$direction"; + #generate empty group rule if don't exist + if(!iptables_chain_exist($rule->{action})){ + generate_group_rules($2); + } + } + #we go to vmbr-IN if accept in out rules + $rule->{action} = "$bridge-IN" if $rule->{action} eq 'ACCEPT' && $direction eq 'OUT'; + iptables_generate_rule($tapchain, $rule); + } + } + + iptables_addrule("-A $tapchain -j LOG --log-prefix \"$tapchain-dropped: \" --log-level 4"); + iptables_addrule("-A $tapchain -j DROP"); + + #plug the tap chain to bridge chain + my $physdevdirection = $direction eq 'IN' ? "out":"in"; + my $rule = "$bridge-$direction -m physdev --physdev-$physdevdirection $iface --physdev-is-bridged -j $tapchain"; + + if(!iptables_rule_exist($rule)){ + iptables_addrule("-I $rule"); + } + + if($direction eq 'OUT'){ + #add tap->host rules + my $rule = "proxmoxfw-INPUT -m physdev --physdev-$physdevdirection $iface -j $tapchain"; + + if(!iptables_rule_exist($rule)){ + iptables_addrule("-A $rule"); + } + } +} + +sub generate_tap_rules { + my ($net, $netid, $vmid) = @_; + + my $filename = "/etc/pve/firewall/$vmid.fw"; + my $fh = IO::File->new($filename, O_RDONLY); + return if !$fh; + + #generate bridge rules + my $bridge = $net->{bridge}; + my $tag = $net->{tag}; + $bridge .= "v$tag" if $tag; + + #generate tap chain + my $rules = parse_fw_rules($filename, $fh); + + my $inrules = $rules->{in}; + my $outrules = $rules->{out}; + + my $iface = "tap".$vmid."i".$1 if $netid =~ m/net(\d+)/; + + generate_bridge_rules($bridge); + generate_tap_rules_direction($iface, $netid, $inrules, $bridge, 'IN'); + generate_tap_rules_direction($iface, $netid, $outrules, $bridge, 'OUT'); + iptables_restore(); +} + +sub flush_tap_rules { + my ($net, $netid, $vmid) = @_; + + my $bridge = $net->{bridge}; + my $iface = "tap".$vmid."i".$1 if $netid =~ m/net(\d+)/; + + flush_tap_rules_direction($iface, $bridge, 'IN'); + flush_tap_rules_direction($iface, $bridge, 'OUT'); + iptables_restore(); +} + +sub flush_tap_rules_direction { + my ($iface, $bridge, $direction) = @_; + + my $tapchain = "$iface-$direction"; + + if(iptables_chain_exist($tapchain)){ + iptables_addrule("-F $tapchain"); + + my $physdevdirection = $direction eq 'IN' ? "out":"in"; + my $rule = "$bridge-$direction -m physdev --physdev-$physdevdirection $iface --physdev-is-bridged -j $tapchain"; + if(iptables_rule_exist($rule)){ + iptables_addrule("-D $rule"); + } + + if($direction eq 'OUT'){ + my $rule = "proxmoxfw-INPUT -m physdev --physdev-$physdevdirection $iface -j $tapchain"; + + if(!iptables_rule_exist($rule)){ + iptables_addrule("-D $rule"); + } + } + + iptables_addrule("-X $tapchain"); + } +} + my $generate_input_rule = sub { my ($zoneinfo, $rule, $net, $netid) = @_; @@ -461,7 +675,7 @@ sub parse_fw_rules { } my $service; - if ($action =~ m/^(ACCEPT|DROP|REJECT)$/) { + if ($action =~ m/^(ACCEPT|DROP|REJECT|GROUP-(\S+))$/) { # OK } elsif ($action =~ m/^(\S+)\((ACCEPT|DROP|REJECT)\)$/) { ($service, $action) = ($1, $2); diff --git a/pvefw b/pvefw index b10895e..9ba1adf 100755 --- a/pvefw +++ b/pvefw @@ -29,6 +29,65 @@ $rpcenv->init_request(); $rpcenv->set_language($ENV{LANG}); $rpcenv->set_user('root@pam'); +__PACKAGE__->register_method({ + name => 'enabletaprules', + path => 'enabletaprules', + method => 'POST', + parameters => { + additionalProperties => 0, + properties => { + vmid => get_standard_option('pve-vmid'), + netid => { + type => 'string', + }, + + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + # test if VM exists + my $vmid = $param->{vmid}; + my $netid = $param->{netid}; + + my $conf = PVE::QemuServer::load_config($vmid); + my $net = PVE::QemuServer::parse_net($conf->{$netid}); + + PVE::Firewall::generate_tap_rules($net, $netid, $vmid); + + return undef; + }}); + +__PACKAGE__->register_method({ + name => 'disabletaprules', + path => 'disabletaprules', + method => 'POST', + parameters => { + additionalProperties => 0, + properties => { + vmid => get_standard_option('pve-vmid'), + netid => { + type => 'string', + }, + + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + # test if VM exists + my $vmid = $param->{vmid}; + my $netid = $param->{netid}; + + my $conf = PVE::QemuServer::load_config($vmid); + my $net = PVE::QemuServer::parse_net($conf->{$netid}); + + PVE::Firewall::flush_tap_rules($net, $netid, $vmid); + + return undef; + }}); __PACKAGE__->register_method ({ name => 'compile', @@ -133,6 +192,8 @@ my $cmddef = { restart => [ __PACKAGE__, 'restart', []], stop => [ __PACKAGE__, 'stop', []], clear => [ __PACKAGE__, 'clear', []], + enabletaprules => [ __PACKAGE__, 'enabletaprules', []], + disabletaprules => [ __PACKAGE__, 'disabletaprules', []], }; my $cmd = shift; -- 2.39.2