תיעוד 83
הגדרת האפליקציה לנייד או מכשיר ה-IoT
עודכן לאחרונה ב-5 במאי 2026
המדריך הזה מראה איך להגדיר את מכשיר האינטרנט של הדברים (IoT) ואת האפליקציה לנייד כך שישתמשו באישורי לקוח עם API Shield.
#פרטי התרחיש
ההדגמה הזו משתמשת בדוגמה של מכשיר שקולט קריאות טמפרטורה ומשדר אותן באמצעות שליחת בקשת POST ל-API שמוגן על ידי Cloudflare. אפליקציה לנייד שנבנתה ב-Swift עבור iOS מאחזרת את הקריאות האלה ומציגה אותן.
כדי לשמור על הדוגמה פשוטה, ה-API ממומש כ-Cloudflare Worker (תוך שימוש בקוד מתוך המדריך To-Do List לבניית אפליקציית jamstack).
הטמפרטורות נשמרות ב-Workers KV כשכתובת ה-IP המקור משמשת כמפתח, אבל אפשר בקלות להשתמש ב-ערך מאישור הלקוח, כגון טביעת האצבע.
קוד ה-API לדוגמה שבהמשך שומר טמפרטורה וחותמת זמן ב-KV כשמתבצעת בקשת POST, ומחזיר את חמש הטמפרטורות האחרונות כשמתבצעת בקשת GET.
const defaultData = { temperatures: [] };
const getCache = (key) => TEMPERATURES.get(key);
const setCache = (key, data) => TEMPERATURES.put(key, data);
async function addTemperature(request) {
// Pull previously recorded temperatures for this client.
const ip = request.headers.get("CF-Connecting-IP");
const cacheKey = `data-${ip}`;
let data;
const cache = await getCache(cacheKey);
if (!cache) {
await setCache(cacheKey, JSON.stringify(defaultData));
data = defaultData;
} else {
data = JSON.parse(cache);
}
// Append the recorded temperatures with the submitted reading (assuming it has both temperature and a timestamp).
try {
const body = await request.text();
const val = JSON.parse(body);
if (val.temperature && val.time) {
data.temperatures.push(val);
await setCache(cacheKey, JSON.stringify(data));
return new Response("", { status: 201 });
} else {
return new Response(
"Unable to parse temperature and/or timestamp from JSON POST body",
{ status: 400 },
);
}
} catch (err) {
return new Response(err, { status: 500 });
}
}
function compareTimestamps(a, b) {
return -1 * (Date.parse(a.time) - Date.parse(b.time));
}
// Return the 5 most recent temperature measurements.
async function getTemperatures(request) {
const ip = request.headers.get("CF-Connecting-IP");
const cacheKey = `data-${ip}`;
const cache = await getCache(cacheKey);
if (!cache) {
return new Response(JSON.stringify(defaultData), {
status: 200,
headers: { "content-type": "application/json" },
});
} else {
data = JSON.parse(cache);
const retval = JSON.stringify(
data.temperatures.sort(compareTimestamps).splice(0, 5),
);
return new Response(retval, {
status: 200,
headers: { "content-type": "application/json" },
});
}
}
export default {
async fetch(request, env, ctx) {
return request.method === "POST"
? addTemperature(request)
: getTemperatures(request);
},
};#1. אימות ה-API
#שליחת נתוני דוגמה ב-POST ל-API
כדי לאמת את ה-API לפני הוספת אימות mTLS, שלחו קריאת טמפרטורה אקראית ב-POST:
$ TEMPERATURE=$(echo $((361 + RANDOM %11)) | awk '{printf("%.2f",$1/10.0)}')
$ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
$ echo -e "$TEMPERATURE\n$TIMESTAMP"
36.70
2020-09-28T02:54:56Z
$ curl --verbose --header "Content-Type: application/json" --data '{"temperature":'''$TEMPERATURE''', "time": "'''$TIMESTAMP'''"}' https://shield.upinatoms.com/temps 2>&1 | grep "< HTTP/2"
< HTTP/2 201#קבלת נתוני דוגמה ב-GET מה-API
בקשת GET לנקודת הקצה temps מחזירה את הקריאות האחרונות, כולל זו שנשלחה בדוגמה למעלה:
$ curl --silent https://shield.upinatoms.com/temps | jq .
[
{
"temperature": 36.3,
"time": "2020-09-28T02:57:49Z"
},
{
"temperature": 36.7,
"time": "2020-09-28T02:54:56Z"
},
{
"temperature": 36.2,
"time": "2020-09-28T02:33:08Z"
}
]#2. יצירת אישורים שהונפקו על ידי Cloudflare
לפני שאפשר להשתמש ב-API Shield כדי להגן על ה-API או על אפליקציית האינטרנט, צרו אישורי לקוח שהונפקו על ידי Cloudflare.
אפשר ליצור אישור לקוח בלוח הבקרה של Cloudflare.
עם זאת, מכיוון שרוב המפתחים שעובדים בקנה מידה גדול מייצרים בעצמם מפתחות פרטיים ובקשות לחתימה על אישור דרך ה-API, הדוגמה הזו משתמשת ב-API של Cloudflare כדי ליצור אישורי לקוח.
כדי ליצור אישור bootstrap עבור אפליקציית ה-iOS ועבור מכשיר ה-IoT, הדוגמה הזו משתמשת ב-ערכת כלי תשתית המפתחות הציבוריים של Cloudflare, CFSSL:
# Generate a private key and CSR for the iOS device.
$ cat <<'EOF' | tee -a csr.json
{
"hosts": [
"ios-bootstrap.devices.upinatoms.com"
],
"CN": "ios-bootstrap.devices.upinatoms.com",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [{
"C": "US",
"L": "Austin",
"O": "Temperature Testers, Inc.",
"OU": "Tech Operations",
"ST": "Texas"
}]
}
EOF
$ cfssl genkey csr.json | cfssljson -bare certificate
2020/09/27 21:28:46 [INFO] generate received request
2020/09/27 21:28:46 [INFO] received CSR
2020/09/27 21:28:46 [INFO] generating key: rsa-2048
2020/09/27 21:28:47 [INFO] encoded CSR
$ mv certificate-key.pem ios-key.pem
$ mv certificate.csr ios.csr
# Do the same for the IoT sensor.
$ sed -i.bak 's/ios-bootstrap/sensor-001/g' csr.json
$ cfssl genkey csr.json | cfssljson -bare certificate
...
$ mv certificate-key.pem sensor-key.pem
$ mv certificate.csr sensor.csr
# now ask that these CSRs be signed by the private CA issued for your zone
# we need to replace actual newlines in the CSR with ‘\n’ before POST’ing
$ CSR=$(cat ios.csr | perl -pe 's/\n/\\n/g')
$ request_body=$(< <(cat <<EOF
{
"validity_days": 3650,
"csr":"$CSR"
}
EOF
))
# save the response so we can view it and then extra the certificate
$ curl https://api.cloudflare.com/client/v4/zones/{zone_id}/client_certificates \
--header "X-Auth-Email: <EMAIL>" \
--header "X-Auth-Key: <API_KEY>" \
--header "Content-Type: application/json" \
--data "$request_body" > response.json
$ cat response.json | jq .
{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "7bf7f70c-7600-42e1-81c4-e4c0da9aa515",
"certificate_authority": {
"id": "8f5606d9-5133-4e53-b062-a2e5da51be5e",
"name": "Cloudflare Managed CA for account 11cbe197c050c9e422aaa103cfe30ed8"
},
"certificate": "-----BEGIN CERTIFICATE-----\nMIIEkzCCA...\n-----END CERTIFICATE-----\n",
"csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIIDITCCA...\n-----END CERTIFICATE REQUEST-----\n",
"ski": "eb2a48a19802a705c0e8a39489a71bd586638fdf",
"serial_number": "133270673305904147240315902291726509220894288063",
"signature": "SHA256WithRSA",
"common_name": "ios-bootstrap.devices.upinatoms.com",
"organization": "Temperature Testers, Inc.",
"organizational_unit": "Tech Operations",
"country": "US",
"state": "Texas",
"location": "Austin",
"expires_on": "2030-09-26T02:41:00Z",
"issued_on": "2020-09-28T02:41:00Z",
"fingerprint_sha256": "84b045d498f53a59bef53358441a3957de81261211fc9b6d46b0bf5880bdaf25",
"validity_days": 3650
}
}
$ cat response.json | jq .result.certificate | perl -npe 's/\\n/\n/g; s/"//g' > ios.pem
# Now ask that the second client certificate signing request be signed.
$ CSR=$(cat sensor.csr | perl -pe 's/\n/\\n/g')
$ request_body=$(< <(cat <<EOF
{
"validity_days": 3650,
"csr":"$CSR"
}
EOF
))
$ curl https://api.cloudflare.com/client/v4/zones/{zone_id}/client_certificates \
--header "X-Auth-Email: <EMAIL>" \
--header "X-Auth-Key: <API_KEY>" \
--header "Content-Type: application/json" \
--data "$request_body" | perl -npe 's/\\n/\n/g; s/"//g' > sensor.pem#3. הטמעת אישור הלקוח באפליקציה לנייד
כדי להגדיר את האפליקציה לנייד כך שתבקש באופן מאובטח את נתוני הטמפרטורה שנשלחו על ידי מכשיר ה-IoT, הטמיעו את אישור הלקוח באפליקציה לנייד.
לשם פשטות, הדוגמה הזו מטמיעה אישור ומפתח "bootstrap" בחבילת האפליקציה כקובץ בפורמט PKCS#12:
$ openssl pkcs12 -export -out bootstrap-cert.pfx -inkey ios-key.pem -in ios.pem
Enter Export Password:
Verifying - Enter Export Password:בפריסה בעולם האמיתי, אישור bootstrap צריך לשמש רק יחד עם פרטי ההזדהות של המשתמשים כדי לבצע אימות מול נקודת קצה של API שיכולה להחזיר אישור משתמש ייחודי. משתמשים ארגוניים ירצו להשתמש בניהול מכשירים ניידים (MDM) כדי להפיץ אישורים.
#הטמעת אישור הלקוח באפליקציית Android
להלן דוגמה לאופן שבו אפשר להשתמש באישור לקוח באפליקציית Android כדי לבצע קריאות HTTP. צריך להוסיף את ההרשאה הבאה ב-AndroidManifest.xml כדי לאפשר חיבור לאינטרנט.
<uses-permission android:name="android.permission.INTERNET" />לצורכי הדגמה, האישור בדוגמה הזו נשמר ב-app/src/main/res/raw/cert.pem והמפתח הפרטי נשמר ב-app/src/main/res/raw/key.pem. אפשר גם לשמור את הקבצים האלה בדרכים מאובטחות אחרות.
הדוגמה הבאה משתמשת ב-OkHttpClient, אבל אפשר גם להשתמש בלקוחות אחרים כגון HttpURLConnection בדרכים דומות. העיקר הוא להשתמש ב-SSLSocketFactory.
private OkHttpClient setUpClient() {
try {
final String SECRET = "secret"; // You may also store this String somewhere more secure.
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
// Get private key
InputStream privateKeyInputStream = getResources().openRawResource(R.raw.key);
byte[] privateKeyByteArray = new byte[privateKeyInputStream.available()];
privateKeyInputStream.read(privateKeyByteArray);
String privateKeyContent = new String(privateKeyByteArray, Charset.defaultCharset())
.replace("-----BEGIN PRIVATE KEY-----", "")
.replaceAll(System.lineSeparator(), "")
.replace("-----END PRIVATE KEY-----", "");
byte[] rawPrivateKeyByteArray = Base64.getDecoder().decode(privateKeyContent);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(rawPrivateKeyByteArray);
// Get certificate
InputStream certificateInputStream = getResources().openRawResource(R.raw.cert);
Certificate certificate = certificateFactory.generateCertificate(certificateInputStream);
// Set up KeyStore
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, SECRET.toCharArray());
keyStore.setKeyEntry("client", keyFactory.generatePrivate(keySpec), SECRET.toCharArray(), new Certificate[]{certificate});
certificateInputStream.close();
// Set up Trust Managers
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
// Set up Key Managers
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, SECRET.toCharArray());
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
// Obtain SSL Socket Factory
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, new SecureRandom());
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
// Finally, return the client, which will then be used to make HTTP calls.
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustManagers[0])
.build();
return client;
} catch (CertificateException | IOException | NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException | KeyManagementException | InvalidKeySpecException e) {
e.printStackTrace();
return null;
}
}הפונקציה שלמעלה מחזירה OkHttpClient שמוטמע בו אישור הלקוח. עכשיו אפשר להשתמש בלקוח הזה כדי לבצע בקשות HTTP לנקודת הקצה של ה-API שמוגנת באמצעות mTLS.
#4. הטמעת אישור הלקוח במכשיר ה-IoT
כדי להכין את מכשיר ה-IoT לתקשורת מאובטחת עם נקודת הקצה של ה-API, הטמיעו את האישור במכשיר והגדירו את המכשיר כך שישתמש באישור בזמן ביצוע בקשות POST.
הדוגמה הזו מניחה שהאישור והמפתח הפרטי הועתקו באופן מאובטח אל /etc/ssl/private/sensor-key.pem ואל /etc/ssl/certs/sensor.pem.
סקריפט הדוגמה שונה כך שיצביע על הקבצים האלה:
import requests
import json
from datetime import datetime
def readSensor():
# Takes a reading from a temperature sensor and store it to temp_measurement
dateTimeObj = datetime.now()
timestampStr = dateTimeObj.strftime('%Y-%m-%dT%H:%M:%SZ')
measurement = {'temperature':str(temp_measurement),'time':timestampStr}
return measurement
def main():
print("Cloudflare API Shield [IoT device demonstration]")
temperature = readSensor()
payload = json.dumps(temperature)
url = 'https://shield.upinatoms.com/temps'
json_headers = {'Content-Type': 'application/json'}
cert_file = ('/etc/ssl/certs/sensor.pem', '/etc/ssl/private/sensor-key.pem')
r = requests.post(url, headers = json_headers, data = payload, cert = cert_file)
print("Request body: ", r.request.body)
print("Response status code: %d" % r.status_code)כשהסקריפט מנסה להתחבר אל https://shield.upinatoms.com/temps, Cloudflare מבקש שיישלח אישור לקוח והסקריפט שולח את התוכן של /etc/ssl/certs/sensor.pem. לאחר מכן, כפי שנדרש כדי להשלים את לחיצת היד של SSL/TLS, הסקריפט מדגים שיש לו החזקה ב-/etc/ssl/private/sensor-key.pem.
בלי אישור הלקוח, Cloudflare דוחה את הבקשה:
Cloudflare API Shield [IoT device demonstration]
Request body: {"temperature": "36.5", "time": "2020-09-28T15:52:19Z"}
Response status code: 403כשמכשיר ה-IoT מציג אישור לקוח תקף, בקשת ה-POST מצליחה וקריאת הטמפרטורה נרשמת:
Cloudflare API Shield [IoT device demonstration]
Request body: {"temperature": "36.5", "time": "2020-09-28T15:56:45Z"}
Response status code: 201#5. הפעלת mTLS
אחרי יצירת אישורים שהונפקו על ידי Cloudflare, השלב הבא הוא להפעיל mTLS עבור המארחים שרוצים להגן עליהם באמצעות API Shield.
#6. הגדרת API Shield לדרישת אישורי לקוח
כדי להגדיר את API Shield כך שידרוש אישורי לקוח, צרו כלל mTLS.