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