Kubernetes v1.35: راهی بهتر برای انتقال رمزهای حساب سرویس به درایورهای CSI 🚀

اگر از درایور CSI ای استفاده می‌کنید که از Service Account Tokens بهره می‌برد، نسخه 1.35 تغییرات مهمی دارد که باید با آن‌ها آشنا باشید. این تغییرات هدفشان کاهش خطر افشای تصادفی توکن‌ها و هماهنگ‌سازی نحوه تحویل آن‌ها به درایورهای CSI است.

تا امروز، وقتی درایورهای CSI از ویژگی TokenRequests استفاده می‌کردند، توکن‌ها به‌عنوان بخشی از volume_context به درایورها ارسال می‌شدند. این روش کار می‌کرد اما برای اطلاعات حساس طراحی نشده است و نمونه‌هایی دیده شده که توکن‌ها به‌طور تصادفی وارد لاگ‌ها یا گزارش‌ها شده‌اند.

در Kubernetes v1.35 یک راه‌حل در حالت بتا معرفی شده: انتخاب نحوه تحویل توکن‌های حساب سرویس از طریق Secrets Field. با این روش، درایورها می‌توانند توکن‌ها را از طریق فیلد Secrets در NodePublishVolumeRequest دریافت کنند — جایی که به‌مراتب مناسب‌تر برای داده‌های حساس است.

برای درک بهتر، ابتدا نگاهی به مکانیزم فعلی بیندازیم. وقتی درایورهای CSI از TokenRequests استفاده می‌کنند، توکن‌ها به‌عنوان بخشی از نقشه ویژگی‌های حجم با کلید csi.storage.k8s.io/serviceAccount.tokens به درایور فرستاده می‌شوند. volume_context برای داده‌های حساس طراحی نشده و همین باعث دو چالش شد:

1️⃣ ابزار protosanitizer که درایورهای CSI گاهی استفاده می‌کنند، volume_context را به‌عنوان داده حساس درنظر نمی‌گیرد؛ بنابراین توکن‌ها ممکن است هنگام ثبت درخواست‌های gRPC لاگ شوند. نمونه‌های این مشکل باعث بروز CVE-2023-2878 در CSI Secrets Store و CVE-2024-3744 در Azure File CSI شد.
2️⃣ هر درایور مجبور بود منطق پاکسازی و محافظت مخصوص خودش را پیاده‌سازی کند که نتیجه‌اش ناسازگاری بین درایورها و پیچیدگی بیشتر بود.

مشکل کلیدی این است که نمی‌توانیم محل ارسال توکن‌ها را ناگهان تغییر دهیم بدون اینکه درایورهای فعلی که هنوز انتظار دارند توکن‌ها در volume_context باشند، مختل شوند.

نحوه کار مکانیزم انتخابی در v1.35: یک فیلد جدید در مشخصات CSIDriver اضافه شده که به درایور اجازه می‌دهد نحوه دریافت توکن‌ها را مشخص کند. درایورهای قدیمی مثل قبل کار می‌کنند و درایورهای جدید می‌توانند به تدریج به فیلد مناسب‌تر مهاجرت کنند. مثال پیکربندی (نمونه آزمایشی — برای خوشه واقعی تغییر دهید):

# احتیاط: این یک پیکربندی نمونه است.
# از این برای خوشه خود استفاده نکنید!
# apiVersion: storage.k8s.io/v1
نوع: ابرداده CSIDriver:
نام: example-csi-driver
spec:
  # ... فیلدهای موجود ...
  tokenRequests:
  - مخاطب: "example.com"
    expirationSeconds: 3600
  # فیلد جدید برای انتخاب سرویس تحویل اسرار
  serviceAccountTokenInSecrets: true

فیلد serviceAccountTokenInSecrets تعیین می‌کند توکن‌ها کجا قرار بگیرند. مقدار پیش‌فرض false است، یعنی رفتار فعلی حفظ می‌شود و توکن‌ها در VolumeContext (با کلید csi.storage.k8s.io/serviceAccount.tokens) ارسال می‌شوند. وقتی این فیلد true شود، توکن‌ها فقط در قسمت Secrets با همان کلید قرار می‌گیرند. دروازهٔ ویژگی CSIServiceAccountTokenSecrets به‌صورت پیش‌فرض در kubelet و kube-apiserver فعال است، اما چون مقدار پیش‌فرض فیلد بالا false است، فعال شدن این دروازه به‌تنهایی هیچ تغییری در رفتار فعلی ایجاد نمی‌کند — این یعنی مهاجرت opt-in است و رفتار موجود ناگهان تغییر نمی‌کند.

راهنمای عملی برای نویسندگان درایور CSI — آماده‌سازی برای مهاجرت: بهترین کار این است که کد درایور خود را طوری به‌روزرسانی کنید که هر دو مکان ممکن برای توکن را چک کند؛ این باعث می‌شود درایور شما هم با رویکرد قدیمی و هم با رویکرد جدید سازگار باشد. نمونهٔ منطق بازگشتی (نمونهٔ کد):

const serviceAccountTokenKey = "csi.storage.k8s.io/serviceAccount.tokens"

func getServiceAccountTokens(req *csi.NodePublishVolumeRequest) (string, error) {
    // ابتدا در secrets بررسی کنید:
    if tokens, ok := req.Secrets[serviceAccountTokenKey]; ok {
        return tokens, nil
    }
    // اگر در secrets نبود، به متن حجم برگردید (رفتار موجود)
    if tokens, ok := req.VolumeContext[serviceAccountTokenKey]; ok {
        return tokens, nil
    }
    return "", fmt.Errorf("service account tokens not found")
}

اضافه کردن این منطق بازگشتی به درایور باعث می‌شود که انتشار نسخهٔ جدید درایور قبل از به‌روزرسانی خوشه ایمن باشد. تشویق می‌کنیم این تغییر را زود پیاده کنید، ریلیز بگیرید و در صورت امکان آن را به شاخه‌های نگهداری(backport) اضافه کنید — این کار مهاجرت را نرم و ایمن می‌کند.

ترتیب ایمن عرضه (Rollout sequence):

1. سرور kube-apiserver را به 1.35 یا بالاتر ارتقا دهید.
2. اطمینان حاصل کنید درایور CSI با منطق بازگشتی مستقر شده است (اگر قبلاً انجام نشده).
3. درایور CSI را به‌صورت DaemonSet روی همهٔ نودها منتشر کنید و منتظر بمانید که عرضه کامل شود.
4. سپس مانیفست CSIDriver را برای تنظیم serviceAccountTokenInSecrets: true به‌روزرسانی کنید.

نکتهٔ خیلی مهم در این فرآیند زمان‌بندی است: اگر CSIDriver را زودتر از انتشار نسخهٔ جدید درایور بروزرسانی کنید، گره‌هایی که هنوز درایور قدیمی را اجرا می‌کنند ممکن است در نصب حجم با شکست مواجه شوند چون آن پادها فقط volume_context را بررسی می‌کنند. بنابراین همیشه ابتدا نسخهٔ درایور سازگار را منتشر کنید، منتظر تکمیل DaemonSet بمانید، سپس CSIDriver را به‌روزرسانی کنید.

چرا این تغییر مهم است؟ خلاصهٔ مزایا:

• کاهش خطر ثبت تصادفی Service Account Tokens در لاگ‌ها. 🔒
• استفاده از فیلد مشخصات CSI که برای داده‌های حساس طراحی شده (Secrets)، به‌جای volume_context.
• حذف نیاز به راه‌حل‌های پراکندهٔ خاص هر درایور؛ این یک گزینهٔ استاندارد و اختیاری است که می‌تواند به تدریج پذیرفته شود. ✅

اگر نظر یا سوالی دربارهٔ طراحی API دارید یا در فرایند پذیرش به مشکلی برخوردید، در کانال #csi در Kubernetes Slack مطرح کنید (برای دعوت به https://slack.k8s.io/ مراجعه کنید). برای دنبال کردن پیشرفت در نسخه‌های آتی می‌توانید KEP-5538 را دنبال کنید. 📣

موفق باشید و اگر در فرایند پیاده‌سازی نیاز به کمک داشتید بپرسید — خوشحال می‌شم کمک کنم! 🙂