المستوى: مبتدئ. الكلام ده موجّه لأي حد بيبني صورة Docker بنفسه وينشرها، حتى لو لسه في أول الطريق مع الحاويات.
لو الحاوية بتاعتك بتشتغل كـ root — وده الوضع الافتراضي في أغلب الصور — يبقي ثغرة واحدة في تطبيقك ممكن تتحوّل لسيطرة على السيرفر كله. سطرين في الـ Dockerfile بيقفلوا الباب ده. تعال نشوف إزاي.
المشكلة باختصار
لما تكتب Dockerfile عادي وتبنيه، العملية جوّه الحاوية بتشتغل بصلاحية root (المستخدم رقم صفر) طول ما إنت مقلتش غير كده. الافتراض الشائع إن الحاوية "معزولة" فمفيش مشكلة. الافتراض ده ناقص.
الحاوية مش جهاز افتراضي منفصل. هي عملية بتشتغل على نواة (kernel) المضيف نفسه. يعني root جوّه الحاوية هو نفس root بتاع النواة بتاعت السيرفر، والفرق بينهم مجرد طبقة عزل رفيعة ممكن تتخرق.
مثال يقرّب الفكرة
تخيّل إن الحاوية شقة في عمارة. لو الساكن ماسك مفتاح العمارة الرئيسي (root)، فأي حرامي يقدر يضحك عليه هيقدر يفتح كل الشقق ومكتب البواب كمان. لكن لو الساكن ماسك مفتاح شقته هو بس (مستخدم عادي)، الحرامي هيفضل محبوس جوّه الشقة، مش هيوصل لباقي العمارة.
علميًا: لو مهاجم استغل ثغرة في تطبيقك وهو شغّال كـ root، بيبقى معاه صلاحية كاملة جوّه الحاوية، وبقى محتاج بس ثغرة "هروب" واحدة من الحاوية عشان يبقى root على المضيف. ده مش كلام نظري: ثغرة runc الشهيرة CVE-2019-5736 سمحت لحاوية شغّالة كـ root بالكتابة فوق ملف runc على المضيف والسيطرة عليه. لو الحاوية كانت شغّالة كمستخدم عادي، الهجوم ده كان بيفشل.
الحل: سطرين في الـ Dockerfile
المبدأ اسمه الأقل امتيازًا (Least Privilege): أعطِ العملية أقل صلاحية تكفي لشغلها بس. تطبيقه في Docker حرفيًا سطرين.
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
# 1) اعمل مستخدم عادي بدون صلاحيات
RUN useradd --system --uid 1000 appuser
# 2) خلّي ده هو المستخدم الافتراضي للحاوية
USER appuser
EXPOSE 8080
CMD ["node", "server.js"]
ملاحظة عملية: أغلب الصور الرسمية (زي node) بتيجي أصلاً بمستخدم غير جذري جاهز اسمه node بالرقم 1000، فتقدر تكتب USER node على طول بدون ما تعمل مستخدم جديد.
وفي Kubernetes كمان
حتى لو الصورة نفسها فيها ، فرض القاعدة على مستوى الكلاستر بيمنع أي صورة تتزحلق كـ root: