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.

network_policy_security_groups.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. # Copyright 2018 Red Hat, Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from kuryr_kubernetes import clients
  15. from kuryr_kubernetes import config
  16. from kuryr_kubernetes import constants
  17. from kuryr_kubernetes.controller.drivers import base
  18. from kuryr_kubernetes.controller.drivers import utils as driver_utils
  19. from kuryr_kubernetes import exceptions
  20. from oslo_config import cfg
  21. from oslo_log import log as logging
  22. LOG = logging.getLogger(__name__)
  23. OPERATORS_WITH_VALUES = [constants.K8S_OPERATOR_IN,
  24. constants.K8S_OPERATOR_NOT_IN]
  25. def _get_kuryrnetpolicy_crds(namespace=None):
  26. kubernetes = clients.get_kubernetes_client()
  27. try:
  28. if namespace:
  29. knp_path = '{}/{}/kuryrnetpolicies'.format(
  30. constants.K8S_API_CRD_NAMESPACES, namespace)
  31. else:
  32. knp_path = constants.K8S_API_CRD_KURYRNETPOLICIES
  33. LOG.debug("K8s API Query %s", knp_path)
  34. knps = kubernetes.get(knp_path)
  35. LOG.debug("Return Kuryr Network Policies with label %s", knps)
  36. except exceptions.K8sResourceNotFound:
  37. LOG.exception("KuryrNetPolicy CRD not found")
  38. raise
  39. except exceptions.K8sClientException:
  40. LOG.exception("Kubernetes Client Exception")
  41. raise
  42. return knps
  43. def _match_expressions(expressions, labels):
  44. for exp in expressions:
  45. exp_op = exp['operator'].lower()
  46. if labels:
  47. if exp_op in OPERATORS_WITH_VALUES:
  48. exp_values = exp['values']
  49. label_value = labels.get(str(exp['key']), None)
  50. if exp_op == constants.K8S_OPERATOR_IN:
  51. if label_value is None or label_value not in exp_values:
  52. return False
  53. elif exp_op == constants.K8S_OPERATOR_NOT_IN:
  54. if label_value in exp_values:
  55. return False
  56. else:
  57. if exp_op == constants.K8S_OPERATOR_EXISTS:
  58. exists = labels.get(str(exp['key']), None)
  59. if exists is None:
  60. return False
  61. elif exp_op == constants.K8S_OPERATOR_DOES_NOT_EXIST:
  62. exists = labels.get(str(exp['key']), None)
  63. if exists is not None:
  64. return False
  65. else:
  66. if exp_op in (constants.K8S_OPERATOR_IN,
  67. constants.K8S_OPERATOR_EXISTS):
  68. return False
  69. return True
  70. def _match_labels(crd_labels, labels):
  71. for crd_key, crd_value in crd_labels.items():
  72. label_value = labels.get(crd_key, None)
  73. if not label_value or crd_value != label_value:
  74. return False
  75. return True
  76. def _match_selector(selector, labels):
  77. crd_labels = selector.get('matchLabels', None)
  78. crd_expressions = selector.get('matchExpressions', None)
  79. match_exp = match_lb = True
  80. if crd_expressions:
  81. match_exp = _match_expressions(crd_expressions,
  82. labels)
  83. if crd_labels and labels:
  84. match_lb = _match_labels(crd_labels, labels)
  85. return match_exp and match_lb
  86. def _get_namespace_labels(namespace):
  87. kubernetes = clients.get_kubernetes_client()
  88. try:
  89. path = '{}/{}'.format(
  90. constants.K8S_API_NAMESPACES, namespace)
  91. LOG.debug("K8s API Query %s", path)
  92. namespaces = kubernetes.get(path)
  93. LOG.debug("Return Namespace: %s", namespaces)
  94. except exceptions.K8sResourceNotFound:
  95. LOG.exception("Namespace not found")
  96. raise
  97. except exceptions.K8sClientException:
  98. LOG.exception("Kubernetes Client Exception")
  99. raise
  100. return namespaces['metadata'].get('labels')
  101. def _create_sg_rules(crd, pod, namespace_selector, pod_selector,
  102. rule_block, crd_rules, direction,
  103. matched):
  104. pod_labels = pod['metadata'].get('labels')
  105. # NOTE (maysams) No need to differentiate between podSelector
  106. # with empty value or with '{}', as they have same result in here.
  107. if (pod_selector and
  108. _match_selector(pod_selector, pod_labels)):
  109. matched = True
  110. pod_ip = driver_utils.get_pod_ip(pod)
  111. sg_id = crd['spec']['securityGroupId']
  112. if 'ports' in rule_block:
  113. for port in rule_block['ports']:
  114. sg_rule = driver_utils.create_security_group_rule_body(
  115. sg_id, direction, port.get('port'),
  116. protocol=port.get('protocol'), cidr=pod_ip)
  117. sgr_id = driver_utils.create_security_group_rule(sg_rule)
  118. sg_rule['security_group_rule']['id'] = sgr_id
  119. crd_rules.append(sg_rule)
  120. else:
  121. sg_rule = driver_utils.create_security_group_rule_body(
  122. sg_id, direction,
  123. port_range_min=1,
  124. port_range_max=65535,
  125. cidr=pod_ip)
  126. sgr_id = driver_utils.create_security_group_rule(sg_rule)
  127. sg_rule['security_group_rule']['id'] = sgr_id
  128. crd_rules.append(sg_rule)
  129. return matched
  130. def _parse_rules(direction, crd, pod):
  131. policy = crd['spec']['networkpolicy_spec']
  132. pod_namespace = pod['metadata']['namespace']
  133. pod_namespace_labels = _get_namespace_labels(pod_namespace)
  134. policy_namespace = crd['metadata']['namespace']
  135. rule_direction = 'from'
  136. crd_rules = crd['spec'].get('ingressSgRules')
  137. if direction == 'egress':
  138. rule_direction = 'to'
  139. crd_rules = crd['spec'].get('egressSgRules')
  140. matched = False
  141. rule_list = policy.get(direction, None)
  142. for rule_block in rule_list:
  143. for rule in rule_block.get(rule_direction, []):
  144. namespace_selector = rule.get('namespaceSelector')
  145. pod_selector = rule.get('podSelector')
  146. if namespace_selector == {}:
  147. if _create_sg_rules(crd, pod, namespace_selector,
  148. pod_selector, rule_block, crd_rules,
  149. direction, matched):
  150. matched = True
  151. elif namespace_selector:
  152. if (pod_namespace_labels and
  153. _match_selector(namespace_selector,
  154. pod_namespace_labels)):
  155. if _create_sg_rules(crd, pod, namespace_selector,
  156. pod_selector, rule_block, crd_rules,
  157. direction, matched):
  158. matched = True
  159. else:
  160. if pod_namespace == policy_namespace:
  161. if _create_sg_rules(crd, pod, namespace_selector,
  162. pod_selector, rule_block, crd_rules,
  163. direction, matched):
  164. matched = True
  165. return matched, crd_rules
  166. def _get_pod_sgs(pod, project_id):
  167. sg_list = []
  168. pod_labels = pod['metadata'].get('labels')
  169. pod_namespace = pod['metadata']['namespace']
  170. knp_crds = _get_kuryrnetpolicy_crds(namespace=pod_namespace)
  171. for crd in knp_crds.get('items'):
  172. pod_selector = crd['spec'].get('podSelector')
  173. if pod_selector:
  174. if _match_selector(pod_selector, pod_labels):
  175. LOG.debug("Appending %s",
  176. str(crd['spec']['securityGroupId']))
  177. sg_list.append(str(crd['spec']['securityGroupId']))
  178. else:
  179. LOG.debug("Appending %s", str(crd['spec']['securityGroupId']))
  180. sg_list.append(str(crd['spec']['securityGroupId']))
  181. # NOTE(maysams) Pods that are not selected by any Networkpolicy
  182. # are fully accessible. Thus, the default security group is associated.
  183. if not sg_list:
  184. sg_list = config.CONF.neutron_defaults.pod_security_groups
  185. if not sg_list:
  186. raise cfg.RequiredOptError('pod_security_groups',
  187. cfg.OptGroup('neutron_defaults'))
  188. return sg_list[:]
  189. class NetworkPolicySecurityGroupsDriver(base.PodSecurityGroupsDriver):
  190. """Provides security groups for pods based on network policies"""
  191. def get_security_groups(self, pod, project_id):
  192. return _get_pod_sgs(pod, project_id)
  193. def create_sg_rules(self, pod):
  194. LOG.debug("Creating sg rule for pod: %s", pod['metadata']['name'])
  195. knp_crds = _get_kuryrnetpolicy_crds()
  196. for crd in knp_crds.get('items'):
  197. crd_selector = crd['spec'].get('podSelector')
  198. i_matched, i_rules = _parse_rules('ingress', crd, pod)
  199. e_matched, e_rules = _parse_rules('egress', crd, pod)
  200. if i_matched or e_matched:
  201. driver_utils.patch_kuryr_crd(crd, i_rules,
  202. e_rules, crd_selector)
  203. def delete_sg_rules(self, pod):
  204. LOG.debug("Deleting sg rule for pod: %s", pod['metadata']['name'])
  205. pod_ip = driver_utils.get_pod_ip(pod)
  206. knp_crds = _get_kuryrnetpolicy_crds()
  207. for crd in knp_crds.get('items'):
  208. crd_selector = crd['spec'].get('podSelector')
  209. ingress_rule_list = crd['spec'].get('ingressSgRules')
  210. egress_rule_list = crd['spec'].get('egressSgRules')
  211. i_rules = []
  212. e_rules = []
  213. matched = False
  214. for i_rule in ingress_rule_list:
  215. LOG.debug("Parsing ingress rule: %r", i_rule)
  216. remote_ip_prefix = i_rule['security_group_rule'].get(
  217. 'remote_ip_prefix')
  218. if remote_ip_prefix and remote_ip_prefix == pod_ip:
  219. matched = True
  220. driver_utils.delete_security_group_rule(
  221. i_rule['security_group_rule']['id'])
  222. else:
  223. i_rules.append(i_rule)
  224. for e_rule in egress_rule_list:
  225. LOG.debug("Parsing egress rule: %r", e_rule)
  226. remote_ip_prefix = e_rule['security_group_rule'].get(
  227. 'remote_ip_prefix')
  228. if remote_ip_prefix and remote_ip_prefix == pod_ip:
  229. matched = True
  230. driver_utils.delete_security_group_rule(
  231. e_rule['security_group_rule']['id'])
  232. else:
  233. e_rules.append(e_rule)
  234. if matched:
  235. driver_utils.patch_kuryr_crd(crd, i_rules, e_rules,
  236. crd_selector)
  237. def update_sg_rules(self, pod):
  238. LOG.debug("Updating sg rule for pod: %s", pod['metadata']['name'])
  239. self.delete_sg_rules(pod)
  240. self.create_sg_rules(pod)
  241. def create_namespace_sg(self, namespace, project_id, crd_spec):
  242. LOG.debug("Security group driver does not create SGs for the "
  243. "namespaces.")
  244. return {}
  245. def delete_sg(self, sg_id):
  246. LOG.debug("Security group driver does not implement deleting "
  247. "SGs.")
  248. class NetworkPolicyServiceSecurityGroupsDriver(
  249. base.ServiceSecurityGroupsDriver):
  250. """Provides security groups for services based on network policies"""
  251. def get_security_groups(self, service, project_id):
  252. sg_list = []
  253. svc_namespace = service['metadata']['namespace']
  254. svc_selector = service['spec'].get('selector')
  255. # skip is no selector
  256. if svc_selector:
  257. # get affected pods by svc selector
  258. pods = driver_utils.get_pods({'selector': svc_selector},
  259. svc_namespace).get('items')
  260. # NOTE(ltomasbo): We assume all the pods pointed by a service
  261. # have the same labels, and the same policy will be applied to
  262. # all of them. Hence only considering the security groups applied
  263. # to the first one.
  264. if pods:
  265. return _get_pod_sgs(pods[0], project_id)
  266. return sg_list[:]