Kubernetes integration with OpenStack networking
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

namespace_security_groups.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. # Copyright (c) 2018 Red Hat, Inc.
  2. # All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. # not use this file except in compliance with the License. You may obtain
  6. # a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations
  14. # under the License.
  15. from kuryr.lib._i18n import _
  16. from oslo_config import cfg
  17. from oslo_log import log as logging
  18. from kuryr_kubernetes import clients
  19. from kuryr_kubernetes import config
  20. from kuryr_kubernetes import constants
  21. from kuryr_kubernetes.controller.drivers import base
  22. from kuryr_kubernetes.controller.drivers import utils
  23. from kuryr_kubernetes import exceptions
  24. from neutronclient.common import exceptions as n_exc
  25. LOG = logging.getLogger(__name__)
  26. namespace_sg_driver_opts = [
  27. cfg.StrOpt('sg_allow_from_namespaces',
  28. help=_("Default security group to allow traffic from the "
  29. "namespaces into the default namespace.")),
  30. cfg.StrOpt('sg_allow_from_default',
  31. help=_("Default security group to allow traffic from the "
  32. "default namespaces into the other namespaces."))
  33. ]
  34. cfg.CONF.register_opts(namespace_sg_driver_opts, "namespace_sg")
  35. DEFAULT_NAMESPACE = 'default'
  36. def _get_net_crd(namespace):
  37. kubernetes = clients.get_kubernetes_client()
  38. try:
  39. ns = kubernetes.get('%s/namespaces/%s' % (constants.K8S_API_BASE,
  40. namespace))
  41. except exceptions.K8sClientException:
  42. LOG.exception("Kubernetes Client Exception.")
  43. raise exceptions.ResourceNotReady(namespace)
  44. try:
  45. annotations = ns['metadata']['annotations']
  46. net_crd_name = annotations[constants.K8S_ANNOTATION_NET_CRD]
  47. except KeyError:
  48. LOG.exception("Namespace missing CRD annotations for selecting "
  49. "the corresponding security group.")
  50. raise exceptions.ResourceNotReady(namespace)
  51. try:
  52. net_crd = kubernetes.get('%s/kuryrnets/%s' % (constants.K8S_API_CRD,
  53. net_crd_name))
  54. except exceptions.K8sClientException:
  55. LOG.exception("Kubernetes Client Exception.")
  56. raise
  57. return net_crd
  58. def _create_sg_rule(sg_id, direction, cidr, port=None, namespace=None):
  59. if port:
  60. sg_rule = utils.create_security_group_rule_body(
  61. sg_id, direction, port.get('port'),
  62. protocol=port.get('protocol'), cidr=cidr, namespace=namespace)
  63. else:
  64. sg_rule = utils.create_security_group_rule_body(
  65. sg_id, direction, port_range_min=1,
  66. port_range_max=65535, cidr=cidr, namespace=namespace)
  67. sgr_id = utils.create_security_group_rule(sg_rule)
  68. sg_rule['security_group_rule']['id'] = sgr_id
  69. return sg_rule
  70. def _parse_rules(direction, crd, namespace):
  71. policy = crd['spec']['networkpolicy_spec']
  72. sg_id = crd['spec']['securityGroupId']
  73. ns_labels = namespace['metadata'].get('labels')
  74. ns_name = namespace['metadata'].get('name')
  75. ns_cidr = utils.get_namespace_subnet_cidr(namespace)
  76. rule_direction = 'from'
  77. crd_rules = crd['spec'].get('ingressSgRules')
  78. if direction == 'egress':
  79. rule_direction = 'to'
  80. crd_rules = crd['spec'].get('egressSgRules')
  81. matched = False
  82. rule_list = policy.get(direction, None)
  83. for rule_block in rule_list:
  84. for rule in rule_block.get(rule_direction, []):
  85. pod_selector = rule.get('podSelector')
  86. ns_selector = rule.get('namespaceSelector')
  87. if (ns_selector and ns_labels and
  88. utils.match_selector(ns_selector, ns_labels)):
  89. if pod_selector:
  90. pods = utils.get_pods(pod_selector, ns_name)
  91. for pod in pods.get('items'):
  92. pod_ip = utils.get_pod_ip(pod)
  93. if 'ports' in rule_block:
  94. for port in rule_block['ports']:
  95. matched = True
  96. crd_rules.append(_create_sg_rule(
  97. sg_id, direction, pod_ip, port=port,
  98. namespace=ns_name))
  99. else:
  100. matched = True
  101. crd_rules.append(_create_sg_rule(
  102. sg_id, direction, pod_ip,
  103. namespace=ns_name))
  104. else:
  105. if 'ports' in rule_block:
  106. for port in rule_block['ports']:
  107. matched = True
  108. crd_rules.append(_create_sg_rule(
  109. sg_id, direction, ns_cidr,
  110. port=port, namespace=ns_name))
  111. else:
  112. matched = True
  113. crd_rules.append(_create_sg_rule(
  114. sg_id, direction, ns_cidr, namespace=ns_name))
  115. return matched, crd_rules
  116. class NamespacePodSecurityGroupsDriver(base.PodSecurityGroupsDriver):
  117. """Provides security groups for Pod based on a configuration option."""
  118. def get_security_groups(self, pod, project_id):
  119. namespace = pod['metadata']['namespace']
  120. net_crd = _get_net_crd(namespace)
  121. sg_list = [str(net_crd['spec']['sgId'])]
  122. extra_sgs = self._get_extra_sg(namespace)
  123. for sg in extra_sgs:
  124. sg_list.append(str(sg))
  125. sg_list.extend(config.CONF.neutron_defaults.pod_security_groups)
  126. return sg_list[:]
  127. def _get_extra_sg(self, namespace):
  128. # Differentiates between default namespace and the rest
  129. if namespace == DEFAULT_NAMESPACE:
  130. return [cfg.CONF.namespace_sg.sg_allow_from_namespaces]
  131. else:
  132. return [cfg.CONF.namespace_sg.sg_allow_from_default]
  133. def create_namespace_sg(self, namespace, project_id, crd_spec):
  134. neutron = clients.get_neutron_client()
  135. sg_name = "ns/" + namespace + "-sg"
  136. # create the associated SG for the namespace
  137. try:
  138. # default namespace is different from the rest
  139. # Default allows traffic from everywhere
  140. # The rest can be accessed from the default one
  141. sg = neutron.create_security_group(
  142. {
  143. "security_group": {
  144. "name": sg_name,
  145. "project_id": project_id
  146. }
  147. }).get('security_group')
  148. neutron.create_security_group_rule(
  149. {
  150. "security_group_rule": {
  151. "direction": "ingress",
  152. "remote_ip_prefix": crd_spec['subnetCIDR'],
  153. "security_group_id": sg['id']
  154. }
  155. })
  156. except n_exc.NeutronClientException as ex:
  157. LOG.error("Error creating security group for the namespace "
  158. "%s: %s", namespace, ex)
  159. raise ex
  160. return {'sgId': sg['id']}
  161. def delete_sg(self, sg_id):
  162. neutron = clients.get_neutron_client()
  163. try:
  164. neutron.delete_security_group(sg_id)
  165. except n_exc.NotFound:
  166. LOG.debug("Security Group not found: %s", sg_id)
  167. except n_exc.NeutronClientException:
  168. LOG.exception("Error deleting security group %s.", sg_id)
  169. raise
  170. def delete_namespace_sg_rules(self, namespace):
  171. ns_name = namespace['metadata']['name']
  172. LOG.debug("Deleting sg rule for namespace: %s",
  173. ns_name)
  174. knp_crds = utils.get_kuryrnetpolicy_crds()
  175. for crd in knp_crds.get('items'):
  176. crd_selector = crd['spec'].get('podSelector')
  177. ingress_rule_list = crd['spec'].get('ingressSgRules')
  178. egress_rule_list = crd['spec'].get('egressSgRules')
  179. i_rules = []
  180. e_rules = []
  181. matched = False
  182. for i_rule in ingress_rule_list:
  183. LOG.debug("Parsing ingress rule: %r", i_rule)
  184. rule_namespace = i_rule.get('namespace', None)
  185. if rule_namespace and rule_namespace == ns_name:
  186. matched = True
  187. utils.delete_security_group_rule(
  188. i_rule['security_group_rule']['id'])
  189. else:
  190. i_rules.append(i_rule)
  191. for e_rule in egress_rule_list:
  192. LOG.debug("Parsing egress rule: %r", e_rule)
  193. rule_namespace = e_rule.get('namespace', None)
  194. if rule_namespace and rule_namespace == ns_name:
  195. matched = True
  196. utils.delete_security_group_rule(
  197. e_rule['security_group_rule']['id'])
  198. else:
  199. e_rules.append(e_rule)
  200. if matched:
  201. utils.patch_kuryr_crd(crd, i_rules, e_rules, crd_selector)
  202. def create_namespace_sg_rules(self, namespace):
  203. kubernetes = clients.get_kubernetes_client()
  204. ns_name = namespace['metadata']['name']
  205. LOG.debug("Creating sg rule for namespace: %s", ns_name)
  206. namespace = kubernetes.get(
  207. '{}/namespaces/{}'.format(constants.K8S_API_BASE, ns_name))
  208. knp_crds = utils.get_kuryrnetpolicy_crds()
  209. for crd in knp_crds.get('items'):
  210. crd_selector = crd['spec'].get('podSelector')
  211. i_matched, i_rules = _parse_rules('ingress', crd, namespace)
  212. e_matched, e_rules = _parse_rules('egress', crd, namespace)
  213. if i_matched or e_matched:
  214. utils.patch_kuryr_crd(crd, i_rules,
  215. e_rules, crd_selector)
  216. def update_namespace_sg_rules(self, namespace):
  217. LOG.debug("Updating sg rule for namespace: %s",
  218. namespace['metadata']['name'])
  219. self.delete_namespace_sg_rules(namespace)
  220. self.create_namespace_sg_rules(namespace)
  221. def create_sg_rules(self, pod):
  222. LOG.debug("Security group driver does not create SG rules for "
  223. "the pods.")
  224. def delete_sg_rules(self, pod):
  225. LOG.debug("Security group driver does not delete SG rules for "
  226. "the pods.")
  227. def update_sg_rules(self, pod):
  228. LOG.debug("Security group driver does not update SG rules for "
  229. "the pods.")
  230. class NamespaceServiceSecurityGroupsDriver(base.ServiceSecurityGroupsDriver):
  231. """Provides security groups for Service based on a configuration option."""
  232. def get_security_groups(self, service, project_id):
  233. namespace = service['metadata']['namespace']
  234. net_crd = _get_net_crd(namespace)
  235. sg_list = []
  236. sg_list.append(str(net_crd['spec']['sgId']))
  237. extra_sgs = self._get_extra_sg(namespace)
  238. for sg in extra_sgs:
  239. sg_list.append(str(sg))
  240. return sg_list[:]
  241. def _get_extra_sg(self, namespace):
  242. # Differentiates between default namespace and the rest
  243. if namespace == DEFAULT_NAMESPACE:
  244. return [cfg.CONF.namespace_sg.sg_allow_from_default]
  245. else:
  246. return [cfg.CONF.namespace_sg.sg_allow_from_namespaces]