diff --git a/capsulflask/cli.py b/capsulflask/cli.py index 1962b01..4baaaa2 100644 --- a/capsulflask/cli.py +++ b/capsulflask/cli.py @@ -11,7 +11,8 @@ from flask import current_app from psycopg2 import ProgrammingError 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 bp = Blueprint('cli', __name__) diff --git a/capsulflask/console.py b/capsulflask/console.py index 5dafc57..c161e7f 100644 --- a/capsulflask/console.py +++ b/capsulflask/console.py @@ -1,5 +1,6 @@ import re import sys +import json from datetime import datetime, timedelta from flask import Blueprint from flask import flash @@ -15,7 +16,8 @@ from nanoid import generate from capsulflask.metrics import durations as metric_durations 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 import cli @@ -25,19 +27,21 @@ def makeCapsulId(): lettersAndNumbers = generate(alphabet="1234567890qwertyuiopasdfghjklzxcvbnm", size=10) return f"capsul-{lettersAndNumbers}" -def double_check_capsul_address(id, ipv4): +def double_check_capsul_address(id, ipv4, get_ssh_host_keys): 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: 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: current_app.logger.error(f""" the virtualization model threw an error in double_check_capsul_address of {id}: {my_exec_info_message(sys.exc_info())}""" ) - return ipv4 + return result @bp.route("/") @account_required @@ -53,7 +57,7 @@ def index(): # 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... 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( lambda x: dict( @@ -104,9 +108,17 @@ def detail(id): return render_template("capsul-detail.html", vm=vm, delete=True, deleted=True) 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["ssh_public_keys"] = ", ".join(vm["ssh_public_keys"]) if len(vm["ssh_public_keys"]) > 0 else "" + vm["ssh_authorized_keys"] = ", ".join(vm["ssh_authorized_keys"]) if len(vm["ssh_authorized_keys"]) > 0 else "" + + + current_app.logger.info(f"asd {needs_ssh_host_keys} {json.dumps(vm['ssh_host_keys'])})") return render_template( "capsul-detail.html", @@ -123,12 +135,12 @@ def detail(id): def create(): vm_sizes = get_model().vm_sizes_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()) capacity_avaliable = current_app.config["VIRTUALIZATION_MODEL"].capacity_avaliable(512*1024*1024) 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"] 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}"] current_app.logger.info(f"ssh key posted_name: {posted_name}") key = None - for x in ssh_public_keys: + for x in public_keys_for_account: if x['name'] == posted_name: current_app.logger.info(f"ssh key posted_name {posted_name} was found") key = x @@ -190,7 +202,7 @@ def create(): id=id, size=size, 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( email = session["account"], @@ -198,7 +210,7 @@ def create(): template_image_file_name=operating_systems[os]['template_image_file_name'], vcpus=vm_sizes[size]['vcpus'], 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}") @@ -219,9 +231,9 @@ def create(): csrf_token = session["csrf-token"], capacity_avaliable=capacity_avaliable, account_balance=format(account_balance, '.2f'), - ssh_public_keys=ssh_public_keys, - ssh_public_key_count=len(ssh_public_keys), - no_ssh_public_keys=len(ssh_public_keys) == 0, + ssh_public_keys=public_keys_for_account, + ssh_public_key_count=len(public_keys_for_account), + no_ssh_public_keys=len(public_keys_for_account) == 0, operating_systems=operating_systems, cant_afford=len(affordable_vm_sizes) == 0, vm_sizes=affordable_vm_sizes diff --git a/capsulflask/db.py b/capsulflask/db.py index 4329288..b0e9a0d 100644 --- a/capsulflask/db.py +++ b/capsulflask/db.py @@ -9,6 +9,7 @@ from flask import current_app from flask import g from capsulflask.db_model import DBModel +from capsulflask.shared import my_exec_info_message def init_app(app): databaseUrl = urlparse(app.config['DATABASE_URL']) @@ -41,7 +42,7 @@ def init_app(app): hasSchemaVersionTable = False actionWasTaken = False schemaVersion = 0 - desiredSchemaVersion = 11 + desiredSchemaVersion = 12 cursor = connection.cursor() @@ -127,5 +128,3 @@ def close_db(e=None): db_model.cursor.close() 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]) diff --git a/capsulflask/db_model.py b/capsulflask/db_model.py index 8216439..fe362eb 100644 --- a/capsulflask/db_model.py +++ b/capsulflask/db_model.py @@ -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.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(""" INSERT INTO vms (email, id, size, os) VALUES (%s, %s, %s, %s) @@ -112,14 +122,14 @@ class DBModel: (email, id, size, os) ) - for ssh_public_key in ssh_public_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}\")") + for ssh_authorized_key in ssh_authorized_keys: + 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(""" - 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) """, - (email, id, ssh_public_key) + (email, id, ssh_authorized_key) ) self.connection.commit() @@ -147,11 +157,19 @@ class DBModel: ) self.cursor.execute(""" - SELECT ssh_public_key_name FROM vm_ssh_public_key - WHERE vm_ssh_public_key.email = %s AND vm_ssh_public_key.vm_id = %s""", + SELECT ssh_public_key_name FROM vm_ssh_authorized_key + WHERE vm_ssh_authorized_key.email = %s AND vm_ssh_authorized_key.vm_id = %s""", (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 diff --git a/capsulflask/metrics.py b/capsulflask/metrics.py index fc33b61..dffaa53 100644 --- a/capsulflask/metrics.py +++ b/capsulflask/metrics.py @@ -5,6 +5,7 @@ from functools import reduce import requests #import json from datetime import datetime +from threading import Lock from io import BytesIO from flask import Blueprint from flask import current_app @@ -15,6 +16,7 @@ from werkzeug.exceptions import abort from capsulflask.db import get_model from capsulflask.auth import account_required +mutex = Lock() bp = Blueprint("metrics", __name__, url_prefix="/metrics") durations = dict( @@ -116,6 +118,7 @@ def get_plot_bytes(metric, capsulid, duration, size): return (502, None) series = prometheus_response.json()["data"]["result"] + if len(series) == 0: now_timestamp = datetime.timestamp(datetime.now()) series = [ @@ -129,13 +132,19 @@ def get_plot_bytes(metric, capsulid, duration, size): 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) 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") fig, my_plot = pyplot.subplots(figsize=(size_x, size_y)) diff --git a/capsulflask/payment.py b/capsulflask/payment.py index 2f65015..6219de5 100644 --- a/capsulflask/payment.py +++ b/capsulflask/payment.py @@ -20,7 +20,8 @@ from werkzeug.exceptions import abort 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") diff --git a/capsulflask/schema_migrations/12_down_ssh_host_keys.sql b/capsulflask/schema_migrations/12_down_ssh_host_keys.sql new file mode 100644 index 0000000..054222e --- /dev/null +++ b/capsulflask/schema_migrations/12_down_ssh_host_keys.sql @@ -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; \ No newline at end of file diff --git a/capsulflask/schema_migrations/12_up_ssh_host_keys.sql b/capsulflask/schema_migrations/12_up_ssh_host_keys.sql new file mode 100644 index 0000000..ae03c1e --- /dev/null +++ b/capsulflask/schema_migrations/12_up_ssh_host_keys.sql @@ -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; \ No newline at end of file diff --git a/capsulflask/shared.py b/capsulflask/shared.py new file mode 100644 index 0000000..23d69cf --- /dev/null +++ b/capsulflask/shared.py @@ -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]) \ No newline at end of file diff --git a/capsulflask/shell_scripts/ssh-keyscan.sh b/capsulflask/shell_scripts/ssh-keyscan.sh new file mode 100644 index 0000000..c730a84 --- /dev/null +++ b/capsulflask/shell_scripts/ssh-keyscan.sh @@ -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' diff --git a/capsulflask/static/style.css b/capsulflask/static/style.css index 80ffae3..4ad29f6 100644 --- a/capsulflask/static/style.css +++ b/capsulflask/static/style.css @@ -102,6 +102,9 @@ main { .row.grid-large > div { flex: 1 1 20em; } +.row.grid-medium > div { + flex: 1 1 13em; +} .row.grid-small > div { flex: 0 0 8em; } @@ -277,8 +280,12 @@ div.metric { border: 1px solid #777e73; background: #bdc7b810; } -.break-word { - word-break: break-word; +pre.code.wrap { + white-space: pre-wrap; +} + +.break-all { + word-break: break-all; } .dim { diff --git a/capsulflask/templates/capsul-detail.html b/capsulflask/templates/capsul-detail.html index 8636ef2..84afcdf 100644 --- a/capsulflask/templates/capsul-detail.html +++ b/capsulflask/templates/capsul-detail.html @@ -73,8 +73,8 @@ cyberian
- - {{ vm['ssh_public_keys'] }} + + {{ vm['ssh_authorized_keys'] }}
@@ -85,6 +85,15 @@
+
+

