Sử dụng Không gian bảo mật với các tài nguyên được bảo vệ không được lưu trữ bằng nhà cung cấp dịch vụ đám mây

1. Tổng quan

Không gian bảo mật cung cấp tính năng chia sẻ và cộng tác dữ liệu an toàn giữa nhiều bên, đồng thời cho phép các tổ chức bảo mật dữ liệu của họ. Điều này có nghĩa là các tổ chức có thể cộng tác với nhau trong khi vẫn duy trì quyền kiểm soát đối với dữ liệu của mình và bảo vệ dữ liệu khỏi bị truy cập trái phép.

Không gian bảo mật mở ra những tình huống mà bạn muốn đạt được giá trị tương hỗ từ việc tổng hợp và phân tích dữ liệu nhạy cảm, thường được quản lý, đồng thời vẫn giữ toàn quyền kiểm soát dữ liệu đó. Với Không gian bảo mật, các tổ chức có thể đạt được giá trị chung từ việc tổng hợp và phân tích dữ liệu nhạy cảm như thông tin nhận dạng cá nhân (PII), thông tin sức khoẻ được bảo vệ (PHI), tài sản trí tuệ và bí mật mã hoá, đồng thời vẫn giữ toàn quyền kiểm soát dữ liệu đó.

Bạn cần có

Kiến thức bạn sẽ học được

  • Cách định cấu hình các tài nguyên cần thiết trên đám mây để chạy Không gian bảo mật
  • Cách chạy một khối lượng công việc trong máy ảo bảo mật chạy hình ảnh Không gian bảo mật
  • Cách uỷ quyền truy cập vào các tài nguyên được bảo vệ dựa trên các thuộc tính của mã khối lượng công việc (cái gì), môi trường Không gian bảo mật (ở đâu) và tài khoản đang chạy khối lượng công việc (ai).

Lớp học lập trình này tập trung vào cách sử dụng Không gian bảo mật với các tài nguyên được bảo vệ được lưu trữ ở nơi khác ngoài Google Cloud. Bạn sẽ tìm hiểu cách yêu cầu mã thông báo tuỳ chỉnh, độc lập từ Dịch vụ chứng thực của Google bằng cách cung cấp số chỉ dùng một lần, đối tượng và loại mã thông báo PKI.

Trong lớp học lập trình này, bạn sẽ thiết lập một Không gian bảo mật giữa một sản phẩm hư cấu – USleep, một ứng dụng được đóng gói trong vùng chứa và một sản phẩm hư cấu – UWear, một thiết bị đeo được kết nối để tính chất lượng giấc ngủ của bạn. UWear sẽ chia sẻ thông tin sức khoẻ được bảo vệ (PHI) với USleep trong một môi trường an toàn, bảo mật và tách biệt (còn gọi là Môi trường thực thi đáng tin cậy hoặc TEE) để chủ sở hữu dữ liệu giữ kín hoàn toàn.

UWear vừa là người kiểm tra mức tải vừa là chủ sở hữu dữ liệu. Là trình kiểm tra khối lượng công việc,công cụ này xem xét mã trong khối lượng công việc đang chạy và ghi lại thông tin tóm tắt hình ảnh. Là chủ sở hữu dữ liệu, UWear sẽ viết logic xác minh để kiểm tra tính hợp lệ của mã thông báo và chữ ký của mã thông báo đó. Công cụ này ghi một chính sách xác thực, sử dụng hàm băm hình ảnh của khối lượng công việc đã kiểm tra, chỉ cho phép hàm băm hình ảnh cụ thể, trong một môi trường cụ thể, truy cập vào dữ liệu nhạy cảm.

Trong lớp học lập trình này, USleep sẽ triển khai ứng dụng được đóng gói trong vùng chứa. USleep không có quyền truy cập vào dữ liệu nhạy cảm nhưng chạy khối lượng công việc đã phê duyệt được phép truy cập vào dữ liệu nhạy cảm.

