One of the neat features of Raspbian is the ability to set up SSH out of the box by placing an ssh file on the boot partition. This is a security feature to ensure that your Pi isn’t booted up with SSH for remote access enabled, plus a widely known default username and password (pi / raspberry) which has full “sudo” rights before you’ve had a chance to change it.
On the surface, Ubuntu on the Pi has some similarities here, and some differences:
- As with Raspbian, there’s a default username and password (ubuntu / ubuntu).
- As with Raspbian, the default user has full “sudo” rights.
- Unlike Raspbian, SSH is enabled by default. This is an obvious security problem given the above points.
- To mitigate this, unlike Raspbian, Ubuntu forces you to change your password on first login.
Still, doesn’t this leave a (hopefully brief) period of time when your Pi is potentially network connected, with an active SSH daemon, and a widely known default username and password combination with full root access? Yes … it does. Can we do better than this? Yes, with …
cloud-init
First, we’ll see what I usually stick in the user-data configuration file on a freshly flashed SD card, then we’ll dissect it to see what’s going on and what else you can do with it!
Note
The file’s name is literally user-data with no extension. It’s essentially a text file though, so feel free to open it in Notepad or any other plain text editor.
This file is part of the configuration for cloud-init and you’ll find it on the boot partition. This is the first partition on the card and, if you’re sticking the card in a Windows or Mac OS X machine, the only partition that’ll appear (because it’s formatted FAT).
Without further ado, here’s the TL;DR version:
1 ssh_pwauth: false 2 3 ssh_import_id: 4 - lp:waveform 5 6 chpasswd: 7 expire: false 8 list: 9 - ubuntu:ubuntu
What does all this YAML mean?
Line 1 configures SSH to disable password authentication, so that’s our security hole fixed (this happens before the default user is created). The username and password may be well known but they’re now useless for remote access. Well that was easy, all done and … oh, wait a minute. How should I access the Pi remotely now?
Lines 3-4 take care of this. These tell cloud-init to use the incredibly useful ssh-import-id utility to grab my public SSH keys from Launchpad (that’s the “lp:” prefix, you can also use “gh:” for GitHub) and install them as authorized keys for all users that cloud-init creates.
Lines 6-9 tell cloud-init that the “ubuntu” user (which it will create by default) should have the password “ubuntu” and that I don’t want it expired (so it won’t prompt me to change it on first login). Half the time I’m dealing with an ephemeral image I won’t be keeping around anyway, so I don’t much care about changing the password, and on the occassions that I do want to keep the image, I don’t want to be bugged about the password on that first login.
And that’s it! I boot my Pi for the first time (making sure it has an Ethernet connection), wait a bit while cloud-init does its thing but, after about a minute, my Pi has an “ubuntu” user I can access over SSH with public-key authentication and no gaping security holes.
Note
cloud-init performs initial set up. That is to say, it only performs set up on the very first boot. If you want to configure your Pi with cloud-init you need to make changes to the configuration (detailed below) before booting your Pi for the first time.
If you wish to re-run cloud-init at a later time, you can do so with the following command but be aware that some settings may not work as expected (because users will already have been created, files already written, etc. etc.):
$ sudo cloud-init clean --logs --reboot
In general, consider this guide relevant for first boot situations only.
YAML Refresher
In case you’re wondering what the format of the file above is, it’s YAML. It may be useful to have a quick refresher course in YAML as that’s what user-data (and all the other cloud-init configuration files) are written in. Here’s the file we saw above again, with some slight alterations for the purposes of demonstration:
1 ssh_pwauth: false 2 3 ssh_import_id: 4 - lp:waveform 5 - gh:foobar 6 7 chpasswd: 8 expire: false 9 list: [ubuntu:ubuntu, pi:raspberry]
There are three top-level values: “ssh_pwauth”, “ssh_import_id”, and “chpasswd”
“ssh_pwauth” is a simple value set to “false”. In all cases, the name of a value is written prior to a colon (“:”)
Next “ssh_import_id” is a list value. There’s two ways of writing lists in YAML and this is the “long-hand” variant: “-” prefixed items, one per line below the name of the value:
mylist: - Item 1 - Item 2 - Item 3
The other way of writing lists is the “short-hand” variant: comma-separated values enclosed in square brackets. The list above could equally well be written like this:
mylist: [Item 1, Item 2, Item 3]
Finally “chpasswd” is a compound value, containing two other values. This is indicating by the indentation before the names of the two sub-values, “expire” and “list”
Warning
You must not mix indentation styles from one line to the next (as in Python). When editing this file you are strongly recommended to stick to spaces for indentation; do not use the Tab key (unless you’re certain your editor is configured to expand tabs to spaces)!
As with lists, there’s a short-hand variant for compound values too: command-separated elements enclosed in curly braces. The following two declarations are equivalent in YAML:
character: {name: Zaphod Beeblebrox, age: 42} character: name: Zaphod Beeblebrox age: 42
List items can be simple values, as we’ve seen above or they can be compound values themselves. For example:
characters: - name: Oberon description: King of the fairies - name: Titania description: Queen of the fairies - name: Bottom description: a weaver - name: Robin Goodfellow description: a mischievous sprite
Coming Up
In the next post we’ll take a look in more detail at the user-data file and see how to create multiple different users, assigning them different SSH keys.