Skip to content

Instantly share code, notes, and snippets.

@m-allanson
Forked from mvisintin/configurationUpdate.js
Last active April 13, 2022 12:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save m-allanson/e290c47523aad988b550df9d5f88be6c to your computer and use it in GitHub Desktop.
Save m-allanson/e290c47523aad988b550df9d5f88be6c to your computer and use it in GitHub Desktop.
Minimal configuration update example
// Note you must be authorised to use the provided GCloud project.
// Use `gcloud auth application-default login` to auth for the current machine
const iot = require('@google-cloud/iot');
const { KeyManagementServiceClient } = require('@google-cloud/kms');
const uuid = require('uuid');
const { readFileSync } = require('fs');
const crypto = require('crypto');
const { config } = require('process');
const iotClient = new iot.v1.DeviceManagerClient();
const kmsClient = new KeyManagementServiceClient();
const deviceId = 'clusterId-5b10fbfc-8e99-42a2-b3e6-cdc6f8e7bd71';
const projectId = 'portal-dev-340714';
const cloudRegion = 'europe-west1';
const containerName = 'clusters';
const licence = readFileSync('./licence.txt').toString();
// RPC Path to IOT device
const devicePath = iotClient.devicePath(
projectId,
cloudRegion,
containerName,
deviceId
);
// RPC Path to key ring
const keyRingPath = kmsClient.keyRingPath(
projectId,
cloudRegion,
containerName
);
// RPC Path to key
const keyPath = kmsClient.cryptoKeyVersionPath(
projectId,
cloudRegion,
containerName,
deviceId,
1
);
/**
* Creates an asymmetric key pair for signing.
* The corresponding public key will be delivered to the cluster with the
* configuration call
*/
async function createKey() {
const [key] = await kmsClient.createCryptoKey({
parent: keyRingPath,
cryptoKeyId: deviceId,
cryptoKey: {
purpose: 'ASYMMETRIC_SIGN',
versionTemplate: {
algorithm: 'RSA_SIGN_PKCS1_4096_SHA512',
},
},
});
return key;
}
/**
* Creates a signature based on the configurationBody passed to the function
*/
const signConfiguration = async (configurationBody) => {
const hash = crypto.createHash('sha512');
hash.update(configurationBody);
const [signResponse] = await kmsClient.asymmetricSign({
name: keyPath,
digest: {
sha512: hash.digest(),
},
});
return signResponse.signature.toString('base64');
};
/**
* Retrieves the last configuration applied to the device (up to 10 can be
* stored)
*/
const getConfiguration = async () => {
const [response] = await iotClient.listDeviceConfigVersions({
name: devicePath,
});
const config = Buffer.from(
response.deviceConfigs[0].binaryData,
'base64'
).toString();
return config || '';
};
/**
* Applies a new configuration against the device
*/
async function applyNewConfig(configuration) {
const request = {
name: devicePath,
binaryData: Buffer.from(JSON.stringify(configuration)).toString('base64'),
};
const [response] = await iotClient.modifyCloudToDeviceConfig(request);
console.log('applyNewConfig success:', response);
}
/**
* This is what the portal manager will do to update the configuration
* - Retrieves the last configuration applied to the device
* - Creates a new configurationId
* - Creates a signature based on the configuration.body
* - Merges previous configuration.body and new one
* - Applies the resulted configuration
*/
const updateConfig = async () => {
const configurationId = uuid.v4();
const body = {
licence: Buffer.from(licence).toString('base64'),
configurationId,
};
let bodyJsonStr = JSON.stringify(body);
let bodyB64 = Buffer.from(bodyJsonStr).toString('base64');
const configuration = {
body: bodyB64,
signature: await signConfiguration(`${bodyB64}`),
};
return applyNewConfig(configuration);
};
/**
* This part simulates what the portal-manager would do, main difference
* is that here I am retrieving the publicKey from Google KMS while the
* portal-manager would have the public key stored in a config map or something
* similar.
*
* Other difference is that the configuration will be received from the specific
* topic.
*
* Rest is similar:
* - Receives new configuration
* - Configuration is parsed as JSON
* - Verifies signature using the publicKey against configuration body
* - ...
*/
const verifyConfiguration = async () => {
const configuration = JSON.parse(await getConfiguration());
const verifier = crypto.createVerify('SHA512');
verifier.update(configuration.body);
verifier.end();
const publicKey = await kmsClient
.getPublicKey({ name: keyPath })
.then((res) => res[0].pem);
console.log('verifyConfig - retrieved config: ', configuration);
console.log(
'verifyConfig - publicKey: ',
Buffer.from(publicKey).toString('base64')
);
console.log(
'verifyConfig - config is valid?',
verifier.verify(publicKey, configuration.signature, 'base64')
);
};
async function run() {
// await createKey();
await updateConfig();
await verifyConfiguration();
}
run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment