Merge pull request #202 from CanonicalLtd/mwhudson/ubuntu-core-login-screen

rewrite login screen per feedback
This commit is contained in:
Michael Hudson-Doyle 2017-02-17 15:12:22 +13:00 committed by GitHub
commit 2944098c98
4 changed files with 76 additions and 49 deletions

View File

@ -13,12 +13,14 @@ stty icrnl -echo
if [ "$(snap managed)" = "true" ]; then if [ "$(snap managed)" = "true" ]; then
# check if we have extrausers that have no password set # check if we have extrausers that have no password set
if grep -qE '^[-a-z0-9+.-_]+:x:' /var/lib/extrausers/passwd && ! grep -qE '^[-a-z0-9+.-_]+:\$[0-9]+\$.*:' /var/lib/extrausers/shadow; then if grep -qE '^[-a-z0-9+.-_]+:x:' /var/lib/extrausers/passwd && ! grep -qE '^[-a-z0-9+.-_]+:\$[0-9]+\$.*:' /var/lib/extrausers/shadow; then
if [ ! -f /run/console-conf/login-details.txt ]; then tty=$(tty)
tty=$(echo ${tty#/dev/} | tr '/' '-')
if [ ! -f /run/console-conf/login-details-${tty}.txt ]; then
mkdir -p /run/console-conf mkdir -p /run/console-conf
/usr/share/subiquity/console-conf-write-login-details > /run/console-conf/login-details.txt.tmp /usr/share/subiquity/console-conf-write-login-details > /run/console-conf/login-details-${tty}.txt.tmp
mv /run/console-conf/login-details.txt.tmp /run/console-conf/login-details.txt mv /run/console-conf/login-details-${tty}.txt.tmp /run/console-conf/login-details-${tty}.txt
fi fi
cat /run/console-conf/login-details.txt cat /run/console-conf/login-details-${tty}.txt
read REPLY read REPLY
else else
touch /var/lib/console-conf/complete touch /var/lib/console-conf/complete

View File

@ -16,7 +16,6 @@
import json import json
import logging import logging
import os import os
import subprocess
import sys import sys
from subiquitycore.controller import BaseController from subiquitycore.controller import BaseController
@ -47,36 +46,78 @@ def get_device_owner():
return result return result
return None return None
login_details_tmpl = """\ def host_key_fingerprints():
Congratulations! This device is now registered to {realname}. """Query sshd to find the host keys and then fingerprint them.
The next step is to log into the device via ssh: Returns a sequence of (key-type, fingerprint) pairs.
"""
config = run_command(['sshd', '-T'])
if config['status'] != 0:
log.debug("sshd -T failed %r", config['err'])
return []
keyfiles = []
for line in config['output'].splitlines():
if line.startswith('hostkey '):
keyfiles.append(line.split(None, 1)[1])
info = []
for keyfile in keyfiles:
result = run_command(['ssh-keygen', '-lf', keyfile])
if result['status'] != 0:
log.debug("ssh-keygen -lf %s failed %r", keyfile, result['err'])
continue
parts = result['output'].strip().split()
length, fingerprint, host, keytype = parts
keytype = keytype.strip('()')
info.append((keytype, fingerprint))
return info
{sshcommands}
These keys can be used to log in:
{sshkeys} host_keys_intro = """
Once you've logged in, you can optionally set a password by running The host key fingerprints are:
"sudo passwd $USER". After you've set a password, you can also use it
to log in here.
You can explore snappy core with snap --help.
""" """
def write_login_details(fp, realname, username, ips, fingerprints): host_key_tmpl = """\
sshcommands = "" {keytype:{width}} {fingerprint}
"""
single_host_key_tmpl = """\
The {keytype} host key fingerprints is:
{fingerprint}
"""
def host_key_info():
fingerprints = host_key_fingerprints()
if len(fingerprints) == 1:
[(keytype, fingerprint)] = fingerprints
return single_host_key_tmpl.format(keytype=keytype, fingerprint=fingerprint)
lines = [host_keys_intro]
longest_type = max([len(keytype) for keytype, _ in fingerprints])
for keytype, fingerprint in fingerprints:
lines.append(host_key_tmpl.format(keytype=keytype, fingerprint=fingerprint, width=longest_type))
return "".join(lines)
login_details_tmpl = """\
Ubuntu Core 16 on {first_ip} ({tty_name})
{host_key_info}
To login:
{sshcommands}
Personalize your account at https://login.ubuntu.com.
"""
def write_login_details(fp, username, ips):
sshcommands = "\n"
for ip in ips: for ip in ips:
sshcommands += " ssh %s@%s\n"%(username, ip) sshcommands += " ssh %s@%s\n"%(username, ip)
sshkeys = "" tty_name = os.ttyname(0)[5:] # strip off the /dev/
for fingerprint in fingerprints: fp.write(login_details_tmpl.format(
sshkeys += " " + fingerprint + "\n" sshcommands=sshcommands, host_key_info=host_key_info(), tty_name=tty_name, first_ip=ips[0]))
fp.write(login_details_tmpl.format(realname=realname, username=username, sshcommands=sshcommands, sshkeys=sshkeys))
def write_login_details_standalone(): def write_login_details_standalone():
owner = get_device_owner() owner = get_device_owner()
if owner is None: if owner is None:
# Nothing much we can do :/ print("No device owner details found.")
print("No device owner details found")
return 0 return 0
from probert import network from probert import network
from subiquitycore.models.network import NETDEV_IGNORED_IFACE_NAMES, NETDEV_IGNORED_IFACE_TYPES from subiquitycore.models.network import NETDEV_IGNORED_IFACE_NAMES, NETDEV_IGNORED_IFACE_TYPES
@ -90,10 +131,9 @@ def write_login_details_standalone():
if l.name in NETDEV_IGNORED_IFACE_NAMES: if l.name in NETDEV_IGNORED_IFACE_NAMES:
continue continue
for _, addr in sorted(l.addresses.items()): for _, addr in sorted(l.addresses.items()):
if addr.scope == "global":
ips.append(addr.ip) ips.append(addr.ip)
key_file = os.path.join(owner['homedir'], ".ssh/authorized_keys") write_login_details(sys.stdout, owner['username'], ips)
fingerprints = run_command(['ssh-keygen', '-lf', key_file])['output'].replace('\r', '').splitlines()
write_login_details(sys.stdout, owner['realname'], owner['username'], ips, fingerprints)
return 0 return 0
@ -124,7 +164,6 @@ class IdentityController(BaseController):
'username': email, 'username': email,
} }
self.model.add_user(result) self.model.add_user(result)
ssh_keys = subprocess.getoutput('ssh-add -L').splitlines()
login_details_path = '.subiquity/login-details.txt' login_details_path = '.subiquity/login-details.txt'
else: else:
self.ui.frame.body.progress.set_text("Contacting store...") self.ui.frame.body.progress.set_text("Contacting store...")
@ -140,24 +179,15 @@ class IdentityController(BaseController):
'realname': email, 'realname': email,
'username': data['username'], 'username': data['username'],
} }
ssh_keys = data['ssh-keys']
os.makedirs('/run/console-conf', exist_ok=True) os.makedirs('/run/console-conf', exist_ok=True)
login_details_path = '/run/console-conf/login-details.txt' login_details_path = '/run/console-conf/login-details.txt'
self.model.add_user(result) self.model.add_user(result)
log.debug('ssh_keys %s', ssh_keys)
fingerprints = []
for key in ssh_keys:
keygen_result = subprocess.Popen(['ssh-keygen', '-lf', '-'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
fingerprint, err = keygen_result.communicate(key.encode('utf-8'))
fingerprints.append(fingerprint.decode('utf-8', 'replace').replace('\r', '').strip())
self.model.user.fingerprints = fingerprints
log.debug('fingerprints %s', fingerprints)
ips = [] ips = []
net_model = self.controllers['Network'].model net_model = self.controllers['Network'].model
for dev in net_model.get_all_netdevs(): for dev in net_model.get_all_netdevs():
ips.extend(dev.actual_ip_addresses) ips.extend(dev.actual_global_ip_addresses)
with open(login_details_path, 'w') as fp: with open(login_details_path, 'w') as fp:
write_login_details(fp, result['realname'], result['username'], ips, fingerprints) write_login_details(fp, result['username'], ips)
self.login() self.login()
def cancel(self): def cancel(self):

View File

@ -70,7 +70,7 @@ class LoginView(BaseView):
login_text += remote_tpl.format(**login_info) login_text += remote_tpl.format(**login_info)
ips = [] ips = []
for dev in self.netdevs: for dev in self.netdevs:
for addr in dev.actual_ip_addresses: for addr in dev.actual_global_ip_addresses:
ips.append(addr) ips.append(addr)
sl += [Text(login_text), Padding.line_break("")] sl += [Text(login_text), Padding.line_break("")]
@ -78,15 +78,6 @@ class LoginView(BaseView):
ssh_iface = " ssh %s@%s" % (user.username, ip) ssh_iface = " ssh %s@%s" % (user.username, ip)
sl.append(Text(ssh_iface)) sl.append(Text(ssh_iface))
sl += [
Padding.line_break(""),
Text("SSH keys with the following fingerprints can be used to log in:"),
Padding.line_break(""),
]
for fingerprint in user.fingerprints:
sl.append(Text(" " + fingerprint))
return Pile(sl) return Pile(sl)
def confirm(self, result): def confirm(self, result):

View File

@ -231,6 +231,10 @@ class Networkdev:
r.append(ip) r.append(ip)
return r return r
@property
def actual_global_ip_addresses(self):
return [addr.ip for _, addr in sorted(self._net_info.addresses.items()) if addr.scope == "global"]
@property @property
def configured_ip_addresses(self): def configured_ip_addresses(self):
return self._configuration.setdefault('addresses', []) return self._configuration.setdefault('addresses', [])