Như phản hồi thú vị từ một số người đọc về bài viết:
JS: jsrsasign xác thực JWT
Các bạn có hỏi "SECRET_ME_RSA"
Vâng đó là 1 "passphrase" đi kèm để tăng thêm bảo mật cho "private key", khi sinh key với openssh thêm flag -des3, và định dạng nó thường là:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,....
Đặt ra ở đây là 1 số bạn thích: "node-jsonwebtoken" hơn thư viện mình giới thiệu là: "jsrsasign" bởi nó đơn giản hơn. Các ví dụ về "node-jsonwebtoken" thường ko nói đến passphrase. Vậy với "passphrase" thì sao?? Đây:
const fs = require('fs');
const jwt = require('jsonwebtoken');
// ...
function createJwt2 (privateKey, passPhrase) {
const payLoad= {
'iat': parseInt(Date.now() / 1000),
'exp': parseInt(Date.now() / 1000) + 20* 60, // hiệu lực 20 phút
'aud': "diepgepa"
};
const privateKey = fs.readFileSync('./rsa_private.pem');
jwt.sign(payLoad,{ key: privateKey, passphrase: passPhrase}, { algorithm: "RS256"},function(err, token) {
if(err) console.log(err);
else {
console.log(token);
}
});
}
Có thể không cần "passphrase" đi kèm để đơn giản.
Cloud IoT Core end-to-end example with NodeJs
Gần đây mình có test "Cloud IoT Core" của Google. Mô hình cũng cung cấp xác thực nhưng khác với những gì mình đã trình bày trước đó.
Mình xin mạn phép nói qua các bước thông qua ví dụ của chính google, ví dụ "End-to-End Example _ Google Cloud Internet of Things Core _ Google Cloud Platform"
1. Tạo "rsa_private.pem", nên bỏ "passphrase" cho đơn giản
Các ví dụ cũng cho luôn cái "rsa_private.pem" này, nhưng tốt nhất là bạn nên tạo 1 cái, vì không có passphrase nên lệnh bash bỏ des3: "openssl genrsa -out rsa_private.pem"
RSA_PRIVATE này sẽ giúp ta chủ động tạo ra các token, hay là chủ động tạo ra các mã xác thực mà khi tạo kết nối của thiết bị sẽ cần tới. Nếu bạn muốn sử dụng "Cloud IoT Core" để cung cấp dịch vụ cho ai đó, dĩ nhiên sẽ không thể đưa "RSA_PRIVATE " chọ họ được, bạn nên tạo 1 web app, sinh ra các token với thời hạn xác định và gia hạn tự động nếu cần...
2. Nhìn vào mô hình ta cần phải tạo ra topic để phục vụ "Cloud Pub/Sub". Nếu trên NodeJs
var PubSub = require('@google-cloud/pubsub')({
projectId: 'xxx',
keyFilename: JSON_PATH
});
const pubsub = PubSub();
hoặc, ngầm xác thực theo kiểu:
process.env.GCLOUD_PROJECT = 'xxx';
process.env.GOOGLE_CLOUD_PROJECT = 'xxx';
process.env.GOOGLE_APPLICATION_CREDENTIALS = JSON_PATH';
const PubSub = require('@google-cloud/pubsub');
const pubsub = PubSub();
JSON_PATH là file json chứa permisson, hình thức này là đăng nhập kiểu "service account key", bản chất là Json Web Token đặc thù mà mình mô tả trong các bài viết trước.
Bạ có thể cấp quyền với "Google Cloud IAM". Phần này không thiên về IOT Core mà mình muốn bàn ở đây, nhưng để test được đầy đủ, phải có Pub/Sub. Nếu không muốn Auto hoàn toàn, các bạn tạo luôn trên "console.cloud.google.com"... Còn muốn auto:
function createIotTopic (topicName) {
pubsub.createTopic(topicName)
.then((results) => {
setupIotTopic(topicName);
})
.catch(err=>{
console.log(err.message);
});
}
3. Có được Topic là để ta cập nhật và đẩy dữ liệu lên thông qua "IoT Core"
Và do đó bản thân pub/sub vừa tạo, IoT Core cũng cần cấp quyền, IOT Core thể hiện mình là ai, và nó cần quyền gì??
Đó là "cloud-iot@system.gserviceaccount.com", nó cần quyền: "roles/pubsub.publisher"
// Configures the topic for Cloud IoT Core.
function setupIotTopic (topicName) {
const topic = pubsub.topic(topicName);
const serviceAccount = `serviceAccount:cloud-iot@system.gserviceaccount.com`;
topic.iam.getPolicy()
.then((results) => {
const policy = results[0] || {};
policy.bindings || (policy.bindings = []);// a||(a=1); /**if(!a) a = 1; */
console.log(JSON.stringify(policy, null, 2));
let hasRole = false;
let binding = {
role: 'roles/pubsub.publisher',
members: [serviceAccount]
};
policy.bindings.forEach((_binding) => {
if (_binding.role === binding.role) {
binding = _binding;
hasRole = true;
return false;
}
});
if (hasRole) {
binding.members || (binding.members = []);
if (binding.members.indexOf(serviceAccount) === -1) {
binding.members.push(serviceAccount);
}
} else {
policy.bindings.push(binding);
}
// Updates the IAM policy for the topic
return topic.iam.setPolicy(policy);
})
.then((results) => {
const updatedPolicy = results[0];
console.log(JSON.stringify(updatedPolicy, null, 2));
})
.catch((err) => {
console.error('ERROR:', err);
});
}
4. Về phía thiết lập IoT Core:
Có được Topic là nơi để đẩy dữ liệu, hay thiết bị hỗ trợ MQTT sẽ đẩy dữ liệu cho Cloud Pub/Sub.
Để làm được điều này IoT Core cần 2 bước là: "Create device registry", và "Add device"
"Create device registry" tương ứng với 1 topic đã tạo ở trên, "Add device" tương ứng với "Device Registry" vừa tạo. Chúng cần mã xác thực. Mã này ở đâu? Nó liên quan gì Private key đã tạo ở bước 1?
Đó là mã chứng chỉ( certificate) tương ứng với private key ở bước 1.
("add device" có thể dùng mã public key cũng được, "create device registry" có thể không cần mã chứng chỉ => tôi chọn, 1 mã chứng cho việc "add device", thế thôi, oke)
Tôi search là: "Generate a Self-Signed Certificate from an Existing Private Key"
Tìm được lệnh bash: "openssl req -newkey rsa:2048 -nodes -keyout rsa_private.pem -x509 -days 365 -out rsa_public.pem", sau đó nhập thông tin liên quan đến chứng chỉ. Nếu ai tạo https web app, tạo chứng chỉ SSH rành rọt quá hey...
Thời hạn chứng chỉ cho thiết bị 1 năm nhé......
Check the expiry date of a Certificate
openssl x509 -in rsa_public.pem -inform PEM -noout -enddate
5. Bước cuối cùng:
Có toàn bộ các thứ rồi, nhưng xem hình vẽ về ví dụ chúng ta theo đuổi: "Example Server" đang thiếu, đi cùng nó là "subscription", tạo luôn "subscription" tương ứng với 1 topic tao được: đặt topic, subscription là "topic1", "sub1" nhé, đau đầu quá... ("topic1" phải có trước đó rồi đấy)
pubsub.createSubscription('topic1', 'sub1').then(function(data) {
var subscription = data[0];
var apiResponse = data[1];
console.log(subscription,apiResponse);
}).catch((err) => {
console.error('ERROR:', err.message);
});
Oke, code tớ sẽ đẩy lên Github sau, hình dung lại ví dụ quan trọng hơn, túm váy lại:
Raspberry Pi 3 (R3) đọc giá trị cảm biến nhiệt, đẩy lên server (chạy trên localhost cho lành)
var sensor = {
nhietdo: 32
};
client.publish(mqttTopic, Buffer(JSON.stringify(sensor)), { qos: 1 });
Server lắng nghe sẵn rồi, nó phân tích dữ liệu nhiệt như sau: ví dụ thôi, nhiệt độ <0 quạt thôi bật, >10 bật quạt lên, thể hiện việc modifyCloudToDeviceConfig
subscription.on('message', function(message) {
message.ack(); // để xác nhận là đã nhận được message, và xóa nó trong hàng đợi
// việc này rất quan trọng, nếu thiếu, lần sau chạy subscription, tin nhắn sẽ ồ ạt chạy về,
// bởi nó chưa được xác thực là đã nhân được
var a = {
id: message.id, //ID of the message.
ackId: message.ackId, //ID used to acknowledge the message receival
data: message.data, //Contents of the message
attr: message.attributes, //Attributes of the message
tim: message.publishTime //Timestamp when Pub/Sub received the message.
}
const { StringDecoder } = require('string_decoder');
const decoder = new StringDecoder('utf8');
var thongtin = {};
try{ thongtin = JSON.parse(decoder.write(a.data)); }
catch(_err){;}
var fan = null;
if(thongtin.nhietdo<0) fan = false;
else if(thongtin.nhietdo>10) fan = true;
if(fan==null) return;
// update device config
const request = {
name: `projects/${opts.projectId}/locations/${opts.cloudRegion}/registries/${opts.registryId}/devices/${opts.deviceId}`,
resource: {
versionToUpdate: 0,
binaryData: Buffer(JSON.stringify({'fan_on': fan})).toString('base64')
}
};
client.projects.locations.registries.devices.modifyCloudToDeviceConfig(request,(err,data)=>{
if(err){
console.log(err.message);
} else{
console.log('--done modifyCloudToDeviceConfig');
}
})
});
Mqtt client trên R3 sẽ tự hiểu và có event liên quan đến nó, để mô phỏng, ta tạo 1 hành vi: quạt bật thì nhiệt đọ cảm biến giảm 1, nếu không thì tăng 1
client.on('message', (_,res)=>{
const { StringDecoder } = require('string_decoder');
const decoder = new StringDecoder('utf8');
var thongtin = {};
try{ thongtin = JSON.parse(decoder.write(res)); }
catch(_err){;}
console.log(thongtin);
if(thongtin['fan_on']){
sensor.nhietdo--;
} else{
sensor.nhietdo++;
}
})
Tìm code trên GitHub cá nhân của tôi: https://github.com/BonsoirDiep
Một ngày đẹp trời nào đó sẽ push lên :D
Hãy tự lên ý tưởng cho chính bạn, ví dụ trên chỉ mô phỏng để tìm hiểu cách thức. Cảm ơn vì đã theo dõi cũng như phản hồi.
Tôi, Hoàng Thanh, xin phép signout ...
JS: jsrsasign xác thực JWT
Các bạn có hỏi "SECRET_ME_RSA"
Vâng đó là 1 "passphrase" đi kèm để tăng thêm bảo mật cho "private key", khi sinh key với openssh thêm flag -des3, và định dạng nó thường là:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,....
Đặt ra ở đây là 1 số bạn thích: "node-jsonwebtoken" hơn thư viện mình giới thiệu là: "jsrsasign" bởi nó đơn giản hơn. Các ví dụ về "node-jsonwebtoken" thường ko nói đến passphrase. Vậy với "passphrase" thì sao?? Đây:
const fs = require('fs');
const jwt = require('jsonwebtoken');
// ...
function createJwt2 (privateKey, passPhrase) {
const payLoad= {
'iat': parseInt(Date.now() / 1000),
'exp': parseInt(Date.now() / 1000) + 20* 60, // hiệu lực 20 phút
'aud': "diepgepa"
};
const privateKey = fs.readFileSync('./rsa_private.pem');
jwt.sign(payLoad,{ key: privateKey, passphrase: passPhrase}, { algorithm: "RS256"},function(err, token) {
if(err) console.log(err);
else {
console.log(token);
}
});
}
Có thể không cần "passphrase" đi kèm để đơn giản.
Funs: Hiểu hơn về HTTPS với ví dụ bồ câu đưa thư
Cloud IoT Core end-to-end example with NodeJs
Gần đây mình có test "Cloud IoT Core" của Google. Mô hình cũng cung cấp xác thực nhưng khác với những gì mình đã trình bày trước đó.
Mình xin mạn phép nói qua các bước thông qua ví dụ của chính google, ví dụ "End-to-End Example _ Google Cloud Internet of Things Core _ Google Cloud Platform"
1. Tạo "rsa_private.pem", nên bỏ "passphrase" cho đơn giản
Các ví dụ cũng cho luôn cái "rsa_private.pem" này, nhưng tốt nhất là bạn nên tạo 1 cái, vì không có passphrase nên lệnh bash bỏ des3: "openssl genrsa -out rsa_private.pem"
RSA_PRIVATE này sẽ giúp ta chủ động tạo ra các token, hay là chủ động tạo ra các mã xác thực mà khi tạo kết nối của thiết bị sẽ cần tới. Nếu bạn muốn sử dụng "Cloud IoT Core" để cung cấp dịch vụ cho ai đó, dĩ nhiên sẽ không thể đưa "RSA_PRIVATE " chọ họ được, bạn nên tạo 1 web app, sinh ra các token với thời hạn xác định và gia hạn tự động nếu cần...
2. Nhìn vào mô hình ta cần phải tạo ra topic để phục vụ "Cloud Pub/Sub". Nếu trên NodeJs
var PubSub = require('@google-cloud/pubsub')({
projectId: 'xxx',
keyFilename: JSON_PATH
});
const pubsub = PubSub();
hoặc, ngầm xác thực theo kiểu:
process.env.GCLOUD_PROJECT = 'xxx';
process.env.GOOGLE_CLOUD_PROJECT = 'xxx';
process.env.GOOGLE_APPLICATION_CREDENTIALS = JSON_PATH';
const PubSub = require('@google-cloud/pubsub');
const pubsub = PubSub();
JSON_PATH là file json chứa permisson, hình thức này là đăng nhập kiểu "service account key", bản chất là Json Web Token đặc thù mà mình mô tả trong các bài viết trước.
Bạ có thể cấp quyền với "Google Cloud IAM". Phần này không thiên về IOT Core mà mình muốn bàn ở đây, nhưng để test được đầy đủ, phải có Pub/Sub. Nếu không muốn Auto hoàn toàn, các bạn tạo luôn trên "console.cloud.google.com"... Còn muốn auto:
function createIotTopic (topicName) {
pubsub.createTopic(topicName)
.then((results) => {
setupIotTopic(topicName);
})
.catch(err=>{
console.log(err.message);
});
}
3. Có được Topic là để ta cập nhật và đẩy dữ liệu lên thông qua "IoT Core"
Và do đó bản thân pub/sub vừa tạo, IoT Core cũng cần cấp quyền, IOT Core thể hiện mình là ai, và nó cần quyền gì??
Đó là "cloud-iot@system.gserviceaccount.com", nó cần quyền: "roles/pubsub.publisher"
// Configures the topic for Cloud IoT Core.
function setupIotTopic (topicName) {
const topic = pubsub.topic(topicName);
const serviceAccount = `serviceAccount:cloud-iot@system.gserviceaccount.com`;
topic.iam.getPolicy()
.then((results) => {
const policy = results[0] || {};
policy.bindings || (policy.bindings = []);// a||(a=1); /**if(!a) a = 1; */
console.log(JSON.stringify(policy, null, 2));
let hasRole = false;
let binding = {
role: 'roles/pubsub.publisher',
members: [serviceAccount]
};
policy.bindings.forEach((_binding) => {
if (_binding.role === binding.role) {
binding = _binding;
hasRole = true;
return false;
}
});
if (hasRole) {
binding.members || (binding.members = []);
if (binding.members.indexOf(serviceAccount) === -1) {
binding.members.push(serviceAccount);
}
} else {
policy.bindings.push(binding);
}
// Updates the IAM policy for the topic
return topic.iam.setPolicy(policy);
})
.then((results) => {
const updatedPolicy = results[0];
console.log(JSON.stringify(updatedPolicy, null, 2));
})
.catch((err) => {
console.error('ERROR:', err);
});
}
4. Về phía thiết lập IoT Core:
Có được Topic là nơi để đẩy dữ liệu, hay thiết bị hỗ trợ MQTT sẽ đẩy dữ liệu cho Cloud Pub/Sub.
Để làm được điều này IoT Core cần 2 bước là: "Create device registry", và "Add device"
"Create device registry" tương ứng với 1 topic đã tạo ở trên, "Add device" tương ứng với "Device Registry" vừa tạo. Chúng cần mã xác thực. Mã này ở đâu? Nó liên quan gì Private key đã tạo ở bước 1?
Đó là mã chứng chỉ( certificate) tương ứng với private key ở bước 1.
("add device" có thể dùng mã public key cũng được, "create device registry" có thể không cần mã chứng chỉ => tôi chọn, 1 mã chứng cho việc "add device", thế thôi, oke)
Tôi search là: "Generate a Self-Signed Certificate from an Existing Private Key"
Tìm được lệnh bash: "openssl req -newkey rsa:2048 -nodes -keyout rsa_private.pem -x509 -days 365 -out rsa_public.pem", sau đó nhập thông tin liên quan đến chứng chỉ. Nếu ai tạo https web app, tạo chứng chỉ SSH rành rọt quá hey...
Thời hạn chứng chỉ cho thiết bị 1 năm nhé......
Check the expiry date of a Certificate
openssl x509 -in rsa_public.pem -inform PEM -noout -enddate
5. Bước cuối cùng:
Có toàn bộ các thứ rồi, nhưng xem hình vẽ về ví dụ chúng ta theo đuổi: "Example Server" đang thiếu, đi cùng nó là "subscription", tạo luôn "subscription" tương ứng với 1 topic tao được: đặt topic, subscription là "topic1", "sub1" nhé, đau đầu quá... ("topic1" phải có trước đó rồi đấy)
pubsub.createSubscription('topic1', 'sub1').then(function(data) {
var subscription = data[0];
var apiResponse = data[1];
console.log(subscription,apiResponse);
}).catch((err) => {
console.error('ERROR:', err.message);
});
Oke, code tớ sẽ đẩy lên Github sau, hình dung lại ví dụ quan trọng hơn, túm váy lại:
Raspberry Pi 3 (R3) đọc giá trị cảm biến nhiệt, đẩy lên server (chạy trên localhost cho lành)
var sensor = {
nhietdo: 32
};
client.publish(mqttTopic, Buffer(JSON.stringify(sensor)), { qos: 1 });
Server lắng nghe sẵn rồi, nó phân tích dữ liệu nhiệt như sau: ví dụ thôi, nhiệt độ <0 quạt thôi bật, >10 bật quạt lên, thể hiện việc modifyCloudToDeviceConfig
subscription.on('message', function(message) {
message.ack(); // để xác nhận là đã nhận được message, và xóa nó trong hàng đợi
// việc này rất quan trọng, nếu thiếu, lần sau chạy subscription, tin nhắn sẽ ồ ạt chạy về,
// bởi nó chưa được xác thực là đã nhân được
var a = {
id: message.id, //ID of the message.
ackId: message.ackId, //ID used to acknowledge the message receival
data: message.data, //Contents of the message
attr: message.attributes, //Attributes of the message
tim: message.publishTime //Timestamp when Pub/Sub received the message.
}
const { StringDecoder } = require('string_decoder');
const decoder = new StringDecoder('utf8');
var thongtin = {};
try{ thongtin = JSON.parse(decoder.write(a.data)); }
catch(_err){;}
var fan = null;
if(thongtin.nhietdo<0) fan = false;
else if(thongtin.nhietdo>10) fan = true;
if(fan==null) return;
// update device config
const request = {
name: `projects/${opts.projectId}/locations/${opts.cloudRegion}/registries/${opts.registryId}/devices/${opts.deviceId}`,
resource: {
versionToUpdate: 0,
binaryData: Buffer(JSON.stringify({'fan_on': fan})).toString('base64')
}
};
client.projects.locations.registries.devices.modifyCloudToDeviceConfig(request,(err,data)=>{
if(err){
console.log(err.message);
} else{
console.log('--done modifyCloudToDeviceConfig');
}
})
});
Mqtt client trên R3 sẽ tự hiểu và có event liên quan đến nó, để mô phỏng, ta tạo 1 hành vi: quạt bật thì nhiệt đọ cảm biến giảm 1, nếu không thì tăng 1
client.on('message', (_,res)=>{
const { StringDecoder } = require('string_decoder');
const decoder = new StringDecoder('utf8');
var thongtin = {};
try{ thongtin = JSON.parse(decoder.write(res)); }
catch(_err){;}
console.log(thongtin);
if(thongtin['fan_on']){
sensor.nhietdo--;
} else{
sensor.nhietdo++;
}
})
Tìm code trên GitHub cá nhân của tôi: https://github.com/BonsoirDiep
Một ngày đẹp trời nào đó sẽ push lên :D
Hãy tự lên ý tưởng cho chính bạn, ví dụ trên chỉ mô phỏng để tìm hiểu cách thức. Cảm ơn vì đã theo dõi cũng như phản hồi.
Tôi, Hoàng Thanh, xin phép signout ...
Nhận xét
Đăng nhận xét