Skip to content

Instantly share code, notes, and snippets.

@mvisintin
Created March 17, 2022 18:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mvisintin/b811f37b3eb24e932fc16efcbf230f0a to your computer and use it in GitHub Desktop.
Save mvisintin/b811f37b3eb24e932fc16efcbf230f0a to your computer and use it in GitHub Desktop.
Minimal configuration update example
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 iotClient = new iot.v1.DeviceManagerClient();
const kmsClient = new KeyManagementServiceClient();
const deviceId = 'clusterId-05e7d73f-fbc2-43c4-aa07-8eaf84a6f417';
const projectId = 'portal-dev-340714';
const cloudRegion = 'europe-west1';
const containerName = 'clusters';
const license = readFileSync('./license.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_2048_SHA256',
},
},
});
return key;
}
/**
* Creates a signature based on the configurationId passed to the function
*/
const signConfiguration = async (configurationId) => {
const hash = crypto.createHash('sha256');
hash.update(configurationId);
const [signResponse] = await kmsClient.asymmetricSign({
name: keyPath,
digest: {
sha256: 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 JSON.parse(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('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 configurationId
* - Merges previous configuration and new one
* - Applies the resulted configuration
*/
const updateConfig = async () => {
const latestConfiguration = await getConfiguration();
const configurationId = uuid.v4();
const configuration = {
...latestConfiguration,
license: Buffer.from(license).toString('base64'),
configurationId,
signature: await signConfiguration(configurationId),
};
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 decoded from base64 and parse as JSON
* - Verifies signature using the publicKey against clusterId
* - ...
*/
const verifyConfiguration = async () => {
const configuration = await getConfiguration();
const verifier = crypto.createVerify('SHA256');
verifier.update(configuration.configurationId);
verifier.end();
const publicKey = await kmsClient
.getPublicKey({ name: keyPath })
.then((res) => res[0].pem);
console.log(verifier.verify(publicKey, configuration.signature, 'base64'));
console.log(configuration);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment