تخطَّ إلى المحتوى

🔐 شرح أمن API وقواعد البيانات

إدارة الأسرار (Secrets Management)

الدرس 9 من 29· ⏱ 8 دقائق قراءة· 🗓 آخر تحديث: ١٩ أكتوبر ٢٠٢٦

ما هو «السر»؟ ولماذا يحتاج إدارة؟

السر (Secret) أي قيمة تمنح صلاحية لمن يملكها: مفتاح API، وكلمة مرور قاعدة البيانات، ومفتاح توقيع JWT، ورمز الدفع (sk_live_...)، ومفاتيح التشفير. من يحصل عليه لا يحتاج اختراق خادمك؛ يكفيه أن يتصرّف باسمك.

إدارة الأسرار تجيب عن أربعة أسئلة:

  1. أين يُخزَّن السر؟ (لا في الكود)
  2. كيف يصل للتطبيق عند التشغيل؟
  3. من يستطيع رؤيته، وأين قد يتسرّب دون أن تقصد؟
  4. ماذا تفعل حين يتسرّب؟ وكيف تغيّره دون توقف الخدمة؟

يبني هذا الدرس على CORS والمصادقة: مفتاح JWT الذي توقّع به الجلسات سر بالتعريف، وتسريبه يعني أن أي أحد يصنع جلسة باسم أي مستخدم.

الخطوة 1: أخرج السر من الكود

// خطأ: السر في الكود يصبح جزءًا من كل نسخة وكل commit
const stripe = new Stripe("sk_live_51H...");

// صحيح: الكود يقرأ السر من البيئة عند التشغيل
const stripe = new Stripe(process.env.STRIPE_API_KEY);

متغيرات البيئة هي الطريقة الأساسية: الكود واحد في كل مكان، والقيمة تختلف حسب مكان التشغيل (جهازك، خادم الاختبار، الإنتاج).

محليًا: ملف .env

# .env  ← على جهازك فقط
DB_PASSWORD=supersecret123
JWT_SECRET=change-me-to-32-random-bytes
STRIPE_API_KEY=sk_test_51H...

لا تحتاج مكتبة لقراءته في Node.js الحديث. جرّبنا على Node 22:

node --env-file=.env app.js              # يحمّل الملف ثم يشغّل التطبيق
node --env-file-if-exists=.env app.js    # لا يفشل إن لم يوجد الملف
process.loadEnvFile(".env");             // أو من داخل الكود

--env-file متاحة منذ Node 20.6. وفي المشاريع الأقدم تؤدي حزمة dotenv الدور نفسه (require("dotenv").config()). وفي بايثون: python-dotenv أو متغيرات البيئة مباشرة عبر os.environ.

ثلاث قواعد لملف .env

# .gitignore
.env
.env.*
!.env.example
# .env.example  ← يُرفع إلى git: الأسماء بلا قيم
DB_PASSWORD=
JWT_SECRET=
STRIPE_API_KEY=
  1. .env في .gitignore قبل أول commit.
  2. .env.example يوثّق المتغيرات المطلوبة لمن يستنسخ المشروع.
  3. لا تُرسل .env في رسالة أو تذكرة. شارك الأسرار عبر مدير كلمات مرور أو مدير أسرار الفريق.

فخ: إن كان الملف مرفوعًا سابقًا، فإضافته لـ .gitignore بعد ذلك لا تكفي. جرّبنا: الملف بقي متتبَّعًا، ولم يتوقف التتبّع إلا بـ:

git rm --cached .env      # يوقف تتبّعه ويبقيه على جهازك
git commit -m "stop tracking .env"

وحتى هذا لا يمسح القيمة من التاريخ. اقرأ القسم التالي.

تحقّق من الأسرار عند الإقلاع

const required = ["DATABASE_URL", "JWT_SECRET", "STRIPE_API_KEY"];
const missing = required.filter((k) => !process.env[k]);
if (missing.length) {
  console.error(`متغيرات بيئة ناقصة: ${missing.join(", ")}`);
  process.exit(1);
}

الأفضل أن يرفض التطبيق الإقلاع برسالة واضحة، بدل أن يعمل وينهار عند أول طلب دفع بخطأ غامض. (والنقص يظهر غالبًا كـ خطأ 500 في الإنتاج.)

الخطوة 2: اعرف أين تتسرّب الأسرار

أغلب التسريبات ليست اختراقًا، بل سر وصل لمكان لم يتوقعه صاحبه.

1) تاريخ git: الحذف لا يحذف

git log --all --oneline -S "sk_live_"     # كل commit أضاف أو حذف هذا النص
git show <commit>:.env                     # محتوى الملف كما كان

