1#!/usr/bin/python 2from __future__ import (absolute_import, division, print_function) 3# Copyright 2019 Fortinet, Inc. 4# 5# This program 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# This program 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 this program. If not, see <https://www.gnu.org/licenses/>. 17 18__metaclass__ = type 19 20ANSIBLE_METADATA = {'status': ['preview'], 21 'supported_by': 'community', 22 'metadata_version': '1.1'} 23 24DOCUMENTATION = ''' 25--- 26module: fortios_router_bfd 27short_description: Configure BFD in Fortinet's FortiOS and FortiGate. 28description: 29 - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the 30 user to set and modify router feature and bfd category. 31 Examples include all parameters and values need to be adjusted to datasources before usage. 32 Tested with FOS v6.0.5 33version_added: "2.8" 34author: 35 - Miguel Angel Munoz (@mamunozgonzalez) 36 - Nicolas Thomas (@thomnico) 37notes: 38 - Requires fortiosapi library developed by Fortinet 39 - Run as a local_action in your playbook 40requirements: 41 - fortiosapi>=0.9.8 42options: 43 host: 44 description: 45 - FortiOS or FortiGate IP address. 46 type: str 47 required: false 48 username: 49 description: 50 - FortiOS or FortiGate username. 51 type: str 52 required: false 53 password: 54 description: 55 - FortiOS or FortiGate password. 56 type: str 57 default: "" 58 vdom: 59 description: 60 - Virtual domain, among those defined previously. A vdom is a 61 virtual instance of the FortiGate that can be configured and 62 used as a different unit. 63 type: str 64 default: root 65 https: 66 description: 67 - Indicates if the requests towards FortiGate must use HTTPS protocol. 68 type: bool 69 default: true 70 ssl_verify: 71 description: 72 - Ensures FortiGate certificate must be verified by a proper CA. 73 type: bool 74 default: true 75 version_added: 2.9 76 router_bfd: 77 description: 78 - Configure BFD. 79 default: null 80 type: dict 81 suboptions: 82 neighbor: 83 description: 84 - neighbor 85 type: list 86 suboptions: 87 interface: 88 description: 89 - Interface name. Source system.interface.name. 90 type: str 91 ip: 92 description: 93 - IPv4 address of the BFD neighbor. 94 required: true 95 type: str 96''' 97 98EXAMPLES = ''' 99- hosts: localhost 100 vars: 101 host: "192.168.122.40" 102 username: "admin" 103 password: "" 104 vdom: "root" 105 ssl_verify: "False" 106 tasks: 107 - name: Configure BFD. 108 fortios_router_bfd: 109 host: "{{ host }}" 110 username: "{{ username }}" 111 password: "{{ password }}" 112 vdom: "{{ vdom }}" 113 https: "False" 114 router_bfd: 115 neighbor: 116 - 117 interface: "<your_own_value> (source system.interface.name)" 118 ip: "<your_own_value>" 119''' 120 121RETURN = ''' 122build: 123 description: Build number of the fortigate image 124 returned: always 125 type: str 126 sample: '1547' 127http_method: 128 description: Last method used to provision the content into FortiGate 129 returned: always 130 type: str 131 sample: 'PUT' 132http_status: 133 description: Last result given by FortiGate on last operation applied 134 returned: always 135 type: str 136 sample: "200" 137mkey: 138 description: Master key (id) used in the last call to FortiGate 139 returned: success 140 type: str 141 sample: "id" 142name: 143 description: Name of the table used to fulfill the request 144 returned: always 145 type: str 146 sample: "urlfilter" 147path: 148 description: Path of the table used to fulfill the request 149 returned: always 150 type: str 151 sample: "webfilter" 152revision: 153 description: Internal revision number 154 returned: always 155 type: str 156 sample: "17.0.2.10658" 157serial: 158 description: Serial number of the unit 159 returned: always 160 type: str 161 sample: "FGVMEVYYQT3AB5352" 162status: 163 description: Indication of the operation's result 164 returned: always 165 type: str 166 sample: "success" 167vdom: 168 description: Virtual domain used 169 returned: always 170 type: str 171 sample: "root" 172version: 173 description: Version of the FortiGate 174 returned: always 175 type: str 176 sample: "v5.6.3" 177 178''' 179 180from ansible.module_utils.basic import AnsibleModule 181from ansible.module_utils.connection import Connection 182from ansible.module_utils.network.fortios.fortios import FortiOSHandler 183from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG 184 185 186def login(data, fos): 187 host = data['host'] 188 username = data['username'] 189 password = data['password'] 190 ssl_verify = data['ssl_verify'] 191 192 fos.debug('on') 193 if 'https' in data and not data['https']: 194 fos.https('off') 195 else: 196 fos.https('on') 197 198 fos.login(host, username, password, verify=ssl_verify) 199 200 201def filter_router_bfd_data(json): 202 option_list = ['neighbor'] 203 dictionary = {} 204 205 for attribute in option_list: 206 if attribute in json and json[attribute] is not None: 207 dictionary[attribute] = json[attribute] 208 209 return dictionary 210 211 212def underscore_to_hyphen(data): 213 if isinstance(data, list): 214 for elem in data: 215 elem = underscore_to_hyphen(elem) 216 elif isinstance(data, dict): 217 new_data = {} 218 for k, v in data.items(): 219 new_data[k.replace('_', '-')] = underscore_to_hyphen(v) 220 data = new_data 221 222 return data 223 224 225def router_bfd(data, fos): 226 vdom = data['vdom'] 227 router_bfd_data = data['router_bfd'] 228 filtered_data = underscore_to_hyphen(filter_router_bfd_data(router_bfd_data)) 229 230 return fos.set('router', 231 'bfd', 232 data=filtered_data, 233 vdom=vdom) 234 235 236def is_successful_status(status): 237 return status['status'] == "success" or \ 238 status['http_method'] == "DELETE" and status['http_status'] == 404 239 240 241def fortios_router(data, fos): 242 243 if data['router_bfd']: 244 resp = router_bfd(data, fos) 245 246 return not is_successful_status(resp), \ 247 resp['status'] == "success", \ 248 resp 249 250 251def main(): 252 fields = { 253 "host": {"required": False, "type": "str"}, 254 "username": {"required": False, "type": "str"}, 255 "password": {"required": False, "type": "str", "default": "", "no_log": True}, 256 "vdom": {"required": False, "type": "str", "default": "root"}, 257 "https": {"required": False, "type": "bool", "default": True}, 258 "ssl_verify": {"required": False, "type": "bool", "default": True}, 259 "router_bfd": { 260 "required": False, "type": "dict", "default": None, 261 "options": { 262 "neighbor": {"required": False, "type": "list", 263 "options": { 264 "interface": {"required": False, "type": "str"}, 265 "ip": {"required": True, "type": "str"} 266 }} 267 268 } 269 } 270 } 271 272 module = AnsibleModule(argument_spec=fields, 273 supports_check_mode=False) 274 275 # legacy_mode refers to using fortiosapi instead of HTTPAPI 276 legacy_mode = 'host' in module.params and module.params['host'] is not None and \ 277 'username' in module.params and module.params['username'] is not None and \ 278 'password' in module.params and module.params['password'] is not None 279 280 if not legacy_mode: 281 if module._socket_path: 282 connection = Connection(module._socket_path) 283 fos = FortiOSHandler(connection) 284 285 is_error, has_changed, result = fortios_router(module.params, fos) 286 else: 287 module.fail_json(**FAIL_SOCKET_MSG) 288 else: 289 try: 290 from fortiosapi import FortiOSAPI 291 except ImportError: 292 module.fail_json(msg="fortiosapi module is required") 293 294 fos = FortiOSAPI() 295 296 login(module.params, fos) 297 is_error, has_changed, result = fortios_router(module.params, fos) 298 fos.logout() 299 300 if not is_error: 301 module.exit_json(changed=has_changed, meta=result) 302 else: 303 module.fail_json(msg="Error in repo", meta=result) 304 305 306if __name__ == '__main__': 307 main() 308