first crack at adding ssh host key display to the capsul detail page

This commit is contained in:
forest 2021-01-30 01:39:48 -06:00
parent 29008bc963
commit 50cea6e0b4
13 changed files with 199 additions and 51 deletions

View File

@ -11,7 +11,8 @@ from flask import current_app
from psycopg2 import ProgrammingError from psycopg2 import ProgrammingError
from flask_mail import Message from flask_mail import Message
from capsulflask.db import get_model, my_exec_info_message from capsulflask.db import get_model
from capsulflask.shared import my_exec_info_message
from capsulflask.console import get_account_balance from capsulflask.console import get_account_balance
bp = Blueprint('cli', __name__) bp = Blueprint('cli', __name__)

View File

@ -1,5 +1,6 @@
import re import re
import sys import sys
import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
from flask import Blueprint from flask import Blueprint
from flask import flash from flask import flash
@ -15,7 +16,8 @@ from nanoid import generate
from capsulflask.metrics import durations as metric_durations from capsulflask.metrics import durations as metric_durations
from capsulflask.auth import account_required from capsulflask.auth import account_required
from capsulflask.db import get_model, my_exec_info_message from capsulflask.db import get_model
from capsulflask.shared import my_exec_info_message
from capsulflask.payment import poll_btcpay_session from capsulflask.payment import poll_btcpay_session
from capsulflask import cli from capsulflask import cli
@ -25,19 +27,21 @@ def makeCapsulId():
lettersAndNumbers = generate(alphabet="1234567890qwertyuiopasdfghjklzxcvbnm", size=10) lettersAndNumbers = generate(alphabet="1234567890qwertyuiopasdfghjklzxcvbnm", size=10)
return f"capsul-{lettersAndNumbers}" return f"capsul-{lettersAndNumbers}"
def double_check_capsul_address(id, ipv4): def double_check_capsul_address(id, ipv4, get_ssh_host_keys):
try: try:
result = current_app.config["VIRTUALIZATION_MODEL"].get(id) result = current_app.config["VIRTUALIZATION_MODEL"].get(id, get_ssh_host_keys)
if result.ipv4 != ipv4: if result.ipv4 != ipv4:
ipv4 = result.ipv4 ipv4 = result.ipv4
get_model().update_vm_ip(email=session["account"], id=id, ipv4=result.ipv4) get_model().update_vm_ip(email=session["account"], id=id, ipv4=result.ipv4)
if get_ssh_host_keys:
get_model().update_vm_ssh_host_keys(email=session["account"], id=id, ssh_host_keys=result.ssh_host_keys)
except: except:
current_app.logger.error(f""" current_app.logger.error(f"""
the virtualization model threw an error in double_check_capsul_address of {id}: the virtualization model threw an error in double_check_capsul_address of {id}:
{my_exec_info_message(sys.exc_info())}""" {my_exec_info_message(sys.exc_info())}"""
) )
return ipv4 return result
@bp.route("/") @bp.route("/")
@account_required @account_required
@ -53,7 +57,7 @@ def index():
# for now we are going to check the IP according to the virt model # for now we are going to check the IP according to the virt model
# on every request. this could be done by a background job and cached later on... # on every request. this could be done by a background job and cached later on...
for vm in vms: for vm in vms:
vm["ipv4"] = double_check_capsul_address(vm["id"], vm["ipv4"]) vm["ipv4"] = double_check_capsul_address(vm["id"], vm["ipv4"], False).ipv4
vms = list(map( vms = list(map(
lambda x: dict( lambda x: dict(
@ -104,9 +108,17 @@ def detail(id):
return render_template("capsul-detail.html", vm=vm, delete=True, deleted=True) return render_template("capsul-detail.html", vm=vm, delete=True, deleted=True)
else: else:
vm["ipv4"] = double_check_capsul_address(vm["id"], vm["ipv4"]) needs_ssh_host_keys = "ssh_host_keys" not in vm or len(vm["ssh_host_keys"]) == 0
vm_from_virt_model = double_check_capsul_address(vm["id"], vm["ipv4"], needs_ssh_host_keys)
vm["ipv4"] = vm_from_virt_model.ipv4
if needs_ssh_host_keys:
vm["ssh_host_keys"] = vm_from_virt_model.ssh_host_keys
vm["created"] = vm['created'].strftime("%b %d %Y %H:%M") vm["created"] = vm['created'].strftime("%b %d %Y %H:%M")
vm["ssh_public_keys"] = ", ".join(vm["ssh_public_keys"]) if len(vm["ssh_public_keys"]) > 0 else "<missing>" vm["ssh_authorized_keys"] = ", ".join(vm["ssh_authorized_keys"]) if len(vm["ssh_authorized_keys"]) > 0 else "<missing>"
current_app.logger.info(f"asd {needs_ssh_host_keys} {json.dumps(vm['ssh_host_keys'])})")
return render_template( return render_template(
"capsul-detail.html", "capsul-detail.html",
@ -123,12 +135,12 @@ def detail(id):
def create(): def create():
vm_sizes = get_model().vm_sizes_dict() vm_sizes = get_model().vm_sizes_dict()
operating_systems = get_model().operating_systems_dict() operating_systems = get_model().operating_systems_dict()
ssh_public_keys = get_model().list_ssh_public_keys_for_account(session["account"]) public_keys_for_account = get_model().list_ssh_public_keys_for_account(session["account"])
account_balance = get_account_balance(get_vms(), get_payments(), datetime.utcnow()) account_balance = get_account_balance(get_vms(), get_payments(), datetime.utcnow())
capacity_avaliable = current_app.config["VIRTUALIZATION_MODEL"].capacity_avaliable(512*1024*1024) capacity_avaliable = current_app.config["VIRTUALIZATION_MODEL"].capacity_avaliable(512*1024*1024)
errors = list() errors = list()
ssh_keys_from_db_string = "\n".join(list(map(lambda x: f"name: {x['name']}**content: {x['content']}", ssh_public_keys))) ssh_keys_from_db_string = "\n".join(list(map(lambda x: f"name: {x['name']}**content: {x['content']}", public_keys_for_account)))
email_to_log = session["account"] email_to_log = session["account"]
current_app.logger.info(f"create for {email_to_log}: ssh keys from db:\n {ssh_keys_from_db_string}") current_app.logger.info(f"create for {email_to_log}: ssh keys from db:\n {ssh_keys_from_db_string}")
@ -161,7 +173,7 @@ def create():
posted_name = request.form[f"ssh_key_{i}"] posted_name = request.form[f"ssh_key_{i}"]
current_app.logger.info(f"ssh key posted_name: {posted_name}") current_app.logger.info(f"ssh key posted_name: {posted_name}")
key = None key = None
for x in ssh_public_keys: for x in public_keys_for_account:
if x['name'] == posted_name: if x['name'] == posted_name:
current_app.logger.info(f"ssh key posted_name {posted_name} was found") current_app.logger.info(f"ssh key posted_name {posted_name} was found")
key = x key = x
@ -190,7 +202,7 @@ def create():
id=id, id=id,
size=size, size=size,
os=os, os=os,
ssh_public_keys=list(map(lambda x: x["name"], posted_keys)) ssh_authorized_keys=list(map(lambda x: x["name"], posted_keys))
) )
current_app.config["VIRTUALIZATION_MODEL"].create( current_app.config["VIRTUALIZATION_MODEL"].create(
email = session["account"], email = session["account"],
@ -198,7 +210,7 @@ def create():
template_image_file_name=operating_systems[os]['template_image_file_name'], template_image_file_name=operating_systems[os]['template_image_file_name'],
vcpus=vm_sizes[size]['vcpus'], vcpus=vm_sizes[size]['vcpus'],
memory_mb=vm_sizes[size]['memory_mb'], memory_mb=vm_sizes[size]['memory_mb'],
ssh_public_keys=list(map(lambda x: x["content"], posted_keys)) ssh_authorized_keys=list(map(lambda x: x["content"], posted_keys))
) )
return redirect(f"{url_for('console.index')}?created={id}") return redirect(f"{url_for('console.index')}?created={id}")
@ -219,9 +231,9 @@ def create():
csrf_token = session["csrf-token"], csrf_token = session["csrf-token"],
capacity_avaliable=capacity_avaliable, capacity_avaliable=capacity_avaliable,
account_balance=format(account_balance, '.2f'), account_balance=format(account_balance, '.2f'),
ssh_public_keys=ssh_public_keys, ssh_public_keys=public_keys_for_account,
ssh_public_key_count=len(ssh_public_keys), ssh_public_key_count=len(public_keys_for_account),
no_ssh_public_keys=len(ssh_public_keys) == 0, no_ssh_public_keys=len(public_keys_for_account) == 0,
operating_systems=operating_systems, operating_systems=operating_systems,
cant_afford=len(affordable_vm_sizes) == 0, cant_afford=len(affordable_vm_sizes) == 0,
vm_sizes=affordable_vm_sizes vm_sizes=affordable_vm_sizes

View File

@ -9,6 +9,7 @@ from flask import current_app
from flask import g from flask import g
from capsulflask.db_model import DBModel from capsulflask.db_model import DBModel
from capsulflask.shared import my_exec_info_message
def init_app(app): def init_app(app):
databaseUrl = urlparse(app.config['DATABASE_URL']) databaseUrl = urlparse(app.config['DATABASE_URL'])
@ -41,7 +42,7 @@ def init_app(app):
hasSchemaVersionTable = False hasSchemaVersionTable = False
actionWasTaken = False actionWasTaken = False
schemaVersion = 0 schemaVersion = 0
desiredSchemaVersion = 11 desiredSchemaVersion = 12
cursor = connection.cursor() cursor = connection.cursor()
@ -127,5 +128,3 @@ def close_db(e=None):
db_model.cursor.close() db_model.cursor.close()
current_app.config['PSYCOPG2_CONNECTION_POOL'].putconn(db_model.connection) current_app.config['PSYCOPG2_CONNECTION_POOL'].putconn(db_model.connection)
def my_exec_info_message(exec_info):
return "{}: {}".format(".".join([exec_info[0].__module__, exec_info[0].__name__]), exec_info[1])

View File

@ -104,7 +104,17 @@ class DBModel:
self.cursor.execute("UPDATE vms SET last_seen_ipv4 = %s WHERE email = %s AND id = %s", (ipv4, email, id)) self.cursor.execute("UPDATE vms SET last_seen_ipv4 = %s WHERE email = %s AND id = %s", (ipv4, email, id))
self.connection.commit() self.connection.commit()
def create_vm(self, email, id, size, os, ssh_public_keys): def update_vm_ssh_host_keys(self, email, id, ssh_host_keys):
for key in ssh_host_keys:
self.cursor.execute("""
INSERT INTO vm_ssh_host_key (email, vm_id, key_type, content, sha256)
VALUES (%s, %s, %s, %s, %s)
""",
(email, id, key['key_type'], key['content'], key['sha256'])
)
self.connection.commit()
def create_vm(self, email, id, size, os, ssh_authorized_keys):
self.cursor.execute(""" self.cursor.execute("""
INSERT INTO vms (email, id, size, os) INSERT INTO vms (email, id, size, os)
VALUES (%s, %s, %s, %s) VALUES (%s, %s, %s, %s)
@ -112,14 +122,14 @@ class DBModel:
(email, id, size, os) (email, id, size, os)
) )
for ssh_public_key in ssh_public_keys: for ssh_authorized_key in ssh_authorized_keys:
current_app.logger.info(f"INSERT INTO vm_ssh_public_key (email, vm_id, ssh_public_key_name) VALUES (\"{email}\", \"{id}\", \"{ssh_public_key}\")") current_app.logger.info(f"INSERT INTO vm_ssh_authorized_key (email, vm_id, ssh_public_key_name) VALUES (\"{email}\", \"{id}\", \"{ssh_authorized_key}\")")
self.cursor.execute(""" self.cursor.execute("""
INSERT INTO vm_ssh_public_key (email, vm_id, ssh_public_key_name) INSERT INTO vm_ssh_authorized_key (email, vm_id, ssh_public_key_name)
VALUES (%s, %s, %s) VALUES (%s, %s, %s)
""", """,
(email, id, ssh_public_key) (email, id, ssh_authorized_key)
) )
self.connection.commit() self.connection.commit()
@ -147,11 +157,19 @@ class DBModel:
) )
self.cursor.execute(""" self.cursor.execute("""
SELECT ssh_public_key_name FROM vm_ssh_public_key SELECT ssh_public_key_name FROM vm_ssh_authorized_key
WHERE vm_ssh_public_key.email = %s AND vm_ssh_public_key.vm_id = %s""", WHERE vm_ssh_authorized_key.email = %s AND vm_ssh_authorized_key.vm_id = %s""",
(email, id) (email, id)
) )
vm["ssh_public_keys"] = list(map( lambda x: x[0], self.cursor.fetchall() )) vm["ssh_authorized_keys"] = list(map( lambda x: x[0], self.cursor.fetchall() ))
self.cursor.execute("""
SELECT key_type, content, sha256 FROM vm_ssh_host_key
WHERE vm_ssh_host_key.email = %s AND vm_ssh_host_key.vm_id = %s""",
(email, id)
)
vm["ssh_host_keys"] = list(map( lambda x: dict(key_type=x[0], content=x[1], sha256=x[2]), self.cursor.fetchall() ))
return vm return vm

View File

@ -5,6 +5,7 @@ from functools import reduce
import requests import requests
#import json #import json
from datetime import datetime from datetime import datetime
from threading import Lock
from io import BytesIO from io import BytesIO
from flask import Blueprint from flask import Blueprint
from flask import current_app from flask import current_app
@ -15,6 +16,7 @@ from werkzeug.exceptions import abort
from capsulflask.db import get_model from capsulflask.db import get_model
from capsulflask.auth import account_required from capsulflask.auth import account_required
mutex = Lock()
bp = Blueprint("metrics", __name__, url_prefix="/metrics") bp = Blueprint("metrics", __name__, url_prefix="/metrics")
durations = dict( durations = dict(
@ -116,6 +118,7 @@ def get_plot_bytes(metric, capsulid, duration, size):
return (502, None) return (502, None)
series = prometheus_response.json()["data"]["result"] series = prometheus_response.json()["data"]["result"]
if len(series) == 0: if len(series) == 0:
now_timestamp = datetime.timestamp(datetime.now()) now_timestamp = datetime.timestamp(datetime.now())
series = [ series = [
@ -129,13 +132,19 @@ def get_plot_bytes(metric, capsulid, duration, size):
series[0]["values"] series[0]["values"]
)) ))
plot_bytes = draw_plot_png_bytes(time_series_data, scale=scales[metric], size_x=sizes[size][0], size_y=sizes[size][1]) mutex.acquire()
try:
plot_bytes = draw_plot_png_bytes(time_series_data, scale=scales[metric], size_x=sizes[size][0], size_y=sizes[size][1])
finally:
mutex.release()
return (200, plot_bytes) return (200, plot_bytes)
def draw_plot_png_bytes(data, scale, size_x=3, size_y=1): def draw_plot_png_bytes(data, scale, size_x=3, size_y=1):
#current_app.logger.info(json.dumps(data, indent=4, default=str))
pyplot.style.use("seaborn-dark") pyplot.style.use("seaborn-dark")
fig, my_plot = pyplot.subplots(figsize=(size_x, size_y)) fig, my_plot = pyplot.subplots(figsize=(size_x, size_y))

View File

@ -20,7 +20,8 @@ from werkzeug.exceptions import abort
from capsulflask.auth import account_required from capsulflask.auth import account_required
from capsulflask.db import get_model, my_exec_info_message from capsulflask.db import get_model
from capsulflask.shared import my_exec_info_message
bp = Blueprint("payment", __name__, url_prefix="/payment") bp = Blueprint("payment", __name__, url_prefix="/payment")

View File

@ -0,0 +1,7 @@
DROP TABLE vm_ssh_host_key;
ALTER TABLE vm_ssh_authorized_key RENAME TO vm_ssh_public_key;
UPDATE schemaversion SET version = 11;

View File

@ -0,0 +1,14 @@
CREATE TABLE vm_ssh_host_key (
email TEXT NOT NULL,
vm_id TEXT NOT NULL,
key_type TEXT NOT NULL,
content TEXT NOT NULL,
sha256 TEXT NOT NULL,
FOREIGN KEY (email, vm_id) REFERENCES vms(email, id) ON DELETE CASCADE,
PRIMARY KEY (email, vm_id, key_type)
);
ALTER TABLE vm_ssh_public_key RENAME TO vm_ssh_authorized_key;
UPDATE schemaversion SET version = 12;

20
capsulflask/shared.py Normal file
View File

@ -0,0 +1,20 @@
from typing import List
# I decided to just use dict everywhere instead because I have to use dict to read it from json
# class SSHHostKey:
# def __init__(self, key_type=None, content=None, sha256=None):
# self.key_type = key_type
# self.content = content
# self.sha256 = sha256
class VirtualMachine:
def __init__(self, id, ipv4: str = None, ipv6: str = None, ssh_host_keys: List[dict] = list()):
self.id = id
self.ipv4 = ipv4
self.ipv6 = ipv6
self.ssh_host_keys = ssh_host_keys
def my_exec_info_message(exec_info):
return "{}: {}".format(".".join([exec_info[0].__module__, exec_info[0].__name__]), exec_info[1])

View File

@ -0,0 +1,22 @@
#!/bin/sh
ip_address="$1"
if echo "$ip_address" | grep -vqE '^([0-9]{1,3}\.){3}[0-9]{1,3}$'; then
echo "ip_address $ip_address must match "'"^([0-9]{1,3}\.){3}[0-9]{1,3}$"'
exit 1
fi
printf '['
DELIMITER="\n"
ssh-keyscan "$ip_address" 2>/dev/null | while read -r line; do
if echo "$line" | grep -qE "^$ip_address"' +(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$'; then
KEY_CONTENT="$(echo "$line" | awk '{ print $2 " " $3 }')"
FINGERPRINT_OUTPUT="$(echo "$KEY_CONTENT" | ssh-keygen -l -E sha256 -f - | sed -E 's/^[0-9]+ SHA256:([0-9A-Za-z+/-]+) .+ \(([A-Z0-9]+)\)$/\1 \2/g')"
SHA256_HASH="$(echo "$FINGERPRINT_OUTPUT" | awk '{ print $1 }')"
KEY_TYPE="$(echo "$FINGERPRINT_OUTPUT" | awk '{ print $2 }')"
printf '%s{"key_type":"%s", "content":"%s", "sha256":"%s"}' "$DELIMITER" "$KEY_TYPE" "$KEY_CONTENT" "$SHA256_HASH"
DELIMITER=",\n"
fi
done
printf '\n]\n'

View File

@ -102,6 +102,9 @@ main {
.row.grid-large > div { .row.grid-large > div {
flex: 1 1 20em; flex: 1 1 20em;
} }
.row.grid-medium > div {
flex: 1 1 13em;
}
.row.grid-small > div { .row.grid-small > div {
flex: 0 0 8em; flex: 0 0 8em;
} }
@ -277,8 +280,12 @@ div.metric {
border: 1px solid #777e73; border: 1px solid #777e73;
background: #bdc7b810; background: #bdc7b810;
} }
.break-word { pre.code.wrap {
word-break: break-word; white-space: pre-wrap;
}
.break-all {
word-break: break-all;
} }
.dim { .dim {

View File

@ -73,8 +73,8 @@
<span id="ssh_username">cyberian</span> <span id="ssh_username">cyberian</span>
</div> </div>
<div class="row justify-start"> <div class="row justify-start">
<label class="align" for="ssh_public_keys">SSH Public Keys</label> <label class="align" for="ssh_authorized_keys">SSH Authorized Keys</label>
<a id="ssh_public_keys" href="/console/ssh">{{ vm['ssh_public_keys'] }}</a> <a id="ssh_authorized_keys" href="/console/ssh">{{ vm['ssh_authorized_keys'] }}</a>
</div> </div>
<div class="row center justify-start"> <div class="row center justify-start">
<label class="align" for="delete_action">Actions</label> <label class="align" for="delete_action">Actions</label>
@ -85,6 +85,15 @@
</form> </form>
</div> </div>
</div> </div>
<div class="row third-margin">
<h1>ssh host key fingerprints</h1>
</div>
<div class="row">
<pre class="code">{% for key in vm['ssh_host_keys'] %}
SHA256:{{ key.sha256 }} ({{ key.key_type }}){% endfor %}</pre>
</div>
<div class="row "> <div class="row ">
<hr/> <hr/>
</div> </div>
@ -136,6 +145,17 @@
</a> </a>
</div> </div>
</div> </div>
<div class="row ">
<hr/>
</div>
<div class="row half-margin">
add the following to your ~/.ssh/known_hosts file (optional)
</div>
<div class="row">
<pre class="code wrap break-all smalltext">{% for key in vm['ssh_host_keys'] %}
{{ vm['ipv4'] }} {{ key.content }}{% endfor %}
</pre>
</div>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -1,5 +1,7 @@
import subprocess import subprocess
import re import re
import sys
import json
from flask import current_app from flask import current_app
from time import sleep from time import sleep
@ -7,28 +9,23 @@ from os.path import join
from subprocess import run from subprocess import run
from capsulflask.db import get_model from capsulflask.db import get_model
from capsulflask.shared import my_exec_info_message, VirtualMachine
def validate_capsul_id(id): def validate_capsul_id(id):
if not re.match(r"^(cvm|capsul)-[a-z0-9]{10}$", id): if not re.match(r"^(cvm|capsul)-[a-z0-9]{10}$", id):
raise ValueError(f"vm id \"{id}\" must match \"^capsul-[a-z0-9]{{10}}$\"") raise ValueError(f"vm id \"{id}\" must match \"^capsul-[a-z0-9]{{10}}$\"")
class VirtualMachine:
def __init__(self, id, ipv4=None, ipv6=None):
self.id = id
self.ipv4 = ipv4
self.ipv6 = ipv6
class VirtualizationInterface: class VirtualizationInterface:
def capacity_avaliable(self, additional_ram_bytes: int) -> bool: def capacity_avaliable(self, additional_ram_bytes: int) -> bool:
pass pass
def get(self, id: str) -> VirtualMachine: def get(self, id: str, get_ssh_host_keys: bool) -> VirtualMachine:
pass pass
def list_ids(self) -> list: def list_ids(self) -> list:
pass pass
def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory: int, ssh_public_keys: list): def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory: int, ssh_authorized_keys: list):
pass pass
def destroy(self, email: str, id: str): def destroy(self, email: str, id: str):
@ -38,14 +35,23 @@ class MockVirtualization(VirtualizationInterface):
def capacity_avaliable(self, additional_ram_bytes): def capacity_avaliable(self, additional_ram_bytes):
return True return True
def get(self, id): def get(self, id, get_ssh_host_keys):
validate_capsul_id(id) validate_capsul_id(id)
if get_ssh_host_keys:
ssh_host_keys = json.loads("""[
{"key_type":"ED25519", "content":"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN8cna0zeKSKl/r8whdn/KmDWhdzuWRVV0GaKIM+eshh", "sha256":"V4X2apAF6btGAfS45gmpldknoDX0ipJ5c6DLfZR2ttQ"},
{"key_type":"RSA", "content":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCvotgzgEP65JUQ8S8OoNKy1uEEPEAcFetSp7QpONe6hj4wPgyFNgVtdoWdNcU19dX3hpdse0G8OlaMUTnNVuRlbIZXuifXQ2jTtCFUA2mmJ5bF+XjGm3TXKMNGh9PN+wEPUeWd14vZL+QPUMev5LmA8cawPiU5+vVMLid93HRBj118aCJFQxLgrdP48VPfKHFRfCR6TIjg1ii3dH4acdJAvlmJ3GFB6ICT42EmBqskz2MPe0rIFxH8YohCBbAbrbWYcptHt4e48h4UdpZdYOhEdv89GrT8BF2C5cbQ5i9qVpI57bXKrj8hPZU5of48UHLSpXG8mbH0YDiOQOfKX/Mt", "sha256":"ghee6KzRnBJhND2kEUZSaouk7CD6o6z2aAc8GPkV+GQ"},
{"key_type":"ECDSA", "content":"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLLgOoATz9R4aS2kk7vWoxX+lshK63t9+5BIHdzZeFE1o+shlcf0Wji8cN/L1+m3bi0uSETZDOAWMP3rHLJj9Hk=", "sha256":"aCYG1aD8cv/TjzJL0bi9jdabMGksdkfa7R8dCGm1yYs"}
]""")
return VirtualMachine(id, ipv4="1.1.1.1", ssh_host_keys=ssh_host_keys)
return VirtualMachine(id, ipv4="1.1.1.1") return VirtualMachine(id, ipv4="1.1.1.1")
def list_ids(self) -> list: def list_ids(self) -> list:
return get_model().all_non_deleted_vm_ids() return get_model().all_non_deleted_vm_ids()
def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory_mb: int, ssh_public_keys: list): def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory_mb: int, ssh_authorized_keys: list):
validate_capsul_id(id) validate_capsul_id(id)
current_app.logger.info(f"mock create: {id} for {email}") current_app.logger.info(f"mock create: {id} for {email}")
sleep(1) sleep(1)
@ -91,19 +97,31 @@ class ShellScriptVirtualization(VirtualizationInterface):
return True return True
def get(self, id): def get(self, id, get_ssh_host_keys):
validate_capsul_id(id) validate_capsul_id(id)
completedProcess = run([join(current_app.root_path, 'shell_scripts/get.sh'), id], capture_output=True) completedProcess = run([join(current_app.root_path, 'shell_scripts/get.sh'), id], capture_output=True)
self.validate_completed_process(completedProcess) self.validate_completed_process(completedProcess)
lines = completedProcess.stdout.splitlines() ipaddr_lines = completedProcess.stdout.splitlines()
if len(lines) == 0: if len(ipaddr_lines) == 0:
return None return None
ipaddr = lines[0].decode("utf-8") ipaddr = ipaddr_lines[0].decode("utf-8")
if not re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", ipaddr): if not re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", ipaddr):
return None return None
if get_ssh_host_keys:
try:
completedProcess2 = run([join(current_app.root_path, 'shell_scripts/ssh-keyscan.sh'), ipaddr], capture_output=True)
self.validate_completed_process(completedProcess2)
ssh_host_keys = json.loads(completedProcess2.stdout)
return VirtualMachine(id, ipv4=ipaddr, ssh_host_keys=ssh_host_keys)
except:
current_app.logger.warning(f"""
failed to ssh-keyscan {id} at {ipaddr}:
{my_exec_info_message(sys.exc_info())}"""
)
return VirtualMachine(id, ipv4=ipaddr) return VirtualMachine(id, ipv4=ipaddr)
def list_ids(self) -> list: def list_ids(self) -> list:
@ -111,13 +129,13 @@ class ShellScriptVirtualization(VirtualizationInterface):
self.validate_completed_process(completedProcess) self.validate_completed_process(completedProcess)
return list(map(lambda x: x.decode("utf-8"), completedProcess.stdout.splitlines() )) return list(map(lambda x: x.decode("utf-8"), completedProcess.stdout.splitlines() ))
def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory_mb: int, ssh_public_keys: list): def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory_mb: int, ssh_authorized_keys: list):
validate_capsul_id(id) validate_capsul_id(id)
if not re.match(r"^[a-zA-Z0-9/_.-]+$", template_image_file_name): if not re.match(r"^[a-zA-Z0-9/_.-]+$", template_image_file_name):
raise ValueError(f"template_image_file_name \"{template_image_file_name}\" must match \"^[a-zA-Z0-9/_.-]+$\"") raise ValueError(f"template_image_file_name \"{template_image_file_name}\" must match \"^[a-zA-Z0-9/_.-]+$\"")
for ssh_public_key in ssh_public_keys: for ssh_public_key in ssh_authorized_keys:
if not re.match(r"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$", ssh_public_key): if not re.match(r"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$", ssh_public_key):
raise ValueError(f"ssh_public_key \"{ssh_public_key}\" must match \"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$\"") raise ValueError(f"ssh_public_key \"{ssh_public_key}\" must match \"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$\"")
@ -127,7 +145,7 @@ class ShellScriptVirtualization(VirtualizationInterface):
if memory_mb < 512 or memory_mb > 16384: if memory_mb < 512 or memory_mb > 16384:
raise ValueError(f"memory_mb \"{memory_mb}\" must match 512 <= memory_mb <= 16384") raise ValueError(f"memory_mb \"{memory_mb}\" must match 512 <= memory_mb <= 16384")
ssh_keys_string = "\n".join(ssh_public_keys) ssh_keys_string = "\n".join(ssh_authorized_keys)
current_app.logger.info(f"create vm virt model ssh_keys_string: {ssh_keys_string}") current_app.logger.info(f"create vm virt model ssh_keys_string: {ssh_keys_string}")
@ -149,7 +167,7 @@ class ShellScriptVirtualization(VirtualizationInterface):
template_image_file_name={template_image_file_name} template_image_file_name={template_image_file_name}
vcpus={str(vcpus)} vcpus={str(vcpus)}
memory={str(memory_mb)} memory={str(memory_mb)}
ssh_public_keys={ssh_keys_string} ssh_authorized_keys={ssh_keys_string}
""" """
current_app.logger.info(f"create vm status: {status} vmSettings: {vmSettings}") current_app.logger.info(f"create vm status: {status} vmSettings: {vmSettings}")