Docker در ۲۰ اکتبر ۲۰۲۶ نسخهٔ Docker Engine 29.9.2 را منتشر کرد؛ یک وصلهٔ ظاهراً معمولی که فقط runtime زبان Go را بهروز میکند. اما معمولی نیست. runtime جدید باعث میشود daemon هر زنجیرهٔ گواهی TLS را که از یکی از هشت CA میانیِ eMudhra عبور کند رد کند، چون کلیدهای خصوصی این هشت گواهی لو رفتهاند. اگر registry خصوصی، mirror یا سرور فایلِ شما گواهیاش را از یکی از این CAهای میانی گرفته باشد، از لحظهٔ ارتقا docker pull و docker push روی آن کار نمیکنند.
پیام خطا هم اوضاع را بدتر میکند، چون علت را اشتباه نشان میدهد. در ادامه میگوییم چه چیزی تغییر کرد، چطور در چند دقیقه هاستها و registryهایتان را بررسی کنید و در تنظیمات daemon، Dockerfileها و CI چه چیزی را باید عوض کنید.
چه اتفاقی افتاد، به ترتیب
طبق افشای عمومی این ماجرا (همان صفحهای که یادداشتهای انتشار خودِ Docker به آن لینک دادهاند)، یک پژوهشگر با امضای یک پیام با کلید خصوصی هر کدام از هشت گواهی میانی eMudhra ثابت کرد که این کلیدها در اختیارش است و در ۱۷ اکتبر موضوع را به برنامههای root مرورگرها و سیستمعاملها گزارش داد. همان صفحه میگوید این کلیدها «توسط CA بهاشتباه تولید شده بودند»، یعنی هر کسی میتوانست آنها را از روی خودِ گواهیها بازیابی کند. Chrome و Firefox از ۱۷ اکتبر، Microsoft از ۲۰ اکتبر و Apple از ۲۲ اکتبر این گواهیهای میانی را مسدود کردند.
مرورگرها از طریق فیدهای ابطال (revocation) اصلاح شدند. اما بسیاری از کلاینتهای دیگر اصلاً دادههای ابطال را نمیخوانند و صفحهٔ افشا صریحاً میگوید این کلاینتها «برای اینکه دیگر به گواهیهای میانیِ لورفته اعتماد نکنند به بهروزرسانی نرمافزار نیاز دارند». CLI، daemon، Buildx و Compose در Docker همگی برنامههای Go هستند، پس اصلاحشان از Go آمد.
- Go 1.26.10 در ۱۹ اکتبر با یک اصلاح امنیتی در crypto/x509 منتشر شد. issue مربوط به CVE-2026-111943 در Go میگوید: «Verify از این پس زنجیرههایی را که شامل این گواهیهای میانی هستند فیلتر میکند.» همین اصلاح بهصورت Go 1.27.3 به شاخهٔ Go 1.27 هم منتقل شد.
- Docker Engine 29.9.2 و Buildx 0.38.1 در ۲۰ اکتبر منتشر شدند و هر دو با Go 1.26.10 ساخته شدهاند.
- Docker Compose 5.6.1 در ۲۱ اکتبر با همین بهروزرسانی Go آمد.
- Docker Desktop اصلاح را تکهتکه گرفت: 4.96.1 در ۲۱ اکتبر (Engine 29.9.2 و Buildx 0.38.1)، و سپس 4.96.2 و 4.96.3 در ۲۲ اکتبر برای بقیهٔ اجزای همراه.
هشت گواهی میانیِ مسدودشده، طبق یادداشتهای انتشار Engine، اینها هستند: EM DV TLS CA - G3B، EM DV TLS CA - G3C، EM OV TLS CA - G3B، EM OV TLS CA - G3C، EM EV TLS CA - G3B، EM EV TLS CA - G3C و دو CA امضای کد، یعنی EM OV CS CA - G3B و EM OV CS CA - G3C.
چه چیزی خراب میشود و خطا چه شکلی است
یادداشتهای انتشار 29.9.2 موضوع را در یک جمله گفتهاند: «اگر زنجیرهٔ گواهی یک registry به یکی از این گواهیهای میانی TLS متکی باشد، pull و push ایمیجها در تأیید گواهی TLS شکست میخورند.» هیچ پرچمی برای خاموش کردن این فیلتر وجود ندارد.
سایت افشا هاستهای آزمایشی دارد که گواهیهایی از هر کدام از این CAهای مسدود ارائه میدهند. ما در ۲ نوامبر هاست Docker 29.9.2 خودمان را به یکی از آنها وصل کردیم:
$ docker pull dv-g3c-emudhra.we-have-a-problem.com/test:latest
Error response from daemon: Get "https://dv-g3c-emudhra.we-have-a-problem.com/v2/": tls: failed to verify certificate: x509: certificate signed by unknown authority
این خطا را با دقت بخوانید. «unknown authority» همان پیامی است که برای registry با گواهی self-signed هم میبینید، برای همین اولین واکنش بیشتر کسانی که آنکال هستند این است که registry را insecure علامت بزنند یا یک فایل CA اضافه کنند. هیچکدام راهحل درست این مشکل نیست و اولی اوضاع را بدتر میکند (پایینتر توضیح میدهیم).
registryهای عمومی بزرگ درگیر نیستند. در ۲ نوامبر زنجیرهٔ گواهی Docker Hub (registry-1.docker.io)، quay.io و public.ecr.aws (همه زیر rootهای Amazon)، ghcr.io و registry.gitlab.com (Sectigo) و gcr.io (Google Trust Services) را بررسی کردیم. هیچکدام از eMudhra عبور نمیکنند. ریسک متوجه Harbor، Nexus، Artifactory یا registry:2 خودمیزبان، mirrorهای pull-through و سرورهای داخلی است که CI از آنها فایل دانلود میکند.
گام ۱: ببینید چه نسخههایی اجرا میکنید
این سه دستور فقطخواندنی هستند و روی هاست production هم بیخطرند:
docker version --format '{{.Client.Version}} / {{.Server.Version}}'
docker buildx version
docker compose version --short
روی هاست ما خروجی 29.9.2 / 29.9.2، v0.38.1 و 5.6.1 است. هر نسخهای قدیمیتر از Engine 29.9.2، Buildx 0.38.1 یا Compose 5.6.1 از بستههای خودِ Docker با runtime قدیمیتر Go ساخته شده و هنوز زنجیرههای لورفته را میپذیرد. این همان مشکل امنیتی است که بهروزرسانی برای رفعش آمده، پس راهحل ارتقا و بعد رسیدگی به چیزهایی است که خراب میشوند، نه عقب ماندن.
گام ۲: همهٔ registryهایی را که از آنها pull میکنید بررسی کنید
هاستهای registry را که stackهایتان به آنها ارجاع میدهند فهرست کنید. این دستور فایلهای compose روی دیسک را هم پیدا میکند، از جمله stackهایی که الان متوقفاند:
grep -rhoE 'image: *"?[a-z0-9.-]+\.[a-z]{2,}(:[0-9]+)?/' --include='*compose*.y*ml' /srv /opt 2>/dev/null \
| sed -E 's/image: *"?//; s#/$##' | sort -u
بعد از هر کدام بپرسید چه زنجیرهای ارائه میدهد و دنبال نام گواهیهای میانی eMudhra بگردید:
for reg in registry.example.com mirror.example.com:5000; do
host=${reg%%:*}; port=${reg##*:}; [ "$port" = "$reg" ] && port=443
if echo | openssl s_client -connect "$host:$port" -servername "$host" -showcerts 2>/dev/null \
| grep -qE 'CN ?= ?EM (DV|OV|EV) TLS CA - G3[BC]'; then
echo "AFFECTED: $reg"
else
echo "ok: $reg"
fi
done
ما این حلقه را روی ghcr.io و روی هاست آزمایشی G3C اجرا کردیم: اولی ok و دومی AFFECTED برگرداند. هاستهایی را که pipelineهای CI از آنها دانلود میکنند هم اضافه کنید، چون buildی که یک فایل باینری را از سرور داخلی میگیرد هم یک کلاینت TLS دیگر است.
گام ۳: registry را درست کنید، نه کلاینت را
یادداشتهای Docker صریح است: «registryهای آسیبدیده به یک زنجیرهٔ گواهی جایگزین نیاز دارند که از این گواهیهای میانی استفاده نکند.» در عمل یعنی یک گواهی جدید از یک CA دیگر (یا زنجیرهٔ صدور دیگری از فروشندهتان)، نصبشده روی هر چیزی که جلوی registry اتصال TLS را خاتمه میدهد؛ خودِ registry باشد یا nginx یا Traefik یا load balancer.
میانبُرِ وسوسهانگیز این است، در /etc/docker/daemon.json:
{
"insecure-registries": ["registry.example.com"]
}
این را به production نبرید. این تنظیم به daemon میگوید بررسی گواهی را برای آن هاست کنار بگذارد، یعنی دقیقاً همان حفاظتی که بهروزرسانی Go اضافه کرد. هر کسی که بتواند در مسیر شبکه بنشیند و گواهیای از یکی از این کلیدهای لورفته داشته باشد، میتواند هر ایمیجی که بخواهد را به اسم registry شما تحویلتان بدهد.
اگر registry داخلی است و نمیخواهید گواهی عمومی دیگری بخرید، از CA خودتان یک گواهی صادر کنید و فایل CA را همانطور که مستندات Docker دربارهٔ گواهی registry توضیح میدهد در اختیار Docker بگذارید:
/etc/docker/certs.d/
registry.example.com/
ca.crt
زنجیرهای که به CA خودتان ختم میشود هیچوقت از eMudhra رد نمیشود، پس فیلتر چیزی برای رد کردن ندارد. وقتی گواهی جدید فعال شد، حلقهٔ گام ۲ را دوباره اجرا کنید و برای اطمینان یک ایمیج pull کنید.
گام ۴: ایمیجهایتان هم همین اصلاح را لازم دارند
ارتقای Docker از خودِ Docker محافظت میکند و برای نرمافزارِ داخل کانتینرها کاری نمیکند. صفحهٔ افشا کلاینتهایی را که هنوز در خطرند نام میبرد: «اپلیکیشنها و کتابخانههایی که بستهٔ CA خودشان را همراه دارند» بهعلاوهٔ اپهای Electron، و یادآوری میکند که بسیاری از کلاینتهای غیرمرورگر ابطال را بررسی نمیکنند.
ایمیجی که با بستهٔ تازهٔ ca-certificates دوباره ساخته شود بهتنهایی این را حل نمیکند. بستهٔ CA فقط rootهای مورد اعتماد را فهرست میکند و راهی ندارد بگوید «به این root اعتماد کن، بهجز این گواهیهای میانیِ زیرش». رد کردن باید از کتابخانهٔ TLS بیاید، همان کاری که Go کرد. پس برای سرویسهای Go، راهحل build دوباره با toolchain وصلهشده است. در یک Dockerfile چندمرحلهای:
# before
FROM golang:1.26.9 AS build
# after
FROM golang:1.26.10 AS build
هر دو ایمیج golang:1.26.10 و golang:1.27.3 روی Docker Hub موجودند. اگر go.mod شما toolchain را pin کرده، آن را هم بالا ببرید، وگرنه نسخهٔ قدیمی بههرحال دانلود میشود:
# before
toolchain go1.26.9
# after
toolchain go1.26.10
build دوباره فقط وقتی فایده دارد که کانتینرها از ایمیج جدید دوباره ساخته شوند. دربارهٔ این تله در راهنمای rebuild برای PHP2Shell نوشتهایم: base image وصلهشده برای کانتینری که از ایمیج قدیمی بالا آمده هیچ کاری نمیکند.
runnerهای CI هم ارزش یک نگاه را دارند. اگر یک workflow نسخهٔ Buildx را از طریق docker/setup-buildx-action pin کرده باشد، همین pin تعیین میکند pushها با کدام runtime Go انجام شوند:
# before
- uses: docker/setup-buildx-action@v4
with:
version: v0.38.0
# after
- uses: docker/setup-buildx-action@v4
with:
version: v0.38.1
لپتاپهای دارای Docker Desktop
Docker در یادداشتهای 4.96.1 هشدار داد که «این اصلاح بهصورت دستهای و همزمان با بهروزرسانی تکتک اجزای Docker Desktop عرضه میشود.» تیمی که روی 4.96.0 است هیچ بخشی از آن را ندارد و تیمی که روی 4.96.1 است بخش Engine را دارد ولی بقیه را نه. همه را دستکم به 4.96.3 برسانید.
کاربران Mac یک شکاف دیگر هم دارند. بهروزرسانی ۲۸ اکتبر در صفحهٔ افشا میگوید ابزارهای توسعهای که Apple همراه macOS عرضه میکند، از جمله git، curl و python3، هنوز به گواهیهای لورفته اعتماد دارند و macOS 27.2 اصلاحاتی را که کاربران دستی اعمال کرده بودند برگرداند. Docker Desktop وصلهشده روی آن لپتاپ چیزی را که git clone به آن اعتماد میکند تغییر نمیدهد.
روی هاستهای خودمان چه کردیم
سرویسهای production ما در Docker اجرا میشوند، پس این کار یک چکلیست کوتاه بود، نه یک پروژه: نسخههای Engine، Buildx و Compose را مثل بالا تأیید کردیم، بررسی زنجیره را روی همهٔ registryهایی که فایلهای compose ما به آنها ارجاع میدهند اجرا کردیم و مطمئن شدیم هیچکدام از گواهیهای خودمان از eMudhra نیست. چیزی درگیر نبود، و این نتیجهای است که میخواهید قبل از ارتقا بدانید، نه بعد از یک deploy ناموفق در ساعت ۲ بامداد. اگر عادت دارید نسخهها را روی همهٔ هاستها بررسی کنید، همان روش راهنمای ارتقای PostgreSQL 14 اینجا هم کار میکند، و نوشتهٔ ما دربارهٔ اینکه CMS خودمان را چطور روی Docker اجرا میکنیم نشان میدهد در یک stack خودمیزبانِ معمولی، TLS کجا خاتمه پیدا میکند.
پرسشهای متداول
آیا میتوانم Docker Engine را downgrade کنم تا pull دوباره کار کند؟
میتوانید، اما engine قدیمی گواهی جعلیای را هم که با یکی از کلیدهای لورفته برای هر registry، از جمله Docker Hub، امضا شده باشد میپذیرد. بهجای آن گواهی registry را عوض کنید.
آیا Docker Hub درگیر است؟
نه. در ۲ نوامبر ۲۰۲۶ زنجیرهٔ گواهی registry-1.docker.io از Amazon RSA 2048 M01 و Amazon Root CA 1 عبور میکرد، نه eMudhra. همین برای ghcr.io، quay.io، public.ecr.aws، registry.gitlab.com و gcr.io هم صادق بود.
registry ما از گواهی self-signed یا CA داخلی استفاده میکند. باید کاری بکنیم؟
بهخاطر این تغییر نه. فیلتر فقط زنجیرههایی را حذف میکند که شامل هشت گواهی میانی eMudhra باشند. زنجیرهای که به CA خودتان ختم میشود و در /etc/docker/certs.d تنظیم شده همچنان کار میکند.
آیا بهروز کردن ca-certificates در ایمیجها گواهیهای میانیِ لورفته را مسدود میکند؟
نه. بستهٔ CA فقط rootها را فهرست میکند، پس نمیتواند گواهیهای میانیِ مشخصی را زیر rootی که مورد اعتماد میماند کنار بگذارد. مسدودسازی باید از کتابخانهٔ TLS یا بررسی ابطال بیاید. برای برنامههای Go، با Go 1.26.10 یا 1.27.3 دوباره build کنید.
کدام نسخههای Docker این اصلاح را دارند؟
Docker Engine 29.9.2، Buildx 0.38.1 و Compose 5.6.1 که در ۲۰ و ۲۱ اکتبر ۲۰۲۶ منتشر شدند. در Docker Desktop این اصلاح در نسخههای 4.96.1، 4.96.2 و 4.96.3 رسید.
منابع: یادداشتهای انتشار Docker Engine 29، انتشار docker-v29.9.2 در moby/moby، Buildx v0.38.1، Compose v5.6.1، یادداشتهای انتشار Docker Desktop، issue شمارهٔ 82345 در Go، تاریخچهٔ انتشار Go و افشای لو رفتن CAهای میانی eMudhra. دستورها در ۲ نوامبر ۲۰۲۶ روی Docker Engine 29.9.2، Buildx 0.38.1 و Compose 5.6.1 آزمایش شدند.



