1#!/usr/bin/python 2# 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# 18 19ANSIBLE_METADATA = {'metadata_version': '1.1', 20 'status': ['preview'], 21 'supported_by': 'network'} 22 23 24DOCUMENTATION = """ 25--- 26module: nxos_nxapi 27extends_documentation_fragment: nxos 28version_added: "2.1" 29author: "Peter Sprygada (@privateip)" 30short_description: Manage NXAPI configuration on an NXOS device. 31description: 32 - Configures the NXAPI feature on devices running Cisco NXOS. The 33 NXAPI feature is absent from the configuration by default. Since 34 this module manages the NXAPI feature it only supports the use 35 of the C(Cli) transport. 36options: 37 http_port: 38 description: 39 - Configure the port with which the HTTP server will listen on 40 for requests. By default, NXAPI will bind the HTTP service 41 to the standard HTTP port 80. This argument accepts valid 42 port values in the range of 1 to 65535. 43 required: false 44 default: 80 45 http: 46 description: 47 - Controls the operating state of the HTTP protocol as one of the 48 underlying transports for NXAPI. By default, NXAPI will enable 49 the HTTP transport when the feature is first configured. To 50 disable the use of the HTTP transport, set the value of this 51 argument to False. 52 required: false 53 default: yes 54 type: bool 55 aliases: ['enable_http'] 56 https_port: 57 description: 58 - Configure the port with which the HTTPS server will listen on 59 for requests. By default, NXAPI will bind the HTTPS service 60 to the standard HTTPS port 443. This argument accepts valid 61 port values in the range of 1 to 65535. 62 required: false 63 default: 443 64 https: 65 description: 66 - Controls the operating state of the HTTPS protocol as one of the 67 underlying transports for NXAPI. By default, NXAPI will disable 68 the HTTPS transport when the feature is first configured. To 69 enable the use of the HTTPS transport, set the value of this 70 argument to True. 71 required: false 72 default: no 73 type: bool 74 aliases: ['enable_https'] 75 sandbox: 76 description: 77 - The NXAPI feature provides a web base UI for developers for 78 entering commands. This feature is initially disabled when 79 the NXAPI feature is configured for the first time. When the 80 C(sandbox) argument is set to True, the developer sandbox URL 81 will accept requests and when the value is set to False, the 82 sandbox URL is unavailable. This is supported on NX-OS 7K series. 83 required: false 84 default: no 85 type: bool 86 aliases: ['enable_sandbox'] 87 state: 88 description: 89 - The C(state) argument controls whether or not the NXAPI 90 feature is configured on the remote device. When the value 91 is C(present) the NXAPI feature configuration is present in 92 the device running-config. When the values is C(absent) the 93 feature configuration is removed from the running-config. 94 choices: ['present', 'absent'] 95 required: false 96 default: present 97 ssl_strong_ciphers: 98 description: 99 - Controls the use of whether strong or weak ciphers are configured. 100 By default, this feature is disabled and weak ciphers are 101 configured. To enable the use of strong ciphers, set the value of 102 this argument to True. 103 required: false 104 default: no 105 type: bool 106 version_added: "2.7" 107 tlsv1_0: 108 description: 109 - Controls the use of the Transport Layer Security version 1.0 is 110 configured. By default, this feature is enabled. To disable the 111 use of TLSV1.0, set the value of this argument to True. 112 required: false 113 default: yes 114 type: bool 115 version_added: "2.7" 116 tlsv1_1: 117 description: 118 - Controls the use of the Transport Layer Security version 1.1 is 119 configured. By default, this feature is disabled. To enable the 120 use of TLSV1.1, set the value of this argument to True. 121 required: false 122 default: no 123 type: bool 124 version_added: "2.7" 125 tlsv1_2: 126 description: 127 - Controls the use of the Transport Layer Security version 1.2 is 128 configured. By default, this feature is disabled. To enable the 129 use of TLSV1.2, set the value of this argument to True. 130 required: false 131 default: no 132 type: bool 133 version_added: "2.7" 134""" 135 136EXAMPLES = """ 137- name: Enable NXAPI access with default configuration 138 nxos_nxapi: 139 state: present 140 141- name: Enable NXAPI with no HTTP, HTTPS at port 9443 and sandbox disabled 142 nxos_nxapi: 143 enable_http: false 144 https_port: 9443 145 https: yes 146 enable_sandbox: no 147 148- name: remove NXAPI configuration 149 nxos_nxapi: 150 state: absent 151""" 152 153RETURN = """ 154updates: 155 description: 156 - Returns the list of commands that need to be pushed into the remote 157 device to satisfy the arguments 158 returned: always 159 type: list 160 sample: ['no feature nxapi'] 161""" 162import re 163 164from distutils.version import LooseVersion 165from ansible.module_utils.network.nxos.nxos import run_commands, load_config 166from ansible.module_utils.network.nxos.nxos import nxos_argument_spec 167from ansible.module_utils.network.nxos.nxos import get_capabilities 168from ansible.module_utils.basic import AnsibleModule 169from ansible.module_utils.six import iteritems 170 171 172def check_args(module, warnings, capabilities): 173 network_api = capabilities.get('network_api', 'nxapi') 174 if network_api == 'nxapi': 175 module.fail_json(msg='module not supported over nxapi transport') 176 177 os_platform = capabilities['device_info']['network_os_platform'] 178 if '7K' not in os_platform and module.params['sandbox']: 179 module.fail_json(msg='sandbox or enable_sandbox is supported on NX-OS 7K series of switches') 180 181 state = module.params['state'] 182 183 if state == 'started': 184 module.params['state'] = 'present' 185 warnings.append('state=started is deprecated and will be removed in a ' 186 'a future release. Please use state=present instead') 187 elif state == 'stopped': 188 module.params['state'] = 'absent' 189 warnings.append('state=stopped is deprecated and will be removed in a ' 190 'a future release. Please use state=absent instead') 191 192 for key in ['http_port', 'https_port']: 193 if module.params[key] is not None: 194 if not 1 <= module.params[key] <= 65535: 195 module.fail_json(msg='%s must be between 1 and 65535' % key) 196 197 return warnings 198 199 200def map_obj_to_commands(want, have, module, warnings, capabilities): 201 send_commands = list() 202 commands = dict() 203 os_platform = None 204 os_version = None 205 206 device_info = capabilities.get('device_info') 207 if device_info: 208 os_version = device_info.get('network_os_version') 209 if os_version: 210 os_version = os_version[:3] 211 os_platform = device_info.get('network_os_platform') 212 if os_platform: 213 os_platform = os_platform[:3] 214 215 def needs_update(x): 216 return want.get(x) is not None and (want.get(x) != have.get(x)) 217 218 if needs_update('state'): 219 if want['state'] == 'absent': 220 return ['no feature nxapi'] 221 send_commands.append('feature nxapi') 222 elif want['state'] == 'absent': 223 return send_commands 224 225 for parameter in ['http', 'https']: 226 port_param = parameter + '_port' 227 if needs_update(parameter): 228 if want.get(parameter) is False: 229 commands[parameter] = 'no nxapi %s' % parameter 230 else: 231 commands[parameter] = 'nxapi %s port %s' % (parameter, want.get(port_param)) 232 233 if needs_update(port_param) and want.get(parameter) is True: 234 commands[parameter] = 'nxapi %s port %s' % (parameter, want.get(port_param)) 235 236 if needs_update('sandbox'): 237 commands['sandbox'] = 'nxapi sandbox' 238 if not want['sandbox']: 239 commands['sandbox'] = 'no %s' % commands['sandbox'] 240 241 if os_platform and os_version: 242 if (os_platform == 'N9K' or os_platform == 'N3K') and LooseVersion(os_version) >= "9.2": 243 if needs_update('ssl_strong_ciphers'): 244 commands['ssl_strong_ciphers'] = 'nxapi ssl ciphers weak' 245 if want['ssl_strong_ciphers'] is True: 246 commands['ssl_strong_ciphers'] = 'no nxapi ssl ciphers weak' 247 248 have_ssl_protocols = '' 249 want_ssl_protocols = '' 250 for key, value in {'tlsv1_2': 'TLSv1.2', 'tlsv1_1': 'TLSv1.1', 'tlsv1_0': 'TLSv1'}.items(): 251 if needs_update(key): 252 if want.get(key) is True: 253 want_ssl_protocols = " ".join([want_ssl_protocols, value]) 254 elif have.get(key) is True: 255 have_ssl_protocols = " ".join([have_ssl_protocols, value]) 256 257 if len(want_ssl_protocols) > 0: 258 commands['ssl_protocols'] = 'nxapi ssl protocols%s' % (" ".join([want_ssl_protocols, have_ssl_protocols])) 259 else: 260 warnings.append('os_version and/or os_platform keys from ' 261 'platform capabilities are not available. ' 262 'Any NXAPI SSL optional arguments will be ignored') 263 264 send_commands.extend(commands.values()) 265 266 return send_commands 267 268 269def parse_http(data): 270 http_res = [r'nxapi http port (\d+)'] 271 http_port = None 272 273 for regex in http_res: 274 match = re.search(regex, data, re.M) 275 if match: 276 http_port = int(match.group(1)) 277 break 278 279 return {'http': http_port is not None, 'http_port': http_port} 280 281 282def parse_https(data): 283 https_res = [r'nxapi https port (\d+)'] 284 https_port = None 285 286 for regex in https_res: 287 match = re.search(regex, data, re.M) 288 if match: 289 https_port = int(match.group(1)) 290 break 291 292 return {'https': https_port is not None, 'https_port': https_port} 293 294 295def parse_sandbox(data): 296 sandbox = [item for item in data.split('\n') if re.search(r'.*sandbox.*', item)] 297 value = False 298 if sandbox and sandbox[0] == 'nxapi sandbox': 299 value = True 300 return {'sandbox': value} 301 302 303def parse_ssl_strong_ciphers(data): 304 ciphers_res = [r'(\w+) nxapi ssl ciphers weak'] 305 value = None 306 307 for regex in ciphers_res: 308 match = re.search(regex, data, re.M) 309 if match: 310 value = match.group(1) 311 break 312 313 return {'ssl_strong_ciphers': value == 'no'} 314 315 316def parse_ssl_protocols(data): 317 tlsv1_0 = re.search(r'(?<!\S)TLSv1(?!\S)', data, re.M) is not None 318 tlsv1_1 = re.search(r'(?<!\S)TLSv1.1(?!\S)', data, re.M) is not None 319 tlsv1_2 = re.search(r'(?<!\S)TLSv1.2(?!\S)', data, re.M) is not None 320 321 return {'tlsv1_0': tlsv1_0, 'tlsv1_1': tlsv1_1, 'tlsv1_2': tlsv1_2} 322 323 324def map_config_to_obj(module): 325 out = run_commands(module, ['show run all | inc nxapi'], check_rc=False)[0] 326 match = re.search(r'no feature nxapi', out, re.M) 327 # There are two possible outcomes when nxapi is disabled on nxos platforms. 328 # 1. Nothing is displayed in the running config. 329 # 2. The 'no feature nxapi' command is displayed in the running config. 330 if match or out == '': 331 return {'state': 'absent'} 332 333 out = str(out).strip() 334 335 obj = {'state': 'present'} 336 obj.update(parse_http(out)) 337 obj.update(parse_https(out)) 338 obj.update(parse_sandbox(out)) 339 obj.update(parse_ssl_strong_ciphers(out)) 340 obj.update(parse_ssl_protocols(out)) 341 342 return obj 343 344 345def map_params_to_obj(module): 346 obj = { 347 'http': module.params['http'], 348 'http_port': module.params['http_port'], 349 'https': module.params['https'], 350 'https_port': module.params['https_port'], 351 'sandbox': module.params['sandbox'], 352 'state': module.params['state'], 353 'ssl_strong_ciphers': module.params['ssl_strong_ciphers'], 354 'tlsv1_0': module.params['tlsv1_0'], 355 'tlsv1_1': module.params['tlsv1_1'], 356 'tlsv1_2': module.params['tlsv1_2'] 357 } 358 359 return obj 360 361 362def main(): 363 """ main entry point for module execution 364 """ 365 argument_spec = dict( 366 http=dict(aliases=['enable_http'], type='bool', default=True), 367 http_port=dict(type='int', default=80), 368 https=dict(aliases=['enable_https'], type='bool', default=False), 369 https_port=dict(type='int', default=443), 370 sandbox=dict(aliases=['enable_sandbox'], type='bool'), 371 state=dict(default='present', choices=['started', 'stopped', 'present', 'absent']), 372 ssl_strong_ciphers=dict(type='bool', default=False), 373 tlsv1_0=dict(type='bool', default=True), 374 tlsv1_1=dict(type='bool', default=False), 375 tlsv1_2=dict(type='bool', default=False) 376 ) 377 378 argument_spec.update(nxos_argument_spec) 379 380 module = AnsibleModule(argument_spec=argument_spec, 381 supports_check_mode=True) 382 383 warnings = list() 384 warning_msg = "Module nxos_nxapi currently defaults to configure 'http port 80'. " 385 warning_msg += "Default behavior is changing to configure 'https port 443'" 386 warning_msg += " when params 'http, http_port, https, https_port' are not set in the playbook" 387 module.deprecate(msg=warning_msg, version="2.11") 388 389 capabilities = get_capabilities(module) 390 391 check_args(module, warnings, capabilities) 392 393 want = map_params_to_obj(module) 394 have = map_config_to_obj(module) 395 396 commands = map_obj_to_commands(want, have, module, warnings, capabilities) 397 398 result = {'changed': False, 'warnings': warnings, 'commands': commands} 399 400 if commands: 401 if not module.check_mode: 402 load_config(module, commands) 403 result['changed'] = True 404 405 module.exit_json(**result) 406 407 408if __name__ == '__main__': 409 main() 410