Skip to content

Commit

Permalink
WIP: netpol whitelist
Browse files Browse the repository at this point in the history
  • Loading branch information
brb committed Oct 22, 2017
1 parent 8755da4 commit 9679eaa
Show file tree
Hide file tree
Showing 13 changed files with 723 additions and 150 deletions.
2 changes: 1 addition & 1 deletion DEPENDENCIES
@@ -1,3 +1,3 @@
DOCKER_VERSION=17.06
KUBERNETES_VERSION=1.6.2
KUBERNETES_VERSION=1.7.8
KUBERNETES_CNI_VERSION=0.5.1
135 changes: 112 additions & 23 deletions npc/analyser.go
Expand Up @@ -5,12 +5,13 @@ import (

apiv1 "k8s.io/api/core/v1"
extnapi "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/util/intstr"

"github.com/weaveworks/weave/npc/ipset"
)

func (ns *ns) analysePolicy(policy *extnapi.NetworkPolicy) (
func (ns *ns) analysePolicyLegacy(policy *extnapi.NetworkPolicy) (
rules map[string]*ruleSpec,
nsSelectors, podSelectors map[string]*selectorSpec,
err error) {
Expand All @@ -19,7 +20,7 @@ func (ns *ns) analysePolicy(policy *extnapi.NetworkPolicy) (
podSelectors = make(map[string]*selectorSpec)
rules = make(map[string]*ruleSpec)

dstSelector, err := newSelectorSpec(&policy.Spec.PodSelector, ns.name, ipset.HashIP)
dstSelector, err := newSelectorSpec(&policy.Spec.PodSelector, true, ns.name, ipset.HashIP)
if err != nil {
return nil, nil, nil, err
}
Expand All @@ -45,7 +46,7 @@ func (ns *ns) analysePolicy(policy *extnapi.NetworkPolicy) (
} else {
// Ports is present and contains at least one item, then this rule allows traffic
// only if the traffic matches at least one port in the ports list.
withNormalisedProtoAndPort(ingressRule.Ports, func(proto, port string) {
withNormalisedProtoAndPortLegacy(ingressRule.Ports, func(proto, port string) {
rule := newRuleSpec(&proto, nil, dstSelector, &port)
rules[rule.key] = rule
})
Expand All @@ -56,14 +57,14 @@ func (ns *ns) analysePolicy(policy *extnapi.NetworkPolicy) (
for _, peer := range ingressRule.From {
var srcSelector *selectorSpec
if peer.PodSelector != nil {
srcSelector, err = newSelectorSpec(peer.PodSelector, ns.name, ipset.HashIP)
srcSelector, err = newSelectorSpec(peer.PodSelector, false, ns.name, ipset.HashIP)
if err != nil {
return nil, nil, nil, err
}
podSelectors[srcSelector.key] = srcSelector
}
if peer.NamespaceSelector != nil {
srcSelector, err = newSelectorSpec(peer.NamespaceSelector, "", ipset.ListSet)
srcSelector, err = newSelectorSpec(peer.NamespaceSelector, false, "", ipset.ListSet)
if err != nil {
return nil, nil, nil, err
}
Expand All @@ -77,7 +78,7 @@ func (ns *ns) analysePolicy(policy *extnapi.NetworkPolicy) (
} else {
// Ports is present and contains at least one item, then this rule allows traffic
// only if the traffic matches at least one port in the ports list.
withNormalisedProtoAndPort(ingressRule.Ports, func(proto, port string) {
withNormalisedProtoAndPortLegacy(ingressRule.Ports, func(proto, port string) {
rule := newRuleSpec(&proto, srcSelector, dstSelector, &port)
rules[rule.key] = rule
})
Expand All @@ -89,26 +90,114 @@ func (ns *ns) analysePolicy(policy *extnapi.NetworkPolicy) (
return rules, nsSelectors, podSelectors, nil
}

func withNormalisedProtoAndPort(npps []extnapi.NetworkPolicyPort, f func(proto, port string)) {
for _, npp := range npps {
// If no proto is specified, default to TCP
proto := string(apiv1.ProtocolTCP)
if npp.Protocol != nil {
proto = string(*npp.Protocol)
}
func (ns *ns) analysePolicy(policy *networkingv1.NetworkPolicy) (
rules map[string]*ruleSpec,
nsSelectors, podSelectors map[string]*selectorSpec,
err error) {

nsSelectors = make(map[string]*selectorSpec)
podSelectors = make(map[string]*selectorSpec)
rules = make(map[string]*ruleSpec)

// If empty, matches all pods in a namespace
dstSelector, err := newSelectorSpec(&policy.Spec.PodSelector, true, ns.name, ipset.HashIP)
if err != nil {
return nil, nil, nil, err
}
podSelectors[dstSelector.key] = dstSelector

// If ingress is empty then this NetworkPolicy does not allow any traffic
if policy.Spec.Ingress == nil || len(policy.Spec.Ingress) == 0 {
return
}

for _, ingressRule := range policy.Spec.Ingress {
// If Ports is empty or missing, this rule matches all ports
allPorts := ingressRule.Ports == nil || len(ingressRule.Ports) == 0
// If From is empty or missing, this rule matches all sources
allSources := ingressRule.From == nil || len(ingressRule.From) == 0

if allSources {
if allPorts {
rule := newRuleSpec(nil, nil, dstSelector, nil)
rules[rule.key] = rule
} else {
withNormalisedProtoAndPort(ingressRule.Ports, func(proto, port string) {
rule := newRuleSpec(&proto, nil, dstSelector, &port)
rules[rule.key] = rule
})
}
} else {
for _, peer := range ingressRule.From {
var srcSelector *selectorSpec

// NetworkPolicyPeer describes a peer to allow traffic from.
// Exactly one of its fields must be specified.
if peer.PodSelector != nil {
srcSelector, err = newSelectorSpec(peer.PodSelector, false, ns.name, ipset.HashIP)
if err != nil {
return nil, nil, nil, err
}
podSelectors[srcSelector.key] = srcSelector

} else if peer.NamespaceSelector != nil {
srcSelector, err = newSelectorSpec(peer.NamespaceSelector, false, "", ipset.ListSet)
if err != nil {
return nil, nil, nil, err
}
nsSelectors[srcSelector.key] = srcSelector
}

// If no port is specified, match any port. Let iptables executable handle
// service name resolution
port := "0:65535"
if npp.Port != nil {
switch npp.Port.Type {
case intstr.Int:
port = fmt.Sprintf("%d", npp.Port.IntVal)
case intstr.String:
port = npp.Port.StrVal
if allPorts {
rule := newRuleSpec(nil, srcSelector, dstSelector, nil)
rules[rule.key] = rule
} else {
withNormalisedProtoAndPort(ingressRule.Ports, func(proto, port string) {
rule := newRuleSpec(&proto, srcSelector, dstSelector, &port)
rules[rule.key] = rule
})
}
}
}
}

f(proto, port)
return rules, nsSelectors, podSelectors, nil
}

func withNormalisedProtoAndPortLegacy(npps []extnapi.NetworkPolicyPort, f func(proto, port string)) {
for _, npp := range npps {
f(proto(npp.Protocol), port(npp.Port))
}
}

func withNormalisedProtoAndPort(npps []networkingv1.NetworkPolicyPort, f func(proto, port string)) {
for _, npp := range npps {
f(proto(npp.Protocol), port(npp.Port))
}
}

func proto(p *apiv1.Protocol) string {
// If no proto is specified, default to TCP
proto := string(apiv1.ProtocolTCP)
if p != nil {
proto = string(*p)
}

return proto
}

func port(p *intstr.IntOrString) string {
// If no port is specified, match any port. Let iptables executable handle
// service name resolution
port := "0:65535"
if p != nil {
switch p.Type {
case intstr.Int:
port = fmt.Sprintf("%d", p.IntVal)
case intstr.String:
port = p.StrVal
}
}

return port
}
55 changes: 43 additions & 12 deletions npc/controller.go
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/pkg/errors"
coreapi "k8s.io/api/core/v1"
extnapi "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"

"github.com/weaveworks/weave/common"
"github.com/weaveworks/weave/npc/ipset"
Expand All @@ -21,9 +22,9 @@ type NetworkPolicyController interface {
UpdatePod(oldObj, newObj *coreapi.Pod) error
DeletePod(obj *coreapi.Pod) error

AddNetworkPolicy(obj *extnapi.NetworkPolicy) error
UpdateNetworkPolicy(oldObj, newObj *extnapi.NetworkPolicy) error
DeleteNetworkPolicy(obj *extnapi.NetworkPolicy) error
AddNetworkPolicy(obj interface{}) error
UpdateNetworkPolicy(oldObj, newObj interface{}) error
DeleteNetworkPolicy(obj interface{}) error
}

type controller struct {
Expand All @@ -36,16 +37,20 @@ type controller struct {

nss map[string]*ns // ns name -> ns struct
nsSelectors *selectorSet // selector string -> nsSelector

legacy bool // use legacy network policy semantics (k8s pre-1.7)
}

func New(nodeName string, ipt iptables.Interface, ips ipset.Interface) NetworkPolicyController {
func New(nodeName string, legacy bool, ipt iptables.Interface, ips ipset.Interface) NetworkPolicyController {
c := &controller{
nodeName: nodeName,
legacy: legacy,
ipt: ipt,
ips: ips,
nss: make(map[string]*ns)}

c.nsSelectors = newSelectorSet(ips, c.onNewNsSelector)
doNothing := func(*selector) error { return nil }
c.nsSelectors = newSelectorSet(ips, c.onNewNsSelector, doNothing, doNothing)

return c
}
Expand All @@ -66,7 +71,7 @@ func (npc *controller) onNewNsSelector(selector *selector) error {
func (npc *controller) withNS(name string, f func(ns *ns) error) error {
ns, found := npc.nss[name]
if !found {
newNs, err := newNS(name, npc.nodeName, npc.ipt, npc.ips, npc.nsSelectors)
newNs, err := newNS(name, npc.nodeName, npc.legacy, npc.ipt, npc.ips, npc.nsSelectors)
if err != nil {
return err
}
Expand Down Expand Up @@ -115,32 +120,47 @@ func (npc *controller) DeletePod(obj *coreapi.Pod) error {
})
}

func (npc *controller) AddNetworkPolicy(obj *extnapi.NetworkPolicy) error {
func (npc *controller) AddNetworkPolicy(obj interface{}) error {
npc.Lock()
defer npc.Unlock()

nsName, err := nsName(obj)
if err != nil {
return err
}

common.Log.Infof("EVENT AddNetworkPolicy %s", js(obj))
return npc.withNS(obj.ObjectMeta.Namespace, func(ns *ns) error {
return npc.withNS(nsName, func(ns *ns) error {
return errors.Wrap(ns.addNetworkPolicy(obj), "add network policy")
})
}

func (npc *controller) UpdateNetworkPolicy(oldObj, newObj *extnapi.NetworkPolicy) error {
func (npc *controller) UpdateNetworkPolicy(oldObj, newObj interface{}) error {
npc.Lock()
defer npc.Unlock()

nsName, err := nsName(oldObj)
if err != nil {
return err
}

common.Log.Infof("EVENT UpdateNetworkPolicy %s %s", js(oldObj), js(newObj))
return npc.withNS(oldObj.ObjectMeta.Namespace, func(ns *ns) error {
return npc.withNS(nsName, func(ns *ns) error {
return errors.Wrap(ns.updateNetworkPolicy(oldObj, newObj), "update network policy")
})
}

func (npc *controller) DeleteNetworkPolicy(obj *extnapi.NetworkPolicy) error {
func (npc *controller) DeleteNetworkPolicy(obj interface{}) error {
npc.Lock()
defer npc.Unlock()

nsName, err := nsName(obj)
if err != nil {
return err
}

common.Log.Infof("EVENT DeleteNetworkPolicy %s", js(obj))
return npc.withNS(obj.ObjectMeta.Namespace, func(ns *ns) error {
return npc.withNS(nsName, func(ns *ns) error {
return errors.Wrap(ns.deleteNetworkPolicy(obj), "delete network policy")
})
}
Expand Down Expand Up @@ -174,3 +194,14 @@ func (npc *controller) DeleteNamespace(obj *coreapi.Namespace) error {
return errors.Wrap(ns.deleteNamespace(obj), "delete namespace")
})
}

func nsName(obj interface{}) (string, error) {
switch obj := obj.(type) {
case *networkingv1.NetworkPolicy:
return obj.ObjectMeta.Namespace, nil
case *extnapi.NetworkPolicy:
return obj.ObjectMeta.Namespace, nil
}

return "", errInvalidNetworkPolicyObjType
}

0 comments on commit 9679eaa

Please sign in to comment.