overhaul of construct.py

This commit is contained in:
checktheroads 2019-05-22 23:27:57 -07:00
parent 370916662d
commit b489874928
2 changed files with 251 additions and 336 deletions

View file

@ -20,317 +20,210 @@ commands = configuration.commands()
routers_list = devices["router"] routers_list = devices["router"]
def frr(router, cmd, ipprefix): class codes:
logger.info(f"Constructing {cmd} command for FRR router {router} to {ipprefix}...") """Class for easy calling & recalling of http success/error codes"""
try:
# Loop through routers config file, match input router with configured routers, set variables def __init__(self):
for r in routers_list: # 200 OK: renders standard display text
if router == r["address"]: self.success = 200
type = r["type"] # 405 Method Not Allowed: Renders Bulma "warning" class notification message with message text
src_addr_ipv4 = r["src_addr_ipv4"] self.warning = 405
src_addr_ipv6 = r["src_addr_ipv6"] # 415 Unsupported Media Type: Renders Bulma "danger" class notification message with message text
try: self.danger = 415
# Loop through commands config file, set variables for matched commands
if cmd == "Query Type":
msg = "You must select a query type." code = codes()
code = 415
logger.error(f"{msg}, {code}, {router}, {cmd}, {ipprefix}")
return (msg, code, router, cmd, ipprefix) def frr(cmd, ipprefix, device):
# BGP Community Query d_address = device["address"]
elif cmd in ["bgp_community"]: d_src_addr_ipv4 = device["src_addr_ipv4"]
# Extended Communities, new-format d_src_addr_ipv6 = device["src_addr_ipv6"]
if re.match("^([0-9]{0,5})\:([0-9]{1,5})$", ipprefix): d_location = device["location"]
query = json.dumps( d_name = device["name"]
{"cmd": cmd, "afi": "dual", "target": ipprefix} d_port = device["port"]
) d_type = device["type"]
msg = f"{ipprefix} matched new-format community."
code = 200 logger.info(f"Constructing {cmd} command for FRR router {d_name} to {ipprefix}...")
return (msg, code, router, query) # BGP Community Query
# Extended Communities, 32 bit format if cmd in ["bgp_community"]:
elif re.match("^[0-9]{1,10}$", ipprefix): # Extended Communities, new-format
query = json.dumps( query = json.dumps({"cmd": cmd, "afi": "dual", "target": ipprefix})
{"cmd": cmd, "afi": "dual", "target": ipprefix} if re.match("^([0-9]{0,5})\:([0-9]{1,5})$", ipprefix):
) msg = f"{ipprefix} matched new-format community."
msg = f"{ipprefix} matched 32 bit community." return (msg, code.success, d_address, query)
code = 200 # Extended Communities, 32 bit format
return (msg, code, router, query) elif re.match("^[0-9]{1,10}$", ipprefix):
# RFC 8092 Large Community Support msg = f"{ipprefix} matched 32 bit community."
elif re.match( return (msg, code.success, d_address, query)
"^([0-9]{1,10})\:([0-9]{1,10})\:[0-9]{1,10}$", ipprefix # RFC 8092 Large Community Support
): elif re.match("^([0-9]{1,10})\:([0-9]{1,10})\:[0-9]{1,10}$", ipprefix):
query = json.dumps( msg = f"{ipprefix} matched large community."
{"cmd": cmd, "afi": "dual", "target": ipprefix} return (msg, code.success, d_address, query)
) else:
msg = f"{ipprefix} matched large community." msg = f"{ipprefix} is an invalid BGP Community Format."
code = 200 logger.error(f"{msg}, {code.danger}, {d_name}, {query}")
return (msg, code, router, query) return (msg, code.danger, d_address, query)
else: # BGP AS_PATH Query
msg = f"{ipprefix} is an invalid BGP Community Format." elif cmd in ["bgp_aspath"]:
code = 415 if re.match(".*", ipprefix):
logger.error(f"{msg}, {code}, {router}, {cmd}, {ipprefix}") query = json.dumps({"cmd": cmd, "afi": "dual", "target": ipprefix})
return (msg, code, router, cmd, ipprefix) msg = f"{ipprefix} matched AS_PATH regex."
# BGP AS_PATH Query return (msg, code.success, d_address, query)
elif cmd in ["bgp_aspath"]: else:
if re.match(".*", ipprefix): msg = f"{ipprefix} is an invalid AS_PATH regex."
query = json.dumps( logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}")
{"cmd": cmd, "afi": "dual", "target": ipprefix} return (msg, code.danger, d_address, query)
) # BGP Route Query
msg = f"{ipprefix} matched AS_PATH regex." elif cmd in ["bgp_route"]:
code = 200 try:
return (msg, code, router, query) # Use netaddr library to verify if input is a valid IPv4 address or prefix
else: if IPNetwork(ipprefix).ip.version == 4:
msg = f"{ipprefix} is an invalid AS_PATH regex." query = json.dumps({"cmd": cmd, "afi": "ipv4", "target": ipprefix})
code = 415 msg = f"{ipprefix} is a valid IPv4 Adddress."
logger.error(f"{msg}, {code}, {router}, {cmd}, {ipprefix}") return (msg, code.success, d_address, query)
return (msg, code, router, query) # Use netaddr library to verify if input is a valid IPv6 address or prefix
# BGP Route Query elif IPNetwork(ipprefix).ip.version == 6:
elif cmd in ["bgp_route"]: query = json.dumps({"cmd": cmd, "afi": "ipv6", "target": ipprefix})
try: msg = f"{ipprefix} is a valid IPv6 Adddress."
# Use netaddr library to verify if input is a valid IPv4 address or prefix return (msg, code.success, d_address, query)
if IPNetwork(ipprefix).ip.version == 4: # Exception from netaddr library will return a user-facing error
query = json.dumps( except:
{"cmd": cmd, "afi": "ipv4", "target": ipprefix} msg = f"{ipprefix} is an invalid IP Address."
) logger.error(f"{msg}, {code.danger}, {d_name}, {query}")
msg = f"{ipprefix} is a valid IPv4 Adddress." return (msg, code.danger, d_address, query)
code = 200 # Ping/Traceroute
return (msg, code, router, query) elif cmd in ["ping", "traceroute"]:
# Use netaddr library to verify if input is a valid IPv6 address or prefix try:
elif IPNetwork(ipprefix).ip.version == 6: if IPNetwork(ipprefix).ip.version == 4:
query = json.dumps( query = json.dumps(
{"cmd": cmd, "afi": "ipv6", "target": ipprefix} {
) "cmd": cmd,
msg = f"{ipprefix} is a valid IPv6 Adddress." "afi": "ipv4",
code = 200 "source": d_src_addr_ipv4,
return (msg, code, router, query) "target": ipprefix,
# Exception from netaddr library will return a user-facing error }
except:
msg = f"{ipprefix} is an invalid IP Address."
code = 415
logger.error(f"{msg}, {code}, {router}, {cmd}, {ipprefix}")
return (msg, code, router, cmd, ipprefix)
# Ping/Traceroute
elif cmd in ["ping", "traceroute"]:
try:
if IPNetwork(ipprefix).ip.version == 4:
query = json.dumps(
{
"cmd": cmd,
"afi": "ipv4",
"source": src_addr_ipv4,
"target": ipprefix,
}
)
msg = f"{ipprefix} is a valid IPv4 Adddress."
code = 200
return (msg, code, router, query)
elif IPNetwork(ipprefix).ip.version == 6:
query = json.dumps(
{
"cmd": cmd,
"afi": "ipv6",
"source": src_addr_ipv6,
"target": ipprefix,
}
)
msg = f"{ipprefix} is a valid IPv6 Adddress."
code = 200
return (msg, code, router, query)
except:
msg = f"{ipprefix} is an invalid IP Address."
code = 415
logger.error(f"{msg}, {code}, {router}, {cmd}, {ipprefix}")
return (msg, code, router, cmd, ipprefix)
else:
msg = f"Command {cmd} not found."
code = 415
logger.error(f"{msg}, {code}, {router}, {cmd}, {ipprefix}")
return (msg, code, router, cmd, ipprefix)
except:
router_ip = r["address"]
error_msg = logger.error(
f"Input router IP {router} does not match the configured router IP of {router_ip}"
) )
raise ValueError(error_msg) msg = f"{ipprefix} is a valid IPv4 Adddress."
except: return (msg, code.success, d_address, query)
raise elif IPNetwork(ipprefix).ip.version == 6:
query = json.dumps(
{
"cmd": cmd,
"afi": "ipv6",
"source": d_src_addr_ipv6,
"target": ipprefix,
}
)
msg = f"{ipprefix} is a valid IPv6 Adddress."
return (msg, code.success, d_address, query)
except:
msg = f"{ipprefix} is an invalid IP Address."
logger.error(f"{msg}, {code.danger}, {d_name}, {query}")
return (msg, code.danger, d_name, query)
else:
msg = f"Command {cmd} not found."
logger.error(f"{msg}, {code.danger}, {d_name}, {query}")
return (msg, code.danger, d_name, query)
def netmiko(router, cmd, ipprefix): def ssh(cmd, ipprefix, device):
"""Receives JSON from Flask, constucts the command that will be passed to the router. Also handles input validation & error handling.""" """Receives JSON from Flask, constucts the command that will be passed to the router. Also handles input validation & error handling."""
logger.info(f"Constructing {cmd} command for {router} to {ipprefix}...") d_address = device["address"]
try: d_src_addr_ipv4 = device["src_addr_ipv4"]
# Loop through routers config file, match input router with configured routers, set variables d_src_addr_ipv6 = device["src_addr_ipv6"]
for r in routers_list: d_location = device["location"]
try: d_name = device["name"]
if router == r["address"]: d_port = device["port"]
type = r["type"] d_type = device["type"]
src_addr_ipv4 = r["src_addr_ipv4"]
src_addr_ipv6 = r["src_addr_ipv6"] logger.info(f"Constructing {cmd} command for {d_name} to {ipprefix}...")
# Loop through commands config file, set variables for matched commands # Loop through commands config file, set variables for matched commands
for nos in commands: class command:
if type == nos: def __init__(self, type):
nos = commands[type] if type in commands:
nos_commands = nos[0] self.dual = commands[type][0]["dual"]
# Dual stack commands (agnostic of IPv4/IPv6) self.ipv4 = commands[type][0]["ipv4"]
dual_commands = nos_commands["dual"] self.ipv6 = commands[type][0]["ipv6"]
# IPv4 Specific Commands else:
ipv4_commands = nos_commands["ipv4"] msg = f"{d_type} is an unsupported network operating system."
# IPv6 Specific Commands logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}")
ipv6_commands = nos_commands["ipv6"] return (msg, code.danger, d_name, cmd, ipprefix)
if cmd == "Query Type":
msg = "You must select a query type." c = command(d_type)
code = 415 # BGP Community Query
logger.error( if cmd == "bgp_community":
f"{msg}, {code}, {router}, {cmd}, {ipprefix}" # Extended Communities, new-format
) if re.match("^([0-9]{0,5})\:([0-9]{1,5})$", ipprefix):
return (msg, code, router, cmd, ipprefix) mc = c.dual[cmd]
# BGP Community Query command = mc.format(target=ipprefix)
elif cmd in ["bgp_community"]: msg = f"{ipprefix} matched new-format community."
# Extended Communities, new-format return (msg, code.success, d_address, d_type, command)
if re.match("^([0-9]{0,5})\:([0-9]{1,5})$", ipprefix): # Extended Communities, 32 bit format
for a, c in dual_commands.items(): elif re.match("^[0-9]{1,10}$", ipprefix):
if a == cmd: mc = c.dual[cmd]
command = c.format(target=ipprefix) command = mc.format(target=ipprefix)
msg = f"{ipprefix} matched new-format community." msg = f"{ipprefix} matched 32 bit community."
code = 200 return (msg, code.success, d_address, d_type, command)
return (msg, code, router, type, command) # RFC 8092 Large Community Support
# Extended Communities, 32 bit format elif re.match("^([0-9]{1,10})\:([0-9]{1,10})\:[0-9]{1,10}$", ipprefix):
elif re.match("^[0-9]{1,10}$", ipprefix): mc = c.dual[cmd]
for a, c in dual_commands.items(): command = mc.format(target=ipprefix)
if a == cmd: msg = f"{ipprefix} matched large community."
command = c.format(target=ipprefix) return (msg, code.success, d_address, d_type, command)
msg = ( else:
f"{ipprefix} matched 32 bit community." msg = f"{ipprefix} is an invalid BGP Community Format."
) logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}")
code = 200 return (msg, code.danger, d_name, cmd, ipprefix)
return (msg, code, router, type, command) # BGP AS_PATH Query
# RFC 8092 Large Community Support elif cmd == "bgp_aspath":
elif re.match( if re.match(".*", ipprefix):
"^([0-9]{1,10})\:([0-9]{1,10})\:[0-9]{1,10}$", mc = c.dual[cmd]
ipprefix, command = mc.format(target=ipprefix)
): msg = f"{ipprefix} matched AS_PATH regex."
for a, c in dual_commands.items(): return (msg, code.success, d_address, d_type, command)
if a == cmd: else:
command = c.format(target=ipprefix) msg = f"{ipprefix} is an invalid AS_PATH regex."
msg = f"{ipprefix} matched large community." logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}")
code = 200 return (msg, code.danger, d_name, cmd, ipprefix)
return (msg, code, router, type, command) # BGP Route Query
else: elif cmd == "bgp_route":
msg = f"{ipprefix} is an invalid BGP Community Format." try:
code = 415 # Use netaddr library to verify if input is a valid IPv4 address or prefix
logger.error( if IPNetwork(ipprefix).ip.version == 4:
f"{msg}, {code}, {router}, {cmd}, {ipprefix}" mc = c.ipv4[cmd]
) command = mc.format(target=ipprefix)
return (msg, code, router, cmd, ipprefix) msg = f"{ipprefix} is a valid IPv4 Adddress."
# BGP AS_PATH Query return (msg, code.success, d_address, d_type, command)
elif cmd in ["bgp_aspath"]: # Use netaddr library to verify if input is a valid IPv6 address or prefix
if re.match(".*", ipprefix): elif IPNetwork(ipprefix).ip.version == 6:
for a, c in dual_commands.items(): mc = c.ipv6[cmd]
if a == cmd: command = mc.format(target=ipprefix)
command = c.format(target=ipprefix) msg = f"{ipprefix} is a valid IPv6 Adddress."
msg = f"{ipprefix} matched AS_PATH regex." return (msg, code.success, d_address, d_type, command)
code = 200 # Exception from netaddr library will return a user-facing error
return (msg, code, router, type, command) except:
else: msg = f"{ipprefix} is an invalid IP Address."
msg = f"{ipprefix} is an invalid AS_PATH regex." logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}")
code = 415 return (msg, code.danger, d_name, cmd, ipprefix)
logger.error( # Ping/Traceroute
f"{msg}, {code}, {router}, {cmd}, {ipprefix}" elif cmd in ["ping", "traceroute"]:
) try:
return (msg, code, router, cmd, ipprefix) if IPNetwork(ipprefix).ip.version == 4:
# BGP Route Query mc = c.ipv4[cmd]
elif cmd in ["bgp_route"]: command = mc.format(target=ipprefix, src_addr_ipv4=d_src_addr_ipv4)
try: msg = f"{ipprefix} is a valid IPv4 Adddress."
# Use netaddr library to verify if input is a valid IPv4 address or prefix return (msg, code.success, d_address, d_type, command)
if IPNetwork(ipprefix).ip.version == 4: elif IPNetwork(ipprefix).ip.version == 6:
for a, c in ipv4_commands.items(): mc = c.ipv6[cmd]
if a == cmd: command = mc.format(target=ipprefix, src_addr_ipv6=d_src_addr_ipv6)
command = c.format(target=ipprefix) msg = f"{ipprefix} is a valid IPv6 Adddress."
msg = f"{ipprefix} is a valid IPv4 Adddress." return (msg, code.success, d_address, d_type, command)
code = 200 except:
return ( msg = f"{ipprefix} is an invalid IP Address."
msg, logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}")
code, return (msg, code.danger, d_name, cmd, ipprefix)
router, else:
type, msg = f"Command {cmd} not found."
command, logger.error(f"{msg}, {code.danger}, {d_name}, {cmd}, {ipprefix}")
) return (msg, code.danger, d_name, cmd, ipprefix)
# Use netaddr library to verify if input is a valid IPv6 address or prefix
elif IPNetwork(ipprefix).ip.version == 6:
for a, c in ipv6_commands.items():
if a == cmd:
command = c.format(target=ipprefix)
msg = f"{ipprefix} is a valid IPv6 Adddress."
code = 200
return (
msg,
code,
router,
type,
command,
)
# Exception from netaddr library will return a user-facing error
except:
msg = f"{ipprefix} is an invalid IP Address."
code = 415
logger.error(
f"{msg}, {code}, {router}, {cmd}, {ipprefix}"
)
return (msg, code, router, cmd, ipprefix)
# Ping/Traceroute
elif cmd in ["ping", "traceroute"]:
try:
if IPNetwork(ipprefix).ip.version == 4:
for a, c in ipv4_commands.items():
if a == cmd:
command = c.format(
target=ipprefix,
src_addr_ipv4=src_addr_ipv4,
)
msg = f"{ipprefix} is a valid IPv4 Adddress."
code = 200
return (
msg,
code,
router,
type,
command,
)
elif IPNetwork(ipprefix).ip.version == 6:
for a, c in ipv6_commands.items():
if a == cmd:
command = c.format(
target=ipprefix,
src_addr_ipv6=src_addr_ipv6,
)
msg = f"{ipprefix} is a valid IPv6 Adddress."
code = 200
return (
msg,
code,
router,
type,
command,
)
except:
msg = f"{ipprefix} is an invalid IP Address."
code = 415
logger.error(
f"{msg}, {code}, {router}, {cmd}, {ipprefix}"
)
return (msg, code, router, cmd, ipprefix)
else:
msg = f"Command {cmd} not found."
code = 415
logger.error(
f"{msg}, {code}, {router}, {cmd}, {ipprefix}"
)
return (msg, code, router, cmd, ipprefix)
except:
router_ip = r["address"]
error_msg = logger.error(
f"Input router IP {router} does not match the configured router IP of {router_ip}"
)
raise ValueError(error_msg)
except:
raise

