Configure your board for headless use¶
All Ubuntu pre-installed images, both for cloud and SBCs use cloud-init for their first boot setup, including initial user creation. It is possible to customize the first boot to configure a board for remote usage. This includes securely importing all necessary credentials, and making it possible to locate easily on the local network, removing the need to ever attach a screen or keyboard to the board.
You will need the following:
An Ubuntu One (Launchpad) or GitHub user account
An SSH public/private key-pair (see ssh-keygen(1) for more information)
Cloud-init seeds¶
The cloud-init “seed” provides the configuration that cloud-init will act upon during the first boot. The location of the seed is image dependent. On the Ubuntu for Raspberry Pi images, it is present on the boot partition (the first FAT partition on the images). On other images, it may be present on a FAT partition labeled “CIDATA”. The location will be documented in the board’s corresponding chapter in these docs.
Wherever it resides, the cloud-init seed always consists of the following files, all in YAML format:
- meta-data
While mandatory, this is largely uninteresting for our purposes here. It simply “identifies” the board and specifies whether the seed has a network-based component (this is more useful in a cloud context)
- user-data
This defines the bulk of the cloud-init configuration, specifying initial user characteristics, packages to install, files to manipulate, and so on
- network-config
Optional to the seed, but required in our case to ensure that cloud-init has a valid network connection from which to retrieve keys and packages
Network configuration¶
The network-config
file contains the netplan compatible
configuration for networking cards present on the board. The default network
configuration typically attempts connection on available interfaces, but does
not require it. This is fine for the default cloud-init configuration, which
does not require network access to complete.
However, if your configuration requires network access for anything (installing packages, retrieving SSH keys from an online account, mounting remote file-systems, and so on), you will need to change the default configuration to require a connection. This ensures that cloud-init will wait until the specified connection is fully online before proceeding with the rest of its configuration.
As an example, take the default network-config
on the Raspberry Pi
images:
version: 2
ethernets:
eth0:
dhcp4: true
optional: true
If your board has an ethernet port which will be connected on first boot, you can simply change this indicate the connection is mandatory:
version: 2
ethernets:
eth0:
dhcp4: true
optional: false
On a board which has ethernet, but which will be using wifi for connectivity
you might add a wifis
section instead with a mandatory interface:
version: 2
ethernets:
eth0:
dhcp4: true
optional: true
wifis:
wlan0:
dhcp4: true
optional: false
access-points:
my-wifi-ssid:
password: "my very secret wifi password"
Note that there is no specific restriction on the type of network connectivity required. Ethernet or wifi can be used for first boot configuration. That said, ethernet is typically preferred where available as the simpler and more reliable medium.
Customizing the users¶
On the vast majority of pre-installed Ubuntu images, the default username is “ubuntu” with a default password of “ubuntu”. Obviously this is well known and insecure. For this reason, SSH password-based authentication is disabled by default on all such images, and an initial password change is mandated on login.
All these aspects may be configured with the user-data
portion of the
cloud-init seed. The user
and users
keys control the details of the
user(s) created on first boot.
- user
Specifies the attributes of the “default” user, including:
- name
The name of the user to create. Defaults to “ubuntu”
- plain_text_passwd
The password for the user. Defaults to “ubuntu”
- hashed_passwd
You can also supply a default password as a hash (see below for instructions on generating the hash)
- lock_passwd
If set to
true
(the default), disables password based login- groups
The list of groups to add the user to
- homedir
The location of the user’s home directory. Defaults to
/home/name
- shell
Path to the user’s login shell. Defaults to bash(1) on Ubuntu
- ssh_import_id
Import SSH keys from the specified account (see below for more information)
- sudo
List of strings containing sudo(8) rules for this user
- users
The list of users to be created. Initially, this is just the “default” user defined by the
user
key above. However, additional entries using the same sub-keys as those underuser
may also be included in the list. The “default” entry may also be excluded to prevent its creation.
The default user configuration on Ubuntu could be expressed as follows. Note,
this configuration is implicit in the cloud-init installation; it doesn’t need
to be specified in your user-data
, this is simply to give context for
the changes below:
user:
name: ubuntu
plain_text_passwd: "ubuntu"
groups: [adm, cdrom, dip, lxd, sudo]
lock_passwd: true
shell: /bin/bash
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
users:
- default
If you want to rename the default user to “fred” and set a different password
that isn’t locked, you can use the following in your user-data
:
user:
name: fred
plain_text_passwd: "flintst0ne"
lock_passwd: false
You can also specify a hash of a password:
user:
name: fred
hashed_passwd: "$6$rounds=500000$V0fxPRRWCnTWfCIz$dV9YdtDo5MOrOyXPMw6tuHVtV/dxc3EtRzIyl7AaZD.GZvL0nNvdG1VT4xYwvM0e/j70eYsbRpKKB5CxtpGUd1"
lock_passwd: false
The hash can be generated with the mkpasswd(1) utility from the whois package, like so:
ubuntu@ubuntu:~$
mkpasswd --method=SHA-512 --rounds=500000
Password: # not echoed
$6$rounds=500000$V0fxPRRWCnTWfCIz$dV9YdtDo5MOrOyXPMw6tuHVtV/dxc3EtRzIyl7AaZD.GZvL0nNvdG1VT4xYwvM0e/j70eYsbRpKKB5CxtpGUd1
Warning
Be aware that this is barely more secure than a plain text password. In both cases, the password or the hash will typically be world readable after the machine has booted. You are strongly recommended not to rely on this for remote first boot login. See SSH authentication below for a more secure alternative.
To define a user in addition to the default one, add it to the users
key.
Include “default” to ensure the user defined under user
is also created:
user:
name: fred
hashed_passwd: "$6$rounds=500000$V0fxPRRWCnTWfCIz$dV9YdtDo5MOrOyXPMw6tuHVtV/dxc3EtRzIyl7AaZD.GZvL0nNvdG1VT4xYwvM0e/j70eYsbRpKKB5CxtpGUd1"
lock_passwd: false
users:
- default
- name: barney
hashed_passwd: "$6$rounds=500000$TEz/1c9AInDtCeCu$enA9jQEKTDHjypdMXdfTXMN5Khw./J3r0uIzHpktNjxZXw26k22mwcJ68el8GFDSR5i6unmmg/ePm.lVxkfbF0"
groups: [lxd]
lock_passwd: false
Alternatively, you can suppress creation of the default user (by not including
“default” under users
), and simply define all the users directly:
users:
- name: fred
hashed_passwd: "$6$rounds=500000$V0fxPRRWCnTWfCIz$dV9YdtDo5MOrOyXPMw6tuHVtV/dxc3EtRzIyl7AaZD.GZvL0nNvdG1VT4xYwvM0e/j70eYsbRpKKB5CxtpGUd1"
groups: [adm, cdrom, dip, lxd, sudo]
lock_passwd: false
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
- name: barney
hashed_passwd: "$6$rounds=500000$TEz/1c9AInDtCeCu$enA9jQEKTDHjypdMXdfTXMN5Khw./J3r0uIzHpktNjxZXw26k22mwcJ68el8GFDSR5i6unmmg/ePm.lVxkfbF0"
groups: [lxd]
lock_passwd: false
Note
In this case, as neither user is the “default” user, their definitions will not inherit from the cloud-init defaults. Remember to include sudo or administration rights for at least one user in this case.
SSH authentication¶
By default, password-based authentication for SSH is disabled because the default usernames and passwords are both well known and trivially guessable.
You can control whether SSH password-based authentication is enabled via
cloud-init. You can also import SSH keys for public-key authentication from
either a GitHub or Launchpad account. The following keys in user-data
are used for this:
- ssh_pwauth
If set to “true” (it is “false” by default), password-based authentication will be permitted for SSH
- ssh_import_id
Defines the list of accounts to request SSH public keys from. May be specified at the top-level, in which case imported keys are assigned to all users created by cloud-init, or under individual user definitions, in which case the keys apply just to that user
We strongly recommend you leave SSH password-based authentication disabled. Importing SSH public keys for your user(s) from GitHub or Launchpad is a much more secure option as at no point will your machine be remotely accessible with a username / password combination defined in a world-readable file. Naturally, this requires an internet connection (see Network configuration above).
As noted, the ssh_import_id
value is a list of account names. GitHub
accounts are prefixed with gh:
and Launchpad accounts with lp:
. For
example:
ssh_import_id:
- lp:launchpad_username
- gh:github_username
For a complete example, consider the following user-data
file, which
changes the default username to “fred”, the default password to “flintst0ne”,
leaves SSH password-based authentication disabled (explicitly), and imports SSH
keys from the GitHub user “fred_flintstone”:
user:
name: fred
plain_text_passwd: flintst0ne
lock_passwd: false
ssh_pwauth: false
ssh_import_id:
- gh:fred_flintstone
Finding your board¶
A common issue with headless SBCs is how to locate them on the network once they have booted. One method is to configure each with a static IP address, but this involves a certain amount of complexity to ensure all machines have a unique configuration. Another is to have a router configuration which is capable of reporting newly seen machines.
However, a popular alternative is to use mDNS. If you have
avahi-daemon(8) active on your system, you can locate machines by
their hostname within the .local
domain. To accomplish this, you will use
the following keys within user-data
:
- hostname
Sets the machine’s hostname to the specified value
- package_update
If set to “true”, causes the local package index to updated. This matters less on Ubuntu cloud images as they are regenerated daily. However, the Ubuntu board images are static once released and thus extremely likely to have an out of date package index on first boot
- package_upgrade
If set to “true”, causes the package manager to run an upgrade of installed packages during first boot. Again, this is more important on Ubuntu board images than cloud images
- packages
The list of extra packages to install, once any requested update or upgrade is concluded
The following example will set the machine’s hostname to “mypi”, ensure all packages are up to date, and install “avahi-daemon”.
hostname: mypi
package_update: true
package_upgrade: true
packages:
- avahi-daemon
Be aware this will require an internet connection on first boot (see Network configuration above).
Once cloud-init has finished, you should be able to reach your machine (with ping(1) or ssh(1)) as “mypi.local”, assuming you also have “avahi-daemon” installed on your client machine.
Optimizations¶
If you are booting many boards with the same release or distribution, it may be
beneficial to configure a local apt cache (see apt-cacher-ng(8)). If
you have such a cache, you can use it by specifying it in your
user-data
. Change the highlighted line to the URL for your cache:
apt:
conf: |
Acquire::http { Proxy "http://acng.example.com:3142"; }