1. Обзор
Confidential Space предлагает безопасный обмен данными и совместную работу между несколькими сторонами, позволяя организациям сохранять конфиденциальность своих данных. Это означает, что организации могут сотрудничать друг с другом, сохраняя при этом контроль над своими данными и защищая их от несанкционированного доступа.
Confidential Space открывает возможности для сценариев, в которых вы хотите получить взаимную выгоду от агрегирования и анализа конфиденциальных, часто регулируемых, данных, сохраняя при этом полный контроль над ними. С помощью Confidential Space организации могут получать взаимную выгоду от агрегирования и анализа конфиденциальных данных, таких как персональные данные (PII), защищенная медицинская информация (PHI), интеллектуальная собственность и криптографические секреты, — сохраняя при этом полный контроль над ними.
Что вам понадобится
- Два отдельных проекта на платформе Google Cloud Platform
- Браузер, например Chrome или Firefox.
- Базовые знания Google Compute Engine , конфиденциальных виртуальных машин , контейнеров и удаленных репозиториев, сертификатов и цепочек сертификатов.
- Базовые знания в области сервисных учетных записей , агента открытых политик , Rego и инфраструктуры открытых ключей.
Что вы узнаете
- Как настроить необходимые облачные ресурсы для работы Confidential Space
- Как запустить рабочую нагрузку на конфиденциальной виртуальной машине, работающей с образом «Конфиденциальное пространство».
- Как авторизовать доступ к защищенным ресурсам на основе атрибутов кода рабочей нагрузки ( что ), среды конфиденциального пространства ( где ) и учетной записи, выполняющей рабочую нагрузку ( кто ).
В этом практическом занятии рассматривается использование Confidential Space с защищенными ресурсами, размещенными вне Google Cloud . Вы узнаете, как запросить пользовательский, автономный токен у службы аттестации Google, указав nonce, аудиторию и тип токена PKI.
В этом практическом задании вы создадите конфиденциальное пространство между вымышленным продуктом — контейнеризированным приложением USleep — и вымышленным продуктом — носимым устройством UWear, подключенным к сети, для расчета качества вашего сна. UWear будет передавать защищенную медицинскую информацию (PHI) в USleep в безопасной, защищенной и изолированной среде (так называемой доверенной среде выполнения или TEE), так что владельцы данных сохранят полную конфиденциальность.
UWear выступает одновременно аудитором рабочих нагрузок и владельцем данных . В качестве аудитора рабочих нагрузок он проверяет код выполняемой рабочей нагрузки и фиксирует дайджест образа. В качестве владельца данных UWear разрабатывает логику проверки действительности токена и его подписи. Он создает политику проверки, используя дайджест образа проверенной рабочей нагрузки, которая разрешает доступ к конфиденциальным данным только определенному дайджесту образа в определенной среде.
В этом практическом задании компания USleep развертывает контейнеризированное приложение. USleep не имеет доступа к конфиденциальным данным, но запускает утвержденную рабочую нагрузку, которой разрешен доступ к конфиденциальным данным.
Практическое занятие включает следующие этапы:
- Шаг 1: Настройте необходимые облачные ресурсы для практического занятия. Настройте проекты, выставление счетов и права доступа. Загрузите исходный код практического занятия и установите переменные среды.
- Шаг 2: Загрузите корневой сертификат и сохраните его вместе с исходным кодом UWear.
- Шаг 3: Создайте отдельные учетные записи службы рабочей нагрузки, которые будут использоваться виртуальной машиной рабочей нагрузки для USleep и UWear.
- Шаг 4: Создайте рабочую нагрузку USleep, которая предоставляет токен аттестации.
- Шаг 5: Создайте рабочую нагрузку UWear, которая проверяет токен подтверждения и отправляет конфиденциальные данные, если токен одобрен.
- Шаг 6: Запустите рабочие нагрузки USleep и UWear. UWear предоставит конфиденциальные данные, а USleep применит к этим данным алгоритм анализа сна и выдаст результат.
- Шаг 7: (Необязательно) Запустите несанкционированную рабочую нагрузку USleep и убедитесь, что конфиденциальные данные не были получены от UWear.
- Шаг 8: Очистите все ресурсы.
Понимание рабочего процесса
USleep будет запускать рабочую нагрузку в конфиденциальном пространстве. Для запуска рабочей нагрузки ей необходим доступ к конфиденциальной медицинской информации UWear. Для получения доступа рабочая нагрузка USleep сначала создает защищенное TLS-соединение. Затем USleep также запросит токен аттестации у службы аттестации Google с полезной нагрузкой .
USleep запросит токен подтверждения с JSON-данными, содержащими три элемента:
- Токен аттестации, привязанный к TLS-сессии . Для привязки токена аттестации к TLS-сессии значение nonce будет представлять собой хеш экспортированного ключевого материала TLS . Привязка токена к TLS-сессии гарантирует отсутствие атак типа «машина-посредник», поскольку только две стороны, участвующие в TLS-сессии, смогут сгенерировать значение nonce.
- Будет предоставлена целевая аудитория для «uwear». UWear проверит, что именно она является целевой аудиторией для токена подтверждения.
- Тип токена "PKI". Тип токена "PKI" означает, что USleep хочет запросить автономный токен. Подпись автономного токена можно проверить, используя корневой ключ, загруженный с известной PKI-платформы Confidential Space . Это отличается от стандартного типа токена OIDC, подпись которого проверяется с помощью открытого ключа , который регулярно обновляется.