Lớp học lập trình này bao gồm các bước sau:

  • Bước 1: Thiết lập các tài nguyên đám mây cần thiết cho lớp học lập trình. Thiết lập dự án, thông tin thanh toán và quyền. Tải mã nguồn của lớp học lập trình xuống và thiết lập các biến môi trường.
  • Bước 2: Tải chứng chỉ gốc xuống và lưu trữ chứng chỉ đó bằng mã nguồn UWear.
  • Bước 3: Tạo các tài khoản dịch vụ riêng cho khối lượng công việc mà máy ảo khối lượng công việc sẽ sử dụng cho USleep và UWear.
  • Bước 4: Tạo khối lượng công việc USleep cung cấp mã thông báo chứng thực.
  • Bước 5: Tạo khối lượng công việc UWear để xác thực mã thông báo chứng thực và gửi dữ liệu nhạy cảm nếu mã thông báo được phê duyệt.
  • Bước 6: Chạy khối lượng công việc USleep và UWear. UWear sẽ cung cấp dữ liệu nhạy cảm và USleep sẽ chạy thuật toán giấc ngủ trên dữ liệu đó và đưa ra kết quả.
  • Bước 7: (Không bắt buộc) Chạy một khối lượng công việc USleep trái phép và xác nhận rằng dữ liệu nhạy cảm chưa được nhận từ UWear.
  • Bước 8: Dọn dẹp tất cả tài nguyên.

Tìm hiểu quy trình làm việc

USleep sẽ chạy khối lượng công việc trong Không gian bảo mật. Để chạy khối lượng công việc, ứng dụng này cần có quyền truy cập vào PHI của UWear. Để có quyền truy cập, trước tiên, khối lượng công việc USleep sẽ tạo một phiên TLS an toàn. Sau đó, USleep cũng sẽ yêu cầu mã thông báo chứng thực từ Dịch vụ chứng thực của Google bằng trọng tải.

USleep sẽ yêu cầu mã thông báo chứng thực có tải trọng JSON chứa 3 phần:

  1. Mã thông báo chứng thực được liên kết với phiên TLS. Để liên kết mã thông báo chứng thực với phiên TLS, giá trị số chỉ dùng một lần sẽ là hàm băm của TLS Exported Keying Material (Chất liệu khoá đã xuất của TLS). Việc liên kết mã thông báo với phiên TLS đảm bảo rằng không có cuộc tấn công giả mạo nào xảy ra vì chỉ có hai bên tham gia phiên TLS mới có thể tạo giá trị số chỉ dùng một lần.
  2. Hệ thống sẽ cung cấp đối tượng "uwear". UWear sẽ xác minh rằng đó là đối tượng dự kiến cho mã xác thực.
  3. Loại mã thông báo "PKI". Loại mã thông báo "PKI" có nghĩa là USleep muốn yêu cầu một mã thông báo độc lập. Bạn có thể xác minh rằng mã thông báo nội tại do Google ký bằng cách sử dụng thư mục gốc được tải xuống từ điểm cuối PKI nổi tiếng của Không gian bảo mật. Điều này trái ngược với loại mã thông báo OIDC mặc định, trong đó chữ ký được xác minh bằng khoá công khai xoay vòng thường xuyên.

bb013916a3222ce7.png

Khối lượng công việc USleep nhận được mã thông báo chứng thực. Sau đó, UWear sẽ tham gia kết nối TLS với USleep và truy xuất mã thông báo chứng thực của USleep. UWear sẽ xác thực mã thông báo bằng cách kiểm tra yêu cầu x5c dựa trên chứng chỉ gốc.

UWear sẽ phê duyệt mức tải USleep nếu:

  1. Mã thông báo vượt qua logic xác thực PKI.
  2. UWear sẽ xác thực mã thông báo bằng cách kiểm tra thông báo xác nhận x5c với chứng chỉ gốc, kiểm tra mã thông báo do chứng chỉ lá ký và cuối cùng là chứng chỉ gốc đã tải xuống giống với chứng chỉ gốc trong thông báo xác nhận x5c.
  3. Các thông báo đo lường mức tải trong mã thông báo khớp với các điều kiện thuộc tính được chỉ định trong chính sách OPA. OPA là một công cụ chính sách nguồn mở, dùng cho nhiều mục đích, giúp hợp nhất việc thực thi chính sách trên ngăn xếp. OPA sử dụng các tài liệu có cú pháp tương tự như JSON để đặt các giá trị cơ sở mà chính sách được xác thực. Hãy xem Giá trị cơ sở OPA để biết ví dụ về những giá trị mà chính sách này kiểm tra.
  4. Số chỉ dùng một lần khớp với số chỉ dùng một lần dự kiến (Tài liệu khoá đã xuất TLS). Điều này được xác minh trong chính sách OPA ở trên.

