AWS - JITP,Just-in-Time Provisioning,即時部署。

春麗 S.T.E.M.
21 min readFeb 20, 2024

--

目錄
1. 前情提要
2. JITP
2-1 Template
3. 開始部署
3-1 自簽 CA 憑證的註冊
3-2 裝置憑證的註冊
4. 裝置連線測試

⦿ 前情提要
⦿ JITP
⦿ Template
⦿ 開始部署
⦿ 自簽 CA 憑證的註冊
⦿ 裝置憑證的註冊
⦿ 裝置連線測試

前情提要

經過了前面三篇文章,如下:

我們了解到要使用安全的 MQTT 與 AWS 溝通,來開展你的 IoT 事業,我們可以讓 AWS 幫我們產生必備要件,第一個是 rootCA Certificate,第二個 Device Certificate,第三個是 private key,以及準備好你的 Endpoint。

但我們也可以讓 AWS 幫我們認證自簽的 CA 憑證,當 AWS 認證你的自簽 CA 憑證,我們就可以用這個自簽 CA 憑證來簽署 Device Certificate,此時這個由你自簽 CA 憑證簽署的 Device Certificate 就可以向 AWS 註冊,註冊完成後,並加入適當政策,我們就可以將憑證放入,或是以 base64 的文字燒錄在 Device 裡,接著就可與 IoT Core 交互。

AWS 幫你產生憑證時,我們可以使用 CLI,也可以在網頁中操作,網頁操作較容易,前面文章也提過,這邊來講講 CLI。

% aws iot create-keys-and-certificate 
--set-as-active
--certificate-pem-outfile 你的憑證名稱
--public-key-outfile 你的公鑰名稱
--private-key-outfile 你的私鑰名稱

注意!這段指令是連起來的,否則你必須像 AWS 教學網頁中在每行後面加上空格反斜線,這段指令意思是讓 AWS IoT Core 幫你產生密鑰對憑證,並且產生後就將憑證設定為作用中

接著,必須給這個憑證附加政策(給予足夠權限),由於附加政策必須要有該憑證的 ARN(Amazon Resource Name,Amazon 資源名稱),所以如下操作:

% aws iot list-certificates

會得到如下訊息

