Add namespaceSelector support for NetworkPolicies
This patch adds namespaceSelector support for ingress and egress Network Policies. In addition it handles the case where either no from/to or not ports section appears on the ingress or egress block Partially Implements: blueprint k8s-network-policies Change-Id: I7bfb1275221b76ad811ac6baff99e642d31f7e0a
This commit is contained in:
parent
4705b69d1a
commit
543b8a2e05
|
@ -50,11 +50,17 @@ Testing the network policy support functionality
|
|||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
project: default
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 6379
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
project: default
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5978
|
||||
|
@ -121,11 +127,17 @@ Testing the network policy support functionality
|
|||
networkpolicy_spec:
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
project: default
|
||||
ports:
|
||||
- port: 5978
|
||||
protocol: TCP
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
project: default
|
||||
ports:
|
||||
- port: 6379
|
||||
protocol: TCP
|
||||
|
@ -223,10 +235,17 @@ Testing the network policy support functionality
|
|||
- port: 5978
|
||||
protocol: TCP
|
||||
to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
project: default
|
||||
ingress:
|
||||
- ports:
|
||||
- port: 8080
|
||||
protocol: TCP
|
||||
from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
project: default
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
|
@ -244,6 +263,10 @@ Testing the network policy support functionality
|
|||
$ curl 10.0.0.68:8080
|
||||
demo-5558c7865d-fdkdv: HELLO! I AM ALIVE!!!
|
||||
|
||||
|
||||
Note the ping will only work from pods (neutron ports) on a namespace that has
|
||||
the label 'project: default' as stated on the policy namespaceSelector.
|
||||
|
||||
10. Confirm the teardown of the resources once the network policy is removed::
|
||||
|
||||
$ kubectl delete -f network_policy.yml
|
||||
|
|
|
@ -185,6 +185,33 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver):
|
|||
LOG.exception('Error annotating network policy')
|
||||
raise
|
||||
|
||||
def _get_namespaces_cidr(self, namespace_selector):
|
||||
cidrs = []
|
||||
namespace_label = urlencode(namespace_selector[
|
||||
'matchLabels'])
|
||||
matching_namespaces = self.kubernetes.get(
|
||||
'{}/namespaces?labelSelector={}'.format(
|
||||
constants.K8S_API_BASE, namespace_label)).get('items')
|
||||
for ns in matching_namespaces:
|
||||
# NOTE(ltomasbo): This requires the namespace handler to be
|
||||
# also enabled
|
||||
try:
|
||||
ns_annotations = ns['metadata']['annotations']
|
||||
ns_name = ns_annotations[constants.K8S_ANNOTATION_NET_CRD]
|
||||
except KeyError:
|
||||
LOG.exception('Namespace handler must be enabled to support '
|
||||
'Network Policies with namespaceSelector')
|
||||
raise
|
||||
try:
|
||||
net_crd = self.kubernetes.get('{}/kuryrnets/{}'.format(
|
||||
constants.K8S_API_CRD, ns_name))
|
||||
except exceptions.K8sClientException:
|
||||
LOG.exception("Kubernetes Client Exception.")
|
||||
raise
|
||||
ns_cidr = net_crd['spec']['subnetCIDR']
|
||||
cidrs.append(ns_cidr)
|
||||
return cidrs
|
||||
|
||||
def parse_network_policy_rules(self, policy, sg_id):
|
||||
"""Create security group rule bodies out of network policies.
|
||||
|
||||
|
@ -207,15 +234,38 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver):
|
|||
ingress_sg_rule_body_list.append(i_rule)
|
||||
for ingress_rule in ingress_rule_list:
|
||||
LOG.debug('Parsing Ingress Rule %s', ingress_rule)
|
||||
allowed_cidrs = []
|
||||
for from_rule in ingress_rule.get('from', []):
|
||||
namespace_selector = from_rule.get('namespaceSelector')
|
||||
if namespace_selector:
|
||||
allowed_cidrs = self._get_namespaces_cidr(
|
||||
namespace_selector)
|
||||
if 'ports' in ingress_rule:
|
||||
for port in ingress_rule['ports']:
|
||||
if allowed_cidrs:
|
||||
for cidr in allowed_cidrs:
|
||||
i_rule = self._create_security_group_rule_body(
|
||||
sg_id, 'ingress', port.get('port'),
|
||||
protocol=port.get('protocol'),
|
||||
cidr=cidr)
|
||||
ingress_sg_rule_body_list.append(i_rule)
|
||||
else:
|
||||
i_rule = self._create_security_group_rule_body(
|
||||
sg_id, 'ingress', port.get('port'),
|
||||
protocol=port.get('protocol'))
|
||||
ingress_sg_rule_body_list.append(i_rule)
|
||||
elif allowed_cidrs:
|
||||
for cidr in allowed_cidrs:
|
||||
i_rule = self._create_security_group_rule_body(
|
||||
sg_id, 'ingress', port['port'],
|
||||
protocol=port['protocol'].lower())
|
||||
sg_id, 'ingress',
|
||||
port_range_min=1,
|
||||
port_range_max=65535,
|
||||
cidr=cidr)
|
||||
ingress_sg_rule_body_list.append(i_rule)
|
||||
else:
|
||||
LOG.debug('This network policy specifies no ingress '
|
||||
'ports: %s', policy['metadata']['selfLink'])
|
||||
LOG.debug('This network policy specifies no ingress from '
|
||||
'and no ports: %s',
|
||||
policy['metadata']['selfLink'])
|
||||
|
||||
if egress_rule_list:
|
||||
if egress_rule_list[0] == {}:
|
||||
|
@ -226,35 +276,66 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver):
|
|||
egress_sg_rule_body_list.append(e_rule)
|
||||
for egress_rule in egress_rule_list:
|
||||
LOG.debug('Parsing Egress Rule %s', egress_rule)
|
||||
allowed_cidrs = []
|
||||
for from_rule in egress_rule.get('to', []):
|
||||
namespace_selector = from_rule.get('namespaceSelector')
|
||||
if namespace_selector:
|
||||
allowed_cidrs = self._get_namespaces_cidr(
|
||||
namespace_selector)
|
||||
if 'ports' in egress_rule:
|
||||
for port in egress_rule['ports']:
|
||||
if allowed_cidrs:
|
||||
for cidr in allowed_cidrs:
|
||||
e_rule = self._create_security_group_rule_body(
|
||||
sg_id, 'egress', port.get('port'),
|
||||
protocol=port.get('protocol'),
|
||||
cidr=cidr)
|
||||
egress_sg_rule_body_list.append(e_rule)
|
||||
else:
|
||||
e_rule = self._create_security_group_rule_body(
|
||||
sg_id, 'egress', port.get('port'),
|
||||
protocol=port.get('protocol'))
|
||||
egress_sg_rule_body_list.append(e_rule)
|
||||
elif allowed_cidrs:
|
||||
for cidr in allowed_cidrs:
|
||||
e_rule = self._create_security_group_rule_body(
|
||||
sg_id, 'egress', port['port'],
|
||||
protocol=port['protocol'].lower())
|
||||
sg_id, 'egress',
|
||||
port_range_min=1,
|
||||
port_range_max=65535,
|
||||
cidr=cidr)
|
||||
egress_sg_rule_body_list.append(e_rule)
|
||||
else:
|
||||
LOG.debug('This network policy specifies no egress '
|
||||
'ports: %s', policy['metadata']['selfLink'])
|
||||
LOG.debug('This network policy specifies no egrees to '
|
||||
'and no ports: %s',
|
||||
policy['metadata']['selfLink'])
|
||||
|
||||
return ingress_sg_rule_body_list, egress_sg_rule_body_list
|
||||
|
||||
def _create_security_group_rule_body(
|
||||
self, security_group_id, direction, port_range_min,
|
||||
port_range_max=None, protocol='TCP', ethertype='IPv4',
|
||||
port_range_max=None, protocol=None, ethertype='IPv4', cidr=None,
|
||||
description="Kuryr-Kubernetes NetPolicy SG rule"):
|
||||
if not port_range_max:
|
||||
if not port_range_min:
|
||||
port_range_min = 1
|
||||
port_range_max = 65535
|
||||
elif not port_range_max:
|
||||
port_range_max = port_range_min
|
||||
if not protocol:
|
||||
protocol = 'TCP'
|
||||
security_group_rule_body = {
|
||||
u'security_group_rule': {
|
||||
u'ethertype': ethertype,
|
||||
u'security_group_id': security_group_id,
|
||||
u'description': description,
|
||||
u'direction': direction,
|
||||
u'protocol': protocol,
|
||||
u'protocol': protocol.lower(),
|
||||
u'port_range_min': port_range_min,
|
||||
u'port_range_max': port_range_max
|
||||
}
|
||||
}
|
||||
if cidr:
|
||||
security_group_rule_body[u'security_group_rule'][
|
||||
u'remote_ip_prefix'] = cidr
|
||||
LOG.debug("Creating sg rule body %s", security_group_rule_body)
|
||||
return security_group_rule_body
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
import mock
|
||||
|
||||
from kuryr_kubernetes import constants
|
||||
from kuryr_kubernetes.controller.drivers import network_policy
|
||||
from kuryr_kubernetes import exceptions
|
||||
from kuryr_kubernetes.tests import base as test_base
|
||||
|
@ -22,6 +23,34 @@ from kuryr_kubernetes.tests.unit import kuryr_fixtures as k_fix
|
|||
from neutronclient.common import exceptions as n_exc
|
||||
|
||||
|
||||
def get_pod_obj():
|
||||
return {
|
||||
'status': {
|
||||
'qosClass': 'BestEffort',
|
||||
'hostIP': '192.168.1.2',
|
||||
},
|
||||
'kind': 'Pod',
|
||||
'spec': {
|
||||
'schedulerName': 'default-scheduler',
|
||||
'containers': [{
|
||||
'name': 'busybox',
|
||||
'image': 'busybox',
|
||||
'resources': {}
|
||||
}],
|
||||
'nodeName': 'kuryr-devstack'
|
||||
},
|
||||
'metadata': {
|
||||
'name': 'busybox-sleep1',
|
||||
'namespace': 'default',
|
||||
'resourceVersion': '53808',
|
||||
'selfLink': '/api/v1/namespaces/default/pods/busybox-sleep1',
|
||||
'uid': '452176db-4a85-11e7-80bd-fa163e29dbbb',
|
||||
'annotations': {
|
||||
'openstack.org/kuryr-vif': {}
|
||||
}
|
||||
}}
|
||||
|
||||
|
||||
class TestNetworkPolicyDriver(test_base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -49,9 +78,17 @@ class TestNetworkPolicyDriver(test_base.TestCase):
|
|||
},
|
||||
'spec': {
|
||||
'egress': [{'ports':
|
||||
[{'port': 5978, 'protocol': 'TCP'}]}],
|
||||
[{'port': 5978, 'protocol': 'TCP'}],
|
||||
'to':
|
||||
[{'namespaceSelector': {
|
||||
'matchLabels': {
|
||||
'project': 'myproject'}}}]}],
|
||||
'ingress': [{'ports':
|
||||
[{'port': 6379, 'protocol': 'TCP'}]}],
|
||||
[{'port': 6379, 'protocol': 'TCP'}],
|
||||
'from':
|
||||
[{'namespaceSelector': {
|
||||
'matchLabels': {
|
||||
'project': 'myproject'}}}]}],
|
||||
'policyTypes': ['Ingress', 'Egress']
|
||||
}
|
||||
}
|
||||
|
@ -242,28 +279,107 @@ class TestNetworkPolicyDriver(test_base.TestCase):
|
|||
self._policy)
|
||||
m_parse.assert_called_with(self._policy, self._sg_id)
|
||||
|
||||
def test_parse_network_policy_rules(self):
|
||||
i_rule, e_rule = (
|
||||
self._driver.parse_network_policy_rules(self._policy, self._sg_id))
|
||||
self.assertEqual(
|
||||
self._policy['spec']['ingress'][0]['ports'][0]['port'],
|
||||
i_rule[0]['security_group_rule']['port_range_min'])
|
||||
self.assertEqual(
|
||||
self._policy['spec']['egress'][0]['ports'][0]['port'],
|
||||
e_rule[0]['security_group_rule']['port_range_min'])
|
||||
def test_get_namespaces_cidr(self):
|
||||
namespace_selector = {'matchLabels': {'test': 'test'}}
|
||||
pod = get_pod_obj()
|
||||
annotation = mock.sentinel.annotation
|
||||
subnet_cidr = mock.sentinel.subnet_cidr
|
||||
net_crd = {'spec': {'subnetCIDR': subnet_cidr}}
|
||||
pod['metadata']['annotations'][constants.K8S_ANNOTATION_NET_CRD] = (
|
||||
annotation)
|
||||
self.kubernetes.get.side_effect = [{'items': [pod]}, net_crd]
|
||||
|
||||
resp = self._driver._get_namespaces_cidr(namespace_selector)
|
||||
self.assertEqual([subnet_cidr], resp)
|
||||
self.kubernetes.get.assert_called()
|
||||
|
||||
def test_get_namespaces_cidr_no_matches(self):
|
||||
namespace_selector = {'matchLabels': {'test': 'test'}}
|
||||
self.kubernetes.get.return_value = {'items': []}
|
||||
|
||||
resp = self._driver._get_namespaces_cidr(namespace_selector)
|
||||
self.assertEqual([], resp)
|
||||
self.kubernetes.get.assert_called_once()
|
||||
|
||||
def test_get_namespaces_cidr_no_annotations(self):
|
||||
namespace_selector = {'matchLabels': {'test': 'test'}}
|
||||
pod = get_pod_obj()
|
||||
self.kubernetes.get.return_value = {'items': [pod]}
|
||||
|
||||
self.assertRaises(KeyError, self._driver._get_namespaces_cidr,
|
||||
namespace_selector)
|
||||
self.kubernetes.get.assert_called_once()
|
||||
|
||||
@mock.patch.object(network_policy.NetworkPolicyDriver,
|
||||
'_get_namespaces_cidr')
|
||||
@mock.patch.object(network_policy.NetworkPolicyDriver,
|
||||
'_create_security_group_rule_body')
|
||||
def test_parse_network_policy_rules_with_rules(self, m_create):
|
||||
def test_parse_network_policy_rules_with_rules(self, m_create,
|
||||
m_get_ns_cidr):
|
||||
subnet_cidr = '10.10.0.0/24'
|
||||
m_get_ns_cidr.return_value = [subnet_cidr]
|
||||
self._driver.parse_network_policy_rules(self._policy, self._sg_id)
|
||||
m_create.assert_called()
|
||||
m_get_ns_cidr.assert_called()
|
||||
|
||||
@mock.patch.object(network_policy.NetworkPolicyDriver,
|
||||
'_get_namespaces_cidr')
|
||||
@mock.patch.object(network_policy.NetworkPolicyDriver,
|
||||
'_create_security_group_rule_body')
|
||||
def test_parse_network_policy_rules_with_no_rules(self, m_create):
|
||||
self._policy['spec'] = {}
|
||||
self._driver.parse_network_policy_rules(self._policy, self._sg_id)
|
||||
m_create.assert_not_called()
|
||||
def test_parse_network_policy_rules_with_no_rules(self, m_create,
|
||||
m_get_ns_cidr):
|
||||
policy = self._policy.copy()
|
||||
policy['spec']['ingress'] = [{}]
|
||||
policy['spec']['egress'] = [{}]
|
||||
self._driver.parse_network_policy_rules(policy, self._sg_id)
|
||||
m_get_ns_cidr.assert_not_called()
|
||||
calls = [mock.call(self._sg_id, 'ingress', port_range_min=1,
|
||||
port_range_max=65535),
|
||||
mock.call(self._sg_id, 'egress', port_range_min=1,
|
||||
port_range_max=65535)]
|
||||
m_create.assert_has_calls(calls)
|
||||
|
||||
@mock.patch.object(network_policy.NetworkPolicyDriver,
|
||||
'_get_namespaces_cidr')
|
||||
@mock.patch.object(network_policy.NetworkPolicyDriver,
|
||||
'_create_security_group_rule_body')
|
||||
def test_parse_network_policy_rules_with_no_pod_selector(self, m_create,
|
||||
m_get_ns_cidr):
|
||||
policy = self._policy.copy()
|
||||
policy['spec']['ingress'] = [{'ports':
|
||||
[{'port': 6379, 'protocol': 'TCP'}]}]
|
||||
policy['spec']['egress'] = [{'ports':
|
||||
[{'port': 6379, 'protocol': 'TCP'}]}]
|
||||
self._driver.parse_network_policy_rules(policy, self._sg_id)
|
||||
m_create.assert_called()
|
||||
m_get_ns_cidr.assert_not_called()
|
||||
|
||||
@mock.patch.object(network_policy.NetworkPolicyDriver,
|
||||
'_get_namespaces_cidr')
|
||||
@mock.patch.object(network_policy.NetworkPolicyDriver,
|
||||
'_create_security_group_rule_body')
|
||||
def test_parse_network_policy_rules_with_no_ports(self, m_create,
|
||||
m_get_ns_cidr):
|
||||
subnet_cidr = '10.10.0.0/24'
|
||||
m_get_ns_cidr.return_value = [subnet_cidr]
|
||||
policy = self._policy.copy()
|
||||
policy['spec']['egress'] = [
|
||||
{'to':
|
||||
[{'namespaceSelector': {
|
||||
'matchLabels': {
|
||||
'project': 'myproject'}}}]}]
|
||||
policy['spec']['ingress'] = [
|
||||
{'from':
|
||||
[{'namespaceSelector': {
|
||||
'matchLabels': {
|
||||
'project': 'myproject'}}}]}]
|
||||
self._driver.parse_network_policy_rules(policy, self._sg_id)
|
||||
m_get_ns_cidr.assert_called()
|
||||
calls = [mock.call(self._sg_id, 'ingress', port_range_min=1,
|
||||
port_range_max=65535, cidr=subnet_cidr),
|
||||
mock.call(self._sg_id, 'egress', port_range_min=1,
|
||||
port_range_max=65535, cidr=subnet_cidr)]
|
||||
m_create.assert_has_calls(calls)
|
||||
|
||||
def test_knps_on_namespace(self):
|
||||
self.kubernetes.get.return_value = {'items': ['not-empty']}
|
||||
|
|
Loading…
Reference in New Issue