Firewall services for OpenStack Neutron.
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.

464 lines
20 KiB

  1. # Copyright 2013 Big Switch Networks, 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 neutron.common import rpc as n_rpc
  16. from neutron.common import utils as n_utils
  17. from neutron_lib.api.definitions import firewall as fw_ext
  18. from neutron_lib import constants as nl_constants
  19. from neutron_lib import context as neutron_context
  20. from neutron_lib.exceptions import firewall_v1 as f_exc
  21. from neutron_lib.plugins import constants as plugin_constants
  22. from neutron_lib.plugins import directory
  23. from oslo_config import cfg
  24. from oslo_log import log as logging
  25. import oslo_messaging
  26. from neutron_fwaas.common import fwaas_constants as f_const
  27. from neutron_fwaas.db.firewall import firewall_db
  28. from neutron_fwaas.db.firewall import firewall_router_insertion_db
  29. LOG = logging.getLogger(__name__)
  30. class FirewallCallbacks(object):
  31. target = oslo_messaging.Target(version='1.0')
  32. def __init__(self, plugin):
  33. super(FirewallCallbacks, self).__init__()
  34. self.plugin = plugin
  35. def set_firewall_status(self, context, firewall_id, status, **kwargs):
  36. """Agent uses this to set a firewall's status."""
  37. LOG.debug("Setting firewall %s to status: %s", firewall_id, status)
  38. # Sanitize status first
  39. if status in (nl_constants.ACTIVE, nl_constants.DOWN,
  40. nl_constants.INACTIVE):
  41. to_update = status
  42. else:
  43. to_update = nl_constants.ERROR
  44. # ignore changing status if firewall expects to be deleted
  45. # That case means that while some pending operation has been
  46. # performed on the backend, neutron server received delete request
  47. # and changed firewall status to PENDING_DELETE
  48. updated = self.plugin.update_firewall_status(
  49. context, firewall_id, to_update,
  50. not_in=(nl_constants.PENDING_DELETE,))
  51. if updated:
  52. LOG.debug("firewall %s status set: %s", firewall_id, to_update)
  53. return updated and to_update != nl_constants.ERROR
  54. def firewall_deleted(self, context, firewall_id, **kwargs):
  55. """Agent uses this to indicate firewall is deleted."""
  56. LOG.debug("firewall_deleted() called")
  57. try:
  58. with context.session.begin(subtransactions=True):
  59. fw_db = self.plugin._get_firewall(context, firewall_id)
  60. # allow to delete firewalls in ERROR state
  61. if fw_db.status in (nl_constants.PENDING_DELETE,
  62. nl_constants.ERROR):
  63. self.plugin.delete_db_firewall_object(context, firewall_id)
  64. return True
  65. else:
  66. LOG.warning('Firewall %(fw)s unexpectedly deleted by '
  67. 'agent, status was %(status)s',
  68. {'fw': firewall_id, 'status': fw_db.status})
  69. fw_db.update({"status": nl_constants.ERROR})
  70. return False
  71. except f_exc.FirewallNotFound:
  72. LOG.info('Firewall %s already deleted', firewall_id)
  73. return True
  74. def get_firewalls_for_tenant(self, context, **kwargs):
  75. """Agent uses this to get all firewalls and rules for a tenant."""
  76. LOG.debug("get_firewalls_for_tenant() called")
  77. fw_list = []
  78. for fw in self.plugin.get_firewalls(context):
  79. fw_with_rules = self.plugin._make_firewall_dict_with_rules(
  80. context, fw['id'])
  81. if fw['status'] == nl_constants.PENDING_DELETE:
  82. fw_with_rules['add-router-ids'] = []
  83. fw_with_rules['del-router-ids'] = fw['router_ids']
  84. else:
  85. fw_with_rules['add-router-ids'] = fw['router_ids']
  86. fw_with_rules['del-router-ids'] = []
  87. fw_list.append(fw_with_rules)
  88. return fw_list
  89. def get_tenants_with_firewalls(self, context, **kwargs):
  90. """Agent uses this to get all tenants that have firewalls."""
  91. LOG.debug("get_tenants_with_firewalls() called")
  92. host = kwargs['host']
  93. ctx = neutron_context.get_admin_context()
  94. tenant_ids = self.plugin.get_firewall_tenant_ids_on_host(ctx, host)
  95. return tenant_ids
  96. class FirewallAgentApi(object):
  97. """Plugin side of plugin to agent RPC API."""
  98. def __init__(self, topic, host):
  99. self.host = host
  100. target = oslo_messaging.Target(topic=topic, version='1.0')
  101. self.client = n_rpc.get_client(target)
  102. def _prepare_rpc_client(self, host=None):
  103. if host:
  104. return self.client.prepare(server=host)
  105. else:
  106. # historical behaviour (RPC broadcast)
  107. return self.client.prepare(fanout=True)
  108. def create_firewall(self, context, firewall, host=None):
  109. cctxt = self._prepare_rpc_client(host)
  110. # TODO(blallau) host param is not used on agent side (to be removed)
  111. cctxt.cast(context, 'create_firewall', firewall=firewall,
  112. host=self.host)
  113. def update_firewall(self, context, firewall, host=None):
  114. cctxt = self._prepare_rpc_client(host)
  115. # TODO(blallau) host param is not used on agent side (to be removed)
  116. cctxt.cast(context, 'update_firewall', firewall=firewall,
  117. host=self.host)
  118. def delete_firewall(self, context, firewall, host=None):
  119. cctxt = self._prepare_rpc_client(host)
  120. # TODO(blallau) host param is not used on agent side (to be removed)
  121. cctxt.cast(context, 'delete_firewall', firewall=firewall,
  122. host=self.host)
  123. class FirewallPlugin(
  124. firewall_db.Firewall_db_mixin,
  125. firewall_router_insertion_db.FirewallRouterInsertionDbMixin):
  126. """Implementation of the Neutron Firewall Service Plugin.
  127. This class manages the workflow of FWaaS request/response.
  128. Most DB related works are implemented in class
  129. firewall_db.Firewall_db_mixin.
  130. """
  131. supported_extension_aliases = ["fwaas", "fwaasrouterinsertion"]
  132. path_prefix = fw_ext.API_PREFIX
  133. def __init__(self):
  134. """Do the initialization for the firewall service plugin here."""
  135. self.start_rpc_listeners()
  136. self.agent_rpc = FirewallAgentApi(
  137. f_const.FW_AGENT,
  138. cfg.CONF.host
  139. )
  140. firewall_db.subscribe()
  141. def start_rpc_listeners(self):
  142. self.endpoints = [FirewallCallbacks(self)]
  143. self.conn = n_rpc.create_connection()
  144. self.conn.create_consumer(
  145. f_const.FIREWALL_PLUGIN, self.endpoints, fanout=False)
  146. return self.conn.consume_in_threads()
  147. def _check_dvr_extensions(self, l3plugin):
  148. return (
  149. n_utils.is_extension_supported(
  150. l3plugin, nl_constants.L3_AGENT_SCHEDULER_EXT_ALIAS) and
  151. n_utils.is_extension_supported(
  152. l3plugin, nl_constants.L3_DISTRIBUTED_EXT_ALIAS) and
  153. getattr(l3plugin, '_get_dvr_hosts_for_router', False))
  154. def _get_hosts_to_notify(self, context, router_ids):
  155. """Returns all hosts to send notification about firewall update"""
  156. l3_plugin = directory.get_plugin(plugin_constants.L3)
  157. no_broadcast = (
  158. n_utils.is_extension_supported(
  159. l3_plugin, nl_constants.L3_AGENT_SCHEDULER_EXT_ALIAS) and
  160. getattr(l3_plugin, 'get_l3_agents_hosting_routers', False))
  161. scheduled_hosts = set()
  162. if no_broadcast:
  163. # This call checks for all scheduled routers to the network node
  164. agents = l3_plugin.get_l3_agents_hosting_routers(
  165. context, router_ids, admin_state_up=True, active=True)
  166. scheduled_hosts = set([a.host for a in agents])
  167. # Now check for unscheduled DVR router on distributed compute hosts
  168. unscheduled_dvr_hosts = set()
  169. dvr_broadcast = self._check_dvr_extensions(l3_plugin)
  170. if (dvr_broadcast):
  171. for router_id in router_ids:
  172. hosts = set(l3_plugin._get_dvr_hosts_for_router(
  173. context, router_id))
  174. unscheduled_dvr_hosts |= hosts
  175. if no_broadcast or dvr_broadcast:
  176. scheduled_hosts = scheduled_hosts.union(unscheduled_dvr_hosts)
  177. return scheduled_hosts
  178. # NOTE(blallau): default: FirewallAgentAPI performs RPC broadcast
  179. return [None]
  180. def _rpc_update_firewall(self, context, firewall_id):
  181. status_update = {"firewall": {"status": nl_constants.PENDING_UPDATE}}
  182. super(FirewallPlugin, self).update_firewall(context, firewall_id,
  183. status_update)
  184. fw_with_rules = self._make_firewall_dict_with_rules(context,
  185. firewall_id)
  186. # this is triggered on an update to fw rule or policy, no
  187. # change in associated routers.
  188. fw_update_rtrs = self.get_firewall_routers(context, firewall_id)
  189. fw_with_rules['add-router-ids'] = fw_update_rtrs
  190. fw_with_rules['del-router-ids'] = []
  191. hosts = self._get_hosts_to_notify(context, fw_update_rtrs)
  192. for host in hosts:
  193. self.agent_rpc.update_firewall(context, fw_with_rules,
  194. host=host)
  195. def _rpc_update_firewall_policy(self, context, firewall_policy_id):
  196. firewall_policy = self.get_firewall_policy(context, firewall_policy_id)
  197. if firewall_policy:
  198. for firewall_id in firewall_policy['firewall_list']:
  199. self._rpc_update_firewall(context, firewall_id)
  200. def _ensure_update_firewall(self, context, firewall_id):
  201. fwall = self.get_firewall(context, firewall_id)
  202. if fwall['status'] in [nl_constants.PENDING_CREATE,
  203. nl_constants.PENDING_UPDATE,
  204. nl_constants.PENDING_DELETE]:
  205. raise f_exc.FirewallInPendingState(firewall_id=firewall_id,
  206. pending_state=fwall['status'])
  207. def _ensure_update_firewall_policy(self, context, firewall_policy_id):
  208. firewall_policy = self.get_firewall_policy(context, firewall_policy_id)
  209. if firewall_policy and 'firewall_list' in firewall_policy:
  210. for firewall_id in firewall_policy['firewall_list']:
  211. self._ensure_update_firewall(context, firewall_id)
  212. def _ensure_update_firewall_rule(self, context, firewall_rule_id):
  213. fw_rule = self.get_firewall_rule(context, firewall_rule_id)
  214. if 'firewall_policy_id' in fw_rule and fw_rule['firewall_policy_id']:
  215. self._ensure_update_firewall_policy(context,
  216. fw_rule['firewall_policy_id'])
  217. def _get_routers_for_create_firewall(self, tenant_id, context, firewall):
  218. # pop router_id as this goes in the router association db
  219. # and not firewall db
  220. router_ids = firewall['firewall'].pop('router_ids', None)
  221. if router_ids == nl_constants.ATTR_NOT_SPECIFIED:
  222. # old semantics router-ids keyword not specified pick up
  223. # all routers on tenant.
  224. l3_plugin = directory.get_plugin(plugin_constants.L3)
  225. ctx = neutron_context.get_admin_context()
  226. routers = l3_plugin.get_routers(ctx)
  227. router_ids = [
  228. router['id']
  229. for router in routers
  230. if router['tenant_id'] == tenant_id]
  231. # validation can still fail this if there is another fw
  232. # which is associated with one of these routers.
  233. self.validate_firewall_routers_not_in_use(context, router_ids)
  234. return router_ids
  235. else:
  236. if not router_ids:
  237. # This indicates that user specifies no routers.
  238. return []
  239. else:
  240. # some router(s) provided.
  241. self.validate_firewall_routers_not_in_use(context, router_ids)
  242. return router_ids
  243. def create_firewall(self, context, firewall):
  244. LOG.debug("create_firewall() called")
  245. fw_new_rtrs = self._get_routers_for_create_firewall(
  246. firewall['firewall']['tenant_id'], context, firewall)
  247. if not fw_new_rtrs:
  248. # no messaging to agent needed, and fw needs to go
  249. # to INACTIVE(no associated rtrs) state.
  250. status = nl_constants.INACTIVE
  251. fw = super(FirewallPlugin, self).create_firewall(
  252. context, firewall, status)
  253. fw['router_ids'] = []
  254. return fw
  255. else:
  256. fw = super(FirewallPlugin, self).create_firewall(
  257. context, firewall)
  258. fw['router_ids'] = fw_new_rtrs
  259. fw_with_rules = (
  260. self._make_firewall_dict_with_rules(context, fw['id']))
  261. fw_with_rtrs = {'fw_id': fw['id'],
  262. 'router_ids': fw_new_rtrs}
  263. self.set_routers_for_firewall(context, fw_with_rtrs)
  264. fw_with_rules['add-router-ids'] = fw_new_rtrs
  265. fw_with_rules['del-router-ids'] = []
  266. hosts = self._get_hosts_to_notify(context, fw_new_rtrs)
  267. for host in hosts:
  268. self.agent_rpc.create_firewall(context, fw_with_rules,
  269. host=host)
  270. return fw
  271. def update_firewall(self, context, id, firewall):
  272. LOG.debug("update_firewall() called on firewall %s", id)
  273. self._ensure_update_firewall(context, id)
  274. # pop router_id as this goes in the router association db
  275. # and not firewall db
  276. router_ids = firewall['firewall'].pop('router_ids', None)
  277. fw_current_rtrs = fw_new_rtrs = self.get_firewall_routers(
  278. context, id)
  279. if router_ids is not None:
  280. if router_ids == []:
  281. # This indicates that user is indicating no routers.
  282. fw_new_rtrs = []
  283. else:
  284. self.validate_firewall_routers_not_in_use(
  285. context, router_ids, id)
  286. fw_new_rtrs = router_ids
  287. self.update_firewall_routers(context, {'fw_id': id,
  288. 'router_ids': fw_new_rtrs})
  289. if not fw_new_rtrs and not fw_current_rtrs:
  290. # no messaging to agent needed, and we need to continue
  291. # in INACTIVE state
  292. firewall['firewall']['status'] = nl_constants.INACTIVE
  293. fw = super(FirewallPlugin, self).update_firewall(
  294. context, id, firewall)
  295. fw['router_ids'] = []
  296. return fw
  297. else:
  298. firewall['firewall']['status'] = nl_constants.PENDING_UPDATE
  299. fw = super(FirewallPlugin, self).update_firewall(
  300. context, id, firewall)
  301. fw['router_ids'] = fw_new_rtrs
  302. fw_with_rules = (
  303. self._make_firewall_dict_with_rules(context, fw['id']))
  304. # determine rtrs to add fw to and del from
  305. fw_with_rules['add-router-ids'] = fw_new_rtrs
  306. fw_with_rules['del-router-ids'] = list(
  307. set(fw_current_rtrs).difference(set(fw_new_rtrs)))
  308. # last-router drives agent to ack with status to set state to INACTIVE
  309. fw_with_rules['last-router'] = not fw_new_rtrs
  310. LOG.debug("update_firewall %s: Add Routers: %s, Del Routers: %s",
  311. fw['id'],
  312. fw_with_rules['add-router-ids'],
  313. fw_with_rules['del-router-ids'])
  314. hosts = self._get_hosts_to_notify(context, list(
  315. set(fw_new_rtrs).union(set(fw_current_rtrs))))
  316. for host in hosts:
  317. self.agent_rpc.update_firewall(context, fw_with_rules,
  318. host=host)
  319. return fw
  320. def delete_db_firewall_object(self, context, id):
  321. super(FirewallPlugin, self).delete_firewall(context, id)
  322. def delete_firewall(self, context, id):
  323. LOG.debug("delete_firewall() called on firewall %s", id)
  324. fw_with_rules = (
  325. self._make_firewall_dict_with_rules(context, id))
  326. fw_delete_rtrs = self.get_firewall_routers(context, id)
  327. fw_with_rules['del-router-ids'] = fw_delete_rtrs
  328. fw_with_rules['add-router-ids'] = []
  329. if not fw_with_rules['del-router-ids']:
  330. # no routers to delete on the agent side
  331. self.delete_db_firewall_object(context, id)
  332. else:
  333. status = {"firewall": {"status": nl_constants.PENDING_DELETE}}
  334. super(FirewallPlugin, self).update_firewall(context, id, status)
  335. # Reflect state change in fw_with_rules
  336. fw_with_rules['status'] = status['firewall']['status']
  337. hosts = self._get_hosts_to_notify(context, fw_delete_rtrs)
  338. if hosts:
  339. for host in hosts:
  340. self.agent_rpc.delete_firewall(context, fw_with_rules,
  341. host=host)
  342. else:
  343. # NOTE(blallau): we directly delete the firewall
  344. # if router is not associated to an agent
  345. self.delete_db_firewall_object(context, id)
  346. def update_firewall_policy(self, context, id, firewall_policy):
  347. LOG.debug("update_firewall_policy() called")
  348. self._ensure_update_firewall_policy(context, id)
  349. fwp = super(FirewallPlugin,
  350. self).update_firewall_policy(context, id, firewall_policy)
  351. self._rpc_update_firewall_policy(context, id)
  352. return fwp
  353. def update_firewall_rule(self, context, id, firewall_rule):
  354. LOG.debug("update_firewall_rule() called")
  355. self._ensure_update_firewall_rule(context, id)
  356. fwr = super(FirewallPlugin,
  357. self).update_firewall_rule(context, id, firewall_rule)
  358. firewall_policy_id = fwr['firewall_policy_id']
  359. if firewall_policy_id:
  360. self._rpc_update_firewall_policy(context, firewall_policy_id)
  361. return fwr
  362. def _notify_firewall_updates(self, context, resource, update_info):
  363. notifier = n_rpc.get_notifier('network')
  364. notifier.info(context, resource, update_info)
  365. def insert_rule(self, context, id, rule_info):
  366. LOG.debug("insert_rule() called")
  367. self._ensure_update_firewall_policy(context, id)
  368. fwp = super(FirewallPlugin,
  369. self).insert_rule(context, id, rule_info)
  370. self._rpc_update_firewall_policy(context, id)
  371. resource = 'firewall_policy.update.insert_rule'
  372. self._notify_firewall_updates(context, resource, rule_info)
  373. return fwp
  374. def remove_rule(self, context, id, rule_info):
  375. LOG.debug("remove_rule() called")
  376. self._ensure_update_firewall_policy(context, id)
  377. fwp = super(FirewallPlugin,
  378. self).remove_rule(context, id, rule_info)
  379. self._rpc_update_firewall_policy(context, id)
  380. resource = 'firewall_policy.update.remove_rule'
  381. self._notify_firewall_updates(context, resource, rule_info)
  382. return fwp
  383. def get_firewalls(self, context, filters=None, fields=None):
  384. LOG.debug("fwaas get_firewalls() called")
  385. has_id_field = not fields or 'id' in fields
  386. if not has_id_field:
  387. fields = fields + ['id']
  388. fw_list = super(FirewallPlugin, self).get_firewalls(
  389. context, filters, fields)
  390. if not fields or 'router_ids' in fields:
  391. for fw in fw_list:
  392. fw['router_ids'] = self.get_firewall_routers(context, fw['id'])
  393. if not has_id_field:
  394. for fw in fw_list:
  395. del fw['id']
  396. return fw_list
  397. def get_firewall(self, context, id, fields=None):
  398. LOG.debug("fwaas get_firewall() called")
  399. res = super(FirewallPlugin, self).get_firewall(
  400. context, id, fields)
  401. fw_current_rtrs = self.get_firewall_routers(context, id)
  402. res['router_ids'] = fw_current_rtrs
  403. return res