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