از فضای محرمانه با منابع محافظت شده استفاده کنید که با ارائه‌دهنده ابر ذخیره نمی‌شوند، از فضای محرمانه با منابع محافظت‌شده استفاده کنید که با ارائه‌دهنده ابر ذخیره نشده‌اند.

۱. مرور کلی

فضای محرمانه، اشتراک‌گذاری و همکاری چندجانبه و امن داده‌ها را ارائه می‌دهد، در حالی که به سازمان‌ها اجازه می‌دهد محرمانگی داده‌های خود را حفظ کنند. این بدان معناست که سازمان‌ها می‌توانند با یکدیگر همکاری کنند و در عین حال کنترل داده‌های خود را حفظ کرده و از دسترسی غیرمجاز به آنها جلوگیری کنند.

فضای محرمانه (Confidential Space) سناریوهایی را ارائه می‌دهد که در آن‌ها می‌خواهید از جمع‌آوری و تجزیه و تحلیل داده‌های حساس و اغلب تنظیم‌شده، ضمن حفظ کنترل کامل بر آن‌ها، ارزش متقابل کسب کنید. با فضای محرمانه، سازمان‌ها می‌توانند از جمع‌آوری و تجزیه و تحلیل داده‌های حساس مانند اطلاعات شخصی قابل شناسایی (PII)، اطلاعات سلامت محافظت‌شده (PHI)، مالکیت معنوی و اسرار رمزنگاری‌شده، ضمن حفظ کنترل کامل بر آن‌ها، ارزش متقابل کسب کنند.

آنچه نیاز دارید

آنچه یاد خواهید گرفت

  • نحوه پیکربندی منابع ابری لازم برای اجرای فضای محرمانه
  • نحوه اجرای یک بار کاری در یک ماشین مجازی محرمانه که تصویر فضای محرمانه را اجرا می‌کند
  • نحوه‌ی اعطای مجوز دسترسی به منابع محافظت‌شده بر اساس ویژگی‌های کد بار کاری ( چه چیزی )، محیط فضای محرمانه ( کجا ) و حسابی که بار کاری را اجرا می‌کند ( چه کسی ).

این آزمایشگاه کد بر نحوه استفاده از فضای محرمانه با منابع محافظت‌شده‌ای که در جایی غیر از Google Cloud میزبانی می‌شوند ، تمرکز دارد. شما یاد خواهید گرفت که چگونه با ارائه یک nonce، مخاطب و نوع توکن PKI، یک توکن سفارشی و مستقل از سرویس Google Attestation درخواست کنید.

در این آزمایشگاه کد، شما یک فضای محرمانه بین یک محصول فرضی - USleep، یک برنامه کانتینری، و یک محصول فرضی - UWear، یک دستگاه پوشیدنی متصل، برای محاسبه کیفیت خواب خود ایجاد خواهید کرد. UWear اطلاعات سلامت محافظت‌شده (PHI) را با USleep در یک محیط امن، ایمن و ایزوله (معروف به محیط اجرای مطمئن یا TEE) به اشتراک می‌گذارد، به طوری که صاحبان داده‌ها محرمانگی کامل را حفظ کنند.

UWear هم حسابرس بار کاری و هم مالک داده‌ها است. به عنوان حسابرس بار کاری، کد موجود در بار کاری که در حال اجرا است را بررسی می‌کند و خلاصه تصویر را یادداشت می‌کند. به عنوان مالک داده ، UWear منطق تأیید را برای بررسی اعتبار توکن و امضای آن می‌نویسد. با استفاده از خلاصه تصویر بارهای کاری حسابرسی شده، یک سیاست اعتبارسنجی می‌نویسد که فقط به خلاصه تصویر خاص، در یک محیط خاص، اجازه دسترسی به داده‌های حساس را می‌دهد.

USleep، در این آزمایشگاه کد، در حال پیاده‌سازی برنامه‌ی کانتینری است. USleep به داده‌های حساس دسترسی ندارد، اما بار کاری تأیید شده‌ای را اجرا می‌کند که اجازه‌ی دسترسی به داده‌های حساس را دارد.

