Created
March 17, 2022 18:31
-
-
Save mvisintin/b811f37b3eb24e932fc16efcbf230f0a to your computer and use it in GitHub Desktop.
Minimal configuration update example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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