1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3# 4# Copyright: (c) 2018, F5 Networks Inc. 5# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10DOCUMENTATION = r''' 11--- 12module: bigip_firewall_global_rules 13short_description: Manage AFM global rule settings on BIG-IP 14description: 15 - Configures the global network firewall rules on AFM (Advanced Firewall Manager). 16 These firewall rules are applied to all packets except those going through 17 the management interface. They are applied first, before any firewall rules 18 for the packet's virtual server, route domain, and/or self IP address. 19version_added: "1.0.0" 20options: 21 enforced_policy: 22 description: 23 - Specifies an enforced firewall policy. 24 - C(enforced_policy) rules are enforced globally. 25 type: str 26 service_policy: 27 description: 28 - Specifies a service policy that would apply to traffic globally. 29 - The service policy is applied to all flows, provided there are 30 no other context specific service policy configurations that 31 override the global service policy. For example, when a service 32 policy is configured both at a global level and on a 33 firewall rule, and a flow matches the rule, the more specific 34 service policy configuration in the rule will override the service 35 policy setting at the global level. 36 - The service policy associated here can be created using the 37 C(bigip_service_policy) module. 38 type: str 39 staged_policy: 40 description: 41 - Specifies a staged firewall policy. 42 - C(staged_policy) rules are not enforced while all the visibility 43 aspects (statistics, reporting, and logging) function as if 44 the staged-policy rules were enforced globally. 45 type: str 46 description: 47 description: 48 - Description for the global list of firewall rules. 49 type: str 50extends_documentation_fragment: f5networks.f5_modules.f5 51author: 52 - Tim Rupp (@caphrim007) 53''' 54 55EXAMPLES = r''' 56- name: Change enforced policy in AFM global rules 57 bigip_firewall_global_rules: 58 enforced_policy: enforcing1 59 provider: 60 password: secret 61 server: lb.mydomain.com 62 user: admin 63 delegate_to: localhost 64''' 65 66RETURN = r''' 67enforced_policy: 68 description: The new global Enforced Policy. 69 returned: changed 70 type: str 71 sample: /Common/enforced1 72service_policy: 73 description: The new global Service Policy. 74 returned: changed 75 type: str 76 sample: /Common/service1 77staged_policy: 78 description: The new global Staged Policy. 79 returned: changed 80 type: str 81 sample: /Common/staged1 82description: 83 description: The new description. 84 returned: changed 85 type: str 86 sample: My description 87''' 88from datetime import datetime 89 90from ansible.module_utils.basic import AnsibleModule 91 92from ..module_utils.bigip import F5RestClient 93from ..module_utils.common import ( 94 F5ModuleError, AnsibleF5Parameters, f5_argument_spec, fq_name 95) 96from ..module_utils.compare import cmp_str_with_none 97from ..module_utils.icontrol import tmos_version 98from ..module_utils.teem import send_teem 99 100 101class Parameters(AnsibleF5Parameters): 102 api_map = { 103 'enforcedPolicy': 'enforced_policy', 104 'servicePolicy': 'service_policy', 105 'stagedPolicy': 'staged_policy', 106 } 107 108 api_attributes = [ 109 'enforcedPolicy', 110 'servicePolicy', 111 'stagedPolicy', 112 'description', 113 ] 114 115 returnables = [ 116 'enforced_policy', 117 'service_policy', 118 'staged_policy', 119 'description', 120 ] 121 122 updatables = [ 123 'enforced_policy', 124 'service_policy', 125 'staged_policy', 126 'description', 127 ] 128 129 130class ApiParameters(Parameters): 131 @property 132 def description(self): 133 if self._values['description'] in [None, 'none']: 134 return None 135 return self._values['description'] 136 137 138class ModuleParameters(Parameters): 139 @property 140 def enforced_policy(self): 141 if self._values['enforced_policy'] is None: 142 return None 143 if self._values['enforced_policy'] in ['', 'none']: 144 return '' 145 return fq_name(self.partition, self._values['enforced_policy']) 146 147 @property 148 def service_policy(self): 149 if self._values['service_policy'] is None: 150 return None 151 if self._values['service_policy'] in ['', 'none']: 152 return '' 153 return fq_name(self.partition, self._values['service_policy']) 154 155 @property 156 def staged_policy(self): 157 if self._values['staged_policy'] is None: 158 return None 159 if self._values['staged_policy'] in ['', 'none']: 160 return '' 161 return fq_name(self.partition, self._values['staged_policy']) 162 163 @property 164 def description(self): 165 if self._values['description'] is None: 166 return None 167 elif self._values['description'] in ['none', '']: 168 return '' 169 return self._values['description'] 170 171 172class Changes(Parameters): 173 def to_return(self): 174 result = {} 175 try: 176 for returnable in self.returnables: 177 result[returnable] = getattr(self, returnable) 178 result = self._filter_params(result) 179 except Exception: 180 raise 181 return result 182 183 184class UsableChanges(Changes): 185 pass 186 187 188class ReportableChanges(Changes): 189 pass 190 191 192class Difference(object): 193 def __init__(self, want, have=None): 194 self.want = want 195 self.have = have 196 197 def compare(self, param): 198 try: 199 result = getattr(self, param) 200 return result 201 except AttributeError: 202 return self.__default(param) 203 204 def __default(self, param): 205 attr1 = getattr(self.want, param) 206 try: 207 attr2 = getattr(self.have, param) 208 if attr1 != attr2: 209 return attr1 210 except AttributeError: 211 return attr1 212 213 @property 214 def description(self): 215 return cmp_str_with_none(self.want.description, self.have.description) 216 217 @property 218 def enforced_policy(self): 219 return cmp_str_with_none(self.want.enforced_policy, self.have.enforced_policy) 220 221 @property 222 def staged_policy(self): 223 return cmp_str_with_none(self.want.staged_policy, self.have.staged_policy) 224 225 @property 226 def service_policy(self): 227 return cmp_str_with_none(self.want.service_policy, self.have.service_policy) 228 229 230class ModuleManager(object): 231 def __init__(self, *args, **kwargs): 232 self.module = kwargs.get('module', None) 233 self.client = F5RestClient(**self.module.params) 234 self.want = ModuleParameters(params=self.module.params) 235 self.have = ApiParameters() 236 self.changes = UsableChanges() 237 238 def _set_changed_options(self): 239 changed = {} 240 for key in Parameters.returnables: 241 if getattr(self.want, key) is not None: 242 changed[key] = getattr(self.want, key) 243 if changed: 244 self.changes = UsableChanges(params=changed) 245 246 def _update_changed_options(self): 247 diff = Difference(self.want, self.have) 248 updatables = Parameters.updatables 249 changed = dict() 250 for k in updatables: 251 change = diff.compare(k) 252 if change is None: 253 continue 254 else: 255 if isinstance(change, dict): 256 changed.update(change) 257 else: 258 changed[k] = change 259 if changed: 260 self.changes = UsableChanges(params=changed) 261 return True 262 return False 263 264 def should_update(self): 265 result = self._update_changed_options() 266 if result: 267 return True 268 return False 269 270 def exec_module(self): 271 start = datetime.now().isoformat() 272 version = tmos_version(self.client) 273 result = dict() 274 275 changed = self.present() 276 277 reportable = ReportableChanges(params=self.changes.to_return()) 278 changes = reportable.to_return() 279 result.update(**changes) 280 result.update(dict(changed=changed)) 281 self._announce_deprecations(result) 282 send_teem(start, self.client, self.module, version) 283 return result 284 285 def _announce_deprecations(self, result): 286 warnings = result.pop('__warnings', []) 287 for warning in warnings: 288 self.client.module.deprecate( 289 msg=warning['msg'], 290 version=warning['version'] 291 ) 292 293 def present(self): 294 return self.update() 295 296 def update(self): 297 self.have = self.read_current_from_device() 298 if not self.should_update(): 299 return False 300 if self.module.check_mode: 301 return True 302 self.update_on_device() 303 return True 304 305 def update_on_device(self): 306 params = self.changes.api_params() 307 uri = "https://{0}:{1}/mgmt/tm/security/firewall/global-rules".format( 308 self.client.provider['server'], 309 self.client.provider['server_port'] 310 ) 311 resp = self.client.api.patch(uri, json=params) 312 try: 313 response = resp.json() 314 except ValueError as ex: 315 raise F5ModuleError(str(ex)) 316 317 if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]: 318 return True 319 raise F5ModuleError(resp.content) 320 321 def read_current_from_device(self): 322 uri = "https://{0}:{1}/mgmt/tm/security/firewall/global-rules".format( 323 self.client.provider['server'], 324 self.client.provider['server_port'] 325 ) 326 resp = self.client.api.get(uri) 327 try: 328 response = resp.json() 329 except ValueError as ex: 330 raise F5ModuleError(str(ex)) 331 332 if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]: 333 return ApiParameters(params=response) 334 raise F5ModuleError(resp.content) 335 336 337class ArgumentSpec(object): 338 def __init__(self): 339 self.supports_check_mode = True 340 argument_spec = dict( 341 enforced_policy=dict(), 342 service_policy=dict(), 343 staged_policy=dict(), 344 description=dict(), 345 ) 346 self.argument_spec = {} 347 self.argument_spec.update(f5_argument_spec) 348 self.argument_spec.update(argument_spec) 349 350 351def main(): 352 spec = ArgumentSpec() 353 354 module = AnsibleModule( 355 argument_spec=spec.argument_spec, 356 supports_check_mode=spec.supports_check_mode, 357 ) 358 359 try: 360 mm = ModuleManager(module=module) 361 results = mm.exec_module() 362 module.exit_json(**results) 363 except F5ModuleError as ex: 364 module.fail_json(msg=str(ex)) 365 366 367if __name__ == '__main__': 368 main() 369