Sau khi hoàn tất và vượt qua tất cả các bước kiểm tra đó, UWear có thể xác nhận rằng dữ liệu sẽ được gửi và xử lý một cách an toàn. Sau đó, UWear sẽ phản hồi bằng PHI nhạy cảm qua cùng một phiên TLS và USleep sẽ có thể sử dụng dữ liệu đó để tính toán chất lượng giấc ngủ của khách hàng.

2. Thiết lập tài nguyên trên đám mây

Trước khi bắt đầu

  1. Thiết lập hai dự án trên Google Cloud, một dự án cho USleep và một dự án cho UWear. Để biết thêm thông tin về cách tạo dự án trên Google Cloud, vui lòng tham khảo lớp học lập trình"Thiết lập và điều hướng dự án đầu tiên trên Google". Bạn có thể tham khảo phần tạo và quản lý dự án để biết thông tin chi tiết về cách truy xuất mã dự án và sự khác biệt giữa mã dự án với tên dự án và số dự án.
  2. Bật tính năng Thanh toán cho các dự án của bạn.
  3. Trong một trong các Cloud Shell của dự án trên Google, hãy đặt các biến môi trường dự án bắt buộc như minh hoạ bên dưới.
export UWEAR_PROJECT_ID=<Google Cloud project id of UWear>
export USLEEP_PROJECT_ID=<Google Cloud project id of USleep>
  1. Bật API điện toán bảo mật và các API sau cho cả hai dự án.
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. Truy xuất giá trị nhận dạng chính bằng
gcloud auth list

# Output should contain
# ACCOUNT: <Principal Identifier>

# Set your member variable
export MEMBER='user:<Principal Identifier>'
  1. Thêm quyền cho hai dự án này. Bạn có thể thêm quyền bằng cách làm theo thông tin chi tiết trên trang web cấp vai trò 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. Trong một trong các dự án Google Cloud Cloud Shell, hãy sao chép Kho lưu trữ GitHub của lớp học lập trình về không gian bảo mật bằng lệnh bên dưới để lấy các tập lệnh bắt buộc được sử dụng trong lớp học lập trình này.
git clone https://github.com/GoogleCloudPlatform/confidential-space.git
  1. Thay đổi thư mục thành thư mục tập lệnh cho lớp học lập trình về dữ liệu sức khoẻ.
cd confidential-space/codelabs/health_data_analysis_codelab/scripts
  1. Cập nhật 2 dòng này trong tập lệnh config_env.sh, nằm trong thư mục codelabs/health_data_analysis_codelab/scripts. Cập nhật mã dự án bằng mã dự án của bạn cho USleep và UWear. Hãy nhớ xoá ký hiệu nhận xét "#" ở đầu dòng.
# TODO: Populate UWear and USleep Project IDs
export UWEAR_PROJECT_ID=your-uwear-project-id
export USLEEP_PROJECT_ID=your-usleep-project-id
  1. Không bắt buộc: Đặt bất kỳ biến có sẵn nào. Bạn có thể ghi đè tên tài nguyên bằng các biến này (ví dụ: export UWEAR_ARTIFACT_REPOSITORY='my-artifact-repository')
  • Bạn có thể đặt các biến sau đây bằng tên tài nguyên trên đám mây hiện có. Nếu bạn đặt biến này, thì tài nguyên đám mây hiện có tương ứng trong dự án sẽ được sử dụng. Nếu bạn không đặt biến này, tên tài nguyên trên đám mây sẽ được tạo từ các giá trị trong tập lệnh config_env.sh.
  1. Chạy tập lệnh config_env.sh để đặt tên biến còn lại thành giá trị dựa trên mã dự án của bạn cho tên tài nguyên.
# 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. Tải chứng chỉ gốc xuống

  1. Để xác thực mã thông báo tự chứa được trả về từ dịch vụ chứng thực, UWear sẽ cần xác thực chữ ký dựa trên chứng chỉ gốc của Không gian bảo mật. UWear sẽ cần tải chứng chỉ gốc xuống và lưu trữ chứng chỉ đó trên thiết bị. Trong một trong các bảng điều khiển của dự án Google Cloud, hãy chạy các lệnh sau:
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. Tạo vân tay số của chứng chỉ gốc đã tải xuống
openssl x509 -fingerprint -in confidential_space_root.pem -noout
  1. Xác minh vân tay số khớp với thông báo SHA-1 sau:
B9:51:20:74:2C:24:E3:AA:34:04:2E:1C:3B:A3:AA:D2:8B:21:23:21

4. Tạo tài khoản dịch vụ khối lượng công việc

Bây giờ, bạn sẽ tạo hai tài khoản dịch vụ; một tài khoản cho USleep và một tài khoản cho khối lượng công việc UWear. Chạy tập lệnh create_service_accounts.sh để tạo tài khoản dịch vụ khối lượng công việc trong dự án USleep và UWear. Các máy ảo chạy khối lượng công việc sẽ sử dụng các tài khoản dịch vụ này.

# Navigate to the scripts folder
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts

# Run the create_service_accounts script
./create_service_accounts.sh

Tập lệnh:

  • Cấp vai trò iam.serviceAccountUser để đính kèm tài khoản dịch vụ vào khối lượng công việc.
  • Cấp vai trò confidentialcomputing.workloadUser cho tài khoản dịch vụ khối lượng công việc . Điều này sẽ cho phép tài khoản người dùng tạo mã thông báo chứng thực.
  • Cấp vai trò logging.logWriter cho quyền tài khoản dịch vụ khối lượng công việc. Điều này cho phép môi trường Không gian bảo mật ghi nhật ký vào Cloud Logging (Nhật ký trên đám mây) ngoài Serial Console (Bảng điều khiển nối tiếp), vì vậy, nhật ký sẽ có sẵn sau khi máy ảo bị chấm dứt.Tạo khối lượng công việc

5. Tạo khối lượng công việc USleep

Trong bước này, bạn sẽ tạo hình ảnh Docker cho các khối lượng công việc được sử dụng trong lớp học lập trình này. USleep là một ứng dụng Golang đơn giản giúp xác định chất lượng giấc ngủ của khách hàng bằng cách sử dụng thông tin sức khoẻ cá nhân trên thiết bị đeo.

Giới thiệu về mức tải USleep

USleep workload là một ứng dụng Golang đơn giản giúp xác định chất lượng giấc ngủ của khách hàng bằng cách sử dụng thông tin sức khoẻ cá nhân trên thiết bị đeo. Gói công việc USleep có ba phần chính:

  1. Thiết lập Phiên TLS và trích xuất Chất liệu khoá đã xuất
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. Yêu cầu mã thông báo từ Dịch vụ chứng thực bằng đối tượng, số chỉ dùng một lần và loại mã thông báo 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. Nhận dữ liệu nhạy cảm và tính chất lượng giấc ngủ của người dùng
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
  ...
}

Các bước tạo USleep Workload

  1. Chạy tập lệnh create_usleep_workload.sh để tạo khối lượng công việc USleep. Tập lệnh này:
  • Tạo Cấu phần phần mềm đăng ký ($USLEEP_ARTIFACT_REPOSITORY) do UWear sở hữu, nơi tải trọng sẽ được phát hành.
  • Tạo mã usleep/workload.go và đóng gói mã đó trong hình ảnh Docker. Xem cấu hình Dockerfile cho USleep.
  • Phát hành hình ảnh Docker lên Cấu phần phần mềm đăng ký ($USLEEP_ARTIFACT_REPOSITORY) do UWear sở hữu.
  • Cấp cho tài khoản dịch vụ $USLEEP_WORKLOAD_SERVICE_ACCOUNT quyền đọc đối với Cấu phần phần mềm ($USLEEP_ARTIFACT_REPOSITORY).
./create_usleep_workload.sh
  1. Quan trọng: Trong nhật ký đầu ra, hãy trích xuất hàm băm hình ảnh cho USleep.
latest: digest: sha256:<USLEEP_IMAGE_DIGEST> size: 945
  1. Chuyển đến thư mục UWear
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/uwear
  1. Thay thế giá trị trong "allowed_submods_container_image_digest" trong opa_validation_values.json bằng 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. Tạo khối lượng công việc UWear

Giới thiệu về khối lượng công việc UWear