ssh host key fingerprints

+
+ +
+
{% for key in vm['ssh_host_keys'] %}
+SHA256:{{ key.sha256 }} ({{ key.key_type }}){% endfor %}
+
+

@@ -136,6 +145,17 @@ +
+
+
+
+ add the following to your ~/.ssh/known_hosts file (optional) +
+
+
{% for key in vm['ssh_host_keys'] %}
+{{ vm['ipv4'] }} {{ key.content }}{% endfor %}
+
+
{% endif %} {% endblock %} diff --git a/capsulflask/virt_model.py b/capsulflask/virt_model.py index e2b413b..1859a9f 100644 --- a/capsulflask/virt_model.py +++ b/capsulflask/virt_model.py @@ -1,5 +1,7 @@ import subprocess import re +import sys +import json from flask import current_app from time import sleep @@ -7,28 +9,23 @@ from os.path import join from subprocess import run from capsulflask.db import get_model +from capsulflask.shared import my_exec_info_message, VirtualMachine def validate_capsul_id(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}}$\"") -class VirtualMachine: - def __init__(self, id, ipv4=None, ipv6=None): - self.id = id - self.ipv4 = ipv4 - self.ipv6 = ipv6 - class VirtualizationInterface: def capacity_avaliable(self, additional_ram_bytes: int) -> bool: pass - def get(self, id: str) -> VirtualMachine: + def get(self, id: str, get_ssh_host_keys: bool) -> VirtualMachine: pass def list_ids(self) -> list: 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 def destroy(self, email: str, id: str): @@ -38,14 +35,23 @@ class MockVirtualization(VirtualizationInterface): def capacity_avaliable(self, additional_ram_bytes): return True - def get(self, id): + def get(self, id, get_ssh_host_keys): 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") def list_ids(self) -> list: 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) current_app.logger.info(f"mock create: {id} for {email}") sleep(1) @@ -91,19 +97,31 @@ class ShellScriptVirtualization(VirtualizationInterface): return True - def get(self, id): + def get(self, id, get_ssh_host_keys): validate_capsul_id(id) completedProcess = run([join(current_app.root_path, 'shell_scripts/get.sh'), id], capture_output=True) self.validate_completed_process(completedProcess) - lines = completedProcess.stdout.splitlines() - if len(lines) == 0: + ipaddr_lines = completedProcess.stdout.splitlines() + if len(ipaddr_lines) == 0: 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): 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) def list_ids(self) -> list: @@ -111,13 +129,13 @@ class ShellScriptVirtualization(VirtualizationInterface): self.validate_completed_process(completedProcess) 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) 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/_.-]+$\"") - 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): 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: 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}") @@ -149,7 +167,7 @@ class ShellScriptVirtualization(VirtualizationInterface): template_image_file_name={template_image_file_name} vcpus={str(vcpus)} 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}")