From e685c8a7735ebca442b28e287fe0f7e191e8d4f3 Mon Sep 17 00:00:00 2001 From: forest Date: Thu, 8 Jul 2021 14:10:14 -0500 Subject: [PATCH] start working on managed IPs --- capsulflask/admin.py | 57 +++++++++++++++++++ capsulflask/auth.py | 16 ++++++ capsulflask/db_model.py | 45 +++++++++++++-- .../schema_migrations/16_down_managed_ips.sql | 6 ++ .../schema_migrations/16_up_managed_ips.sql | 14 +++++ 5 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 capsulflask/admin.py create mode 100644 capsulflask/schema_migrations/16_down_managed_ips.sql create mode 100644 capsulflask/schema_migrations/16_up_managed_ips.sql diff --git a/capsulflask/admin.py b/capsulflask/admin.py new file mode 100644 index 0000000..0a9cba4 --- /dev/null +++ b/capsulflask/admin.py @@ -0,0 +1,57 @@ +import re +import sys +import json +import ipaddress +from datetime import datetime, timedelta +from flask import Blueprint +from flask import flash +from flask import current_app +from flask import g +from flask import request +from flask import session +from flask import render_template +from flask import redirect +from flask import url_for +from werkzeug.exceptions import abort + +from capsulflask.metrics import durations as metric_durations +from capsulflask.auth import admin_account_required +from capsulflask.db import get_model +from capsulflask.shared import my_exec_info_message + +bp = Blueprint("admin", __name__, url_prefix="/admin") + +@bp.route("/") +@admin_account_required +def index(): + hosts = get_model().list_hosts_with_networks() + vms = get_model().all_non_deleted_vms() + operations = get_model().list_all_operations() + + display_hosts = [] + + for kv in hosts.items(): + name = kv[0] + value = kv[1] + + for network in value['networks']: + network["network_name"] + + ipv4_network = ipaddress.ip_network(network["public_ipv4_cidr_block"], False) + network_start_int = -1 + network_end_int = -1 + i = 0 + for ipv4_address in ipv4_network: + i += 1 + if i > 2: + if network_start_int == -1: + network_start_int = int(ipv4_address) + + network_end_int = int(ipv4_address) + + + + display_hosts.append(dict(name=name, last_health_check=value['last_health_check'])) + + + return render_template("admin.html", vms=mappedVms, has_vms=len(vms) > 0, created=created) diff --git a/capsulflask/auth.py b/capsulflask/auth.py index 4aacc74..5244a24 100644 --- a/capsulflask/auth.py +++ b/capsulflask/auth.py @@ -31,6 +31,22 @@ def account_required(view): return wrapped_view + +def admin_account_required(view): + """View decorator that redirects non-admin users to the login page.""" + + @functools.wraps(view) + def wrapped_view(**kwargs): + if session.get("account") is None or session.get("csrf-token") is None: + return redirect(url_for("auth.login")) + + if session.get("account") not in current_app.config["ADMIN_EMAIL_ADDRESSES_CSV"].split(","): + return redirect(url_for("auth.login")) + + return view(**kwargs) + + return wrapped_view + @bp.route("/login", methods=("GET", "POST")) def login(): if request.method == "POST": diff --git a/capsulflask/db_model.py b/capsulflask/db_model.py index 50a1e1a..6f134d8 100644 --- a/capsulflask/db_model.py +++ b/capsulflask/db_model.py @@ -56,12 +56,15 @@ class DBModel: # ------ VM & ACCOUNT MANAGEMENT --------- + def all_non_deleted_vms(self): + self.cursor.execute("SELECT id, host, network_name, last_seen_ipv4, last_seen_ipv6 FROM vms WHERE deleted IS NULL") + return list(map(lambda x: dict(id=x[0], host=x[1], network_name=x[2], last_seen_ipv4=x[3], last_seen_ipv6=x[4]), self.cursor.fetchall())) - def all_non_deleted_vm_ids(self,): + def all_non_deleted_vm_ids(self): self.cursor.execute("SELECT id FROM vms WHERE deleted IS NULL") return list(map(lambda x: x[0], self.cursor.fetchall())) - def operating_systems_dict(self,): + def operating_systems_dict(self): self.cursor.execute("SELECT id, template_image_file_name, description FROM os_images WHERE deprecated = FALSE") operatingSystems = dict() @@ -70,7 +73,7 @@ class DBModel: return operatingSystems - def vm_sizes_dict(self,): + def vm_sizes_dict(self): self.cursor.execute("SELECT id, dollars_per_month, vcpus, memory_mb, bandwidth_gb_per_month FROM vm_sizes") vmSizes = dict() @@ -301,6 +304,21 @@ class DBModel: # ------ HOSTS --------- + def list_hosts_with_networks(self): + self.cursor.execute(""" + SELECT hosts.id, hosts.last_health_check, host_network.network_name, host_network.public_ipv4_cidr_block FROM hosts + JOIN host_network ON host_network.host = hosts.id + """) + + hosts = dict() + for row in self.cursor.fetchall(): + if row[0] not in hosts: + hosts[row[0]] = dict(last_health_check=row[1], networks=[]) + + hosts[row[0]]["networks"].append(dict(network_name=row[2], public_ipv4_cidr_block=row[3])) + + return hosts + def authorized_for_host(self, id, token) -> bool: self.cursor.execute("SELECT id FROM hosts WHERE id = %s AND token = %s", (id, token)) return self.cursor.fetchone() != None @@ -317,8 +335,27 @@ class DBModel: self.cursor.execute("SELECT id, https_url FROM hosts WHERE last_health_check > NOW() - INTERVAL '20 seconds'") return list(map(lambda x: OnlineHost(id=x[0], url=x[1]), self.cursor.fetchall())) - def create_operation(self, online_hosts: List[OnlineHost], email: str, payload: str) -> int: + def list_all_operations(self): + self.cursor.execute(""" + SELECT operations.id, operations.email, operations.created, operations.payload, + host_operation.host host_operation.assignment_status, host_operation.assigned, + host_operation.completed, host_operation.results FROM operations + JOIN host_operation ON host_operation.operation = operations.id + """) + operations = dict() + for row in self.cursor.fetchall(): + if row[0] not in operations: + operations[row[0]] = dict(email=row[1], created=row[2], payload=row[3], hosts=[]) + + operations[row[0]]["hosts"].append(dict( + host=row[4], assignment_status=row[5], assigned=row[6], + completed=row[7], results=row[8], + )) + + return operations + + def create_operation(self, online_hosts: List[OnlineHost], email: str, payload: str) -> int: self.cursor.execute( "INSERT INTO operations (email, payload) VALUES (%s, %s) RETURNING id", (email, payload) ) operation_id = self.cursor.fetchone()[0] diff --git a/capsulflask/schema_migrations/16_down_managed_ips.sql b/capsulflask/schema_migrations/16_down_managed_ips.sql new file mode 100644 index 0000000..fb0f031 --- /dev/null +++ b/capsulflask/schema_migrations/16_down_managed_ips.sql @@ -0,0 +1,6 @@ + +DROP TABLE host_network; + +ALTER TABLE vms DROP COLUMN network; + +UPDATE schemaversion SET version = 15; diff --git a/capsulflask/schema_migrations/16_up_managed_ips.sql b/capsulflask/schema_migrations/16_up_managed_ips.sql new file mode 100644 index 0000000..695c46c --- /dev/null +++ b/capsulflask/schema_migrations/16_up_managed_ips.sql @@ -0,0 +1,14 @@ + +CREATE TABLE host_network ( + public_ipv4_cidr_block TEXT PRIMARY KEY NOT NULL, + network_name TEXT NOT NULL, + host TEXT NOT NULL REFERENCES hosts(id) ON DELETE RESTRICT, +); + +INSERT INTO host_network (public_ipv4_cidr_block, network_name, host) VALUES ('baikal', 'virbr1', '69.61.2.162/27'), + ('baikal', 'virbr2', '69.61.2.194/26'); + +ALTER TABLE vms ADD COLUMN network_name TEXT NOT NULL; +ALTER TABLE vms ADD FOREIGN KEY (host, network_name) REFERENCES host_network(host, network_name) ON DELETE RESTRICT; + +UPDATE schemaversion SET version = 16;