Gói công việc UWear có 4 phần chính:

  1. Tham gia cùng một phiên TLS được tạo trong khối lượng công việc của USleep và truy xuất mã thông báo chứng thực từ USleep qua Phiên TLS an toàn.
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. Xác thực mã thông báo tự chứa bằng cách:
  • Kiểm tra thông báo xác nhận x5c có chứa một chuỗi chứng chỉ liên kết chính xác từ chứng chỉ lá đến chứng chỉ trung gian và cuối cùng là chứng chỉ gốc.
  • Kiểm tra mã thông báo do chứng chỉ lá chứa trong thông báo xác nhận x5c ký.
  • Kiểm tra để đảm bảo rằng chứng chỉ gốc đã tải xuống / lưu trữ giống với chứng chỉ gốc trong thông báo xác nhận 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. Sau đó, khối lượng công việc UWear sẽ kiểm tra xem các tuyên bố đo lường khối lượng công việc trong mã thông báo có khớp với các điều kiện thuộc tính được chỉ định trong chính sách OPA hay không. OPA là một công cụ chính sách nguồn mở, dùng cho nhiều mục đích, giúp hợp nhất việc thực thi chính sách trên ngăn xếp. OPA sử dụng các tài liệu có cú pháp tương tự như JSON để đặt các giá trị cơ sở mà chính sách được xác thực.
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"
}
  • Truy vấn Rego mẫu.
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
"

Mã ví dụ để lấy hàm băm 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. Sau khi hoàn tất và vượt qua tất cả các bước kiểm tra đó, UWear có thể xác nhận rằng dữ liệu sẽ được gửi và xử lý một cách an toàn. Sau đó, UWear sẽ phản hồi bằng PHI nhạy cảm qua cùng một phiên TLS và USleep sẽ có thể sử dụng dữ liệu đó để tính toán chất lượng giấc ngủ của khách hàng.
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()
  
  ...
}

Các bước tạo USleep Workload

  1. Chuyển đến thư mục tập lệnh
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
  1. Chạy tập lệnh create_uwear_workload.sh để tạo khối lượng công việc UWear:
  • Tạo Cấu phần phần mềm đăng ký ($UWEAR_ARTIFACT_REPOSITORY) do UWear sở hữu, nơi tải trọng sẽ được phát hành.
  • Tạo mã uwear/workload.go và đóng gói mã đó trong hình ảnh Docker. Xem cấu hình Dockerfile cho USleep.
  • Phát hành hình ảnh Docker lên Cấu phần phần mềm đăng ký ($UWEAR_ARTIFACT_REPOSITORY) do UWear sở hữu.
  • Cấp cho tài khoản dịch vụ $UWEAR_WORKLOAD_SERVICE_ACCOUNT quyền đọc đối với Cấu phần phần mềm ($UWEAR_ARTIFACT_REPOSITORY).
./create_uwear_workload.sh

7. Chạy USleep và UWear Workloads

Chạy khối lượng công việc 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

Phản hồi sẽ trả về STATUS: RUNNING (TÌNH TRẠNG: ĐANG CHẠY) và EXTERNAL_IP cũng sẽ được trả về tương tự như sau:

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

Lưu trữ IP bên ngoài trong một biến

export USLEEP_EXTERNAL_IP=<add your external IP> 

Xác minh rằng Workload USleep chạy chính xác

Để xác minh rằng khối lượng công việc USleep đang chạy đúng cách, hãy chuyển đến trang Bản sao máy ảo trong dự án USleep. Nhấp vào thực thể "usleep" rồi nhấn vào "Cổng nối tiếp 1(bảng điều khiển)" trong phần Nhật ký. Sau khi máy chủ khởi động và chạy, ở cuối nhật ký, bạn sẽ thấy nội dung tương tự như sau.

2024/09/13 17:00:00 workload task started
#####----- Local IP Address is <YOUR-LOCAL-IP> -----#####
Starting Server..

Chạy khối lượng công việc 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

Xác minh rằng khối lượng công việc UWear đã chạy chính xác

Để xem nhật ký của khối lượng công việc UWear, hãy chuyển đến trang Phiên bản máy ảo trong dự án UWear. Nhấp vào thực thể "uwear" rồi nhấn vào "Cổng nối tiếp 1(bảng điều khiển)" trong mục Nhật ký.

Kết quả nhật ký sau khi thực thể khởi động hoàn toàn sẽ có dạng như sau

Trong dự án UWear, nhật ký nối tiếp sẽ hiển thị nội dung tương tự như

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

Nếu khối lượng công việc UWear của bạn không giống như vậy, hãy xem ghi chú bên dưới để biết hướng dẫn.

