1- نظرة عامة
توفّر المساحة الآمنة مشاركة البيانات والتعاون الآمنين بين عدة أطراف، مع السماح للمؤسسات بالحفاظ على سرية بياناتها. وهذا يعني أنّه يمكن للمؤسسات التعاون مع بعضها البعض مع الحفاظ على التحكّم في بياناتها وحمايتها من الوصول غير المصرّح به.
تتيح لك "المساحة الآمنة" الاستفادة من سيناريوهات يمكنك فيها الحصول على قيمة متبادلة من خلال تجميع البيانات الحساسة، التي غالبًا ما تكون خاضعة للرقابة، وتحليلها، مع الاحتفاظ بالتحكّم الكامل فيها. باستخدام Confidential Space، يمكن للمؤسسات الاستفادة بشكل متبادل من تجميع البيانات الحسّاسة وتحليلها، مثل معلومات تحديد الهوية الشخصية والمعلومات الصحية المحمية والملكية الفكرية والأسرار المشفرة، مع الاحتفاظ بالتحكّم الكامل فيها.
المتطلبات
- مشروعان منفصلان على Google Cloud Platform
- متصفّح، مثل Chrome أو Firefox
- معرفة أساسية بـ Google Compute Engine وConfidential VM والحاويات والمستودعات البعيدة والشهادات وسلاسل الشهادات
- معرفة أساسية بحسابات الخدمة وOpen Policy Agent وRego والبنية التحتية للمفتاح العام
ما ستتعلمه
- كيفية ضبط موارد السحابة الإلكترونية اللازمة لتشغيل Confidential Space
- كيفية تشغيل عبء عمل في "آلة افتراضية مراعية للخصوصية" تعمل بنسخة Confidential Space
- كيفية منح الإذن بالوصول إلى الموارد المحمية استنادًا إلى سمات رمز عبء العمل (ماذا) وبيئة Confidential Space (أين) والحساب الذي يشغّل عبء العمل (مَن)
يركّز هذا الدرس العملي على كيفية استخدام Confidential Space مع الموارد المحمية المستضافة في مكان آخر غير Google Cloud. ستتعرّف على كيفية طلب رمز مميّز مخصّص ومستقل من "خدمة التصديق من Google" من خلال تقديم رقم عشوائي واستخدامه كمرة واحدة والجمهور ونوع رمز البنية الأساسية للمفاتيح العامة.
في هذا الدرس التطبيقي حول الترميز، ستعمل على إعداد مساحة آمنة بين منتج وهمي، وهو USleep، وهو تطبيق في حاوية، ومنتج وهمي آخر، وهو UWear، وهو جهاز قابل للارتداء ومتصل، وذلك لحساب جودة نومك. ستشارك UWear المعلومات الصحية المحمية (PHI) مع USleep في بيئة آمنة ومعزولة (تُعرف أيضًا باسم بيئة التنفيذ الموثوقة أو TEE) بحيث يحتفظ مالكو البيانات بالسرية الكاملة.
UWear هي مدقّق أحمال العمل ومالك البيانات. بصفتك مدقّق وحدات العمل، ستراجع الرمز البرمجي في وحدة العمل التي يتم تشغيلها وستدوّن ملخّص الصورة. بصفتك مالك البيانات، يكتب تطبيق UWear منطق التحقّق للتحقّق من صلاحية الرمز المميّز وتوقيعه. تكتب سياسة التحقّق من الصحة باستخدام الملخّص الرقمي لصورة أحمال العمل التي تم تدقيقها، ولا تسمح إلا للملخّص الرقمي المحدّد للصورة، في بيئة محدّدة، بالوصول إلى البيانات الحساسة.
في هذا الدرس العملي، تنشر USleep التطبيق المحفوظ في حاوية. لا يمكن لتطبيق USleep الوصول إلى البيانات الحسّاسة، ولكنّه ينفّذ عبء العمل الموافق عليه والذي يُسمح له بالوصول إلى البيانات الحسّاسة.
يتضمّن الدرس التطبيقي حول الترميز الخطوات التالية:
- الخطوة 1: إعداد موارد السحابة الإلكترونية اللازمة لبرنامج التدريب العملي. إعداد المشاريع والفوترة والأذونات نزِّل رمز المصدر الخاص بالتجربة العملية واضبط متغيّرات البيئة.
- الخطوة 2: نزِّل شهادة الجذر وخزِّنها مع الرمز المصدر لتطبيق UWear.
- الخطوة 3: إنشاء حسابات خدمة منفصلة لأحمال العمل سيتم استخدامها من قِبل الجهاز الظاهري الخاص بأحمال العمل في USleep وUWear
- الخطوة 4: أنشئ عبء عمل USleep الذي يوفّر رمزًا مميّزًا للتصديق.
- الخطوة 5: إنشاء عبء عمل UWear الذي يتحقّق من صحة رمز الإثبات ويرسل البيانات الحسّاسة في حال الموافقة على الرمز
- الخطوة 6: تشغيل أحمال العمل USleep وUWear ستوفّر UWear البيانات الحسّاسة، وستشغّل USleep خوارزمية النوم على البيانات وستعرض نتيجة.
- الخطوة 7: (اختيارية) شغِّل عبء عمل غير مصرَّح به في USleep وتأكَّد من عدم تلقّي بيانات حسّاسة من UWear.
- الخطوة 8: تنظيف جميع الموارد
فهم سير العمل
ستنفّذ USleep عبء العمل في "مساحة للبيانات السرية". لتشغيل عبء العمل، يجب أن يتمكّن من الوصول إلى معلومات الصحة المحمية في UWear. للحصول على إذن الوصول، ينشئ حمل عمل USleep أولاً جلسة آمنة عبر بروتوكول أمان طبقة النقل (TLS). سيطلب تطبيق USleep أيضًا رمزًا مميّزًا للتصديق من "خدمة التصديق من Google" مع حمولة.
سيطلب تطبيق USleep رمزًا مميّزًا للتصديق مع حمولة JSON تحتوي على ثلاثة عناصر:
- رمز إثبات صحة مرتبط بجلسة بروتوكول أمان طبقة النقل (TLS) من أجل ربط رمز الإثبات بجلسة أمان طبقة النقل (TLS)، ستكون قيمة الرقم العشوائي هي تجزئة مواد التشفير التي تم تصديرها من بروتوكول أمان طبقة النقل (TLS). يضمن ربط الرمز المميز بجلسة TLS عدم حدوث هجمات الوسيط، لأنّ الطرفين المشاركين في جلسة TLS فقط هما من سيتمكّن من إنشاء قيمة الرقم العشوائي.
- سيتم توفير شريحة جمهور من مستخدمي "uwear". ستتحقّق UWear من أنّها الجمهور المستهدَف لرمز الإقرار المميز.
- نوع الرمز المميز "PKI" يعني نوع الرمز المميّز "PKI" أنّ USleep تريد طلب رمز مميّز مستقل. يمكن التحقّق من أنّ الرمز المميز المستقل موقَّع من Google باستخدام الشهادة الجذر التي تم تنزيلها من نقطة نهاية البنية الأساسية للمفتاح العام (PKI) المعروفة في Confidential Space. ويختلف ذلك عن نوع الرمز المميز التلقائي في OIDC، والذي يتم التحقّق من توقيعه باستخدام مفتاح عام يتم تغييره بانتظام.