آزمایشگاه کد شامل مراحل زیر است:

  • مرحله ۱: منابع ابری لازم برای codelab را تنظیم کنید. پروژه‌ها، صورتحساب و مجوزها را تنظیم کنید. کد منبع codelab را دانلود کنید و متغیرهای محیطی را تنظیم کنید.
  • مرحله ۲: گواهی ریشه را دانلود کنید و آن را در کنار کد منبع UWear خود ذخیره کنید.
  • مرحله ۳: حساب‌های سرویس بار کاری جداگانه‌ای ایجاد کنید که توسط ماشین مجازی بار کاری برای USleep و UWear استفاده خواهند شد.
  • مرحله ۴: ایجاد بار کاری USleep که یک توکن گواهی ارائه می‌دهد.
  • مرحله ۵: ایجاد بار کاری UWear که توکن گواهی را اعتبارسنجی می‌کند و در صورت تأیید توکن، داده‌های حساس را ارسال می‌کند.
  • مرحله ۶: بارهای کاری USleep و UWear را اجرا کنید. UWear داده‌های حساس را ارائه می‌دهد و USleep یک الگوریتم sleep را روی داده‌ها اجرا کرده و نتیجه را به عنوان خروجی ارائه می‌دهد.
  • مرحله ۷: (اختیاری) یک بار کاری غیرمجاز USleep اجرا کنید و تأیید کنید که داده‌های حساس از UWear دریافت نشده است.
  • مرحله ۸: تمام منابع را پاکسازی کنید.

آشنایی با گردش کار

USleep بار کاری را در فضای محرمانه اجرا خواهد کرد. برای اجرای بار کاری، به دسترسی به PHI UWear نیاز دارد. برای دسترسی، بار کاری USleep ابتدا یک جلسه TLS امن ایجاد می‌کند. سپس USleep یک توکن گواهی از سرویس گواهی گوگل به همراه یک payload درخواست می‌کند.

USleep یک توکن گواهی با یک فایل JSON درخواست می‌کند که شامل سه چیز است:

  1. یک توکن گواهی که به جلسه TLS متصل است . برای اتصال توکن گواهی به جلسه TLS، مقدار nonce، هشِ TLS Exported Keying Material خواهد بود. اتصال توکن به جلسه TLS تضمین می‌کند که هیچ حمله ماشینی در میان رخ نمی‌دهد، زیرا فقط دو طرف درگیر در جلسه TLS قادر به تولید مقدار nonce خواهند بود.
  2. مخاطبی از «uwear» ارائه خواهد شد. UWear تأیید می‌کند که این مخاطب، مخاطب مورد نظر برای توکن گواهی است.
  3. یک توکن از نوع "PKI". یک توکن از نوع "PKI" به این معنی است که USleep می‌خواهد یک توکن مستقل درخواست کند. توکن مستقل را می‌توان با استفاده از ریشه دانلود شده از نقطه پایانی PKI شناخته شده Confidential Space تأیید کرد که توسط گوگل امضا شده است. این برخلاف نوع توکن پیش‌فرض OIDC است که امضای آن با استفاده از یک کلید عمومی که به طور منظم می‌چرخد ​​تأیید می‌شود.

bb013916a3222ce7.png

بار کاری USleep توکن گواهی را دریافت می‌کند. سپس UWear به اتصال TLS با USleep متصل می‌شود و توکن گواهی USleep را بازیابی می‌کند. UWear با بررسی ادعای x5c در برابر گواهی ریشه، توکن را اعتبارسنجی می‌کند.

UWear در صورت وجود شرایط زیر، حجم کاری USleep را تأیید خواهد کرد:

  1. توکن از منطق اعتبارسنجی PKI عبور می‌کند.
  2. UWear با بررسی ادعای x5c در برابر گواهی ریشه، بررسی امضای توکن توسط گواهی برگ و در نهایت اینکه گواهی ریشه دانلود شده همان ریشه‌ای است که در ادعای x5c وجود دارد، توکن را اعتبارسنجی می‌کند.
  3. ادعاهای اندازه‌گیری بار کاری در توکن با شرایط ویژگی مشخص شده در سیاست OPA مطابقت دارند. OPA یک موتور سیاست متن‌باز و همه منظوره است که اجرای سیاست را در سراسر پشته یکپارچه می‌کند. OPA از اسنادی با سینتکس مشابه JSON برای تعیین مقادیر پایه‌ای که سیاست با آنها اعتبارسنجی می‌شود، استفاده می‌کند. برای مثالی از اینکه سیاست چه مقادیری را بررسی می‌کند، به مقادیر پایه OPA مراجعه کنید.
  4. این nonce با nonce مورد انتظار (TLS Exported Keying Material ) مطابقت دارد. این موضوع در سیاست OPA فوق تأیید شده است.

