গোপনীয় GKE নোডগুলিতে (vTPM, SEV-SNP, TDX) দূরবর্তী সত্যায়ন এবং সীলমোহর

১. সংক্ষিপ্ত বিবরণ

কনফিডেনশিয়াল জিকেই (CGKE) নোডগুলো নিশ্চিত করে যে ওয়ার্কলোডের ডেটা ব্যবহারের সময় এনক্রিপ্টেড থাকে। CGKE ওয়ার্কলোডের কাছে vTPM ডিভাইসটিকে উন্মুক্ত করলে, ওয়ার্কলোডগুলো vTPM-এর ফিচারগুলো ব্যবহার করতে পারে। এই কোডল্যাবে, আপনি vTPM-এর ফিচারগুলো শেখার পাশাপাশি cc-device-plugin ব্যবহার করে কীভাবে Intel TDX এবং AMD SEV-SNP হার্ডওয়্যার অ্যাটেস্টেশন কাজে লাগাতে হয়, তাও শিখবেন।

  • vTPM রিমোট অ্যাটেস্টেশন কোনো দূরবর্তী পক্ষকে যাচাই করার সুযোগ দেয় যে, ওয়ার্কলোড হোস্টকারী CGKE নোডগুলো কনফিডেনশিয়াল ভিএম (CVM)-এ চলছে।
  • vTPM অনুমোদন এবং vTPM সীলমোহর।
  • এক্সপোজড ইন্টেল TDX এবং এএমডি SEV-SNP গেস্ট ডিভাইস ব্যবহার করে হার্ডওয়্যার অ্যাটেস্টেশন।

683a3b43587ef69f.png

উপরের চিত্রে যেমন দেখানো হয়েছে, এই কোডল্যাবের প্রথম অংশে নিম্নলিখিত ধাপগুলো অন্তর্ভুক্ত রয়েছে:

  • CGKE নোডগুলি vTPM ডিভাইসটিকে সেটআপ করে এবং নির্বাচিত ওয়ার্কলোডগুলির জন্য উন্মুক্ত করে।
  • একটি ওয়ার্কলোড ডেপ্লয় করুন এবং ওয়ার্কলোডটি হোস্টকারী CGKE নোডটিকে রিমোট অ্যাটেস্ট করুন।
  • সরাসরি TDX বা SEV-SNP ডিভাইস থেকে হার্ডওয়্যার অ্যাটেস্টেশন কোটেশন সংগ্রহ করার জন্য একটি ওয়ার্কলোড স্থাপন করুন।
  • সিক্রেট রিলিজ ওয়েব সার্ভার সেটআপ।

8f6e80c762a5d911.png

উপরের চিত্রে যেমন দেখানো হয়েছে, এই কোডল্যাবের দ্বিতীয় অংশে রয়েছে:

  • CGKE নোডগুলিতে vTPM অনুমোদন সেটআপ এবং vTPM সিলিং।

cc_device_plugin_concept.png

উপরের চিত্রে যেমন দেখানো হয়েছে, এই কোডল্যাবের তৃতীয় অংশে রয়েছে:

  • সেটআপ পর্বের সময় cc-device-plugin কীভাবে হার্ডওয়্যার ডিভাইসগুলোকে নিরাপদে ম্যাপ করে।
  • রানটাইম ফেজে কোনো ওভারহেড ছাড়াই সরাসরি TDX বা SEV-SNP ডিভাইস থেকে হার্ডওয়্যার অ্যাটেস্টেশন কোট সংগ্রহের জন্য ওয়ার্কলোড স্থাপন করা।

আপনি যা শিখবেন

  • vTPM ডিভাইসটিকে কীভাবে CGKE ওয়ার্কলোডের জন্য উন্মুক্ত করা যায়।
  • কনফিডেনশিয়াল কম্পিউটিং এপিআই (অ্যাটেস্টেশন ভেরিফায়ার সার্ভিস)-এর মাধ্যমে সিজিকেই (CGKE) ওয়ার্কলোডগুলিতে কীভাবে রিমোট অ্যাটেস্ট করবেন।
  • vTPM অনুমোদন কীভাবে সেট আপ করবেন এবং vTPM সিলিং কীভাবে সম্পাদন করবেন।
  • cc-device-plugin ব্যবহার করে CGKE ওয়ার্কলোডে SEV-SNP এবং TDX ডিভাইসগুলি কীভাবে অ্যাক্সেস করবেন।
  • go-tdx-guest এবং go-sev-guest ব্যবহার করে কীভাবে হার্ডওয়্যার অ্যাটেস্টেশন কোট সংগ্রহ করবেন।

আপনার যা যা লাগবে

২. সেটআপ এবং প্রয়োজনীয়তা

প্রয়োজনীয় API-গুলো সক্রিয় করতে, ক্লাউড কনসোল অথবা আপনার স্থানীয় ডেভেলপমেন্ট এনভায়রনমেন্টে নিম্নলিখিত কমান্ডটি চালান:

gcloud auth login

gcloud services enable \
    cloudapis.googleapis.com \
    cloudshell.googleapis.com \
    container.googleapis.com \
    artifactregistry.googleapis.com \
    confidentialcomputing.googleapis.com \
    iamcredentials.googleapis.com \
    compute.googleapis.com

৩. শেয়ার্ড CGKE কন্ট্রোল প্লেন সেট আপ করা

এই ধাপে, আপনি শেয়ার্ড GKE কন্ট্রোল প্লেন এবং একটি আর্টিফ্যাক্ট রেজিস্ট্রি ডকার রিপোজিটরি সেট আপ করবেন।

১) এনভায়রনমেন্ট ভেরিয়েবল সেট আপ করুন এবং শেয়ার্ড GKE ক্লাস্টার তৈরি করুন:

