به گزارش از وبسایت cncf

این مطلب دومین قسمت از یک مجموعهٔ سه‌گانه دربارهٔ سخت‌تر کردن خط لولهٔ Cilium/CD است که توسط آندره مارتینز (نگهدارنده و مهندس نرم‌افزار Cilium، Isovalent در سیسکو) و فروز سلام (تیم امنیت و مهندس امنیت Cilium، Isovalent در سیسکو) منتشر شده است. در بخش اول، کنترل دسترسی—یعنی اینکه چه کسانی می‌توانند بیلدها را اجرا کنند و چه کدی اجازهٔ اجرا در CI را دارد—بررسی شد. این پست به لایهٔ وابستگی می‌پردازد: کدی که بیلدها آن را وارد می‌کنند و چگونه از تغییرناپذیری و سلامت آن اطمینان حاصل کنیم. 🛡️

پس از تعیین اینکه چه کسی می‌تواند بیلدها را اجرا کند، سؤال بعدی این است که خودِ آن بیلدها چه کدی را وارد می‌کنند. پین کردن وابستگی‌ها مهم است، اما حتی یک گردش کار پین‌شده هم اگر وابستگی‌های گذرا (transitive) نامشخص داشته باشد، در معرض خطر خواهد بود. به همین دلیل ما به جای اعتماد به برچسب‌های قابل تغییر، اقدامات GitHub Actions را با SHA‌های کامل پین می‌کنیم تا از تغییر ناخواسته جلوگیری شود. 🔒

به‌عنوان مثال، در فایل‌های workflow ما هر استفاده از یک اکشن به یک commit SHA کامل 40 کاراکتری ارجاع داده می‌شود و نسخهٔ خواناترِ انسانی به صورت یک نظر چسبانده می‌شود:

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0cheoutg

با این کار وقتی که کسی یک تگ قابل تغییر را در مخزن اکشنِ اصلی به‌روزرسانی کند، گردش کار ما به‌واسطهٔ پین به یک commit خاص متکی می‌ماند و متغیر شدن برچسب تأثیری در کد اجراشونده نخواهد داشت. با این حال یک نقطهٔ کور باقی می‌ماند: اگر اکشنِ پینی که ما استفاده می‌کنیم خود به اکشن دیگری ارجاع دهد (مثلاً some-org/some-helper@v1)، آن وابستگی گذرا در زمان اجرا روشن می‌شود و برای ما نامرئی است. مهاجمی که بتواند وابستگی‌های تودرتو را تغییر دهد، می‌تواند به خط لولهٔ ما راه یابد.

راه‌حل در دسترس است: GitHub در نقشه‌‌راه امنیتی Actions برای 2026 بخشی برای dependency locking در YAML گردش کار معرفی کرده که تمام وابستگی‌های مستقیم و گذرا را با commit SHA قفل می‌کند، مشابه go.mod + go.sum در اکوسیستم Go. ما به‌محض انتشار این قابلیت آن را پذیرفتیم. 🤝

مدیریت دستی پین‌های SHA دشوار و خطاپذیر است، بنابراین ما از ابزارهای خودکار استفاده می‌کنیم. پیکربندی Renovate ما به‌صورت جهانی گزینه‌هایی مانند pinGitHubActionDigests و pinDigests: true را فعال کرده تا وقتی نسخهٔ جدیدی از یک اکشن منتشر می‌شود، Renovate یک PR ایجاد کند که SHA را به‌روز می‌کند و ما را بدون بازگشت به مرجع قابل تغییر به‌روز نگه دارد.

نمونه‌ای از بخشی از پیکربندی Renovate که برای گروه‌بندی و مرج خودکار وابستگی‌های مورد اعتماد استفاده می‌شود را در اینجا می‌بینید:

{
  "matchPackageNames": [
    "actions/**",
    "docker/**",
    "cilium/**",
    "k8s.io/**",
    "golang.org/x/**",
    "github.com/golang/**",
    "github.com/prometheus/**",
    "github.com/hashicorp/**",
    "go.etcd.io/etcd"
  ],
  "automergeType": "pr",
  "groupName": "auto-merge-trusted-deps",
  "reviewers": ["ciliumbot"]
}

به‌روزرسانی‌های این فهرست پس از عبور CI اجازهٔ ادغام خودکار دارند؛ بقیهٔ به‌روزرسانی‌ها نیاز به بازبینی انسانی دارند. علاوه بر این، ما لایهٔ تأیید اضافی‌ای اضافه کرده‌ایم که بررسی می‌کند PR واقعاً توسط cilium-renovate[bot] ساخته شده و درخواست بازبینی نیز از طرف همان ربات آمده است، در غیر این صورت ادغام خودکار انجام نمی‌شود:

if: ${{ github.event.pull_request.user.login == 'cilium-renovate[bot]' || github.triggering_actor == 'auto-committer[bot]' }}

برای وابستگی‌های Go، ما تمام ماژول‌های عرضه‌شده را در مخزن نگهداری (vendor) و commit می‌کنیم. CI بررسی می‌کند که بین go.mod، go.sum و vendor/ هیچ ناهماهنگی‌ای وجود نداشته باشد. این کار باعث می‌شود بیلدها قابل تکرار باشند و در زمان ساخت به پراکسی‌های ماژول خارجی متکی نباشند؛ در نتیجه یک ماژول دستکاری‌شده روی یک پراکسی هرگز به ما تحویل داده نخواهد شد. همچنین بررسی‌های مجوز (license checks) را اجرا می‌کنیم تا از ورود وابستگی‌های با مجوزهای نامطلوب جلوگیری کنیم:

go run ./tools/licensecheck

در مورد اینکه آیا بهتر نیست همهٔ اکشن‌های شخص ثالث را در سازمان خودمان فورک کنیم و از آن SHA‌های چنگال‌شده استفاده کنیم، در نظریه این کار امن‌تر است، اما در عمل هزینهٔ نگهداری بسیار بالاست. ما از ده‌ها اکشن شخص ثالث استفاده می‌کنیم و همگام‌سازی دائمی فورک‌ها با وصله‌های بالادست به یک بار عملیاتی بزرگ تبدیل می‌شود؛ ضمن اینکه فورک‌های قدیمی با آسیب‌پذیری‌های رفع‌نشده خود می‌توانند مشکل‌آفرین شوند. pining به یک commit خاص، همراه با Renovate که به‌روزرسانی‌ها را پیشنهاد می‌دهد، تعادل خوبی از نظر امنیت و هزینهٔ عملیاتی فراهم می‌کند.

همین نکته در مورد وابستگی‌های Go نیز صادق است. Cilium صدها ماژول Go را به‌کار می‌گیرد؛ فورک‌زدن و نگهداری همهٔ آن‌ها واقع‌بینانه نیست. یکی از مزایای Go این است که مسیرهای واردات معمولاً شامل منبع صریح مثل github.com/stretchr/testify هستند که کلاس حملهٔ Dependency Confusion را تا حد زیادی کاهش می‌دهد. با این حال typosquatting هنوز تهدیدی است؛ پژوهش‌ها نشان داده‌اند بسته‌های اشتباه‌نام‌گذاری‌شده در طبیعت وجود دارند که می‌توانند مشکلاتی ایجاد کنند.

برای کاهش این خطر، ما از فروشنده (vendor) به‌عنوان دفاع اصلی استفاده می‌کنیم: تصمیم اعتماد را از زمان بیلد که نامرئی است به زمان بررسی کد منتقل می‌کنیم تا انسان بتواند تفاوت‌ها را ببیند. یک مسیر واردات مشکوک در vendor/ در بازبینی‌ها مشخص می‌شود و با مکانیزم‌های گیتینگ مانند CODEOWNERS بررسی می‌گردد. همچنین فهرستی از وابستگی‌هایی داریم که آن‌ها را به‌طور دستی مدیریت می‌کنیم—چه به‌خاطر نیاز به به‌روزرسانی‌های هماهنگ، چه به‌خاطر نگهداری فورک با وصله‌های خاص؛ این موارد در پیکربندی Renovate فهرست‌شده‌اند.

یک اصل سادهٔ توسعه همواره در ذهن ماست: گاهی کمی کپی کردن بهتر از افزودن یک وابستگی کوچک است. ما به‌طور دوره‌ای کتابخانه‌های شخص ثالث را بازبینی می‌کنیم و در صورت امکان وابستگی‌های غیرفعال یا کوچک را حذف یا به صورت inlined جایگزین می‌کنیم تا سطح حمله را کم کنیم.

حتی با همهٔ این سیاست‌ها، خطاهای انسانی و اشتباهات رخ می‌دهد. یک مشارکت‌کنندهٔ خوش‌نیت ممکن است مثلاً یک workflow بدون مجوز صحیح اضافه کند یا به‌جای runner پین‌شده از ubuntu-latest استفاده کند. برای یافتن چنین مشکلاتی پیش از مرج، ما از تحلیل ایستا استفاده می‌کنیم: CodeQL روی هر فشار و PR با قانونی مانند actions/missing-workflow-permissions اجرا می‌شود تا فایل‌های گردش کاری که مجوزها را صراحتاً تنظیم نکرده‌اند شکست بخورند. علاوه بر این، actionlint به‌صورت ایستا فایل‌های workflow را برای خطاهای نحوی، الگوهای ناامن و پیکربندی‌های نادرست بررسی می‌کند. 🤖⚠️

علاوه بر بررسی‌های امنیتی، همان خط لولهٔ CI قراردادهای پروژه را نیز اعمال می‌کند: هر job و step باید نام داشته باشد، از تگ‌های شناور runner استفاده نشود (مثلاً ما ubuntu-24.04 را پین می‌کنیم)، و فایل‌های workflow از فضاهای خالی انتهایی پاک باشند. این قواعد معمولاً خطاها و ناسازگاری‌های ناشی از مشارکت‌های ناخواسته را زود تشخیص می‌دهند.

متن اصلی تا اینجا آمده و برخی بخش‌ها تکمیل یا قطع شده‌اند؛ در کل، ترکیب پین کردن SHA، استفاده از Renovate برای به‌روزرسانی‌های کنترل‌شده، فروشنده کردن وابستگی‌های حیاتی، و تحلیل ایستا یک چارچوب چندلایه برای کاهش خطرات وابستگی‌ها و زنجیرهٔ تأمین فراهم می‌کند که هم امنیت و هم قابلیت نگهداری را در نظر می‌گیرد. 🔧