{
"certificates": [
{
"certificateArn": "arn:aws:iot:ap-southwest-4:124567899012:cert/該憑證ID",
"certificateId": "該憑證ID",
"status": "ACTIVE",
"creationDate": "2024-02-19T09:29:55.419000+08:00"
}
}

我們可以看到憑證的 ARN 其實就是由 arn:IoT Core某伺服器下:帳號ID:cert/憑證ID 去組合而成的,這即是說即便你僅有 certificateId 也有機會兜出 ARN,畢竟前面那串在這種使用情境下是固定的。

接著如下操作:

% aws iot attach-policy --policy 你的政策名稱 --target 該憑證ARN

足夠權限政策附加到某個特定憑證。而如果你想要為這個憑證產生 Thing,以便於憑證的管理,我們可以如下操作:

% aws iot attach-thing-principal --thing-name 你的物件名稱 --principal 該憑證ARN

當然,我們也可以在憑證連上線時去驅動 Lambda Function,再附加 Thing 到這個憑證上,並且產生 shadow 等等,作法參考下面文章:

到此為止,這些步驟都是可以使用 AWS CLI 或直接在網頁上操作的,而 private key 跟 Device Certificate 都是 AWS 幫忙產生的,不過,我們仍然可以用自簽 CA 憑證去簽署 Device Certificate,因為這比較靈活,且不一定要全程有網路連線時才能動作。

自簽 CA 憑證經 AWS 認證後,我們就可以用來簽署 Device Certificate 了,不過,在第三篇文章中,我們直接在網頁上操作上傳憑證這件事,比對第二篇文章中,讓 AWS 認證自簽 CA 憑證這段指令,我們可以依樣畫葫蘆。

讓 AWS 認證自簽 CA 憑證。

% aws iot register-ca-certificate --ca-certificate file:///location/sampleCACertificate.pem --verification-certificate file:///location/privateKeyVerification.crt

向 AWS 註冊自簽 CA 憑證簽署的 Device Certificate。

% aws iot register-certificate --certificate-pem file:///location/deviceCert.crt --ca-certificate-pem file:///location/rootCA.pem --status ACTIVE

差別僅在於一個是註冊 CA 憑證,一個是註冊裝置憑證,註冊 CA 憑證需要的是由 CSR、CA 憑證、CA private key 組合而成的 CRT 來發起驗證;註冊憑證則需要既有的 CA 憑證(當然需先經過 AWS 認證),後面的 --status ACTIVE 即是將憑證狀態設定為作用中。

如此一來,在 AWS IoT Core 左方頁籤選取憑證後,憑證就會出現在右側。

而憑證附加政策物件,也同樣可以透過網頁AWS CLI 操作。

這樣的流程似乎相當嚴實,不過操作過程有些許繁雜,為此,AWS 給出了一個解決繁雜流程的方案——JITP,下面我們就來講講 JITP。

回目錄

繼續閱讀|回目錄

JITP

JITP,Just-in-Time Provisioning,即時部署。JITP 的目的在於,當設備使用憑證初次連到 AWS 時,就部署與註冊,註冊指的是憑證經過 AWS 認可,並且設置為作用中,然而,光是憑證註冊、設定為作用中,仍不足以構成設備能夠與 AWS 交互的條件。

我們需要賦予憑證相應的政策(足夠的權限),接著,我們也極有可能想將憑證與物件關聯起來,前段說的嚴實,卻實實在在多了好幾項任務,有沒有辦法在設備第一次連上線,即刻幫我們做到這些事呢?

有的,藉由 JITP 來即時部署,首先,到 IAM 建立名為 JITP 的角色,如下:

接著,回到 CLI 鍵入如下:

% vi jitp_template.json

編輯 JITP 模板,放入如下:

{
"templateBody":"{ \"Parameters\" : { \"AWS::IoT::Certificate::CommonName\" : { \"Type\" : \"String\" },\"AWS::IoT::Certificate::Country\" : { \"Type\" : \"String\" }, \"AWS::IoT::Certificate::Id\" : { \"Type\" : \"String\" }}, \"Resources\" : { \"thing\" : { \"Type\" : \"AWS::IoT::Thing\", \"Properties\" : { \"ThingName\" : {\"Ref\" : \"AWS::IoT::Certificate::CommonName\"}, \"AttributePayload\" : { \"version\" : \"v1\", \"country\" : {\"Ref\" : \"AWS::IoT::Certificate::Country\"}} } }, \"certificate\" : { \"Type\" : \"AWS::IoT::Certificate\", \"Properties\" : { \"CertificateId\": {\"Ref\" : \"AWS::IoT::Certificate::Id\"}, \"Status\" : \"ACTIVE\" } }, \"policy\" : {\"Type\" : \"AWS::IoT::Policy\", \"Properties\" : { \"PolicyDocument\" : \"{ \\\"Version\\\": \\\"2012-10-17\\\", \\\"Statement\\\": [ { \\\"Effect\\\": \\\"Allow\\\", \\\"Action\\\": [ \\\"iot:Connect\\\" ], \\\"Resource\\\": [ \\\"arn:aws:iot:us-east-2:<ACCOUNT_ID>:client\\\/${iot:Connection.Thing.ThingName}\\\" ] }, { \\\"Effect\\\": \\\"Allow\\\", \\\"Action\\\": [ \\\"iot:Publish\\\", \\\"iot:Receive\\\" ], \\\"Resource\\\": [ \\\"arn:aws:iot:us-east-2:<ACCOUNT_ID>:topic\\\/${iot:Connection.Thing.ThingName}\\\/*\\\" ] }, { \\\"Effect\\\": \\\"Allow\\\", \\\"Action\\\": [ \\\"iot:Subscribe\\\" ], \\\"Resource\\\": [ \\\"arn:aws:iot:us-east-2:<ACCOUNT_ID>:topicfilter\\\/${iot:Connection.Thing.ThingName}\\\/*\\\" ] } ] }\" } } } }",
"roleArn":"arn:aws:iam::<ACCOUNT_ID>:role/JITPRole"
}

先注意幾個重點:

  1. roleArn 更改為剛才建立的 JITP Role 的 ARN
  2. <ACCOUNT_ID> 換成你帳號的 ID,是一連串數字
  3. us-east-2 換成你伺服器所在區域
  4. 編輯完成後按下 esc 跳出 insert 模式,輸入 wq 存檔離開。

Template

我們來看看模板裡做了什麼,首先,將模板的階層整理清楚,呈現如下:

{
"templateBody": {
"Parameters": {
"AWS::IoT::Certificate::CommonName": {
"Type": "String"
},
"AWS::IoT::Certificate::Country": {
"Type": "String"
},
"AWS::IoT::Certificate::Id": {
"Type": "String"
}
},
"Resources": {
"thing": {
"Type": "AWS::IoT::Thing",
"Properties": {
"ThingName": {
"Ref": "AWS::IoT::Certificate::CommonName"
},
"AttributePayload": {
"version": "v1",
"country": {
"Ref": "AWS::IoT::Certificate::Country"
}
}
}
},
"certificate": {
"Type": "AWS::IoT::Certificate",
"Properties": {
"CertificateId": {
"Ref": "AWS::IoT::Certificate::Id"
},
"Status": "ACTIVE"
}
},
"policy": {
"Type": "AWS::IoT::Policy",
"Properties": {
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:Connect",
"iot:Publish",
"iot:Subscribe",
"iot:Receive"
],
"Resource": [
"*"
]
}
]
}
}
}
}
},
"roleArn": "arn:aws:iam::<ACCOUNT_ID>:role/JITPRole"
}