your-project-id জায়গায় আপনার প্রজেক্ট আইডি বসান। us-central1-c জায়গায় আপনার কাঙ্ক্ষিত জোন বসান। ( অঞ্চল ও জোন দেখুন)

export PROJECT_ID="your-project-id"
export ZONE="us-central1-c"
export CLUSTER_NAME="cgke-attestation-codelab"

gcloud config set project ${PROJECT_ID}

gcloud container clusters create ${CLUSTER_NAME} \
    --zone=${ZONE} \
    --num-nodes=1 \
    --machine-type=e2-medium \
    --workload-pool=${PROJECT_ID}.svc.id.goog \
    --workload-metadata=GKE_METADATA

gcloud container clusters get-credentials ${CLUSTER_NAME} --zone=${ZONE}

২) ওয়ার্কলোড কন্টেইনার ইমেজ সংরক্ষণের জন্য একটি আর্টিফ্যাক্ট রেজিস্ট্রি ডকার রিপোজিটরি তৈরি করুন:

gcloud artifacts repositories create codelab-repo \
    --repository-format=docker \
    --location=us

আপনার পথ বেছে নিন

শেয়ার্ড GKE কন্ট্রোল প্লেন এবং আর্টিফ্যাক্ট রেজিস্ট্রি রিপোজিটরি এখন প্রস্তুত। আপনার পথ বেছে নিন:

৪. CGKE নোড স্থাপন করা এবং নির্বাচিত ওয়ার্কলোডগুলিতে vTPM ডিভাইসকে উন্মুক্ত করা

এই ধাপে, আপনি CGKE-তে একটি vTPM নোড পুল তৈরি করবেন এবং ওয়ার্কলোডের কাছে CVM vTPM ডিভাইসটিকে উন্মুক্ত করার জন্য একটি ডিভাইস প্লাগইন প্রয়োগ করবেন। কমান্ডগুলো চালানোর জন্য ক্লাউড কনসোল বা আপনার স্থানীয় ডেভেলপমেন্ট এনভায়রনমেন্টে যান।

১) একটি AMD SEV নোড পুল যোগ করুন

gcloud container node-pools create sev-pool \
    --cluster=${CLUSTER_NAME} \
    --zone=${ZONE} \
    --machine-type=n2d-standard-2 \
    --num-nodes=1 \
    --enable-confidential-nodes \
    --confidential-node-type=sev

২) ডিফল্ট নোড পুলটি মুছে ফেলুন (ঐচ্ছিক, তবে খরচ ও নয়েজ বাঁচাতে এটি করার পরামর্শ দেওয়া হয়)।

gcloud container node-pools delete default-pool \
    --cluster=${CLUSTER_NAME} \
    --zone=${ZONE} \
    --quiet

৩) CGKE ক্লাস্টারকে ওয়ার্কলোডের কাছে vTPM ডিভাইস উন্মুক্ত করার অনুমতি দিতে ডিভাইস প্লাগইনটি চালু করুন। আমরা নতুন রিসোর্স তৈরি করতে একটি কুবারনেটিস ডিভাইস প্লাগইন ব্যবহার করি ( google.com/cc )। নতুন রিসোর্সের সাথে যুক্ত যেকোনো ওয়ার্কলোড ওয়ার্কার নোডের ডিভাইসগুলো দেখতে পাবে।

gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${ZONE} --project ${PROJECT_ID}

kubectl apply -f https://raw.githubusercontent.com/google/cc-device-plugin/main/manifests/cc-device-plugin.yaml

নিম্নলিখিত কমান্ডটি আপনাকে ডেপ্লয় করা cc-device-plugin দেখতে সাহায্য করবে।

kubectl get pods -A | grep "cc-device-plugin"

দ্রষ্টব্য: একটি মিশ্র মোডের GKE ক্লাস্টারের ক্ষেত্রে (যেখানে গোপনীয় এবং অ-গোপনীয় উভয় ধরনের GKE ওয়ার্কার নোড থাকে), অপারেটরকে শুধুমাত্র গোপনীয় GKE ওয়ার্কার নোডগুলিতে cc-device-plugin স্থাপন করার পরামর্শ দেওয়া হয়।

(ঐচ্ছিক) CGKE পড প্রোমিথিউস মনিটরিং চালু করুন। মনিটরিং চালু করলে আপনি ডিভাইস প্লাগইনের অবস্থা পর্যবেক্ষণ করতে পারবেন।

kubectl apply -f https://raw.githubusercontent.com/google/cc-device-plugin/main/manifests/cc-device-plugin-pod-monitoring.yaml

https://console.cloud.google.com/monitoring/metrics-explorer- এ যান এবং cc-device-plugin মেট্রিক্স খুঁজুন অথবা PROMQL ব্যবহার করুন। যেমন, নিম্নলিখিত PROMQL কমান্ডটি প্রতিটি cc-device-plugin প্রসেসের জন্য সিপিইউ সেকেন্ড দেখায়।

rate(process_cpu_seconds_total[${__interval}])

৫. একটি ওয়ার্কলোড স্থাপন করা এবং ওয়ার্কলোডটির উপর রিমোট অ্যাটেস্টেশন সম্পাদন করা (vTPM)

এই ধাপে, আপনি পূর্ববর্তী ধাপে তৈরি করা CGKE ক্লাস্টারে একটি ওয়ার্কলোড তৈরি ও ডেপ্লয় করবেন এবং ওয়ার্কার নোড থেকে একটি অ্যাটেস্টেশন টোকেন (OIDC টোকেন) পুনরুদ্ধার করার জন্য একটি vTPM রিমোট অ্যাটেস্টেশন সম্পাদন করবেন।

১) অ্যাপ্লিকেশন কন্টেইনার ইমেজটি তৈরি করুন এবং আর্টিফ্যাক্ট রেজিস্ট্রি-তে পুশ করুন। অ্যাপ্লিকেশন কন্টেইনার ইমেজটিতে go-tpm টুলটি থাকে, যা অ্যাটেস্টেশনের প্রমাণ সংগ্রহ করে এবং একটি অ্যাটেস্টেশন টোকেন (একটি OIDC টোকেন)-এর জন্য অ্যাটেস্টেশন ভেরিফায়ার সার্ভিসে পাঠাতে পারে।

  1. অ্যাপ্লিকেশন কন্টেইনার ইমেজের জন্য Dockerfile তৈরি করুন।
cat << 'EOF' > Dockerfile
FROM golang:1.26.4 as builder
WORKDIR /
RUN git clone https://github.com/google/go-tpm-tools.git
WORKDIR /go-tpm-tools/cmd/gotpm
RUN CGO_ENABLED=0 GOOS=linux go build -o /gotpm

FROM debian:trixie
WORKDIR /
RUN apt-get update -y
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl
RUN rm -rf /etc/apt/sources.list.d
COPY --from=builder /gotpm /gotpm
CMD ["tail", "-f", "/dev/null"]
EOF
  1. অ্যাপ্লিকেশন কন্টেইনার ইমেজটি আর্টিফ্যাক্ট রেজিস্ট্রি-তে পুশ করুন।
docker build -t us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/go-tpm:latest .
docker push us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/go-tpm:latest

২) GCP রিসোর্সগুলিতে GCP সার্ভিস অ্যাকাউন্টের অনুমতিগুলি উত্তরাধিকারসূত্রে পাওয়ার জন্য একটি Kubernetes সার্ভিস অ্যাকাউন্ট সেট আপ করুন।

  1. codelab-ksa একটি Kubernetes সার্ভিস অ্যাকাউন্ট তৈরি করুন।
kubectl create serviceaccount codelab-ksa \
    --namespace default
  1. Confidential_Computing_Workload_User নামে একটি রোল তৈরি করুন এবং রোলটিকে Confidential Computing API-গুলো অ্যাক্সেস করার অনুমতি দিন।
gcloud iam roles create Confidential_Computing_Workload_User --project=${PROJECT_ID} \
    --title="CGKE Workload User" --description="Grants the ability to generate an attestation token in a GKE workload." \
    --permissions="confidentialcomputing.challenges.create,confidentialcomputing.challenges.verify,confidentialcomputing.locations.get,confidentialcomputing.locations.list" --stage=GA
  1. codelab-csa একটি GCP সার্ভিস অ্যাকাউন্ট তৈরি করুন এবং এটিকে Confidential_Computing_Workload_User রোলের সাথে যুক্ত করুন। যাতে codelab-csa এর Confidential Computing API-গুলো অ্যাক্সেস করার অনুমতি থাকে।
gcloud iam service-accounts create codelab-csa \
    --project=${PROJECT_ID}

gcloud projects add-iam-policy-binding ${PROJECT_ID} \
    --member "serviceAccount:codelab-csa@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role "projects/${PROJECT_ID}/roles/Confidential_Computing_Workload_User"

gcloud iam service-accounts add-iam-policy-binding codelab-csa@${PROJECT_ID}.iam.gserviceaccount.com \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/codelab-ksa]"
  1. Kubernetes সার্ভিস অ্যাকাউন্ট codelab-ksa GCP সার্ভিস অ্যাকাউন্ট codelab-csa সাথে সংযুক্ত করুন। যাতে codelab-ksa Confidential Computing API-গুলো অ্যাক্সেস করার অনুমতি থাকে।
kubectl annotate serviceaccount codelab-ksa \
    --namespace default \
    iam.gke.io/gcp-service-account=codelab-csa@${PROJECT_ID}.iam.gserviceaccount.com

৩) ডেমো অ্যাপ্লিকেশনের জন্য অ্যাপ্লিকেশন ডিপ্লয়মেন্ট YAML তৈরি করুন। নির্বাচিত ওয়ার্কলোডগুলিতে Kubernetes সার্ভিস অ্যাকাউন্ট codelab-ksa বরাদ্দ করুন।

cat << EOF > deploy.yaml
apiVersion: v1
kind: Pod
metadata:
  name: go-tpm-demo
  labels:
    app.kubernetes.io/name: go-tpm-demo
spec:
  serviceAccountName: codelab-ksa
  nodeSelector:
    iam.gke.io/gke-metadata-server-enabled: "true"
    cloud.google.com/machine-family: "n2d" 
    cloud.google.com/gke-confidential-nodes-instance-type: "SEV"
  containers:
  - name: go-tpm
    image: us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/go-tpm:latest
    resources:
      limits:
        google.com/cc: 1
EOF

৪) CGKE ক্লাস্টারে ডেপ্লয়মেন্টটি প্রয়োগ করুন।

kubectl apply -f deploy.yaml

৫) ওয়ার্কলোডের সাথে সংযোগ স্থাপন করুন এবং একটি অ্যাটেস্টেশন টোকেন (একটি OIDC টোকেন) সংগ্রহ করতে রিমোট অ্যাটেস্টেশন চালু করুন।

kubectl exec -it go-tpm-demo -- /bin/bash
./gotpm token --event-log=/run/cc-device-plugin/binary_bios_measurements > attestation_token

৬) টোকেনটি স্ক্রিনে প্রিন্ট করুন যাতে আপনি এটি কপি করতে পারেন।

cat attestation_token

দাবিগুলো দেখার জন্য আপনি jwt.io- তে অ্যাটেস্টেশন টোকেনটি ডিকোড করতে পারেন।