جرّبنا: رفعنا .env فيه مفتاح، ثم حذفناه وأضفناه لـ .gitignore في commit تالٍ. المجلد صار نظيفًا، لكن الأمر الأول وجد المفتاح فورًا، والثاني طبع الملف كاملًا. أي سر رُفع مرة إلى مستودع مشترك أو عام يُعدّ مسرّبًا، حتى لو حذفته بعد دقيقة، فهناك أدوات تمسح GitHub العام باستمرار بحثًا عن المفاتيح.

2) كود الواجهة الأمامية: كل ما يصل المتصفح عام

# .env في مشروع Vite
DB_PASSWORD=supersecret123
VITE_API_URL=https://api.example.com

بنينا مشروع Vite بهذا الملف، وصفحة تقرأ المتغيرين، ثم بحثنا في ملفات dist/ الناتجة: api.example.com موجود، وsupersecret123 غير موجود. Vite يدمج في الكود المتغيرات التي تبدأ بـ VITE_ فقط. وNext.js يفعل المثل مع NEXT_PUBLIC_.

القاعدة: أي متغير ببادئة «عامة» يقرؤه كل زائر بفتح أدوات المطوّر. لا تضع خلفها سرًا أبدًا. ومفتاح خدمة يجب أن يبقى سريًا (الدفع، والبريد، وقاعدة البيانات) يُستخدم في الخادم فقط، والواجهة تطلب من خادمك.

3) السجلات ورسائل الخطأ

console.log("config:", process.env);            // يطبع كل الأسرار في السجل
console.log("connecting to", process.env.DATABASE_URL);   // فيه كلمة المرور

السجلات تُنسخ لأنظمة مراقبة ويقرؤها فريق أكبر. لا تطبع كائن الإعدادات كاملًا، ولا روابط الاتصال (postgres://user:password@host). وصفحات الخطأ في الإنتاج لا تعرض تفاصيل البيئة (DEBUG = False في Django).

4) صور Docker

# خطأ: القيمة تُحفظ في طبقات الصورة، ويقرؤها docker history وأي من يسحب الصورة
ENV STRIPE_API_KEY=sk_live_51H...
COPY .env .

مرّر الأسرار عند التشغيل (docker run --env-file .env أو إعدادات المنصة)، لا عند البناء. وأضف .env إلى .dockerignore. وإن احتجت سرًا أثناء البناء نفسه (رمز لمستودع حزم خاص مثلًا)، استخدم أسرار BuildKit التي لا تُحفظ في الصورة:

RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
docker build --secret id=npmrc,src=$HOME/.npmrc .

(أمن الحاويات بالتفصيل في درس أمن الحاويات.)

الخطوة 3: في الإنتاج، لا ملفات

على الخادم لا تنسخ ملف .env. ضع الأسرار في إعدادات المنصة (Vercel وRender وRailway وغيرها تملك قسم Environment Variables)، أو في مدير أسرار حين يكبر النظام.

متى تحتاج مدير أسرار (Vault)؟

الحاجةمتغيرات المنصةمدير أسرار
تطبيق واحد وفريق صغيركافيةزائد
خدمات كثيرة تتشارك أسرارًاتكرار وفوضىمصدر واحد
من قرأ السر ومتى؟ (سجل تدقيق)محدودمدمج
تدوير تلقائي وأسرار مؤقتةيدويمدمج
صلاحيات دقيقة لكل خدمةمحدودةمدمجة

AWS Secrets Manager

import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";

const client = new SecretsManagerClient({ region: "eu-central-1" });

export async function getDbPassword() {
  const res = await client.send(new GetSecretValueCommand({ SecretId: "prod/db" }));
  return JSON.parse(res.SecretString).password;
}

هذه صيغة الإصدار 3 من SDK (تحقّقنا من أسماء الأوامر في الحزمة نفسها). والتطبيق لا يحمل مفتاحًا لقراءة السر: صلاحيته تأتي من دور (IAM role) مربوط بالخادم أو الحاوية نفسها، فلا يبقى «سر لحماية الأسرار» في أي ملف.

HashiCorp Vault

import vault from "node-vault";

const client = vault({ endpoint: "https://vault.example.com", token: process.env.VAULT_TOKEN });

export async function getApiSecrets() {
  const res = await client.read("secret/data/api");   // محرك KV الإصدار 2
  return res.data.data;                               // القيم داخل data.data في KV v2
}

اقرأ الأسرار عند الإقلاع واحفظها في الذاكرة، أو خزّنها مؤقتًا لدقائق، بدل طلبها مع كل طلب HTTP.

الأسرار في CI/CD