Рабочая нагрузка USleep получает токен аттестации. Затем UWear устанавливает TLS-соединение с USleep и получает токен аттестации от USleep. UWear проверяет токен, сверяя утверждение x5c с корневым сертификатом.
Компания UWear одобрит рабочую нагрузку USleep, если:
- Токен проходит проверку в соответствии с логикой PKI .
- UWear проверит токен, сопоставив утверждение x5c с корневым сертификатом, убедившись, что токен подписан конечным сертификатом, и, наконец, проверив, что загруженный корневой сертификат совпадает с корневым сертификатом, указанным в утверждении x5c.
- Утверждения об измерении рабочей нагрузки в токене соответствуют условиям атрибутов, указанным в политике OPA . OPA — это открытый, универсальный механизм управления политиками, который объединяет обеспечение соблюдения политик на всех уровнях. OPA использует документы с синтаксисом, аналогичным JSON, для установки базовых значений, на основе которых проверяется политика. Пример того, какие значения проверяет политика, см. в разделе «Базовые значения OPA» .
- Значение nonce соответствует ожидаемому значению nonce ( экспортируемый ключевой материал TLS). Это проверяется в политике OPA, указанной выше.
После завершения всех проверок и их успешного прохождения, UWear сможет подтвердить, что данные будут отправлены и обработаны безопасно. Затем UWear отправит конфиденциальную медицинскую информацию по тому же TLS-соединению, и USleep сможет использовать эти данные для расчета качества сна клиента.
2. Настройка облачных ресурсов
Прежде чем начать
- Создайте два проекта Google Cloud: один для USleep, а другой для UWear. Более подробную информацию о создании проекта Google Cloud см. в руководстве «Настройка и работа с первым проектом Google» . Подробную информацию о том, как получить идентификатор проекта и чем он отличается от имени и номера проекта, можно найти в разделе «Создание и управление проектами» .
- Включите выставление счетов для ваших проектов.
- В Cloud Shell одного из ваших проектов Google установите необходимые переменные среды проекта, как показано ниже.
export UWEAR_PROJECT_ID=<Google Cloud project id of UWear>
export USLEEP_PROJECT_ID=<Google Cloud project id of USleep>
- Включите 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вам потребуются права администратора реестра артефактов и администратора учетной записи службы .
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вам потребуются права администратора вычислительных ресурсов , администратора хранилища , администратора реестра артефактов и администратора учетных записей служб .
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, чтобы получить необходимые скрипты, используемые в рамках этого практического занятия.
git clone https://github.com/GoogleCloudPlatform/confidential-space.git
- Перейдите в каталог скриптов для практического занятия по анализу медицинских данных.
cd confidential-space/codelabs/health_data_analysis_codelab/scripts
- Обновите эти две строки в скрипте config_env.sh , расположенном в каталоге codelabs/health_data_analysis_codelab/scripts. Замените идентификаторы проектов на идентификаторы ваших проектов USleep и UWear. Обязательно удалите символ комментария "#" в начале строки.
# TODO: Populate UWear and USleep Project IDs
export UWEAR_PROJECT_ID=your-uwear-project-id
export USLEEP_PROJECT_ID=your-usleep-project-id
- Необязательно: задайте любые существующие переменные . Вы можете переопределить имена ресурсов, используя эти переменные (например,
export UWEAR_ARTIFACT_REPOSITORY='my-artifact-repository').
- Вы можете задать следующие переменные, используя существующие имена облачных ресурсов. Если переменная задана, будет использоваться соответствующий существующий облачный ресурс из проекта. Если переменная не задана, имя облачного ресурса будет сгенерировано из значений в скрипте config_env.sh .
- Запустите скрипт config_env.sh , чтобы присвоить оставшимся переменным значения, основанные на идентификаторе вашего проекта для имен ресурсов.
# Navigate to the scripts folder
cd ~/confidential-space/codelabs/health_data_analysis_codelab/scripts
# Run the config_env script
source config_env.sh
# Verify the variables were set
# Expected output for default variable should be `workload-sa`
echo $USLEEP_WORKLOAD_SERVICE_ACCOUNT
3. Загрузите корневой сертификат.
- Для проверки автономный токен, возвращаемый службой аттестации, UWear необходимо проверить подпись по корневому сертификату конфиденциального пространства. UWear потребуется загрузить корневой сертификат и сохранить его локально. В консоли одного из ваших проектов Google Cloud выполните следующие команды:
cd ~/confidential-space/codelabs/health_data_analysis_codelab/src/uwear
wget https://confidentialcomputing.googleapis.com/.well-known/confidential_space_root.crt -O confidential_space_root.pem
- Сгенерировать отпечаток корневого сертификата, который был загружен.
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.
В рамках этого этапа вы создадите образы 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.
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. Этот скрипт:
- Создает реестр артефактов (
$USLEEP_ARTIFACT_REPOSITORY), принадлежащий UWear, куда будет опубликована рабочая нагрузка. - Создает и компилирует код usleep/workload.go и упаковывает его в образ Docker. См. конфигурацию Dockerfile для USleep.
- Публикует образ Docker в реестре артефактов (
$USLEEP_ARTIFACT_REPOSITORY), принадлежащем UWear. - Предоставляет учетной записи службы
$USLEEP_WORKLOAD_SERVICE_ACCOUNTразрешение на чтение реестра артефактов ($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
- Замените значение в поле "allowed_submods_container_image_digest" в файле opa_validation_values.json на значение USLEEP_IMAGE_DIGEST.
# Replace the image digest
sed -i 's/sha256:bc4c32cb2ca046ba07dcd964b07a320b7d0ca88a5cf8e979da15cae68a2103ee/sha256:<USLEEP_IMAGE_DIGEST>/' ~/confidential-space/codelabs/health_data_analysis_codelab/src/uwear/opa_validation_values.json
6. Создайте рабочую нагрузку UWear.
О нагрузке на UWear
В состав рабочей нагрузки UWear входят 4 основных компонента:
- Подключение к той же TLS-сессии, которая была создана в рабочей нагрузке USleep, и получение токена аттестации от USleep через защищенную TLS-сессию.
func main() {
fmt.Println("Initializing client...")
tlsconfig := &tls.Config{
// Skipping client verification of the server's certificate chain and host name since we are
// doing custom verification using the attestation token.
InsecureSkipVerify: true,
}
dialer := websocket.Dialer{
TLSClientConfig: tlsconfig,
HandshakeTimeout: 5 * time.Second,
}
ipAddress := os.Getenv(ipAddrEnvVar)
url := fmt.Sprintf("wss://%s:8081/connection", ipAddress)
fmt.Printf("Attempting to dial to url %v...\n", url)
conn, _, err := dialer.Dial(url, nil)
if err != nil {
fmt.Printf("Failed to dial to url %s, err %v\n", url, err)
return
}
defer conn.Close()
tokenString, ekm, err := retrieveTokenAndEKMFromConn(conn)
if err != nil {
fmt.Printf("Failed to retrieve token and EKM from connection: %v\n", err)
return
}
fmt.Printf("token: %v\n", tokenString)
...
}
- Проверка самодостаточного токена осуществляется следующим образом:
- Проверка наличия в утверждении 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"
]
}
- Пример политики OPA , составленной на языке Rego.
package confidential_space
import rego.v1
default allow := false
default hw_verified := false
default image_digest_verified := false
default audience_verified := false
default nonce_verified := false
default issuer_verified := false
default secboot_verified := false
default sw_name_verified := false
allow if {
hw_verified
image_digest_verified
audience_verified
nonce_verified
issuer_verified
secboot_verified
sw_name_verified
}
hw_verified if input.hwmodel in data.allowed_hwmodel
image_digest_verified if input.submods.container.image_digest in data.allowed_submods_container_image_digest
audience_verified if input.aud in data.allowed_aud
issuer_verified if input.iss in data.allowed_issuer
secboot_verified if input.secboot in data.allowed_secboot
sw_name_verified if input.swname in data.allowed_sw_name
nonce_verified if {
input.eat_nonce == "%s"
}
- Пример запроса по регистрационному номеру.
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 — EKM). Проверка nonce выполняется в политике OPA с использованием EKM, передаваемого в оценщик политик .
Пример кода для получения хеша 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
}
- После завершения всех проверок и их успешного прохождения, UWear сможет подтвердить, что данные будут отправлены и обработаны безопасно. Затем UWear отправит конфиденциальную медицинскую информацию по тому же TLS-соединению, и 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_REPOSITORY), принадлежащий UWear, куда будет опубликована рабочая нагрузка. - Создает и компилирует код uwear/workload.go и упаковывает его в образ Docker. См. конфигурацию Dockerfile для USleep.
- Публикует образ Docker в реестре артефактов (
$UWEAR_ARTIFACT_REPOSITORY), принадлежащем UWear. - Предоставляет учетной записи службы
$UWEAR_WORKLOAD_SERVICE_ACCOUNTразрешение на чтение реестра артефактов ($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 для обмена важной информацией!
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".В этом примере, чтобы изменить дайджест изображения, мы изменим значение audience на другое, которое 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
- Извлеките новый внешний IP-адрес USleep для дальнейшего использования.
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
- Создайте заново экземпляр виртуальной машины UWear, используя новый внешний IP-адрес.
gcloud compute instances create \
--confidential-compute-type=SEV \
--shielded-secure-boot \
--maintenance-policy=MIGRATE \
--scopes=cloud-platform --zone=${UWEAR_PROJECT_ZONE} \
--image-project=confidential-space-images \
--image-family=confidential-space \
--service-account=${UWEAR_WORKLOAD_SERVICE_ACCOUNT}@${UWEAR_PROJECT_ID}.iam.gserviceaccount.com \
--metadata ^~^tee-image-reference=${UWEAR_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/${UWEAR_PROJECT_ID}/${UWEAR_ARTIFACT_REPOSITORY}/${UWEAR_WORKLOAD_IMAGE_NAME}:${UWEAR_WORKLOAD_IMAGE_TAG}~tee-restart-policy=Never~tee-container-log-redirect=true~tee-env-remote_ip_addr=$USLEEP_EXTERNAL_IP uwear
- В последовательных журналах 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. Уборка
Скрипт очистки можно использовать для удаления ресурсов, созданных нами в рамках этого практического задания. В ходе этой очистки будут удалены следующие ресурсы:
- Сервисный аккаунт UWear (
$UWEAR_SERVICE_ACCOUNT). - Реестр артефактов UWear (
$UWEAR_ARTIFACT_REPOSITORY). - Вычислительный экземпляр UWear
- Учетная запись службы USleep (
$USLEEP_SERVICE_ACCOUNT). - Реестр артефактов USleep (
$USLEEP_ARTIFACT_REPOSITORY). - Вычислительный экземпляр USleep
./cleanup.sh
Если вы завершили изучение проекта, пожалуйста, удалите его, следуя этим инструкциям .
Поздравляем!
Поздравляем, вы успешно завершили практическое занятие!
Вы научились безопасно обмениваться данными, сохраняя при этом их конфиденциальность, используя Confidential Space.
Что дальше?
Посмотрите похожие обучающие материалы по программированию...
- Защита моделей машинного обучения и интеллектуальной собственности с помощью конфиденциального пространства.
- Как осуществлять операции с цифровыми активами с использованием многосторонних вычислений и конфиденциальных сред
- Анализируйте конфиденциальные данные с помощью конфиденциальных пространств.
Дополнительная информация
- Чувствуете себя изолированными? На помощь придут конфиденциальные вычислительные системы.
- Конфиденциальные вычисления в GCP
- Конфиденциальное пространство: будущее сотрудничества с сохранением конфиденциальности.
- Как Google и Intel повышают безопасность конфиденциальных вычислений
- Конфиденциальность против прогресса — повышение безопасности с помощью конфиденциальных вычислений в Google Cloud