1. 概要
Confidential Space は、組織がデータの機密性を維持しながら、安全なマルチパーティのデータ共有とコラボレーションを実現します。つまり、組織はデータの制御を維持し、不正アクセスから保護しながら、他の組織とコラボレーションできます。
Confidential Space を使用すると、機密性の高いデータ(多くの場合規制対象のデータ)を完全に管理しながら集約、分析し、相互に価値を得ることができます。Confidential Space では、個人を特定できる情報(PII)、保護医療情報(PHI)、知的財産、暗号シークレットなどの機密データを完全に管理しながら集約、分析することで、共同作業を行う企業は相互に価値を得ることができます。
必要なもの
- 2 つの個別の Google Cloud Platform プロジェクト
- Chrome や Firefox などのブラウザ
- Google Compute Engine、Confidential VM、コンテナ、リモート リポジトリ、証明書、証明書チェーンに関する基本的な知識。
- サービス アカウント、Open Policy Agent、Rego、公開鍵インフラストラクチャに関する基本的な知識
学習内容
- Confidential Space の実行に必要な Cloud リソースを構成する方法
- Confidential Space イメージを実行している Confidential VM でワークロードを実行する方法
- ワークロード コードの属性(what)、Confidential Space 環境(where)、ワークロードを実行しているアカウント(who)に基づいて、保護されたリソースへのアクセスを承認する方法。
この Codelab では、Google Cloud 以外の場所にホストされている保護されたリソースで Confidential Space を使用する方法について説明します。ノンス、オーディエンス、PKI トークン タイプを指定して、Google 構成証明サービスからカスタムの自己完結型トークンをリクエストする方法について学習します。
この Codelab では、架空の製品である USleep(コンテナ化されたアプリケーション)と架空の製品である UWear(接続されたウェアラブル デバイス)の間に Confidential Space を設定して、睡眠の質を計算します。UWear は、安全で確実な隔離環境(高信頼実行環境、TEE)で保護対象保健情報(PHI)を USleep と共有し、データのオーナーが完全な機密性を維持できるようにします。
UWear は、ワークロード監査機能とデータオーナーの両方です。ワークロード監査ツールとして、実行中のワークロードのコードを確認し、イメージ ダイジェストを記録します。UWear はデータ所有者として、トークンとその署名の有効性を確認する検証ロジックを記述します。監査対象のワークロードのイメージ ダイジェストを使用して検証ポリシーが書き込まれ、特定の環境の特定のイメージ ダイジェストのみが機密データにアクセスできるようになります。
この Codelab では、USleep がコンテナ化されたアプリケーションをデプロイします。USleep は機密データにアクセスできませんが、機密データへのアクセスが許可されている承認済みのワークロードを実行します。
この Codelab では、次の手順を行います。
- ステップ 1: Codelab に必要なクラウド リソースを設定します。プロジェクト、課金、権限を設定します。Codelab のソースコードをダウンロードして、環境変数を設定します。
- ステップ 2: ルート証明書をダウンロードし、UWear ソースコードと一緒に保存します。
- ステップ 3: USleep と UWear のワークロード VM で使用される個別のワークロード サービス アカウントを作成します。
- ステップ 4: 構成証明トークンを提供する USleep ワークロードを作成します。
- ステップ 5: 構成証明トークンを検証し、トークンが承認された場合に機密データを送信する UWear ワークロードを作成します。
- ステップ 6: USleep ワークロードと UWear ワークロードを実行します。UWear が機密データを提供します。USleep は、そのデータに対して睡眠アルゴリズムを実行し、結果を出力します。
- ステップ 7: (省略可)不正な USleep ワークロードを実行し、UWear から機密データが受信されていないことを確認します。
- ステップ 8: すべてのリソースをクリーンアップする
ワークフローを理解する
USleep は Confidential Space でワークロードを実行します。ワークロードを実行するには、UWear の PHI にアクセスする必要があります。アクセスを取得するために、USleep ワークロードはまず安全な TLS セッションを作成します。USleep は、ペイロードを使用して Google 構成証明サービスから構成証明トークンもリクエストします。
USleep は、次の 3 つを含む JSON ペイロードを使用して構成証明トークンをリクエストします。
- TLS セッションにバインドされた構成証明トークン。構成証明トークンを TLS セッションにバインドするために、ノンス値は TLS エクスポート鍵マテリアルのハッシュになります。トークンを TLS セッションにバインドすると、TLS セッションに関与する 2 つの当事者だけがノンス値を生成できるため、中間者攻撃が発生しなくなります。
- 「uwear」のオーディエンスが提供されます。UWear は、これが構成証明トークンの対象となるオーディエンスであることを確認します。
- トークン タイプが「PKI」の場合。トークンタイプが「PKI」の場合、USleep は自己完結型トークンをリクエストします。自己完結型トークンが Google によって署名されていることを確認するには、Confidential Space の well-known PKI エンドポイントからダウンロードしたルートを使用します。これは、定期的にローテーションされる公開鍵を使用して署名が検証されるデフォルトの OIDC トークン タイプとは対照的です。
USleep ワークロードが構成証明トークンを受け取ります。UWear は USleep との TLS 接続に参加し、USleep の構成証明トークンを取得します。UWear は、x5c クレームをルート証明書と照合して、トークンを検証します。
UWear は、次の場合に USleep ワークロードを承認します。
- トークンが PKI 検証ロジックに合格します。
- UWear は、x5c クレームをルート証明書と照合し、トークンがリーフ証明書によって署名されていることを確認します。最後に、ダウンロードしたルート証明書が x5c クレームのルート証明書と同じであることを確認します。
- トークンのワークロード測定クレームが、OPA ポリシーで指定された属性条件と一致します。OPA は、スタック全体でポリシー適用を統合するオープンソースの汎用ポリシー エンジンです。OPA は、JSON に似た構文のドキュメントを使用して、ポリシーの検証に使用するベースライン値を設定します。ポリシーでチェックされる値の例については、OPA ベースライン値をご覧ください。
- ノンスが、想定されるノンス(TLS エクスポート鍵マテリアル)と一致している。これは上記の OPA ポリシーで確認できます。
これらのチェックがすべて完了し、合格すると、UWear はデータが安全に送信、処理されることを確認できます。UWear は、同じ TLS セッションで機密性の高い PHI を返します。USleep は、そのデータを使用してお客様の睡眠の質を計算できます。
2. Cloud リソースを設定する
始める前に
- USleep 用と UWear 用の 2 つの Google Cloud プロジェクトを設定します。Google Cloud プロジェクトの作成の詳細については、「最初の Google プロジェクトを設定して操作する」Codelab をご覧ください。プロジェクト ID の取得方法と、プロジェクト ID とプロジェクト名、プロジェクト番号の違いについては、プロジェクトの作成と管理をご覧ください。
- プロジェクトの課金を有効にします。
- Google プロジェクトの Cloud Shell で、次のコマンドを使用して必要なプロジェクト環境変数を設定します。
export UWEAR_PROJECT_ID=<Google Cloud project id of UWear>
export USLEEP_PROJECT_ID=<Google Cloud project id of USleep>
- 両方のプロジェクトで Confidential Computing API と次の 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
- プリンシパル ID を取得する
gcloud auth list
# Output should contain
# ACCOUNT: <Principal Identifier>
# Set your member variable
export MEMBER='user:<Principal Identifier>'
- これらの 2 つのプロジェクトの権限を追加します。権限を追加するには、IAM ロールを付与するウェブページの詳細をご覧ください。
$UWEAR_PROJECT_ID
には、Artifact Registry 管理者とサービス アカウント管理者が必要です。
gcloud config set project $UWEAR_PROJECT_ID
# Add Artifact Registry Administrator role
gcloud projects add-iam-policy-binding $UWEAR_PROJECT_ID --member=$MEMBER --role='roles/iam.serviceAccountAdmin'
# Add Service Account Administrator role
gcloud projects add-iam-policy-binding $UWEAR_PROJECT_ID --member=$MEMBER --role='roles/artifactregistry.admin'
$USLEEP_PROJECT_ID
には、Compute 管理者、ストレージ管理者、Artifact Registry 管理者、サービス アカウント管理者のロールが必要です。
gcloud config set project $USLEEP_PROJECT_ID
# Add Service Account Administrator role
gcloud projects add-iam-policy-binding $USLEEP_PROJECT_ID --member=$MEMBER --role='roles/iam.serviceAccountAdmin'
# Add Artifact Registry Administrator role
gcloud projects add-iam-policy-binding $USLEEP_PROJECT_ID --member=$MEMBER --role='roles/artifactregistry.admin'
# Add Compute Administrator role
gcloud projects add-iam-policy-binding $USLEEP_PROJECT_ID --member=$MEMBER --role='roles/compute.admin'
# Add Storage Administrator role
gcloud projects add-iam-policy-binding $USLEEP_PROJECT_ID --member=$MEMBER --role='roles/compute.storageAdmin'
- Google Cloud プロジェクトのいずれかの Cloud Shell で、次のコマンドを使用して Confidential Space Codelab GitHub リポジトリのクローンを作成し、この Codelab で使用する必要なスクリプトを取得します。
git clone https://github.com/GoogleCloudPlatform/confidential-space.git
- ディレクトリを、ヘルスデータ コードラボのスクリプト ディレクトリに変更します。
cd confidential-space/codelabs/health_data_analysis_codelab/scripts
- codelabs/health_data_analysis_codelab/scripts ディレクトリにある config_env.sh スクリプトの次の 2 行を更新します。プロジェクト ID は、USleep と UWear のプロジェクト ID に置き換えます。行の先頭にあるコメント記号「#」は必ず削除してください。
# TODO: Populate UWear and USleep Project IDs
export UWEAR_PROJECT_ID=your-uwear-project-id
export USLEEP_PROJECT_ID=your-usleep-project-id
- 省略可: 既存の変数を設定します。リソース名は、これらの変数(
export UWEAR_ARTIFACT_REPOSITORY='my-artifact-repository'
など)を使用してオーバーライドできます。
- 次の変数は、既存のクラウド リソース名を使用して設定できます。変数が設定されている場合、プロジェクト内の対応する既存のクラウド リソースが使用されます。変数が設定されていない場合、クラウド リソース名は config_env.sh スクリプトの値から生成されます。
- config_env.sh スクリプトを実行して、残りの変数名を、リソース名のプロジェクト 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. ルート証明書をダウンロードします。
- 構成証明サービスから返された自己完結型トークンを検証するには、UWear が Confidential Space ルート証明書と照らし合わせて署名を検証する必要があります。UWear は、ルート証明書をダウンロード してローカルに保存する必要があります。Google Cloud プロジェクトのコンソールで、次のコマンドを実行します。
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/uwear
wget https://confidentialcomputing.googleapis.com/.well-known/confidential_space_root.crt -O confidential_space_root.pem
- ダウンロードしたルート証明書のフィンガープリントを生成する
openssl x509 -fingerprint -in confidential_space_root.pem -noout
- フィンガープリントが次の SHA-1 ダイジェストと一致していることを確認します。
B9:51:20:74:2C:24:E3:AA:34:04:2E:1C:3B:A3:AA:D2:8B:21:23:21
4. ワークロード サービス アカウントを作成する
次に、USleep ワークロード用と UWear ワークロード用の 2 つのサービス アカウントを作成します。create_service_accounts.sh スクリプトを実行して、USleep プロジェクトと UWear プロジェクトにワークロード サービス アカウントを作成します。ワークロードを実行する VM は、これらのサービス アカウントを使用します。
# 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 ワークロードを作成する
このステップでは、この Codelab で使用するワークロードの Docker イメージを作成します。USleep ワークロードは、ウェアラブル デバイス上の個人の健康情報を使用して、ユーザーの睡眠の質を判断するシンプルな Golang アプリケーションです。
USleep ワークロードについて
USleep ワークロードは、ウェアラブル デバイス上の個人的な健康情報を使用して、ユーザーの睡眠の質を判断するシンプルな Golang アプリケーションです。USleep ワークロードには、主に 3 つの部分があります。
- 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
}
- オーディエンス、ノンス、PKI トークン タイプを指定して構成証明サービスからトークンをリクエストする。
func handleConnectionRequest(w http.ResponseWriter, r *http.Request) {
...
// Request token with TLS Exported Keying Material (EKM) hashed.
token, err := getCustomToken(hash)
if err != nil {
fmt.Printf("failed to get custom token from token endpoint: %v", err)
return
}
// Respond to the client with the token.
conn.WriteMessage(websocket.TextMessage, token)
...
}
var (
socketPath = "/run/container_launcher/teeserver.sock"
tokenEndpoint = "http://localhost/v1/token"
contentType = "application/json"
)
func getCustomToken(nonce string) ([]byte, error) {
httpClient := http.Client{
Transport: &http.Transport{
// Set the DialContext field to a function that creates
// a new network connection to a Unix domain socket
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", socketPath)
},
},
}
body := fmt.Sprintf(`{
"audience": "uwear",
"nonces": ["%s"],
"token_type": "PKI"
}`, nonce)
resp, err := httpClient.Post(tokenEndpoint, contentType, strings.NewReader(body))
if err != nil {
return nil, err
}
fmt.Printf("Response from launcher: %v\n", resp)
text, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("Failed to read resp.Body: %w", err)
}
fmt.Printf("Token from the attestation service: %s\n", text)
return text, nil
}
- 機密データを受信してユーザーの睡眠の質を計算する
func handleConnectionRequest(w http.ResponseWriter, r *http.Request) {
...
// Read the sensitive data
_, content, err := conn.ReadMessage()
if err != nil {
fmt.Printf("failed to read message from the connection: %v\n", err)
}
fmt.Printf("Received content from other side, %v\n", string(content))
// TODO: Handle sensitive data
...
}
USleep ワークロードを作成する手順
- create_usleep_workload.sh スクリプトを実行して、USleep ワークロードを作成します。このスクリプトは次の処理を行います。
- UWear が所有する Artifact Registry(
$USLEEP_ARTIFACT_REPOSITORY
)を作成します。この Artifact Registry にワークロードが公開されます。 - usleep/workload.go コードをビルドし、Docker イメージにパッケージ化します。USleep の Dockerfile 構成をご覧ください。
- UWear が所有する Artifact Registry(
$USLEEP_ARTIFACT_REPOSITORY
)に Docker イメージを公開します。 - サービス アカウント
$USLEEP_WORKLOAD_SERVICE_ACCOUNT
に Artifact Registry($USLEEP_ARTIFACT_REPOSITORY
)に対する読み取り権限を付与します。
./create_usleep_workload.sh
- 重要: 出力ログで、USleep のイメージ ダイジェストを抽出します。
latest: digest: sha256:<USLEEP_IMAGE_DIGEST> size: 945
- UWear ディレクトリに移動します
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/uwear
- opa_validation_values.json の「allowed_submods_container_image_digest」の値を USLEEP_IMAGE_DIGEST に置き換えます。
# Replace the image digest
sed -i 's/sha256:bc4c32cb2ca046ba07dcd964b07a320b7d0ca88a5cf8e979da15cae68a2103ee/sha256:<USLEEP_IMAGE_DIGEST>/' ~/confidential-space/codelabs/health_data_analysis_codelab/src/uwear/opa_validation_values.json
6. UWear ワークロードを作成する
UWear ワークロードについて
UWear ワークロードには、主に次の 4 つの要素があります。
- USleep ワークロードで作成された TLS セッションに参加し、安全な TLS セッションを介して USleep から構成証明トークンを取得します。
func main() {
fmt.Println("Initializing client...")
tlsconfig := &tls.Config{
// Skipping client verification of the server's certificate chain and host name since we are
// doing custom verification using the attestation token.
InsecureSkipVerify: true,
}
dialer := websocket.Dialer{
TLSClientConfig: tlsconfig,
HandshakeTimeout: 5 * time.Second,
}
ipAddress := os.Getenv(ipAddrEnvVar)
url := fmt.Sprintf("wss://%s:8081/connection", ipAddress)
fmt.Printf("Attempting to dial to url %v...\n", url)
conn, _, err := dialer.Dial(url, nil)
if err != nil {
fmt.Printf("Failed to dial to url %s, err %v\n", url, err)
return
}
defer conn.Close()
tokenString, ekm, err := retrieveTokenAndEKMFromConn(conn)
if err != nil {
fmt.Printf("Failed to retrieve token and EKM from connection: %v\n", err)
return
}
fmt.Printf("token: %v\n", tokenString)
...
}
- 次のように自己完結型トークンを検証します。
- x5c クレームを確認すると、リーフ証明書から中間証明書、最後にルート証明書に正しく連結された証明書チェーンが含まれています。
- x5c クレームに含まれるリーフ証明書によってトークンが署名されていることを確認します。
- ダウンロードまたは保存されたルート証明書が x5c クレームのルートと同じであることを確認します。
func main() {
...
token, err := validatePKIToken(tokenString)
if err != nil {
fmt.Printf("Failed to validate PKI token, err: %v\n.", err)
return
}
fmt.Println("PKI token validated successfully")
...
}
// validatePKIToken validates the PKI token returned from the attestation service.
// It verifies the token the certificate chain and that the token is signed by Google
// Returns a jwt.Token or returns an error if invalid.
func validatePKIToken(attestationToken string) (jwt.Token, error) {
// IMPORTANT: The attestation token should be considered untrusted until the certificate chain and
// the signature is verified.
rawRootCertificate, err := readFile(rootCertificateFile)
if err != nil {
return jwt.Token{}, fmt.Errorf("readFile(%v) - failed to read root certificate: %w", rootCertificateFile, err)
}
storedRootCert, err := decodeAndParsePEMCertificate(string(rawRootCertificate))
if err != nil {
return jwt.Token{}, fmt.Errorf("DecodeAndParsePEMCertificate(string) - failed to decode and parse root certificate: %w", err)
}
jwtHeaders, err := extractJWTHeaders(attestationToken)
if err != nil {
return jwt.Token{}, fmt.Errorf("ExtractJWTHeaders(token) - failed to extract JWT headers: %w", err)
}
if jwtHeaders["alg"] != "RS256" {
return jwt.Token{}, fmt.Errorf("ValidatePKIToken(attestationToken, ekm) - got Alg: %v, want: %v", jwtHeaders["alg"], "RS256")
}
// Additional Check: Validate the ALG in the header matches the certificate SPKI.
// https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.7
// This is included in Golang's jwt.Parse function
x5cHeaders := jwtHeaders["x5c"].([]any)
certificates, err := extractCertificatesFromX5CHeader(x5cHeaders)
if err != nil {
return jwt.Token{}, fmt.Errorf("ExtractCertificatesFromX5CHeader(x5cHeaders) returned error: %w", err)
}
// Verify the leaf certificate signature algorithm is an RSA key
if certificates.LeafCert.SignatureAlgorithm != x509.SHA256WithRSA {
return jwt.Token{}, fmt.Errorf("leaf certificate signature algorithm is not SHA256WithRSA")
}
// Verify the leaf certificate public key algorithm is RSA
if certificates.LeafCert.PublicKeyAlgorithm != x509.RSA {
return jwt.Token{}, fmt.Errorf("leaf certificate public key algorithm is not RSA")
}
// Verify the storedRootCertificate is the same as the root certificate returned in the token
// storedRootCertificate is downloaded from the confidential computing well known endpoint
// https://confidentialcomputing.googleapis.com/.well-known/attestation-pki-root
err = compareCertificates(*storedRootCert, *certificates.RootCert)
if err != nil {
return jwt.Token{}, fmt.Errorf("failed to verify certificate chain: %w", err)
}
err = verifyCertificateChain(certificates)
if err != nil {
return jwt.Token{}, fmt.Errorf("VerifyCertificateChain(CertificateChain) - error verifying x5c chain: %v", err)
}
keyFunc := func(token *jwt.Token) (any, error) {
return certificates.LeafCert.PublicKey, nil
}
verifiedJWT, err := jwt.Parse(attestationToken, keyFunc)
return *verifiedJWT, err
}
// verifyCertificateChain verifies the certificate chain from leaf to root.
// It also checks that all certificate lifetimes are valid.
func verifyCertificateChain(certificates CertificateChain) error {
// Additional check: Verify that all certificates in the cert chain are valid.
// Note: The *x509.Certificate Verify method in Golang already validates this but for other coding
// languages it is important to make sure the certificate lifetimes are checked.
if isCertificateLifetimeValid(certificates.LeafCert) {
return fmt.Errorf("leaf certificate is not valid")
}
if isCertificateLifetimeValid(certificates.IntermediateCert) {
return fmt.Errorf("intermediate certificate is not valid")
}
interPool := x509.NewCertPool()
interPool.AddCert(certificates.IntermediateCert)
if isCertificateLifetimeValid(certificates.RootCert) {
return fmt.Errorf("root certificate is not valid")
}
rootPool := x509.NewCertPool()
rootPool.AddCert(certificates.RootCert)
_, err := certificates.LeafCert.Verify(x509.VerifyOptions{
Intermediates: interPool,
Roots: rootPool,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
})
if err != nil {
return fmt.Errorf("failed to verify certificate chain: %v", err)
}
return nil
}
- UWear ワークロードは、トークンのワークロード測定クレームが OPA ポリシーで指定された属性条件と一致するかどうかを確認します。OPA は、スタック全体でポリシー適用を統合するオープンソースの汎用ポリシー エンジンです。OPA は、JSON に似た構文のドキュメントを使用して、ポリシーの検証に使用するベースライン値を設定します。
func main() {
...
err = validateClaimsAgainstOPAPolicy(token, ekm)
if err != nil {
fmt.Printf("Failed to validate claims against OPA policy: %v\n", err)
return
}
fmt.Println("Validated token and claims. Sending sensitive data")
...
}
// validateClaimsAgainstOPAPolicy validates the claims in the JWT token against the OPA policy.
func validateClaimsAgainstOPAPolicy(token jwt.Token, ekm string) error {
data, err := os.ReadFile("opa_validation_values.json")
authorized, err := evaluateOPAPolicy(context.Background(), token, ekm, string(data))
if err != nil {
fmt.Println("Error evaluating OPA policy:", err)
return fmt.Errorf("failed to evaluate OPA policy: %w", err)
}
if !authorized {
fmt.Println("Remote TEE's JWT failed policy check.")
return fmt.Errorf("remote TEE's JWT failed policy check")
}
fmt.Println("JWT is authorized.")
return nil
}
// evaluateOPAPolicy returns boolean indicating if OPA policy is satisfied or not, or error if occurred
func evaluateOPAPolicy(ctx context.Context, token jwt.Token, ekm string, policyData string) (bool, error) {
var claims jwt.MapClaims
var ok bool
if claims, ok = token.Claims.(jwt.MapClaims); !ok {
return false, fmt.Errorf("failed to get the claims from the JWT")
}
module := fmt.Sprintf(opaPolicy, ekm)
var json map[string]any
err := util.UnmarshalJSON([]byte(policyData), &json)
store := inmem.NewFromObject(json)
// Bind 'allow' to the value of the policy decision
// Bind 'hw_verified', 'image_verified', 'audience_verified, 'nonce_verified' to their respective policy evaluations
query, err := rego.New(
rego.Query(regoQuery), // Argument 1 (Query string)
rego.Store(store), // Argument 2 (Data store)
rego.Module("confidential_space.rego", module), // Argument 3 (Policy module)
).PrepareForEval(ctx)
if err != nil {
fmt.Printf("Error creating query: %v\n", err)
return false, err
}
fmt.Println("Performing OPA query evaluation...")
results, err := query.Eval(ctx, rego.EvalInput(claims))
if err != nil {
fmt.Printf("Error evaluating OPA policy: %v\n", err)
return false, err
} else if len(results) == 0 {
fmt.Println("Undefined result from evaluating OPA policy")
return false, err
} else if result, ok := results[0].Bindings["allow"].(bool); !ok {
fmt.Printf("Unexpected result type: %v\n", ok)
fmt.Printf("Result: %+v\n", result)
return false, err
}
fmt.Println("OPA policy evaluation completed.")
fmt.Println("OPA policy result values:")
for key, value := range results[0].Bindings {
fmt.Printf("[ %s ]: %v\n", key, value)
}
result := results[0].Bindings["allow"]
if result == true {
fmt.Println("Policy check PASSED")
return true, nil
}
fmt.Println("Policy check FAILED")
return false, nil
}
- OPA ベースライン値の例:
{
"allowed_submods_container_image_digest": [
"sha256:<USLEEP_IMAGE_DIGEST>"
],
"allowed_hwmodel": [
"GCP_INTEL_TDX",
"GCP_SHIELDED_VM",
"GCP_AMD_SEV_ES",
"GCP_AMD_SEV"
],
"allowed_aud": [
"uwear"
],
"allowed_issuer": [
"https://confidentialcomputing.googleapis.com"
],
"allowed_secboot": [
true
],
"allowed_sw_name": [
"CONFIDENTIAL_SPACE"
]
}
- Rego で記述された OPA ポリシーの例。
package confidential_space
import rego.v1
default allow := false
default hw_verified := false
default image_digest_verified := false
default audience_verified := false
default nonce_verified := false
default issuer_verified := false
default secboot_verified := false
default sw_name_verified := false
allow if {
hw_verified
image_digest_verified
audience_verified
nonce_verified
issuer_verified
secboot_verified
sw_name_verified
}
hw_verified if input.hwmodel in data.allowed_hwmodel
image_digest_verified if input.submods.container.image_digest in data.allowed_submods_container_image_digest
audience_verified if input.aud in data.allowed_aud
issuer_verified if input.iss in data.allowed_issuer
secboot_verified if input.secboot in data.allowed_secboot
sw_name_verified if input.swname in data.allowed_sw_name
nonce_verified if {
input.eat_nonce == "%s"
}
- Rego クエリの例。
regoQuery = "
allow = data.confidential_space.allow;
hw_verified = data.confidential_space.hw_verified;
image__digest_verified = data.confidential_space.image_digest_verified;
audience_verified = data.confidential_space.audience_verified;
nonce_verified = data.confidential_space.nonce_verified;
issuer_verified = data.confidential_space.issuer_verified;
secboot_verified = data.confidential_space.secboot_verified;
sw_name_verified = data.confidential_space.sw_name_verified
"
- OPA 検証中に、UWear ワークロードはノンスが想定されるノンス(TLS エクスポートされた鍵生成素材 - EKM)と一致することも検証します。ポリシー評価ツールに渡される EKM を使用して、ノンスが OPA ポリシーで検証されます。
func getEKMHashFromConn(c *websocket.Conn) (string, error) {
conn, ok := c.NetConn().(*tls.Conn)
if !ok {
return "", fmt.Errorf("failed to cast NetConn to *tls.Conn")
}
state := conn.ConnectionState()
ekm, err := state.ExportKeyingMaterial("testing_nonce", nil, 32)
if err != nil {
return "", fmt.Errorf("failed to get EKM from TLS connection: %w", err)
}
sha := sha256.New()
sha.Write(ekm)
hash := base64.StdEncoding.EncodeToString(sha.Sum(nil))
return hash, nil
}
- これらのチェックがすべて完了し、合格すると、UWear はデータが安全に送信、処理されることを確認できます。UWear は、同じ TLS セッションで機密性の高い 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 ワークロードを作成する手順
- スクリプト ディレクトリに移動します。
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
- create_uwear_workload.sh スクリプトを実行して UWear ワークロードを作成します。
- UWear が所有する Artifact Registry(
$UWEAR_ARTIFACT_REPOSITORY
)を作成します。この Artifact Registry にワークロードが公開されます。 - uwear/workload.go コードをビルドし、Docker イメージにパッケージ化します。USleep の Dockerfile 構成をご覧ください。
- UWear が所有する Artifact Registry(
$UWEAR_ARTIFACT_REPOSITORY
)に Docker イメージを公開します。 - サービス アカウント
$UWEAR_WORKLOAD_SERVICE_ACCOUNT
に Artifact Registry($UWEAR_ARTIFACT_REPOSITORY
)に対する読み取り権限を付与します。
./create_uwear_workload.sh
7. USleep ワークロードと UWear ワークロードを実行します。
USleep ワークロードを実行する
gcloud config set project $USLEEP_PROJECT_ID
gcloud compute instances create \
--confidential-compute-type=SEV \
--shielded-secure-boot \
--maintenance-policy=MIGRATE \
--scopes=cloud-platform --zone=${USLEEP_PROJECT_ZONE} \
--image-project=confidential-space-images \
--image-family=confidential-space \
--service-account=${USLEEP_WORKLOAD_SERVICE_ACCOUNT}@${USLEEP_PROJECT_ID}.iam.gserviceaccount.com \
--metadata ^~^tee-image-reference=${USLEEP_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${USLEEP_PROJECT_ID}/${USLEEP_ARTIFACT_REPOSITORY}/${USLEEP_WORKLOAD_IMAGE_NAME}:${USLEEP_WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-container-log-redirect=true usleep
レスポンスには STATUS: RUNNING が返され、EXTERNAL_IP も次のような値が返されます。
NAME: usleep
ZONE: us-west1-b
MACHINE_TYPE: n2d-standard-2
PREEMPTIBLE:
INTERNAL_IP: 10.138.0.6
EXTERNAL_IP: 34.168.56.10
STATUS: RUNNING
外部 IP を変数に格納する
export USLEEP_EXTERNAL_IP=<add your external IP>
USleep ワークロードが正しく実行されたことを確認する
USleep ワークロードが正しく実行されていることを確認するには、USleep プロジェクトの [VM インスタンス] ページに移動します。[usleep] インスタンスをクリックし、[ログ] セクションの [シリアルポート 1(コンソール)] をクリックします。サーバーが稼働すると、ログの下部に次のようなログが表示されます。
2024/09/13 17:00:00 workload task started
#####----- Local IP Address is <YOUR-LOCAL-IP> -----#####
Starting Server..
UWear ワークロードを実行する
gcloud config set project $UWEAR_PROJECT_ID
gcloud compute instances create \
--confidential-compute-type=SEV \
--shielded-secure-boot \
--maintenance-policy=MIGRATE \
--scopes=cloud-platform --zone=${UWEAR_PROJECT_ZONE} \
--image-project=confidential-space-images \
--image-family=confidential-space \
--service-account=${UWEAR_WORKLOAD_SERVICE_ACCOUNT}@${UWEAR_PROJECT_ID}.iam.gserviceaccount.com \
--metadata ^~^tee-image-reference=${UWEAR_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${UWEAR_PROJECT_ID}/${UWEAR_ARTIFACT_REPOSITORY}/${UWEAR_WORKLOAD_IMAGE_NAME}:${UWEAR_WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-container-log-redirect=true~tee-env-remote_ip_addr=$USLEEP_EXTERNAL_IP uwear
UWear ワークロードが正しく実行されたことを確認する
UWear ワークロードのログを表示するには、UWear プロジェクトの [VM インスタンス] ページに移動します。[uwear] インスタンスをクリックし、[ログ] セクションの [シリアルポート 1(コンソール)] をクリックします。
インスタンスの起動が完了すると、ログ出力は次のようになります。
UWear プロジェクトのシリアルログには、次のような内容が表示されます。
token: eyJ[...]MrXUg
PKI token validated successfully
Performing OPA query evaluation...
OPA policy evaluation completed.
OPA policy result values:
[ hw_verified ]: true
[ image__digest_verified ]: true
[ audience_verified ]: true
[ nonce_verified ]: true
[ issuer_verified ]: true
[ secboot_verified ]: true
[ sw_name_verified ]: true
[ allow ]: true
Policy check PASSED
JWT is authorized.
Validated token and claims. Sending sensitive data
Sent payload. Closing the connection
UWear ワークロードがこの形式でない場合、以下の注記を参照してください。
USleep の結果を表示する
結果を表示するには、USleep プロジェクトの [VM インスタンス] ページに戻ります。[usleep] インスタンスをクリックし、[ログ] セクションの [シリアルポート 1(コンソール)] をクリックします。ログの下部でワークロードの結果を確認します。以下のサンプルのようになります。
Token from the attestation service: eyJhbGci...Ii5A3CJBuDM2o5Q
Received content from other side, {
"name": "Amy",
"age": 29,
"sleep": {
"light": {
"minutes": 270
},
"deep": {
"minutes": 135
},
"rem": {
"minutes": 105
}
}
}
Sleep quality result: total sleep time is less than 8 hours
結果は "total sleep time is less than 8 hours".
になります。
これで、UWear と USleep の間に機密情報を共有するための機密スペースが正常に作成されました。
8. (省略可)不正なワークロードを実行する
次のシナリオでは、USleep がコードを更新し、UWear から提供された睡眠データに対して別のワークロードを実行します。UWear はこの新しいワークロードに同意しておらず、新しいイメージ ダイジェストを許可するように OPA ポリシーを更新していません。UWear が機密データを不正なワークロードに送信しないようにします。
USleep がワークロードを変更する
- プロジェクトを $USLEEP_PROJECT_ID に設定します。
gcloud config set project $USLEEP_PROJECT_ID
- USleep VM インスタンスを削除します。
gcloud compute instances delete usleep --zone $USLEEP_PROJECT_ZONE
- usleep/workload.go ディレクトリに移動します。
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/usleep
- usleep/workload.go ファイルで、行
"audience": "uwear".
を更新します。この例では、画像ダイジェストを変更するために、オーディエンスを UWear が承認していない別の値に更新します。そのため、UWear は、承認されていない画像ダイジェストと不適切なオーディエンスの 2 つの理由で、この広告を拒否する必要があります。
"audience": "anotherCompany.com",
- 新しい USleep ワークロードを作成する
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
./create_usleep_workload.sh
- 新しい USleep VM インスタンスを作成してワークロードを実行する
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
- 後で使用するために、新しい USleep 外部 IP を抽出します。
export USLEEP_EXTERNAL_IP=<add your external IP>
ワークロードを再実行する
- UWear VM インスタンスを削除する
gcloud config set project $UWEAR_PROJECT_ID
gcloud compute instances delete uwear --zone $UWEAR_PROJECT_ZONE
- 新しい外部 IP を使用して UWear VM インスタンスを再作成する
gcloud compute instances create \
--confidential-compute-type=SEV \
--shielded-secure-boot \
--maintenance-policy=MIGRATE \
--scopes=cloud-platform --zone=${UWEAR_PROJECT_ZONE} \
--image-project=confidential-space-images \
--image-family=confidential-space \
--service-account=${UWEAR_WORKLOAD_SERVICE_ACCOUNT}@${UWEAR_PROJECT_ID}.iam.gserviceaccount.com \
--metadata ^~^tee-image-reference=${UWEAR_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${UWEAR_PROJECT_ID}/${UWEAR_ARTIFACT_REPOSITORY}/${UWEAR_WORKLOAD_IMAGE_NAME}:${UWEAR_WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-container-log-redirect=true~tee-env-remote_ip_addr=$USLEEP_EXTERNAL_IP uwear
- UWear シリアルログに次のメッセージが表示され、USleep 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. クリーンアップ
クリーンアップ スクリプトを使用して、この Codelab で作成したリソースをクリーンアップできます。このクリーンアップの一環として、次のリソースが削除されます。
- UWear サービス アカウント(
$UWEAR_SERVICE_ACCOUNT
)。 - UWear アーティファクト レジストリ(
$UWEAR_ARTIFACT_REPOSITORY
)。 - UWear Compute インスタンス
- USleep サービス アカウント(
$USLEEP_SERVICE_ACCOUNT
)。 - USleep アーティファクト レジストリ(
$USLEEP_ARTIFACT_REPOSITORY
)。 - USleep Compute インスタンス
./cleanup.sh
試用を終了した場合は、こちらの手順に沿ってプロジェクトを削除することをご検討ください。
おめでとうございます!
これで、この Codelab は終了です。
Confidential Space を使用して機密性を維持しながらデータを安全に共有する方法について学習しました。
次のステップ
以下の類似の Codelab をご覧ください。
- Confidential Space を使用して ML モデルと知的財産を保護する
- マルチパーティ コンピューティングと Confidential Space によってデジタル アセットをトランザクションする方法
- Confidential Space を使用して機密データを分析する