৬. সিক্রেট রিলিজ ওয়েব সার্ভার সেট আপ করা

এই ধাপে, আপনি পূর্ববর্তী SSH সেশন থেকে বেরিয়ে এসে আরেকটি VM সেট আপ করবেন। এই VM-এ, আপনি একটি সিক্রেট রিলিজ ওয়েব সার্ভার সেট আপ করবেন। ওয়েব সার্ভারটি প্রাপ্ত অ্যাটেস্টেশন টোকেন এবং এর ক্লেইমগুলো যাচাই করে। যদি যাচাইকরণ সফল হয়, তবে এটি অনুরোধকারীর কাছে সিক্রেটটি পাঠিয়ে দেয়।

১) ক্লাউড কনসোল অথবা আপনার লোকাল ডেভেলপমেন্ট এনভায়রনমেন্টে যান। একটি ভার্চুয়াল মেশিন তৈরি করুন।

gcloud config set project ${PROJECT_ID}

gcloud compute instances create cgke-attestation-codelab-web-server \
    --machine-type=n2d-standard-2 \
    --zone=${ZONE} \
    --image-family=ubuntu-2204-lts \
    --image-project=ubuntu-os-cloud

২) আপনার নতুন ভিএম-এ SSH করুন।

gcloud compute ssh --zone ${ZONE} cgke-attestation-codelab-web-server

৩) Go পরিবেশটি প্রস্তুত করুন।

wget https://go.dev/dl/go1.26.4.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.26.4.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin

৪) সিক্রেট রিলিজ ওয়েব সার্ভারের সোর্স কোড সংরক্ষণের জন্য নিম্নলিখিত দুটি ফাইল তৈরি করুন।

main.go তৈরি করুন :

cat << 'EOF' > 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)

	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)
	// Support attestation of both AMD SEV and Intel TDX
	if !ok || (hwModel != "GCP_AMD_SEV" && hwModel != "GCP_INTEL_TDX") {
		return false, fmt.Errorf("missing or invalid 'hwModel': %v", hwModel)
	}
	fmt.Println("hwmodel:", hwModel)

	swName, ok := claims["swname"].(string)
	if !ok || swName != "GCE" {
		return false, fmt.Errorf("missing or invalid 'swName'")
	}
	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)
	}
}
EOF

helper.go তৈরি করুন :

cat << 'EOF' > 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)
}
EOF

৫) ওয়েব সার্ভারটি বিল্ড ও রান করতে নিম্নলিখিত কমান্ডগুলো চালান। এটি :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

৬) আরেকটি ক্লাউড কনসোল ট্যাব অথবা লোকাল ডেভেলপমেন্ট এনভায়রনমেন্ট সেশন চালু করুন এবং নিম্নলিখিত কমান্ডটি চালান। এর মাধ্যমে আপনি cgke-attestation-codelab-web-server-internal-ip পেয়ে যাবেন।

gcloud compute instances describe cgke-attestation-codelab-web-server \
    --format='get(networkInterfaces[0].networkIP)' \
    --zone=${ZONE}

৭) আপনার CGKE ওয়ার্কলোডের সাথে সংযোগ করুন এবং একটি অ্যাটেস্টেশন টোকেন (একটি OIDC টোকেন) আনার জন্য রিমোট অ্যাটেস্টেশন চালু করুন। তারপর, নিম্নলিখিত কমান্ডে attestation-token এবং cgke-attestation-codelab-web-server-internal-ip এর বিষয়বস্তু যুক্ত করুন। এটি আপনাকে সিক্রেট রিলিজ ওয়েব সার্ভারে থাকা সিক্রেটটি এনে দেবে!

kubectl exec -it go-tpm-demo -- /bin/bash
./gotpm token --event-log=/run/cc-device-plugin/binary_bios_measurements > attestation_token
curl http://<cgke-attestation-codelab-web-server-internal-ip>:8080 -H "Authorization: Bearer $(cat ./attestation_token)"

নিম্নলিখিতগুলি প্রতিস্থাপন করুন:

  • cgke-attestation-codelab-web-server-internal-ip হলো cgke-attestation-codelab-web-server VM ইনস্ট্যান্সটির অভ্যন্তরীণ আইপি।

৭. CGKE নোডগুলিতে vTPM সিলিং

এই ধাপে, আপনি CGKE নোডগুলিতে vTPM মালিকের অনুমোদন সেট আপ করেন এবং vTPM মালিকের পাসফ্রেজ ব্যবহার করে একটি ওয়ার্কলোড ডেপ্লয় করেন। এরপরে, আপনি vTPM সিলিং ক্যাপাবিলিটি ব্যবহার করে ওয়ার্কলোডের ডেটা সিল ও আনসিল করার জন্য একটি vTPM প্রাইমারি কী তৈরি করেন।

১) CGKE নোডগুলিতে vTPM মালিকের অনুমোদন ব্যবস্থা স্থাপন করুন।

  1. একটি ওয়ান-টাইম জব কন্টেইনার ইমেজ তৈরি করুন। এই ওয়ান-টাইম জবটি সমস্ত vTPM-এর জন্য মালিকের পাসওয়ার্ড নির্ধারণ করে। কন্টেইনার ইমেজটি তৈরি করার জন্য Dockerfile নিচে দেওয়া হলো।
cat << 'EOF' > Dockerfile
FROM debian:latest

RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
RUN apt-get update

RUN apt -y install \
  autoconf-archive \
  libcmocka0 \
  libcmocka-dev \
  net-tools \
  build-essential \
  git \
  pkg-config \
  gcc \
  g++ \
  m4 \
  libtool \
  automake \
  libgcrypt20-dev \
  libssl-dev \
  uthash-dev \
  autoconf \
  uuid-dev \
  libcurl4-openssl-dev \
  libjson-c-dev

RUN mkdir /src

WORKDIR /src
RUN git clone https://github.com/tpm2-software/tpm2-tss
WORKDIR /src/tpm2-tss
RUN ./bootstrap
RUN ./configure --prefix=/usr/local
RUN make all install

WORKDIR /src
RUN git clone https://github.com/tpm2-software/tpm2-tools
WORKDIR /src/tpm2-tools
RUN apt-get -y install libcurl4 libcurl4-openssl-dev pandoc man-db
RUN ./bootstrap
RUN ./configure --prefix=/usr/local
RUN make all install

RUN apt-get -y install vim

ENTRYPOINT ["/bin/bash"]
EOF
  1. ওয়ান-টাইম জব কন্টেইনার ইমেজটি বিল্ড করে আর্টিফ্যাক্ট রেজিস্ট্রি-তে পুশ করুন।
docker build -t us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/tpm-tools:latest .
docker push us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/tpm-tools:latest
  1. এককালীন কাজটি একটি Kubernetes জবের মাধ্যমে সম্পাদন করুন। (সতর্কতা: এই জবটি প্রতিটি CVM থেকে vTPM মুছে ফেলে। যদি আপনার CVM ডিস্ক এনক্রিপ্ট করার জন্য vTPM ব্যবহার করে, তবে এই জবটি রিবুটের পর আপনার CVM-কে অব্যবহারযোগ্য করে তুলবে। আপনি lsblk -f কমান্ডের মাধ্যমে আপনার ডিস্কে FSTYPE crypto_LUKS আছে কিনা তা পরীক্ষা করে দেখতে পারেন।)
cat << EOF > tpm-tools-task.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: tpm-tools-task
spec:
  template:
    spec:
      nodeSelector:
        cloud.google.com/machine-family: "n2d"
        cloud.google.com/gke-confidential-nodes-instance-type: "SEV"
      containers:
      - name: tpm-tools
        image: us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/tpm-tools:latest
        command: ["/bin/sh", "-c"]
        args: ["tpm2_clear; tpm2_changeauth -c owner this_is_passphrase"]
        resources:
          limits:
            google.com/cc: 1
      restartPolicy: Never
EOF

পরামর্শ: যদি আপনাকে tpm-tools-task জবটি পুনরায় চালাতে হয়, তাহলে প্রথমে বিদ্যমান জবটি মুছে ফেলতে ভুলবেন না:

kubectl delete job tpm-tools-task
  1. এককালীন কাজটি চালু করুন। এই কাজটি সমস্ত ওয়ার্কার নোডে vTPM মালিকের পাসফ্রেজ সেট করে।
kubectl apply -f tpm-tools-task.yaml

২) vTPM মালিকের পাসফ্রেজ ধারণ করার জন্য একটি Kubernetes সিক্রেট তৈরি করুন।

kubectl create secret generic tpm-secret --from-literal=passphrase='this_is_passphrase'

৩) একটি ডেমো অ্যাপ্লিকেশন কন্টেইনার তৈরি করুন এবং এতে পাসফ্রেজটি পাঠান। ডেমো অ্যাপ্লিকেশন কন্টেইনারটিতে vTPM-এর সাথে যোগাযোগের জন্য tpm2-tools রয়েছে।

  1. ডেমো অ্যাপ্লিকেশন কন্টেইনারের জন্য ডিপ্লয়মেন্ট YAML ফাইলটি তৈরি করুন।
cat << EOF > deploy_demo.yaml
apiVersion: v1
kind: Pod
metadata:
  name: tpm-tools-demo
  labels:
    app.kubernetes.io/name: tpm-tools-demo
spec:
  nodeSelector:
    cloud.google.com/machine-family: "n2d" 
    cloud.google.com/gke-confidential-nodes-instance-type: "SEV"
  containers:
  - name: tpm-tools
    image: us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/tpm-tools:latest
    command: ["tail", "-f", "/dev/null"]
    resources:
      limits:
        google.com/cc: 1
    volumeMounts:
      - name: secret-volume
        mountPath: "/etc/tpmsecret"
        readOnly: true
  volumes:
    - name: secret-volume
      secret:
        secretName: tpm-secret
EOF
  1. ডেমো অ্যাপ্লিকেশনটি স্থাপন করুন।
kubectl apply -f deploy_demo.yaml

৪) ডেমো অ্যাপ্লিকেশন কন্টেইনারে vTPM সিলিং সম্পাদন করুন।

  1. ডেমো অ্যাপ্লিকেশন কন্টেইনারের সাথে সংযোগ করুন এবং পাসফ্রেজ দিয়ে একটি প্রাইমারি কী সেট করুন।
kubectl exec -it tpm-tools-demo -- /bin/bash
tpm2_createprimary -C o -c primary.ctx -P $(cat /etc/tpmsecret/passphrase)

tpm2_createprimary নির্দিষ্ট হায়ারার্কি ও টেমপ্লেটের উপর ভিত্তি করে প্রাইমারি অবজেক্ট তৈরি করার জন্য vTPM-এর সাথে ইন্টারঅ্যাক্ট করে।

  • -C o: নির্দেশ করে যে প্রাইমারি কী-টি TPM-এর মালিকানা ক্রমের অধীনে তৈরি করা হবে।
  • -c primary.ctx : তৈরি করা প্রাইমারি অবজেক্টের কনটেক্সট (হ্যান্ডেল এবং সংশ্লিষ্ট ডেটা) primary.ctx ফাইলে সংরক্ষণ করে। পরবর্তী কার্যক্রমের জন্য এই কনটেক্সট অপরিহার্য।

ওয়ার্কলোডটি প্রাইমারি কী তৈরি করার জন্য ভুল ওনার পাসফ্রেজ ব্যবহার করতে পারবে না।

tpm2_createprimary -C o -P wrong_passphrase

কমান্ডটি নিম্নলিখিত ত্রুটিগুলি ফেরত দেয়:

WARNING:esys:src/tss2-esys/api/Esys_CreatePrimary.c:401:Esys_CreatePrimary_Finish() Received TPM Error
ERROR:esys:src/tss2-esys/api/Esys_CreatePrimary.c:135:Esys_CreatePrimary() Esys Finish ErrorCode (0x000009a2)
ERROR: Esys_CreatePrimary(0x9A2) - tpm:session(1):authorization failure without DA implications
ERROR: Unable to run tpm2_createprimary
  1. তৈরি করা প্রাইমারি কী-টি তখন ডেটা সিল এবং আনসিল করতে ব্যবহার করা যেতে পারে।
echo "This is my secret message" > secret.txt
tpm2_create -C primary.ctx -u sealed.pub -r sealed.priv -i secret.txt
tpm2_load -C primary.ctx -u sealed.pub -r sealed.priv -c sealed.ctx
tpm2_unseal -c sealed.ctx -o unsealed.txt
cat unsealed.txt

tpm2_create কাঙ্ক্ষিত ক্রিপ্টোগ্রাফিক অবজেক্টটি তৈরি করার জন্য vTPM-এর সাথে মিথস্ক্রিয়া করে।

  • -C primary.ctx : আমরা পূর্বে তৈরি করা প্রাইমারি কী কনটেক্সটটি ব্যবহার করি।
  • -u sealed.pub : সিলিং কী-এর (আনসিলিং-এর জন্য প্রয়োজনীয়) পাবলিক অংশটি sealed.pub ফাইলে সংরক্ষণ করে।
  • -r sealed.priv : সিলিং কী-এর ব্যক্তিগত অংশ sealed.priv ফাইলে সংরক্ষণ করে।
  • -i secret.txt : যে ফাইলে গোপনীয় তথ্যটি রাখা আছে।

tpm2_load : পাবলিক এবং প্রাইভেট অংশ ( sealed.pub , sealed.priv ) ব্যবহার করে সিলিং কী-টি TPM-এ লোড করে এবং এর কনটেক্সট sealed.ctx এ সংরক্ষণ করে।

tpm2_unseal : একটি vTPM সিলিং অবজেক্ট ব্যবহার করে পূর্বে এনক্রিপ্ট (সিল) করা ডেটা ডিক্রিপ্ট (আনসিল) করুন।

cat unsealed.txt : প্রক্রিয়াটি সফল হয়েছে তা নিশ্চিত করতে আনসিল করা গোপন বার্তাটি প্রদর্শন করে।

উল্লেখ্য যে, primary.ctx এবং sealed.priv ফাইলগুলো শুধুমাত্র একটি vTPM ডিভাইসে ব্যবহারযোগ্য। এবং যার কাছে vTPM ডিভাইস ও এই ফাইলগুলোর অ্যাক্সেস আছে, তিনি সিল করা ডেটাও অ্যাক্সেস করতে পারবেন। ডেটা সিল করার জন্য আপনি PCR ভ্যালুর উপর পলিসিও ব্যবহার করতে পারেন, কিন্তু তা এই কোডল্যাবের আওতার বাইরে।

৮. হার্ডওয়্যার অ্যাটেস্টেশন সম্পাদনের জন্য ওয়ার্কলোড স্থাপন: ইন্টেল টিডিএক্স এবং এএমডি এসইভি-এসএনপি

cc_device_plugin_arch.png

এই ধাপে, আপনি একটি ইন্টেল TDX বা এএমডি SEV-SNP ভিত্তিক ওয়ার্কলোড তৈরি করবেন এবং কনফিডেনশিয়াল GKE নোডগুলিতে হার্ডওয়্যার-স্তরের অ্যাটেস্টেশন সম্পাদন করবেন। উপরের আর্কিটেকচার ডায়াগ্রামে যেমন দেখানো হয়েছে, আমরা ইন্টেল TDX এবং এএমডি SEV-SNP আর্কিটেকচারের জন্য অ্যাটেস্টেশন কোট সংগ্রহ করতে গেস্ট ডিভাইস প্লাগইন ব্যবহার করব। কমান্ডগুলো চালানোর জন্য ক্লাউড কনসোল বা আপনার স্থানীয় ডেভেলপমেন্ট এনভায়রনমেন্টে যান।

আপনি যে কনফিডেনশিয়াল কম্পিউটিং আর্কিটেকচারটি—ইন্টেল টিডিএক্স অথবা এএমডি এসইভি-এসএনপি—পরীক্ষা করতে চান, সেটি বেছে নিন।

১) একটি ইন্টেল TDX নোড পুল যোগ করুন

gcloud container node-pools create tdx-pool \
    --cluster=${CLUSTER_NAME} \
    --zone=${ZONE} \
    --machine-type=c3-standard-4 \
    --num-nodes=1 \
    --enable-confidential-nodes \
    --confidential-node-type=tdx

২) একটি AMD SEV-SNP নোড পুল যোগ করুন।

gcloud container node-pools create snp-pool \
    --cluster=${CLUSTER_NAME} \
    --zone=${ZONE} \
    --machine-type=n2d-standard-2 \
    --num-nodes=1 \
    --enable-confidential-nodes \
    --confidential-node-type=sev_snp \
    --min-cpu-platform="AMD Milan"

৩) ডিফল্ট নোড পুলটি মুছে ফেলুন (ঐচ্ছিক, তবে খরচ ও নয়েজ বাঁচাতে এটি করার পরামর্শ দেওয়া হয়)।