roleArn 不用說了,templateBody 中有 ParametersResources,Parameters 中有三個項目,他們的 Type 都是 String,如下:

  1. AWS::IoT::Certificate::CommonName
  2. AWS::IoT::Certificate::Country
  3. AWS::IoT::Certificate::Id
當然,如果你需要,也可以加入下面這些參數: 
AWS::IoT::Certificate::Organization
AWS::IoT::Certificate::OrganizationalUnit
AWS::IoT::Certificate::DistinguishedNameQualifier
AWS::IoT::Certificate::StateName
AWS::IoT::Certificate::SerialNumber

產生 Device Certificate 時,輸入的這些欄位,成為了參數,就可以用在需要的地方,比方填入的 CommonName 會被用在 ThingName,若輸入 Charles,產生的 Thing 的 Name 就是 Charles

Thing => ThingName => CommonName
Thing => AttributePayload => versioncountry

如果是使用 Ref 代表帶入的是前面的參數(Parameters),像是 country 的 RefAWS::IoT::Certificate::Country;ThingName 的 RefAWS::IoT::Certificate::CommonName

接著,我們也看到 policy 裡的 PolicyDocument 裡的 Statement,它 Allow 了 iot:Connectiot:Publishiot:Subscribeiot:Receive,這表示當設備第一次連上線,因為 CA 憑證因為使用了這個 Template 部署,會產生一個政策,並附加在 Device Certificate 上;除此之外,也會產生 Thing,並將 Device Certificate 關聯到這個 Thing 上。

若你想要附加已存在的 policy,需改成如下:

"policy": {
"Type": "AWS::IoT::Policy",
"Properties": {
"PolicyName": "Your_Policy_Name"
}
}

當然,如果你覺得輸入跳脫字元很麻煩,可以使用類似這樣的網站,將排列整齊的 json 階層,利用網站轉換成有跳脫字元的格式,json 檔變成如下:

{
"templateBody": "{\"Parameters\":{\"AWS::IoT::Certificate::CommonName\":{\"Type\":\"String\"},\"AWS::IoT::Certificate::Country\":{\"Type\":\"String\"},\"AWS::IoT::Certificate::Id\":{\"Type\":\"String\"}},\"Resources\":{\"thing\":{\"Type\":\"AWS::IoT::Thing\",\"Properties\":{\"ThingName\":{\"Ref\":\"AWS::IoT::Certificate::CommonName\"},\"AttributePayload\":{\"version\":\"v1\",\"country\":{\"Ref\":\"AWS::IoT::Certificate::Country\"}}}},\"certificate\":{\"Type\":\"AWS::IoT::Certificate\",\"Properties\":{\"CertificateId\":{\"Ref\":\"AWS::IoT::Certificate::Id\"},\"Status\":\"ACTIVE\"}},\"policy\":{\"Type\":\"AWS::IoT::Policy\",\"Properties\":{\"PolicyName\":\"YourExistingPolicyName\"}}}}",
"roleArn": "arn:aws:iam::<ACCOUNT_ID>:role/JITPRole"
}

好了!JITP 準備工作已完成。

回目錄

繼續閱讀|回目錄

開始部署

自簽 CA 憑證的註冊

