Files
weave/npc/controller_test.go
T
Martynas Pumputis 0a0dbe115a npc: Track ipset entries per user
There was a bug in handling "default-allow" ipsets - it could not
cope with out of order receive of Pod updates. E.g:

    1) AddPod(Pod_A, PodIP_A),
    2) AddPod(Pod_B, PodIP_A),
    3) DeletePod(Pod_A),

would result with PodIP_A being removed from "default-allow" ipset,
as such ipset entries were not refcounted. Bypassing of refcounting
was due to the fact that adding a netpol which dst-selects a Pod which
IP addr is in "default-allow" ipset has to remove the IP addr from
the ipset regardless of refcount value of the IP addr entry in the
ipset. So, refcounting in such case didn't make sense and it was
bypassed.

This commit changes the way entries are refcounted in ipsets, and
it makes "default-allow" ipset the same citizen as other ipsets, so
from now "default-allow" ipset is refcounted.
2017-11-21 14:41:29 +02:00

379 lines
11 KiB
Go

package npc
import (
"log"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"github.com/weaveworks/weave/npc/ipset"
coreapi "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
)
type mockSet struct {
name ipset.Name
setType ipset.Type
subSets map[string]map[types.UID]bool
}
type mockIPSet struct {
sets map[string]mockSet
}
func newMockIPSet() mockIPSet {
return mockIPSet{sets: make(map[string]mockSet)}
}
func (i *mockIPSet) Create(ipsetName ipset.Name, ipsetType ipset.Type) error {
if _, ok := i.sets[string(ipsetName)]; ok {
return errors.Errorf("ipset %s already exists", ipsetName)
}
i.sets[string(ipsetName)] = mockSet{name: ipsetName, setType: ipsetType, subSets: make(map[string]map[types.UID]bool)}
return nil
}
func (i *mockIPSet) AddEntry(user types.UID, ipsetName ipset.Name, entry string, comment string) error {
log.Printf("adding entry %s to %s for %s", entry, ipsetName, user)
if _, ok := i.sets[string(ipsetName)]; !ok {
return errors.Errorf("%s does not exist", entry)
}
if i.sets[string(ipsetName)].subSets[entry] == nil {
i.sets[string(ipsetName)].subSets[entry] = make(map[types.UID]bool)
}
if _, ok := i.sets[string(ipsetName)].subSets[entry][user]; ok {
return errors.Errorf("user %s already owns entry %s", user, entry)
}
i.sets[string(ipsetName)].subSets[entry][user] = true
return nil
}
func (i *mockIPSet) DelEntry(user types.UID, ipsetName ipset.Name, entry string) error {
log.Printf("deleting entry %s from %s for %s", entry, ipsetName, user)
if _, ok := i.sets[string(ipsetName)]; !ok {
return errors.Errorf("ipset %s does not exist", ipsetName)
}
if _, ok := i.sets[string(ipsetName)].subSets[entry][user]; !ok {
return errors.Errorf("user %s does not own entry %s", user, entry)
}
delete(i.sets[string(ipsetName)].subSets[entry], user)
if len(i.sets[string(ipsetName)].subSets[entry]) == 0 {
delete(i.sets[string(ipsetName)].subSets, entry)
}
return nil
}
func (i *mockIPSet) Exist(user types.UID, ipsetName ipset.Name, entry string) bool {
_, ok := i.sets[string(ipsetName)].subSets[entry][user]
return ok
}
func (i *mockIPSet) entryExists(ipsetName ipset.Name, entry string) bool {
return len(i.sets[string(ipsetName)].subSets[entry]) > 0
}
func (i *mockIPSet) Flush(ipsetName ipset.Name) error {
return errors.New("Not Implemented")
}
func (i *mockIPSet) FlushAll() error {
return errors.New("Not Implemented")
}
func (i *mockIPSet) Destroy(ipsetName ipset.Name) error {
if _, ok := i.sets[string(ipsetName)]; !ok {
return errors.Errorf("ipset %s does not exist", ipsetName)
}
delete(i.sets, string(ipsetName))
return nil
}
func (i *mockIPSet) DestroyAll() error {
return errors.New("Not Implemented")
}
func (i *mockIPSet) List(prefix string) ([]ipset.Name, error) {
return []ipset.Name{}, errors.New("Not Implemented")
}
type mockIPTables struct {
}
func (ipt *mockIPTables) Append(table, chain string, rulespec ...string) error {
return nil
}
func (ipt *mockIPTables) Delete(table, chain string, rulespec ...string) error {
return nil
}
func (ipt *mockIPTables) Insert(table, chain string, pos int, rulespec ...string) error {
return nil
}
func TestRegressionPolicyNamespaceOrdering3059(t *testing.T) {
// Test for race condition between namespace and networkpolicy events
// https://github.com/weaveworks/weave/issues/3059
sourceNamespace := &coreapi.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "source",
Labels: map[string]string{
"app": "source",
},
},
}
destinationNamespace := &coreapi.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "destination",
},
}
port := intstr.FromInt(12345)
networkPolicy := &networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "network-policy",
Namespace: "destination",
},
Spec: networkingv1.NetworkPolicySpec{
Ingress: []networkingv1.NetworkPolicyIngressRule{
{
From: []networkingv1.NetworkPolicyPeer{
{
NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "source",
},
},
},
},
Ports: []networkingv1.NetworkPolicyPort{
{
Port: &port,
},
},
},
},
},
}
// Namespaces first
m := newMockIPSet()
controller := New("foo", false, &mockIPTables{}, &m)
const (
selectorIPSetName = "weave-I239Zp%sCvoVt*D6u=A!2]YEk"
sourceIPSetName = "weave-HboJG1fGgG]/SR%k9H#hv5e96"
)
controller.AddNamespace(sourceNamespace)
controller.AddNamespace(destinationNamespace)
controller.AddNetworkPolicy(networkPolicy)
require.Equal(t, true, len(m.sets[selectorIPSetName].subSets[sourceIPSetName]) > 0)
// NetworkPolicy first
m = newMockIPSet()
controller = New("foo", false, &mockIPTables{}, &m)
controller.AddNetworkPolicy(networkPolicy)
controller.AddNamespace(sourceNamespace)
controller.AddNamespace(destinationNamespace)
require.Equal(t, true, len(m.sets[selectorIPSetName].subSets[sourceIPSetName]) > 0)
}
// Tests default-allow ipset behavior when running in non-legacy mode.
func TestDefaultAllow(t *testing.T) {
const (
defaultAllowIPSetName = "weave-E.1.0W^NGSp]0_t5WwH/]gX@L"
fooPodIP = "10.32.0.10"
barPodIP = "10.32.0.11"
barPodNewIP = "10.32.0.12"
)
m := newMockIPSet()
controller := New("bar", false, &mockIPTables{}, &m)
defaultNamespace := &coreapi.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
},
}
controller.AddNamespace(defaultNamespace)
// Should create an ipset for default-allow
require.Contains(t, m.sets, defaultAllowIPSetName)
podFoo := &coreapi.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "foo",
Namespace: "default",
Name: "foo",
Labels: map[string]string{"run": "foo"}},
Status: coreapi.PodStatus{PodIP: fooPodIP}}
controller.AddPod(podFoo)
// Should add the foo pod to default-allow
require.True(t, m.entryExists(defaultAllowIPSetName, fooPodIP))
podBar := &coreapi.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "bar",
Namespace: "default",
Name: "bar",
Labels: map[string]string{"run": "bar"}},
Status: coreapi.PodStatus{PodIP: barPodIP}}
podBarNoIP := &coreapi.Pod{ObjectMeta: podBar.ObjectMeta}
controller.AddPod(podBarNoIP)
controller.UpdatePod(podBarNoIP, podBar)
// Should add the bar pod to default-allow
require.True(t, m.entryExists(defaultAllowIPSetName, barPodIP))
// Allow access from the bar pod to the foo pod
netpol := &networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "allow-from-bar-to-foo",
Namespace: "default",
},
Spec: networkingv1.NetworkPolicySpec{
PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"run": "foo"}},
Ingress: []networkingv1.NetworkPolicyIngressRule{{
From: []networkingv1.NetworkPolicyPeer{{
PodSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"run": "bar"},
},
}},
}},
},
}
controller.AddNetworkPolicy(netpol)
// Should remove the foo pod from default-allow as the netpol selects it
require.False(t, m.entryExists(defaultAllowIPSetName, fooPodIP))
require.True(t, m.entryExists(defaultAllowIPSetName, barPodIP))
podBarWithNewIP := *podBar
podBarWithNewIP.Status.PodIP = barPodNewIP
controller.UpdatePod(podBar, &podBarWithNewIP)
// Should update IP addr of the bar pod in default-allow
require.False(t, m.entryExists(defaultAllowIPSetName, barPodIP))
require.True(t, m.entryExists(defaultAllowIPSetName, barPodNewIP))
controller.UpdatePod(&podBarWithNewIP, podBarNoIP)
// Should remove the bar pod from default-allow as it does not have any IP addr
require.False(t, m.entryExists(defaultAllowIPSetName, barPodNewIP))
podFooWithNewLabel := *podFoo
podFooWithNewLabel.ObjectMeta.Labels = map[string]string{"run": "new-foo"}
controller.UpdatePod(podFoo, &podFooWithNewLabel)
// Should bring back the foo pod to default-allow as it does not match dst of any netpol
require.True(t, m.entryExists(defaultAllowIPSetName, fooPodIP))
controller.UpdatePod(&podFooWithNewLabel, podFoo)
// Should remove from default-allow as it matches the netpol after the update
require.False(t, m.entryExists(defaultAllowIPSetName, fooPodIP))
controller.DeleteNetworkPolicy(netpol)
// Should bring back the foo pod to default-allow as no netpol selects it
require.True(t, m.entryExists(defaultAllowIPSetName, fooPodIP))
controller.DeletePod(podFoo)
// Should remove foo pod from default-allow
require.False(t, m.entryExists(defaultAllowIPSetName, fooPodIP))
}
func TestOutOfOrderPodEvents(t *testing.T) {
const (
defaultAllowIPSetName = "weave-E.1.0W^NGSp]0_t5WwH/]gX@L"
runBarIPSetName = "weave-bZ~x=yBgzH)Ht()K*Uv3z{M]Y"
podIP = "10.32.0.10"
)
m := newMockIPSet()
controller := New("qux", false, &mockIPTables{}, &m)
defaultNamespace := &coreapi.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
},
}
controller.AddNamespace(defaultNamespace)
podFoo := &coreapi.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "foo",
Namespace: "default",
Name: "foo",
Labels: map[string]string{"run": "foo"}},
Status: coreapi.PodStatus{PodIP: podIP}}
controller.AddPod(podFoo)
// Should be in default-allow as no netpol selects podFoo
require.True(t, m.entryExists(defaultAllowIPSetName, podIP))
netpol := &networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "allow-from-bar-to-foo",
Namespace: "default",
},
Spec: networkingv1.NetworkPolicySpec{
PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"run": "foo"}},
Ingress: []networkingv1.NetworkPolicyIngressRule{{
From: []networkingv1.NetworkPolicyPeer{{
PodSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"run": "bar"},
},
}},
}},
},
}
controller.AddNetworkPolicy(netpol)
// Shouldn't be in default-allow as netpol above selects podFoo
require.False(t, m.entryExists(defaultAllowIPSetName, podIP))
podBar := &coreapi.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "bar",
Namespace: "default",
Name: "bar",
Labels: map[string]string{"run": "bar"}},
Status: coreapi.PodStatus{PodIP: podIP}}
controller.AddPod(podBar)
// Should be in default-allow as no netpol selects podBar
require.True(t, m.entryExists(defaultAllowIPSetName, podIP))
require.True(t, m.Exist(podBar.ObjectMeta.UID, defaultAllowIPSetName, podIP))
// Should be in run=bar ipset
require.True(t, m.entryExists(runBarIPSetName, podIP))
controller.DeletePod(podFoo)
// Multiple duplicate events should not affect npc state
controller.DeletePod(podFoo)
controller.DeletePod(podFoo)
// Should be in default-allow as no netpol selects podBar and podFoo removal
// should not affect podBar in default-allow
require.True(t, m.entryExists(defaultAllowIPSetName, podIP))
controller.DeletePod(podBar)
// Should remove from default-allow and run=bar ipsets
require.Equal(t, 0, len(m.sets[defaultAllowIPSetName].subSets))
require.False(t, m.entryExists(runBarIPSetName, podIP))
}