do not set installer user password if it has password or authorized keys
dearie me making a nice UI can involve a lot of typing.
This commit is contained in:
parent
47eefa42fe
commit
e3361aa51c
|
@ -79,11 +79,26 @@ class ApplicationStatus:
|
|||
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: str
|
||||
password_kind: PasswordKind
|
||||
password: Optional[str]
|
||||
authorized_key_fingerprints: List[KeyFingerprint]
|
||||
ips: List[str]
|
||||
host_key_fingerprints: List[KeyFingerprint]
|
||||
|
||||
|
||||
class RefreshCheckState(enum.Enum):
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import pwd
|
||||
import shlex
|
||||
import sys
|
||||
import time
|
||||
|
@ -26,6 +25,7 @@ from aiohttp import web
|
|||
|
||||
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
|
||||
|
||||
|
@ -37,6 +37,10 @@ from subiquitycore.async_helpers import run_in_thread, schedule_task
|
|||
from subiquitycore.context import with_context
|
||||
from subiquitycore.core import Application
|
||||
from subiquitycore.prober import Prober
|
||||
from subiquitycore.ssh import (
|
||||
host_key_fingerprints,
|
||||
user_key_fingerprints,
|
||||
)
|
||||
from subiquitycore.utils import arun_command, run_command
|
||||
|
||||
from subiquity.common.api.server import (
|
||||
|
@ -53,7 +57,9 @@ from subiquity.common.types import (
|
|||
ApplicationState,
|
||||
ApplicationStatus,
|
||||
ErrorReportRef,
|
||||
KeyFingerprint,
|
||||
LiveSessionSSHInfo,
|
||||
PasswordKind,
|
||||
)
|
||||
from subiquity.server.controller import SubiquityController
|
||||
from subiquity.models.subiquity import SubiquityModel
|
||||
|
@ -102,18 +108,32 @@ class MetaController:
|
|||
controller.configured()
|
||||
|
||||
async def ssh_info_GET(self) -> Optional[LiveSessionSSHInfo]:
|
||||
password = self.app.installer_user_passwd
|
||||
if password is None:
|
||||
return None
|
||||
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='installer',
|
||||
password=password,
|
||||
ips=ips)
|
||||
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():
|
||||
|
@ -188,6 +208,8 @@ class SubiquityServer(Application):
|
|||
self.confirming_tty = ''
|
||||
self.fatal_error = None
|
||||
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())
|
||||
|
@ -398,14 +420,14 @@ class SubiquityServer(Application):
|
|||
init = stages.Init()
|
||||
init.read_cfg()
|
||||
init.fetch(existing="trust")
|
||||
cloud = init.cloudify()
|
||||
self.cloud = init.cloudify()
|
||||
autoinstall_path = '/autoinstall.yaml'
|
||||
if 'autoinstall' in cloud.cfg:
|
||||
if 'autoinstall' in self.cloud.cfg:
|
||||
if not os.path.exists(autoinstall_path):
|
||||
atomic_helper.write_file(
|
||||
autoinstall_path,
|
||||
safeyaml.dumps(
|
||||
cloud.cfg['autoinstall']).encode('utf-8'),
|
||||
self.cloud.cfg['autoinstall']).encode('utf-8'),
|
||||
mode=0o600)
|
||||
if os.path.exists(autoinstall_path):
|
||||
self.opts.autoinstall = autoinstall_path
|
||||
|
@ -414,35 +436,60 @@ class SubiquityServer(Application):
|
|||
"cloud-init status: %r, assumed disabled",
|
||||
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-passwd")
|
||||
passfile = self.state_path("installer-user-passwd")
|
||||
|
||||
if os.path.exists(passfile):
|
||||
with open(passfile) as fp:
|
||||
self.installer_user_passwd = fp.read()
|
||||
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_passwd = rand_user_password()
|
||||
self.installer_user_name = os.environ['USER']
|
||||
use_passwd(rand_user_password())
|
||||
return
|
||||
# refreshing from a version of subiquity that relied on
|
||||
# cloud-init to set the password to one that does not should
|
||||
# not reset the password.
|
||||
|
||||
(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:
|
||||
self.installer_user_passwd = passwd
|
||||
return
|
||||
try:
|
||||
pwd.getpwnam('installer')
|
||||
except KeyError:
|
||||
log.info("no installer user")
|
||||
return
|
||||
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='installer:'+passwd+'\n')
|
||||
cp = run_command('chpasswd', input=username + ':'+passwd+'\n')
|
||||
if cp.returncode == 0:
|
||||
self.installer_user_passwd = passwd
|
||||
with open(passfile, 'w') as fp:
|
||||
fp.write(passwd)
|
||||
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):
|
||||
self.controllers.load_all()
|
||||
|
|
|
@ -25,7 +25,7 @@ from urwid import (
|
|||
)
|
||||
|
||||
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 (
|
||||
header_btn,
|
||||
other_btn,
|
||||
|
@ -55,6 +55,7 @@ from subiquitycore.ui.width import (
|
|||
widget_width,
|
||||
)
|
||||
|
||||
from subiquity.common.types import PasswordKind
|
||||
from subiquity.ui.views.error import ErrorReportListStretchy
|
||||
|
||||
log = logging.getLogger('subiquity.ui.help')
|
||||
|
@ -108,13 +109,30 @@ To connect, SSH to any of these addresses:
|
|||
""")
|
||||
|
||||
SSH_HELP_ONE_ADDRESSES = _("""
|
||||
To connect, SSH to {username}@{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 = _("""
|
||||
The password you should use is "{password}".
|
||||
SSH_HELP_EPILOGUE_MULTIPLE_KEYS = _("""\
|
||||
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 = _("""
|
||||
Unfortunately this system seems to have no global IP addresses at this
|
||||
time.
|
||||
|
@ -132,7 +150,7 @@ def ssh_help_texts(ssh_info):
|
|||
|
||||
if len(ssh_info.ips) > 0:
|
||||
if len(ssh_info.ips) > 1:
|
||||
texts.append(rewrap(_(SSH_HELP_MULTIPLE_ADDRESSES)))
|
||||
texts.append(_(SSH_HELP_MULTIPLE_ADDRESSES))
|
||||
texts.append("")
|
||||
for ip in ssh_info.ips:
|
||||
texts.append(Text(
|
||||
|
@ -141,11 +159,37 @@ def ssh_help_texts(ssh_info):
|
|||
texts.append(_(SSH_HELP_ONE_ADDRESSES).format(
|
||||
username=ssh_info.username, ip=str(ssh_info.ips[0])))
|
||||
texts.append("")
|
||||
texts.append(
|
||||
rewrap(_(SSH_HELP_EPILOGUE).format(
|
||||
password=ssh_info.password)))
|
||||
if ssh_info.authorized_key_fingerprints:
|
||||
if len(ssh_info.authorized_key_fingerprints) == 1:
|
||||
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("")
|
||||
texts.append(Text(host_key_info()))
|
||||
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(Text(summarize_host_keys([
|
||||
(key.keytype, key.fingerprint)
|
||||
for key in ssh_info.host_key_fingerprints
|
||||
])))
|
||||
else:
|
||||
texts.append("")
|
||||
texts.append(_(SSH_HELP_NO_ADDRESSES))
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
import os
|
||||
import pwd
|
||||
|
||||
from subiquitycore.utils import run_command
|
||||
|
||||
|
@ -36,13 +38,20 @@ def host_key_fingerprints():
|
|||
keyfiles.append(line.split(None, 1)[1])
|
||||
info = []
|
||||
for keyfile in keyfiles:
|
||||
info.extend(fingerprints(keyfile))
|
||||
return info
|
||||
|
||||
|
||||
def fingerprints(keyfile):
|
||||
info = []
|
||||
cp = run_command(['ssh-keygen', '-lf', keyfile])
|
||||
if cp.returncode != 0:
|
||||
log.debug("ssh-keygen -lf %s failed %r", keyfile, cp.stderr)
|
||||
continue
|
||||
parts = cp.stdout.strip().split()
|
||||
length, fingerprint, host, keytype = parts
|
||||
keytype = keytype.strip('()')
|
||||
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))
|
||||
return info
|
||||
|
||||
|
@ -55,13 +64,16 @@ host_key_tmpl = """
|
|||
{keytype:{width}} {fingerprint}"""
|
||||
|
||||
single_host_key_tmpl = _("""\
|
||||
The {keytype} host key fingerprints is:
|
||||
The {keytype} host key fingerprint is:
|
||||
{fingerprint}
|
||||
""")
|
||||
|
||||
|
||||
def host_key_info():
|
||||
fingerprints = host_key_fingerprints()
|
||||
return summarize_host_keys(host_key_fingerprints())
|
||||
|
||||
|
||||
def summarize_host_keys(fingerprints):
|
||||
if len(fingerprints) == 0:
|
||||
return ''
|
||||
if len(fingerprints) == 1:
|
||||
|
@ -77,6 +89,19 @@ def host_key_info():
|
|||
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():
|
||||
from probert.prober import Prober
|
||||
from subiquitycore.models.network import NETDEV_IGNORED_IFACE_TYPES
|
||||
|
|
Loading…
Reference in New Issue