From 370916662d83cbfe99607ef852284c9d21ee6033 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Wed, 22 May 2019 11:40:44 -0700 Subject: [PATCH] complete overhaul of execute.py --- hyperglass/command/execute-old.py | 206 +++++++++++++++++++ hyperglass/command/execute.py | 325 ++++++++++++++++-------------- 2 files changed, 378 insertions(+), 153 deletions(-) create mode 100644 hyperglass/command/execute-old.py diff --git a/hyperglass/command/execute-old.py b/hyperglass/command/execute-old.py new file mode 100644 index 0000000..b694830 --- /dev/null +++ b/hyperglass/command/execute-old.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 + +import sys +import json +import time +import requests +from netaddr import * +from logzero import logger +from netmiko import redispatch +from netmiko import ConnectHandler +from hyperglass import configuration +from hyperglass.command import parse +from hyperglass.command import construct + +# Load TOML devices file +devices = configuration.devices() +# Filter config to router list +routers_list = devices["router"] +# Filter config to credential list +credentials_list = devices["credential"] +# Filter config to proxy servers +proxies_list = devices["proxy"] + +blacklist_config = configuration.blacklist() +blacklist = IPSet(blacklist_config["blacklist"]) + +general_error = "Error connecting to device." + + +def execute(lg_data): + logger.info(f"Received lookup request for: {lg_data}") + # Check POST data from JS, if location matches a configured router's + # location, use the router's configured IP address to connect + router = lg_data["router"] + cmd = lg_data["cmd"] + ipprefix = lg_data["ipprefix"] + + for r in routers_list: + if r["location"] == router: + lg_router_address = r["address"] + lg_router_port = r["port"] + lg_router_type = r["type"] + + # Check blacklist.toml array for prefixes/IPs and return an error upon a match + if cmd in ["bgp_route", "ping", "traceroute"]: + try: + if IPNetwork(ipprefix).ip in blacklist: + msg = f"{ipprefix} is not allowed." + code = 405 + logger.error(f"{msg}, {code}, {lg_data}") + return (msg, code, lg_data) + # If netaddr library throws an exception, return a user-facing error. + except: + msg = f"{ipprefix} is not a valid IP Address." + code = 415 + logger.error(f"{msg}, {code}, {lg_data}") + return (msg, code, lg_data) + + # Send "clean" request to constructor to build the command that will be sent to the router + if lg_router_type == "frr": + msg, status, router, query = construct.frr(lg_router_address, cmd, ipprefix) + else: + msg, status, router, type, command = construct.netmiko( + lg_router_address, cmd, ipprefix + ) + nm_host = { + "host": router, + "device_type": type, + "username": returnCred(findCred(router))[0], + "password": returnCred(findCred(router))[1], + "global_delay_factor": 0.5, + } + + def matchProxy(search_proxy): + """Loops through proxy config, matches configured proxy name for each router with a configured proxy. Returns configured proxy parameters for netmiko""" + if configured_proxy in proxies_list: + proxy_address = proxies_list[search_proxy]["address"] + proxy_username = proxies_list[search_proxy]["username"] + proxy_password = proxies_list[search_proxy]["password"] + proxy_type = proxies_list[search_proxy]["type"] + proxy_ssh_command = proxies_list[search_proxy]["ssh_command"] + return ( + proxy_address, + proxy_username, + proxy_password, + proxy_type, + proxy_ssh_command, + ) + else: + msg = "Router does not have a proxy configured." + code = 415 + logger.error(f"{msg}, {code}, {lg_data}") + return (msg, code, lg_data) + + def findCred(router): + """Matches router with configured credential""" + for r in routers_list: + if r["address"] == router: + configured_credential = r["credential"] + return configured_credential + + def returnCred(configured_credential): + """Matches configured credential with real username/password""" + if configured_credential in credentials_list: + matched_username = credentials_list[configured_credential]["username"] + matched_password = credentials_list[configured_credential]["password"] + return matched_username, matched_password + else: + msg = f"Credential {configured_credential} does not exist" + code = 415 + logger.error(f"{msg}, {code}, {lg_data}") + return (general_error, code, lg_data) + + def frr_api_direct(query): + """Sends HTTP POST to router running the hyperglass-frr API""" + global lg_router_address + global lg_router_port + try: + headers = { + "Content-Type": "application/json", + "X-API-Key": returnCred(findCred(router))[1], + } + json_query = json.dumps(query) + frr_endpoint = f"http://{router}:{lg_router_port}/frr" + frr_output = requests.post(frr_endpoint, headers=headers, data=json_query) + return frr_output + except: + raise + + def netmiko_direct(): + """Connects to the router via netmiko library, return the command output""" + try: + nm_connect_direct = ConnectHandler(**nm_host) + nm_output_direct = nm_connect_direct.send_command(command) + return nm_output_direct + except: + msg = f"Unable to reach target {router}" + code = 415 + logger.error(f"{msg}, {code}, {lg_data}") + return (general_error, code, lg_data) + + def netmiko_proxied(router_proxy): + """Connects to the proxy server via netmiko library, then logs into the router via standard SSH""" + nm_proxy = { + "host": matchProxy(router_proxy)[0], + "username": matchProxy(router_proxy)[1], + "password": matchProxy(router_proxy)[2], + "device_type": matchProxy(router_proxy)[3], + "global_delay_factor": 0.5, + } + nm_connect_proxied = ConnectHandler(**nm_proxy) + nm_ssh_command = matchProxy(router_proxy)[4].format(**nm_host) + "\n" + nm_connect_proxied.write_channel(nm_ssh_command) + time.sleep(1) + proxy_output = nm_connect_proxied.read_channel() + try: + # Accept SSH key warnings + if "Are you sure you want to continue connecting" in proxy_output: + nm_connect_proxied.write_channel("yes" + "\n") + # time.sleep(1) + nm_connect_proxied.write_channel(nm_host["password"] + "\n") + # Send password on prompt + elif "assword" in proxy_output: + nm_connect_proxied.write_channel(nm_host["password"] + "\n") + # time.sleep(1) + proxy_output += nm_connect_proxied.read_channel() + # Reclassify netmiko connection as configured device type + redispatch(nm_connect_proxied, nm_host["device_type"]) + + host_output = nm_connect_proxied.send_command(command) + if host_output: + return host_output + except: + msg = f'Proxy server {nm_proxy["host"]} unable to reach target {nm_host["host"]}' + code = 415 + logger.error(f"{msg}, {code}, {lg_data}") + return (general_error, code, lg_data) + + # Loop through router list, determine if proxy exists + for r in routers_list: + if r["address"] == router: + configured_proxy = r["proxy"] + if len(configured_proxy) == 0: + connection_proxied = False + else: + connection_proxied = True + if status == 200: + logger.info(f"Executing {command} on {router}...") + try: + if connection_proxied is True: + output_proxied = netmiko_proxied(configured_proxy) + parsed_output = parse.parse(output_proxied, type, cmd) + return parsed_output, status, router, type, command + elif connection_proxied is False: + if type == "frr": + output_direct = frr_api_direct(query) + parsed_output = parse.parse(output_direct, type, cmd) + return parsed_output, status, router, type, command + else: + output_direct = netmiko_direct() + parsed_output = parse.parse(output_direct, type, cmd) + return parsed_output, status, router, type, command + except: + raise + else: + return msg, status, router, type, command diff --git a/hyperglass/command/execute.py b/hyperglass/command/execute.py index 62f51c6..0518e2b 100644 --- a/hyperglass/command/execute.py +++ b/hyperglass/command/execute.py @@ -9,8 +9,8 @@ from logzero import logger from netmiko import redispatch from netmiko import ConnectHandler from hyperglass import configuration -from hyperglass.command import construct from hyperglass.command import parse +from hyperglass.command import construct # Load TOML devices file devices = configuration.devices() @@ -27,175 +27,194 @@ blacklist = IPSet(blacklist_config["blacklist"]) general_error = "Error connecting to device." +class device: + def __init__(self, lg_router): + for r in routers_list: + if r["location"] == lg_router: + self.address = r["address"] + self.port = r["port"] + self.type = r["type"] + self.credential = r["credential"] + self.location = r["location"] + self.proxy = r["proxy"] + + +class credential: + def __init__(self, cred): + if cred in credentials_list: + self.username = credentials_list[cred]["username"] + self.password = credentials_list[cred]["password"] + + +class proxy: + def __init__(self, proxy): + if proxy in proxies_list: + self.address = proxies_list[proxy]["address"] + self.username = proxies_list[proxy]["username"] + self.password = proxies_list[proxy]["password"] + self.type = proxies_list[proxy]["type"] + self.ssh_command = proxies_list[proxy]["ssh_command"] + + +class params: + class http: + def __init__(self): + self.msg, self.status, self.router, self.query = construct.frr( + d.address, lg_cmd, lg_ipprefix + ) + + def all(self): + return self.msg, self.status, self.router, self.query + + class ssh: + def __init__(self): + self.msg, self.status, self.router, self.type, self.command = construct.netmiko( + d.address, lg_cmd, lg_ipprefix + ) + + def all(self): + return self.msg, self.status, self.router, self.type, self.command + + def nm_host(self): + c = credential(d.credential) + attr = { + "host": self.router, + "device_type": self.type, + "username": c.username, + "password": c.password, + "global_delay_factor": 0.5, + } + return attr + + def nm_proxy(self): + p = proxy(d.proxy) + attr = { + "host": p.address, + "username": p.username, + "password": p.password, + "device_type": p.type, + "global_delay_factor": 0.5, + } + return attr + + +class connect: + class restapi: + def frr(): + """Sends HTTP POST to router running the hyperglass-frr API""" + http = params().http() + c = credential(d.credential) + logger.error(f"API Key: {c.password}") + try: + headers = {"Content-Type": "application/json", "X-API-Key": c.password} + json_query = json.dumps(http.query) + frr_endpoint = f"http://{d.address}:{d.port}/frr" + frr_output = requests.post( + frr_endpoint, headers=headers, data=json_query + ) + return frr_output + except: + raise + + class nm: + def direct(): + """Connects to the router via netmiko library, return the command output""" + ssh = params().ssh() + nm_host = ssh.nm_host() + nm_connect_direct = ConnectHandler(**nm_host) + nm_output_direct = nm_connect_direct.send_command(ssh.command) + return nm_output_direct + + def proxied(device_proxy): + """Connects to the proxy server via netmiko library, then logs into the router via standard SSH""" + ssh = params().ssh() + nm_proxy = ssh.nm_proxy() + nm_host = ssh.nm_host() + + nm_connect_proxied = ConnectHandler(**nm_proxy) + nm_ssh_command = dp.ssh_command.format(**nm_host) + "\n" + + nm_connect_proxied.write_channel(nm_ssh_command) + time.sleep(1) + proxy_output = nm_connect_proxied.read_channel() + + try: + # Accept SSH key warnings + if "Are you sure you want to continue connecting" in proxy_output: + nm_connect_proxied.write_channel("yes" + "\n") + nm_connect_proxied.write_channel(nm_host["password"] + "\n") + # Send password on prompt + elif "assword" in proxy_output: + nm_connect_proxied.write_channel(nm_host["password"] + "\n") + proxy_output += nm_connect_proxied.read_channel() + # Reclassify netmiko connection as configured device type + redispatch(nm_connect_proxied, nm_host["device_type"]) + + host_output = nm_connect_proxied.send_command(ssh.command) + + if host_output: + return host_output + except: + msg = f'Proxy server {nm_proxy["host"]} unable to reach target {nm_host["host"]}' + code = 415 + logger.error(f"{msg}, {code}, {lg_params}") + raise + return (general_error, code, lg_params) + + def execute(lg_data): logger.info(f"Received lookup request for: {lg_data}") - # Check POST data from JS, if location matches a configured router's - # location, use the router's configured IP address to connect - router = lg_data["router"] - cmd = lg_data["cmd"] - ipprefix = lg_data["ipprefix"] + # Create individual variables for POSTed JSON from main app + global lg_router + lg_router = lg_data["router"] - for r in routers_list: - if r["location"] == router: - lg_router_address = r["address"] - lg_router_port = r["port"] + global lg_cmd + lg_cmd = lg_data["cmd"] + + global lg_ipprefix + lg_ipprefix = lg_data["ipprefix"] + + global lg_params + lg_params = lg_data # Check blacklist.toml array for prefixes/IPs and return an error upon a match - if cmd in ["bgp_route", "ping", "traceroute"]: + if lg_cmd in ["bgp_route", "ping", "traceroute"]: try: - if IPNetwork(ipprefix).ip in blacklist: - msg = f"{ipprefix} is not allowed." + if IPNetwork(lg_ipprefix).ip in blacklist: + msg = f"{lg_ipprefix} is not allowed." code = 405 logger.error(f"{msg}, {code}, {lg_data}") return (msg, code, lg_data) # If netaddr library throws an exception, return a user-facing error. except: - msg = f"{ipprefix} is not a valid IP Address." - code = 415 - logger.error(f"{msg}, {code}, {lg_data}") - return (msg, code, lg_data) - # Send "clean" request to constructor to build the command that will be sent to the router - msg, status, router, type, command = construct.netmiko( - lg_router_address, cmd, ipprefix - ) - - def matchProxy(search_proxy): - """Loops through proxy config, matches configured proxy name for each router with a configured proxy. Returns configured proxy parameters for netmiko""" - if configured_proxy in proxies_list: - proxy_address = proxies_list[search_proxy]["address"] - proxy_username = proxies_list[search_proxy]["username"] - proxy_password = proxies_list[search_proxy]["password"] - proxy_type = proxies_list[search_proxy]["type"] - proxy_ssh_command = proxies_list[search_proxy]["ssh_command"] - return ( - proxy_address, - proxy_username, - proxy_password, - proxy_type, - proxy_ssh_command, - ) - else: - msg = "Router does not have a proxy configured." + msg = f"{lg_ipprefix} is not a valid IP Address." code = 415 logger.error(f"{msg}, {code}, {lg_data}") return (msg, code, lg_data) - def findCred(router): - """Matches router with configured credential""" - for r in routers_list: - if r["address"] == router: - configured_credential = r["credential"] - return configured_credential + global d + d = device(lg_router) - def returnCred(configured_credential): - """Matches configured credential with real username/password""" - if configured_credential in credentials_list: - matched_username = credentials_list[configured_credential]["username"] - matched_password = credentials_list[configured_credential]["password"] - return matched_username, matched_password - else: - msg = f"Credential {configured_credential} does not exist" - code = 415 - logger.error(f"{msg}, {code}, {lg_data}") - return (general_error, code, lg_data) - - def frr_api_direct(): - """Sends HTTP POST to router running the hyperglass-frr API""" - msg, status, router, query = construct.frr(lg_router_address, cmd, ipprefix) + if d.type == "frr": + http = params().http() try: - headers = { - "Content-Type": "application/json", - "X-API-Key": returnCred(findCred(router))[1], - } - json_query = json.dumps(query) - frr_endpoint = f"http://{router}:{lg_router_port}/frr" - frr_output = requests.post(frr_endpoint, headers=headers, data=json_query) - return frr_output - except: - raise - - def netmiko_direct(): - """Connects to the router via netmiko library, return the command output""" - try: - nm_connect_direct = ConnectHandler(**nm_host) - nm_output_direct = nm_connect_direct.send_command(command) - return nm_output_direct - except: - msg = f"Unable to reach target {router}" - code = 415 - logger.error(f"{msg}, {code}, {lg_data}") - return (general_error, code, lg_data) - - def netmiko_proxied(router_proxy): - """Connects to the proxy server via netmiko library, then logs into the router via standard SSH""" - nm_proxy = { - "host": matchProxy(router_proxy)[0], - "username": matchProxy(router_proxy)[1], - "password": matchProxy(router_proxy)[2], - "device_type": matchProxy(router_proxy)[3], - "global_delay_factor": 0.5, - } - nm_connect_proxied = ConnectHandler(**nm_proxy) - nm_ssh_command = matchProxy(router_proxy)[4].format(**nm_host) + "\n" - nm_connect_proxied.write_channel(nm_ssh_command) - time.sleep(1) - proxy_output = nm_connect_proxied.read_channel() - try: - # Accept SSH key warnings - if "Are you sure you want to continue connecting" in proxy_output: - nm_connect_proxied.write_channel("yes" + "\n") - # time.sleep(1) - nm_connect_proxied.write_channel(nm_host["password"] + "\n") - # Send password on prompt - elif "assword" in proxy_output: - nm_connect_proxied.write_channel(nm_host["password"] + "\n") - # time.sleep(1) - proxy_output += nm_connect_proxied.read_channel() - # Reclassify netmiko connection as configured device type - redispatch(nm_connect_proxied, nm_host["device_type"]) - - host_output = nm_connect_proxied.send_command(command) - if host_output: - return host_output - except: - msg = f'Proxy server {nm_proxy["host"]} unable to reach target {nm_host["host"]}' - code = 415 - logger.error(f"{msg}, {code}, {lg_data}") - return (general_error, code, lg_data) - - nm_host = { - "host": router, - "device_type": type, - "username": returnCred(findCred(router))[0], - "password": returnCred(findCred(router))[1], - "global_delay_factor": 0.5, - } - - # Loop through router list, determine if proxy exists - for r in routers_list: - if r["address"] == router: - configured_proxy = r["proxy"] - if len(configured_proxy) == 0: - connection_proxied = False - else: - connection_proxied = True - if status == 200: - logger.info(f"Executing {command} on {router}...") - try: - if connection_proxied is True: - output_proxied = netmiko_proxied(configured_proxy) - parsed_output = parse.parse(output_proxied, type, cmd) - return parsed_output, status, router, type, command - elif connection_proxied is False: - if type == "frr": - output_direct = frr_api_direct() - parsed_output = parse.parse(output_direct, type, cmd) - return parsed_output, status, router, type, command - else: - output_direct = netmiko_direct() - parsed_output = parse.parse(output_direct, type, cmd) - return parsed_output, status, router, type, command + output = connect.restapi.frr() + parsed_output = parse.parse(output, d.type, lg_cmd) + return parsed_output, http.status, params().http().all() except: raise else: - return msg, status, router, type, command + try: + ssh = params().ssh() + if d.proxy: + global dp + dp = proxy(d.proxy) + output = connect.nm.proxied(d.proxy) + parsed_output = parse.parse(output, d.type, lg_cmd) + return parsed_output, ssh.status, ssh.router, ssh.command + elif not d.proxy: + output = connect.nm.direct() + parsed_output = parse.parse(output, d.type, lg_cmd) + return parsed_output, ssh.status, ssh.router, ssh.command + except: + raise