1. 概览
Confidential Space 提供安全的多方数据共享和协作功能,同时让组织能够保护其数据的机密性。这意味着,组织可以在相互协作的同时,仍能控制自己的数据并保护其免遭未经授权的访问。
Confidential Space 可用于以下场景:您希望通过汇总和分析敏感数据(通常受监管)来获得相互价值,同时保留对这些数据的完全控制权。借助 Confidential Space,组织可以通过汇总和分析敏感数据(例如个人身份信息 [PII]、受保护健康信息 [PHI]、知识产权和加密密钥)获得相互价值,同时保留对这些数据的完全控制权。
所需条件
- 两个单独的 Google Cloud Platform 项目
- 一个浏览器,例如 Chrome 或 Firefox
- 具备 Google Compute Engine、机密虚拟机、容器以及远程代码库、证书和证书链的基础知识。
- 了解 Service Accounts、Open Policy Agent、Rego 和公共密钥基础架构的基础知识
学习内容
- 如何配置运行 Confidential Space 所需的 Cloud 资源
- 如何在运行 Confidential Space 映像的机密虚拟机中运行工作负载
- 如何根据工作负载代码的属性(what)、机密空间环境(where)以及运行工作负载的账号(who)授权访问受保护资源。
本 Codelab 重点介绍了如何将 Confidential Space 与托管在 Google Cloud 以外位置的受保护资源搭配使用。您将学习如何通过提供 Nonce、受众群体和 PKI 令牌类型,从 Google 认证服务请求自包含的自定义令牌。
在此 Codelab 中,您将在一个虚构的产品(容器化应用 USleep)和另一个虚构的产品(联网穿戴式设备 UWear)之间设置一个 Confidential Space,以计算您的睡眠质量。UWear 将在安全、可靠且隔离的环境(也称为可信执行环境 [TEE])中与 USleep 共享受保护的健康信息 (PHI),以便数据所有者保留完全机密性。
UWear 既是工作负载审核员,也是数据所有者。作为工作负载审核程序,它会审核正在运行的工作负载中的代码,并记下映像摘要。作为数据所有者,UWear 会编写验证逻辑,以检查令牌及其签名的有效性。它使用经过审核的工作负载映像摘要写入验证政策,该政策仅允许特定环境中的特定映像摘要访问敏感数据。
在此 Codelab 中,USleep 会部署容器化应用。USleep 无权访问敏感数据,但会运行获准访问敏感数据的批准工作负载。
此 Codelab 包含以下步骤:
- 第 1 步:为本 Codelab 设置必要的云资源。设置项目、结算和权限。下载 Codelab 源代码并设置环境变量。
- 第 2 步:下载根证书,并将其与 UWear 源代码一起存储。
- 第 3 步:创建单独的工作负载服务账号,供工作负载虚拟机用于 USleep 和 UWear。
- 第 4 步:创建提供证明令牌的 USleep 工作负载。
- 第 5 步:创建 UWear 工作负载,用于验证认证令牌,并在令牌获得批准时发送敏感数据。
- 第 6 步:运行 USleep 和 UWear 工作负载。UWear 将提供敏感数据,USleep 将对数据运行睡眠算法并输出结果。
- 第 7 步(可选):运行未经授权的 USleep 工作负载,并确认未从 UWear 收到敏感数据。
- 第 8 步:清理所有资源。
了解工作流程
USleep 将在 Confidential Space 中运行工作负载。为了运行工作负载,它需要访问 UWear 的 PHI。为了获得访问权限,USleep 工作负载会先创建一个安全的 TLS 会话。然后,USleep 还会使用载荷向 Google Attestation 服务请求认证令牌。
USleep 将使用 JSON 载荷请求认证令牌,该载荷将包含以下三项:
- 绑定到 TLS 会话的认证令牌。为了将认证令牌绑定到 TLS 会话,Nonce 值将是 TLS 导出的密钥材料的哈希值。将令牌绑定到 TLS 会话可确保不会发生中间人攻击,因为只有 TLS 会话中的两个参与方才能生成 Nonce 值。
- 系统会提供“服装”受众群体。UWear 将验证自己是否是该认证令牌的预期受众群体。
- 令牌类型为“PKI”。如果令牌类型为“PKI”,则表示 USleep 想要请求自包含令牌。您可以使用从 Confidential Space 的知名 PKI 端点下载的根证书,验证自包含令牌是否由 Google 签名。这与默认的 OIDC 令牌类型形成鲜明对比,默认 OIDC 令牌类型的签名是使用定期轮替的公钥进行验证的。
USleep 工作负载会收到认证令牌。然后,UWear 会加入与 USleep 的 TLS 连接,并检索 USleep 的认证令牌。UWear 会通过将 x5c 声明与根证书进行对比来验证令牌。
如果满足以下条件,UWear 会批准 USleep 工作负载:
- 令牌通过了 PKI 验证逻辑。
- UWear 将通过以下方式验证令牌:根据根证书检查 x5c 声明、检查令牌是否由叶证书签名,最后检查下载的根证书是否与 x5c 声明中的根证书相同。
- 令牌中的工作负载衡量声明与 OPA 政策中指定的属性条件匹配。OPA 是一个开源的通用政策引擎,可统一整个堆栈中的政策强制执行。OPA 使用与 JSON 类似的语法的文档来设置用于验证政策的基准值。如需查看该政策会检查哪些值的示例,请参阅 OPA 基准值。
- Nonce 与预期的 nonce(TLS 导出的密钥材料)匹配。上述 OPA 政策中对此进行了验证。
完成并通过所有这些检查后,UWear 可以确认数据将以安全的方式发送和处理。然后,UWear 将通过同一 TLS 会话返回敏感的 PHI,USleep 将能够使用这些数据计算客户的睡眠质量。
2. 设置 Cloud 资源
准备工作
- 设置两个 Google Cloud 项目,一个用于 USleep,另一个用于 UWear。如需详细了解如何创建 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
- 使用以下方法检索主账号标识符
gcloud auth list
# Output should contain
# ACCOUNT: <Principal Identifier>
# Set your member variable
export MEMBER='user:<Principal Identifier>'
- 为这两个项目添加权限。如需添加权限,请按照“授予 IAM 角色”网页上的详细说明操作。
- 对于
$UWEAR_PROJECT_ID
,您需要拥有 Artifact Registry Administrator 和 Service Account Admin 角色。
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 Admin、Storage Admin、Artifact Registry Administrator 和 Service Account 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'
- 在 Google Cloud 项目的 Cloud Shell 中,使用以下命令克隆“机密聊天室”Codelab GitHub 代码库,以获取此 Codelab 中所需的脚本。
git clone https://github.com/GoogleCloudPlatform/confidential-space.git
- 将目录更改为健康数据 Codelab 的脚本目录。
cd confidential-space/codelabs/health_data_analysis_codelab/scripts
- 更新 config_env.sh 脚本中的这两行代码,该脚本位于 codelabs/health_data_analysis_codelab/scripts 目录中。将项目 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 工作负载。运行 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,因此在虚拟机终止后,日志仍可用。
5. 创建 USleep 工作负载
在此步骤中,您将为此 Codelab 中使用的工作负载创建 Docker 映像。USleep 工作负载是一个简单的 Golang 应用,可使用穿戴式设备上的个人健康信息来确定客户的睡眠质量。
USleep 工作负载简介
USleep 工作负载是一个简单的 Golang 应用,可使用穿戴式设备上的个人健康信息来确定客户的睡眠质量。USleep 工作负载包含三个主要部分:
- 设置 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
}
- 使用受众群体、Nonce 和 PKI 令牌类型从 Attestation Service 请求令牌。
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
),用于发布工作负载。 - 构建 usleep/workload.go 代码并将其打包到 Docker 映像中。请参阅 USleep 的 Dockerfile 配置。
- 将 Docker 映像发布到 UWear 拥有的 Artifact Registry (
$USLEEP_ARTIFACT_REPOSITORY
)。 - 向服务账号
$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 工作负载还会验证 Nonce 是否与预期的 Nonce(TLS Exported Keying Material - EKM)匹配。系统会使用传递给政策评估器的 EKM在 OPA 政策中验证 Nonce。
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
),用于发布工作负载。 - 构建 uwear/workload.go 代码并将其打包到 Docker 映像中。请参阅 USleep 的 Dockerfile 配置。
- 将 Docker 映像发布到 UWear 拥有的 Artifact Registry (
$UWEAR_ARTIFACT_REPOSITORY
)。 - 向服务账号
$UWEAR_WORKLOAD_SERVICE_ACCOUNT
授予对 Artifact Registry ($UWEAR_ARTIFACT_REPOSITORY
) 的读取权限。
./create_uwear_workload.sh
7. 运行 USleep 和 UWear 工作负载
运行 USleep 工作负载
gcloud config set project $USLEEP_PROJECT_ID
gcloud compute instances create \
--confidential-compute-type=SEV \
--shielded-secure-boot \
--maintenance-policy=MIGRATE \
--scopes=cloud-platform --zone=${USLEEP_PROJECT_ZONE} \
--image-project=confidential-space-images \
--image-family=confidential-space \
--service-account=${USLEEP_WORKLOAD_SERVICE_ACCOUNT}@${USLEEP_PROJECT_ID}.iam.gserviceaccount.com \
--metadata ^~^tee-image-reference=${USLEEP_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${USLEEP_PROJECT_ID}/${USLEEP_ARTIFACT_REPOSITORY}/${USLEEP_WORKLOAD_IMAGE_NAME}:${USLEEP_WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-container-log-redirect=true usleep
响应应返回 STATUS: RUNNING,并且还应返回类似于以下内容的 EXTERNAL_IP:
NAME: usleep
ZONE: us-west1-b
MACHINE_TYPE: n2d-standard-2
PREEMPTIBLE:
INTERNAL_IP: 10.138.0.6
EXTERNAL_IP: 34.168.56.10
STATUS: RUNNING
将外部 IP 存储在变量中
export USLEEP_EXTERNAL_IP=<add your external IP>
验证 USleep 工作负载是否已正确运行
如需验证 USleep 工作负载是否正常运行,请前往 USleep 项目中的虚拟机实例页面。点击“usleep”实例,然后按“日志”部分下的“串行端口 1(控制台)”。服务器启动运行后,日志底部应显示类似以下内容的日志。
2024/09/13 17:00:00 workload task started
#####----- Local IP Address is <YOUR-LOCAL-IP> -----#####
Starting Server..
运行 UWear 工作负载
gcloud config set project $UWEAR_PROJECT_ID
gcloud compute instances create \
--confidential-compute-type=SEV \
--shielded-secure-boot \
--maintenance-policy=MIGRATE \
--scopes=cloud-platform --zone=${UWEAR_PROJECT_ZONE} \
--image-project=confidential-space-images \
--image-family=confidential-space \
--service-account=${UWEAR_WORKLOAD_SERVICE_ACCOUNT}@${UWEAR_PROJECT_ID}.iam.gserviceaccount.com \
--metadata ^~^tee-image-reference=${UWEAR_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${UWEAR_PROJECT_ID}/${UWEAR_ARTIFACT_REPOSITORY}/${UWEAR_WORKLOAD_IMAGE_NAME}:${UWEAR_WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-container-log-redirect=true~tee-env-remote_ip_addr=$USLEEP_EXTERNAL_IP uwear
验证 UWear 工作负载是否正常运行
如需查看 UWear 工作负载的日志,请前往 UWear 项目中的虚拟机实例页面。点击“uwear”实例,然后按“日志”部分下的“串行端口 1(控制台)”。
实例完全启动后,日志输出应如下所示
在 UWear 项目中,串行日志应显示类似以下内容
token: eyJ[...]MrXUg
PKI token validated successfully
Performing OPA query evaluation...
OPA policy evaluation completed.
OPA policy result values:
[ hw_verified ]: true
[ image__digest_verified ]: true
[ audience_verified ]: true
[ nonce_verified ]: true
[ issuer_verified ]: true
[ secboot_verified ]: true
[ sw_name_verified ]: true
[ allow ]: true
Policy check PASSED
JWT is authorized.
Validated token and claims. Sending sensitive data
Sent payload. Closing the connection
如果您的 UWear 工作负载看起来不像这样,请参阅以下备注了解相关说明。
查看 USleep 结果
如需查看结果,请返回 USleep 项目中的“虚拟机实例”页面。点击“usleep”实例,然后按“日志”部分下的“串行端口 1(控制台)”。在日志底部查看工作负载的结果。它们应类似于以下示例。
Token from the attestation service: eyJhbGci...Ii5A3CJBuDM2o5Q
Received content from other side, {
"name": "Amy",
"age": 29,
"sleep": {
"light": {
"minutes": 270
},
"deep": {
"minutes": 135
},
"rem": {
"minutes": 105
}
}
}
Sleep quality result: total sleep time is less than 8 hours
结果应为 "total sleep time is less than 8 hours".
恭喜,您已成功在 UWear 和 USleep 之间创建了 Confidential Space,以便分享敏感信息!
8. (可选)运行未经授权的工作负载
在下一个场景中,USleep 会更新代码,并针对 UWear 提供的睡眠数据运行不同的工作负载。UWear 尚未同意此新工作负载,也未更新其 OPA 政策以允许新的映像摘要。我们将验证 UWear 不会将其敏感数据发送到未经授权的工作负载。
USleep 修改其工作负载
- 将项目设置为 $USLEEP_PROJECT_ID。
gcloud config set project $USLEEP_PROJECT_ID
- 删除 USleep 虚拟机实例。
gcloud compute instances delete usleep --zone $USLEEP_PROJECT_ZONE
- 前往 usleep/workload.go 目录。
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/usleep
- 在 usleep/workload.go 文件中。更新行
"audience": "uwear".
在此示例中,为了更改图片摘要,我们将受众群体更新为 UWear 尚未批准的其他值。因此,UWear 应出于以下两个原因拒登该应用:图片摘要未获批准且受众群体不正确。
"audience": "anotherCompany.com",
- 创建新的 USleep 工作负载
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
./create_usleep_workload.sh
- 创建新的 USleep 虚拟机实例并运行工作负载
gcloud compute instances create \
--confidential-compute-type=SEV \
--shielded-secure-boot \
--maintenance-policy=MIGRATE \
--scopes=cloud-platform --zone=${USLEEP_PROJECT_ZONE} \
--image-project=confidential-space-images \
--image-family=confidential-space \
--service-account=${USLEEP_WORKLOAD_SERVICE_ACCOUNT}@${USLEEP_PROJECT_ID}.iam.gserviceaccount.com \
--metadata ^~^tee-image-reference=${USLEEP_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${USLEEP_PROJECT_ID}/${USLEEP_ARTIFACT_REPOSITORY}/${USLEEP_WORKLOAD_IMAGE_NAME}:${USLEEP_WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-container-log-redirect=true usleep
- 提取新的 USleep 外部 IP 地址以备后用
export USLEEP_EXTERNAL_IP=<add your external IP>
重新运行工作负载
- 删除 UWear 虚拟机实例
gcloud config set project $UWEAR_PROJECT_ID
gcloud compute instances delete uwear --zone $UWEAR_PROJECT_ZONE
- 使用新的外部 IP 重新创建 UWear 虚拟机实例
gcloud compute instances create \
--confidential-compute-type=SEV \
--shielded-secure-boot \
--maintenance-policy=MIGRATE \
--scopes=cloud-platform --zone=${UWEAR_PROJECT_ZONE} \
--image-project=confidential-space-images \
--image-family=confidential-space \
--service-account=${UWEAR_WORKLOAD_SERVICE_ACCOUNT}@${UWEAR_PROJECT_ID}.iam.gserviceaccount.com \
--metadata ^~^tee-image-reference=${UWEAR_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${UWEAR_PROJECT_ID}/${UWEAR_ARTIFACT_REPOSITORY}/${UWEAR_WORKLOAD_IMAGE_NAME}:${UWEAR_WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-container-log-redirect=true~tee-env-remote_ip_addr=$USLEEP_EXTERNAL_IP uwear
- UWear 串行日志中应显示以下消息,并且 USleep 虚拟机不应收到任何敏感数据
OPA policy result values:
[ nonce_verified ]: true
[ issuer_verified ]: true
[ secboot_verified ]: true
[ sw_name_verified ]: true
[ allow ]: false
[ hw_verified ]: true
[ image__digest_verified ]: false
[ audience_verified ]: false
Policy check FAILED
Remote TEE's JWT failed policy check.
Failed to validate claims against OPA policy: remote TEE's JWT failed policy check
9. 清理
清理脚本可用于清理我们在此 Codelab 中创建的资源。在此清理过程中,系统将删除以下资源:
- UWear 服务账号 (
$UWEAR_SERVICE_ACCOUNT
)。 - UWear 工件注册表 (
$UWEAR_ARTIFACT_REPOSITORY
)。 - UWear 计算实例
- USleep 服务账号 (
$USLEEP_SERVICE_ACCOUNT
)。 - USleep 工件注册表 (
$USLEEP_ARTIFACT_REPOSITORY
)。 - USleep 计算实例
./cleanup.sh
如果您已完成探索,不妨考虑按照这些说明删除您的项目。
恭喜
恭喜,您已成功完成此 Codelab!
您了解了如何使用 Confidential Space 安全地共享数据,同时确保数据的机密性。
后续操作
查看下列类似 Codelab…
- 使用 Confidential Space 保护机器学习模型和知识产权
- 如何利用多方计算和 Confidential Space 交易数字资产
- 使用 Confidential Space 分析机密数据