# .github/workflows/deploy.yml
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        env:
          DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
        run: ./deploy.sh
  • GitHub يخفي قيم الأسرار في سجل التشغيل (تظهر ***)، لكن لا يخفي تحويلاتها: طباعة السر بترميز base64 مثلًا تكشفه. لا تطبع الأسرار في الخطوات أبدًا.
  • لا تُمرَّر الأسرار لسير العمل الذي يشغّله pull request من fork، حتى لا يسرقها كود غريب.
  • الأفضل من مفتاح سحابي طويل الأمد: OIDC. GitHub Actions يثبت هويته لـ AWS أو GCP أو Azure، فيحصل على صلاحيات مؤقتة لساعة دون أي مفتاح مخزّن في الأسرار.

(بناء خط النشر نفسه في مسار DevOps.)

التدوير: غيّر السر قبل أن تحتاج لذلك

التدوير (Rotation) استبدال السر بآخر جديد دوريًا، أو فورًا عند الشك. والهدف أن يكون السر المسرّب عديم القيمة بأسرع وقت. الطريقة دون توقف الخدمة:

  1. أنشئ السر الجديد، واجعل الخدمة الأخرى تقبل القديم والجديد معًا.
  2. حدّث تطبيقك ليستخدم الجديد، وانشر.
  3. بعد التأكد، ألغِ القديم.

ومفتاح توقيع JWT يُدوَّر بالفكرة نفسها: وقّع بالمفتاح الجديد، واقبل التحقّق بالمفتاحين حتى تنتهي صلاحية الجلسات القديمة. وأفضل سر هو السر قصير العمر: رموز مؤقتة بساعة بدل مفتاح يعيش سنوات.

تسرّب سر: ماذا تفعل الآن؟ (بهذا الترتيب)

  1. ألغِ السر فورًا من لوحة الخدمة (Stripe أو AWS أو قاعدة البيانات)، وأنشئ بديلًا. هذه الخطوة الأولى لأنها الوحيدة التي توقف الضرر.
  2. حدّث التطبيق بالسر الجديد وانشر.
  3. افحص الاستخدام: سجلات الخدمة في فترة التسريب. هل استُخدم المفتاح من مكان غريب؟
  4. نظّف التاريخ إن أردت (git filter-repo لإزالة الملف من كل commit). هذا يأتي أخيرًا، لأن السر نُسخ غالبًا قبل أن تنظّف.

تنظيف التاريخ وحده لا يكفي أبدًا. إن لم تُلغِ السر، فهو ما زال يعمل عند من نسخه.

الوقاية الآلية: افحص قبل أن ترفع

  • GitHub Secret Scanning و Push Protection: يرفض الدفع حين يكتشف صيغة مفتاح معروفة (لمزوّدين كثيرين). فعّله في إعدادات المستودع.
  • gitleaks أو trufflehog: أدوات مفتوحة المصدر تفحص المستودع وتاريخه، وتعمل كـ hook قبل الـ commit أو كخطوة في CI.
  • مراجعة الكود: أي .env أو مفتاح في diff يوقف الدمج.

أخطاء شائعة

  • سر في الكود «مؤقتًا للتجربة»: يصل git ويبقى في التاريخ.
  • .gitignore بعد أول commit: الملف ما زال متتبَّعًا. استخدم git rm --cached.
  • سر خلف VITE_ أو NEXT_PUBLIC_: صار عامًا في كل متصفح.
  • console.log(process.env): كل الأسرار في السجلات.
  • ENV أو COPY .env في Dockerfile: السر داخل طبقات الصورة.
  • تنظيف التاريخ بدل إلغاء السر: المفتاح المسرّب ما زال يعمل.
  • مفتاح واحد لكل البيئات: تسريب بيئة التطوير يفتح الإنتاج. لكل بيئة أسرارها.

الخلاصة

  • الأسرار خارج الكود: .env محليًا مع .gitignore و.env.example، وإعدادات المنصة أو مدير أسرار في الإنتاج.
  • اعرف طرق التسريب الأربع: تاريخ git، ومتغيرات الواجهة العامة، والسجلات، وصور Docker.
  • مدير أسرار (AWS Secrets Manager أو Vault) حين تكثر الخدمات أو تحتاج تدقيقًا وتدويرًا.
  • في CI: أسرار المنصة، والأفضل OIDC بلا مفاتيح دائمة.
  • عند التسريب: ألغِ أولًا، ثم حدّث، ثم افحص، ثم نظّف.

🎯 التالي: تشفير البيانات (Data Encryption)

شرح إدارة الأسرار (Secrets Management) — أمن API وقواعد البيانات بالعربي
إدارة الأسرار (Secrets Management) — أمن API وقواعد البيانات بالعربي · The Code Fix

هل كان هذا الدرس مفيدًا؟