پس از تکمیل و موفقیت‌آمیز بودن تمام این بررسی‌ها، UWear می‌تواند تأیید کند که داده‌ها به صورت ایمن ارسال و پردازش می‌شوند. سپس UWear در همان جلسه TLS با اطلاعات حساس PHI پاسخ می‌دهد و USleep می‌تواند از آن داده‌ها برای محاسبه کیفیت خواب مشتری استفاده کند.

۲. منابع ابری را تنظیم کنید

قبل از اینکه شروع کنی

  1. دو پروژه Google Cloud، یکی برای USleep و یکی برای UWear، راه‌اندازی کنید. برای اطلاعات بیشتر در مورد ایجاد یک پروژه Google Cloud، لطفاً به آزمایشگاه کد «تنظیم و پیمایش اولین پروژه Google خود» مراجعه کنید. می‌توانید برای کسب اطلاعات بیشتر در مورد نحوه بازیابی شناسه پروژه و تفاوت آن با نام پروژه و شماره پروژه، به بخش ایجاد و مدیریت پروژه‌ها مراجعه کنید.
  2. فعال کردن صورتحساب برای پروژه‌هایتان
  3. در یکی از پوسته‌های ابری (Cloud Shell) پروژه گوگل خود، متغیرهای محیطی مورد نیاز پروژه را مطابق شکل زیر تنظیم کنید.
export UWEAR_PROJECT_ID=<Google Cloud project id of UWear>
export USLEEP_PROJECT_ID=<Google Cloud project id of USleep>
  1. API محاسبات محرمانه و 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
  1. شناسه اصلی خود را با استفاده از
gcloud auth list

# Output should contain
# ACCOUNT: <Principal Identifier>

# Set your member variable
export MEMBER='user:<Principal Identifier>'
  1. مجوزهای این دو پروژه را اضافه کنید. مجوزها را می‌توان با دنبال کردن جزئیات موجود در صفحه وب اعطای نقش IAM اضافه کرد.
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'
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'
  1. در یکی از پروژه‌های گوگل کلود خود، یعنی Cloud Shell، با استفاده از دستور زیر، مخزن گیت‌هاب Codelab فضای محرمانه (Confidential Space Codelab Github Repository) را کلون کنید تا اسکریپت‌های مورد نیاز که به عنوان بخشی از این آزمایشگاه کد استفاده می‌شوند را دریافت کنید.
git clone https://github.com/GoogleCloudPlatform/confidential-space.git
  1. دایرکتوری را به دایرکتوری اسکریپت‌ها برای codelab داده‌های سلامت تغییر دهید.
cd confidential-space/codelabs/health_data_analysis_codelab/scripts
  1. این دو خط را در اسکریپت 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
  1. اختیاری: هر متغیر از پیش موجود را تنظیم کنید. می‌توانید نام منابع را با استفاده از این متغیرها لغو کنید (مثلاً export UWEAR_ARTIFACT_REPOSITORY='my-artifact-repository' )
  • You can set the following variables with existing cloud resource names. If the variable is set, then the corresponding existing cloud resource from the project would be used. If the variable is not set, cloud resource name would be generated from the values in the config_env.sh script.
  1. اسکریپت config_env.sh را اجرا کنید تا نام متغیرهای باقی‌مانده بر اساس شناسه پروژه شما برای نام منابع، به مقادیری تنظیم شود.
# 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

۳. گواهی ریشه را دانلود کنید

  1. برای اعتبارسنجی توکن مستقل برگردانده شده از سرویس گواهی، 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
  1. اثر انگشت گواهی ریشه‌ای که دانلود شده است را ایجاد کنید
openssl x509 -fingerprint -in confidential_space_root.pem -noout
  1. تأیید کنید که اثر انگشت با خلاصه SHA-1 زیر مطابقت دارد:
B9:51:20:74:2C:24:E3:AA:34:04:2E:1C:3B:A3:AA:D2:8B:21:23:21

۴. ایجاد حساب کاربری سرویس حجم کار

اکنون، شما دو حساب سرویس ایجاد خواهید کرد؛ یکی برای 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 نیز بنویسد، بنابراین گزارش‌ها پس از خاتمه ماشین مجازی در دسترس هستند. ایجاد بار کاری

۵. ایجاد حجم کاری USleep

