Using the Mender API: The Basics of the Management API


Api icons created by DinosoftLabs - Flaticon

Customer engineer Alan Martinovic explores the basics of using the Mender API - specifically the management API

Mender was designed with easy integration into CI CD pipelines and fleet management workflows in mind and this is also reflected in our open core business offering and in our open source offering. Open REST APIs are the practical manifestation of this. This article will provide you with all you need to quickly get started with the Mender API. Refer to the Mender Docs for full implementation details on the management API and the Device API.

Overview of Mender API

The Mender API is separated into two categories:

  • Device API - the IoT device which has the Mender client installed uses the device API to poll for and receive the updates
  • Management API - these are what the UI uses and what you can leverage to create automations in your OTA software deployment workflows.

This tutorial will focus on the use of the Management API.

Versioning

As you start reading through the Management API documentation you will notice that some sections are seemingly duplicated with a version number in front of it.

Those represent the higher version of the API.
As described in the Mender API docs:

The higher version API contains a mix of old and new API endpoints. For endpoints which haven’t changed in the new version the previous version endpoints are assumed. Practically, this translates always using the highest API version available. If an endpoint is not there, the one from the previous API version is assumed.

Authentication

To start working with the API, you will need to authenticate. Practically this means that getting a hold of a JWT token the server considers valid. Only with that token in your API calls will the server grant you access.

There are two ways to get the JWT token:

Directly generate a Personal Access Token in Hosted Mender
Call the /api/management/v1/useradm/auth/login API endpoint with your Hosted Mender username and password and have the server return the token for you.

The usage of a PAT is recommended when dealing with the API as it gives the flexibility by allowing to set an expiration period.

For this tutorial we will use the username and password approach to generate a PAT. From there the PAT will be used in the rest of the examples.[1]

Hands on examples - bash

Assumptions: You’ve registered for the Mender free trial and successfully completed the Get started process.

The following tools are available in your command line:

  • curl - to send the http requests
  • jq - it will be used to parse the json files

Authentication - getting the PAT

Generating a PAT requires an API call. To make this API call, we need to authenticate with a username and password.

USER= <----- The email you use to sign into Hosted Mender i.e. myuser@email.com
PASS= <----- The password you use to sign into Hosted Mender

# i.e.
# USER=user@email.com 
# PASS=123456

BASE_URL="https://hosted.mender.io/api/management/v1/useradm"
API_ENDPOINT=/auth/login

JWT_TEMP=$(curl -X POST -H "Content-Type: application/json;" -u "$USER:$PASS" $BASE_URL$API_ENDPOINT)

echo $JWT_TEMP

# A large string of random characters will be printed

Now that we can make API calls, let’s generate a PAT.

BASE_URL=https://hosted.mender.io/api/management/v1/useradm
API_ENDPOINT=/settings/tokens

UNIQUE=$(openssl rand -hex 5)

BODY_PARAMETER=$(cat << EOF 
{
  "name": "demo-pat-$UNIQUE",
  "expires_in": 7200
}
EOF
)

JWT=$(curl -X POST $BASE_URL$API_ENDPOINT \
  -H 'Content-Type: application/json' \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $JWT_TEMP" \
  -d "$BODY_PARAMETER")
  
echo $JWT

This PAT is also a JWT token that will be used in the examples.
All future examples rely on the existence of the JWT environment variable.

Example 1 - list all devices by their identity

The Device Identity is:

A unique set of key-value pairs produced by the device and unchangeable for the lifecycle of the device. It is generated by the script on the device located at /usr/share/mender/identity/mender-device-identity.

Not to be confused with Device ID which is a:

A unique, unchangeable identifier the server assigns internally to every device.

When using the API’s you are exposed to both of these identifiers, and for this example we’re interested in getting the Device Identity for all devices.

Let’s first get all the device data and store it in a file:

BASE_URL=https://hosted.mender.io/api/management/v2/devauth
API_ENDPOINT=/devices

curl -X GET $BASE_URL$API_ENDPOINT \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $JWT"  >  devices.json

jq length devices.json
# Outputs 1

In this example, 1 device is already a part of the system as part of the Getting started.

Now as the json response is stored in the file, we need to understand its structure.

The response schema for the /devices endpoint is listed in the docs for the List Devices API endpoint.

Listing based on the value of identity_data will give us the answer we’re looking for.

jq '.[].identity_data' devices.json 

# OUTPUT:
#
#{
#  "mac": "52:54:00:ce:34:5b"
#}

Example 2 - get the inventory data of a device

In the previous example we’ve listed the Device Identities.
Now we want to know the inventory attributes of a specific device. The device inventory is:

A customizable list of key=value pairs providing information about the device. The inventory key=value pairs are generated by the scripts located at /usr/share/mender/inventory/ on the device. The device sends the updated inventory values to the server with the period defined by InventoryPollIntervalSeconds.

The Get Device Inventory endpoint can be used for that. However it soon becomes obvious that we can’t use the Device Identity as input, instead what is needed is the Device ID.

The snippet below is a quick way to get the Device ID[2]:

DEVICE_IDENTITY="dc:a6:32:7d:9f:f2"
DEVICE_ID=$(jq -r '.[] | select(.identity_data.mac=="b8:27:eb:d6:95:af") | .id' devices.json)
echo $DEVICE_ID

With the Device ID available we can get the device inventory:

BASE_URL=https://hosted.mender.io/api/management/v1/inventory
API_ENDPOINT=/devices/$DEVICE_ID

curl -X GET $BASE_URL$API_ENDPOINT \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $JWT"    >  inventory-$DEVICE_IDENTITY.json

The device inventory is large so it won’t be listed here.
You can print and examine it by running jq . inventory-$DEVICE_IDENTITY.json

Example 3 - create a new user and assign it a new role

Let’s create a new user now using the Create User API endpoint:

BASE_URL=https://hosted.mender.io/api/management/v1/useradm
API_ENDPOINT=/users

EMAIL="$(openssl rand -hex 5)@example-email.com"
PASSWORD=$(openssl rand -base64 10)

BODY_PARAMETER=$(cat << EOF 
{
  "email": "$EMAIL",
  "password": "$PASSWORD",
  "login": {}
}
EOF
)

curl -i -X POST $BASE_URL$API_ENDPOINT \
  -H 'Content-Type: application/json' \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $JWT" \
  -d "$BODY_PARAMETER"

The above command created the user. [3]
Let’s locate the new user and get some information about them.
We first need to get a list of all the users, and then filter out the one relevant to us:

BASE_URL=https://hosted.mender.io/api/management/v1/useradm
API_ENDPOINT=/users

curl -X GET $BASE_URL$API_ENDPOINT \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $JWT"    >  all-users.json

jq --arg EMAIL "$EMAIL"  -r '.[] | select(.email==$EMAIL)' all-users.json

# OUTPUT:
# {
#  "id": "22d01bc9-e67d-4a0c-81d1-2fc2061adff5",
#  "email": "369a8650db@mender-api-blog.com",
#  "tfa_status": "",
#  "created_ts": "2022-12-13T13:38:59.228Z",
#  "updated_ts": "2022-12-13T13:38:59.228Z",
#  "roles": [
#    "RBAC_ROLE_PERMIT_ALL"
#  ]
# }

Mender Enterprise plan and free trial have a concept of [Role Based Access Control (RBAC)[(Role-Based Access Control | Mender documentation).

A Role is a set of permissions you can assign to a user. A user can have multiple Roles and a Role can be assigned to multiple different users.

Seems like our new user was created with an admin role [RBAC_ROLE_PERMIT_ALL].
Let’s check the roles for something less permissive.

BASE_URL=https://hosted.mender.io/api/management/v1/useradm
API_ENDPOINT=/roles

curl -X GET $BASE_URL$API_ENDPOINT \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $JWT"  |  jq .[].name

The RBAC_ROLE_OBSERVER is a much more restrictive, read-only role.
Let’s assign it to the new user instead.

USER_ID=$(jq --arg EMAIL "$EMAIL"  -r '.[] | select(.email==$EMAIL) | .id' all-users.json)

BASE_URL=https://hosted.mender.io/api/management/v1/useradm
API_ENDPOINT=/users/$USER_ID

BODY_PARAMETER=$(cat << EOF 
{
  "roles": [
    "RBAC_ROLE_OBSERVER"
  ]
}
EOF
)

curl -X PUT $BASE_URL$API_ENDPOINT \
  -H 'Content-Type: application/json' \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $JWT" \
  -d "$BODY_PARAMETER"

Once the new role is set, let’s quickly call the List users again to confirm the change:

BASE_URL=https://hosted.mender.io/api/management/v1/useradm
API_ENDPOINT=/users

curl -X GET $BASE_URL$API_ENDPOINT \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $JWT"    | jq --arg EMAIL "$EMAIL"  -r '.[] | select(.email==$EMAIL)'

The output confirms the new user has the new role [RBAC_ROLE_OBSERVER].

Try out the Mender API for yourself

The few examples in this tutorial provided a taste of what it’s like to use the Mender API. This only scratches the surface of what you can do with the API, but it provides a working reference you can copy and modify to explore the workings of other API endpoints.

[1] - if you have created your account using SSO (Github/Google/Microsoft) you will need to create the PAT using the UI.

[2] - please note that we’re assuming that the key for the Device identity is mac which is the default. If you modified this to another value, the script will need to be adjusted.

[3] - for return codes other then success (201) please take a look at the list of responses.