Merge pull request #918 from mwhudson/set-installer-password
have subiquity set password for installer user in live session
This commit is contained in:
commit
b0ac3960be
|
@ -50,7 +50,7 @@ from subiquity.common.types import (
|
||||||
from subiquity.journald import journald_listen
|
from subiquity.journald import journald_listen
|
||||||
from subiquity.ui.frame import SubiquityUI
|
from subiquity.ui.frame import SubiquityUI
|
||||||
from subiquity.ui.views.error import ErrorReportStretchy
|
from subiquity.ui.views.error import ErrorReportStretchy
|
||||||
from subiquity.ui.views.help import HelpMenu
|
from subiquity.ui.views.help import HelpMenu, ssh_help_texts
|
||||||
from subiquity.ui.views.installprogress import (
|
from subiquity.ui.views.installprogress import (
|
||||||
InstallConfirmation,
|
InstallConfirmation,
|
||||||
)
|
)
|
||||||
|
@ -283,12 +283,11 @@ class SubiquityClient(TuiApplication):
|
||||||
self.interactive = status.interactive
|
self.interactive = status.interactive
|
||||||
if self.interactive:
|
if self.interactive:
|
||||||
if self.opts.ssh:
|
if self.opts.ssh:
|
||||||
from subiquity.ui.views.help import (
|
ssh_info = self.client.meta.ssh_info.GET()
|
||||||
ssh_help_texts, get_installer_password)
|
if ssh_info is None:
|
||||||
from subiquitycore.ssh import get_ips_standalone
|
print("no ssh?")
|
||||||
texts = ssh_help_texts(
|
return
|
||||||
get_ips_standalone(),
|
texts = ssh_help_texts(ssh_info)
|
||||||
get_installer_password(self.opts.dry_run))
|
|
||||||
for line in texts:
|
for line in texts:
|
||||||
if hasattr(line, 'text'):
|
if hasattr(line, 'text'):
|
||||||
if line.text.startswith('installer@'):
|
if line.text.startswith('installer@'):
|
||||||
|
|
|
@ -31,10 +31,10 @@ class WelcomeController(SubiquityTuiController):
|
||||||
i18n.switch_language(language)
|
i18n.switch_language(language)
|
||||||
serial = self.app.opts.run_on_serial
|
serial = self.app.opts.run_on_serial
|
||||||
if serial:
|
if serial:
|
||||||
ips = await self.app.client.network.global_addresses.GET()
|
ssh_info = await self.app.client.meta.ssh_info.GET()
|
||||||
else:
|
else:
|
||||||
ips = None
|
ssh_info = None
|
||||||
return WelcomeView(self, language, serial, ips)
|
return WelcomeView(self, language, serial, ssh_info)
|
||||||
|
|
||||||
def run_answers(self):
|
def run_answers(self):
|
||||||
if 'lang' in self.answers:
|
if 'lang' in self.answers:
|
||||||
|
|
|
@ -36,6 +36,7 @@ from subiquity.common.types import (
|
||||||
SnapListResponse,
|
SnapListResponse,
|
||||||
SnapSelection,
|
SnapSelection,
|
||||||
SSHData,
|
SSHData,
|
||||||
|
LiveSessionSSHInfo,
|
||||||
StorageResponse,
|
StorageResponse,
|
||||||
ZdevInfo,
|
ZdevInfo,
|
||||||
)
|
)
|
||||||
|
@ -67,6 +68,10 @@ class API:
|
||||||
class restart:
|
class restart:
|
||||||
def POST() -> None:
|
def POST() -> None:
|
||||||
"""Restart the server process."""
|
"""Restart the server process."""
|
||||||
|
|
||||||
|
class ssh_info:
|
||||||
|
def GET() -> Optional[LiveSessionSSHInfo]: ...
|
||||||
|
|
||||||
class errors:
|
class errors:
|
||||||
class wait:
|
class wait:
|
||||||
def GET(error_ref: ErrorReportRef) -> ErrorReportRef:
|
def GET(error_ref: ErrorReportRef) -> ErrorReportRef:
|
||||||
|
|
|
@ -79,6 +79,28 @@ class ApplicationStatus:
|
||||||
event_syslog_id: str
|
event_syslog_id: str
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordKind(enum.Enum):
|
||||||
|
NONE = enum.auto()
|
||||||
|
KNOWN = enum.auto()
|
||||||
|
UNKNOWN = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(auto_attribs=True)
|
||||||
|
class KeyFingerprint:
|
||||||
|
keytype: str
|
||||||
|
fingerprint: str
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(auto_attribs=True)
|
||||||
|
class LiveSessionSSHInfo:
|
||||||
|
username: str
|
||||||
|
password_kind: PasswordKind
|
||||||
|
password: Optional[str]
|
||||||
|
authorized_key_fingerprints: List[KeyFingerprint]
|
||||||
|
ips: List[str]
|
||||||
|
host_key_fingerprints: List[KeyFingerprint]
|
||||||
|
|
||||||
|
|
||||||
class RefreshCheckState(enum.Enum):
|
class RefreshCheckState(enum.Enum):
|
||||||
UNKNOWN = enum.auto()
|
UNKNOWN = enum.auto()
|
||||||
AVAILABLE = enum.auto()
|
AVAILABLE = enum.auto()
|
||||||
|
|
|
@ -24,6 +24,8 @@ from typing import List, Optional
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
from cloudinit import atomic_helper, safeyaml, stages
|
from cloudinit import atomic_helper, safeyaml, stages
|
||||||
|
from cloudinit.config.cc_set_passwords import rand_user_password
|
||||||
|
from cloudinit.distros import ug_util
|
||||||
|
|
||||||
import jsonschema
|
import jsonschema
|
||||||
|
|
||||||
|
@ -35,7 +37,11 @@ from subiquitycore.async_helpers import run_in_thread, schedule_task
|
||||||
from subiquitycore.context import with_context
|
from subiquitycore.context import with_context
|
||||||
from subiquitycore.core import Application
|
from subiquitycore.core import Application
|
||||||
from subiquitycore.prober import Prober
|
from subiquitycore.prober import Prober
|
||||||
from subiquitycore.utils import arun_command
|
from subiquitycore.ssh import (
|
||||||
|
host_key_fingerprints,
|
||||||
|
user_key_fingerprints,
|
||||||
|
)
|
||||||
|
from subiquitycore.utils import arun_command, run_command
|
||||||
|
|
||||||
from subiquity.common.api.server import (
|
from subiquity.common.api.server import (
|
||||||
bind,
|
bind,
|
||||||
|
@ -51,6 +57,9 @@ from subiquity.common.types import (
|
||||||
ApplicationState,
|
ApplicationState,
|
||||||
ApplicationStatus,
|
ApplicationStatus,
|
||||||
ErrorReportRef,
|
ErrorReportRef,
|
||||||
|
KeyFingerprint,
|
||||||
|
LiveSessionSSHInfo,
|
||||||
|
PasswordKind,
|
||||||
)
|
)
|
||||||
from subiquity.server.controller import SubiquityController
|
from subiquity.server.controller import SubiquityController
|
||||||
from subiquity.models.subiquity import SubiquityModel
|
from subiquity.models.subiquity import SubiquityModel
|
||||||
|
@ -98,6 +107,48 @@ class MetaController:
|
||||||
if controller.endpoint in endpoints:
|
if controller.endpoint in endpoints:
|
||||||
controller.configured()
|
controller.configured()
|
||||||
|
|
||||||
|
async def ssh_info_GET(self) -> Optional[LiveSessionSSHInfo]:
|
||||||
|
ips = []
|
||||||
|
for dev in self.app.base_model.network.get_all_netdevs():
|
||||||
|
ips.extend(map(str, dev.actual_global_ip_addresses))
|
||||||
|
if not ips:
|
||||||
|
return None
|
||||||
|
username = self.app.installer_user_name
|
||||||
|
if username is None:
|
||||||
|
return None
|
||||||
|
user_fingerprints = [
|
||||||
|
KeyFingerprint(keytype, fingerprint)
|
||||||
|
for keytype, fingerprint in user_key_fingerprints(username)
|
||||||
|
]
|
||||||
|
if self.app.installer_user_passwd_kind == PasswordKind.NONE:
|
||||||
|
if not user_key_fingerprints:
|
||||||
|
return None
|
||||||
|
host_fingerprints = [
|
||||||
|
KeyFingerprint(keytype, fingerprint)
|
||||||
|
for keytype, fingerprint in host_key_fingerprints()
|
||||||
|
]
|
||||||
|
return LiveSessionSSHInfo(
|
||||||
|
username=username,
|
||||||
|
password_kind=self.app.installer_user_passwd_kind,
|
||||||
|
password=self.app.installer_user_passwd,
|
||||||
|
authorized_key_fingerprints=user_fingerprints,
|
||||||
|
ips=ips,
|
||||||
|
host_key_fingerprints=host_fingerprints)
|
||||||
|
|
||||||
|
|
||||||
|
def get_installer_password_from_cloudinit_log():
|
||||||
|
try:
|
||||||
|
fp = open("/var/log/cloud-init-output.log")
|
||||||
|
except FileNotFoundError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
with fp:
|
||||||
|
for line in fp:
|
||||||
|
if line.startswith("installer:"):
|
||||||
|
return line[len("installer:"):].strip()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class SubiquityServer(Application):
|
class SubiquityServer(Application):
|
||||||
|
|
||||||
|
@ -157,6 +208,9 @@ class SubiquityServer(Application):
|
||||||
self.confirming_tty = ''
|
self.confirming_tty = ''
|
||||||
self.fatal_error = None
|
self.fatal_error = None
|
||||||
self.running_error_commands = False
|
self.running_error_commands = False
|
||||||
|
self.installer_user_name = None
|
||||||
|
self.installer_user_passwd_kind = PasswordKind.NONE
|
||||||
|
self.installer_user_passwd = None
|
||||||
|
|
||||||
self.echo_syslog_id = 'subiquity_echo.{}'.format(os.getpid())
|
self.echo_syslog_id = 'subiquity_echo.{}'.format(os.getpid())
|
||||||
self.event_syslog_id = 'subiquity_event.{}'.format(os.getpid())
|
self.event_syslog_id = 'subiquity_event.{}'.format(os.getpid())
|
||||||
|
@ -366,14 +420,14 @@ class SubiquityServer(Application):
|
||||||
init = stages.Init()
|
init = stages.Init()
|
||||||
init.read_cfg()
|
init.read_cfg()
|
||||||
init.fetch(existing="trust")
|
init.fetch(existing="trust")
|
||||||
cloud = init.cloudify()
|
self.cloud = init.cloudify()
|
||||||
autoinstall_path = '/autoinstall.yaml'
|
autoinstall_path = '/autoinstall.yaml'
|
||||||
if 'autoinstall' in cloud.cfg:
|
if 'autoinstall' in self.cloud.cfg:
|
||||||
if not os.path.exists(autoinstall_path):
|
if not os.path.exists(autoinstall_path):
|
||||||
atomic_helper.write_file(
|
atomic_helper.write_file(
|
||||||
autoinstall_path,
|
autoinstall_path,
|
||||||
safeyaml.dumps(
|
safeyaml.dumps(
|
||||||
cloud.cfg['autoinstall']).encode('utf-8'),
|
self.cloud.cfg['autoinstall']).encode('utf-8'),
|
||||||
mode=0o600)
|
mode=0o600)
|
||||||
if os.path.exists(autoinstall_path):
|
if os.path.exists(autoinstall_path):
|
||||||
self.opts.autoinstall = autoinstall_path
|
self.opts.autoinstall = autoinstall_path
|
||||||
|
@ -382,11 +436,67 @@ class SubiquityServer(Application):
|
||||||
"cloud-init status: %r, assumed disabled",
|
"cloud-init status: %r, assumed disabled",
|
||||||
status_txt)
|
status_txt)
|
||||||
|
|
||||||
|
def _user_has_password(self, username):
|
||||||
|
with open('/etc/shadow') as fp:
|
||||||
|
for line in fp:
|
||||||
|
if line.startswith(username + ":$"):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def set_installer_password(self):
|
||||||
|
passfile = self.state_path("installer-user-passwd")
|
||||||
|
|
||||||
|
if os.path.exists(passfile):
|
||||||
|
with open(passfile) as fp:
|
||||||
|
contents = fp.read()
|
||||||
|
self.installer_user_passwd_kind = PasswordKind.KNOWN
|
||||||
|
self.installer_user_name, self.installer_user_passwd = \
|
||||||
|
contents.split(':', 1)
|
||||||
|
return
|
||||||
|
|
||||||
|
def use_passwd(passwd):
|
||||||
|
self.installer_user_passwd = passwd
|
||||||
|
self.installer_user_passwd_kind = PasswordKind.KNOWN
|
||||||
|
with open(passfile, 'w') as fp:
|
||||||
|
fp.write(self.installer_user_name + ':' + passwd)
|
||||||
|
|
||||||
|
if self.opts.dry_run:
|
||||||
|
self.installer_user_name = os.environ['USER']
|
||||||
|
use_passwd(rand_user_password())
|
||||||
|
return
|
||||||
|
|
||||||
|
(users, _groups) = ug_util.normalize_users_groups(
|
||||||
|
self.cloud.cfg, self.cloud.distro)
|
||||||
|
(username, _user_config) = ug_util.extract_default(users)
|
||||||
|
|
||||||
|
self.installer_user_name = username
|
||||||
|
|
||||||
|
if self._user_has_password(username):
|
||||||
|
# Was the password set to a random password by a version of
|
||||||
|
# cloud-init that records the username in the log? (This is the
|
||||||
|
# case we hit on upgrading the subiquity snap)
|
||||||
|
passwd = get_installer_password_from_cloudinit_log()
|
||||||
|
if passwd:
|
||||||
|
use_passwd(passwd)
|
||||||
|
else:
|
||||||
|
self.installer_user_passwd_kind = PasswordKind.UNKNOWN
|
||||||
|
elif not user_key_fingerprints(username):
|
||||||
|
passwd = rand_user_password()
|
||||||
|
cp = run_command('chpasswd', input=username + ':'+passwd+'\n')
|
||||||
|
if cp.returncode == 0:
|
||||||
|
use_passwd(passwd)
|
||||||
|
else:
|
||||||
|
log.info("setting installer password failed %s", cp)
|
||||||
|
self.installer_user_passwd_kind = PasswordKind.NONE
|
||||||
|
else:
|
||||||
|
self.installer_user_passwd_kind = PasswordKind.NONE
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
self.controllers.load_all()
|
self.controllers.load_all()
|
||||||
await self.start_api_server()
|
await self.start_api_server()
|
||||||
self.update_state(ApplicationState.CLOUD_INIT_WAIT)
|
self.update_state(ApplicationState.CLOUD_INIT_WAIT)
|
||||||
await self.wait_for_cloudinit()
|
await self.wait_for_cloudinit()
|
||||||
|
self.set_installer_password()
|
||||||
self.load_autoinstall_config(only_early=True)
|
self.load_autoinstall_config(only_early=True)
|
||||||
if self.autoinstall_config and self.controllers.Early.cmds:
|
if self.autoinstall_config and self.controllers.Early.cmds:
|
||||||
stamp_file = self.state_path("early-commands")
|
stamp_file = self.state_path("early-commands")
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import io
|
|
||||||
|
|
||||||
from urwid import (
|
from urwid import (
|
||||||
connect_signal,
|
connect_signal,
|
||||||
|
@ -25,9 +24,8 @@ from urwid import (
|
||||||
Text,
|
Text,
|
||||||
)
|
)
|
||||||
|
|
||||||
from subiquitycore.async_helpers import schedule_task
|
|
||||||
from subiquitycore.lsb_release import lsb_release
|
from subiquitycore.lsb_release import lsb_release
|
||||||
from subiquitycore.ssh import host_key_info
|
from subiquitycore.ssh import summarize_host_keys
|
||||||
from subiquitycore.ui.buttons import (
|
from subiquitycore.ui.buttons import (
|
||||||
header_btn,
|
header_btn,
|
||||||
other_btn,
|
other_btn,
|
||||||
|
@ -57,6 +55,7 @@ from subiquitycore.ui.width import (
|
||||||
widget_width,
|
widget_width,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from subiquity.common.types import PasswordKind
|
||||||
from subiquity.ui.views.error import ErrorReportListStretchy
|
from subiquity.ui.views.error import ErrorReportListStretchy
|
||||||
|
|
||||||
log = logging.getLogger('subiquity.ui.help')
|
log = logging.getLogger('subiquity.ui.help')
|
||||||
|
@ -110,13 +109,30 @@ To connect, SSH to any of these addresses:
|
||||||
""")
|
""")
|
||||||
|
|
||||||
SSH_HELP_ONE_ADDRESSES = _("""
|
SSH_HELP_ONE_ADDRESSES = _("""
|
||||||
To connect, SSH to installer@{ip}.
|
To connect, SSH to {username}@{ip}.""")
|
||||||
|
|
||||||
|
SSH_HELP_EPILOGUE_KNOWN_PASS_NO_KEYS = _("""\
|
||||||
|
The password you should use is "{password}".""")
|
||||||
|
|
||||||
|
SSH_HELP_EPILOGUE_UNKNOWN_PASS_NO_KEYS = _("""\
|
||||||
|
You should use the preconfigured password passed to cloud-init.""")
|
||||||
|
|
||||||
|
SSH_HELP_EPILOGUE_ONE_KEY = _("""\
|
||||||
|
You can log in with the {keytype} key with fingerprint:
|
||||||
|
|
||||||
|
{fingerprint}
|
||||||
""")
|
""")
|
||||||
|
|
||||||
SSH_HELP_EPILOGUE = _("""
|
SSH_HELP_EPILOGUE_MULTIPLE_KEYS = _("""\
|
||||||
The password you should use is "{password}".
|
You can log in with one of the following keys:
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
SSH_HELP_EPILOGUE_KNOWN_PASS_KEYS = _("""
|
||||||
|
Or you can use the password "{password}".""")
|
||||||
|
|
||||||
|
SSH_HELP_EPILOGUE_UNKNOWN_PASS_KEYS = _("""
|
||||||
|
Or you can use the preconfigured password passed to cloud-init.""")
|
||||||
|
|
||||||
SSH_HELP_NO_ADDRESSES = _("""
|
SSH_HELP_NO_ADDRESSES = _("""
|
||||||
Unfortunately this system seems to have no global IP addresses at this
|
Unfortunately this system seems to have no global IP addresses at this
|
||||||
time.
|
time.
|
||||||
|
@ -128,26 +144,52 @@ been set.
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
||||||
def ssh_help_texts(ips, password):
|
def ssh_help_texts(ssh_info):
|
||||||
|
|
||||||
texts = [_(SSH_HELP_PROLOGUE), ""]
|
texts = [_(SSH_HELP_PROLOGUE), ""]
|
||||||
|
|
||||||
if len(ips) > 0:
|
if len(ssh_info.ips) > 0:
|
||||||
if len(ips) > 1:
|
if len(ssh_info.ips) > 1:
|
||||||
texts.append(rewrap(_(SSH_HELP_MULTIPLE_ADDRESSES)))
|
texts.append(_(SSH_HELP_MULTIPLE_ADDRESSES))
|
||||||
texts.append("")
|
texts.append("")
|
||||||
for ip in ips:
|
for ip in ssh_info.ips:
|
||||||
texts.append(Text(
|
texts.append(Text(
|
||||||
"installer@" + str(ip), align='center'))
|
"{}@{}".format(ssh_info.username, ip), align='center'))
|
||||||
else:
|
else:
|
||||||
texts.append(_(SSH_HELP_ONE_ADDRESSES).format(
|
texts.append(_(SSH_HELP_ONE_ADDRESSES).format(
|
||||||
ip=str(ips[0])))
|
username=ssh_info.username, ip=str(ssh_info.ips[0])))
|
||||||
texts.append("")
|
texts.append("")
|
||||||
texts.append(
|
if ssh_info.authorized_key_fingerprints:
|
||||||
rewrap(_(SSH_HELP_EPILOGUE).format(
|
if len(ssh_info.authorized_key_fingerprints) == 1:
|
||||||
password=password)))
|
key = ssh_info.authorized_key_fingerprints[0]
|
||||||
|
texts.append(Text(_(SSH_HELP_EPILOGUE_ONE_KEY).format(
|
||||||
|
keytype=key.keytype, fingerprint=key.fingerprint)))
|
||||||
|
else:
|
||||||
|
texts.append(_(SSH_HELP_EPILOGUE_MULTIPLE_KEYS))
|
||||||
|
texts.append("")
|
||||||
|
rows = []
|
||||||
|
for key in ssh_info.authorized_key_fingerprints:
|
||||||
|
rows.append(
|
||||||
|
TableRow([Text(key.keytype), Text(key.fingerprint)]))
|
||||||
|
texts.append(TablePile(rows))
|
||||||
|
if ssh_info.password_kind == PasswordKind.KNOWN:
|
||||||
|
texts.append("")
|
||||||
|
texts.append(SSH_HELP_EPILOGUE_KNOWN_PASS_KEYS.format(
|
||||||
|
password=ssh_info.password))
|
||||||
|
elif ssh_info.password_kind == PasswordKind.UNKNOWN:
|
||||||
|
texts.append("")
|
||||||
|
texts.append(SSH_HELP_EPILOGUE_UNKNOWN_PASS_KEYS)
|
||||||
|
else:
|
||||||
|
if ssh_info.password_kind == PasswordKind.KNOWN:
|
||||||
|
texts.append(SSH_HELP_EPILOGUE_KNOWN_PASS_NO_KEYS.format(
|
||||||
|
password=ssh_info.password))
|
||||||
|
elif ssh_info.password_kind == PasswordKind.UNKNOWN:
|
||||||
|
texts.append(SSH_HELP_EPILOGUE_UNKNOWN_PASS_NO_KEYS)
|
||||||
texts.append("")
|
texts.append("")
|
||||||
texts.append(Text(host_key_info()))
|
texts.append(Text(summarize_host_keys([
|
||||||
|
(key.keytype, key.fingerprint)
|
||||||
|
for key in ssh_info.host_key_fingerprints
|
||||||
|
])))
|
||||||
else:
|
else:
|
||||||
texts.append("")
|
texts.append("")
|
||||||
texts.append(_(SSH_HELP_NO_ADDRESSES))
|
texts.append(_(SSH_HELP_NO_ADDRESSES))
|
||||||
|
@ -241,30 +283,6 @@ def menu_item(text, on_press=None):
|
||||||
return Color.frame_button(icon)
|
return Color.frame_button(icon)
|
||||||
|
|
||||||
|
|
||||||
async def get_global_addresses(app):
|
|
||||||
return await app.wait_with_text_dialog(
|
|
||||||
app.client.network.global_addresses.GET(),
|
|
||||||
_("Getting network info"),
|
|
||||||
can_cancel=True)
|
|
||||||
|
|
||||||
|
|
||||||
def get_installer_password(dry_run=False):
|
|
||||||
if dry_run:
|
|
||||||
fp = io.StringIO('installer:rAnd0Mpass')
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
fp = open("/var/log/cloud-init-output.log")
|
|
||||||
except FileNotFoundError:
|
|
||||||
fp = io.StringIO('')
|
|
||||||
|
|
||||||
with fp:
|
|
||||||
for line in fp:
|
|
||||||
if line.startswith("installer:"):
|
|
||||||
return line[len("installer:"):].strip()
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class OpenHelpMenu(WidgetWrap):
|
class OpenHelpMenu(WidgetWrap):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
|
@ -282,7 +300,7 @@ class OpenHelpMenu(WidgetWrap):
|
||||||
drop_to_shell,
|
drop_to_shell,
|
||||||
keys,
|
keys,
|
||||||
}
|
}
|
||||||
if self.parent.ssh_password is not None:
|
if self.parent.ssh_info is not None:
|
||||||
ssh_help = menu_item(
|
ssh_help = menu_item(
|
||||||
_("Help on SSH access"), on_press=self.parent.ssh_help)
|
_("Help on SSH access"), on_press=self.parent.ssh_help)
|
||||||
buttons.add(ssh_help)
|
buttons.add(ssh_help)
|
||||||
|
@ -324,7 +342,7 @@ class OpenHelpMenu(WidgetWrap):
|
||||||
about,
|
about,
|
||||||
]
|
]
|
||||||
|
|
||||||
if self.parent.ssh_password is not None:
|
if self.parent.ssh_info is not None:
|
||||||
entries.append(ssh_help)
|
entries.append(ssh_help)
|
||||||
|
|
||||||
if self.parent.app.opts.run_on_serial:
|
if self.parent.app.opts.run_on_serial:
|
||||||
|
@ -379,17 +397,20 @@ class HelpMenu(PopUpLauncher):
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.btn = header_btn(_("Help"), on_press=self._open)
|
self.btn = header_btn(_("Help"), on_press=self._open)
|
||||||
self.ssh_password = None
|
self.ssh_info = None
|
||||||
self.current_help = None
|
self.current_help = None
|
||||||
super().__init__(self.btn)
|
super().__init__(self.btn)
|
||||||
|
|
||||||
def _open(self, sender):
|
async def _get_ssh_info(self):
|
||||||
log.debug("open help menu")
|
self.ssh_info = await self.app.wait_with_text_dialog(
|
||||||
|
self.app.client.meta.ssh_info.GET(), "Getting SSH info")
|
||||||
self.open_pop_up()
|
self.open_pop_up()
|
||||||
|
|
||||||
|
def _open(self, sender):
|
||||||
|
log.debug("open help menu")
|
||||||
|
self.app.aio_loop.create_task(self._get_ssh_info())
|
||||||
|
|
||||||
def create_pop_up(self):
|
def create_pop_up(self):
|
||||||
if self.ssh_password is None:
|
|
||||||
self.ssh_password = get_installer_password(self.app.opts.dry_run)
|
|
||||||
self._menu = OpenHelpMenu(self)
|
self._menu = OpenHelpMenu(self)
|
||||||
return self._menu
|
return self._menu
|
||||||
|
|
||||||
|
@ -435,10 +456,8 @@ class HelpMenu(PopUpLauncher):
|
||||||
_("About the installer"),
|
_("About the installer"),
|
||||||
template.format(**info)))
|
template.format(**info)))
|
||||||
|
|
||||||
async def _ssh_help(self):
|
def ssh_help(self, sender=None):
|
||||||
texts = ssh_help_texts(
|
texts = ssh_help_texts(self.ssh_info)
|
||||||
await get_global_addresses(self.app),
|
|
||||||
self.ssh_password)
|
|
||||||
|
|
||||||
self._show_overlay(
|
self._show_overlay(
|
||||||
SimpleTextStretchy(
|
SimpleTextStretchy(
|
||||||
|
@ -447,9 +466,6 @@ class HelpMenu(PopUpLauncher):
|
||||||
*texts,
|
*texts,
|
||||||
))
|
))
|
||||||
|
|
||||||
def ssh_help(self, sender=None):
|
|
||||||
schedule_task(self._ssh_help())
|
|
||||||
|
|
||||||
def show_local(self, local_title, local_doc):
|
def show_local(self, local_title, local_doc):
|
||||||
|
|
||||||
def cb(sender=None):
|
def cb(sender=None):
|
||||||
|
|
|
@ -30,10 +30,6 @@ from subiquitycore.ui.utils import button_pile, rewrap, screen
|
||||||
from subiquitycore.screen import is_linux_tty
|
from subiquitycore.screen import is_linux_tty
|
||||||
from subiquitycore.view import BaseView
|
from subiquitycore.view import BaseView
|
||||||
|
|
||||||
from subiquity.ui.views.help import (
|
|
||||||
get_installer_password,
|
|
||||||
)
|
|
||||||
|
|
||||||
log = logging.getLogger("subiquity.views.welcome")
|
log = logging.getLogger("subiquity.views.welcome")
|
||||||
|
|
||||||
|
|
||||||
|
@ -85,11 +81,11 @@ def get_languages():
|
||||||
class WelcomeView(BaseView):
|
class WelcomeView(BaseView):
|
||||||
title = "Willkommen! Bienvenue! Welcome! Добро пожаловать! Welkom!"
|
title = "Willkommen! Bienvenue! Welcome! Добро пожаловать! Welkom!"
|
||||||
|
|
||||||
def __init__(self, controller, cur_lang, serial, ips):
|
def __init__(self, controller, cur_lang, serial, ssh_info):
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.cur_lang = cur_lang
|
self.cur_lang = cur_lang
|
||||||
if serial and not controller.app.rich_mode:
|
if serial and not controller.app.rich_mode:
|
||||||
s = self.make_serial_choices(ips)
|
s = self.make_serial_choices(ssh_info)
|
||||||
self.title = "Welcome!"
|
self.title = "Welcome!"
|
||||||
else:
|
else:
|
||||||
s = self.make_language_choices()
|
s = self.make_language_choices()
|
||||||
|
@ -119,8 +115,7 @@ class WelcomeView(BaseView):
|
||||||
lb, buttons=None, narrow_rows=True,
|
lb, buttons=None, narrow_rows=True,
|
||||||
excerpt=_("Use UP, DOWN and ENTER keys to select your language."))
|
excerpt=_("Use UP, DOWN and ENTER keys to select your language."))
|
||||||
|
|
||||||
def make_serial_choices(self, ips):
|
def make_serial_choices(self, ssh_info):
|
||||||
ssh_password = get_installer_password(self.controller.opts.dry_run)
|
|
||||||
btns = [
|
btns = [
|
||||||
other_btn(
|
other_btn(
|
||||||
label="Switch to rich mode",
|
label="Switch to rich mode",
|
||||||
|
@ -135,13 +130,13 @@ class WelcomeView(BaseView):
|
||||||
Text(rewrap(SERIAL_TEXT)),
|
Text(rewrap(SERIAL_TEXT)),
|
||||||
Text(""),
|
Text(""),
|
||||||
]
|
]
|
||||||
if ssh_password and ips:
|
if ssh_info:
|
||||||
widgets.append(Text(rewrap(SSH_TEXT)))
|
widgets.append(Text(rewrap(SSH_TEXT)))
|
||||||
widgets.append(Text(""))
|
widgets.append(Text(""))
|
||||||
btns.insert(1, other_btn(
|
btns.insert(1, other_btn(
|
||||||
label="View SSH instructions",
|
label="View SSH instructions",
|
||||||
on_press=self.ssh_help,
|
on_press=self.ssh_help,
|
||||||
user_arg=ssh_password))
|
user_arg=ssh_info))
|
||||||
widgets.extend([
|
widgets.extend([
|
||||||
button_pile(btns),
|
button_pile(btns),
|
||||||
])
|
])
|
||||||
|
@ -154,9 +149,9 @@ class WelcomeView(BaseView):
|
||||||
self.controller.ui.set_header(self.title)
|
self.controller.ui.set_header(self.title)
|
||||||
self._w = self.make_language_choices()
|
self._w = self.make_language_choices()
|
||||||
|
|
||||||
def ssh_help(self, sender, password):
|
def ssh_help(self, sender, ssh_info):
|
||||||
menu = self.controller.app.help_menu
|
menu = self.controller.app.help_menu
|
||||||
menu.ssh_password = password
|
menu.ssh_info = ssh_info
|
||||||
menu.ssh_help()
|
menu.ssh_help()
|
||||||
|
|
||||||
def choose_language(self, sender, code):
|
def choose_language(self, sender, code):
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import pwd
|
||||||
|
|
||||||
from subiquitycore.utils import run_command
|
from subiquitycore.utils import run_command
|
||||||
|
|
||||||
|
@ -36,13 +38,20 @@ def host_key_fingerprints():
|
||||||
keyfiles.append(line.split(None, 1)[1])
|
keyfiles.append(line.split(None, 1)[1])
|
||||||
info = []
|
info = []
|
||||||
for keyfile in keyfiles:
|
for keyfile in keyfiles:
|
||||||
cp = run_command(['ssh-keygen', '-lf', keyfile])
|
info.extend(fingerprints(keyfile))
|
||||||
if cp.returncode != 0:
|
return info
|
||||||
log.debug("ssh-keygen -lf %s failed %r", keyfile, cp.stderr)
|
|
||||||
continue
|
|
||||||
parts = cp.stdout.strip().split()
|
def fingerprints(keyfile):
|
||||||
length, fingerprint, host, keytype = parts
|
info = []
|
||||||
keytype = keytype.strip('()')
|
cp = run_command(['ssh-keygen', '-lf', keyfile])
|
||||||
|
if cp.returncode != 0:
|
||||||
|
log.debug("ssh-keygen -lf %s failed %r", keyfile, cp.stderr)
|
||||||
|
return info
|
||||||
|
for line in cp.stdout.splitlines():
|
||||||
|
parts = line.strip().replace('\r', '').split()
|
||||||
|
fingerprint = parts[1]
|
||||||
|
keytype = parts[-1].strip('()')
|
||||||
info.append((keytype, fingerprint))
|
info.append((keytype, fingerprint))
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
@ -55,13 +64,16 @@ host_key_tmpl = """
|
||||||
{keytype:{width}} {fingerprint}"""
|
{keytype:{width}} {fingerprint}"""
|
||||||
|
|
||||||
single_host_key_tmpl = _("""\
|
single_host_key_tmpl = _("""\
|
||||||
The {keytype} host key fingerprints is:
|
The {keytype} host key fingerprint is:
|
||||||
{fingerprint}
|
{fingerprint}
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
||||||
def host_key_info():
|
def host_key_info():
|
||||||
fingerprints = host_key_fingerprints()
|
return summarize_host_keys(host_key_fingerprints())
|
||||||
|
|
||||||
|
|
||||||
|
def summarize_host_keys(fingerprints):
|
||||||
if len(fingerprints) == 0:
|
if len(fingerprints) == 0:
|
||||||
return ''
|
return ''
|
||||||
if len(fingerprints) == 1:
|
if len(fingerprints) == 1:
|
||||||
|
@ -77,6 +89,19 @@ def host_key_info():
|
||||||
return "".join(lines)
|
return "".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def user_key_fingerprints(username):
|
||||||
|
try:
|
||||||
|
user_info = pwd.getpwnam(username)
|
||||||
|
except KeyError:
|
||||||
|
log.exception("getpwnam(%s) failed", username)
|
||||||
|
return []
|
||||||
|
user_key_file = '{}/.ssh/authorized_keys'.format(user_info.pw_dir)
|
||||||
|
if os.path.exists(user_key_file):
|
||||||
|
return fingerprints(user_key_file)
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def get_ips_standalone():
|
def get_ips_standalone():
|
||||||
from probert.prober import Prober
|
from probert.prober import Prober
|
||||||
from subiquitycore.models.network import NETDEV_IGNORED_IFACE_TYPES
|
from subiquitycore.models.network import NETDEV_IGNORED_IFACE_TYPES
|
||||||
|
|
Loading…
Reference in New Issue