View file

@ -32,12 +32,19 @@ class device:
for r in routers_list: for r in routers_list:
if r["location"] == lg_router: if r["location"] == lg_router:
self.address = r["address"] self.address = r["address"]
self.port = r["port"] self.asn = r["asn"]
self.type = r["type"] self.src_addr_ipv4 = r["src_addr_ipv4"]
self.src_addr_ipv6 = r["src_addr_ipv6"]
self.credential = r["credential"] self.credential = r["credential"]
self.location = r["location"] self.location = r["location"]
self.name = r["name"]
self.port = r["port"]
self.type = r["type"]
self.proxy = r["proxy"] self.proxy = r["proxy"]
def __call__(self):
return vars(self)
class credential: class credential:
def __init__(self, cred): def __init__(self, cred):
@ -60,20 +67,20 @@ class params:
class http: class http:
def __init__(self): def __init__(self):
self.msg, self.status, self.router, self.query = construct.frr( self.msg, self.status, self.router, self.query = construct.frr(
d.address, lg_cmd, lg_ipprefix lg_cmd, lg_ipprefix, d()
) )
def all(self): def __call__(self):
return self.msg, self.status, self.router, self.query return vars(self)
class ssh: class ssh:
def __init__(self): def __init__(self):
self.msg, self.status, self.router, self.type, self.command = construct.netmiko( self.msg, self.status, self.router, self.type, self.command = construct.ssh(
d.address, lg_cmd, lg_ipprefix lg_cmd, lg_ipprefix, d()
) )
def all(self): def __call__(self):
return self.msg, self.status, self.router, self.type, self.command return vars(self)
def nm_host(self): def nm_host(self):
c = credential(d.credential) c = credential(d.credential)
@ -104,15 +111,14 @@ class connect:
"""Sends HTTP POST to router running the hyperglass-frr API""" """Sends HTTP POST to router running the hyperglass-frr API"""
http = params().http() http = params().http()
c = credential(d.credential) c = credential(d.credential)
logger.error(f"API Key: {c.password}")
try: try:
headers = {"Content-Type": "application/json", "X-API-Key": c.password} headers = {"Content-Type": "application/json", "X-API-Key": c.password}
json_query = json.dumps(http.query) json_query = json.dumps(http.query)
frr_endpoint = f"http://{d.address}:{d.port}/frr" frr_endpoint = f"http://{d.address}:{d.port}/frr"
frr_output = requests.post( frr_response = requests.post(
frr_endpoint, headers=headers, data=json_query frr_endpoint, headers=headers, data=json_query
) )
return frr_output return frr_response.text, frr_response.status_code
except: except:
raise raise
@ -130,6 +136,7 @@ class connect:
ssh = params().ssh() ssh = params().ssh()
nm_proxy = ssh.nm_proxy() nm_proxy = ssh.nm_proxy()
nm_host = ssh.nm_host() nm_host = ssh.nm_host()
dp = proxy(d.proxy)
nm_connect_proxied = ConnectHandler(**nm_proxy) nm_connect_proxied = ConnectHandler(**nm_proxy)
nm_ssh_command = dp.ssh_command.format(**nm_host) + "\n" nm_ssh_command = dp.ssh_command.format(**nm_host) + "\n"
@ -188,9 +195,14 @@ def execute(lg_data):
# If netaddr library throws an exception, return a user-facing error. # If netaddr library throws an exception, return a user-facing error.
except: except:
msg = f"{lg_ipprefix} is not a valid IP Address." msg = f"{lg_ipprefix} is not a valid IP Address."
code = 415 code = 405
logger.error(f"{msg}, {code}, {lg_data}") logger.error(f"{msg}, {code}, {lg_data}")
return (msg, code, lg_data) return (msg, code, lg_data)
elif lg_cmd == "Query Type":
msg = "You must select a query type."
code = 405
logger.error(f"{msg}, {code}, {lg_data}")
return (msg, code, lg_data)
global d global d
d = device(lg_router) d = device(lg_router)
@ -198,23 +210,33 @@ def execute(lg_data):
if d.type == "frr": if d.type == "frr":
http = params().http() http = params().http()
try: try:
output = connect.restapi.frr() if http.status in range(200, 300):
parsed_output = parse.parse(output, d.type, lg_cmd) output, frr_status = connect.restapi.frr()
return parsed_output, http.status, params().http().all() parsed_output = parse.parse(output, d.type, lg_cmd)
return parsed_output, frr_status, http()
elif http.status in range(400, 500):
return http.msg, http.status, http()
else:
logger.error(general_error, 500, http())
return general_error, 500, http()
except: except:
raise raise
else: else:
try: try:
ssh = params().ssh() ssh = params().ssh()
if d.proxy: if ssh.status in range(200, 300):
global dp if d.proxy:
dp = proxy(d.proxy) output = connect.nm.proxied(d.proxy)
output = connect.nm.proxied(d.proxy) parsed_output = parse.parse(output, d.type, lg_cmd)
parsed_output = parse.parse(output, d.type, lg_cmd) return parsed_output, ssh.status, ssh.router, ssh.command
return parsed_output, ssh.status, ssh.router, ssh.command elif not d.proxy:
elif not d.proxy: output = connect.nm.direct()
output = connect.nm.direct() parsed_output = parse.parse(output, d.type, lg_cmd)
parsed_output = parse.parse(output, d.type, lg_cmd) return parsed_output, ssh.status, ssh.router, ssh.command
return parsed_output, ssh.status, ssh.router, ssh.command elif ssh.status in range(400, 500):
return ssh.msg, ssh.status, ssh()
else:
logger.error(general_error, 500, ssh())
return general_error, 500, ssh()
except: except:
raise raise