1. खास जानकारी
कॉन्फ़िडेंशियल वर्चुअल मशीनें (सीवीएम), Compute Engine वर्चुअल मशीनें होती हैं. ये हार्डवेयर पर आधारित मेमोरी एन्क्रिप्शन का इस्तेमाल करती हैं. इससे यह पक्का करने में मदद मिलती है कि इस्तेमाल के दौरान, आपके डेटा और ऐप्लिकेशन को पढ़ा या बदला न जा सके. इस कोडलैब में, रिमोट अटैस्टेशन के बारे में बताया गया है. इससे कोई रिमोट पार्टी, सीवीएम नोड की पुष्टि कर सकती है. इसके अलावा, आपको ऑडिटिंग के लिए क्लाउड लॉगिंग के बारे में भी जानकारी मिलेगी.

ऊपर दिए गए डायग्राम में दिखाया गया है कि इस कोडलैब में ये चरण शामिल हैं:
- CVM सेटअप और रिमोट अटेंशन
- CVM की रिमोट अटेस्टेशन सुविधा के लिए, Cloud Logging एक्सप्लोरेशन
- सीक्रेट रिलीज़ वेब सर्वर सेटअप करना
ऊपर दिए गए डायग्राम में, go-tpm टूल और Google Cloud Attestation जैसे कॉम्पोनेंट शामिल हैं:
- go-tpm टूल: यह एक ओपन सोर्स टूल है. इसका इस्तेमाल, CVM पर मौजूद vTPM (वर्चुअल ट्रस्टेड प्लैटफ़ॉर्म मॉड्यूल) से पुष्टि करने के सबूत पाने के लिए किया जाता है. साथ ही, इसे Google Cloud Attestation को भेजा जाता है, ताकि पुष्टि करने वाला टोकन मिल सके.
- Google Cloud Attestation: पुष्टि करने के लिए मिले सबूत की पुष्टि करना और उसे मान्य करना. साथ ही, पुष्टि करने का ऐसा टोकन वापस भेजना जिसमें अनुरोध करने वाले के CVM के बारे में जानकारी हो.
आपको क्या सीखने को मिलेगा
- CVM पर Confidential Computing API के ज़रिए, रिमोट अटेंशन कैसे किया जाता है
- CVM की रिमोट अटैस्टेशन की प्रोसेस को मॉनिटर करने के लिए, Cloud Logging का इस्तेमाल कैसे करें
आपको किन चीज़ों की ज़रूरत होगी
- Google Cloud Platform प्रोजेक्ट
- Chrome या Firefox जैसे ब्राउज़र
- Google Compute Engine और Confidential VM के बारे में बुनियादी जानकारी
2. सेटअप और ज़रूरी शर्तें
ज़रूरी एपीआई चालू करने के लिए, क्लाउड कंसोल या अपने लोकल डेवलपमेंट एनवायरमेंट में यह कमांड चलाएं:
gcloud auth login
gcloud services enable \
cloudapis.googleapis.com \
cloudshell.googleapis.com \
confidentialcomputing.googleapis.com \
compute.googleapis.com
3. CVM और vTPM रिमोट अटेस्टेशन सेट अप करना
इस चरण में, आपको सीवीएम बनाना होगा. साथ ही, सीवीएम पर पुष्टि करने वाला टोकन (ओआईडीसी टोकन) पाने के लिए, वीटीपीएम की रिमोट पुष्टि करनी होगी.
Cloud Console या अपने लोकल डेवलपमेंट एनवायरमेंट पर जाएं. नीचे दिए गए तरीके से सीवीएम बनाएं. इसके लिए, Create a Confidential VM instance | Google Cloud लेख पढ़ें. गोपनीय कंप्यूटिंग एपीआई ऐक्सेस करने के लिए, स्कोप की ज़रूरत होती है.
gcloud config set project <project-id>
gcloud compute instances create cvm-attestation-codelab \
--machine-type=n2d-standard-2 \
--min-cpu-platform="AMD Milan" \
--zone=us-central1-c \
--confidential-compute \
--image=ubuntu-2204-jammy-v20240228 \
--image-project=ubuntu-os-cloud \
--scopes https://www.googleapis.com/auth/cloud-platform
CVM के डिफ़ॉल्ट सेवा खाते को कॉन्फ़िडेंशियल कंप्यूटिंग एपीआई ऐक्सेस करने की अनुमति दें. CVM को कॉन्फ़िडेंशियल कंप्यूटिंग एपीआई से अटेस्टेशन टोकन पाने के लिए, इन अनुमतियों की ज़रूरत होती है:
1). कॉन्फ़िडेंशियल कंप्यूटिंग एपीआई का ऐक्सेस देने के लिए कोई भूमिका बनाएं.
gcloud iam roles create Confidential_Computing_User --project=<project-id> \
--title="CVM User" --description="Grants the ability to generate an attestation token in a CVM." \
--permissions="confidentialcomputing.challenges.create,confidentialcomputing.challenges.verify,confidentialcomputing.locations.get,confidentialcomputing.locations.list" --stage=GA
इनकी जगह ये डालें:
project-idप्रोजेक्ट का यूनीक आइडेंटिफ़ायर है.
2). वीएम के डिफ़ॉल्ट सेवा खाते को भूमिका में जोड़ें.
gcloud projects add-iam-policy-binding <project-id> \
--member serviceAccount:$(gcloud iam service-accounts list --filter="email ~ compute@developer.gserviceaccount.com$" --format='value(email)'
) \
--role "projects/<project-id>/roles/Confidential_Computing_User"
इनकी जगह ये डालें:
project-idप्रोजेक्ट का यूनीक आइडेंटिफ़ायर है.
3). CVM से कनेक्ट करें और go-tpm टूल बाइनरी सेट अप करें. इससे Google Cloud Attestation की ओर से उपलब्ध कराए गए Confidential Computing API से, Attestation Token फ़ेच किया जा सकेगा.
- सीवीएम से कनेक्ट करें.
gcloud compute ssh --zone us-central1-c cvm-attestation-codelab
- Go एनवायरमेंट सेट अप करने का तरीका:
wget https://go.dev/dl/go1.22.0.linux-amd64.tar.gz sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz export PATH=$PATH:/usr/local/go/bin
- go-tpm टूल की बाइनरी बनाएं. go-tpm टूल बाइनरी, CVM पर मौजूद vTPM से पुष्टि का सबूत फ़ेच करती है. इसके बाद, पुष्टि के टोकन के लिए इसे Google Cloud Attestation को भेजती है.
git clone https://github.com/google/go-tpm-tools.git --depth 1 cd go-tpm-tools/cmd/gotpm/ go build
- go-tpm टूल कमांड, vTPM से CVM के अटेस्टेशन का सबूत निकालती है और उसे Google Cloud Attestation को भेजती है. Google Cloud Attestation, पुष्टि करने से जुड़े सबूत की पुष्टि करता है और उसे मान्य करता है. साथ ही, Attestation Token दिखाता है. इस कमांड से attestation_token फ़ाइल बनती है. इसमें आपका
attestation-tokenशामिल होता है. बाद में सीक्रेट पाने के लिए, आपकोattestation-tokenका इस्तेमाल करना होगा. दावों को देखने के लिए, पुष्टि करने वाले टोकन को jwt.io में डिकोड किया जा सकता है.
sudo ./gotpm token > attestation_token
- (ज़रूरी नहीं) go-tpm टूल और Google Cloud Attestation की मदद से रिमोट अटेस्टेशन करने के बजाय, हम vTPM अटेस्टेशन के सबूत पाने के लिए कमांड दिखाते हैं. इस तरह, पुष्टि करने के सबूत की पुष्टि करने और उसे मान्य करने के लिए, Google Cloud Attestation जैसी सेवा बनाई जा सकती है:
nonce=$(head -c 16 /dev/urandom | xxd -p) sudo ./gotpm attest --nonce $nonce --format textproto --output quote.dat sudo ./gotpm verify debug --nonce $nonce --format textproto --input quote.dat --output vtpm_report
vtpm_report में पुष्टि किया गया इवेंटलॉग शामिल होता है. इसे देखने के लिए, अपने पसंदीदा एडिटर का इस्तेमाल किया जा सकता है. ध्यान दें कि verify कमांड, कोट की पुष्टि करने वाली कुंजी के सर्टिफ़िकेट की जांच नहीं करती है.
4. Cloud Logging चालू करें और रिमोट अटेस्टेशन लॉग देखें
अपने cvm-attestation-codelab CVM में यहां दिया गया कमांड चलाएं. इस बार, यह Cloud Logging पर गतिविधि को लॉग करता है.
sudo ./gotpm token --cloud-log --audience "https://api.cvm-attestation-codelab.com"
अपने क्लाउड कंसोल या लोकल डेवलपमेंट एनवायरमेंट में cvm-attestation-codelab <instance-id> पाएं.
gcloud compute instances describe cvm-attestation-codelab --zone us-central1-c --format='value(id)'
Cloud Logging के बारे में जानने के लिए, यह यूआरएल खोलें: https://console.cloud.google.com/logs. क्वेरी फ़ील्ड में, यह डालें:
resource.type="gce_instance" resource.labels.zone="us-central1-c" resource.labels.instance_id=<instance-id> log_name="projects/<project-id>/logs/gotpm" severity>=DEFAULT
इनकी जगह ये डालें:
project-idप्रोजेक्ट का यूनीक आइडेंटिफ़ायर है.instance-idइंस्टेंस का यूनीक आइडेंटिफ़ायर है.
आपको अटेस्टेशन टोकन, उसके दावे, रॉ एविडेंस, और Google Cloud Attestation को भेजा गया नॉनस मिल जाना चाहिए.
5. सीक्रेट रिलीज़ वेब सर्वर सेट अप करना
इस चरण में, आपको पिछले SSH सेशन से बाहर निकलना होगा और दूसरी वर्चुअल मशीन सेट अप करनी होगी. इस वीएम पर, आपको सीक्रेट रिलीज़ वेब सर्वर सेट अप करना होगा. वेब सर्वर, मिले हुए Attestation Token और उसके दावों की पुष्टि करता है. अगर पुष्टि हो जाती है, तो यह अनुरोध करने वाले व्यक्ति को सीक्रेट जारी कर देता है.
1). Cloud Console या अपने लोकल डेवलपमेंट एनवायरमेंट पर जाएं. वर्चुअल मशीन बनाएं.
gcloud config set project <project-id>
gcloud compute instances create cvm-attestation-codelab-web-server \
--machine-type=n2d-standard-2 \
--zone=us-central1-c \
--image=ubuntu-2204-jammy-v20240228 \
--image-project=ubuntu-os-cloud
इनकी जगह ये डालें:
project-idप्रोजेक्ट का यूनीक आइडेंटिफ़ायर है.
2). अपने नए वीएम से एसएसएच करें.
gcloud compute ssh --zone us-central1-c cvm-attestation-codelab-web-server
3). Go एनवायरमेंट सेट अप करें.
wget https://go.dev/dl/go1.22.0.linux-amd64.tar.gz sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz export PATH=$PATH:/usr/local/go/bin
4). सीक्रेट रिलीज़ वेब सर्वर के सोर्स कोड को सेव करने के लिए, यहां दी गई दो फ़ाइलें बनाएं (nano की मदद से कॉपी/चिपकाएं).
main.go
package main
import (
"fmt"
"net/http"
"strings"
"time"
"log"
"github.com/golang-jwt/jwt/v4"
)
const (
theSecret = "This is the super secret information!"
)
func homePage(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if tokenString != "" {
tokenString, err := extractToken(tokenString)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
}
tokenBytes := []byte(tokenString)
// A method to return a public key from the well-known endpoint
keyFunc := getRSAPublicKeyFromJWKsFile
token, err := decodeAndValidateToken(tokenBytes, keyFunc)
if err != nil {
http.Error(w, "Invalid JWT Token", http.StatusUnauthorized)
}
if ok, err := isValid(token.Claims.(jwt.MapClaims)); ok {
fmt.Fprintln(w, theSecret)
} else {
if err != nil {
http.Error(w, "Error validating JWT claims: "+err.Error(), http.StatusUnauthorized)
} else {
http.Error(w, "Invalid JWT token Claims", http.StatusUnauthorized)
}
}
} else {
http.Error(w, "Authorization token required", http.StatusUnauthorized)
}
}
func extractToken(tokenString string) (string, error) {
if strings.HasPrefix(tokenString, "Bearer ") {
return strings.TrimPrefix(tokenString, "Bearer "), nil
}
return "", fmt.Errorf("invalid token format")
}
func isValid(claims jwt.MapClaims) (bool, error) {
// 1. Evaluating Standard Claims:
subject, ok := claims["sub"].(string)
if !ok {
return false, fmt.Errorf("missing or invalid 'sub' claim")
}
fmt.Println("Subject:", subject)
// e.g. "sub":"https://www.googleapis.com/compute/v1/projects/<project_id>/zones/<project_zone>/instances/<instance_name>"
issuedAt, ok := claims["iat"].(float64)
if !ok {
return false, fmt.Errorf("missing or invalid 'iat' claim")
}
fmt.Println("Issued At:", time.Unix(int64(issuedAt), 0))
// 2. Evaluating Remote Attestation Claims:
hwModel, ok := claims["hwmodel"].(string)
if !ok || hwModel != "GCP_AMD_SEV" {
return false, fmt.Errorf("missing or invalid 'hwModel'")
}
fmt.Println("hwmodel:", hwModel)
swName, ok := claims["swname"].(string)
if !ok || swName != "GCE" {
return false, fmt.Errorf("missing or invalid 'hwModel'")
}
fmt.Println("swname:", swName)
return true, nil
}
func main() {
http.HandleFunc("/", homePage)
fmt.Println("Server listening on :8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatalf("Server failed to start: %v", err)
}
}
helper.go
package main
import (
"crypto/rsa"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"math/big"
"net/http"
"github.com/golang-jwt/jwt/v4"
)
const (
socketPath = "/run/container_launcher/teeserver.sock"
expectedIssuer = "https://confidentialcomputing.googleapis.com"
wellKnownPath = "/.well-known/openid-configuration"
)
type jwksFile struct {
Keys []jwk `json:"keys"`
}
type jwk struct {
N string `json:"n"` // "nMMTBwJ7H6Id8zUCZd-L7uoNyz9b7lvoyse9izD9l2rtOhWLWbiG-7pKeYJyHeEpilHP4KdQMfUo8JCwhd-OMW0be_XtEu3jXEFjuq2YnPSPFk326eTfENtUc6qJohyMnfKkcOcY_kTE11jM81-fsqtBKjO_KiSkcmAO4wJJb8pHOjue3JCP09ZANL1uN4TuxbM2ibcyf25ODt3WQn54SRQTV0wn098Y5VDU-dzyeKYBNfL14iP0LiXBRfHd4YtEaGV9SBUuVhXdhx1eF0efztCNNz0GSLS2AEPLQduVuFoUImP4s51YdO9TPeeQ3hI8aGpOdC0syxmZ7LsL0rHE1Q",
E string `json:"e"` // "AQAB" or 65537 as an int
Kid string `json:"kid"` // "1f12fa916c3a0ef585894b4b420ad17dc9d6cdf5",
// Unused fields:
// Alg string `json:"alg"` // "RS256",
// Kty string `json:"kty"` // "RSA",
// Use string `json:"use"` // "sig",
}
type wellKnown struct {
JwksURI string `json:"jwks_uri"` // "https://www.googleapis.com/service_accounts/v1/metadata/jwk/signer@confidentialspace-sign.iam.gserviceaccount.com"
// Unused fields:
// Iss string `json:"issuer"` // "https://confidentialcomputing.googleapis.com"
// Subject_types_supported string `json:"subject_types_supported"` // [ "public" ]
// Response_types_supported string `json:"response_types_supported"` // [ "id_token" ]
// Claims_supported string `json:"claims_supported"` // [ "sub", "aud", "exp", "iat", "iss", "jti", "nbf", "dbgstat", "eat_nonce", "google_service_accounts", "hwmodel", "oemid", "secboot", "submods", "swname", "swversion" ]
// Id_token_signing_alg_values_supported string `json:"id_token_signing_alg_values_supported"` // [ "RS256" ]
// Scopes_supported string `json:"scopes_supported"` // [ "openid" ]
}
func getWellKnownFile() (wellKnown, error) {
httpClient := http.Client{}
resp, err := httpClient.Get(expectedIssuer + wellKnownPath)
if err != nil {
return wellKnown{}, fmt.Errorf("failed to get raw .well-known response: %w", err)
}
wellKnownJSON, err := io.ReadAll(resp.Body)
if err != nil {
return wellKnown{}, fmt.Errorf("failed to read .well-known response: %w", err)
}
wk := wellKnown{}
json.Unmarshal(wellKnownJSON, &wk)
return wk, nil
}
func getJWKFile() (jwksFile, error) {
wk, err := getWellKnownFile()
if err != nil {
return jwksFile{}, fmt.Errorf("failed to get .well-known json: %w", err)
}
// Get JWK URI from .wellknown
uri := wk.JwksURI
fmt.Printf("jwks URI: %v\n", uri)
httpClient := http.Client{}
resp, err := httpClient.Get(uri)
if err != nil {
return jwksFile{}, fmt.Errorf("failed to get raw JWK response: %w", err)
}
jwkbytes, err := io.ReadAll(resp.Body)
if err != nil {
return jwksFile{}, fmt.Errorf("failed to read JWK body: %w", err)
}
file := jwksFile{}
err = json.Unmarshal(jwkbytes, &file)
if err != nil {
return jwksFile{}, fmt.Errorf("failed to unmarshall JWK content: %w", err)
}
return file, nil
}
// N and E are 'base64urlUInt' encoded: https://www.rfc-editor.org/rfc/rfc7518#section-6.3
func base64urlUIntDecode(s string) (*big.Int, error) {
b, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
return nil, err
}
z := new(big.Int)
z.SetBytes(b)
return z, nil
}
func getRSAPublicKeyFromJWKsFile(t *jwt.Token) (any, error) {
keysfile, err := getJWKFile()
if err != nil {
return nil, fmt.Errorf("failed to fetch the JWK file: %w", err)
}
// Multiple keys are present in this endpoint to allow for key rotation.
// This method finds the key that was used for signing to pass to the validator.
kid := t.Header["kid"]
for _, key := range keysfile.Keys {
if key.Kid != kid {
continue // Select the key used for signing
}
n, err := base64urlUIntDecode(key.N)
if err != nil {
return nil, fmt.Errorf("failed to decode key.N %w", err)
}
e, err := base64urlUIntDecode(key.E)
if err != nil {
return nil, fmt.Errorf("failed to decode key.E %w", err)
}
// The parser expects an rsa.PublicKey: https://github.com/golang-jwt/jwt/blob/main/rsa.go#L53
// or an array of keys. We chose to show passing a single key in this example as its possible
// not all validators accept multiple keys for validation.
return &rsa.PublicKey{
N: n,
E: int(e.Int64()),
}, nil
}
return nil, fmt.Errorf("failed to find key with kid '%v' from well-known endpoint", kid)
}
func decodeAndValidateToken(tokenBytes []byte, keyFunc func(t *jwt.Token) (any, error)) (*jwt.Token, error) {
var err error
fmt.Println("Unmarshalling token and checking its validity...")
token, err := jwt.NewParser().Parse(string(tokenBytes), keyFunc)
fmt.Printf("Token valid: %v\n", token.Valid)
if token.Valid {
return token, nil
}
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return nil, fmt.Errorf("token format invalid. Please contact the Confidential Space team for assistance")
}
if ve.Errors&(jwt.ValidationErrorNotValidYet) != 0 {
// If device time is not synchronized with the Attestation Service you may need to account for that here.
return nil, errors.New("token is not active yet")
}
if ve.Errors&(jwt.ValidationErrorExpired) != 0 {
return nil, fmt.Errorf("token is expired")
}
return nil, fmt.Errorf("unknown validation error: %v", err)
}
return nil, fmt.Errorf("couldn't handle this token or couldn't read a validation error: %v", err)
}
5). वेब सर्वर बनाने और उसे चलाने के लिए, यहां दिए गए कमांड चलाएं. इससे :8080 पोर्ट पर सीक्रेट रिलीज़ वेबसर्वर शुरू होता है.
go mod init google.com/codelab go mod tidy go get github.com/golang-jwt/jwt/v4 go build ./codelab
समस्या हल करना: आपको यह चेतावनी दिख सकती है. go mod tidy: चलाने पर इसे अनदेखा किया जा सकता है
go: finding module for package github.com/golang-jwt/jwt/v4 go: downloading github.com/golang-jwt/jwt v3.2.2+incompatible go: downloading github.com/golang-jwt/jwt/v4 v4.5.0 go: found github.com/golang-jwt/jwt/v4 in github.com/golang-jwt/jwt/v4 v4.5.0 go: google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible: import path "google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible" should not have @version go: google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/cmd/jwt: import path "google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/cmd/jwt" should not have @version go: google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/request: import path "google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/request" should not have @version go: google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/test: import path "google.com/codelab/go/pkg/mod/github.com/golang-jwt/jwt@v3.2.2+incompatible/test" should not have @version
6). अब कोई दूसरा क्लाउड कंसोल टैब या लोकल डेवलपमेंट एनवायरमेंट सेशन शुरू करें और यहां दिया गया कमांड चलाएं. इससे आपको <cvm-attestation-codelab-web-server-internal-ip> मिल जाएगा.
gcloud compute instances describe cvm-attestation-codelab-web-server --format='get(networkInterfaces[0].networkIP)' --zone=us-central1-c
7). अपने cvm-attestation-codelab वीएम इंस्टेंस से एसएसएच करें.
gcloud compute ssh --zone "us-central1-c" "cvm-attestation-codelab"
8). नीचे दी गई कमांड, पहले से मिले attestation-token (~/go-tpm-tools/cmd/gotpm/ में) को बदल देती है. इससे आपको सीक्रेट रिलीज़ करने वाले वेब सर्वर का सीक्रेट मिल जाएगा!
cd ~/go-tpm-tools/cmd/gotpm/ curl http://<cvm-attestation-codelab-web-server-internal-ip>:8080 -H "Authorization: Bearer $(cat ./attestation_token)"
इनकी जगह ये डालें:
cvm-attestation-codelab-web-server-internal-ip, cvm-attestation-codelab-web-server VM इंस्टेंस का इंटरनल आईपी है.
आपको अपनी स्क्रीन पर "यह बहुत गोपनीय जानकारी है!" दिखेगा.
गलत या समयसीमा खत्म हो चुके attestation-token डालने पर, आपको "curl: (52) Empty reply from server" दिखेगा. आपको cvm-attestation-codelab-web-server वीएम इंस्टेंस में, सीक्रेट रिलीज़ वेब सर्वर के लॉग में "टोकन मान्य है: गलत" भी दिखेगा.
6. साफ़-सफ़ाई सेवा
क्लाउड कंसोल या अपने लोकल डेवलपमेंट एनवायरमेंट में, ये कमांड चलाएं:
# Delete the role binding
gcloud projects remove-iam-policy-binding <project-id> \
--member serviceAccount:$(gcloud iam service-accounts list --filter="email ~ compute@developer.gserviceaccount.com$" --format='value(email)'
) \
--role "projects/<project-id>/roles/Confidential_Computing_User"
# Delete the role
gcloud iam roles delete Confidential_Computing_User --project=<project-id>
# Delete the web server VM instance
gcloud compute instances delete cvm-attestation-codelab-web-server --zone=us-central1-c
# Delete the CVM instance
gcloud compute instances delete cvm-attestation-codelab --zone=us-central1-c
इनकी जगह ये डालें:
project-idप्रोजेक्ट का यूनीक आइडेंटिफ़ायर है.
7. आगे क्या करना है
कॉन्फ़िडेंशियल वीएम और Compute Engine के बारे में ज़्यादा जानें.