Automating preauthorization using the API

this returns;

sed: 1: "‘N
": invalid command code ?
zsh: no such file or directory: s/n/n/
zsh: command not found: ta’

I’m running Catalina 10.15.7 on my Mac, and Rasbian Buster v10 on the RPi I’m trying to Preauth.

This was after having re-ran the ./keygen-client and

DEVICE_PUBLIC_KEY="$(cat keys-client-generated/public.key | sed -e :a  -e 'N;s/\n/\\n/;ta')"

to format it in my environment variable.

Interesting. It looks like the sed syntax in this command is specific to GNU sed and does not work with the MacOS version of sed. I was able to get a similar error on my Big Sur machine.

If you have homebrew installed and the sed from there installed as “gsed” you can modify the command to use that instead.

Alternately it looks like using:

DEVICE_PUBLIC_KEY="$(cat keys-client-generated/public.key | awk 1 ORS='\\n')"

will work. Can you give that a shot?

Drew

That got the public key set to my DEVICE_PUBLIC_KEY variable and running;

curl -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" -X POST -d "{ \"identity_data\" : $DEVICE_IDENTITY_JSON_OBJECT_STRING, \"pubkey\" : \"$DEVICE_PUBLIC_KEY\" }" $MENDER_SERVER_URI/api/management/v2/devauth/devices

now adds the device to my Preauthorized devices in hosted Mender. Thanks for the support! Now I just have to add the formatted public key to my Google Cloud function and make sure the call from there works as well.

Awesome. I’ll submit a docs change with that fix.
Drew

So the code I posted earlier that makes the API call from my Firebase Function still fails to put the device in the Preauthoried column of my dashboard after having copy-pasted the formatted public key used in the curl request that worked.

I’ve tried adding the public key to the call from my environment several different ways;

"identity_data": {
    "sku": `"${deviceId}"`,
    "sn": `"${sn}"`
  },
  "pubkey": `"${process.env.MENDER_PUBKEY}"`,
};

 "identity_data": {
    "sku": `'${deviceId}'`,
    "sn": `'${sn}'`
  },
  "pubkey": `'${process.env.MENDER_PUBKEY}'`,
};

 "identity_data": {
    "sku": `${deviceId}`,
    "sn": `${sn}`
  },
  "pubkey": `${process.env.MENDER_PUBKEY}`,
};

But they all return the response body: {"size":0, "timeout":0} as seen in my earlier post.

Hi @bradw the only thing I see different from our JavaScript example code is that pubkey needs to be quoted as a string.
Drew

That’s what I was showing in my last post. I tried it the three ways shown there. Using string interpolation I pass it from my process.env using single, double and no quotes. All three return the same result.

Sorry but I don’t know much about Javascript. Perhaps @mzedel can comment on that.

Does Firebase have any kind of logging so we can see exactly what the API call looks like on the wire?

Drew

Just what I’ve taken screenshots of earlier.

Can you generate another screenshot with the full details of the preauth call? It looks like there is a twistout to show more details.

