Enable IPv6 in network policy driver.

In network policy driver we are using security groups for the OpenStack
side to create appropriate port ranges to be open for certain hosts (or
all hosts). In this patch we add a mechanism for selecting right IP
version to the rule, or create rules for both (IPv4 and IPv6) network
types.

Implements: blueprint kuryr-ipv6-support
Change-Id: Ie7544aeebb1d18038ebc19c8f815b69213b55a88
This commit is contained in:
Roman Dobosz 2020-02-28 08:29:08 +01:00
parent 49ff055988
commit 58e3ca2829
5 changed files with 424 additions and 54 deletions

View File

@ -138,9 +138,12 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver):
if worker_subnet_id:
default_cidrs.append(utils.get_subnet_cidr(worker_subnet_id))
for cidr in default_cidrs:
ethertype = constants.IPv4
if ipaddress.ip_network(cidr).version == constants.IP_VERSION_6:
ethertype = constants.IPv6
default_rule = {
'security_group_rule': {
'ethertype': 'IPv4',
'ethertype': ethertype,
'security_group_id': sg_id,
'direction': 'ingress',
'description': 'Kuryr-Kubernetes NetPolicy SG rule',
@ -365,12 +368,15 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver):
crd_rules, sg_id, direction, port, pod_selector,
policy_namespace)
if allow_all:
container_port = None
for container_port, pods in matched_pods.items():
sg_rule = driver_utils.create_security_group_rule_body(
sg_id, direction, container_port,
protocol=port.get('protocol'),
pods=pods)
crd_rules.append(sg_rule)
for ethertype in (constants.IPv4, constants.IPv6):
sg_rule = driver_utils.create_security_group_rule_body(
sg_id, direction, container_port,
protocol=port.get('protocol'),
ethertype=ethertype,
pods=pods)
crd_rules.append(sg_rule)
if direction == 'egress':
rules = self._create_svc_egress_sg_rule(
sg_id, policy_namespace, port=container_port,
@ -410,26 +416,29 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver):
sg_rule_body_list, pod_selector, policy_namespace,
allow_all=True)
else:
sg_rule = (
driver_utils.create_security_group_rule_body(
sg_id, direction, port.get('port'),
protocol=port.get('protocol')))
sg_rule_body_list.append(sg_rule)
if direction == 'egress':
rule = self._create_svc_egress_sg_rule(
sg_id, policy_namespace, port=port.get('port'),
protocol=port.get('protocol'))
sg_rule_body_list.extend(rule)
for ethertype in (constants.IPv4, constants.IPv6):
sg_rule = (
driver_utils.create_security_group_rule_body(
sg_id, direction, port.get('port'),
ethertype=ethertype,
protocol=port.get('protocol')))
sg_rule_body_list.append(sg_rule)
if direction == 'egress':
rule = self._create_svc_egress_sg_rule(
sg_id, policy_namespace, port=port.get('port'),
protocol=port.get('protocol'))
sg_rule_body_list.extend(rule)
def _create_default_sg_rule(self, sg_id, direction, sg_rule_body_list):
default_rule = {
'security_group_rule': {
'ethertype': 'IPv4',
'security_group_id': sg_id,
'direction': direction,
'description': 'Kuryr-Kubernetes NetPolicy SG rule',
}}
sg_rule_body_list.append(default_rule)
for ethertype in (constants.IPv4, constants.IPv6):
default_rule = {
'security_group_rule': {
'ethertype': ethertype,
'security_group_id': sg_id,
'direction': direction,
'description': 'Kuryr-Kubernetes NetPolicy SG rule',
}}
sg_rule_body_list.append(default_rule)
def _parse_sg_rules(self, sg_rule_body_list, direction, policy, sg_id):
"""Parse policy into security group rules.
@ -478,9 +487,10 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver):
if rule_list[0] == {}:
LOG.debug('Applying default all open policy from %s',
policy['metadata']['selfLink'])
rule = driver_utils.create_security_group_rule_body(sg_id,
direction)
sg_rule_body_list.append(rule)
for ethertype in (constants.IPv4, constants.IPv6):
rule = driver_utils.create_security_group_rule_body(
sg_id, direction, ethertype=ethertype)
sg_rule_body_list.append(rule)
for rule_block in rule_list:
LOG.debug('Parsing %(dir)s Rule %(rule)s', {'dir': direction,
@ -546,15 +556,17 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver):
sg_id, policy_namespace, resource=resource)
sg_rule_body_list.extend(rule)
if allow_all:
rule = driver_utils.create_security_group_rule_body(
sg_id, direction,
port_range_min=1,
port_range_max=65535)
if direction == 'egress':
rule = self._create_svc_egress_sg_rule(
sg_id, policy_namespace)
sg_rule_body_list.extend(rule)
sg_rule_body_list.append(rule)
for ethertype in (constants.IPv4, constants.IPv6):
rule = driver_utils.create_security_group_rule_body(
sg_id, direction,
port_range_min=1,
port_range_max=65535,
ethertype=ethertype)
sg_rule_body_list.append(rule)
if direction == 'egress':
rule = self._create_svc_egress_sg_rule(
sg_id, policy_namespace)
sg_rule_body_list.extend(rule)
else:
LOG.debug('This network policy specifies no %(direction)s '
'%(rule_direction)s and no ports: %(policy)s',

View File

@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_config import cfg
from oslo_log import log as logging
from kuryr_kubernetes import clients
from kuryr_kubernetes import config
@ -20,9 +22,6 @@ from kuryr_kubernetes.controller.drivers import base
from kuryr_kubernetes.controller.drivers import utils as driver_utils
from kuryr_kubernetes import exceptions
from oslo_config import cfg
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
@ -172,12 +171,27 @@ def _create_sg_rule_on_text_port(sg_id, direction, port, rule_selected_pods,
matched_pods, container_ports, allow_all,
namespace, matched, crd_rules, sg_id, direction,
port, rule_selected_pod)
_apply_sg_rules_on_matched_pods(matched_pods, sg_id, direction, namespace,
port, crd_rules, allow_all)
return matched
def _apply_sg_rules_on_matched_pods(matched_pods, sg_id, direction, namespace,
port, crd_rules, allow_all=False):
for container_port, pods in matched_pods.items():
if allow_all:
sg_rule = driver_utils.create_security_group_rule_body(
sg_id, direction, container_port,
protocol=port.get('protocol'),
pods=pods)
for ethertype in (constants.IPv4, constants.IPv6):
sg_rule = driver_utils.create_security_group_rule_body(
sg_id, direction, container_port,
protocol=port.get('protocol'),
ethertype=ethertype,
pods=pods)
sgr_id = driver_utils.create_security_group_rule(sg_rule)
sg_rule['security_group_rule']['id'] = sgr_id
if sg_rule not in crd_rules:
crd_rules.append(sg_rule)
else:
namespace_obj = driver_utils.get_namespace(namespace)
if not namespace_obj:
@ -190,11 +204,10 @@ def _create_sg_rule_on_text_port(sg_id, direction, port, rule_selected_pods,
sg_id, direction, container_port,
protocol=port.get('protocol'), cidr=namespace_cidr,
pods=pods)
sgr_id = driver_utils.create_security_group_rule(sg_rule)
sg_rule['security_group_rule']['id'] = sgr_id
if sg_rule not in crd_rules:
crd_rules.append(sg_rule)
return matched
sgr_id = driver_utils.create_security_group_rule(sg_rule)
sg_rule['security_group_rule']['id'] = sgr_id
if sg_rule not in crd_rules:
crd_rules.append(sg_rule)
def _create_sg_rules(crd, pod, pod_selector, rule_block,

View File

@ -15,6 +15,7 @@
import urllib
import netaddr
from openstack import exceptions as os_exc
from oslo_config import cfg
from oslo_log import log
@ -233,7 +234,7 @@ def patch_kuryrnetworkpolicy_crd(crd, i_rules, e_rules, pod_selector,
def create_security_group_rule_body(
security_group_id, direction, port_range_min=None,
port_range_max=None, protocol=None, ethertype='IPv4', cidr=None,
port_range_max=None, protocol=None, ethertype=None, cidr=None,
description="Kuryr-Kubernetes NetPolicy SG rule", namespace=None,
pods=None):
if not port_range_min:
@ -243,6 +244,12 @@ def create_security_group_rule_body(
port_range_max = port_range_min
if not protocol:
protocol = 'TCP'
if not ethertype:
ethertype = 'IPv4'
if cidr and netaddr.IPNetwork(cidr).version == 6:
ethertype = 'IPv6'
security_group_rule_body = {
'security_group_rule': {
'ethertype': ethertype,

View File

@ -240,7 +240,7 @@ class TestNetworkPolicyDriver(test_base.TestCase):
self._driver.os_net.create_security_group.return_value = (
munch.Munch({'id': mock.sentinel.id,
'security_group_rules': []}))
m_utils.get_subnet_cidr.return_value = {'cidr': mock.sentinel.cidr}
m_utils.get_subnet_cidr.return_value = mock.sentinel.cidr
m_parse.return_value = (self._i_rules, self._e_rules)
self._driver.os_net.create_security_group_rule.return_value = (
munch.Munch({'id': mock.sentinel.id}))
@ -265,7 +265,7 @@ class TestNetworkPolicyDriver(test_base.TestCase):
self._driver.os_net.create_security_group.return_value = (
munch.Munch({'id': mock.sentinel.id,
'security_group_rules': []}))
m_utils.get_subnet_cidr.return_value = {'cidr': mock.sentinel.cidr}
m_utils.get_subnet_cidr.return_value = mock.sentinel.cidr
m_parse.return_value = (self._i_rules, self._e_rules)
m_get_crd.side_effect = exceptions.K8sClientException
self._driver.os_net.create_security_group_rule.return_value = (
@ -292,7 +292,7 @@ class TestNetworkPolicyDriver(test_base.TestCase):
self._driver.os_net.create_security_group.return_value = (
munch.Munch({'id': mock.sentinel.id,
'security_group_rules': []}))
m_utils.get_subnet_cidr.return_value = {'cidr': mock.sentinel.cidr}
m_utils.get_subnet_cidr.return_value = mock.sentinel.cidr
m_parse.return_value = (self._i_rules, self._e_rules)
m_add_crd.side_effect = exceptions.K8sClientException
self._driver.os_net.create_security_group_rule.return_value = (
@ -393,8 +393,10 @@ class TestNetworkPolicyDriver(test_base.TestCase):
policy['spec']['egress'] = [{}]
self._driver.parse_network_policy_rules(policy, self._sg_id)
m_get_ns.assert_not_called()
calls = [mock.call(self._sg_id, 'ingress'),
mock.call(self._sg_id, 'egress')]
calls = [mock.call(self._sg_id, 'ingress', ethertype='IPv4'),
mock.call(self._sg_id, 'ingress', ethertype='IPv6'),
mock.call(self._sg_id, 'egress', ethertype='IPv4'),
mock.call(self._sg_id, 'egress', ethertype='IPv6')]
m_create.assert_has_calls(calls)
@mock.patch.object(network_policy.NetworkPolicyDriver,
@ -520,3 +522,248 @@ class TestNetworkPolicyDriver(test_base.TestCase):
def test_release_network_policy_removed_crd(self, m_del_crd):
self._driver.release_network_policy(None)
m_del_crd.assert_not_called()
@mock.patch.object(network_policy.NetworkPolicyDriver,
'_create_sg_rules_with_container_ports')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.get_ports')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.get_pods')
def test__create_sg_rule_body_on_text_port_ingress(self,
m_get_pods,
m_get_ports,
m_create_sgr_cont):
pod = mock.sentinel.pod
port = mock.sentinel.port
container_ports = mock.sentinel.ports
resources = [mock.sentinel.resource]
crd_rules = mock.sentinel.crd_rules
pod_selector = {}
namespace = mock.sentinel.namespace
direction = 'ingress'
m_get_pods.return_value = {'items': [pod]}
m_get_ports.return_value = container_ports
self._driver._create_sg_rule_body_on_text_port(self._sg_id,
direction,
port,
resources,
crd_rules,
pod_selector,
namespace)
m_get_pods.assert_called_with(pod_selector, namespace)
m_get_ports.assert_called_with(pod, port)
@mock.patch('kuryr_kubernetes.controller.drivers.utils.'
'create_security_group_rule_body')
@mock.patch.object(network_policy.NetworkPolicyDriver,
'_create_sg_rules_with_container_ports')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.get_ports')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.get_pods')
def test__create_sg_rule_body_on_text_port_ingress_all(self,
m_get_pods,
m_get_ports,
m_create_sgr_cont,
m_create_sgr):
pod = mock.sentinel.pod
port = mock.sentinel.port
container_ports = mock.sentinel.ports
resources = [mock.sentinel.resource]
crd_rules = mock.sentinel.crd_rules
pod_selector = {}
namespace = mock.sentinel.namespace
direction = 'ingress'
m_get_pods.return_value = {'items': [pod]}
m_get_ports.return_value = container_ports
self._driver._create_sg_rule_body_on_text_port(self._sg_id,
direction,
port,
resources,
crd_rules,
pod_selector,
namespace,
allow_all=True)
m_get_pods.assert_called_with(pod_selector, namespace)
m_get_ports.assert_called_with(pod, port)
m_create_sgr.assert_not_called()
@mock.patch('kuryr_kubernetes.controller.drivers.utils.'
'create_security_group_rule_body')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.get_ports')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.get_pods')
def test__create_sg_rule_body_on_text_port_ingress_match(self,
m_get_pods,
m_get_ports,
m_create_sgr):
def _create_sgr_cont(container_ports, allow_all, resource,
matched_pods, crd_rules, sg_id, direction, port,
pod_selector=None, policy_namespace=None):
matched_pods[container_ports[0][1]] = 'foo'
pod = mock.sentinel.pod
port = {'protocol': 'TCP', 'port': 22}
container_ports = [("pod", mock.sentinel.container_port)]
resources = [mock.sentinel.resource]
crd_rules = []
pod_selector = {}
namespace = mock.sentinel.namespace
direction = 'ingress'
self._driver._create_sg_rules_with_container_ports = _create_sgr_cont
m_get_pods.return_value = {'items': [pod]}
m_get_ports.return_value = container_ports
self._driver._create_sg_rule_body_on_text_port(self._sg_id,
direction,
port,
resources,
crd_rules,
pod_selector,
namespace,
allow_all=True)
m_get_pods.assert_called_with(pod_selector, namespace)
m_get_ports.assert_called_with(pod, port)
calls = [mock.call(self._sg_id, direction, container_ports[0][1],
protocol=port['protocol'], ethertype=e,
pods='foo') for e in ('IPv4', 'IPv6')]
m_create_sgr.assert_has_calls(calls)
self.assertEqual(len(crd_rules), 2)
@mock.patch.object(network_policy.NetworkPolicyDriver,
'_create_sg_rules_with_container_ports')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.get_ports')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.get_pods')
def test__create_sg_rule_body_on_text_port_egress(self,
m_get_pods,
m_get_ports,
m_create_sgr_cont):
pod = mock.sentinel.pod
port = mock.sentinel.port
container_ports = mock.sentinel.ports
resources = [{'spec': 'foo'}]
crd_rules = mock.sentinel.crd_rules
pod_selector = {}
namespace = mock.sentinel.namespace
direction = 'egress'
m_get_pods.return_value = {'items': [pod]}
m_get_ports.return_value = container_ports
self._driver._create_sg_rule_body_on_text_port(self._sg_id,
direction,
port,
resources,
crd_rules,
pod_selector,
namespace)
m_get_ports.assert_called_with(resources[0], port)
@mock.patch('kuryr_kubernetes.controller.drivers.utils.'
'create_security_group_rule_body')
@mock.patch.object(network_policy.NetworkPolicyDriver,
'_create_sg_rules_with_container_ports')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.get_ports')
def test__create_sg_rule_body_on_text_port_egress_all(self,
m_get_ports,
m_create_sgr_cont,
m_create_sgr):
port = {'protocol': 'TCP', 'port': 22}
container_ports = mock.sentinel.ports
resources = [{'spec': 'foo'}]
crd_rules = []
pod_selector = {}
namespace = mock.sentinel.namespace
direction = 'egress'
m_get_ports.return_value = container_ports
self._driver._create_sg_rule_body_on_text_port(self._sg_id,
direction,
port,
resources,
crd_rules,
pod_selector,
namespace,
allow_all=True)
m_get_ports.assert_called_with(resources[0], port)
m_create_sgr.assert_called_once_with(self._sg_id, 'egress', None,
cidr=mock.ANY, protocol='TCP')
self.assertEqual(len(crd_rules), 1)
@mock.patch('kuryr_kubernetes.controller.drivers.utils.'
'create_security_group_rule_body')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.get_ports')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.get_pods')
def test__create_sg_rule_body_on_text_port_egress_match(self,
m_get_pods,
m_get_ports,
m_create_sgr):
def _create_sgr_cont(container_ports, allow_all, resource,
matched_pods, crd_rules, sg_id, direction, port,
pod_selector=None, policy_namespace=None):
matched_pods[container_ports[0][1]] = 'foo'
pod = mock.sentinel.pod
port = {'protocol': 'TCP', 'port': 22}
container_ports = [("pod", mock.sentinel.container_port)]
resources = [{'spec': 'foo'}]
crd_rules = []
pod_selector = {}
namespace = mock.sentinel.namespace
direction = 'egress'
self._driver._create_sg_rules_with_container_ports = _create_sgr_cont
m_get_pods.return_value = {'items': [pod]}
m_get_ports.return_value = container_ports
self._driver._create_sg_rule_body_on_text_port(self._sg_id,
direction,
port,
resources,
crd_rules,
pod_selector,
namespace,
allow_all=True)
m_get_ports.assert_called_with(resources[0], port)
calls = [mock.call(self._sg_id, direction, container_ports[0][1],
protocol=port['protocol'], ethertype=e,
pods='foo') for e in ('IPv4', 'IPv6')]
m_create_sgr.assert_has_calls(calls)
# NOTE(gryf): there are 3 rules created in case of egress direction,
# since additional one is created for specific cidr in service subnet.
self.assertEqual(len(crd_rules), 3)
def test__create_all_pods_sg_rules(self):
port = {'protocol': 'TCP', 'port': 22}
direction = 'ingress'
rules = []
self._driver._create_all_pods_sg_rules(port, self._sg_id, direction,
rules, '', None)
self.assertEqual(len(rules), 2)
def test__create_default_sg_rule(self):
for direction in ('ingress', 'egress'):
rules = []
self._driver._create_default_sg_rule(self._sg_id, direction, rules)
self.assertEqual(len(rules), 2)
self.assertListEqual(rules, [{'security_group_rule': {
'ethertype': e,
'security_group_id': self._sg_id,
'direction': direction,
'description': 'Kuryr-Kubernetes NetPolicy SG rule'
}} for e in ('IPv4', 'IPv6')])

View File

@ -700,3 +700,94 @@ class TestNetworkPolicySecurityGroupsDriver(test_base.TestCase):
self.assertEqual(matched, matched_selector)
self.assertEqual(rules, final_crd_rules)
class TestNetworkPolicySecurityGroupsFunctions(test_base.TestCase):
def setUp(self):
super().setUp()
self.kubernetes = self.useFixture(k_fix.MockK8sClient()).client
self.npsg = network_policy_security_groups
self.sg_id = mock.sentinel.sg_id
self.crd = {
'spec': {
'ingressSgRules': [],
'networkpolicy_spec': {
'ingress': [],
'policyTypes': ['Ingress']
}
},
'metadata': {'namespace': 'ns'}
}
@mock.patch('kuryr_kubernetes.controller.drivers.utils.'
'create_security_group_rule')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.'
'create_security_group_rule_body')
def test__apply_sg_rules_on_matched_pods_empty_match(self, m_create_sgrb,
m_create_sgr):
self.npsg._apply_sg_rules_on_matched_pods({}, self.sg_id, 'ingress',
'ns', 'port', 'crd_rules')
m_create_sgrb.assert_not_called()
m_create_sgr.assert_not_called()
@mock.patch('kuryr_kubernetes.controller.drivers.utils.'
'get_namespace_subnet_cidr')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.'
'get_namespace')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.'
'create_security_group_rule')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.'
'create_security_group_rule_body')
def test__apply_sg_rules_on_matched_pods_not_all(self, m_create_sgrb,
m_create_sgr, m_get_ns,
m_get_ns_sub_cidr):
pod = mock.sentinel.pod
ns = mock.sentinel.ns
port = {'protocol': 'TCP', 'port': 22}
matched_pods = {'container_port': [pod]}
m_get_ns.return_value = ns
m_create_sgrb.return_value = {'security_group_rule': {}}
crd_rules = []
direction = 'ingress'
self.npsg._apply_sg_rules_on_matched_pods(matched_pods, self.sg_id,
direction, 'ns', port,
crd_rules)
m_get_ns_sub_cidr.assert_called_once_with(ns)
m_create_sgrb.assert_called_once_with(self.sg_id, direction,
'container_port',
protocol=mock.ANY, cidr=mock.ANY,
pods=[pod])
m_create_sgr.assert_called_once()
self.assertEqual(len(crd_rules), 1)
@mock.patch('kuryr_kubernetes.controller.drivers.utils.'
'get_namespace_subnet_cidr')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.'
'get_namespace')
@mock.patch('kuryr_kubernetes.controller.drivers.utils.'
'create_security_group_rule')
def test__apply_sg_rules_on_matched_pods_all(self, m_create_sgr, m_get_ns,
m_get_ns_sub_cidr):
pod = mock.sentinel.pod
ns = mock.sentinel.ns
port = {'protocol': 'TCP', 'port': 22}
matched_pods = {'container_port': [pod]}
m_get_ns.return_value = ns
crd_rules = []
direction = 'ingress'
self.npsg._apply_sg_rules_on_matched_pods(matched_pods, self.sg_id,
direction, 'ns', port,
crd_rules, allow_all=True)
self.assertEqual(m_create_sgr.call_count, 2)
self.assertEqual(len(crd_rules), 2)
self.assertListEqual([r['security_group_rule']['ethertype']
for r in crd_rules], ['IPv4', 'IPv6'])