gcloud container node-pools delete default-pool \
    --cluster=${CLUSTER_NAME} \
    --zone=${ZONE} \
    --quiet

(দ্রষ্টব্য: আপনি যদি আগেই vTPM অংশটি সম্পন্ন করে থাকেন, তাহলে এই পুলটি ইতোমধ্যেই মুছে ফেলা হয়েছে এবং এখানকার যেকোনো "খুঁজে পাওয়া যায়নি" ত্রুটি আপনি নিশ্চিন্তে উপেক্ষা করতে পারেন।)

৪) হার্ডওয়্যার অ্যাটেস্টেশন গেস্ট ডিভাইসগুলোকে ওয়ার্কলোডের কাছে উন্মুক্ত করার জন্য CGKE ক্লাস্টারকে অনুমতি দিতে ডিভাইস প্লাগইনটি চালু করুন। আমরা নতুন রিসোর্স ( intel.com/tdx এবং amd.com/sev-snp ) তৈরি করতে একটি কুবারনেটিস ডিভাইস প্লাগইন ব্যবহার করি। এই রিসোর্সগুলোর সাথে যুক্ত যেকোনো ওয়ার্কলোড ওয়ার্কার নোডের গেস্ট ডিভাইসগুলো অ্যাক্সেস করতে পারবে।

gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${ZONE} --project ${PROJECT_ID}

kubectl apply -f https://raw.githubusercontent.com/google/cc-device-plugin/main/manifests/cc-device-plugin.yaml

নিম্নলিখিত কমান্ডটি আপনাকে ডেপ্লয় করা cc-device-plugin দেখতে সাহায্য করবে।

kubectl get pods -A | grep "cc-device-plugin"

দ্রষ্টব্য: একটি মিশ্র মোডের GKE ক্লাস্টারের ক্ষেত্রে (যেখানে গোপনীয় এবং অ-গোপনীয় উভয় ধরনের GKE ওয়ার্কার নোড থাকে), অপারেটরকে শুধুমাত্র গোপনীয় GKE ওয়ার্কার নোডগুলিতে cc-device-plugin স্থাপন করার পরামর্শ দেওয়া হয়।

৯. হার্ডওয়্যার প্রত্যয়ন: ইন্টেল টিডিএক্স এবং এএমডি এসইভি-এসএনপি

এই অংশে, আপনি শিখবেন কীভাবে গেস্ট ডিভাইস প্লাগইন ব্যবহার করে ইন্টেল TDX এবং এএমডি SEV-SNP আর্কিটেকচারের জন্য অ্যাটেস্টেশন কোট সংগ্রহ করার মাধ্যমে কনফিডেনশিয়াল GKE নোডের অভ্যন্তরে হার্ডওয়্যার-স্তরের অ্যাটেস্টেশন সম্পাদন করতে হয়।

১) সংশ্লিষ্ট গেস্ট ডিভাইস থেকে হার্ডওয়্যার অ্যাটেস্টেশন কোটেশন সংগ্রহ করার জন্য একটি Go অ্যাপ্লিকেশন তৈরি করুন।

cat << 'EOF' > main.go
package main

import (
	"crypto/rand"
	"log"
	"os"
	"strconv"
	"time"
	"unsafe"

	sevclient "github.com/google/go-sev-guest/client"
	"golang.org/x/sys/unix"
)

// TDX Linux ABI Definitions
const (
	iocTdxGetReport = 0xc4405401
)

type tdxReportReq struct {
	ReportData [64]byte
	TdReport   [1024]byte
}

func main() {
	ccType := os.Getenv("CC_TYPE") // Expected "tdx" or "snp"

	iterationsStr := os.Getenv("ITERATIONS")
	iterations := 100
	if val, err := strconv.Atoi(iterationsStr); err == nil && val > 0 {
		iterations = val
	}

	successCount := 0
	failureCount := 0
	startTime := time.Now()
	
	log.Printf("Starting Attestation test for CC_TYPE: %s (iterations: %d)", ccType, iterations)

	if ccType == "tdx" {
		devicePath := "/dev/tdx_guest"
		file, err := os.OpenFile(devicePath, os.O_RDWR|os.O_SYNC, 0)
		if err != nil {
			log.Fatalf("FATAL: Failed to open %s: %v", devicePath, err)
		}
		defer file.Close()

		for i := 1; i <= iterations; i++ {
			var reportData [64]byte
			rand.Read(reportData[:])
			req := tdxReportReq{ReportData: reportData}
			
			_, _, errno := unix.Syscall(unix.SYS_IOCTL, file.Fd(), uintptr(iocTdxGetReport), uintptr(unsafe.Pointer(&req)))
			if errno != 0 || (req.TdReport[0] == 0 && req.TdReport[10] == 0) {
				failureCount++
			} else {
				successCount++
			}
		}
	} else if ccType == "snp" {
		device, err := sevclient.OpenDevice()
		if err != nil {
			log.Fatalf("FATAL: Failed to open SEV-SNP device: %v", err)
		}
		defer device.Close()

		for i := 1; i <= iterations; i++ {
			var reportData [64]byte
			rand.Read(reportData[:])
			report, err := sevclient.GetRawReport(device, reportData)
			if err != nil || len(report) == 0 {
				failureCount++
			} else {
				successCount++
			}
		}
	}

	elapsed := time.Since(startTime)
	log.Printf("=== Test Completed ===")
	log.Printf("Total Iterations: %d", iterations)
	log.Printf("Success: %d, Failures: %d", successCount, failureCount)
	log.Printf("Total Time: %s", elapsed)
}
EOF

২) হার্ডওয়্যার অ্যাটেস্টেশন টেস্ট টুলের জন্য Dockerfile তৈরি করুন:

cat << 'EOF' > Dockerfile
FROM golang:1.26.4 AS builder
WORKDIR /app
COPY main.go .
RUN go mod init cc-attestation
RUN go mod tidy
RUN CGO_ENABLED=0 GOOS=linux go build -o /cc-attestation-check main.go

FROM ubuntu:22.04
COPY --from=builder /cc-attestation-check /usr/local/bin/
CMD ["/usr/local/bin/cc-attestation-check"]
EOF

৩) ইমেজটি বিল্ড করে আপনার আর্টিফ্যাক্ট রেজিস্ট্রি-তে পুশ করুন:

docker build -t us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/cc-attestation-check:latest .
docker push us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/cc-attestation-check:latest

৪) হার্ডওয়্যার ডিভাইসগুলোকে এক্সপোজ করার জন্য Intel TDX এবং AMD SEV-SNP উভয় ওয়ার্কলোডের জন্য অ্যাপ্লিকেশন ডিপ্লয়মেন্ট YAML তৈরি করুন।

  • Intel TDX ওয়ার্কলোডের জন্য , নিম্নলিখিত ডিপ্লয়মেন্ট YAML তৈরি করুন:
cat << EOF > deploy-tdx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: tdx-attestation-pod
spec:
  restartPolicy: Never
  containers:
  - name: tdx-container
    image: us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/cc-attestation-check:latest
    env:
    - name: CC_TYPE
      value: "tdx"
    - name: ITERATIONS
      value: "1000"
    resources:
      limits:
        intel.com/tdx: 1
  nodeSelector:
    cloud.google.com/gke-confidential-nodes-instance-type: "TDX"
    cloud.google.com/machine-family: "c3"
EOF
  • AMD SEV-SNP ওয়ার্কলোডগুলির জন্য , নিম্নলিখিত ডিপ্লয়মেন্ট YAML তৈরি করুন:
cat << EOF > deploy-snp.yaml
apiVersion: v1
kind: Pod
metadata:
  name: snp-attestation-pod
spec:
  restartPolicy: Never
  containers:
  - name: snp-container
    image: us-docker.pkg.dev/${PROJECT_ID}/codelab-repo/cc-attestation-check:latest
    env:
    - name: CC_TYPE
      value: "snp"
    - name: ITERATIONS
      value: "100"
    resources:
      limits:
        amd.com/sev-snp: 1
  nodeSelector:
    cloud.google.com/gke-confidential-nodes-instance-type: "SEV_SNP"
    cloud.google.com/machine-family: "n2d"
EOF

৫) CGKE ক্লাস্টারে ডেপ্লয়মেন্টগুলো প্রয়োগ করুন।

kubectl apply -f deploy-tdx.yaml
kubectl apply -f deploy-snp.yaml

৬) পডগুলো সম্পূর্ণ হওয়া পর্যন্ত অপেক্ষা করুন এবং হার্ডওয়্যার অ্যাটেস্টেশন লগগুলো যাচাই করুন।

প্রথমে, আপনার পডগুলি সফলভাবে শিডিউল করা হয়েছে এবং চলছে কিনা তা নিশ্চিত করতে সেগুলির স্ট্যাটাস চেক করুন (সেগুলিতে Completed বা Running দেখানো পর্যন্ত অপেক্ষা করুন):

kubectl get pods -w

পডগুলো প্রস্তুত হয়ে গেলে, হার্ডওয়্যার অ্যাটেস্টেশন কোটগুলো সফলভাবে আনা হয়েছে কিনা তা যাচাই করতে লগগুলো পরীক্ষা করুন:

# View the TDX attestation logs
kubectl logs tdx-attestation-pod

# View the SEV-SNP attestation logs
kubectl logs snp-attestation-pod

(আপনি মোট নির্বাহের সময়, মোট পুনরাবৃত্তি এবং সাফল্য/ব্যর্থতার সংখ্যা লগ করা দেখতে পাবেন।)

১০. পরিচ্ছন্নতা

আপনার গুগল ক্লাউড অ্যাকাউন্টে চলমান চার্জ এড়ানোর জন্য, এই কোডল্যাবে তৈরি করা রিসোর্সগুলো মুছে ফেলা জরুরি।

ক্লাউড শেল অথবা আপনার স্থানীয় ডেভেলপমেন্ট এনভায়রনমেন্টে নিম্নলিখিত কমান্ডগুলো চালান।

১) এনভায়রনমেন্ট ভেরিয়েবল সেট করুন:

export PROJECT_ID="your-project-id"
export ZONE="us-central1-c"
export CLUSTER_NAME="cgke-attestation-codelab"

gcloud config set project ${PROJECT_ID}

২) GKE রিসোর্সগুলো মুছে ফেলুন

gcloud container clusters delete ${CLUSTER_NAME} \
    --zone=${ZONE} \
    --quiet

৩) আর্টিফ্যাক্ট রেজিস্ট্রি মুছে ফেলুন

gcloud artifacts repositories delete codelab-repo \
    --location=us \
    --quiet

৪) ওয়েব সার্ভার ভিএম মুছে ফেলুন

gcloud compute instances delete cgke-attestation-codelab-web-server \
    --zone=${ZONE}

৫) IAM রিসোর্সগুলো মুছে ফেলুন

# Delete the GCP service account
gcloud iam service-accounts delete codelab-csa@${PROJECT_ID}.iam.gserviceaccount.com \
    --quiet

# Delete the custom IAM role
gcloud iam roles delete Confidential_Computing_Workload_User \
    --project=${PROJECT_ID} \
    --quiet

১১. এরপর কী?

গোপনীয় GKE নোড সম্পর্কে আরও জানুন।