The point where it says “function execution took ms, finished with status ‘ok’” and “Function execution started” doesn’t expand with nay additional info, even though there is an expand arrow there. The same with where it says body: {"size": 0, "timeout:0)

Here is the other where it says menderPreauth with the sensitive info blacked out;

I think that it may be related to the actual deployment of the function to FIrebase Cloud Functions.

Heyho @bradw,
just now I used your code to get devices accepted and it was pretty close to working, the issues I found were the auth header needs to have the Bearer part in:

const headers = {
      'Content-Type':'application/json',
      'Accept':'application/json',
      'Authorization':`Bearer ${process.env.MENDER_API_KEY}`
}

Apart from that node-fetch didn’t stringify the body automatically so I had to change the request options to:

{
        method: 'POST',
        body: JSON.stringify(inputBody),
        headers: headers
}

and finally, the response body will be empty if successful, with the headers containing the location of the newly created device:

.then(res => {
    const deviceId = res.headers.get('Location').substring(res.headers.get('Location').indexOf('/') + 1);
    console.log(`deviceId: ${deviceId}`);
    return Promise.resolve(deviceId);
})

Looking at the docs I see that the bearer part in the header is missing there and the res.json() is misleading there… While I fear this is due to the underlying docs generator we use, I’ll create a ticket to get this fixed!

1 Like

I knew it had to be something simple like that. Thanks so much for looking into this for me. I’ll plug this all in to my code, give it a go and let you know how it works out.

When I update my code with what you’ve shown it now hits my catch() block and returns Error: {}.

Here is the code that produces Error:{} from the catch() block

const fetch = require('node-fetch');
const functions = require('firebase-functions');

module.exports = functions.pubsub.topic('menderPreauthorize').onPublish((message) => {
  const deviceId = message.data ? Buffer.from(message.data, 'base64').toString() : null;
  let response
  if (deviceId) {
    const sn = deviceId.split('y')[1]
    const inputBody = {
    "identity_data": {
        "sku": deviceId,
        "sn": sn
      },
      "pubkey": process.env.MENDER_PUBKEY,
    };
    const headers = {
      'Content-Type':'application/json',
      'Accept':'application/json',
      'Authorization':`Bearer ${process.env.MENDER_API_KEY}`
    };
    response = fetch ('https://hosted.mender.io/api/management/v2/devauth/devices',
    {
      method: 'POST',
      body: JSON.stringify(inputBody),
      headers: headers
    })
    .then(res => {
        const deviceId = res.headers.get('Location').substring(res.headers.get('Location').indexOf('/') + 1);
        console.log(`deviceId: ${deviceId}`);
        return Promise.resolve(deviceId);
    })
    .catch((err) => {
      console.log(`Error: ${JSON.stringify(err)}`)
      return err
    })
  } else {
    response = 'deviceId was null.'
  }
  return response
})

After some investigation I found this Stack Overflow post that notes node-fetch not playing nicely with Firebase Functions and is now suggesting axios as a better alternative. I was also getting the {size:0, timeout:0} and body related errors some mention in the comments under the solution using node-fetch.

Using axios and the same MENDER_PUBKEY and MENDER_API_KEY as when I had successfully made the call using curl, I updated my code to the following;

const axios = require('axios');
const functions = require('firebase-functions');

module.exports = functions.pubsub.topic('menderPreauthorize').onPublish((message) => {
  const deviceId = message.data ? Buffer.from(message.data, 'base64').toString() : null;
  let response
  if (deviceId) {
    const sn = deviceId.split('y')[1]
    const inputBody = {
    "identity_data": {
        "sku": deviceId,
        "sn": sn
      },
      "pubkey": process.env.MENDER_PUBKEY,
    };
    const headers = {
      'content-type':'application/json',
      'Accept':'application/json',
      'Authorization':`Bearer ${process.env.MENDER_API_KEY}`
    };
    response = axios({
      method: 'post',
      url: 'https://hosted.mender.io/api/management/v2/devauth/devices',
      data: JSON.stringify(inputBody),
      headers: headers
    })
    .then((res) => {
        return res.data
    })
    .catch((err) => {
      if (err.response) {
        console.log(`error data: ${JSON.stringify(err.response.data)}`)
        console.log(`error status: ${err.response.status}`)
        console.log(`error headers: ${JSON.stringify(err.response.headers)}`)
      } else if (err.request) {
        console.log(`error response: Request was made but no response received, ${err.request}`)
      } else {
        console.log(`Error: ${err.message}`)
      }
    })
  } else {
    response = 'deviceId was null.'
    console.log('deviceId was null')
  }
  return response
})

Now I receive a 401 error status code, Unauthorized as shown here;

@drewmoseley I updated JWT again as you show here, verified I could call the API via curl, regenerated my MENDER_PUBKEY and updated it and MENDER_API_KEY accordingly in my Firebase Function’s environment variables and still get this 401 Unauthorized error (which is at least a more descriptive error than I had been getting).

I’ve tried it with data in lieu of body in the axios request per their docs and tried both with and without JSON.stringify() around the inputBody. Any other ideas?

I don’t have any ideas. Perhaps @mzedel, @Alan or @peter know more.

@bradw can you please dump the full request and paste it here, removing the JWT token?