1#!/usr/bin/python
2
3# Copyright: (c) 2015, VMware, Inc. All Rights Reserved.
4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5
6from __future__ import absolute_import, division, print_function
7__metaclass__ = type
8
9ANSIBLE_METADATA = {'metadata_version': '1.1',
10                    'status': ['preview'],
11                    'supported_by': 'community'}
12
13DOCUMENTATION = '''
14---
15module: vca_fw
16short_description: add remove firewall rules in a gateway  in a vca
17description:
18  - Adds or removes firewall rules from a gateway in a vca environment
19version_added: "2.0"
20author:
21- Peter Sprygada (@privateip)
22options:
23    fw_rules:
24      description:
25        - A list of firewall rules to be added to the gateway, Please see examples on valid entries
26      required: True
27      default: false
28extends_documentation_fragment: vca.documentation
29'''
30
31EXAMPLES = '''
32
33#Add a set of firewall rules
34
35- hosts: localhost
36  connection: local
37  tasks:
38   - vca_fw:
39       instance_id: 'b15ff1e5-1024-4f55-889f-ea0209726282'
40       vdc_name: 'benz_ansible'
41       state: 'absent'
42       fw_rules:
43         - description: "ben testing"
44           source_ip: "Any"
45           dest_ip: 192.0.2.23
46         - description: "ben testing 2"
47           source_ip: 192.0.2.50
48           source_port: "Any"
49           dest_port: "22"
50           dest_ip: 192.0.2.101
51           is_enable: "true"
52           enable_logging: "false"
53           protocol: "Tcp"
54           policy: "allow"
55
56'''
57
58try:
59    from pyvcloud.schema.vcd.v1_5.schemas.vcloud.networkType import FirewallRuleType
60    from pyvcloud.schema.vcd.v1_5.schemas.vcloud.networkType import ProtocolsType
61except ImportError:
62    # normally set a flag here but it will be caught when testing for
63    # the existence of pyvcloud (see module_utils/vca.py).  This just
64    # protects against generating an exception at runtime
65    pass
66
67from ansible.module_utils.basic import AnsibleModule
68from ansible.module_utils.vca import VcaError, vca_argument_spec, vca_login
69
70
71VALID_PROTO = ['Tcp', 'Udp', 'Icmp', 'Other', 'Any']
72VALID_RULE_KEYS = ['policy', 'is_enable', 'enable_logging', 'description',
73                   'dest_ip', 'dest_port', 'source_ip', 'source_port',
74                   'protocol']
75
76
77def protocol_to_tuple(protocol):
78    return (protocol.get_Tcp(),
79            protocol.get_Udp(),
80            protocol.get_Icmp(),
81            protocol.get_Other(),
82            protocol.get_Any())
83
84
85def protocol_to_string(protocol):
86    protocol = protocol_to_tuple(protocol)
87    if protocol[0] is True:
88        return 'Tcp'
89    elif protocol[1] is True:
90        return 'Udp'
91    elif protocol[2] is True:
92        return 'Icmp'
93    elif protocol[3] is True:
94        return 'Other'
95    elif protocol[4] is True:
96        return 'Any'
97
98
99def protocol_to_type(protocol):
100    try:
101        protocols = ProtocolsType()
102        setattr(protocols, protocol, True)
103        return protocols
104    except AttributeError:
105        raise VcaError("The value in protocol is not valid")
106
107
108def validate_fw_rules(fw_rules):
109    for rule in fw_rules:
110        for k in rule.keys():
111            if k not in VALID_RULE_KEYS:
112                raise VcaError("%s is not a valid key in fw rules, please "
113                               "check above.." % k, valid_keys=VALID_RULE_KEYS)
114
115        rule['dest_port'] = str(rule.get('dest_port', 'Any')).lower()
116        rule['dest_ip'] = rule.get('dest_ip', 'Any').lower()
117        rule['source_port'] = str(rule.get('source_port', 'Any')).lower()
118        rule['source_ip'] = rule.get('source_ip', 'Any').lower()
119        rule['protocol'] = rule.get('protocol', 'Any').lower()
120        rule['policy'] = rule.get('policy', 'allow').lower()
121        rule['is_enable'] = rule.get('is_enable', True)
122        rule['enable_logging'] = rule.get('enable_logging', False)
123        rule['description'] = rule.get('description', 'rule added by Ansible')
124
125    return fw_rules
126
127
128def fw_rules_to_dict(rules):
129    fw_rules = list()
130    for rule in rules:
131        fw_rules.append(
132            dict(
133                dest_port=rule.get_DestinationPortRange().lower(),
134                dest_ip=rule.get_DestinationIp().lower().lower(),
135                source_port=rule.get_SourcePortRange().lower(),
136                source_ip=rule.get_SourceIp().lower(),
137                protocol=protocol_to_string(rule.get_Protocols()).lower(),
138                policy=rule.get_Policy().lower(),
139                is_enable=rule.get_IsEnabled(),
140                enable_logging=rule.get_EnableLogging(),
141                description=rule.get_Description()
142            )
143        )
144    return fw_rules
145
146
147def create_fw_rule(is_enable, description, policy, protocol, dest_port,
148                   dest_ip, source_port, source_ip, enable_logging):
149
150    return FirewallRuleType(IsEnabled=is_enable,
151                            Description=description,
152                            Policy=policy,
153                            Protocols=protocol_to_type(protocol),
154                            DestinationPortRange=dest_port,
155                            DestinationIp=dest_ip,
156                            SourcePortRange=source_port,
157                            SourceIp=source_ip,
158                            EnableLogging=enable_logging)
159
160
161def main():
162    argument_spec = vca_argument_spec()
163    argument_spec.update(
164        dict(
165            fw_rules=dict(required=True, type='list'),
166            gateway_name=dict(default='gateway'),
167            state=dict(default='present', choices=['present', 'absent'])
168        )
169    )
170
171    module = AnsibleModule(argument_spec, supports_check_mode=True)
172
173    fw_rules = module.params.get('fw_rules')
174    gateway_name = module.params.get('gateway_name')
175    vdc_name = module.params['vdc_name']
176
177    vca = vca_login(module)
178
179    gateway = vca.get_gateway(vdc_name, gateway_name)
180    if not gateway:
181        module.fail_json(msg="Not able to find the gateway %s, please check "
182                             "the gateway_name param" % gateway_name)
183
184    fwservice = gateway._getFirewallService()
185
186    rules = gateway.get_fw_rules()
187    current_rules = fw_rules_to_dict(rules)
188
189    try:
190        desired_rules = validate_fw_rules(fw_rules)
191    except VcaError as e:
192        module.fail_json(msg=e.message)
193
194    result = dict(changed=False)
195    result['current_rules'] = current_rules
196    result['desired_rules'] = desired_rules
197
198    updates = list()
199    additions = list()
200    deletions = list()
201
202    for (index, rule) in enumerate(desired_rules):
203        try:
204            if rule != current_rules[index]:
205                updates.append((index, rule))
206        except IndexError:
207            additions.append(rule)
208
209    eol = len(current_rules) - len(desired_rules)
210    if eol > 0:
211        for rule in current_rules[eol:]:
212            deletions.append(rule)
213
214    for rule in additions:
215        if not module.check_mode:
216            rule['protocol'] = rule['protocol'].capitalize()
217            gateway.add_fw_rule(**rule)
218        result['changed'] = True
219
220    for index, rule in updates:
221        if not module.check_mode:
222            rule = create_fw_rule(**rule)
223            fwservice.replace_FirewallRule_at(index, rule)
224        result['changed'] = True
225
226    keys = ['protocol', 'dest_port', 'dest_ip', 'source_port', 'source_ip']
227    for rule in deletions:
228        if not module.check_mode:
229            kwargs = dict([(k, v) for k, v in rule.items() if k in keys])
230            kwargs['protocol'] = protocol_to_string(kwargs['protocol'])
231            gateway.delete_fw_rule(**kwargs)
232        result['changed'] = True
233
234    if not module.check_mode and result['changed'] is True:
235        task = gateway.save_services_configuration()
236        if task:
237            vca.block_until_completed(task)
238
239    result['rules_updated'] = len(updates)
240    result['rules_added'] = len(additions)
241    result['rules_deleted'] = len(deletions)
242
243    return module.exit_json(**result)
244
245
246if __name__ == '__main__':
247    main()
248