有了前置作業,就可以開始產生自簽 CA 憑證,不過此時自簽 CA 憑證,在向 AWS 註冊時,必須跟剛才的 jitp_template.json 模板關聯起來,我們同樣順過流程。

  1. openssl genrsa -out CA_Private.key 2048
    產生 private key
  2. openssl req -x509 -new -nodes -key CA_Private.key -sha256 -days 365 -out CA_Certificate.pem
    使用 private key 產生自簽 CA 憑證,同樣填入組織相關內容
  3. aws iot get-registration-code
    取得 AWS 註冊碼,會得到 “registercode”: “一長串英數字
  4. openssl genrsa -out Verification_Private.key 2048
    產生驗證 private key
  5. openssl req -new -key Verification_Private.key -out Verification.csr
    以 private key 產生 CSR,此時 Common Name (e.g. server FQDN or YOUR name) []: 需填入註冊碼
  6. openssl x509 -req -in Verification.csr -CA CA_Certificate.pem -CAkey CA_Private.key -CAcreateserial -out Verification.crt -days 365 -sha256
    將 CSR、自簽 CA 憑證、CA private key 結合成 CRT
  7. 向 AWS 註冊 CA 憑證,使用驗證憑證 CRT,並且使用 JITP 模板:
aws iot register-ca-certificate --ca-certificate file:///Location/CA_Certificate.pem --verification-certificate file:///Location/Verification.crt --set-as-active --allow-auto-registration --registration-config file:///Location/jitp_template.json

我們也可以看到中間 --set-as-active --allow-auto-registration 表示將憑證設置為作用中,並且允許自動註冊

到這一步,若 AWS 回傳如下:

{
"certificateArn": "你的憑證 ARN",
"certificateId": "你的憑證 ID"
}

就代表成功了,實際上跟前面幾篇文章步驟的差別只在註冊時需要以 --registration-config 採用設定模板 jitp_template.json

下面再用 CA 憑證簽署裝置憑證。

裝置憑證的註冊

同樣順過流程。

  1. openssl genrsa -out Device.key 2048
    產生 Device private key
  2. openssl req -new -key Device.key -out Device_Certificate.csr
    以 private key 產生 CSR,填入組織相關內容,而此時的 Common Name (e.g. server FQDN or YOUR name) []: 後方填入的內容即是你的 Thing Name
  3. openssl x509 -req -in Device_Certificate.csr -CA CA_Certificate.pem -CAkey CA_Private.key -CAcreateserial -out Device_Certificate.crt -days 365 -sha256
    同樣將 CSR、CA 憑證、CA private key 組成 Device 憑證(CRT)

到這步為止就完成了。

回目錄

繼續閱讀|回目錄

裝置連線測試

我們將裝置的 CRTprivate key 燒錄在裝置裡,開始與 AWS 做 MQTT 連線,此時我們去檢查憑證。

發現憑證作用中,並且以模板產生了一個 IoT 政策,名稱為一長串英數字,裡面有 IoT 的一些基本權限,並限制了 Resource。

我們再去檢查實物(物件)。

發現多了一個 Thing Name 為 Charles 的 Thing(因為我剛才 common name 輸入 Charles),而屬性中的索引 Key-Value,如 country:TW、version:v1 都是模板產生的,關聯到的憑證就是由自簽 CA 憑證簽署的 Device Certificate。

我們再看到 MQTT 測試用戶端。

很好!確實有收到訊息。

在 Arduino 的 monitor 也出現如下:

這時我們可以把設備的電源移除,讓它斷線看看。

裝置在一段時間後經 MQTT 發出遺囑訊息(LWT),詳細參考這篇文章

然而從頭到尾的步驟只有,第一個建立模板 json 檔,第二個自簽 CA 憑證以模板註冊,第三個簽署 Device Certificate,第四個 Device 連線,但是其他的諸如 Device Certificate 設定作用中,附加政策,產生 Thing⋯⋯全都在 Device 第一次連線完成。

如此一來,整個產線流程相當方便,並且不需要一開始就產生 Thing 只為了管理產出的憑證,因為你想,憑證絕對是事先大量產出,設備卻不一定用到那些憑證,就算用到那些憑證,這些設備可能並沒有賣出去,設備若沒有連線 AWS,卻一開始就產生一堆 Thing,在管理上就會變得相當困難。

然而透過 JITP 即時部署,將這些步驟縮減為四個,並且降低了追蹤憑證的難度,因為沒有連線的憑證是不會產生 Thing,更不會註冊的,那麼,你的 IoT Core 就不需要塞一堆憑證而多了好幾頁內容,最後不清楚哪個是哪個。

JITP 是不是很棒呢?是的。

這次就分享到這,感謝您的閱讀。

回目錄

繼續閱讀|回目錄

Reference:

--

--

春麗 S.T.E.M.
春麗 S.T.E.M.

Written by 春麗 S.T.E.M.

Do not go gentle into that good night, Old age should burn and rave at close of day; Rage, rage, against the dying of the light.

No responses yet