diff --git a/example/kubernetes/k8s_qinling_role.yaml b/example/kubernetes/k8s_qinling_role.yaml index 99887df8..94cd8756 100644 --- a/example/kubernetes/k8s_qinling_role.yaml +++ b/example/kubernetes/k8s_qinling_role.yaml @@ -58,6 +58,9 @@ rules: - apiGroups: ["extensions"] resources: ["replicasets"] verbs: ["deletecollection"] +- apiGroups: ["extensions"] + resources: ["networkpolicies"] + verbs: ["list", "get", "create", "delete"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 diff --git a/qinling/orchestrator/kubernetes/manager.py b/qinling/orchestrator/kubernetes/manager.py index fe3900c4..0be46b74 100644 --- a/qinling/orchestrator/kubernetes/manager.py +++ b/qinling/orchestrator/kubernetes/manager.py @@ -49,6 +49,9 @@ class KubernetesManager(base.OrchestratorBase): # Create namespace if not exists self._ensure_namespace() + # Create the network policy if not exists + self._ensure_network_policy() + # Get templates. template_loader = jinja2.FileSystemLoader( searchpath=os.path.dirname(TEMPLATES_DIR) @@ -87,6 +90,29 @@ class KubernetesManager(base.OrchestratorBase): LOG.info('Namespace %s created.', self.conf.kubernetes.namespace) + def _ensure_network_policy(self): + policy_name = 'disable-interpods-connections' + namespace = self.conf.kubernetes.namespace + ret = self.v1extension.list_namespaced_network_policy(namespace) + policies = [i.metadata.name for i in ret.items] + + if policy_name not in policies: + LOG.info('Creating network policy %s in namespace %s', + policy_name, namespace) + + policy_body = { + 'apiVersion': 'extensions/v1beta1', + 'kind': 'NetworkPolicy', + 'metadata': {'name': policy_name}, + 'spec': {'pod_selector': {}} + } + + self.v1extension.create_namespaced_network_policy( + namespace, policy_body) + + LOG.info('Network policy %s in namespace %s created.', + policy_name, namespace) + @tenacity.retry( wait=tenacity.wait_fixed(2), stop=tenacity.stop_after_delay(600), diff --git a/qinling/tests/unit/orchestrator/kubernetes/test_manager.py b/qinling/tests/unit/orchestrator/kubernetes/test_manager.py index 25d7f164..f4a6219d 100644 --- a/qinling/tests/unit/orchestrator/kubernetes/test_manager.py +++ b/qinling/tests/unit/orchestrator/kubernetes/test_manager.py @@ -60,6 +60,14 @@ class TestKubernetesManager(base.DbTestCase): namespaces.items = [namespace] self.k8s_v1_api.list_namespace.return_value = namespaces + network_policy = mock.Mock() + network_policy.metadata.name = 'disable-interpods-connections' + network_policies = mock.Mock() + network_policies.items = [network_policy] + self.k8s_v1_ext.list_namespaced_network_policy.return_value = ( + network_policies + ) + self.manager = k8s_manager.KubernetesManager(self.conf, self.qinling_endpoint) @@ -131,6 +139,43 @@ class TestKubernetesManager(base.DbTestCase): self.assertEqual(2, self.k8s_v1_api.list_namespace.call_count) self.k8s_v1_api.create_namespace.assert_not_called() + def test__ensure_network_policy(self): + # self.manager is not used in this test. + network_policies = mock.Mock() + network_policies.items = [] + v1ext = self.k8s_v1_ext + v1ext.list_namespaced_network_policy.return_value = network_policies + + k8s_manager.KubernetesManager(self.conf, self.qinling_endpoint) + + network_policy_body = { + 'apiVersion': 'extensions/v1beta1', + 'kind': 'NetworkPolicy', + 'metadata': {'name': 'disable-interpods-connections'}, + 'spec': {'pod_selector': {}} + } + v1ext.list_namespaced_network_policy.assert_called_with( + self.fake_namespace + ) + v1ext.create_namespaced_network_policy.assert_called_once_with( + self.fake_namespace, network_policy_body) + + def test__ensure_network_policy_not_create(self): + # self.manager is not used in this test. + item = mock.Mock() + item.metadata.name = 'disable-interpods-connections' + network_policies = mock.Mock() + network_policies.items = [item] + v1ext = self.k8s_v1_ext + v1ext.list_namespaced_network_policy.return_value = network_policies + + k8s_manager.KubernetesManager(self.conf, self.qinling_endpoint) + + v1ext.list_namespaced_network_policy.assert_called_with( + self.fake_namespace + ) + v1ext.create_namespaced_network_policy.assert_not_called() + def test_create_pool(self): ret = mock.Mock() ret.status.replicas = 5 diff --git a/releasenotes/notes/isolate-k8s-pods-617fec5dc5fbd2d8.yaml b/releasenotes/notes/isolate-k8s-pods-617fec5dc5fbd2d8.yaml new file mode 100644 index 00000000..c3a79411 --- /dev/null +++ b/releasenotes/notes/isolate-k8s-pods-617fec5dc5fbd2d8.yaml @@ -0,0 +1,8 @@ +--- +security: + - | + When using Kubernetes as the orchestrator, Qinling will create Kubernetes + pods to run executions of functions. In Kubernetes, pods are non-isolated + unless the NetworkPolicy is configured and enforced. In Qinling, we create + NetworkPolicy to disable the communication between pods and the traffic + from outside the cluster.