1#!/usr/bin/python 2# 3# Ansible module to manage IP addresses on fortios devices 4# (c) 2016, Benjamin Jolivot <bjolivot@gmail.com> 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': 'community'} 14 15DOCUMENTATION = """ 16--- 17module: fortios_address 18version_added: "2.4" 19author: "Benjamin Jolivot (@bjolivot)" 20short_description: Manage fortios firewall address objects 21description: 22 - This module provide management of firewall addresses on FortiOS devices. 23extends_documentation_fragment: fortios 24options: 25 state: 26 description: 27 - Specifies if address need to be added or deleted. 28 required: true 29 choices: ['present', 'absent'] 30 name: 31 description: 32 - Name of the address to add or delete. 33 required: true 34 type: 35 description: 36 - Type of the address. 37 choices: ['iprange', 'fqdn', 'ipmask', 'geography'] 38 value: 39 description: 40 - Address value, based on type. 41 If type=fqdn, something like www.google.com. 42 If type=ipmask, you can use simple ip (192.168.0.1), ip+mask (192.168.0.1 255.255.255.0) or CIDR (192.168.0.1/32). 43 start_ip: 44 description: 45 - First ip in range (used only with type=iprange). 46 end_ip: 47 description: 48 - Last ip in range (used only with type=iprange). 49 country: 50 description: 51 - 2 letter country code (like FR). 52 interface: 53 description: 54 - interface name the address apply to. 55 default: any 56 comment: 57 description: 58 - free text to describe address. 59notes: 60 - This module requires netaddr python library. 61""" 62 63EXAMPLES = """ 64- name: Register french addresses 65 fortios_address: 66 host: 192.168.0.254 67 username: admin 68 password: p4ssw0rd 69 state: present 70 name: "fromfrance" 71 type: geography 72 country: FR 73 comment: "French geoip address" 74 75- name: Register some fqdn 76 fortios_address: 77 host: 192.168.0.254 78 username: admin 79 password: p4ssw0rd 80 state: present 81 name: "Ansible" 82 type: fqdn 83 value: www.ansible.com 84 comment: "Ansible website" 85 86- name: Register google DNS 87 fortios_address: 88 host: 192.168.0.254 89 username: admin 90 password: p4ssw0rd 91 state: present 92 name: "google_dns" 93 type: ipmask 94 value: 8.8.8.8 95 96""" 97 98RETURN = """ 99firewall_address_config: 100 description: full firewall addresses config string. 101 returned: always 102 type: str 103change_string: 104 description: The commands executed by the module. 105 returned: only if config changed 106 type: str 107""" 108 109from ansible.module_utils.network.fortios.fortios import fortios_argument_spec, fortios_required_if 110from ansible.module_utils.network.fortios.fortios import backup, AnsibleFortios 111 112from ansible.module_utils.basic import AnsibleModule 113 114 115# check for netaddr lib 116try: 117 from netaddr import IPNetwork 118 HAS_NETADDR = True 119except Exception: 120 HAS_NETADDR = False 121 122 123# define valid country list for GEOIP address type 124FG_COUNTRY_LIST = ( 125 'ZZ', 'A1', 'A2', 'O1', 'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AN', 'AO', 126 'AP', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 127 'BF', 'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BQ', 'BR', 'BS', 'BT', 128 'BV', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 129 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CW', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 130 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 'EU', 'FI', 'FJ', 131 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 132 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 133 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 134 'JE', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR', 'KW', 135 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'LY', 136 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP', 137 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 138 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 139 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE', 140 'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH', 'SI', 'SJ', 141 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'SS', 'ST', 'SV', 'SX', 'SY', 'SZ', 'TC', 142 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 143 'TW', 'TZ', 'UA', 'UG', 'UM', 'US', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 144 'VN', 'VU', 'WF', 'WS', 'YE', 'YT', 'ZA', 'ZM', 'ZW' 145) 146 147 148def get_formated_ipaddr(input_ip): 149 """ 150 Format given ip address string to fortigate format (ip netmask) 151 Args: 152 * **ip_str** (string) : string representing ip address 153 accepted format: 154 - ip netmask (ex: 192.168.0.10 255.255.255.0) 155 - ip (ex: 192.168.0.10) 156 - CIDR (ex: 192.168.0.10/24) 157 158 Returns: 159 formated ip if ip is valid (ex: "192.168.0.10 255.255.255.0") 160 False if ip is not valid 161 """ 162 try: 163 if " " in input_ip: 164 # ip netmask format 165 str_ip, str_netmask = input_ip.split(" ") 166 ip = IPNetwork(str_ip) 167 mask = IPNetwork(str_netmask) 168 return "%s %s" % (str_ip, str_netmask) 169 else: 170 ip = IPNetwork(input_ip) 171 return "%s %s" % (str(ip.ip), str(ip.netmask)) 172 except Exception: 173 return False 174 175 return False 176 177 178def main(): 179 argument_spec = dict( 180 state=dict(required=True, choices=['present', 'absent']), 181 name=dict(required=True), 182 type=dict(choices=['iprange', 'fqdn', 'ipmask', 'geography'], default='ipmask'), 183 value=dict(), 184 start_ip=dict(), 185 end_ip=dict(), 186 country=dict(), 187 interface=dict(default='any'), 188 comment=dict(), 189 ) 190 191 # merge argument_spec from module_utils/fortios.py 192 argument_spec.update(fortios_argument_spec) 193 194 # Load module 195 module = AnsibleModule( 196 argument_spec=argument_spec, 197 required_if=fortios_required_if, 198 supports_check_mode=True, 199 ) 200 result = dict(changed=False) 201 202 if not HAS_NETADDR: 203 module.fail_json(msg='Could not import the python library netaddr required by this module') 204 205 # check params 206 if module.params['state'] == 'absent': 207 if module.params['type'] != "ipmask": 208 module.fail_json(msg='Invalid argument type=%s when state=absent' % module.params['type']) 209 if module.params['value'] is not None: 210 module.fail_json(msg='Invalid argument `value` when state=absent') 211 if module.params['start_ip'] is not None: 212 module.fail_json(msg='Invalid argument `start_ip` when state=absent') 213 if module.params['end_ip'] is not None: 214 module.fail_json(msg='Invalid argument `end_ip` when state=absent') 215 if module.params['country'] is not None: 216 module.fail_json(msg='Invalid argument `country` when state=absent') 217 if module.params['interface'] != "any": 218 module.fail_json(msg='Invalid argument `interface` when state=absent') 219 if module.params['comment'] is not None: 220 module.fail_json(msg='Invalid argument `comment` when state=absent') 221 else: 222 # state=present 223 # validate IP 224 if module.params['type'] == "ipmask": 225 formated_ip = get_formated_ipaddr(module.params['value']) 226 if formated_ip is not False: 227 module.params['value'] = get_formated_ipaddr(module.params['value']) 228 else: 229 module.fail_json(msg="Bad ip address format") 230 231 # validate country 232 if module.params['type'] == "geography": 233 if module.params['country'] not in FG_COUNTRY_LIST: 234 module.fail_json(msg="Invalid country argument, need to be in `diagnose firewall ipgeo country-list`") 235 236 # validate iprange 237 if module.params['type'] == "iprange": 238 if module.params['start_ip'] is None: 239 module.fail_json(msg="Missing argument 'start_ip' when type is iprange") 240 if module.params['end_ip'] is None: 241 module.fail_json(msg="Missing argument 'end_ip' when type is iprange") 242 243 # init forti object 244 fortigate = AnsibleFortios(module) 245 246 # Config path 247 config_path = 'firewall address' 248 249 # load config 250 fortigate.load_config(config_path) 251 252 # Absent State 253 if module.params['state'] == 'absent': 254 fortigate.candidate_config[config_path].del_block(module.params['name']) 255 256 # Present state 257 if module.params['state'] == 'present': 258 # define address params 259 new_addr = fortigate.get_empty_configuration_block(module.params['name'], 'edit') 260 261 if module.params['comment'] is not None: 262 new_addr.set_param('comment', '"%s"' % (module.params['comment'])) 263 264 if module.params['type'] == 'iprange': 265 new_addr.set_param('type', 'iprange') 266 new_addr.set_param('start-ip', module.params['start_ip']) 267 new_addr.set_param('end-ip', module.params['end_ip']) 268 269 if module.params['type'] == 'geography': 270 new_addr.set_param('type', 'geography') 271 new_addr.set_param('country', '"%s"' % (module.params['country'])) 272 273 if module.params['interface'] != 'any': 274 new_addr.set_param('associated-interface', '"%s"' % (module.params['interface'])) 275 276 if module.params['value'] is not None: 277 if module.params['type'] == 'fqdn': 278 new_addr.set_param('type', 'fqdn') 279 new_addr.set_param('fqdn', '"%s"' % (module.params['value'])) 280 if module.params['type'] == 'ipmask': 281 new_addr.set_param('subnet', module.params['value']) 282 283 # add the new address object to the device 284 fortigate.add_block(module.params['name'], new_addr) 285 286 # Apply changes (check mode is managed directly by the fortigate object) 287 fortigate.apply_changes() 288 289 290if __name__ == '__main__': 291 main() 292