Xem kết quả USleep

Để xem kết quả, hãy quay lại trang Phiên bản máy ảo trong dự án USleep. Nhấp vào thực thể "usleep" rồi nhấn vào "Cổng nối tiếp 1(bảng điều khiển)" trong phần Nhật ký. Xem kết quả của khối lượng công việc ở cuối nhật ký. Các tệp này sẽ có dạng tương tự như mẫu bên dưới.

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

Kết quả sẽ là "total sleep time is less than 8 hours".

Xin chúc mừng! Bạn đã tạo thành công một Không gian bảo mật giữa UWear và USleep để chia sẻ thông tin nhạy cảm!

8. (Không bắt buộc) Chạy Workload trái phép

Trong trường hợp tiếp theo, USleep sẽ cập nhật mã và chạy một khối lượng công việc khác trên dữ liệu giấc ngủ do UWear cung cấp. UWear chưa đồng ý với khối lượng công việc mới này và chưa cập nhật chính sách OPA để cho phép thông báo tóm tắt hình ảnh mới. Chúng tôi sẽ xác minh rằng UWear sẽ không gửi dữ liệu nhạy cảm của mình đến khối lượng công việc trái phép.

USleep sửa đổi khối lượng công việc của chúng

  1. Đặt dự án thành $USLEEP_PROJECT_ID.
gcloud config set project $USLEEP_PROJECT_ID
  1. Xoá thực thể máy ảo USleep.
gcloud compute instances delete usleep --zone $USLEEP_PROJECT_ZONE
  1. Chuyển đến thư mục usleep/workload.go.
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/usleep
  1. Trong tệp usleep/workload.go. Cập nhật dòng "audience": "uwear". Trong ví dụ này, để thay đổi thông báo tóm tắt hình ảnh, chúng ta sẽ cập nhật đối tượng thành một giá trị khác mà UWear chưa phê duyệt. Vì vậy, UWear nên từ chối quảng cáo này vì hai lý do: thông báo tóm tắt hình ảnh chưa được phê duyệt và đối tượng không chính xác.
"audience": "anotherCompany.com",
  1. Tạo khối lượng công việc USleep mới
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts

./create_usleep_workload.sh
  1. Tạo phiên bản máy ảo USleep mới và chạy khối lượng công việc
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. Trích xuất địa chỉ IP bên ngoài USleep mới để sử dụng sau này
export USLEEP_EXTERNAL_IP=<add your external IP>

Chạy lại khối lượng công việc

  1. Xoá phiên bản máy ảo UWear
gcloud config set project $UWEAR_PROJECT_ID

gcloud compute instances delete uwear --zone $UWEAR_PROJECT_ZONE
  1. Tạo lại thực thể máy ảo UWear bằng IP bên ngoài mới
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. Trong nhật ký tuần tự UWear, thông báo sau sẽ xuất hiện và máy ảo USleep sẽ không nhận được dữ liệu nhạy cảm nào
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. Dọn dẹp

Bạn có thể dùng tập lệnh dọn dẹp để dọn dẹp các tài nguyên mà chúng ta đã tạo trong lớp học lập trình này. Trong quá trình dọn dẹp này, các tài nguyên sau sẽ bị xoá:

  • Tài khoản dịch vụ UWear ($UWEAR_SERVICE_ACCOUNT).
  • Cấu phần đăng ký cấu phần phần mềm UWear ($UWEAR_ARTIFACT_REPOSITORY).
  • Phiên bản máy tính UWear
  • Tài khoản dịch vụ USleep ($USLEEP_SERVICE_ACCOUNT).
  • Cấu phần đăng ký cấu phần phần mềm USleep ($USLEEP_ARTIFACT_REPOSITORY).
  • Phiên bản điện toán USleep
./cleanup.sh

Nếu bạn đã khám phá xong, vui lòng cân nhắc xoá dự án của mình bằng cách làm theo hướng dẫn này.

Chúc mừng bạn!

Xin chúc mừng, bạn đã hoàn tất thành công lớp học lập trình này!

Bạn đã tìm hiểu cách chia sẻ dữ liệu một cách an toàn mà vẫn giữ được tính bảo mật bằng không gian bảo mật.

Tiếp theo là gì?

Hãy xem một số lớp học lập trình tương tự sau đây...

Tài liệu đọc thêm