mirror of
https://github.com/checktheroads/hyperglass
synced 2024-05-11 05:55:08 +00:00
171 lines
5.3 KiB
Python
171 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# Module Imports
|
|
import os
|
|
import sys
|
|
import json
|
|
import toml
|
|
from logzero import logger
|
|
from flask import Flask, request, Response, jsonify, flash
|
|
from flask_caching import Cache
|
|
from flask_limiter import Limiter
|
|
from flask_limiter.util import get_remote_address
|
|
|
|
# Local Imports
|
|
import hyperglass.configuration as configuration
|
|
from hyperglass.command import execute
|
|
from hyperglass import render
|
|
|
|
# Load TOML config file
|
|
devices = configuration.devices()
|
|
# Filter config file to list of routers & subsequent configurations
|
|
routers_list = devices["router"]
|
|
# Filter config file to array of operating systems that require IPv6 BGP lookups in CIDR format
|
|
ipv6_cidr_list = configuration.requires_ipv6_cidr()
|
|
# Main Flask definition
|
|
app = Flask(__name__, static_url_path="/static")
|
|
|
|
# Flask-Limiter Config
|
|
rate_limit_query = configuration.gen.rate_limit_query() + " per minute"
|
|
rate_limit_site = configuration.gen.rate_limit_site() + "per minute"
|
|
limiter = Limiter(app, key_func=get_remote_address, default_limits=[rate_limit_site])
|
|
|
|
|
|
def renderCSS():
|
|
try:
|
|
render.css.renderTemplate()
|
|
except:
|
|
raise
|
|
|
|
|
|
# Render Main Flask-Limiter Error Message
|
|
@app.errorhandler(429)
|
|
def error429(e):
|
|
"""Renders full error page for too many site queries"""
|
|
html = render.html.renderTemplate("429")
|
|
return html, 429
|
|
|
|
|
|
def error415():
|
|
"""Renders full error page for generic errors"""
|
|
html = render.html.renderTemplate("415")
|
|
return html, 415
|
|
|
|
|
|
def errorQuery():
|
|
"""Renders modal error message"""
|
|
return 429
|
|
|
|
|
|
def errorGeneral(id):
|
|
"""Renders notification error message with an ID number"""
|
|
return "An unknown error occurred." + "\s" + id, 415
|
|
|
|
|
|
# Flask-Caching Config
|
|
cache = Cache(
|
|
app,
|
|
config={
|
|
"CACHE_TYPE": "filesystem",
|
|
"CACHE_DIR": configuration.gen.cache_directory(),
|
|
"CACHE_DEFAULT_TIMEOUT": configuration.gen.cache_timeout(),
|
|
},
|
|
)
|
|
|
|
|
|
def clearCache():
|
|
"""Function to clear the Flask-Caching cache"""
|
|
with app.app_context():
|
|
try:
|
|
cache.clear()
|
|
except:
|
|
raise
|
|
|
|
|
|
# Main / Flask route where html is rendered via Jinja2
|
|
@app.route("/", methods=["GET"])
|
|
@limiter.limit(rate_limit_site)
|
|
def site():
|
|
"""Main front-end web application"""
|
|
html = render.html.renderTemplate("index")
|
|
return html
|
|
|
|
|
|
# Test route for various tests
|
|
@app.route("/test", methods=["GET"])
|
|
def testRoute():
|
|
html = render.html.renderTemplate("test")
|
|
return html
|
|
|
|
|
|
# Flask GET route provides a JSON list of all routers for the selected network/ASN
|
|
@app.route("/routers/<asn>", methods=["GET"])
|
|
def get_routers(asn):
|
|
results = []
|
|
# For any configured router matching the queried ASN, return only the address/hostname, location, and OS type of the matching routers
|
|
for r in routers_list:
|
|
if r["asn"] == asn:
|
|
if r["type"] in ipv6_cidr_list:
|
|
results.append(
|
|
dict(
|
|
location=r["location"],
|
|
hostname=r["name"],
|
|
type=r["type"],
|
|
requiresIP6Cidr=True,
|
|
)
|
|
)
|
|
else:
|
|
results.append(
|
|
dict(
|
|
location=r["location"],
|
|
hostname=r["name"],
|
|
type=r["type"],
|
|
requiresIP6Cidr=False,
|
|
)
|
|
)
|
|
results_json = json.dumps(results)
|
|
return results_json
|
|
|
|
|
|
# Flask POST route ingests data from the JS form submit, passes it to the backend looking glass application to perform the filtering/lookups
|
|
@app.route("/lg", methods=["POST"])
|
|
# Invoke Flask-Limiter with configured rate limit
|
|
@limiter.limit(rate_limit_query)
|
|
def lg():
|
|
"""Main backend application initiator"""
|
|
lg_data = request.get_json()
|
|
# Stringify the form response containing serialized JSON for the request, use as key for k/v cache store so each command output value is unique
|
|
cache_key = str(lg_data)
|
|
# Check if cached entry exists
|
|
if cache.get(cache_key) is None:
|
|
cache_value = execute.execute(lg_data)
|
|
value_output = cache_value[0]
|
|
value_code = cache_value[1]
|
|
value_entry = cache_value[0:2]
|
|
value_params = cache_value[2:]
|
|
logger.info(f"No cache match for: {cache_key}")
|
|
# If it doesn't, create a cache entry
|
|
try:
|
|
cache.set(cache_key, value_entry)
|
|
logger.info(f"Added cache entry: {value_params}")
|
|
except:
|
|
raise RuntimeError("Unable to add output to cache.", 415, *value_params)
|
|
# If 200, return output
|
|
response = cache.get(cache_key)
|
|
if value_code == 200:
|
|
return Response(response[0], response[1])
|
|
# If 400 error, return error message and code
|
|
elif value_code in [405, 415]:
|
|
return Response(response[0], response[1])
|
|
# If it does, return the cached entry
|
|
else:
|
|
logger.info(f"Cache match for: {cache_key}, returning cached entry...")
|
|
response = cache.get(cache_key)
|
|
try:
|
|
return Response(response[0], response[1])
|
|
except:
|
|
raise
|
|
# Upon exception, render generic error
|
|
log.error(f"Error returning cached entry for: {cache_key}")
|
|
return Response(errorGeneral(4152))
|