Merge pull request #918 from mwhudson/set-installer-password

have subiquity set password for installer user in live session
This commit is contained in:
Michael Hudson-Doyle 2021-03-29 15:46:36 +13:00 committed by GitHub
commit b0ac3960be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 262 additions and 90 deletions

View File

@ -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@'):

View File

@ -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:

View File

@ -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:

View File

@ -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()

View File

@ -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")

View File

@ -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):

View File

@ -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):

View File

@ -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