به عنوان بخشی از این مرحله، شما تصاویر داکر را برای بارهای کاری مورد استفاده در این آزمایشگاه کد ایجاد خواهید کرد. بار کاری USleep یک برنامه ساده Golang است که کیفیت خواب مشتری را با استفاده از اطلاعات سلامت شخصی در یک دستگاه پوشیدنی تعیین می‌کند.

درباره حجم کار USleep

حجم کاری USleep یک برنامه ساده Golang است که کیفیت خواب مشتری را با استفاده از اطلاعات سلامت شخصی روی یک دستگاه پوشیدنی تعیین می‌کند. حجم کاری USleep سه بخش اصلی دارد:

  1. راه‌اندازی یک جلسه TLS و استخراج محتوای کلیدگذاری صادر شده
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
}
  1. درخواست یک توکن از سرویس گواهی‌دهی با نوع مخاطب، نانس و توکن 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
}
  1. دریافت داده‌های حساس و محاسبه کیفیت خواب کاربر
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

  1. اسکریپت create_usleep_workload.sh را برای ایجاد بار کاری USleep اجرا کنید. این اسکریپت:
  • یک رجیستری مصنوعات ( $USLEEP_ARTIFACT_REPOSITORY ‎) متعلق به UWear ایجاد می‌کند که در آن بار کاری منتشر می‌شود.
  • کد usleep/workload.go را می‌سازد و آن را در یک تصویر Docker بسته‌بندی می‌کند. به پیکربندی Dockerfile برای USleep مراجعه کنید.
  • ایمیج داکر را در رجیستری مصنوعات ( $USLEEP_ARTIFACT_REPOSITORY ‎) که متعلق به UWear است، منتشر می‌کند.
  • به حساب سرویس $USLEEP_WORKLOAD_SERVICE_ACCOUNT مجوز خواندن برای رجیستری مصنوعات ( $USLEEP_ARTIFACT_REPOSITORY ) اعطا می‌کند.
./create_usleep_workload.sh
  1. مهم: در گزارش‌های خروجی، خلاصه تصویر مربوط به USleep را استخراج کنید.
latest: digest: sha256:<USLEEP_IMAGE_DIGEST> size: 945
  1. به فهرست UWear بروید
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/uwear
  1. مقدار زیر «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

۶. ایجاد حجم کاری UWear

درباره حجم کار UWear

حجم کاری UWear چهار بخش اصلی دارد:

  1. پیوستن به همان جلسه 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)

  ...
}
  1. اعتبارسنجی توکن خود-موجود توسط:
  • بررسی ادعای 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
}
  1. سپس بار کاری 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
}
{
  "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
"

کد نمونه برای دریافت هش 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
}
  1. پس از تکمیل و موفقیت‌آمیز بودن تمام این بررسی‌ها، UWear می‌تواند تأیید کند که داده‌ها به صورت ایمن ارسال و پردازش می‌شوند. سپس UWear در همان جلسه TLS با اطلاعات حساس PHI پاسخ می‌دهد و 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

  1. به دایرکتوری اسکریپت‌ها بروید
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
  1. اسکریپت create_uwear_workload.sh را برای ایجاد بار کاری UWear اجرا کنید:
  • یک رجیستری مصنوعات ( $UWEAR_ARTIFACT_REPOSITORY ‎) متعلق به UWear ایجاد می‌کند که در آن بار کاری منتشر می‌شود.
  • کد uwear/workload.go را می‌سازد و آن را در یک تصویر Docker بسته‌بندی می‌کند. به پیکربندی Dockerfile برای USleep مراجعه کنید.
  • ایمیج داکر را در رجیستری مصنوعات ( $UWEAR_ARTIFACT_REPOSITORY ‎) که متعلق به UWear است، منتشر می‌کند.
  • به حساب سرویس $UWEAR_WORKLOAD_SERVICE_ACCOUNT مجوز خواندن برای رجیستری مصنوعات ( $UWEAR_ARTIFACT_REPOSITORY ) اعطا می‌کند.
./create_uwear_workload.sh

۷. بارهای کاری 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

آی‌پی خارجی را در یک متغیر ذخیره کنید

export USLEEP_EXTERNAL_IP=<add your external IP> 

تأیید کنید که USleep Workload به درستی اجرا شده است

