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

1. بررسی اجمالی

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

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

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

چیزی که یاد خواهید گرفت

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

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

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

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

USleep، در این کد لبه، در حال استقرار برنامه کانتینری شده است. USleep به داده های حساس دسترسی ندارد اما حجم کاری تایید شده را اجرا می کند که اجازه دسترسی به داده های حساس را دارد.

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

  • مرحله 1: منابع ابری لازم را برای Codelab تنظیم کنید. پروژه ها، صورتحساب و مجوزها را تنظیم کنید. کد منبع کد لبه را دانلود کنید و متغیرهای محیط را تنظیم کنید.
  • مرحله 2: گواهی ریشه را دانلود کنید و آن را با کد منبع UWear خود ذخیره کنید.
  • مرحله 3: حساب های خدمات بار کاری جداگانه ایجاد کنید که توسط VM حجم کاری برای USleep و UWear استفاده می شود.
  • مرحله 4: بار کاری USleep را ایجاد کنید که یک نشانه گواهی ارائه می دهد.
  • مرحله 5: بار کاری UWear را ایجاد کنید که توکن تأیید اعتبار را تأیید می کند و در صورت تأیید توکن، داده های حساس را ارسال می کند.
  • مرحله 6: بارهای کاری USleep و UWear را اجرا کنید. UWear داده‌های حساس را ارائه می‌کند و USleep یک الگوریتم خواب را روی داده‌ها اجرا می‌کند و نتیجه را به دست می‌آورد.
  • مرحله 7: (اختیاری) بار کاری USleep غیرمجاز را اجرا کنید و تأیید کنید که داده های حساس از UWear دریافت نشده اند.
  • مرحله 8: تمام منابع را پاکسازی کنید.

درک گردش کار

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

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

  1. یک نشانه تأیید که به جلسه TLS محدود شده است . به منظور اتصال نشانه گواهی به جلسه TLS، مقدار nonce هش ماده کلیدی صادر شده TLS خواهد بود. اتصال توکن به جلسه TLS تضمین می کند که هیچ حمله ماشینی در وسط رخ نمی دهد زیرا فقط دو طرف درگیر در جلسه TLS می توانند مقدار nonce را ایجاد کنند.
  2. مخاطبان "uwear" ارائه خواهد شد. UWear بررسی خواهد کرد که مخاطب مورد نظر برای توکن گواهی است.
  3. یک نوع رمز از "PKI". نوع توکن "PKI" به این معنی است که USleep مایل است یک توکن خود را درخواست کند. با استفاده از ریشه بارگیری شده از نقطه پایانی شناخته شده PKI Confidential Space ، می توان تأیید کرد که رمز خود حاوی امضا شده توسط Google است. این برخلاف نوع توکن پیش‌فرض 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 می‌تواند از آن داده‌ها برای محاسبه کیفیت خواب مشتری استفاده کند.

2. راه اندازی منابع ابری

قبل از شروع

  1. دو پروژه Google Cloud را راه‌اندازی کنید، یکی برای USleep و دیگری برای UWear. برای کسب اطلاعات بیشتر در مورد ایجاد یک پروژه Google Cloud، لطفاً به لبه کد "تنظیم و پیمایش اولین پروژه Google خود" مراجعه کنید. برای دریافت جزئیات در مورد نحوه بازیابی شناسه پروژه و تفاوت آن با نام پروژه و شماره پروژه می توانید به ایجاد و مدیریت پروژه ها مراجعه کنید.
  2. صورتحساب برای پروژه های خود را فعال کنید .
  3. در یکی از Cloud Shell پروژه Google خود، متغیرهای محیط پروژه مورد نیاز را مطابق شکل زیر تنظیم کنید.
export UWEAR_PROJECT_ID=<Google Cloud project id of UWear>
export USLEEP_PROJECT_ID=<Google Cloud project id of USleep>
  1. Confidential Computing 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. در یکی از پروژه‌های Google Cloud خود در Cloud Shell، مخزن فضای محرمانه Codelab Github را با استفاده از دستور زیر شبیه‌سازی کنید تا اسکریپت‌های مورد نیاز را که به‌عنوان بخشی از این Codelab استفاده می‌شود، دریافت کنید.
git clone https://github.com/GoogleCloudPlatform/confidential-space.git
  1. دایرکتوری را به دایرکتوری اسکریپت ها برای آزمایشگاه داده سلامت تغییر دهید.
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' )
  • می توانید متغیرهای زیر را با نام منابع ابری موجود تنظیم کنید. اگر متغیر تنظیم شده باشد، منبع ابری مربوطه از پروژه استفاده خواهد شد. اگر متغیر تنظیم نشده باشد، نام منبع ابری از مقادیر موجود در اسکریپت config_env.sh ایجاد می شود.
  1. اسکریپت config_env.sh را اجرا کنید تا نام متغیرهای باقیمانده را بر اساس ID پروژه خود برای نام منابع تنظیم کنید.
