Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Commit

Permalink
Don't run traffic on the INPUT chain through warden-dispatch
Browse files Browse the repository at this point in the history
Traffic between the host and its containers must not be limited in any
form. Only traffic between containers, and between containers and the
outside networks must be limited. To achieve this in the filter table,
warden no longer intervenes on traffic on both the INPUT and FORWARD
chains, but only on the FORWARD chain.

For easier introspection into what is going on this change also removes
the fast path for non-SYN TCP packets. If this proves to be a big
performance hit, it can be put back in place.

The network filtering tests are changed to no longer rely on the
internet being reachable when the suite it run. Filtering is now tested
by running multiple containers and testing reachability between them.
  • Loading branch information
Pieter Noordhuis and Tim Labeeuw committed Mar 19, 2013
1 parent 7ad023e commit debef6d
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 69 deletions.
57 changes: 32 additions & 25 deletions warden/root/linux/net.sh
Expand Up @@ -5,7 +5,7 @@ set -o nounset
set -o errexit
shopt -s nullglob

filter_dispatch_chain="warden-dispatch"
filter_forward_chain="warden-forward"
filter_default_chain="warden-default"
filter_instance_prefix="warden-instance-"
nat_prerouting_chain="warden-prerouting"
Expand All @@ -21,9 +21,31 @@ function external_ip() {
ip route get 8.8.8.8 | sed 's/.*src\s\(.*\)\s/\1/;tx;d;:x'
}

function teardown_deprecated_rules() {
# Remove jump to warden-dispatch from INPUT
iptables -S INPUT 2> /dev/null |
grep " -j warden-dispatch" |
sed -e "s/-A/-D/" -e "s/\s\+\$//" |
xargs --no-run-if-empty --max-lines=1 iptables

# Remove jump to warden-dispatch from FORWARD
iptables -S FORWARD 2> /dev/null |
grep " -j warden-dispatch" |
sed -e "s/-A/-D/" -e "s/\s\+\$//" |
xargs --no-run-if-empty --max-lines=1 iptables

# Prune warden-dispatch
iptables -F warden-dispatch 2> /dev/null || true

# Delete warden-dispatch
iptables -X warden-dispatch 2> /dev/null || true
}

