This post will show you how to connect to a cattlepi managed device using SSH. We assume that you’ve already gone through the Hello CattlePi Tutorial and have your account and default boot target config.

A few considerations before we get started:

  • in the image that you’re using the SSH daemon should be running, but it’s configured to not allow password logins (see https://github.com/cattlepi/cattlepi/blob/master/builder/tasks/squashfs.yml#L29). It also does not allow empty passwords or the root user login in (keep in mind that the pi user does have sudo though)
  • by default we don’t have any ssh keys baked in into the image for the pi used
  • the host ssh keys are regenerated on boot (if you reboot a cattlepi device the ssh client will complain that the server key has changed - this is normal - actually this is expected)

Now, how do we connect? It turns out that there is a hook in the device configuration that allows you to inject keys that are authorized to connect as the pi user (technically inject public keys and need to separately have the private key).

The code responsible for this is here.
You can observe that if an array is present at config.ssh.pi.authorized_keys in the configuration that is served to the device, each element in that array will get injected into the authorized keys. In the demo server we see one way you can generate the config. For the sake of simplicity and brevity we will do this using api.cattlepi.com and we will update the config via curl.

PAYLOAD='{"config":{"ssh":{"pi":{"authorized_keys":["'$(head -1 $HOME/.ssh/id_rsa.pub)'"]}}},"initfs":{"md5sum":"8e0e2870637e77462b40b8fe67c6d91b", "url":"https://api.cattlepi.com/images/global/raspbian-lite/2018-06-29/v4/initramfs.tgz"},"rootfs":{"md5sum":"0826222b56df3d074fa4b21fd4b9d891","url":"https://api.cattlepi.com/images/global/raspbian-lite/2018-06-29/v4/rootfs.sqsh"}}'
curl -vvv -H "Accept: application/json" \
    -H "Content-Type: application/json" \
    -H "X-Api-Key: 8db071a4-63ef-47f7-9cfc-ca479b5422da" \
    -X POST -d "$PAYLOAD" \
    https://api.cattlepi.com/boot/default/config

The payload is the same with the exception that the authorized keys are now injected. Notice how the id_rsa.pub is picked up and injected in the config. You can inject any keys you want - this serves as an example. You can also have multiple keys as authorized_keys is an array.

To retrieve the config and inspect it:

{
  "bootcode": "",
  "config": {
    "ssh": {
      "pi": {
        "authorized_keys": [
          "ssh-rsa actual_base64_encodedkey email@test.com"
        ]
      }
    }
  },
  "initfs": {
    "md5sum": "8e0e2870637e77462b40b8fe67c6d91b",
    "url": "https://api.cattlepi.com/images/global/raspbian-lite/2018-06-29/v4/initramfs.tgz"
  },
  "rootfs": {
    "md5sum": "0826222b56df3d074fa4b21fd4b9d891",
    "url": "https://api.cattlepi.com/images/global/raspbian-lite/2018-06-29/v4/rootfs.sqsh"
  },
  "usercode": ""
}

Now, reboot the Pi and attempt connecting to it:

ssh pi@192.168.1.87
The authenticity of host '192.168.1.87 (192.168.1.87)' can't be established.
ECDSA key fingerprint is SHA256:GxIl+YtmQjC5y6ri/KGhYy4j9AbUzx5slQsIpIk0bt0.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.1.87' (ECDSA) to the list of known hosts.
Linux raspberrypi 4.14.52-v7+ #1123 SMP Wed Jun 27 17:35:49 BST 2018 armv7l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sat Aug 11 04:45:02 2018 from 192.168.1.166
pi@raspberrypi:~$

You can not connect to the device. While we are here let’s take a look in /cattlepi dir:

pi@raspberrypi:~$ ls -lah /cattlepi/
total 16K
drwxr-xr-x 1 root root  120 Aug 11 20:32 .
drwxr-xr-x 1 root root  160 Aug 11 20:32 ..
-rwxr-xr-x 1 root root   37 Aug 11 20:32 apikey
-rw-r--r-- 1 root root   25 Aug 11 20:32 base
-rw-r--r-- 1 root root   30 Aug 11 20:32 base_relative_config
-rwxr-xr-x 1 root root 1.2K Aug 11 20:32 config
pi@raspberrypi:~$ cat /cattlepi/apikey 
8db071a4-63ef-47f7-9cfc-ca479b5422da
pi@raspberrypi:~$ cat /cattlepi/base
https://api.cattlepi.com
pi@raspberrypi:~$ cat /cattlepi/base_relative_config 
boot/b8:27:eb:6c:33:e2/config
pi@raspberrypi:~$ cat /cattlepi/config | jq .
{
  "bootcode": "",
  "config": {
    "ssh": {
      "pi": {
        "authorized_keys": [
          "ssh-rsa actual_base64_encodedkey email@test.com"
        ]
      }
    }
  },
  "initfs": {
    "md5sum": "8e0e2870637e77462b40b8fe67c6d91b",
    "url": "https://api.cattlepi.com/images/global/raspbian-lite/2018-06-29/v4/initramfs.tgz"
  },
  "rootfs": {
    "md5sum": "0826222b56df3d074fa4b21fd4b9d891",
    "url": "https://api.cattlepi.com/images/global/raspbian-lite/2018-06-29/v4/rootfs.sqsh"
  },
  "usercode": ""
}

You can see what your api key is, you can see the api endpoint and the configuration from within the running Pi.