This repository contains the tools and instructions for releasing a Thistle OTA update bundle signed with a key managed in a Cloud KMS (key management system). Currently, GCP KMS is supported.
You need
-
MacOS (
x86_64
oraarch64
) or Linux (x86_64
, tested on Ubuntu) -
A Google Cloud project with Cloud Key Management Service (KMS) API enabled, and a principal (user account) with the following IAM roles assigned
- roles/cloudkms.signerVerifier
- This role is a role needed for
sign
, and for querying an existing public key in KMS withkeygen
- It can be assigned to a principal responsible for signing an OTA bundle
- This role is a role needed for
- roles/cloudkms.admin
- This is a role only needed for
keygen
for the first-time key generation. It alone does not allow for signing of an OTA bundle - It can be assigned to a principal responsible for administration of Cloud KMS operations in your Cloud account
- This is a role only needed for
- roles/cloudkms.signerVerifier
This step only needs to be done once per release signing key.
-
First, clone this repository, and activate Hermit
$ git clone https://github.com/thistletech/trh-k.git $ . trh-k/bin/activate-hermit trh-kπ $
-
Create a JSON configuration file, say,
gcp_config.json
(cf. sample_gcp_config.json), containing the following fields that describe the to-be-generated signing key.{ "project": "GCP project name, e.g., my-awsome-gcp-project", "location": "GCP region, e.g., us-west1", "keyring": "Name of keyring, e.g., dummy-keyring-for-trh-k", "keyname": "Name of signing key, e.g., dummy-key-for-trh-k" }
-
Generate a new ECDSA key pair in KMS for OTA bundle signing, as described in
gcp_config.json
. All commands below are executed under an active Hermit environment.# Login to GCP project $ gcloud auth login $ gcloud config set project YOUR_PROJECT_ID # Keygen $ trh-k/thistle-bin/keygen-gcp-k -c gcp_config.json # Example output Please add the following public key to the public_keys array in TUC's configuration: ecdsa:BK9k+finvyLNVoqj5p07EvMh9OiOQ2DIK7w4uii1UVNc7yWB4lCI/Wk3I/PRHthBbPKSog3dis5ALzMdsHqSIC4=
Save the public key value for the test release update step later, or one can run
keygen-gcp-k
again to print the public key value to the console.
-
Login to the Thistle Web app, and create a new project where your fleet of devices associated with the OTA bundle are managed. In the example below, we name the project "kms signing".
-
In the newly created project "kms signing", visit the "Settings" section of a project to obtain the API token to be used as
THISTLE_TOKEN
in the initialization step below.
In the project initialization step, we generate a release manifest file
(manifest.json
) using the Thistle Release Helper
(trh
).
- The manifest file will be used by a release operator, with the help of
trh
, to describe an OTA bundle for devices in the project.
On a Linux or macOS machine, initialize the project as follows.
# Create an empty "release" directory for the tutorial, and change to it
$ mkdir release
$ cd release
# Clone this repo
$ git clone https://github.com/thistletech/trh-k.git
# Activate Hermit, and run subsequent commands in Hermit environment
$ . trh-k/bin/activate-hermit
# Paste the "Access Token" of the project obtained from Thistle portal, and type
# `Enter + Ctrl-d`. Use this command to prevent the sensitive $THISTLE_TOKEN
# from being logged in shell history
trh-kπ $ export THISTLE_TOKEN=$(cat)
trh-kπ $ trh init
...
Manifest generated at: "./manifest.json"
-
An example of
manifest.json
created in this step.{ "comment": null, "id": "01GKMZAJ5X229EHVR1WK4T43FX", "ts": "1670372477.117195000", "version": 1, "name": "default", "signature": "", "pre_install": null, "post_install": null, "post_reboot": null, "rootfs": null, "files": [], "status": null, "path": null }
As with File Update
and Full System Update, we
use trh
's subcommands prepare
and release
to prepare, sign and release OTA
bundles, but also supply the --external-sign
argument for KMS signing.
Let's use the following file update case as an example.
In the release
directory on a Linux or macOS machine, run the follow commands.
Make sure manifest.json
and gcp_config.json
are directly under release/
.
# Activate Hermit, and run subsequent commands in Hermit environment
$ . trh-k/bin/activate-hermit
# Paste the "Access Token" of the project obtained from Thistle portal, and type
# enter + ctrl-d
trh-kπ $ export THISTLE_TOKEN=$(cat)
# Prepare a local OTA bundle
trh-kπ $ mkdir -p rel/tmp
trh-kπ $ echo "This is a test" > rel/tmp/test.txt
trh-kπ $ trh --external-sign "trh-k/thistle-bin/sign-gcp-k -c gcp_config.json" prepare -target=./rel --file-base-path=/
Read manifest at "./manifest.json"
Processed file "./rel/tmp/test.txt"
Executing external signing command
=====
=====
Manifest amended successfully
# Release OTA bundle to Thistle backend
trh-kπ $ trh --external-sign "trh-k/thistle-bin/sign-gcp-k -c gcp_config.json" release
Read manifest at "./manifest.json"
Executing external signing command
=====
=====
Prepared backup release
Uploaded asset test.txt
Executing external signing command
=====
=====
Backup manifest uploaded successfully
Manifest uploaded successfully
Local compressed artifacts removed
To update an existing release, re-run trh prepare
and trh release
commands
with the --external-sign
argument exactly as above, and as described
here,
to get the manifest updated and signed, and OTA bundle uploaded to Thistle
backend.
On the machine where an OTA update release was published, generate a device
configuraiton file config.json
.
trh-kπ $ trh --signing-method="external" --public-key=<PUBLIC_KEY> \
gen-device-config \
--device-name="my-demo-device" \
--enrollment-type="group-enroll" \
--persist="${HOME}/thistle-ota"
Here, <PUBLIC_KEY>
is the public key value shown in the KMS key pair creation
step.
-
An example of
config.json
created by this command.{ "name": "my-demo-device", "persistent_directory": "/home/thistle/thistle-ota", "device_enrollment_token": "redacted_device_token", "public_keys": ["ecdsa:BK9k+finvyLNVoqj5p07EvMh9OiOQ2DIK7w4uii1UVNc7yWB4lCI/Wk3I/PRHthBbPKSog3dis5ALzMdsHqSIC4="] }
This configuration file can be used as a template for other devices in the same project. While
persistent_directory
,device_enrollment_token
andpublic_keys
values are shared by all devices in the project, the fieldsname
anddevice_id
can be device unique, and customizable according to a customer's device management setting.Note that the above device configuration file is for a "group enrollment" model: Suppose
device_id
is missing fromconfig.json
, anddevice_enrollment_token
is valid. During the first HTTP request from TUC to the backenddevice_id
is not present. In this case, this request will be viewed as a "device provisioning" request (authorized bydevice_enrollment_token
): a new, uniquedevice_id
anddevice_token
will be automatically created on the backend, and returned to the client to persist. Subsequent client-initiated requests will havedevice_id
anddevice_token
values included for device authentication and authorization.
A "pre-enrollment" model is also supported by TRH by setting
--enrollment-model="pre-enroll
in the command above. This will generate a
config.json
file with unique device_id
and device_token
in it, without
obtaining a device_enrollment_token
. For the two device provisioning models,
refer to Device
Provisioning in Thistle's
documentation.
On a device running tuc
, run the following command to test the released OTA
bundle, using config.json
. This is similar to what is described
here.
trh-kπ $ tuc --log-level=info -c config.json
...
!! setting update status to Pass
.. signature verified with public key #0 type ecdsa
.. reported data to server
~~ next check in 3600s
-
thistle-bin/keygen-gcp-k
Usage: keygen-gcp-k -h Display this help message keygen-gcp-k -c CONFIG_FILE If GCP KMS resources in CONFIG_FILE are already provisioned, prints out public key string. Otherwise, generates a new key pair in GCP KMS as specified in CONFIG_FILE. User needs to login to appropriate GCP account and project before invoking command.
-
thistle-bin/sign-gcp-k
Usage: sign-gcp-k -h Display this help message sign-gcp-k -c CONFIG_FILE SIGNABLE_STRING Signs SIGNABLE_STRING using GCP resources specified in CONFIG_FILE