الوحدة 2 · Images جاهزة للإنتاج
فحص الـ Images وإبعاد الأسرار عنها
20 دقراءة 4 داكتشف الخطأ
الدرس في لمحة
افحص images notes-api بحثًا عن الثغرات المعروفة باستخدام Docker Scout، وصنّف ما تجده حسب الأولوية، واستخدم secret mounts في BuildKit حتى لا تصل الـ tokens المطلوبة وقت البناء إلى أي layer في الـ image.
- 1افحص قبل أن تشحن
- 2صنّف بترتيب معقول
- 3كيف تتسرّب الأسرار إلى الـ images
- 4الـ secret mounts في BuildKit
- 5أسرار وقت التشغيل ليست من شأن الـ image
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
أسوأ حادث في سجلّي لم يكن انهيارًا. خطوة بناء احتاجت إلى token لـ registry خاص بـ npm، فمرّره أحدهم بوصفه وسيط بناء (build argument). ذهبت الـ image إلى registry متاح للقراءة على نطاق واسع، وبعد أشهر وجد تدقيق روتيني الـ token قابعًا في البيانات الوصفية للـ image، صالحًا طوال تلك المدة. هذا الدرس عمّا بداخل الـ images: الأجزاء التي لم تكتبها، والأجزاء التي لم تقصد تضمينها.
افحص قبل أن تشحن
كل حزمة في الـ image، من libssl في Debian إلى express في npm، لها تاريخ علني من الثغرات (CVEs). الماسح يقرأ جرد الحزم في الـ image ويطابقه مع قواعد البيانات تلك. Docker Scout مدمج في واجهة Docker CLI:
docker scout quickview notes-api:1.4.0
docker scout cves --only-severity critical,high notes-api:1.4.0
docker scout recommendations notes-api:1.4.0الأمر quickview يعطي ملخّصًا في شاشة واحدة: عدد الثغرات في الـ image لديك، وفي الـ base image التي بُنيت عليها، وفي إصدار أحدث من تلك الـ base إن وُجد. والأمر cves يسرد كل نتيجة مع الحزمة، والإصدار المثبّت، والإصدار الذي يصلحها. والأمر recommendations يقترح tags لـ base images فيها ثغرات معروفة أقل. Trivy وGrype ماسحان مفتوحا المصدر شائعان يؤدّيان العمل نفسه؛ اختر واحدًا واستخدمه في كل مكان.
صنّف بترتيب معقول
القائمة الطويلة من النتائج ليست قائمة مهام. اعمل عليها هكذا:
- نتائج الـ base image أولًا. إذا جاءت معظم النتائج من حزم نظام التشغيل، فحدّث إلى base مرقّعة حديثًا أو انتقل إلى base أصغر. الانتقال إلى distroless في الدرس 2.2 يزيل معظمها بخطوة واحدة.
- اعتماديات التطبيق القابلة للإصلاح بعد ذلك. إذا وُجد إصدار مُصلَح، فارفع رقم إصداره في
package.json، وشغّلnpm ci، وأعِد البناء. - النتائج غير القابلة للإصلاح تحتاج إلى قرار، لا إلى صمت. اكتب لماذا لا يمكن الوصول إلى مسار الكود ذاك، أو ما الذي يخفّف الخطر، وحدّد موعدًا للنظر فيها مجددًا.
ثم اجعل الفحص بوّابة حتى لا تتسلّل الانتكاسات (regressions):
docker scout cves --only-severity critical,high --exit-code notes-api:1.4.0مع --exit-code يفشل الأمر حين يجد ثغرات بتلك الدرجات من الخطورة، فتفشل مهمّة الـ CI قبل الدفع. وما دمت في الـ CI، فاطلب من أداة البناء بيانات سلسلة التوريد أيضًا: docker buildx build --sbom=true --provenance=mode=max … يرفق بالـ image قائمة مكوّنات البرمجيات (SBOM) وبيانات مصدر البناء (provenance)، فيصبح التدقيق التالي بحثًا سريعًا بدلًا من تحقيق.
كيف تتسرّب الأسرار إلى الـ images
الـ images تتذكّر أكثر مما تظن. ثلاثة تسريبات شائعة:
ENV API_KEY=…تُخزَّن في إعدادات الـ image وتُضبط في كل container. والأمرdocker image inspectيُظهرها.ARG NPM_TOKENالمستخدمة في خطوةRUNتُسجَّل في سجلّ بناء الـ image. والأمرdocker history --no-truncقد يكشفها.- ملف كُتب ثم حُذف لاحقًا (
COPY .npmrcثمRUN rm .npmrc) ما زال موجودًا في الـ layer السابقة. أي شخص لديه الـ image يستطيع استخراج تلك الـ layer.
تحذّر build checks في Docker من الأوّلين (SecretsUsedInArgOrEnv) حين يوحي اسم المتغيّر بأنه سرّ. خذ التحذير بجدية.
الـ secret mounts في BuildKit
الأداة الصحيحة هي الـ secret mount: يُتاح السرّ لخطوة RUN واحدة على هيئة ملف (أو متغيّر بيئة) ولا يُكتب أبدًا في layer. بناء notes-api يحتاج إلى token لحزمة خاصة، لذا:
FROM node:24-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
--mount=type=cache,target=/root/.npm \
npm cidocker build --secret id=npmrc,src=$HOME/.npmrc -t notes-api:1.4.0 .خلال خطوة RUN تلك، يجد npm إعداداته في /root/.npmrc. وقبلها وبعدها لا يوجد الملف، ولا يُخزَّن عنه شيء في الـ image أو في الـ build cache. ودون target، تُركَّب الأسرار في /run/secrets/<id>. وإذا كانت أداة ما تقرأ الـ token من متغيّر بيئة، فركّبه متغيّرَ بيئة بـ RUN --mount=type=secret,id=npm_token,env=NPM_TOKEN npm ci ومرّر --secret id=npm_token,env=NPM_TOKEN في سطر الأوامر، فتُقرأ القيمة من المتغيّر NPM_TOKEN في الـ shell لديك.
أسرار وقت التشغيل ليست من شأن الـ image
كلمة مرور قاعدة البيانات التي يستخدمها notes-api في الإنتاج لا مكان لها في الـ image إطلاقًا. إنها تصل حين يبدأ الـ container: من متغيّر بيئة في Compose، أو من Kubernetes Secret، أو من مدير الأسرار في منصّتك السحابية. ستربط الأوّلين في الأقسام القادمة. تبقى الـ image متطابقة عبر البيئات؛ ولا يتغيّر إلا الإعداد المحقون.
انتهى القسم الثاني: الـ image لديك نحيفة، وبلا صلاحيات، وقابلة للتتبّع، ونظيفة. في القسم التالي ستعطيها قاعدة بيانات تتحدّث معها، باستخدام Docker Compose.
أهم النقاط
- الأمر
docker scout cvesيسرد ثغرات CVE المعروفة لكل حزمة؛ ومع--only-severity critical,high --exit-codeيتحوّل إلى بوّابة في الـ CI. - أصلح نتائج الـ base image أولًا بتحديثها أو تغييرها؛ والأمر
docker scout recommendationsيقترح بدائل. - كل ما يُمرَّر بـ
ARGأوENV، أو يُكتب في ملف داخل خطوةRUN، يمكن استرجاعه من الـ image، حتى لو حذفته خطوة لاحقة. - التعليمة
RUN --mount=type=secretتتيح السرّ لخطوة بناء واحدة دون كتابته في أي layer. - أسرار وقت التشغيل مثل كلمات مرور قاعدة البيانات تُحقن عند بدء الـ container، ولا تُبنى داخل الـ image أبدًا.