1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3# This file is part of Ansible
4#
5# Ansible is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# Ansible is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
17
18from __future__ import absolute_import, division, print_function
19__metaclass__ = type
20
21DOCUMENTATION = '''
22---
23module: oneandone_firewall_policy
24short_description: Configure 1&1 firewall policy.
25description:
26     - Create, remove, reconfigure, update firewall policies.
27       This module has a dependency on 1and1 >= 1.0
28options:
29  state:
30    description:
31      - Define a firewall policy state to create, remove, or update.
32    required: false
33    type: str
34    default: 'present'
35    choices: [ "present", "absent", "update" ]
36  auth_token:
37    description:
38      - Authenticating API token provided by 1&1.
39    type: str
40  api_url:
41    description:
42      - Custom API URL. Overrides the
43        ONEANDONE_API_URL environment variable.
44    type: str
45    required: false
46  name:
47    description:
48      - Firewall policy name used with present state. Used as identifier (id or name) when used with absent state.
49        maxLength=128
50    type: str
51  firewall_policy:
52    description:
53      - The identifier (id or name) of the firewall policy used with update state.
54    type: str
55  rules:
56    description:
57      - A list of rules that will be set for the firewall policy.
58        Each rule must contain protocol parameter, in addition to three optional parameters
59        (port_from, port_to, and source)
60    type: list
61    elements: dict
62  add_server_ips:
63    description:
64      - A list of server identifiers (id or name) to be assigned to a firewall policy.
65        Used in combination with update state.
66    type: list
67    elements: str
68    required: false
69  remove_server_ips:
70    description:
71      - A list of server IP ids to be unassigned from a firewall policy. Used in combination with update state.
72    type: list
73    elements: str
74    required: false
75  add_rules:
76    description:
77      - A list of rules that will be added to an existing firewall policy.
78        It is syntax is the same as the one used for rules parameter. Used in combination with update state.
79    type: list
80    elements: dict
81    required: false
82  remove_rules:
83    description:
84      - A list of rule ids that will be removed from an existing firewall policy. Used in combination with update state.
85    type: list
86    elements: str
87    required: false
88  description:
89    description:
90      - Firewall policy description. maxLength=256
91    type: str
92    required: false
93  wait:
94    description:
95      - wait for the instance to be in state 'running' before returning
96    required: false
97    default: "yes"
98    type: bool
99  wait_timeout:
100    description:
101      - how long before wait gives up, in seconds
102    type: int
103    default: 600
104  wait_interval:
105    description:
106      - Defines the number of seconds to wait when using the _wait_for methods
107    type: int
108    default: 5
109
110requirements:
111  - "1and1"
112  - "python >= 2.6"
113
114author:
115  -  "Amel Ajdinovic (@aajdinov)"
116  -  "Ethan Devenport (@edevenport)"
117'''
118
119EXAMPLES = '''
120- name: Create a firewall policy
121  community.general.oneandone_firewall_policy:
122    auth_token: oneandone_private_api_key
123    name: ansible-firewall-policy
124    description: Testing creation of firewall policies with ansible
125    rules:
126     -
127       protocol: TCP
128       port_from: 80
129       port_to: 80
130       source: 0.0.0.0
131    wait: true
132    wait_timeout: 500
133
134- name: Destroy a firewall policy
135  community.general.oneandone_firewall_policy:
136    auth_token: oneandone_private_api_key
137    state: absent
138    name: ansible-firewall-policy
139
140- name: Update a firewall policy
141  community.general.oneandone_firewall_policy:
142    auth_token: oneandone_private_api_key
143    state: update
144    firewall_policy: ansible-firewall-policy
145    name: ansible-firewall-policy-updated
146    description: Testing creation of firewall policies with ansible - updated
147
148- name: Add server to a firewall policy
149  community.general.oneandone_firewall_policy:
150    auth_token: oneandone_private_api_key
151    firewall_policy: ansible-firewall-policy-updated
152    add_server_ips:
153     - server_identifier (id or name)
154     - server_identifier #2 (id or name)
155    wait: true
156    wait_timeout: 500
157    state: update
158
159- name: Remove server from a firewall policy
160  community.general.oneandone_firewall_policy:
161    auth_token: oneandone_private_api_key
162    firewall_policy: ansible-firewall-policy-updated
163    remove_server_ips:
164     - B2504878540DBC5F7634EB00A07C1EBD (server's IP id)
165    wait: true
166    wait_timeout: 500
167    state: update
168
169- name: Add rules to a firewall policy
170  community.general.oneandone_firewall_policy:
171    auth_token: oneandone_private_api_key
172    firewall_policy: ansible-firewall-policy-updated
173    description: Adding rules to an existing firewall policy
174    add_rules:
175     -
176       protocol: TCP
177       port_from: 70
178       port_to: 70
179       source: 0.0.0.0
180     -
181       protocol: TCP
182       port_from: 60
183       port_to: 60
184       source: 0.0.0.0
185    wait: true
186    wait_timeout: 500
187    state: update
188
189- name: Remove rules from a firewall policy
190  community.general.oneandone_firewall_policy:
191    auth_token: oneandone_private_api_key
192    firewall_policy: ansible-firewall-policy-updated
193    remove_rules:
194     - rule_id #1
195     - rule_id #2
196     - ...
197    wait: true
198    wait_timeout: 500
199    state: update
200'''
201
202RETURN = '''
203firewall_policy:
204    description: Information about the firewall policy that was processed
205    type: dict
206    sample: '{"id": "92B74394A397ECC3359825C1656D67A6", "name": "Default Policy"}'
207    returned: always
208'''
209
210import os
211from ansible.module_utils.basic import AnsibleModule
212from ansible_collections.community.general.plugins.module_utils.oneandone import (
213    get_firewall_policy,
214    get_server,
215    OneAndOneResources,
216    wait_for_resource_creation_completion
217)
218
219HAS_ONEANDONE_SDK = True
220
221try:
222    import oneandone.client
223except ImportError:
224    HAS_ONEANDONE_SDK = False
225
226
227def _check_mode(module, result):
228    if module.check_mode:
229        module.exit_json(
230            changed=result
231        )
232
233
234def _add_server_ips(module, oneandone_conn, firewall_id, server_ids):
235    """
236    Assigns servers to a firewall policy.
237    """
238    try:
239        attach_servers = []
240
241        for _server_id in server_ids:
242            server = get_server(oneandone_conn, _server_id, True)
243            attach_server = oneandone.client.AttachServer(
244                server_id=server['id'],
245                server_ip_id=next(iter(server['ips'] or []), None)['id']
246            )
247            attach_servers.append(attach_server)
248
249        if module.check_mode:
250            if attach_servers:
251                return True
252            return False
253
254        firewall_policy = oneandone_conn.attach_server_firewall_policy(
255            firewall_id=firewall_id,
256            server_ips=attach_servers)
257        return firewall_policy
258    except Exception as e:
259        module.fail_json(msg=str(e))
260
261
262def _remove_firewall_server(module, oneandone_conn, firewall_id, server_ip_id):
263    """
264    Unassigns a server/IP from a firewall policy.
265    """
266    try:
267        if module.check_mode:
268            firewall_server = oneandone_conn.get_firewall_server(
269                firewall_id=firewall_id,
270                server_ip_id=server_ip_id)
271            if firewall_server:
272                return True
273            return False
274
275        firewall_policy = oneandone_conn.remove_firewall_server(
276            firewall_id=firewall_id,
277            server_ip_id=server_ip_id)
278        return firewall_policy
279    except Exception as e:
280        module.fail_json(msg=str(e))
281
282
283def _add_firewall_rules(module, oneandone_conn, firewall_id, rules):
284    """
285    Adds new rules to a firewall policy.
286    """
287    try:
288        firewall_rules = []
289
290        for rule in rules:
291            firewall_rule = oneandone.client.FirewallPolicyRule(
292                protocol=rule['protocol'],
293                port_from=rule['port_from'],
294                port_to=rule['port_to'],
295                source=rule['source'])
296            firewall_rules.append(firewall_rule)
297
298        if module.check_mode:
299            firewall_policy_id = get_firewall_policy(oneandone_conn, firewall_id)
300            if (firewall_rules and firewall_policy_id):
301                return True
302            return False
303
304        firewall_policy = oneandone_conn.add_firewall_policy_rule(
305            firewall_id=firewall_id,
306            firewall_policy_rules=firewall_rules
307        )
308        return firewall_policy
309    except Exception as e:
310        module.fail_json(msg=str(e))
311
312
313def _remove_firewall_rule(module, oneandone_conn, firewall_id, rule_id):
314    """
315    Removes a rule from a firewall policy.
316    """
317    try:
318        if module.check_mode:
319            rule = oneandone_conn.get_firewall_policy_rule(
320                firewall_id=firewall_id,
321                rule_id=rule_id)
322            if rule:
323                return True
324            return False
325
326        firewall_policy = oneandone_conn.remove_firewall_rule(
327            firewall_id=firewall_id,
328            rule_id=rule_id
329        )
330        return firewall_policy
331    except Exception as e:
332        module.fail_json(msg=str(e))
333
334
335def update_firewall_policy(module, oneandone_conn):
336    """
337    Updates a firewall policy based on input arguments.
338    Firewall rules and server ips can be added/removed to/from
339    firewall policy. Firewall policy name and description can be
340    updated as well.
341
342    module : AnsibleModule object
343    oneandone_conn: authenticated oneandone object
344    """
345    try:
346        firewall_policy_id = module.params.get('firewall_policy')
347        name = module.params.get('name')
348        description = module.params.get('description')
349        add_server_ips = module.params.get('add_server_ips')
350        remove_server_ips = module.params.get('remove_server_ips')
351        add_rules = module.params.get('add_rules')
352        remove_rules = module.params.get('remove_rules')
353
354        changed = False
355
356        firewall_policy = get_firewall_policy(oneandone_conn, firewall_policy_id, True)
357        if firewall_policy is None:
358            _check_mode(module, False)
359
360        if name or description:
361            _check_mode(module, True)
362            firewall_policy = oneandone_conn.modify_firewall(
363                firewall_id=firewall_policy['id'],
364                name=name,
365                description=description)
366            changed = True
367
368        if add_server_ips:
369            if module.check_mode:
370                _check_mode(module, _add_server_ips(module,
371                                                    oneandone_conn,
372                                                    firewall_policy['id'],
373                                                    add_server_ips))
374
375            firewall_policy = _add_server_ips(module, oneandone_conn, firewall_policy['id'], add_server_ips)
376            changed = True
377
378        if remove_server_ips:
379            chk_changed = False
380            for server_ip_id in remove_server_ips:
381                if module.check_mode:
382                    chk_changed |= _remove_firewall_server(module,
383                                                           oneandone_conn,
384                                                           firewall_policy['id'],
385                                                           server_ip_id)
386
387                _remove_firewall_server(module,
388                                        oneandone_conn,
389                                        firewall_policy['id'],
390                                        server_ip_id)
391            _check_mode(module, chk_changed)
392            firewall_policy = get_firewall_policy(oneandone_conn, firewall_policy['id'], True)
393            changed = True
394
395        if add_rules:
396            firewall_policy = _add_firewall_rules(module,
397                                                  oneandone_conn,
398                                                  firewall_policy['id'],
399                                                  add_rules)
400            _check_mode(module, firewall_policy)
401            changed = True
402
403        if remove_rules:
404            chk_changed = False
405            for rule_id in remove_rules:
406                if module.check_mode:
407                    chk_changed |= _remove_firewall_rule(module,
408                                                         oneandone_conn,
409                                                         firewall_policy['id'],
410                                                         rule_id)
411
412                _remove_firewall_rule(module,
413                                      oneandone_conn,
414                                      firewall_policy['id'],
415                                      rule_id)
416            _check_mode(module, chk_changed)
417            firewall_policy = get_firewall_policy(oneandone_conn, firewall_policy['id'], True)
418            changed = True
419
420        return (changed, firewall_policy)
421    except Exception as e:
422        module.fail_json(msg=str(e))
423
424
425def create_firewall_policy(module, oneandone_conn):
426    """
427    Create a new firewall policy.
428
429    module : AnsibleModule object
430    oneandone_conn: authenticated oneandone object
431    """
432    try:
433        name = module.params.get('name')
434        description = module.params.get('description')
435        rules = module.params.get('rules')
436        wait = module.params.get('wait')
437        wait_timeout = module.params.get('wait_timeout')
438        wait_interval = module.params.get('wait_interval')
439
440        firewall_rules = []
441
442        for rule in rules:
443            firewall_rule = oneandone.client.FirewallPolicyRule(
444                protocol=rule['protocol'],
445                port_from=rule['port_from'],
446                port_to=rule['port_to'],
447                source=rule['source'])
448            firewall_rules.append(firewall_rule)
449
450        firewall_policy_obj = oneandone.client.FirewallPolicy(
451            name=name,
452            description=description
453        )
454
455        _check_mode(module, True)
456        firewall_policy = oneandone_conn.create_firewall_policy(
457            firewall_policy=firewall_policy_obj,
458            firewall_policy_rules=firewall_rules
459        )
460
461        if wait:
462            wait_for_resource_creation_completion(
463                oneandone_conn,
464                OneAndOneResources.firewall_policy,
465                firewall_policy['id'],
466                wait_timeout,
467                wait_interval)
468
469        firewall_policy = get_firewall_policy(oneandone_conn, firewall_policy['id'], True)  # refresh
470        changed = True if firewall_policy else False
471
472        _check_mode(module, False)
473
474        return (changed, firewall_policy)
475    except Exception as e:
476        module.fail_json(msg=str(e))
477
478
479def remove_firewall_policy(module, oneandone_conn):
480    """
481    Removes a firewall policy.
482
483    module : AnsibleModule object
484    oneandone_conn: authenticated oneandone object
485    """
486    try:
487        fp_id = module.params.get('name')
488        firewall_policy_id = get_firewall_policy(oneandone_conn, fp_id)
489        if module.check_mode:
490            if firewall_policy_id is None:
491                _check_mode(module, False)
492            _check_mode(module, True)
493        firewall_policy = oneandone_conn.delete_firewall(firewall_policy_id)
494
495        changed = True if firewall_policy else False
496
497        return (changed, {
498            'id': firewall_policy['id'],
499            'name': firewall_policy['name']
500        })
501    except Exception as e:
502        module.fail_json(msg=str(e))
503
504
505def main():
506    module = AnsibleModule(
507        argument_spec=dict(
508            auth_token=dict(
509                type='str', no_log=True,
510                default=os.environ.get('ONEANDONE_AUTH_TOKEN')),
511            api_url=dict(
512                type='str',
513                default=os.environ.get('ONEANDONE_API_URL')),
514            name=dict(type='str'),
515            firewall_policy=dict(type='str'),
516            description=dict(type='str'),
517            rules=dict(type='list', elements="dict", default=[]),
518            add_server_ips=dict(type='list', elements="str", default=[]),
519            remove_server_ips=dict(type='list', elements="str", default=[]),
520            add_rules=dict(type='list', elements="dict", default=[]),
521            remove_rules=dict(type='list', elements="str", default=[]),
522            wait=dict(type='bool', default=True),
523            wait_timeout=dict(type='int', default=600),
524            wait_interval=dict(type='int', default=5),
525            state=dict(type='str', default='present', choices=['present', 'absent', 'update']),
526        ),
527        supports_check_mode=True
528    )
529
530    if not HAS_ONEANDONE_SDK:
531        module.fail_json(msg='1and1 required for this module')
532
533    if not module.params.get('auth_token'):
534        module.fail_json(
535            msg='The "auth_token" parameter or ' +
536            'ONEANDONE_AUTH_TOKEN environment variable is required.')
537
538    if not module.params.get('api_url'):
539        oneandone_conn = oneandone.client.OneAndOneService(
540            api_token=module.params.get('auth_token'))
541    else:
542        oneandone_conn = oneandone.client.OneAndOneService(
543            api_token=module.params.get('auth_token'), api_url=module.params.get('api_url'))
544
545    state = module.params.get('state')
546
547    if state == 'absent':
548        if not module.params.get('name'):
549            module.fail_json(
550                msg="'name' parameter is required to delete a firewall policy.")
551        try:
552            (changed, firewall_policy) = remove_firewall_policy(module, oneandone_conn)
553        except Exception as e:
554            module.fail_json(msg=str(e))
555
556    elif state == 'update':
557        if not module.params.get('firewall_policy'):
558            module.fail_json(
559                msg="'firewall_policy' parameter is required to update a firewall policy.")
560        try:
561            (changed, firewall_policy) = update_firewall_policy(module, oneandone_conn)
562        except Exception as e:
563            module.fail_json(msg=str(e))
564
565    elif state == 'present':
566        for param in ('name', 'rules'):
567            if not module.params.get(param):
568                module.fail_json(
569                    msg="%s parameter is required for new firewall policies." % param)
570        try:
571            (changed, firewall_policy) = create_firewall_policy(module, oneandone_conn)
572        except Exception as e:
573            module.fail_json(msg=str(e))
574
575    module.exit_json(changed=changed, firewall_policy=firewall_policy)
576
577
578if __name__ == '__main__':
579    main()
580