function teardown_filter() {
# Prune dispatch chain
iptables -S ${filter_dispatch_chain} 2> /dev/null |
teardown_deprecated_rules

# Prune warden-forward chain
iptables -S ${filter_forward_chain} 2> /dev/null |
grep "\-g ${filter_instance_prefix}" |
sed -e "s/-A/-D/" -e "s/\s\+\$//" |
xargs --no-run-if-empty --max-lines=1 iptables
Expand All @@ -40,39 +62,26 @@ function teardown_filter() {
sed -e "s/-N/-X/" -e "s/\s\+\$//" |
xargs --no-run-if-empty --max-lines=1 iptables

# Remove jump to dispatch from INPUT
iptables -S INPUT 2> /dev/null |
grep " -j ${filter_dispatch_chain}" |
sed -e "s/-A/-D/" -e "s/\s\+\$//" |
xargs --no-run-if-empty --max-lines=1 iptables

# Remove jump to dispatch from FORWARD
# Remove jump to warden-forward from FORWARD
iptables -S FORWARD 2> /dev/null |
grep " -j ${filter_dispatch_chain}" |
grep " -j ${filter_forward_chain}" |
sed -e "s/-A/-D/" -e "s/\s\+\$//" |
xargs --no-run-if-empty --max-lines=1 iptables

# Flush dispatch chain
iptables -F ${filter_dispatch_chain} 2> /dev/null || true

# Flush default chain
iptables -F ${filter_forward_chain} 2> /dev/null || true
iptables -F ${filter_default_chain} 2> /dev/null || true
}

function setup_filter() {
teardown_filter

# Create or flush dispatch chain
iptables -N ${filter_dispatch_chain} 2> /dev/null || iptables -F ${filter_dispatch_chain}
iptables -A ${filter_dispatch_chain} -j DROP

# If the packet is NOT a canonical SYN packet, allow immediately
iptables -I ${filter_dispatch_chain} 1 -p tcp ! --syn --jump ACCEPT
# Create or flush forward chain
iptables -N ${filter_forward_chain} 2> /dev/null || iptables -F ${filter_forward_chain}
iptables -A ${filter_forward_chain} -j DROP

# Create or flush default chain
iptables -N ${filter_default_chain} 2> /dev/null || iptables -F ${filter_default_chain}

# Whitelist
for n in ${ALLOW_NETWORKS}; do
if [ "$n" == "" ]
then
Expand All @@ -91,9 +100,7 @@ function setup_filter() {
iptables -A ${filter_default_chain} --destination "$n" --jump DROP
done

# Bind chain
iptables -A INPUT -i w-+ --jump ${filter_dispatch_chain}
iptables -A FORWARD -i w-+ --jump ${filter_dispatch_chain}
iptables -A FORWARD -i w-+ --jump ${filter_forward_chain}
}

function teardown_nat() {
Expand Down
10 changes: 5 additions & 5 deletions warden/root/linux/skeleton/net.sh
Expand Up @@ -9,7 +9,7 @@ cd $(dirname "${0}")

source ./etc/config

filter_dispatch_chain="warden-dispatch"
filter_forward_chain="warden-forward"
filter_default_chain="warden-default"
filter_instance_prefix="warden-instance-"
filter_instance_chain="${filter_instance_prefix}${id}"
Expand All @@ -18,8 +18,8 @@ nat_instance_prefix="warden-instance-"
nat_instance_chain="${filter_instance_prefix}${id}"

function teardown_filter() {
# Prune dispatch chain
iptables -S ${filter_dispatch_chain} 2> /dev/null |
# Prune forward chain
iptables -S ${filter_forward_chain} 2> /dev/null |
grep "\-g ${filter_instance_chain}\b" |
sed -e "s/-A/-D/" |
xargs --no-run-if-empty --max-lines=1 iptables
Expand All @@ -37,8 +37,8 @@ function setup_filter() {
iptables -A ${filter_instance_chain} \
--goto ${filter_default_chain}

# Bind instance chain to dispatch chain
iptables -I ${filter_dispatch_chain} 2 \
# Bind instance chain to forward chain
iptables -I ${filter_forward_chain} \
--in-interface ${network_host_iface} \
--goto ${filter_instance_chain}
}
Expand Down
90 changes: 51 additions & 39 deletions warden/spec/container/linux_spec.rb
Expand Up @@ -95,6 +95,9 @@ def create_client
client
end

let(:allow_networks) { [] }
let(:deny_networks) { [] }

def start_warden
FileUtils.rm_f(unix_domain_path)

Expand All @@ -116,8 +119,8 @@ def start_warden
"network" => {
"pool_start_address" => @start_address,
"pool_size" => 64,
"allow_networks" => ["4.2.2.3/32"],
"deny_networks" => ["4.2.2.0/24"] },
"allow_networks" => allow_networks,
"deny_networks" => deny_networks },
"port" => {
"pool_start_port" => 64000,
"pool_size" => 1000 },
Expand Down Expand Up @@ -368,79 +371,88 @@ def limit_bandwidth(options = {})
end

describe "net_out", :netfilter => true do
attr_reader :handle

def net_out_ok?(options = {})
response = client.net_out(options.merge(:handle => handle))
def net_out(options = {})
response = client.net_out(options)
response.should be_ok
response
end

def net_out_not_ok?(err_message, options = {})
expect do
client.net_out(options.merge(:handle => handle))
end.to raise_error(Warden::Client::ServerError, /#{err_message}/)
end

def run(script)
def run(handle, script)
response = client.run(:handle => handle, :script => script)
response.should be_ok
response
end

def reachable?(ip)
response = run "ping -q -W 1 -c 1 #{ip}"
def reachable?(handle, ip)
response = run(handle, "ping -q -W 1 -c 1 #{ip}")
response.stdout =~ /\b(\d+) received\b/i
$1.to_i > 0
end

before do
@handle = client.create.handle
end
context "reachability" do
# Allow traffic to the first two subnets
let(:allow_networks) do
[0, 4].map do |i|
"#{(next_class_c + i).to_human}/30"
end
end

# Deny traffic to everywhere else
let(:deny_networks) do
["0.0.0.0/0"]
end

describe "to denied range" do
before do
# Make sure the host can reach an ip in the denied range
`ping -q -w 1 -c 1 4.2.2.2` =~ /\b(\d+) received\b/i
$1.to_i.should == 1
@containers = 3.times.inject([]) do |a, _|
handle = client.create.handle
a << { :handle => handle, :ip => client.info(:handle => handle).container_ip }
a
end
end

it "should deny traffic" do
reachable?("4.2.2.2").should be_false
it "reaches every container from the host" do
@containers.each do |e|
`ping -q -w 1 -c 1 #{e[:ip]}` =~ /\b(\d+) received\b/i
$1.to_i.should == 1
end
end

it "should allow traffic after explicitly allowing it" do
net_out_ok?(:network => "4.2.2.2")
reachable?("4.2.2.2").should be_true
it "allows traffic to networks configured in allowed networks" do
reachable?(@containers[0][:handle], @containers[1][:ip]).should be_true
reachable?(@containers[1][:handle], @containers[0][:ip]).should be_true
end
end

describe "to allowed range" do
it "should allow traffic" do
reachable?("4.2.2.3").should be_true
it "does not allow traffic to networks not configured in allowed networks" do
reachable?(@containers[0][:handle], @containers[2][:ip]).should be_false
reachable?(@containers[2][:handle], @containers[0][:ip]).should be_false
end
end

describe "to other range" do
it "should allow traffic" do
reachable?("8.8.8.8").should be_true
it "allows traffic to networks after net_out" do
net_out(:handle => @containers[0][:handle], :network => @containers[2][:ip])
reachable?(@containers[0][:handle], @containers[2][:ip]).should be_true
reachable?(@containers[2][:handle], @containers[0][:ip]).should be_true
end
end

describe "check network and port fields" do
let(:handle) { client.create.handle }

it "should raise error when both fields are absent" do
net_out_not_ok?("specify network and/or port")
expect do
net_out(:handle => handle)
end.to raise_error(Warden::Client::ServerError, %r"specify network and/or port"i)
end

it "should not raise error when network field is present" do
net_out_ok?(:network => "4.2.2.2")
net_out(:handle => handle, :network => "4.2.2.2").should be_ok
end

it "should not raise error when port field is present" do
net_out_ok?(:port => 1234)
net_out(:handle => handle, :port => 1234).should be_ok
end

it "should not raise error when both network and port fields are present" do
net_out_ok?(:network => "4.2.2.2", :port => 1234)
net_out(:handle => handle, :network => "4.2.2.2", :port => 1234).should be_ok
end
end
end
Expand Down

0 comments on commit debef6d

Please sign in to comment.