# 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. گواهی ریشه را دانلود کنید

  1. برای تأیید اعتبار رمز خود حاوی بازگردانده شده از سرویس گواهی، UWear باید امضا را در برابر گواهی ریشه فضای محرمانه تأیید کند. 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

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

5. USleep Workload را ایجاد کنید

به عنوان بخشی از این مرحله، تصاویر Docker را برای بارهای کاری مورد استفاده در این Codelab ایجاد خواهید کرد. بار کاری 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. درخواست توکن از سرویس گواهی با مخاطب، nonce و نوع توکن 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. برای ایجاد بار کاری USleep اسکریپت create_usleep_workload.sh را اجرا کنید. این اسکریپت:
  • رجیستری مصنوع ( $USLEEP_ARTIFACT_REPOSITORY ) متعلق به UWear را ایجاد می کند که در آن حجم کار منتشر می شود.
  • کد usleep/workload.go را می سازد و آن را در یک تصویر داکر بسته بندی می کند. به پیکربندی Dockerfile برای USleep مراجعه کنید.
  • تصویر Docker را در رجیستری مصنوع ( $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

6. UWear Workload را ایجاد کنید

درباره بار کاری UWear

حجم کاری UWear دارای 4 قطعه اصلی است:

  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 := decodeAndParseCertificate(string(rawRootCertificate))
  if err != nil {
    return jwt.Token{}, fmt.Errorf("DecodeAndParseCertificate(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"
  ]
}
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 Query.
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 مراجعه کنید.
  • تصویر Docker را در رجیستری Artifact ( $UWEAR_ARTIFACT_REPOSITORY ) متعلق به UWear منتشر می کند.
  • به حساب سرویس $UWEAR_WORKLOAD_SERVICE_ACCOUNT مجوز خواندن برای رجیستری مصنوع ( $UWEAR_ARTIFACT_REPOSITORY ) می دهد.
./create_uwear_workload.sh

7. USleep و UWear Workloads را اجرا کنید

بار کاری USleep را اجرا کنید

gcloud config set project $USLEEP_PROJECT_ID


gcloud compute instances create \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --maintenance-policy=MIGRATE \
 --min-cpu-platform="AMD Milan" \
 --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

پاسخ باید وضعیتی را برگرداند: 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 به درستی اجرا می شود، به صفحه نمونه های VM در پروژه USleep بروید. روی نمونه "usleep" کلیک کنید و "Serial port 1 (console)" را در قسمت Logs فشار دهید. هنگامی که سرور راه‌اندازی می‌شود، در پایین گزارش‌ها، گزارش‌ها باید چیزی شبیه به زیر نشان دهند.

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 \
 --min-cpu-platform="AMD Milan" \
 --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 در پروژه UWear بروید. روی نمونه "uwear" کلیک کنید و "Serial port 1 (console)" را در قسمت Logs فشار دهید.

خروجی ورود به سیستم زمانی که نمونه به طور کامل راه اندازی شد باید به این شکل باشد

در پروژه 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 در پروژه USleep برگردید. روی نمونه "usleep" کلیک کنید و "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 ایجاد کردید تا اطلاعات حساس را به اشتراک بگذارید!

8. (اختیاری) بار کاری غیرمجاز را اجرا کنید

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

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

  1. پروژه را روی $USLEEP_PROJECT_ID تنظیم کنید.
gcloud config set project $USLEEP_PROJECT_ID
  1. نمونه USleep VM را حذف کنید.
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". در این مثال، برای تغییر خلاصه تصویر، مخاطب را به مقدار متفاوتی که UWear تایید نکرده است، به روز می کنیم. بنابراین UWear باید آن را به دو دلیل رد کند - خلاصه تصویر تایید نشده و مخاطب نادرست.
"audience": "anotherCompany.com",
  1. بار کاری USleep جدید ایجاد کنید
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts

./create_usleep_workload.sh
  1. نمونه جدید USleep VM را ایجاد کنید و حجم کار را اجرا کنید
gcloud compute instances create \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --maintenance-policy=MIGRATE \
 --min-cpu-platform="AMD Milan" \
 --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 VM را حذف کنید
gcloud config set project $UWEAR_PROJECT_ID

gcloud compute instances delete uwear --zone $UWEAR_PROJECT_ZONE
  1. نمونه UWear VM را با استفاده از IP خارجی جدید دوباره ایجاد کنید
gcloud compute instances create \
 --confidential-compute-type=SEV \
 --shielded-secure-boot \
 --maintenance-policy=MIGRATE \
 --min-cpu-platform="AMD Milan" \
 --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 VM نباید هیچ داده حساسی را دریافت کند.
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 ).
  • UWear Compute Instance
  • حساب سرویس USleep ( $USLEEP_SERVICE_ACCOUNT ).
  • رجیستری مصنوع USleep ( $USLEEP_ARTIFACT_REPOSITORY ).
  • مثال USleep Compute
./cleanup.sh

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

تبریک میگم

تبریک می‌گوییم، شما با موفقیت برنامه کد را تکمیل کردید!

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

بعدش چی؟

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

در ادامه مطلب