diff --git a/kuryr_kubernetes/constants.py b/kuryr_kubernetes/constants.py index b579033ae..4e8999f49 100644 --- a/kuryr_kubernetes/constants.py +++ b/kuryr_kubernetes/constants.py @@ -37,6 +37,7 @@ K8S_POD_STATUS_PENDING = 'Pending' K8S_ANNOTATION_PREFIX = 'openstack.org/kuryr' K8S_ANNOTATION_VIF = K8S_ANNOTATION_PREFIX + '-vif' K8S_ANNOTATION_LABEL = K8S_ANNOTATION_PREFIX + '-pod-label' +K8S_ANNOTATION_NAMESPACE_LABEL = K8S_ANNOTATION_PREFIX + '-namespace-label' K8S_ANNOTATION_LBAAS_SPEC = K8S_ANNOTATION_PREFIX + '-lbaas-spec' K8S_ANNOTATION_LBAAS_STATE = K8S_ANNOTATION_PREFIX + '-lbaas-state' K8S_ANNOTATION_NET_CRD = K8S_ANNOTATION_PREFIX + '-net-crd' diff --git a/kuryr_kubernetes/controller/drivers/base.py b/kuryr_kubernetes/controller/drivers/base.py index 765e3dd7e..d5574366b 100644 --- a/kuryr_kubernetes/controller/drivers/base.py +++ b/kuryr_kubernetes/controller/drivers/base.py @@ -268,6 +268,27 @@ class PodSecurityGroupsDriver(DriverBase): """ raise NotImplementedError() + def delete_namespace_sg_rules(self, namespace): + """Delete security group rule associated to a namespace. + + :param namespace: dict containing K8S Namespace object + """ + raise NotImplementedError() + + def create_namespace_sg_rules(self, namespace): + """Create security group rule associated to a namespace. + + :param namespace: dict containing K8S Namespace object + """ + raise NotImplementedError() + + def update_namespace_sg_rules(self, namespace): + """Update security group rule associated to a namespace. + + :param namespace: dict containing K8S Namespace object + """ + raise NotImplementedError() + @six.add_metaclass(abc.ABCMeta) class ServiceSecurityGroupsDriver(DriverBase): diff --git a/kuryr_kubernetes/controller/drivers/default_security_groups.py b/kuryr_kubernetes/controller/drivers/default_security_groups.py index 0a4dd9573..432af0d73 100644 --- a/kuryr_kubernetes/controller/drivers/default_security_groups.py +++ b/kuryr_kubernetes/controller/drivers/default_security_groups.py @@ -59,6 +59,18 @@ class DefaultPodSecurityGroupsDriver(base.PodSecurityGroupsDriver): LOG.debug("Security group driver does not update SG rules for " "the pods.") + def delete_namespace_sg_rules(self, namespace): + LOG.debug("Security group driver does not delete SG rules for " + "namespace.") + + def create_namespace_sg_rules(self, namespace): + LOG.debug("Security group driver does not create SG rules for " + "namespace.") + + def update_namespace_sg_rules(self, namespace): + LOG.debug("Security group driver does not update SG rules for " + "namespace.") + class DefaultServiceSecurityGroupsDriver(base.ServiceSecurityGroupsDriver): """Provides security groups for Service based on a configuration option.""" diff --git a/kuryr_kubernetes/controller/drivers/namespace_security_groups.py b/kuryr_kubernetes/controller/drivers/namespace_security_groups.py index 3e25b56c2..e4e2b87d9 100644 --- a/kuryr_kubernetes/controller/drivers/namespace_security_groups.py +++ b/kuryr_kubernetes/controller/drivers/namespace_security_groups.py @@ -21,6 +21,7 @@ from kuryr_kubernetes import clients from kuryr_kubernetes import config from kuryr_kubernetes import constants from kuryr_kubernetes.controller.drivers import base +from kuryr_kubernetes.controller.drivers import utils from kuryr_kubernetes import exceptions from neutronclient.common import exceptions as n_exc @@ -67,6 +68,73 @@ def _get_net_crd(namespace): return net_crd +def _create_sg_rule(sg_id, direction, cidr, port=None, namespace=None): + if port: + sg_rule = utils.create_security_group_rule_body( + sg_id, direction, port.get('port'), + protocol=port.get('protocol'), cidr=cidr, namespace=namespace) + else: + sg_rule = utils.create_security_group_rule_body( + sg_id, direction, port_range_min=1, + port_range_max=65535, cidr=cidr, namespace=namespace) + + sgr_id = utils.create_security_group_rule(sg_rule) + + sg_rule['security_group_rule']['id'] = sgr_id + return sg_rule + + +def _parse_rules(direction, crd, namespace): + policy = crd['spec']['networkpolicy_spec'] + sg_id = crd['spec']['securityGroupId'] + + ns_labels = namespace['metadata'].get('labels') + ns_name = namespace['metadata'].get('name') + ns_cidr = utils.get_namespace_subnet_cidr(namespace) + + rule_direction = 'from' + crd_rules = crd['spec'].get('ingressSgRules') + if direction == 'egress': + rule_direction = 'to' + crd_rules = crd['spec'].get('egressSgRules') + + matched = False + rule_list = policy.get(direction, None) + for rule_block in rule_list: + for rule in rule_block.get(rule_direction, []): + pod_selector = rule.get('podSelector') + ns_selector = rule.get('namespaceSelector') + if (ns_selector and ns_labels and + utils.match_selector(ns_selector, ns_labels)): + if pod_selector: + pods = utils.get_pods(pod_selector, ns_name) + for pod in pods.get('items'): + pod_ip = utils.get_pod_ip(pod) + if 'ports' in rule_block: + for port in rule_block['ports']: + matched = True + crd_rules.append(_create_sg_rule( + sg_id, direction, pod_ip, port=port, + namespace=ns_name)) + else: + matched = True + crd_rules.append(_create_sg_rule( + sg_id, direction, pod_ip, + namespace=ns_name)) + else: + if 'ports' in rule_block: + for port in rule_block['ports']: + matched = True + crd_rules.append(_create_sg_rule( + sg_id, direction, ns_cidr, + port=port, namespace=ns_name)) + else: + matched = True + crd_rules.append(_create_sg_rule( + sg_id, direction, ns_cidr, namespace=ns_name)) + return matched, crd_rules + + class NamespacePodSecurityGroupsDriver(base.PodSecurityGroupsDriver): """Provides security groups for Pod based on a configuration option.""" @@ -131,6 +199,68 @@ class NamespacePodSecurityGroupsDriver(base.PodSecurityGroupsDriver): LOG.exception("Error deleting security group %s.", sg_id) raise + def delete_namespace_sg_rules(self, namespace): + ns_name = namespace['metadata']['name'] + LOG.debug("Deleting sg rule for namespace: %s", + ns_name) + + knp_crds = utils.get_kuryrnetpolicy_crds() + for crd in knp_crds.get('items'): + crd_selector = crd['spec'].get('podSelector') + ingress_rule_list = crd['spec'].get('ingressSgRules') + egress_rule_list = crd['spec'].get('egressSgRules') + i_rules = [] + e_rules = [] + + matched = False + for i_rule in ingress_rule_list: + LOG.debug("Parsing ingress rule: %r", i_rule) + rule_namespace = i_rule.get('namespace', None) + + if rule_namespace and rule_namespace == ns_name: + matched = True + utils.delete_security_group_rule( + i_rule['security_group_rule']['id']) + else: + i_rules.append(i_rule) + + for e_rule in egress_rule_list: + LOG.debug("Parsing egress rule: %r", e_rule) + rule_namespace = e_rule.get('namespace', None) + + if rule_namespace and rule_namespace == ns_name: + matched = True + utils.delete_security_group_rule( + e_rule['security_group_rule']['id']) + else: + e_rules.append(e_rule) + + if matched: + utils.patch_kuryr_crd(crd, i_rules, e_rules, crd_selector) + + def create_namespace_sg_rules(self, namespace): + kubernetes = clients.get_kubernetes_client() + ns_name = namespace['metadata']['name'] + LOG.debug("Creating sg rule for namespace: %s", ns_name) + namespace = kubernetes.get( + '{}/namespaces/{}'.format(constants.K8S_API_BASE, ns_name)) + knp_crds = utils.get_kuryrnetpolicy_crds() + for crd in knp_crds.get('items'): + crd_selector = crd['spec'].get('podSelector') + + i_matched, i_rules = _parse_rules('ingress', crd, namespace) + e_matched, e_rules = _parse_rules('egress', crd, namespace) + + if i_matched or e_matched: + utils.patch_kuryr_crd(crd, i_rules, + e_rules, crd_selector) + + def update_namespace_sg_rules(self, namespace): + LOG.debug("Updating sg rule for namespace: %s", + namespace['metadata']['name']) + self.delete_namespace_sg_rules(namespace) + self.create_namespace_sg_rules(namespace) + def create_sg_rules(self, pod): LOG.debug("Security group driver does not create SG rules for " "the pods.") diff --git a/kuryr_kubernetes/controller/drivers/network_policy.py b/kuryr_kubernetes/controller/drivers/network_policy.py index 2f1e07fe4..3b7593065 100644 --- a/kuryr_kubernetes/controller/drivers/network_policy.py +++ b/kuryr_kubernetes/controller/drivers/network_policy.py @@ -203,40 +203,27 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver): matching_pods = driver_utils.get_pods(pod_selector, namespace) for pod in matching_pods.get('items'): if pod['status']['podIP']: - ips.append(pod['status']['podIP']) + pod_ip = pod['status']['podIP'] + ns = pod['metadata']['namespace'] + ips.append({'cidr': pod_ip, 'namespace': ns}) return ips - def _get_namespace_subnet_cidr(self, namespace): - try: - ns_annotations = namespace['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 exceptions.ResourceNotReady(namespace) - try: - net_crd = self.kubernetes.get('{}/kuryrnets/{}'.format( - constants.K8S_API_CRD, ns_name)) - except exceptions.K8sClientException: - LOG.exception("Kubernetes Client Exception.") - raise - return net_crd['spec']['subnetCIDR'] - def _get_namespaces_cidr(self, namespace_selector, namespace=None): cidrs = [] if not namespace_selector and namespace: ns = self.kubernetes.get( '{}/namespaces/{}'.format(constants.K8S_API_BASE, namespace)) - ns_cidr = self._get_namespace_subnet_cidr(ns) - cidrs.append(ns_cidr) + ns_cidr = driver_utils.get_namespace_subnet_cidr(ns) + cidrs.append({'cidr': ns_cidr, 'namespace': namespace}) else: matching_namespaces = driver_utils.get_namespaces( namespace_selector) for ns in matching_namespaces.get('items'): # NOTE(ltomasbo): This requires the namespace handler to be # also enabled - ns_cidr = self._get_namespace_subnet_cidr(ns) - cidrs.append(ns_cidr) + ns_cidr = driver_utils.get_namespace_subnet_cidr(ns) + ns_name = ns['metadata']['name'] + cidrs.append({'cidr': ns_cidr, 'namespace': ns_name}) return cidrs def _parse_selectors(self, rule_block, rule_direction, policy_namespace): @@ -315,7 +302,8 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver): driver_utils.create_security_group_rule_body( sg_id, direction, port.get('port'), protocol=port.get('protocol'), - cidr=cidr)) + cidr=cidr.get('cidr'), + namespace=cidr.get('namespace'))) sg_rule_body_list.append(rule) if allow_all: rule = ( @@ -334,7 +322,8 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver): sg_id, direction, port_range_min=1, port_range_max=65535, - cidr=cidr) + cidr=cidr.get('cidr'), + namespace=cidr.get('namespace')) sg_rule_body_list.append(rule) if allow_all: rule = driver_utils.create_security_group_rule_body( diff --git a/kuryr_kubernetes/controller/drivers/network_policy_security_groups.py b/kuryr_kubernetes/controller/drivers/network_policy_security_groups.py index 00a3d20d4..69fda9c75 100644 --- a/kuryr_kubernetes/controller/drivers/network_policy_security_groups.py +++ b/kuryr_kubernetes/controller/drivers/network_policy_security_groups.py @@ -26,81 +26,6 @@ from oslo_log import log as logging LOG = logging.getLogger(__name__) -OPERATORS_WITH_VALUES = [constants.K8S_OPERATOR_IN, - constants.K8S_OPERATOR_NOT_IN] - - -def _get_kuryrnetpolicy_crds(namespace=None): - kubernetes = clients.get_kubernetes_client() - - try: - if namespace: - knp_path = '{}/{}/kuryrnetpolicies'.format( - constants.K8S_API_CRD_NAMESPACES, namespace) - else: - knp_path = constants.K8S_API_CRD_KURYRNETPOLICIES - LOG.debug("K8s API Query %s", knp_path) - knps = kubernetes.get(knp_path) - LOG.debug("Return Kuryr Network Policies with label %s", knps) - except exceptions.K8sResourceNotFound: - LOG.exception("KuryrNetPolicy CRD not found") - raise - except exceptions.K8sClientException: - LOG.exception("Kubernetes Client Exception") - raise - return knps - - -def _match_expressions(expressions, labels): - for exp in expressions: - exp_op = exp['operator'].lower() - if labels: - if exp_op in OPERATORS_WITH_VALUES: - exp_values = exp['values'] - label_value = labels.get(str(exp['key']), None) - if exp_op == constants.K8S_OPERATOR_IN: - if label_value is None or label_value not in exp_values: - return False - elif exp_op == constants.K8S_OPERATOR_NOT_IN: - if label_value in exp_values: - return False - else: - if exp_op == constants.K8S_OPERATOR_EXISTS: - exists = labels.get(str(exp['key']), None) - if exists is None: - return False - elif exp_op == constants.K8S_OPERATOR_DOES_NOT_EXIST: - exists = labels.get(str(exp['key']), None) - if exists is not None: - return False - else: - if exp_op in (constants.K8S_OPERATOR_IN, - constants.K8S_OPERATOR_EXISTS): - return False - return True - - -def _match_labels(crd_labels, labels): - for crd_key, crd_value in crd_labels.items(): - label_value = labels.get(crd_key, None) - if not label_value or crd_value != label_value: - return False - return True - - -def _match_selector(selector, labels): - crd_labels = selector.get('matchLabels', None) - crd_expressions = selector.get('matchExpressions', None) - - match_exp = match_lb = True - if crd_expressions: - match_exp = _match_expressions(crd_expressions, - labels) - if crd_labels and labels: - match_lb = _match_labels(crd_labels, labels) - return match_exp and match_lb - - def _get_namespace_labels(namespace): kubernetes = clients.get_kubernetes_client() @@ -119,15 +44,15 @@ def _get_namespace_labels(namespace): return namespaces['metadata'].get('labels') -def _create_sg_rules(crd, pod, namespace_selector, pod_selector, - rule_block, crd_rules, direction, - matched): +def _create_sg_rules(crd, pod, pod_selector, rule_block, crd_rules, + direction, matched, namespace=None): pod_labels = pod['metadata'].get('labels') # NOTE (maysams) No need to differentiate between podSelector # with empty value or with '{}', as they have same result in here. if (pod_selector and - _match_selector(pod_selector, pod_labels)): + driver_utils.match_selector(pod_selector, pod_labels)): + matched = True pod_ip = driver_utils.get_pod_ip(pod) sg_id = crd['spec']['securityGroupId'] @@ -135,7 +60,8 @@ def _create_sg_rules(crd, pod, namespace_selector, pod_selector, for port in rule_block['ports']: sg_rule = driver_utils.create_security_group_rule_body( sg_id, direction, port.get('port'), - protocol=port.get('protocol'), cidr=pod_ip) + protocol=port.get('protocol'), cidr=pod_ip, + namespace=namespace) sgr_id = driver_utils.create_security_group_rule(sg_rule) sg_rule['security_group_rule']['id'] = sgr_id crd_rules.append(sg_rule) @@ -144,7 +70,8 @@ def _create_sg_rules(crd, pod, namespace_selector, pod_selector, sg_id, direction, port_range_min=1, port_range_max=65535, - cidr=pod_ip) + cidr=pod_ip, + namespace=namespace) sgr_id = driver_utils.create_security_group_rule(sg_rule) sg_rule['security_group_rule']['id'] = sgr_id crd_rules.append(sg_rule) @@ -171,23 +98,22 @@ def _parse_rules(direction, crd, pod): namespace_selector = rule.get('namespaceSelector') pod_selector = rule.get('podSelector') if namespace_selector == {}: - if _create_sg_rules(crd, pod, namespace_selector, - pod_selector, rule_block, crd_rules, - direction, matched): + if _create_sg_rules(crd, pod, pod_selector, rule_block, + crd_rules, direction, matched): matched = True elif namespace_selector: if (pod_namespace_labels and - _match_selector(namespace_selector, - pod_namespace_labels)): - if _create_sg_rules(crd, pod, namespace_selector, - pod_selector, rule_block, crd_rules, - direction, matched): + driver_utils.match_selector(namespace_selector, + pod_namespace_labels)): + if _create_sg_rules(crd, pod, pod_selector, rule_block, + crd_rules, direction, matched, + pod_namespace): matched = True else: if pod_namespace == policy_namespace: - if _create_sg_rules(crd, pod, namespace_selector, - pod_selector, rule_block, crd_rules, - direction, matched): + if _create_sg_rules(crd, pod, pod_selector, rule_block, + crd_rules, direction, matched, + pod_namespace): matched = True return matched, crd_rules @@ -198,11 +124,12 @@ def _get_pod_sgs(pod, project_id): pod_labels = pod['metadata'].get('labels') pod_namespace = pod['metadata']['namespace'] - knp_crds = _get_kuryrnetpolicy_crds(namespace=pod_namespace) + knp_crds = driver_utils.get_kuryrnetpolicy_crds( + namespace=pod_namespace) for crd in knp_crds.get('items'): pod_selector = crd['spec'].get('podSelector') if pod_selector: - if _match_selector(pod_selector, pod_labels): + if driver_utils.match_selector(pod_selector, pod_labels): LOG.debug("Appending %s", str(crd['spec']['securityGroupId'])) sg_list.append(str(crd['spec']['securityGroupId'])) @@ -229,7 +156,7 @@ class NetworkPolicySecurityGroupsDriver(base.PodSecurityGroupsDriver): def create_sg_rules(self, pod): LOG.debug("Creating sg rule for pod: %s", pod['metadata']['name']) - knp_crds = _get_kuryrnetpolicy_crds() + knp_crds = driver_utils.get_kuryrnetpolicy_crds() for crd in knp_crds.get('items'): crd_selector = crd['spec'].get('podSelector') @@ -244,7 +171,7 @@ class NetworkPolicySecurityGroupsDriver(base.PodSecurityGroupsDriver): LOG.debug("Deleting sg rule for pod: %s", pod['metadata']['name']) pod_ip = driver_utils.get_pod_ip(pod) - knp_crds = _get_kuryrnetpolicy_crds() + knp_crds = driver_utils.get_kuryrnetpolicy_crds() for crd in knp_crds.get('items'): crd_selector = crd['spec'].get('podSelector') ingress_rule_list = crd['spec'].get('ingressSgRules') @@ -293,6 +220,18 @@ class NetworkPolicySecurityGroupsDriver(base.PodSecurityGroupsDriver): LOG.debug("Security group driver does not implement deleting " "SGs.") + def delete_namespace_sg_rules(self, namespace): + LOG.debug("Security group driver does not delete SG rules for " + "namespace.") + + def create_namespace_sg_rules(self, namespace): + LOG.debug("Security group driver does not create SG rules for " + "namespace.") + + def update_namespace_sg_rules(self, namespace): + LOG.debug("Security group driver does not update SG rules for " + "namespace.") + class NetworkPolicyServiceSecurityGroupsDriver( base.ServiceSecurityGroupsDriver): diff --git a/kuryr_kubernetes/controller/drivers/utils.py b/kuryr_kubernetes/controller/drivers/utils.py index 6bf8eb02e..dc758cd09 100644 --- a/kuryr_kubernetes/controller/drivers/utils.py +++ b/kuryr_kubernetes/controller/drivers/utils.py @@ -238,7 +238,7 @@ def patch_kuryr_crd(crd, i_rules, e_rules, pod_selector, np_spec=None): def create_security_group_rule_body( security_group_id, direction, port_range_min, port_range_max=None, protocol=None, ethertype='IPv4', cidr=None, - description="Kuryr-Kubernetes NetPolicy SG rule"): + description="Kuryr-Kubernetes NetPolicy SG rule", namespace=None): if not port_range_min: port_range_min = 1 port_range_max = 65535 @@ -260,6 +260,8 @@ def create_security_group_rule_body( if cidr: security_group_rule_body[u'security_group_rule'][ u'remote_ip_prefix'] = cidr + if namespace: + security_group_rule_body['namespace'] = namespace LOG.debug("Creating sg rule body %s", security_group_rule_body) return security_group_rule_body @@ -280,11 +282,100 @@ def get_pod_ip(pod): return first_subnet_ip -def get_pod_annotated_labels(pod): +def get_annotated_labels(resource, annotation_labels): try: - annotations = pod['metadata']['annotations'] - pod_labels_annotation = annotations[constants.K8S_ANNOTATION_LABEL] + annotations = resource['metadata']['annotations'] + labels_annotation = annotations[annotation_labels] except KeyError: return None - pod_labels = jsonutils.loads(pod_labels_annotation) - return pod_labels + labels = jsonutils.loads(labels_annotation) + return labels + + +def get_kuryrnetpolicy_crds(namespace=None): + kubernetes = clients.get_kubernetes_client() + + try: + if namespace: + knp_path = '{}/{}/kuryrnetpolicies'.format( + constants.K8S_API_CRD_NAMESPACES, namespace) + else: + knp_path = constants.K8S_API_CRD_KURYRNETPOLICIES + LOG.debug("K8s API Query %s", knp_path) + knps = kubernetes.get(knp_path) + LOG.debug("Return Kuryr Network Policies with label %s", knps) + except k_exc.K8sResourceNotFound: + LOG.exception("KuryrNetPolicy CRD not found") + raise + except k_exc.K8sClientException: + LOG.exception("Kubernetes Client Exception") + raise + return knps + + +def match_expressions(expressions, labels): + for exp in expressions: + exp_op = exp['operator'].lower() + if labels: + if exp_op in OPERATORS_WITH_VALUES: + exp_values = exp['values'] + label_value = labels.get(str(exp['key']), None) + if exp_op == constants.K8S_OPERATOR_IN: + if label_value is None or label_value not in exp_values: + return False + elif exp_op == constants.K8S_OPERATOR_NOT_IN: + if label_value in exp_values: + return False + else: + if exp_op == constants.K8S_OPERATOR_EXISTS: + exists = labels.get(str(exp['key']), None) + if exists is None: + return False + elif exp_op == constants.K8S_OPERATOR_DOES_NOT_EXIST: + exists = labels.get(str(exp['key']), None) + if exists is not None: + return False + else: + if exp_op in (constants.K8S_OPERATOR_IN, + constants.K8S_OPERATOR_EXISTS): + return False + return True + + +def match_labels(crd_labels, labels): + for crd_key, crd_value in crd_labels.items(): + label_value = labels.get(crd_key, None) + if not label_value or crd_value != label_value: + return False + return True + + +def match_selector(selector, labels): + crd_labels = selector.get('matchLabels', None) + crd_expressions = selector.get('matchExpressions', None) + + match_exp = match_lb = True + if crd_expressions: + match_exp = match_expressions(crd_expressions, + labels) + if crd_labels and labels: + match_lb = match_labels(crd_labels, labels) + return match_exp and match_lb + + +def get_namespace_subnet_cidr(namespace): + kubernetes = clients.get_kubernetes_client() + try: + ns_annotations = namespace['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 k_exc.ResourceNotReady(namespace) + try: + net_crd = kubernetes.get('{}/kuryrnets/{}'.format( + constants.K8S_API_CRD, ns_name)) + except k_exc.K8sClientException: + LOG.exception("Kubernetes Client Exception.") + raise + return net_crd['spec']['subnetCIDR'] diff --git a/kuryr_kubernetes/controller/handlers/namespace.py b/kuryr_kubernetes/controller/handlers/namespace.py index a685ddeba..d5d82bfb3 100644 --- a/kuryr_kubernetes/controller/handlers/namespace.py +++ b/kuryr_kubernetes/controller/handlers/namespace.py @@ -15,10 +15,12 @@ from oslo_cache import core as cache from oslo_config import cfg as oslo_cfg from oslo_log import log as logging +from oslo_serialization import jsonutils from kuryr_kubernetes import clients from kuryr_kubernetes import constants from kuryr_kubernetes.controller.drivers import base as drivers +from kuryr_kubernetes.controller.drivers import utils as drivers_utils from kuryr_kubernetes import exceptions from kuryr_kubernetes.handlers import k8s_base from kuryr_kubernetes import utils @@ -57,6 +59,17 @@ class NamespaceHandler(k8s_base.ResourceEventHandler): self._drv_vif_pool.set_vif_driver() def on_present(self, namespace): + current_namespace_labels = namespace['metadata'].get('labels') + previous_namespace_labels = drivers_utils.get_annotated_labels( + namespace, constants.K8S_ANNOTATION_NAMESPACE_LABEL) + LOG.debug("Got previous namespace labels from annotation: %r", + previous_namespace_labels) + + if (previous_namespace_labels and + current_namespace_labels != previous_namespace_labels): + self._drv_sg.update_namespace_sg_rules(namespace) + self._set_namespace_labels(namespace, current_namespace_labels) + ns_name = namespace['metadata']['name'] project_id = self._drv_project.get_project(namespace) net_crd_id = self._get_net_crd_id(namespace) @@ -85,6 +98,8 @@ class NamespaceHandler(k8s_base.ResourceEventHandler): try: net_crd = self._add_kuryrnet_crd(ns_name, net_crd_spec) self._set_net_crd(namespace, net_crd) + self._drv_sg.create_namespace_sg_rules(namespace) + self._set_namespace_labels(namespace, current_namespace_labels) except exceptions.K8sClientException: LOG.exception("Kuryrnet CRD could not be added. Rolling back " "network resources created for the namespace.") @@ -108,8 +123,8 @@ class NamespaceHandler(k8s_base.ResourceEventHandler): else: LOG.debug("There is no security group associated with the " "namespace to be deleted") - self._del_kuryrnet_crd(net_crd_id) + self._drv_sg.delete_namespace_sg_rules(namespace) def is_ready(self, quota): if not utils.has_kuryr_crd(constants.K8S_API_CRD_KURYRNETS): @@ -191,3 +206,16 @@ class NamespaceHandler(k8s_base.ResourceEventHandler): LOG.exception("Kubernetes Client Exception deleting kuryrnet " "CRD.") raise + + def _set_namespace_labels(self, namespace, labels): + if not labels: + LOG.debug("Removing Label annotation: %r", labels) + annotation = None + else: + annotation = jsonutils.dumps(labels, sort_keys=True) + LOG.debug("Setting Labels annotation: %r", annotation) + + k8s = clients.get_kubernetes_client() + k8s.annotate(namespace['metadata']['selfLink'], + {constants.K8S_ANNOTATION_NAMESPACE_LABEL: annotation}, + resource_version=namespace['metadata']['resourceVersion']) diff --git a/kuryr_kubernetes/tests/unit/controller/drivers/test_namespace_security_groups.py b/kuryr_kubernetes/tests/unit/controller/drivers/test_namespace_security_groups.py index 63d452bce..3ccf8611c 100644 --- a/kuryr_kubernetes/tests/unit/controller/drivers/test_namespace_security_groups.py +++ b/kuryr_kubernetes/tests/unit/controller/drivers/test_namespace_security_groups.py @@ -61,6 +61,131 @@ def get_namespace_obj(): } +def get_no_match_crd_namespace_obj(): + return { + "kind": "Namespace", + "metadata": { + "annotations": { + "openstack.org/kuryr-namespace-label": '{"name": "dev"}', + "openstack.org/kuryr-net-crd": "ns-dev" + }, + "labels": {"name": "prod"}, + "name": "prod", + "selfLink": "/api/v1/namespaces/dev"}} + + +def get_match_crd_namespace_obj(): + return { + "kind": "Namespace", + "metadata": { + "annotations": { + "openstack.org/kuryr-namespace-label": '{"name": "dev"}', + "openstack.org/kuryr-net-crd": "ns-dev" + }, + "labels": { + "name": "dev" + }, + "name": "dev", + "selfLink": "/api/v1/namespaces/dev"}} + + +def get_match_crd_pod_obj(): + return { + 'kind': 'Pod', + 'metadata': { + 'name': mock.sentinel.pod_name, + 'namespace': 'dev', + 'labels': { + 'tier': 'backend'}, + 'annotations': { + 'openstack.org/kuryr-pod-label': '{"tier": "backend"}'}}, + 'status': {'podIP': mock.sentinel.podIP}} + + +def get_sg_rule(): + pod_ip = get_match_crd_pod_obj()['status'].get('podIP') + return { + "namespace": 'dev', + "security_group_rule": { + "description": "Kuryr-Kubernetes NetPolicy SG rule", + "direction": "ingress", + "ethertype": "IPv4", + "id": 'f15ff50a-e8a4-4872-81bf-a04cbb8cb388', + "port_range_max": 6379, + "port_range_min": 6379, + "protocol": "tcp", + "remote_ip_prefix": pod_ip, + "security_group_id": '36923e76-026c-422b-8dfd-7292e7c88228'}} + + +def get_matched_crd_obj(): + return { + "kind": "KuryrNetPolicy", + "metadata": {"name": "np-test-network-policy", + "namespace": "default"}, + "spec": { + "egressSgRules": [], + "ingressSgRules": [get_sg_rule()], + "networkpolicy_spec": { + "ingress": [ + {"from": [ + {"namespaceSelector": { + "matchLabels": {"name": "dev"}}}], + "ports": [ + {"port": 6379, + "protocol": "TCP"}]}], + "podSelector": {"matchLabels": {"app": "demo"}}, + "policyTypes": ["Ingress"]}, + "podSelector": {"matchLabels": {"app": "demo"}}, + "securityGroupId": '36923e76-026c-422b-8dfd-7292e7c88228'}} + + +def get_crd_obj_no_match(): + return { + "kind": "KuryrNetPolicy", + "metadata": {"name": "np-test-network-policy", + "namespace": "default"}, + "spec": { + "egressSgRules": [], + "ingressSgRules": [], + "networkpolicy_spec": { + "ingress": [ + {"from": [ + {"namespaceSelector": { + "matchLabels": {"name": "dev"}}}], + "ports": [ + {"port": 6379, + "protocol": "TCP"}]}], + "podSelector": {"matchLabels": {"app": "demo"}}, + "policyTypes": ["Ingress"]}, + "podSelector": {"matchLabels": {"app": "demo"}}, + "securityGroupId": '36923e76-026c-422b-8dfd-7292e7c88228'}} + + +def get_crd_obj_with_all_selectors(): + return { + "kind": "KuryrNetPolicy", + "metadata": {"name": "np-test-network-policy", + "namespace": "default"}, + "spec": { + "egressSgRules": [], + "ingressSgRules": [], + "networkpolicy_spec": { + "ingress": [ + {"from": [ + {"namespaceSelector": { + "matchLabels": {"name": "dev"}}, + "podSelector": { + "matchLabels": {"tier": "backend"}}}], + "ports": [ + {"port": 6379, + "protocol": "TCP"}]}], + "podSelector": {"matchLabels": {"app": "demo"}}, + "policyTypes": ["Ingress"]}, + "podSelector": {"matchLabels": {"app": "demo"}}, + "securityGroupId": '36923e76-026c-422b-8dfd-7292e7c88228'}} + + class TestNamespacePodSecurityGroupsDriver(test_base.TestCase): @mock.patch('kuryr_kubernetes.controller.drivers.' @@ -165,3 +290,143 @@ class TestNamespacePodSecurityGroupsDriver(test_base.TestCase): cls.delete_sg(m_driver, sg_id) neutron.delete_security_group.assert_called_once_with(sg_id) + + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'patch_kuryr_crd') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'delete_security_group_rule') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'get_kuryrnetpolicy_crds') + def test_delete_namespace_sg_rule(self, m_get_knp_crd, m_delete_sg_rule, + m_patch_kuryr_crd): + cls = namespace_security_groups.NamespacePodSecurityGroupsDriver + m_driver = mock.MagicMock(spec=cls) + i_rule = get_matched_crd_obj()['spec']['ingressSgRules'][0] + sg_rule_id = i_rule.get('security_group_rule')['id'] + + m_get_knp_crd.return_value = {"items": [get_matched_crd_obj()]} + + cls.delete_namespace_sg_rules(m_driver, get_match_crd_namespace_obj()) + + m_get_knp_crd.assert_called_once() + m_delete_sg_rule.assert_called_once_with(sg_rule_id) + m_patch_kuryr_crd.assert_called_once() + + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'patch_kuryr_crd') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'delete_security_group_rule') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'get_kuryrnetpolicy_crds') + def test_delete_namespace_sg_rule_no_match(self, m_get_knp_crd, + m_delete_sg_rule, + m_patch_kuryr_crd): + cls = namespace_security_groups.NamespacePodSecurityGroupsDriver + m_driver = mock.MagicMock(spec=cls) + + m_get_knp_crd.return_value = {"items": [get_matched_crd_obj()]} + + cls.delete_namespace_sg_rules(m_driver, + get_no_match_crd_namespace_obj()) + + m_get_knp_crd.assert_called_once() + m_delete_sg_rule.assert_not_called() + m_patch_kuryr_crd.assert_not_called() + + @mock.patch('kuryr_kubernetes.controller.drivers.' + 'namespace_security_groups._create_sg_rule') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'match_selector') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'get_namespace_subnet_cidr') + def test__parse_rules(self, m_get_ns_subnet_cidr, m_match_selector, + m_create_sg_rule): + crd = get_crd_obj_no_match() + policy = crd['spec']['networkpolicy_spec'] + i_rule = policy.get('ingress')[0] + ns_selector = i_rule['from'][0].get('namespaceSelector') + ns = get_match_crd_namespace_obj() + + m_get_ns_subnet_cidr.return_value = '10.0.2.0/26' + m_match_selector.return_value = True + m_create_sg_rule.return_value = get_sg_rule() + + matched, rules = namespace_security_groups._parse_rules('ingress', + crd, ns) + + m_get_ns_subnet_cidr.assert_called_once_with(ns) + m_match_selector.assert_called_once_with(ns_selector, + ns['metadata']['labels']) + m_create_sg_rule.assert_called_once() + + self.assertEqual(matched, True) + self.assertEqual(rules, [get_sg_rule()]) + + @mock.patch('kuryr_kubernetes.controller.drivers.' + 'namespace_security_groups._create_sg_rule') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'match_selector') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'get_namespace_subnet_cidr') + def test__parse_rules_no_match(self, m_get_ns_subnet_cidr, + m_match_selector, m_create_sg_rule): + crd = get_crd_obj_no_match() + policy = crd['spec']['networkpolicy_spec'] + i_rule = policy.get('ingress')[0] + ns_selector = i_rule['from'][0].get('namespaceSelector') + ns = get_no_match_crd_namespace_obj() + + m_get_ns_subnet_cidr.return_value = '10.0.2.0/26' + m_match_selector.return_value = False + + matched, rules = namespace_security_groups._parse_rules('ingress', + crd, ns) + + m_get_ns_subnet_cidr.assert_called_once_with(ns) + m_match_selector.assert_called_once_with(ns_selector, + ns['metadata']['labels']) + m_create_sg_rule.assert_not_called() + + self.assertEqual(matched, False) + self.assertEqual(rules, []) + + @mock.patch('kuryr_kubernetes.controller.drivers.' + 'namespace_security_groups._create_sg_rule') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'get_pod_ip') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'get_pods') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'match_selector') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'get_namespace_subnet_cidr') + def test__parse_rules_all_selectors(self, m_get_ns_subnet_cidr, + m_match_selector, m_get_pods, + m_get_pod_ip, m_create_sg_rule): + crd = get_crd_obj_with_all_selectors() + policy = crd['spec']['networkpolicy_spec'] + i_rule = policy.get('ingress')[0] + ns_selector = i_rule['from'][0].get('namespaceSelector') + pod_selector = i_rule['from'][0].get('podSelector') + ns = get_match_crd_namespace_obj() + pod = get_match_crd_pod_obj() + + m_get_ns_subnet_cidr.return_value = '10.0.2.0/26' + m_match_selector.return_value = True + m_get_pods.return_value = {"items": [pod]} + m_get_pod_ip.return_value = pod['status']['podIP'] + m_create_sg_rule.return_value = get_sg_rule() + + matched, rules = namespace_security_groups._parse_rules('ingress', + crd, ns) + + m_get_ns_subnet_cidr.assert_called_once_with(ns) + m_match_selector.assert_called_once_with(ns_selector, + ns['metadata']['labels']) + m_get_pods.assert_called_once_with(pod_selector, + ns['metadata']['name']) + m_get_pod_ip.assert_called_once_with(pod) + m_create_sg_rule.assert_called_once() + + self.assertEqual(matched, True) + self.assertEqual(rules, [get_sg_rule()]) diff --git a/kuryr_kubernetes/tests/unit/controller/drivers/test_network_policy.py b/kuryr_kubernetes/tests/unit/controller/drivers/test_network_policy.py index 8924419ab..ff57dae96 100644 --- a/kuryr_kubernetes/tests/unit/controller/drivers/test_network_policy.py +++ b/kuryr_kubernetes/tests/unit/controller/drivers/test_network_policy.py @@ -302,7 +302,7 @@ class TestNetworkPolicyDriver(test_base.TestCase): self.kubernetes.get.side_effect = [{'items': [pod]}, net_crd] resp = self._driver._get_namespaces_cidr(namespace_selector) - self.assertEqual([subnet_cidr], resp) + self.assertEqual(subnet_cidr, resp[0].get('cidr')) self.kubernetes.get.assert_called() def test_get_namespaces_cidr_no_matches(self): @@ -330,7 +330,7 @@ class TestNetworkPolicyDriver(test_base.TestCase): 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] + m_get_ns_cidr.return_value = [{'cidr': subnet_cidr, 'namespace': ''}] self._driver.parse_network_policy_rules(self._policy, self._sg_id) m_create.assert_called() m_get_ns_cidr.assert_called() @@ -374,7 +374,7 @@ class TestNetworkPolicyDriver(test_base.TestCase): 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] + m_get_ns_cidr.return_value = [{'cidr': subnet_cidr, 'namespace': ''}] policy = self._policy.copy() selectors = {'namespaceSelector': { 'matchLabels': { @@ -389,9 +389,11 @@ class TestNetworkPolicyDriver(test_base.TestCase): 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), + port_range_max=65535, cidr=subnet_cidr, + namespace=''), mock.call(self._sg_id, 'egress', port_range_min=1, - port_range_max=65535, cidr=subnet_cidr)] + port_range_max=65535, cidr=subnet_cidr, + namespace='')] m_create.assert_has_calls(calls) def test_knps_on_namespace(self): diff --git a/kuryr_kubernetes/tests/unit/controller/drivers/test_network_policy_security_groups.py b/kuryr_kubernetes/tests/unit/controller/drivers/test_network_policy_security_groups.py index 3ca030ceb..69de89e3e 100644 --- a/kuryr_kubernetes/tests/unit/controller/drivers/test_network_policy_security_groups.py +++ b/kuryr_kubernetes/tests/unit/controller/drivers/test_network_policy_security_groups.py @@ -258,8 +258,8 @@ class TestNetworkPolicySecurityGroupsDriver(test_base.TestCase): 'create_security_group_rule') @mock.patch('kuryr_kubernetes.controller.drivers.utils.' 'create_security_group_rule_body') - @mock.patch.object(network_policy_security_groups, - '_match_selector', return_value=True) + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'match_selector', return_value=True) @mock.patch('kuryr_kubernetes.controller.drivers.utils.get_pod_ip') def test__create_sg_rules(self, m_get_pod_ip, m_match_selector, @@ -277,16 +277,15 @@ class TestNetworkPolicySecurityGroupsDriver(test_base.TestCase): policy = crd['spec']['networkpolicy_spec'] rule_list = policy.get('ingress', None) crd_rules = crd['spec'].get('ingressSgRules') + pod_ns = pod['metadata']['namespace'] for rule_block in rule_list: for rule in rule_block.get('from', []): - namespace_selector = rule.get('namespaceSelector') pod_selector = rule.get('podSelector') matched = network_policy_security_groups._create_sg_rules( - crd, pod, namespace_selector, pod_selector, rule_block, - crd_rules, 'ingress', matched) - new_sg_rule['namespaceSelector'] = namespace_selector - new_sg_rule['podSelector'] = pod_selector + crd, pod, pod_selector, rule_block, + crd_rules, 'ingress', matched, pod_ns) + new_sg_rule['namespace'] = pod_ns new_sg_rule['security_group_rule']['id'] = sgr_id m_match_selector.assert_called_once_with( pod_selector, pod['metadata']['labels']) @@ -296,8 +295,8 @@ class TestNetworkPolicySecurityGroupsDriver(test_base.TestCase): self.assertEqual([new_sg_rule], crd_rules) self.assertEqual(matched, True) - @mock.patch.object(network_policy_security_groups, - '_match_selector', return_value=False) + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'match_selector', return_value=False) def test__create_sg_rules_no_match(self, m_match_selector): crd = self._crd_without_rules pod = self._pod2 @@ -308,17 +307,16 @@ class TestNetworkPolicySecurityGroupsDriver(test_base.TestCase): for rule_block in rule_list: for rule in rule_block.get('from', []): - namespace_selector = rule.get('namespaceSelector') pod_selector = rule.get('podSelector') matched = network_policy_security_groups._create_sg_rules( - crd, pod, namespace_selector, pod_selector, rule_block, - crd_rules, 'ingress', False) + crd, pod, pod_selector, rule_block, + crd_rules, 'ingress', False, self._namespace) self.assertEqual(matched, False) @mock.patch('kuryr_kubernetes.controller.drivers.utils.' 'patch_kuryr_crd') - @mock.patch.object(network_policy_security_groups, - '_get_kuryrnetpolicy_crds') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'get_kuryrnetpolicy_crds') @mock.patch('kuryr_kubernetes.controller.drivers.utils.' 'delete_security_group_rule') @mock.patch('kuryr_kubernetes.controller.drivers.utils.get_pod_ip') @@ -347,8 +345,8 @@ class TestNetworkPolicySecurityGroupsDriver(test_base.TestCase): crd, i_rules, e_rules, crd['spec'].get('podSelector')) @mock.patch('kuryr_kubernetes.config.CONF') - @mock.patch.object(network_policy_security_groups, - '_get_kuryrnetpolicy_crds') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'get_kuryrnetpolicy_crds') def test_get_sgs_for_pod_without_label(self, m_get_crds, m_cfg): m_get_crds.return_value = self._crds sg_list = [str(mock.sentinel.sg_id)] @@ -360,12 +358,12 @@ class TestNetworkPolicySecurityGroupsDriver(test_base.TestCase): m_get_crds.assert_called_once_with(namespace=self._namespace) self.assertEqual(sg_list, sgs) - @mock.patch.object(network_policy_security_groups, - '_match_expressions') - @mock.patch.object(network_policy_security_groups, - '_match_labels') - @mock.patch.object(network_policy_security_groups, - '_get_kuryrnetpolicy_crds') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'match_expressions') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'match_labels') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'get_kuryrnetpolicy_crds') def test_get_sgs_for_pod_with_label(self, m_get_crds, m_match_labels, m_match_expressions): m_get_crds.return_value = self._crds @@ -382,12 +380,12 @@ class TestNetworkPolicySecurityGroupsDriver(test_base.TestCase): self.assertEqual(resp, [str(self._sg_id)]) @mock.patch('kuryr_kubernetes.config.CONF') - @mock.patch.object(network_policy_security_groups, - '_match_expressions') - @mock.patch.object(network_policy_security_groups, - '_match_labels') - @mock.patch.object(network_policy_security_groups, - '_get_kuryrnetpolicy_crds') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'match_expressions') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'match_labels') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'get_kuryrnetpolicy_crds') def test_get_sgs_for_pod_with_label_no_match(self, m_get_crds, m_match_labels, m_match_expressions, m_cfg): @@ -407,8 +405,8 @@ class TestNetworkPolicySecurityGroupsDriver(test_base.TestCase): self._crd['spec']['podSelector']['matchLabels'], pod_labels) self.assertEqual(sg_list, sgs) - @mock.patch.object(network_policy_security_groups, - '_get_kuryrnetpolicy_crds') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'get_kuryrnetpolicy_crds') def test_get_sgs_no_crds(self, m_get_crds): m_get_crds.return_value = self._empty_crds cfg.CONF.set_override('pod_security_groups', [], @@ -419,12 +417,12 @@ class TestNetworkPolicySecurityGroupsDriver(test_base.TestCase): self._project_id) m_get_crds.assert_called_with(namespace=self._namespace) - @mock.patch.object(network_policy_security_groups, - '_match_expressions') - @mock.patch.object(network_policy_security_groups, - '_match_labels') - @mock.patch.object(network_policy_security_groups, - '_get_kuryrnetpolicy_crds') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'match_expressions') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'match_labels') + @mock.patch('kuryr_kubernetes.controller.drivers.utils.' + 'get_kuryrnetpolicy_crds') def test_get_sgs_multiple_crds(self, m_get_crds, m_match_labels, m_match_expressions): m_match_expressions.return_value = True