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
|
- Egress
|
||||||
ingress:
|
ingress:
|
||||||
- from:
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
project: default
|
||||||
ports:
|
ports:
|
||||||
- protocol: TCP
|
- protocol: TCP
|
||||||
port: 6379
|
port: 6379
|
||||||
egress:
|
egress:
|
||||||
- to:
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
project: default
|
||||||
ports:
|
ports:
|
||||||
- protocol: TCP
|
- protocol: TCP
|
||||||
port: 5978
|
port: 5978
|
||||||
|
@ -121,11 +127,17 @@ Testing the network policy support functionality
|
||||||
networkpolicy_spec:
|
networkpolicy_spec:
|
||||||
egress:
|
egress:
|
||||||
- to:
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
project: default
|
||||||
ports:
|
ports:
|
||||||
- port: 5978
|
- port: 5978
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
ingress:
|
ingress:
|
||||||
- from:
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
project: default
|
||||||
ports:
|
ports:
|
||||||
- port: 6379
|
- port: 6379
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
|
@ -223,10 +235,17 @@ Testing the network policy support functionality
|
||||||
- port: 5978
|
- port: 5978
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
to:
|
to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
project: default
|
||||||
ingress:
|
ingress:
|
||||||
- ports:
|
- ports:
|
||||||
- port: 8080
|
- port: 8080
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
|
from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
project: default
|
||||||
policyTypes:
|
policyTypes:
|
||||||
- Ingress
|
- Ingress
|
||||||
- Egress
|
- Egress
|
||||||
|
@ -244,6 +263,10 @@ Testing the network policy support functionality
|
||||||
$ curl 10.0.0.68:8080
|
$ curl 10.0.0.68:8080
|
||||||
demo-5558c7865d-fdkdv: HELLO! I AM ALIVE!!!
|
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::
|
10. Confirm the teardown of the resources once the network policy is removed::
|
||||||
|
|
||||||
$ kubectl delete -f network_policy.yml
|
$ kubectl delete -f network_policy.yml
|
||||||
|
|
|
@ -185,6 +185,33 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver):
|
||||||
LOG.exception('Error annotating network policy')
|
LOG.exception('Error annotating network policy')
|
||||||
raise
|
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):
|
def parse_network_policy_rules(self, policy, sg_id):
|
||||||
"""Create security group rule bodies out of network policies.
|
"""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)
|
ingress_sg_rule_body_list.append(i_rule)
|
||||||
for ingress_rule in ingress_rule_list:
|
for ingress_rule in ingress_rule_list:
|
||||||
LOG.debug('Parsing Ingress Rule %s', ingress_rule)
|
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:
|
if 'ports' in ingress_rule:
|
||||||
for port in ingress_rule['ports']:
|
for port in ingress_rule['ports']:
|
||||||
|
if allowed_cidrs:
|
||||||
|
for cidr in allowed_cidrs:
|
||||||
i_rule = self._create_security_group_rule_body(
|
i_rule = self._create_security_group_rule_body(
|
||||||
sg_id, 'ingress', port['port'],
|
sg_id, 'ingress', port.get('port'),
|
||||||
protocol=port['protocol'].lower())
|
protocol=port.get('protocol'),
|
||||||
|
cidr=cidr)
|
||||||
ingress_sg_rule_body_list.append(i_rule)
|
ingress_sg_rule_body_list.append(i_rule)
|
||||||
else:
|
else:
|
||||||
LOG.debug('This network policy specifies no ingress '
|
i_rule = self._create_security_group_rule_body(
|
||||||
'ports: %s', policy['metadata']['selfLink'])
|
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_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 from '
|
||||||
|
'and no ports: %s',
|
||||||
|
policy['metadata']['selfLink'])
|
||||||
|
|
||||||
if egress_rule_list:
|
if egress_rule_list:
|
||||||
if egress_rule_list[0] == {}:
|
if egress_rule_list[0] == {}:
|
||||||
|
@ -226,35 +276,66 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver):
|
||||||
egress_sg_rule_body_list.append(e_rule)
|
egress_sg_rule_body_list.append(e_rule)
|
||||||
for egress_rule in egress_rule_list:
|
for egress_rule in egress_rule_list:
|
||||||
LOG.debug('Parsing Egress Rule %s', egress_rule)
|
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:
|
if 'ports' in egress_rule:
|
||||||
for port in egress_rule['ports']:
|
for port in egress_rule['ports']:
|
||||||
|
if allowed_cidrs:
|
||||||
|
for cidr in allowed_cidrs:
|
||||||
e_rule = self._create_security_group_rule_body(
|
e_rule = self._create_security_group_rule_body(
|
||||||
sg_id, 'egress', port['port'],
|
sg_id, 'egress', port.get('port'),
|
||||||
protocol=port['protocol'].lower())
|
protocol=port.get('protocol'),
|
||||||
|
cidr=cidr)
|
||||||
egress_sg_rule_body_list.append(e_rule)
|
egress_sg_rule_body_list.append(e_rule)
|
||||||
else:
|
else:
|
||||||
LOG.debug('This network policy specifies no egress '
|
e_rule = self._create_security_group_rule_body(
|
||||||
'ports: %s', policy['metadata']['selfLink'])
|
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_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 egrees to '
|
||||||
|
'and no ports: %s',
|
||||||
|
policy['metadata']['selfLink'])
|
||||||
|
|
||||||
return ingress_sg_rule_body_list, egress_sg_rule_body_list
|
return ingress_sg_rule_body_list, egress_sg_rule_body_list
|
||||||
|
|
||||||
def _create_security_group_rule_body(
|
def _create_security_group_rule_body(
|
||||||
self, security_group_id, direction, port_range_min,
|
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"):
|
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
|
port_range_max = port_range_min
|
||||||
|
if not protocol:
|
||||||
|
protocol = 'TCP'
|
||||||
security_group_rule_body = {
|
security_group_rule_body = {
|
||||||
u'security_group_rule': {
|
u'security_group_rule': {
|
||||||
u'ethertype': ethertype,
|
u'ethertype': ethertype,
|
||||||
u'security_group_id': security_group_id,
|
u'security_group_id': security_group_id,
|
||||||
u'description': description,
|
u'description': description,
|
||||||
u'direction': direction,
|
u'direction': direction,
|
||||||
u'protocol': protocol,
|
u'protocol': protocol.lower(),
|
||||||
u'port_range_min': port_range_min,
|
u'port_range_min': port_range_min,
|
||||||
u'port_range_max': port_range_max
|
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)
|
LOG.debug("Creating sg rule body %s", security_group_rule_body)
|
||||||
return security_group_rule_body
|
return security_group_rule_body
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
|
from kuryr_kubernetes import constants
|
||||||
from kuryr_kubernetes.controller.drivers import network_policy
|
from kuryr_kubernetes.controller.drivers import network_policy
|
||||||
from kuryr_kubernetes import exceptions
|
from kuryr_kubernetes import exceptions
|
||||||
from kuryr_kubernetes.tests import base as test_base
|
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
|
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):
|
class TestNetworkPolicyDriver(test_base.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -49,9 +78,17 @@ class TestNetworkPolicyDriver(test_base.TestCase):
|
||||||
},
|
},
|
||||||
'spec': {
|
'spec': {
|
||||||
'egress': [{'ports':
|
'egress': [{'ports':
|
||||||
[{'port': 5978, 'protocol': 'TCP'}]}],
|
[{'port': 5978, 'protocol': 'TCP'}],
|
||||||
|
'to':
|
||||||
|
[{'namespaceSelector': {
|
||||||
|
'matchLabels': {
|
||||||
|
'project': 'myproject'}}}]}],
|
||||||
'ingress': [{'ports':
|
'ingress': [{'ports':
|
||||||
[{'port': 6379, 'protocol': 'TCP'}]}],
|
[{'port': 6379, 'protocol': 'TCP'}],
|
||||||
|
'from':
|
||||||
|
[{'namespaceSelector': {
|
||||||
|
'matchLabels': {
|
||||||
|
'project': 'myproject'}}}]}],
|
||||||
'policyTypes': ['Ingress', 'Egress']
|
'policyTypes': ['Ingress', 'Egress']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,28 +279,107 @@ class TestNetworkPolicyDriver(test_base.TestCase):
|
||||||
self._policy)
|
self._policy)
|
||||||
m_parse.assert_called_with(self._policy, self._sg_id)
|
m_parse.assert_called_with(self._policy, self._sg_id)
|
||||||
|
|
||||||
def test_parse_network_policy_rules(self):
|
def test_get_namespaces_cidr(self):
|
||||||
i_rule, e_rule = (
|
namespace_selector = {'matchLabels': {'test': 'test'}}
|
||||||
self._driver.parse_network_policy_rules(self._policy, self._sg_id))
|
pod = get_pod_obj()
|
||||||
self.assertEqual(
|
annotation = mock.sentinel.annotation
|
||||||
self._policy['spec']['ingress'][0]['ports'][0]['port'],
|
subnet_cidr = mock.sentinel.subnet_cidr
|
||||||
i_rule[0]['security_group_rule']['port_range_min'])
|
net_crd = {'spec': {'subnetCIDR': subnet_cidr}}
|
||||||
self.assertEqual(
|
pod['metadata']['annotations'][constants.K8S_ANNOTATION_NET_CRD] = (
|
||||||
self._policy['spec']['egress'][0]['ports'][0]['port'],
|
annotation)
|
||||||
e_rule[0]['security_group_rule']['port_range_min'])
|
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,
|
@mock.patch.object(network_policy.NetworkPolicyDriver,
|
||||||
'_create_security_group_rule_body')
|
'_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)
|
self._driver.parse_network_policy_rules(self._policy, self._sg_id)
|
||||||
m_create.assert_called()
|
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,
|
@mock.patch.object(network_policy.NetworkPolicyDriver,
|
||||||
'_create_security_group_rule_body')
|
'_create_security_group_rule_body')
|
||||||
def test_parse_network_policy_rules_with_no_rules(self, m_create):
|
def test_parse_network_policy_rules_with_no_rules(self, m_create,
|
||||||
self._policy['spec'] = {}
|
m_get_ns_cidr):
|
||||||
self._driver.parse_network_policy_rules(self._policy, self._sg_id)
|
policy = self._policy.copy()
|
||||||
m_create.assert_not_called()
|
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):
|
def test_knps_on_namespace(self):
|
||||||
self.kubernetes.get.return_value = {'items': ['not-empty']}
|
self.kubernetes.get.return_value = {'items': ['not-empty']}
|
||||||
|
|
Loading…
Reference in New Issue