برای تأیید صحت اجرای بار کاری USleep، به صفحه VM Instances در پروژه USleep بروید. روی نمونه "usleep" کلیک کنید و در بخش Logs، گزینه "Serial port 1(console)" را فشار دهید. پس از راه‌اندازی و اجرای سرور، در پایین گزارش‌ها، گزارش‌ها باید چیزی شبیه به موارد زیر را نشان دهند.

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، به صفحه‌ی VM Instances در پروژه‌ی UWear بروید. روی نمونه‌ی "uwear" کلیک کنید و در بخش گزارش‌ها، گزینه‌ی "Serial port 1(console)" را انتخاب کنید.

خروجی لاگ پس از راه‌اندازی کامل نمونه باید به این شکل باشد

در پروژه 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 را مشاهده کنید

برای مشاهده نتایج، به صفحه VM Instances در پروژه USleep برگردید. روی نمونه "usleep" کلیک کنید و در بخش Logs، گزینه "Serial port 1(console)" را انتخاب کنید. نتایج حجم کار را در پایین Logs مشاهده کنید. آنها باید مشابه نمونه زیر باشند.

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 برای اشتراک‌گذاری اطلاعات حساس ایجاد کردید!

۸. (اختیاری) اجرای حجم کار غیرمجاز

در سناریوی بعدی، USleep کد را به‌روزرسانی می‌کند و بار کاری متفاوتی را روی داده‌های خواب ارائه شده توسط UWear اجرا می‌کند. UWear با این بار کاری جدید موافقت نکرده و سیاست OPA خود را برای اجازه دادن به هضم تصویر جدید به‌روزرسانی نکرده است. ما تأیید خواهیم کرد که UWear داده‌های حساس خود را به بار کاری غیرمجاز ارسال نمی‌کند.

USleep حجم کاری آنها را تغییر می‌دهد

  1. پروژه را روی $USLEEP_PROJECT_ID تنظیم کنید.
gcloud config set project $USLEEP_PROJECT_ID
  1. نمونه ماشین مجازی USleep را حذف کنید.
gcloud compute instances delete usleep --zone $USLEEP_PROJECT_ZONE
  1. به دایرکتوری usleep/workload.go بروید.
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/usleep
  1. در فایل usleep/workload.go ، خط "audience": "uwear". در این مثال، برای تغییر خلاصه تصویر، مقدار audience را به مقداری متفاوت که UWear تایید نکرده است، به‌روزرسانی می‌کنیم. بنابراین UWear باید آن را به دو دلیل رد کند - خلاصه تصویر تایید نشده و مخاطب نادرست.
"audience": "anotherCompany.com",
  1. ایجاد بار کاری جدید USleep
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts

./create_usleep_workload.sh
  1. نمونه جدید ماشین مجازی 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
  1. IP خارجی جدید USleep را برای استفاده‌های بعدی استخراج کنید
export USLEEP_EXTERNAL_IP=<add your external IP>

حجم کار را دوباره اجرا کنید

  1. نمونه ماشین مجازی UWear را حذف کنید
gcloud config set project $UWEAR_PROJECT_ID

gcloud compute instances delete uwear --zone $UWEAR_PROJECT_ZONE
  1. نمونه ماشین مجازی 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
  1. در گزارش‌های سریال 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

۹. تمیز کردن

اسکریپت پاکسازی می‌تواند برای پاکسازی منابعی که به عنوان بخشی از این آزمایشگاه کد ایجاد کرده‌ایم، مورد استفاده قرار گیرد. به عنوان بخشی از این پاکسازی، منابع زیر حذف خواهند شد:

  • حساب سرویس UWear ( $UWEAR_SERVICE_ACCOUNT ).
  • رجیستری مصنوعات UWear ( $UWEAR_ARTIFACT_REPOSITORY ).
  • نمونه محاسباتی UWear
  • حساب سرویس USleep ( $USLEEP_SERVICE_ACCOUNT ).
  • رجیستری مصنوعات USleep ( $USLEEP_ARTIFACT_REPOSITORY ).
  • نمونه محاسباتی USleep
./cleanup.sh

اگر کاوش شما تمام شد، لطفاً با دنبال کردن این دستورالعمل‌ها، پروژه خود را حذف کنید.

تبریک می‌گویم

تبریک می‌گویم، شما با موفقیت آزمایشگاه کد را به پایان رساندید!

شما یاد گرفتید که چگونه با استفاده از Confidential Space، داده‌ها را به صورت ایمن به اشتراک بگذارید و در عین حال محرمانگی آنها را حفظ کنید.

بعدش چی؟

برخی از این آزمایشگاه‌های کد مشابه را بررسی کنید...

مطالعه بیشتر