Create a pool of pre-bootstrapped keys

How to create pre-seeded keys beforehand and transfer these to devices in production

It is possible to pre-create qbee-keys for production. In this tutorial we show you how to bootstrap devices beforehand and transfer the necessary files and keys to devices afterwards. This allows to pre-breed qbee keys that can be used in a production process that does not want to run the bootstrap procedure due to timing or no internet.

Steps and prerequisites for creating qbee-keys:

  • a defined bootstrap key that assigns devices to a group and allows auto-accept
  • a device to create these keys on
  • download the script to your device and run it

You can find our qbee-preseed.sh script here:

qbee-preseed.sh
#!/usr/bin/env bash
BASEDIR=$(cd $(dirname "$0") && pwd)
HOSTNAME_PREFIX="$(basename $0 .sh)-"

JQ=$(command -v jq)
CURL=$(command -v curl)
OPENSSL=$(command -v openssl)
DEVICE_API_HOST=${DEVICE_API_HOST:-device.app.qbee.io}
DEVICE_VPN_SERVER=${DEVICE_VPN_SERVER:-vpn.app.qbee.io}
CLIENT_NAME="${HOSTNAME_PREFIX}$(tr -dc A-Za-z0-9 </dev/urandom | head -c 13)"
CLIENT_DIR="$BASEDIR/clients/$CLIENT_NAME"
CONF_DIR="$CLIENT_DIR/etc/qbee"

if [[ -d "$CLIENT_DIR" ]]; then
  echo "Client $CLIENT_NAME already provisioned, exiting..."
  exit 1
fi

if [[ -z "$JQ" ]]; then
  echo "Command line utility 'jq' not found, exiting..."
  exit 1
fi

if [[ -z "$CURL" ]]; then
  echo "Command line utility 'curl' not found, exiting..."
  exit 1
fi

if [[ -z "$OPENSSL" ]]; then
  echo "Command line utility 'openssl' not found, exiting..."
  exit 1
fi

mkdir -p "$CONF_DIR/ppkeys"
mkdir -p "$BASEDIR/common"

PRIVKEY="$CONF_DIR/ppkeys/qbee.key"
PUBKEY="$CONF_DIR/ppkeys/qbee.pub"
CLIENT_CERT="$CONF_DIR/ppkeys/qbee.cert"
QBEE_CA="$BASEDIR/common/qbee-ca-cert.pem"

if [[ ! -f "$QBEE_CA" ]]; then
  # Retrieve the qbee ca cert
  curl -o "$QBEE_CA" -sfL https://cdn.qbee.io/app/device/ca-cert.pem
fi

# Generate the device priv key
$OPENSSL ecparam -name secp521r1 -genkey -noout -out $PRIVKEY > /dev/null 2>&1

# Generate the device pubkey
$OPENSSL ec -in $PRIVKEY -pubout -out $PUBKEY > /dev/null 2>&1

#key Generate bootstrap info
PUB_KEY_JSON=$(head -c -1 $PUBKEY | jq -R -s -c 'split("\n")')

BOOTSTRAP_FILE="$CLIENT_DIR/bootstrap.tmp.json"
INVENTORY_FILE="$CLIENT_DIR/inventory.tmp.json"

cat > ${BOOTSTRAP_FILE} << EOF
{"fqhost": "$CLIENT_NAME","pub_key":$PUB_KEY_JSON,"host":"$CLIENT_NAME","uqhost":"$CLIENT_NAME"}
EOF

cat > ${INVENTORY_FILE} << EOF
{"system":{"fqhost": "$CLIENT_NAME","host":"$CLIENT_NAME","uqhost":"$CLIENT_NAME"}}
EOF

RESPONSE="{}"
ITER_MAX=5
ITER=0
while [[ "$(echo $RESPONSE | jq -e 'has("cert")')" == "false" ]]; do
  RESPONSE=$($CURL -k -sf --cacert $QBEE_CA -XPOST -H "Authorization: token $1" https://${DEVICE_API_HOST}/v1/org/device/xauth/bootstrap -d@${BOOTSTRAP_FILE})
  if [[ -z $RESPONSE ]]; then
    echo "ERROR: Bootstrap of preseed device $CLIENT_NAME failed, please check that you are using the correct bootstrap key"
    rm $CLIENT_DIR -rf
    exit 1
  fi

  ((ITER++))
  if [[ $ITER -ge $ITER_MAX ]]; then
    echo "ERROR: Bootstrap of preseed device $CLIENT_NAME failed after $ITER_MAX attempts, please check that you are using an auto accept bootstrap key"
    rm $CLIENT_DIR -rf
    exit 1
  fi
done

echo $RESPONSE | jq -r '.cert | join("\n")' > "$CLIENT_CERT"

# Post inventory to complete bootstrap
curl -X PUT --cacert $QBEE_CA --key $PRIVKEY --cert $CLIENT_CERT https://${DEVICE_API_HOST}/v1/org/device/auth/inventory/system -d@$INVENTORY_FILE

cat > $CONF_DIR/qbee-agent.json << EOF
{"server":"${DEVICE_API_HOST}","port":"443","vpn_server":"${DEVICE_VPN_SERVER}"}
EOF

echo
echo "INFO: Provisioned client '$CLIENT_NAME' with config, certs and keys in $CONF_DIR"
echo "INFO: Transfer the following files to the preseed host:"
echo "INFO:   $CLIENT_CERT -> /etc/qbee/ppkeys/qbee.cert"
echo "INFO:   $PRIVKEY -> /etc/qbee/ppkeys/qbee.key"
echo "INFO:   $CONF_DIR/qbee-agent.json -> /etc/qbee/qbee-agent.json"

Then you need to create a bootstrap key that has auto-accept.

Then run ./qbee-preseed.sh <bootstrap-key>.

This will create a client config structure that needs to be copied to the individual device. Each time you run it a new folder will be created that has a new unique ID. Thus you can initiate multiple of these runs below without overwriting the seeded keys and files.