تتلقّى أعباء عمل USleep الرمز المميز للمصادقة. بعد ذلك، ينضم تطبيق UWear إلى اتصال TLS مع تطبيق USleep ويسترد رمز المصادقة الخاص بتطبيق USleep. ستتحقّق UWear من صحة الرمز المميّز من خلال مقارنة مطالبة x5c بشهادة الجذر.
ستوافق UWear على عبء عمل USleep في الحالات التالية:
- أن يجتاز الرمز المميّز منطق التحقّق من صحة البنية الأساسية للمفاتيح العامة.
- ستتحقّق UWear من الرمز المميّز من خلال مقارنة مطالبة x5c بشهادة الجذر، والتأكّد من أنّ الرمز المميّز موقَّع من شهادة الورقة، وأخيرًا من أنّ شهادة الجذر التي تم تنزيلها هي شهادة الجذر نفسها الواردة في مطالبة x5c.
- تتطابق ادّعاءات قياس عبء العمل في الرمز المميّز مع شروط السمة المحدّدة في سياسة OPA. OPA هو محرّك سياسات مفتوح المصدر وعام الأغراض يوحّد تنفيذ السياسات على مستوى الحزمة. تستخدم OPA المستندات، مع بنية مشابهة لـ JSON، لضبط القيم الأساسية التي يتم التحقّق من صحة السياسة استنادًا إليها. اطّلِع على القيم الأساسية لسياسة OPA للحصول على مثال على القيم التي تتحقّق منها السياسة.
- يتطابق الرقم العشوائي مع الرقم العشوائي المتوقّع (مادة إنشاء المفاتيح التي تم تصديرها في بروتوكول أمان طبقة النقل). يتم التحقّق من ذلك في سياسة OPA أعلاه.
بعد اكتمال عمليات التحقّق هذه واجتيازها، يمكن لخدمة UWear تأكيد أنّه سيتم إرسال البيانات ومعالجتها بشكل آمن. ستردّ UWear بعد ذلك ببيانات الصحة المحمية الحساسة عبر جلسة TLS نفسها، وسيتمكّن USleep من استخدام هذه البيانات لحساب جودة نوم العميل.
2. إعداد موارد السحابة
قبل البدء
- إعداد مشروعَين على Google Cloud، أحدهما لتطبيق USleep والآخر لتطبيق UWear لمزيد من المعلومات حول إنشاء مشروع على Google Cloud، يُرجى الرجوع إلى "برنامج التدريب العملي حول إعداد مشروعك الأول على Google واستخدامه". يمكنك الرجوع إلى مقالة إنشاء المشاريع وإدارتها للحصول على تفاصيل حول كيفية استرداد رقم تعريف المشروع وكيفية اختلافه عن اسم المشروع ورقمه.
- فعِّل الفوترة لمشاريعك.
- في إحدى مشاريع Google على Cloud Shell، اضبط متغيرات بيئة المشروع المطلوبة كما هو موضّح أدناه.
export UWEAR_PROJECT_ID=<Google Cloud project id of UWear>
export USLEEP_PROJECT_ID=<Google Cloud project id of USleep>
- فعِّل واجهة برمجة التطبيقات Confidential Computing API وواجهات برمجة التطبيقات التالية لكلا المشروعَين.
gcloud config set project $UWEAR_PROJECT_ID
gcloud services enable \
cloudapis.googleapis.com \
cloudshell.googleapis.com \
container.googleapis.com \
containerregistry.googleapis.com \
confidentialcomputing.googleapis.com
gcloud config set project $USLEEP_PROJECT_ID
gcloud services enable \
cloudapis.googleapis.com \
cloudshell.googleapis.com \
container.googleapis.com \
containerregistry.googleapis.com \
confidentialcomputing.googleapis.com
- استرداد المعرّف الرئيسي باستخدام
gcloud auth list
# Output should contain
# ACCOUNT: <Principal Identifier>
# Set your member variable
export MEMBER='user:<Principal Identifier>'
- أضِف أذونات لهذين المشروعَين. يمكن إضافة الأذونات باتّباع التفاصيل الواردة في صفحة الويب الخاصة بمنح دور في "إدارة الهوية وإمكانية الوصول".
- بالنسبة إلى
$UWEAR_PROJECT_ID، ستحتاج إلى مشرف Artifact Registry ومشرف حساب الخدمة.
gcloud config set project $UWEAR_PROJECT_ID
# Add Artifact Registry Administrator role
gcloud projects add-iam-policy-binding $UWEAR_PROJECT_ID --member=$MEMBER --role='roles/iam.serviceAccountAdmin'
# Add Service Account Administrator role
gcloud projects add-iam-policy-binding $UWEAR_PROJECT_ID --member=$MEMBER --role='roles/artifactregistry.admin'
- بالنسبة إلى
$USLEEP_PROJECT_ID، ستحتاج إلى مشرف Compute ومشرف مساحة التخزين ومشرف Artifact Registry ومشرف حساب الخدمة.
gcloud config set project $USLEEP_PROJECT_ID
# Add Service Account Administrator role
gcloud projects add-iam-policy-binding $USLEEP_PROJECT_ID --member=$MEMBER --role='roles/iam.serviceAccountAdmin'
# Add Artifact Registry Administrator role
gcloud projects add-iam-policy-binding $USLEEP_PROJECT_ID --member=$MEMBER --role='roles/artifactregistry.admin'
# Add Compute Administrator role
gcloud projects add-iam-policy-binding $USLEEP_PROJECT_ID --member=$MEMBER --role='roles/compute.admin'
# Add Storage Administrator role
gcloud projects add-iam-policy-binding $USLEEP_PROJECT_ID --member=$MEMBER --role='roles/compute.storageAdmin'
- في أحد مشاريعك على Google Cloud، استنسِخ مستودع Confidential Space Codelab على GitHub في Cloud Shell باستخدام الأمر أدناه للحصول على النصوص البرمجية المطلوبة التي يتم استخدامها كجزء من هذا الدرس العملي.
git clone https://github.com/GoogleCloudPlatform/confidential-space.git
- غيِّر الدليل إلى دليل النصوص البرمجية الخاص بتجربة الترميز حول بيانات الصحة.
cd confidential-space/codelabs/health_data_analysis_codelab/scripts
- عدِّل هذين السطرين في نص config_env.sh البرمجي، والموجود في دليل codelabs/health_data_analysis_codelab/scripts. عدِّل أرقام تعريف المشاريع لتصبح أرقام تعريف مشاريعك في USleep وUWear. احرص على إزالة رمز التعليق "#" في بداية السطر.
# TODO: Populate UWear and USleep Project IDs
export UWEAR_PROJECT_ID=your-uwear-project-id
export USLEEP_PROJECT_ID=your-usleep-project-id
- اختياري: اضبط أي متغيّرات حالية. يمكنك تجاهل أسماء الموارد باستخدام هذه المتغيرات (مثل
export UWEAR_ARTIFACT_REPOSITORY='my-artifact-repository').
- يمكنك ضبط المتغيّرات التالية باستخدام أسماء موارد السحابة الإلكترونية الحالية. في حال ضبط المتغيّر، سيتم استخدام مرجع السحابة الإلكترونية الحالي المقابل من المشروع. في حال عدم ضبط المتغيّر، سيتم إنشاء اسم مورد السحابة الإلكترونية من القيم الواردة في النص البرمجي config_env.sh.
- نفِّذ config_env.sh script لضبط أسماء المتغيرات المتبقية على قيم استنادًا إلى رقم تعريف مشروعك لأسماء الموارد.
# Navigate to the scripts folder
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
# Run the config_env script
source config_env.sh
# Verify the variables were set
# Expected output for default variable should be `workload-sa`
echo $USLEEP_WORKLOAD_SERVICE_ACCOUNT
3- تنزيل شهادة الجذر
- من أجل التحقّق من صحة الرمز المميز المستقل الذي يتم إرجاعه من خدمة التصديق، يجب أن تتحقّق UWear من صحة التوقيع مقارنةً بشهادة الجذر في Confidential Space. على UWear تنزيل شهادة الجذر وتخزينها محليًا. في إحدى وحدات تحكّم مشاريع Google Cloud، نفِّذ الأوامر التالية:
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/uwear
wget https://confidentialcomputing.googleapis.com/.well-known/confidential_space_root.crt -O confidential_space_root.pem
- إنشاء الملف المرجعي لشهادة الجذر التي تم تنزيلها
openssl x509 -fingerprint -in confidential_space_root.pem -noout
- تأكَّد من أنّ بصمة الإصبع تطابق ملخّص SHA-1 التالي:
B9:51:20:74:2C:24:E3:AA:34:04:2E:1C:3B:A3:AA:D2:8B:21:23:21
4. إنشاء حساب خدمة خاص بعبء العمل
الآن، عليك إنشاء حسابَي خدمة، أحدهما لأحمال عمل USleep والآخر لأحمال عمل UWear. نفِّذ النص البرمجي create_service_accounts.sh لإنشاء حسابات خدمة أحمال العمل في مشروعَي USleep وUWear. ستستخدِم الأجهزة الافتراضية التي تشغّل أحمال العمل حسابات الخدمة هذه.
# Navigate to the scripts folder
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
# Run the create_service_accounts script
./create_service_accounts.sh
النص البرمجي:
- يمنح هذا الدور
iam.serviceAccountUserالذي يربط حساب الخدمة بعبء العمل. - يمنح هذا الأمر حساب خدمة عبء العمل الدور
confidentialcomputing.workloadUser. سيسمح ذلك لحساب المستخدم بإنشاء رمز مميّز للتصديق. - يمنح هذا الإذن دور
logging.logWriterلحساب خدمة عبء العمل. يتيح ذلك لبيئة Confidential Space كتابة السجلات في Cloud Logging بالإضافة إلى Serial Console، وبالتالي تتوفّر السجلات بعد إيقاف الجهاز الظاهري.
5- إنشاء حمل عمل USleep
في إطار هذه الخطوة، ستنشئ صور Docker لأحمال العمل المستخدَمة في هذا الدرس التطبيقي حول الترميز. حِمل العمل USleep هو تطبيق بسيط مكتوب بلغة Golang يحدّد جودة نوم العميل باستخدام معلومات الصحة الشخصية على جهاز يمكن ارتداؤه.
لمحة عن مقياس USleep Workload
حِمل عمل USleep هو تطبيق بسيط مكتوب بلغة Golang يحدّد جودة نوم العميل باستخدام معلومات صحية شخصية على جهاز يمكن ارتداؤه. يتضمّن عبء عمل USleep ثلاثة أجزاء رئيسية:
- إعداد جلسة بروتوكول أمان طبقة النقل واستخراج مواد إنشاء المفاتيح التي تم تصديرها
func handleConnectionRequest(w http.ResponseWriter, r *http.Request) {
// Upgrade HTTP Connection to a websocket.
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Printf("failed to upgrade connection to a websocket with err: %v\n", err)
return
}
defer conn.Close()
// Get EKM
hash, err := getEKMHashFromRequest(r)
if err != nil {
fmt.Printf("Failed to get EKM: %v", err)
}
...
}
func getEKMHashFromRequest(r *http.Request) (string, error) {
ekm, err := r.TLS.ExportKeyingMaterial("testing_nonce", nil, 32)
if err != nil {
err := fmt.Errorf("failed to get EKM from inbound http request: %w", err)
return "", err
}
sha := sha256.New()
sha.Write(ekm)
hash := base64.StdEncoding.EncodeToString(sha.Sum(nil))
fmt.Printf("EKM: %v\nSHA hash: %v", ekm, hash)
return hash, nil
}
- طلب رمز مميّز من "خدمة التصديق" يتضمّن جمهورًا وقيمة عشوائية ونوع رمز مميّز للبنية الأساسية للمفاتيح العامة (PKI).
func handleConnectionRequest(w http.ResponseWriter, r *http.Request) {
...
// Request token with TLS Exported Keying Material (EKM) hashed.
token, err := getCustomToken(hash)
if err != nil {
fmt.Printf("failed to get custom token from token endpoint: %v", err)
return
}
// Respond to the client with the token.
conn.WriteMessage(websocket.TextMessage, token)
...
}
var (
socketPath = "/run/container_launcher/teeserver.sock"
tokenEndpoint = "http://localhost/v1/token"
contentType = "application/json"
)
func getCustomToken(nonce string) ([]byte, error) {
httpClient := http.Client{
Transport: &http.Transport{
// Set the DialContext field to a function that creates
// a new network connection to a Unix domain socket
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", socketPath)
},
},
}
body := fmt.Sprintf(`{
"audience": "uwear",
"nonces": ["%s"],
"token_type": "PKI"
}`, nonce)
resp, err := httpClient.Post(tokenEndpoint, contentType, strings.NewReader(body))
if err != nil {
return nil, err
}
fmt.Printf("Response from launcher: %v\n", resp)
text, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("Failed to read resp.Body: %w", err)
}
fmt.Printf("Token from the attestation service: %s\n", text)
return text, nil
}
- تلقّي البيانات الحساسة واحتساب جودة نوم المستخدم
func handleConnectionRequest(w http.ResponseWriter, r *http.Request) {
...
// Read the sensitive data
_, content, err := conn.ReadMessage()
if err != nil {
fmt.Printf("failed to read message from the connection: %v\n", err)
}
fmt.Printf("Received content from other side, %v\n", string(content))
// TODO: Handle sensitive data
...
}
خطوات إنشاء عبء عمل USleep
- نفِّذ النص البرمجي create_usleep_workload.sh لإنشاء عبء عمل USleep. يعمل هذا النص البرمجي على:
- يتم إنشاء Artifact Registry (
$USLEEP_ARTIFACT_REPOSITORY) مملوك من UWear حيث سيتم نشر عبء العمل. - ينشئ الرمز usleep/workload.go ويحزّمه في صورة Docker. اطّلِع على إعدادات Dockerfile الخاصة بـ USleep.
- ينشر صورة Docker في Artifact Registry (
$USLEEP_ARTIFACT_REPOSITORY) المملوكة من UWear. - يمنح حساب الخدمة إذن القراءة
$USLEEP_WORKLOAD_SERVICE_ACCOUNTفي Artifact Registry ($USLEEP_ARTIFACT_REPOSITORY).
./create_usleep_workload.sh
- ملاحظة مهمة: في سجلّات الإخراج، استخرِج ملخّص الصورة الخاص بتطبيق USleep.
latest: digest: sha256:<USLEEP_IMAGE_DIGEST> size: 945
- الانتقال إلى دليل UWear
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/uwear
- استبدِل القيمة ضمن "allowed_submods_container_image_digest" في ملف opa_validation_values.json بـ USLEEP_IMAGE_DIGEST.
# Replace the image digest
sed -i 's/sha256:bc4c32cb2ca046ba07dcd964b07a320b7d0ca88a5cf8e979da15cae68a2103ee/sha256:<USLEEP_IMAGE_DIGEST>/' ~/confidential-space/codelabs/health_data_analysis_codelab/src/uwear/opa_validation_values.json
6. إنشاء حمل عمل UWear
لمحة عن مقياس UWear Workload
تتضمّن أداة UWear أربعة أجزاء رئيسية:
- الانضمام إلى جلسة TLS نفسها التي تم إنشاؤها في حمل عمل USleep واسترداد رمز الإثبات من USleep عبر جلسة TLS الآمنة
func main() {
fmt.Println("Initializing client...")
tlsconfig := &tls.Config{
// Skipping client verification of the server's certificate chain and host name since we are
// doing custom verification using the attestation token.
InsecureSkipVerify: true,
}
dialer := websocket.Dialer{
TLSClientConfig: tlsconfig,
HandshakeTimeout: 5 * time.Second,
}
ipAddress := os.Getenv(ipAddrEnvVar)
url := fmt.Sprintf("wss://%s:8081/connection", ipAddress)
fmt.Printf("Attempting to dial to url %v...\n", url)
conn, _, err := dialer.Dial(url, nil)
if err != nil {
fmt.Printf("Failed to dial to url %s, err %v\n", url, err)
return
}
defer conn.Close()
tokenString, ekm, err := retrieveTokenAndEKMFromConn(conn)
if err != nil {
fmt.Printf("Failed to retrieve token and EKM from connection: %v\n", err)
return
}
fmt.Printf("token: %v\n", tokenString)
...
}
- التحقّق من صحة الرمز المميّز المستقل من خلال:
- التحقّق من أنّ مطالبة x5c تحتوي على سلسلة شهادات مرتبطة بشكل صحيح من الشهادة الطرفية إلى الشهادة الوسيطة وأخيرًا إلى شهادة الجذر
- التحقّق من أنّ الرمز المميّز موقّع من خلال الشهادة الطرفية الواردة في مطالبة x5c
- التحقّق من أنّ شهادة الجذر التي تم تنزيلها أو تخزينها هي شهادة الجذر نفسها الواردة في مطالبة x5c
func main() {
...
token, err := validatePKIToken(tokenString)
if err != nil {
fmt.Printf("Failed to validate PKI token, err: %v\n.", err)
return
}
fmt.Println("PKI token validated successfully")
...
}
// validatePKIToken validates the PKI token returned from the attestation service.
// It verifies the token the certificate chain and that the token is signed by Google
// Returns a jwt.Token or returns an error if invalid.
func validatePKIToken(attestationToken string) (jwt.Token, error) {
// IMPORTANT: The attestation token should be considered untrusted until the certificate chain and
// the signature is verified.
rawRootCertificate, err := readFile(rootCertificateFile)
if err != nil {
return jwt.Token{}, fmt.Errorf("readFile(%v) - failed to read root certificate: %w", rootCertificateFile, err)
}
storedRootCert, err := decodeAndParsePEMCertificate(string(rawRootCertificate))
if err != nil {
return jwt.Token{}, fmt.Errorf("DecodeAndParsePEMCertificate(string) - failed to decode and parse root certificate: %w", err)
}
jwtHeaders, err := extractJWTHeaders(attestationToken)
if err != nil {
return jwt.Token{}, fmt.Errorf("ExtractJWTHeaders(token) - failed to extract JWT headers: %w", err)
}
if jwtHeaders["alg"] != "RS256" {
return jwt.Token{}, fmt.Errorf("ValidatePKIToken(attestationToken, ekm) - got Alg: %v, want: %v", jwtHeaders["alg"], "RS256")
}
// Additional Check: Validate the ALG in the header matches the certificate SPKI.
// https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.7
// This is included in Golang's jwt.Parse function
x5cHeaders := jwtHeaders["x5c"].([]any)
certificates, err := extractCertificatesFromX5CHeader(x5cHeaders)
if err != nil {
return jwt.Token{}, fmt.Errorf("ExtractCertificatesFromX5CHeader(x5cHeaders) returned error: %w", err)
}
// Verify the leaf certificate signature algorithm is an RSA key
if certificates.LeafCert.SignatureAlgorithm != x509.SHA256WithRSA {
return jwt.Token{}, fmt.Errorf("leaf certificate signature algorithm is not SHA256WithRSA")
}
// Verify the leaf certificate public key algorithm is RSA
if certificates.LeafCert.PublicKeyAlgorithm != x509.RSA {
return jwt.Token{}, fmt.Errorf("leaf certificate public key algorithm is not RSA")
}
// Verify the storedRootCertificate is the same as the root certificate returned in the token
// storedRootCertificate is downloaded from the confidential computing well known endpoint
// https://confidentialcomputing.googleapis.com/.well-known/attestation-pki-root
err = compareCertificates(*storedRootCert, *certificates.RootCert)
if err != nil {
return jwt.Token{}, fmt.Errorf("failed to verify certificate chain: %w", err)
}
err = verifyCertificateChain(certificates)
if err != nil {
return jwt.Token{}, fmt.Errorf("VerifyCertificateChain(CertificateChain) - error verifying x5c chain: %v", err)
}
keyFunc := func(token *jwt.Token) (any, error) {
return certificates.LeafCert.PublicKey, nil
}
verifiedJWT, err := jwt.Parse(attestationToken, keyFunc)
return *verifiedJWT, err
}
// verifyCertificateChain verifies the certificate chain from leaf to root.
// It also checks that all certificate lifetimes are valid.
func verifyCertificateChain(certificates CertificateChain) error {
// Additional check: Verify that all certificates in the cert chain are valid.
// Note: The *x509.Certificate Verify method in Golang already validates this but for other coding
// languages it is important to make sure the certificate lifetimes are checked.
if isCertificateLifetimeValid(certificates.LeafCert) {
return fmt.Errorf("leaf certificate is not valid")
}
if isCertificateLifetimeValid(certificates.IntermediateCert) {
return fmt.Errorf("intermediate certificate is not valid")
}
interPool := x509.NewCertPool()
interPool.AddCert(certificates.IntermediateCert)
if isCertificateLifetimeValid(certificates.RootCert) {
return fmt.Errorf("root certificate is not valid")
}
rootPool := x509.NewCertPool()
rootPool.AddCert(certificates.RootCert)
_, err := certificates.LeafCert.Verify(x509.VerifyOptions{
Intermediates: interPool,
Roots: rootPool,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
})
if err != nil {
return fmt.Errorf("failed to verify certificate chain: %v", err)
}
return nil
}
- ستتحقّق أداة UWear بعد ذلك ممّا إذا كانت ادّعاءات قياس أداء عبء العمل في الرمز المميز تتطابق مع شروط السمة المحدّدة في سياسة OPA. OPA هو محرّك سياسات مفتوح المصدر وعام الأغراض يعمل على توحيد تنفيذ السياسات على مستوى جميع الطبقات. تستخدم OPA المستندات، مع بنية مشابهة لـ JSON، لضبط القيم الأساسية التي يتم التحقّق من صحة السياسة استنادًا إليها.
func main() {
...
err = validateClaimsAgainstOPAPolicy(token, ekm)
if err != nil {
fmt.Printf("Failed to validate claims against OPA policy: %v\n", err)
return
}
fmt.Println("Validated token and claims. Sending sensitive data")
...
}
// validateClaimsAgainstOPAPolicy validates the claims in the JWT token against the OPA policy.
func validateClaimsAgainstOPAPolicy(token jwt.Token, ekm string) error {
data, err := os.ReadFile("opa_validation_values.json")
authorized, err := evaluateOPAPolicy(context.Background(), token, ekm, string(data))
if err != nil {
fmt.Println("Error evaluating OPA policy:", err)
return fmt.Errorf("failed to evaluate OPA policy: %w", err)
}
if !authorized {
fmt.Println("Remote TEE's JWT failed policy check.")
return fmt.Errorf("remote TEE's JWT failed policy check")
}
fmt.Println("JWT is authorized.")
return nil
}
// evaluateOPAPolicy returns boolean indicating if OPA policy is satisfied or not, or error if occurred
func evaluateOPAPolicy(ctx context.Context, token jwt.Token, ekm string, policyData string) (bool, error) {
var claims jwt.MapClaims
var ok bool
if claims, ok = token.Claims.(jwt.MapClaims); !ok {
return false, fmt.Errorf("failed to get the claims from the JWT")
}
module := fmt.Sprintf(opaPolicy, ekm)
var json map[string]any
err := util.UnmarshalJSON([]byte(policyData), &json)
store := inmem.NewFromObject(json)
// Bind 'allow' to the value of the policy decision
// Bind 'hw_verified', 'image_verified', 'audience_verified, 'nonce_verified' to their respective policy evaluations
query, err := rego.New(
rego.Query(regoQuery), // Argument 1 (Query string)
rego.Store(store), // Argument 2 (Data store)
rego.Module("confidential_space.rego", module), // Argument 3 (Policy module)
).PrepareForEval(ctx)
if err != nil {
fmt.Printf("Error creating query: %v\n", err)
return false, err
}
fmt.Println("Performing OPA query evaluation...")
results, err := query.Eval(ctx, rego.EvalInput(claims))
if err != nil {
fmt.Printf("Error evaluating OPA policy: %v\n", err)
return false, err
} else if len(results) == 0 {
fmt.Println("Undefined result from evaluating OPA policy")
return false, err
} else if result, ok := results[0].Bindings["allow"].(bool); !ok {
fmt.Printf("Unexpected result type: %v\n", ok)
fmt.Printf("Result: %+v\n", result)
return false, err
}
fmt.Println("OPA policy evaluation completed.")
fmt.Println("OPA policy result values:")
for key, value := range results[0].Bindings {
fmt.Printf("[ %s ]: %v\n", key, value)
}
result := results[0].Bindings["allow"]
if result == true {
fmt.Println("Policy check PASSED")
return true, nil
}
fmt.Println("Policy check FAILED")
return false, nil
}
- مثال على القيم الأساسية لـ OPA:
{
"allowed_submods_container_image_digest": [
"sha256:<USLEEP_IMAGE_DIGEST>"
],
"allowed_hwmodel": [
"GCP_INTEL_TDX",
"GCP_SHIELDED_VM",
"GCP_AMD_SEV_ES",
"GCP_AMD_SEV"
],
"allowed_aud": [
"uwear"
],
"allowed_issuer": [
"https://confidentialcomputing.googleapis.com"
],
"allowed_secboot": [
true
],
"allowed_sw_name": [
"CONFIDENTIAL_SPACE"
]
}
- مثال على سياسة OPA مكتوبة بلغة Rego
package confidential_space
import rego.v1
default allow := false
default hw_verified := false
default image_digest_verified := false
default audience_verified := false
default nonce_verified := false
default issuer_verified := false
default secboot_verified := false
default sw_name_verified := false
allow if {
hw_verified
image_digest_verified
audience_verified
nonce_verified
issuer_verified
secboot_verified
sw_name_verified
}
hw_verified if input.hwmodel in data.allowed_hwmodel
image_digest_verified if input.submods.container.image_digest in data.allowed_submods_container_image_digest
audience_verified if input.aud in data.allowed_aud
issuer_verified if input.iss in data.allowed_issuer
secboot_verified if input.secboot in data.allowed_secboot
sw_name_verified if input.swname in data.allowed_sw_name
nonce_verified if {
input.eat_nonce == "%s"
}
- مثال على طلب بحث Rego
regoQuery = "
allow = data.confidential_space.allow;
hw_verified = data.confidential_space.hw_verified;
image__digest_verified = data.confidential_space.image_digest_verified;
audience_verified = data.confidential_space.audience_verified;
nonce_verified = data.confidential_space.nonce_verified;
issuer_verified = data.confidential_space.issuer_verified;
secboot_verified = data.confidential_space.secboot_verified;
sw_name_verified = data.confidential_space.sw_name_verified
"
- أثناء عملية التحقّق من صحة OPA، تتحقّق أيضًا حملة عمل UWear من أنّ الرقم العشوائي يتطابق مع الرقم العشوائي المتوقّع (مادة التشفير التي تم تصديرها في بروتوكول أمان طبقة النقل - EKM). يتم التحقّق من الرقم العشوائي في سياسة OPA باستخدام EKM الذي يتم تمريره إلى أداة تقييم السياسة.
مثال على الرمز البرمجي للحصول على تجزئة EKM:
func getEKMHashFromConn(c *websocket.Conn) (string, error) {
conn, ok := c.NetConn().(*tls.Conn)
if !ok {
return "", fmt.Errorf("failed to cast NetConn to *tls.Conn")
}
state := conn.ConnectionState()
ekm, err := state.ExportKeyingMaterial("testing_nonce", nil, 32)
if err != nil {
return "", fmt.Errorf("failed to get EKM from TLS connection: %w", err)
}
sha := sha256.New()
sha.Write(ekm)
hash := base64.StdEncoding.EncodeToString(sha.Sum(nil))
return hash, nil
}
- بعد اكتمال عمليات التحقّق هذه واجتيازها، يمكن لتطبيق UWear تأكيد أنّه سيتم إرسال البيانات ومعالجتها بشكل آمن. ستردّ UWear بعد ذلك ببيانات الصحة المحمية الحساسة عبر جلسة TLS نفسها، وسيتمكّن USleep من استخدام هذه البيانات لحساب جودة نوم العميل.
func main() {
...
fmt.Println("Validated token and claims. Sending sensitive data")
data, err := readFile(mySensitiveDataFile)
if err != nil {
fmt.Printf("Failed to read data from the file: %v\n", err)
}
conn.WriteMessage(websocket.BinaryMessage, data)
fmt.Println("Sent payload. Closing the connection")
conn.Close()
...
}
خطوات إنشاء عبء عمل USleep
- الانتقال إلى دليل النصوص البرمجية
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
- شغِّل النص البرمجي create_uwear_workload.sh لإنشاء عبء عمل UWear:
- يتم إنشاء Artifact Registry (
$UWEAR_ARTIFACT_REPOSITORY) مملوك من قِبل UWear حيث سيتم نشر عبء العمل. - ينشئ الرمز uwear/workload.go ويحزّمه في صورة Docker. اطّلِع على إعدادات Dockerfile الخاصة بـ USleep.
- ينشر صورة Docker في Artifact Registry (
$UWEAR_ARTIFACT_REPOSITORY) المملوكة من UWear. - يمنح حساب الخدمة إذن القراءة
$UWEAR_WORKLOAD_SERVICE_ACCOUNTفي Artifact Registry ($UWEAR_ARTIFACT_REPOSITORY).
./create_uwear_workload.sh
7. تشغيل أعباء عمل USleep وUWear
تشغيل حمل عمل USleep
gcloud config set project $USLEEP_PROJECT_ID
gcloud compute instances create \
--confidential-compute-type=SEV \
--shielded-secure-boot \
--maintenance-policy=MIGRATE \
--scopes=cloud-platform --zone=${USLEEP_PROJECT_ZONE} \
--image-project=confidential-space-images \
--image-family=confidential-space \
--service-account=${USLEEP_WORKLOAD_SERVICE_ACCOUNT}@${USLEEP_PROJECT_ID}.iam.gserviceaccount.com \
--metadata ^~^tee-image-reference=${USLEEP_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${USLEEP_PROJECT_ID}/${USLEEP_ARTIFACT_REPOSITORY}/${USLEEP_WORKLOAD_IMAGE_NAME}:${USLEEP_WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-container-log-redirect=true usleep
يجب أن تعرض الاستجابة STATUS: RUNNING، ويجب أيضًا عرض EXTERNAL_IP بشكل مشابه لما يلي:
NAME: usleep
ZONE: us-west1-b
MACHINE_TYPE: n2d-standard-2
PREEMPTIBLE:
INTERNAL_IP: 10.138.0.6
EXTERNAL_IP: 34.168.56.10
STATUS: RUNNING
تخزين عنوان IP الخارجي في متغيّر
export USLEEP_EXTERNAL_IP=<add your external IP>
التحقّق من أنّ عبء عمل USleep تم تنفيذه بشكل صحيح
للتأكّد من أنّ عبء عمل USleep يعمل بشكل صحيح، انتقِل إلى صفحة "الأجهزة الافتراضية" في مشروع USleep. انقر على مثيل "usleep" واضغط على "المنفذ التسلسلي 1(وحدة التحكّم)" ضمن قسم "السجلات". بعد تشغيل الخادم، من المفترض أن تعرض السجلات في أسفلها شيئًا مشابهًا لما يلي.
2024/09/13 17:00:00 workload task started
#####----- Local IP Address is <YOUR-LOCAL-IP> -----#####
Starting Server..
تشغيل حمل عمل UWear
gcloud config set project $UWEAR_PROJECT_ID
gcloud compute instances create \
--confidential-compute-type=SEV \
--shielded-secure-boot \
--maintenance-policy=MIGRATE \
--scopes=cloud-platform --zone=${UWEAR_PROJECT_ZONE} \
--image-project=confidential-space-images \
--image-family=confidential-space \
--service-account=${UWEAR_WORKLOAD_SERVICE_ACCOUNT}@${UWEAR_PROJECT_ID}.iam.gserviceaccount.com \
--metadata ^~^tee-image-reference=${UWEAR_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${UWEAR_PROJECT_ID}/${UWEAR_ARTIFACT_REPOSITORY}/${UWEAR_WORKLOAD_IMAGE_NAME}:${UWEAR_WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-container-log-redirect=true~tee-env-remote_ip_addr=$USLEEP_EXTERNAL_IP uwear
التأكّد من أنّ عبء عمل UWear تم تنفيذه بشكل صحيح
للاطّلاع على سجلّات وحدة عمل UWear، انتقِل إلى صفحة "مثيلات الجهاز الافتراضي" في مشروع UWear. انقر على مثيل "uwear" واضغط على "المنفذ التسلسلي 1(وحدة التحكّم)" ضمن قسم "السجلات".
يجب أن تبدو مخرجات السجلّ بعد بدء تشغيل الجهاز الافتراضي بالكامل على النحو التالي
في مشروع UWear، يجب أن تعرض سجلّات الأرقام التسلسلية شيئًا مشابهًا لما يلي
token: eyJ[...]MrXUg
PKI token validated successfully
Performing OPA query evaluation...
OPA policy evaluation completed.
OPA policy result values:
[ hw_verified ]: true
[ image__digest_verified ]: true
[ audience_verified ]: true
[ nonce_verified ]: true
[ issuer_verified ]: true
[ secboot_verified ]: true
[ sw_name_verified ]: true
[ allow ]: true
Policy check PASSED
JWT is authorized.
Validated token and claims. Sending sensitive data
Sent payload. Closing the connection
إذا لم تكن مهام UWear تبدو بهذا الشكل، اطّلِع على الملاحظات أدناه للحصول على التعليمات.
عرض نتائج USleep
للاطّلاع على النتائج، انتقِل مجددًا إلى صفحة "مثيلات الجهاز الافتراضي" في مشروع USleep. انقر على مثيل "usleep" واضغط على "المنفذ التسلسلي 1(وحدة التحكّم)" ضمن قسم "السجلات". اطّلِع على نتائج عبء العمل في أسفل السجلّات. يجب أن تبدو مشابهة للمثال أدناه.
Token from the attestation service: eyJhbGci...Ii5A3CJBuDM2o5Q
Received content from other side, {
"name": "Amy",
"age": 29,
"sleep": {
"light": {
"minutes": 270
},
"deep": {
"minutes": 135
},
"rem": {
"minutes": 105
}
}
}
Sleep quality result: total sleep time is less than 8 hours
يجب أن تكون النتيجة "total sleep time is less than 8 hours".
تهانينا، لقد نجحت في إنشاء "مساحة آمنة" بين UWear وUSleep لمشاركة المعلومات الحسّاسة.
8. (اختياري) تنفيذ عبء عمل غير مصرّح به
في السيناريو التالي، تعدّل USleep الرمز البرمجي وتنفّذ عبء عمل مختلفًا على بيانات النوم المقدَّمة من UWear. لم توافق UWear على عبء العمل الجديد هذا ولم تعدّل سياسة OPA الخاصة بها للسماح باستخدام ملخّص الصورة الجديد. سنتأكّد من أنّ UWear لن ترسل بياناتها الحسّاسة إلى عبء العمل غير المصرّح به.
تعديل USleep على وحدات العمل
- اضبط المشروع على $USLEEP_PROJECT_ID.
gcloud config set project $USLEEP_PROJECT_ID
- احذف مثيل الجهاز الافتراضي USleep.
gcloud compute instances delete usleep --zone $USLEEP_PROJECT_ZONE
- انتقِل إلى الدليل usleep/workload.go.
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/usleep
- في الملف usleep/workload.go عدِّل السطر
"audience": "uwear".في هذا المثال، لتغيير ملخّص الصورة، سنعدّل الجمهور إلى قيمة مختلفة لم توافق عليها UWear. لذلك، يجب أن ترفض UWear الطلب لسببَين: ملخّص الصور غير معتمَد والجمهور غير صحيح.
"audience": "anotherCompany.com",
- إنشاء عبء عمل USleep جديد
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
./create_usleep_workload.sh
- إنشاء مثيل جهاز USleep افتراضي جديد وتشغيل عبء العمل
gcloud compute instances create \
--confidential-compute-type=SEV \
--shielded-secure-boot \
--maintenance-policy=MIGRATE \
--scopes=cloud-platform --zone=${USLEEP_PROJECT_ZONE} \
--image-project=confidential-space-images \
--image-family=confidential-space \
--service-account=${USLEEP_WORKLOAD_SERVICE_ACCOUNT}@${USLEEP_PROJECT_ID}.iam.gserviceaccount.com \
--metadata ^~^tee-image-reference=${USLEEP_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${USLEEP_PROJECT_ID}/${USLEEP_ARTIFACT_REPOSITORY}/${USLEEP_WORKLOAD_IMAGE_NAME}:${USLEEP_WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-container-log-redirect=true usleep
- استخراج عنوان IP الجديد للزيارات الواردة الخارجية في USleep لاستخدامه لاحقًا
export USLEEP_EXTERNAL_IP=<add your external IP>
إعادة تشغيل حمل العمل
- حذف مثيل الجهاز الافتراضي UWear
gcloud config set project $UWEAR_PROJECT_ID
gcloud compute instances delete uwear --zone $UWEAR_PROJECT_ZONE
- إعادة إنشاء مثيل الجهاز الافتراضي UWear باستخدام عنوان IP الخارجي الجديد
gcloud compute instances create \
--confidential-compute-type=SEV \
--shielded-secure-boot \
--maintenance-policy=MIGRATE \
--scopes=cloud-platform --zone=${UWEAR_PROJECT_ZONE} \
--image-project=confidential-space-images \
--image-family=confidential-space \
--service-account=${UWEAR_WORKLOAD_SERVICE_ACCOUNT}@${UWEAR_PROJECT_ID}.iam.gserviceaccount.com \
--metadata ^~^tee-image-reference=${UWEAR_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${UWEAR_PROJECT_ID}/${UWEAR_ARTIFACT_REPOSITORY}/${UWEAR_WORKLOAD_IMAGE_NAME}:${UWEAR_WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-container-log-redirect=true~tee-env-remote_ip_addr=$USLEEP_EXTERNAL_IP uwear
- في سجلات UWear التسلسلية، يجب أن تظهر الرسالة التالية ويجب ألا تتلقّى الآلة الافتراضية USleep أي بيانات حساسة
OPA policy result values:
[ nonce_verified ]: true
[ issuer_verified ]: true
[ secboot_verified ]: true
[ sw_name_verified ]: true
[ allow ]: false
[ hw_verified ]: true
[ image__digest_verified ]: false
[ audience_verified ]: false
Policy check FAILED
Remote TEE's JWT failed policy check.
Failed to validate claims against OPA policy: remote TEE's JWT failed policy check
9- تنظيف
يمكن استخدام برنامج التنظيف النصي لتنظيف الموارد التي أنشأناها كجزء من هذا الدرس التطبيقي حول الترميز. وكجزء من عملية التنظيف هذه، سيتم حذف الموارد التالية:
- حساب خدمة UWear (
$UWEAR_SERVICE_ACCOUNT) - سجلّ عناصر UWear (
$UWEAR_ARTIFACT_REPOSITORY) - مثيل Compute في UWear
- حساب خدمة USleep (
$USLEEP_SERVICE_ACCOUNT) - مسجّل بيانات USleep (
$USLEEP_ARTIFACT_REPOSITORY) - The USleep Compute Instance
./cleanup.sh
إذا انتهيت من استكشاف المشروع، يُرجى حذف مشروعك باتّباع هذه التعليمات.
تهانينا
تهانينا، لقد أكملت درس البرمجة بنجاح.
تعرّفت على كيفية مشاركة البيانات بأمان مع الحفاظ على سريتها باستخدام Confidential Space.
ما هي الخطوات التالية؟
يمكنك الاطّلاع على بعض دروس البرمجة المشابهة هذه...
- تأمين نماذج تعلُّم الآلة والملكية الفكرية باستخدام Confidential Space
- كيفية إجراء معاملات الأصول الرقمية باستخدام الحوسبة المتعددة الأطراف والمساحات السرية
- تحليل البيانات السرية باستخدام "المساحات السرية"