0
votes

I'm trying to use a backend nodeJS server to access (and edit) the device configuration on IoT-Core referring to this API docs

However, I keep getting error:

code 401 with error message "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.", "status": "UNAUTHENTICATED".

I created a service account and a key from Google IAM, and gave it Cloud IoT Device Controller permissions, which could update device configurations but not create or delete. Subsequently, I changed it to Cloud IoT Admin and even Project Editor permissions, but still saw the same error message. Am I getting the keys all wrong, or not doing something else I should be doing?

Code below was how I invoked the request

function createJwt (projectId, privateKeyFile, algorithm) {
    // Create a JWT to authenticate this device. The device will be disconnected
    // after the token expires, and will have to reconnect with a new token. The
    // audience field should always be set to the GCP project ID.
    const token = {
      'iat': parseInt(Date.now() / 1000),
      'exp': parseInt(Date.now() / 1000) + 20 * 60,  // 20 minutes
      'aud': projectId
    };
    const privateKey = fs.readFileSync(privateKeyFile);
    return jwt.sign(token, privateKey, { algorithm: algorithm });
}

app.get('/', function(req, res){

    let authToken = createJwt('test-project', './keys/device-config.pem', 'RS256');

    const options = {
        url: 'https://cloudiot.googleapis.com/v1/projects/test-project/locations/us-central1/registries/dev-registry/devices/test-device',
        headers: {
            'authorization': 'Bearer ' + authToken,
            'content-type': 'application/json',
            'cache-control': 'no-cache'
        },
        json: true
    }

    request.get(options, function(error, response){
        if(error) res.json(error);
        else res.json(response);
    })

});
3

3 Answers

1
votes

For backend servers to interact with IoT-Core, the authentication method is not the same as for device MQTT or HTTP connections. Reference: https://cloud.google.com/iot/docs/samples/device-manager-samples#get_a_device

I was able to retrieve and update device configurations using the code below

function getClient (serviceAccountJson, cb) {
    const serviceAccount = JSON.parse(fs.readFileSync(serviceAccountJson));
    const jwtAccess = new google.auth.JWT();
    jwtAccess.fromJSON(serviceAccount);
    // Note that if you require additional scopes, they should be specified as a
    // string, separated by spaces.
    jwtAccess.scopes = 'https://www.googleapis.com/auth/cloud-platform';
    // Set the default authentication to the above JWT access.
    google.options({ auth: jwtAccess });

    const DISCOVERY_API = 'https://cloudiot.googleapis.com/$discovery/rest';
    const API_VERSION = 'v1';
    const discoveryUrl = `${DISCOVERY_API}?version=${API_VERSION}`;

    google.discoverAPI(discoveryUrl, {}, (err, client) => {
        if (err) {
        console.log('Error during API discovery', err);
        return undefined;
        }
        cb(client);
    });
}

function getDevice (client, deviceId, registryId, projectId, cloudRegion) {
    const parentName = `projects/${process.env.GCP_PROJECT_ID}/locations/${cloudRegion}`;
    const registryName = `${parentName}/registries/${registryId}`;
    const request = {
      name: `${registryName}/devices/${deviceId}`
    };

    const promise = new Promise(function(resolve, reject){
        client.projects.locations.registries.devices.get(request, (err, data) => {
            if (err) {
                console.log('Could not find device:', deviceId);
                console.log(err);
                reject(err);
            } else {
                console.log(data.config.binaryData);
                resolve(data);
            }
        });

    });
    return promise;
}

app.get('/', function(req, res){
    const cb = function(client){
        getDevice(client, 'test-device', 'dev-registry', process.env.GCP_PROJECT_ID, 'us-central1')
            .then(function(response){
                let decoded = new Buffer(response.config.binaryData, 'base64').toString();
                res.json(decoded);
            })
            .catch(function(error){
                res.json(error);
            })
    }
    getClient(serviceAccountJson, cb);

});
1
votes

I think what you're looking to do is best accomplished using the client library for NodeJS.

First, retrieve an API client object as done in the sample. This will take in the service account credentials you used and will authenticate against Google API Core servers.

At the point in the referenced code where cb(client); is invoked, you'll have your client object and are ready to update your device. Add the imports and API constants from the sample and replace the code where you have a client object with the following code and you should be set.

Use some strings for your device identifiers:

const projectId = 'my-project';
const cloudRegion = 'us-central1';
const registryId = 'my-registry';
const deviceId = 'my-device;
const config = '{fan: 800}';

Next, form your device String:

const deviceId = `projects/${projectId}/locations/${cloudRegion}/registries/${registryId}/devices/${deviceId}`;
const binaryData = Buffer.from(config).toString('base64');

Now you form your request object and execute:

const request = {
  name: `${registryName}`,
  versionToUpdate: 0,
  binaryData: binaryData
};
console.log(request);

client.projects.locations.registries.devices
  .modifyCloudToDeviceConfig(
    request,
    (err, data) => {
      if (err) {
        console.log('Could not update config:', deviceId);
        console.log('Message: ', err);
      } else {
        console.log('Success :', data);
      }
    });

Your configuration is updated. If your device is subscribed to the config topic on MQTT it will receive the latest configuration, otherwise, you can poll for the configuration with HTTP from your device.

0
votes

Just to confirm, when you created the SSL key pair, and when you registered the device with the Cloud IoT Core registry, did you match the type of key created with the radio button you registered it with?

Also to confirm, you put the Google root certificate on the device in the same directory as the private key: ./keys/device-config.pem ? If not you can fetch it with: wget https://pki.google.com/roots.pem.