From 4465c2062a57c938d6621897935362a48d56ee8d Mon Sep 17 00:00:00 2001 From: Daniel Mellado Date: Wed, 11 Jul 2018 07:21:37 -0400 Subject: [PATCH] Implement NP SG create/delete actions This commit implements NP driver actions for creating/updating SG and SG rules. It also creates KuryrNetPolicy as a CRD so we don't have to rely on the slow neutron API for time-costly operations such as listing SG and so. Security group rules and label matching will be handled in a follow-up patch, as well as storing CRD object_id in a network policy annotation. Unit tests will also be added after some more functionality is added with the remaining patch series. Partially-Implements: bp/k8s-network-policies Change-Id: I6d45a462e812b24073b529144fc0843e8725a06e --- devstack/plugin.sh | 1 + doc/source/installation/index.rst | 1 + doc/source/installation/network_policy.rst | 98 ++++++++++++++++ kubernetes_crds/kuryrnetpolicy.yaml | 14 +++ kuryr_kubernetes/constants.py | 3 + .../controller/drivers/network_policy.py | 105 +++++++++++++++++- kuryr_kubernetes/k8s_client.py | 2 + 7 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 doc/source/installation/network_policy.rst create mode 100644 kubernetes_crds/kuryrnetpolicy.yaml diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 41f7e7bc0..590ccf711 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -902,6 +902,7 @@ if [[ "$1" == "stack" && "$2" == "extra" ]]; then KURYR_K8S_CONTAINERIZED_DEPLOYMENT=$(trueorfalse False KURYR_K8S_CONTAINERIZED_DEPLOYMENT) if is_service_enabled kuryr-kubernetes; then /usr/local/bin/kubectl apply -f ${KURYR_HOME}/kubernetes_crds/kuryrnet.yaml + /usr/local/bin/kubectl apply -f ${KURYR_HOME}/kubernetes_crds/kuryrnetpolicy.yaml if [ "$KURYR_K8S_CONTAINERIZED_DEPLOYMENT" == "True" ]; then if is_service_enabled kuryr-daemon; then build_kuryr_containers $CNI_BIN_DIR $CNI_CONF_DIR True diff --git a/doc/source/installation/index.rst b/doc/source/installation/index.rst index 99baa3de0..951eda927 100644 --- a/doc/source/installation/index.rst +++ b/doc/source/installation/index.rst @@ -37,6 +37,7 @@ This section describes how you can install and configure kuryr-kubernetes default_configuration trunk_ports network_namespace + network_policy testing_connectivity testing_nested_connectivity containerized diff --git a/doc/source/installation/network_policy.rst b/doc/source/installation/network_policy.rst new file mode 100644 index 000000000..55f143c44 --- /dev/null +++ b/doc/source/installation/network_policy.rst @@ -0,0 +1,98 @@ +Enable network policy support functionality +=========================================== + +Please follow the next steps in order to enable the network policy support +feature: + +1. Enable the policy handler to response to network policy events. As this is + not enabled by default you'd have to explicitly add that to the list of + enabled handlers at kuryr.conf (further info on how to do this can be found + at :doc:`./devstack/containerized`):: + + [kubernetes] + enabled_handlers=vif,lb,lbaasspec,policy + +Note that you need to restart the kuryr controller after applying the above +detailed steps. For devstack non-containerized deployments:: + + $ sudo systemctl restart devstack@kuryr-kubernetes.service + + +Same for containerized deployments:: + + $ kubectl -n kube-system get pod | grep kuryr-controller + $ kubectl -n kube-system delete pod KURYR_CONTROLLER_POD_NAME + + +For directly enabling the driver when deploying with devstack, you just need +to add the policy handler with:: + + KURYR_ENABLED_HANDLERS=vif,lb,lbaasspec,policy + + +Testing the network policy support functionality +------------------------------------------------ + +1. Given a yaml file with a network policy, such as:: + + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: test-network-policy + namespace: default + spec: + podSelector: + matchLabels: + role: db + policyTypes: + - Ingress + - Egress + ingress: + - from: + - ipBlock: + cidr: 172.17.0.0/16 + except: + - 172.17.1.0/24 + - namespaceSelector: + matchLabels: + project: myproject + - podSelector: + matchLabels: + role: frontend + ports: + - protocol: TCP + port: 6379 + egress: + - to: + - ipBlock: + cidr: 10.0.0.0/24 + ports: + - protocol: TCP + port: 5978 + +2. Apply the network policy:: + + $ kubectl apply -f network_policy.yml + +3. Check that the resources has been created:: + + $ kubectl get kuryrnetpolicies + NAME AGE + np-test-network-policy 2s + + $ kubectl get networkpolicies + NAME POD-SELECTOR AGE + test-network-policy role=db 2s + + $ openstack security group list | grep test-network-policy + | dabdf308-7eed-43ef-a058-af84d1954acb | test-network-policy + +4. Check that the teardown of the resources once the network policy is removed:: + + $ kubectl delete -f network_policy.yml + + $ kubectl get kuryrnetpolicies + + $ kubectl get networkpolicies + + $ openstack security group list | grep test-network-policy diff --git a/kubernetes_crds/kuryrnetpolicy.yaml b/kubernetes_crds/kuryrnetpolicy.yaml new file mode 100644 index 000000000..018ed6b53 --- /dev/null +++ b/kubernetes_crds/kuryrnetpolicy.yaml @@ -0,0 +1,14 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: kuryrnetpolicies.openstack.org +spec: + group: openstack.org + version: v1 + scope: Namespaced + names: + plural: kuryrnetpolicies + singular: kuryrnetpolicy + kind: KuryrNetPolicy + shortNames: + - knp diff --git a/kuryr_kubernetes/constants.py b/kuryr_kubernetes/constants.py index 304ef9428..532f5525a 100644 --- a/kuryr_kubernetes/constants.py +++ b/kuryr_kubernetes/constants.py @@ -16,6 +16,7 @@ K8S_API_BASE = '/api/v1' K8S_API_NAMESPACES = K8S_API_BASE + '/namespaces' K8S_API_CRD = '/apis/openstack.org/v1' +K8S_API_CRD_NAMESPACES = K8S_API_CRD + '/namespaces' K8S_API_POLICIES = '/apis/networking.k8s.io/v1/networkpolicies' K8S_API_NPWG_CRD = '/apis/k8s.cni.cncf.io/v1' @@ -27,6 +28,7 @@ K8S_OBJ_ENDPOINTS = 'Endpoints' K8S_OBJ_POLICY = 'NetworkPolicy' K8S_OBJ_KURYRNET = 'KuryrNet' +K8S_OBJ_KURYRNETPOLICY = 'KuryrNetPolicy' K8S_POD_STATUS_PENDING = 'Pending' @@ -35,6 +37,7 @@ K8S_ANNOTATION_VIF = K8S_ANNOTATION_PREFIX + '-vif' 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' +K8S_ANNOTATION_NETPOLICY_CRD = K8S_ANNOTATION_PREFIX + '-netpolicy-crd' K8S_ANNOTATION_LBAAS_RT_STATE = K8S_ANNOTATION_PREFIX + '-lbaas-route-state' K8S_ANNOTATION_LBAAS_RT_NOTIF = K8S_ANNOTATION_PREFIX + '-lbaas-route-notif' K8S_ANNOTATION_ROUTE_STATE = K8S_ANNOTATION_PREFIX + '-route-state' diff --git a/kuryr_kubernetes/controller/drivers/network_policy.py b/kuryr_kubernetes/controller/drivers/network_policy.py index 26fa49830..85dd03dc4 100644 --- a/kuryr_kubernetes/controller/drivers/network_policy.py +++ b/kuryr_kubernetes/controller/drivers/network_policy.py @@ -15,7 +15,12 @@ from oslo_log import log as logging +from neutronclient.common import exceptions as n_exc + +from kuryr_kubernetes import clients +from kuryr_kubernetes import constants from kuryr_kubernetes.controller.drivers import base +from kuryr_kubernetes import exceptions LOG = logging.getLogger(__name__) @@ -24,7 +29,103 @@ class NetworkPolicyDriver(base.NetworkPolicyDriver): """Provides security groups actions based on K8s Network Policies""" def ensure_network_policy(self, policy, project_id): - pass + neutron = clients.get_neutron_client() + LOG.debug("Creating network policy %s" % policy['metadata']['name']) + if self._get_kuryrnetpolicy_crd(policy): + LOG.debug("Already existing CRD") + return + security_group_body = { + "security_group": + { + "name": policy['metadata']['name'], + "project_id": project_id + } + } + try: + sg = neutron.create_security_group(body=security_group_body) + except n_exc.NeutronClientException: + LOG.exception("Error creating security group for network policy. ") + raise + try: + self._add_kuryrnetpolicy_crd(policy, project_id, + sg['security_group']['id']) + except exceptions.K8sClientException: + LOG.exception("Rolling back security groups") + neutron.delete_security_group(sg['security_group']['id']) + raise def release_network_policy(self, policy, project_id): - pass + neutron = clients.get_neutron_client() + netpolicy_crd = self._get_kuryrnetpolicy_crd(policy) + if netpolicy_crd is not None: + try: + sg_id = netpolicy_crd['spec']['securityGroupId'] + neutron.delete_security_group(sg_id) + except n_exc.NotFound: + LOG.debug("Security Group not found: %s", sg_id) + except n_exc.NeutronClientException: + LOG.exception("Error deleting security group %s.", sg_id) + raise + self._del_kuryrnetpolicy_crd( + netpolicy_crd['metadata']['name'], + netpolicy_crd['metadata']['namespace']) + + def _get_kuryrnetpolicy_crd(self, policy): + kubernetes = clients.get_kubernetes_client() + netpolicy_crd_name = "np-" + policy['metadata']['name'] + netpolicy_crd_namespace = policy['metadata']['namespace'] + try: + netpolicy_crd = kubernetes.get('{}/{}/kuryrnetpolicies/{}'.format( + constants.K8S_API_CRD_NAMESPACES, netpolicy_crd_namespace, + netpolicy_crd_name)) + except exceptions.K8sResourceNotFound: + return None + except exceptions.K8sClientException: + LOG.exception("Kubernetes Client Exception.") + raise + return netpolicy_crd + + def _add_kuryrnetpolicy_crd(self, policy, project_id, sg_id): + kubernetes = clients.get_kubernetes_client() + netpolicy_crd_name = "np-" + policy['metadata']['name'] + netpolicy_crd_namespace = policy['metadata']['namespace'] + netpolicy_crd = { + 'apiVersion': 'openstack.org/v1', + 'kind': constants.K8S_OBJ_KURYRNETPOLICY, + 'metadata': { + 'name': netpolicy_crd_name, + 'namespace': netpolicy_crd_namespace, + 'annotations': { + 'policy': policy + } + }, + 'spec': { + 'securityGroupName': policy['metadata']['name'], + 'securityGroupId': sg_id, + }, + } + try: + LOG.debug("Creating KuryrNetPolicy CRD %s" % netpolicy_crd) + kubernetes_post = '{}/{}/kuryrnetpolicies'.format( + constants.K8S_API_CRD_NAMESPACES, + netpolicy_crd_namespace) + kubernetes.post(kubernetes_post, netpolicy_crd) + except exceptions.K8sClientException: + LOG.exception("Kubernetes Client Exception creating kuryrnetpolicy" + " CRD. %s" % exceptions.K8sClientException) + raise + return netpolicy_crd + + def _del_kuryrnetpolicy_crd(self, netpolicy_crd_name, + netpolicy_crd_namespace): + kubernetes = clients.get_kubernetes_client() + try: + LOG.debug("Deleting KuryrNetPolicy CRD %s" % netpolicy_crd_name) + kubernetes.delete('{}/{}/kuryrnetpolicies/{}'.format( + constants.K8S_API_CRD_NAMESPACES, + netpolicy_crd_namespace, + netpolicy_crd_name)) + except exceptions.K8sClientException: + LOG.exception("Kubernetes Client Exception deleting kuryrnetpolicy" + " CRD.") + raise diff --git a/kuryr_kubernetes/k8s_client.py b/kuryr_kubernetes/k8s_client.py index 5d3ce393c..de555624f 100644 --- a/kuryr_kubernetes/k8s_client.py +++ b/kuryr_kubernetes/k8s_client.py @@ -76,6 +76,8 @@ class K8sClient(object): response = requests.get(url, cert=self.cert, verify=self.verify_server, headers=header) + if response.status_code == requests.codes.not_found: + raise exc.K8sResourceNotFound(response.text) if not response.ok: raise exc.K8sClientException(response.text) result = response.json() if json else response.text