Secrets API في ووردبريس 7.2: خزّن مفاتيح API مشفّرة

خزنة مكعبات شفافة تحمي مفتاحًا مضيئًا، رمز لتخزين مفاتيح API في ووردبريس بشكل مشفّر

افتح جدول wp_options في أي موقع شغّال من سنتين، وغالبًا هتلاقي مفتاح Stripe أو مفتاح OpenAI أو كلمة سر SMTP قاعدين هناك نص صريح. أي نسخة احتياطية تتسرّب، أو أي إضافة فيها ثغرة قراءة، بتكشفهم كلهم مرة واحدة.

ووردبريس 7.2 بيحل المشكلة دي من جوه النواة: واجهة جديدة اسمها واجهة الأسرار (Secrets API) بتخزّن المفاتيح دي مشفّرة. المقال ده للي بيكتب إضافات أو بيدير مواقع عملاء، وفيه اللي محتاج تعمله قبل الترقية مش بعدها.

ما الذي تغيّر بالضبط؟

حتى الآن كان كل مطور يحفظ مفاتيح API (API Keys) بطريقته، وأغلب الطرق تنتهي بـ update_option() ونص صريح في قاعدة البيانات. واجهة الأسرار تعطي الإضافات مكانًا موحّدًا تُشفَّر فيه هذه القيم، وتحتفظ بنسخة سابقة واحدة من كل سر لتسهيل تدوير المفاتيح (Key Rotation).

التشفير يتم بمكتبة libsodium بخوارزمية XChaCha20-Poly1305، والسجلات المشفّرة تبقى في جداول الخيارات افتراضيًا. مفاتيح التشفير نفسها مشتقة من مفتاح جذري عشوائي، وهذا المفتاح الجذري مشفّر بدوره بقيمة WP_SECRETS_KEY التي تضعها أنت في wp-config.php.

تنبيه: الواجهة لا تمنع إضافة من قراءة أسرار إضافة أخرى. أي كود يعمل على الموقع يستطيع قراءة أي سر، فالحماية هنا ضد تسريب قاعدة البيانات والنسخ الاحتياطية، لا ضد إضافة خبيثة مثبّتة.

الخطوة الأولى لأي موقع قائم: WP_SECRETS_KEY

بدون مفتاح صالح لن تخزّن الواجهة شيئًا، وكل كتابة أو قراءة لسر مخزَّن ستعيد خطأ secret_key_unavailable. التثبيتات الجديدة تحصل على المفتاح تلقائيًا مع الـ Salts (مفاتيح التمليح)، أما المواقع القائمة فتحتاج إضافته يدويًا.

أسهل طريق: افتح صحة الموقع (Site Health) في لوحة التحكم، وستجد سطرًا جاهزًا تنسخه إلى wp-config.php تحت الـ salts مباشرة:

define( 'WP_SECRETS_KEY', '…a base64-encoded random 32-byte key…' );

ولو تفضّل توليده بنفسك من سطر الأوامر:

php -r 'echo base64_encode( random_bytes( 32 ) ), "\n";'

القيم التالية لا تُقبل مفتاحًا: قيمة فارغة، أو النص التجريبي الموجود في wp-config-sample.php، أو قيمة منسوخة من أحد مفاتيح المصادقة أو الـ salts.

تحذير: لا تعامل هذا المفتاح مثل الـ salts. تغيير الـ salts يسجّل خروج المستخدمين فقط، أما تغيير WP_SECRETS_KEY أو حذفه بعد تخزين أسرار فيجعلها كلها غير مقروءة حتى تعيد القيمة القديمة. احفظه في مدير كلمات المرور مع بيانات العميل، ولا تتركه في ملف واحد على السيرفر.

في ووردبريس: لو بتنقل موقع عميل بين استضافتين (مثلًا من استضافة مشتركة محلية إلى Hostinger)، انقل wp-config.php كما هو. أداة نقل تعيد توليد الملف من الصفر ستنتج مفتاحًا جديدًا وتكسر كل الأسرار المخزنة.

WordPress 7.2

4 خطوات قبل استخدام Secrets API

  1. أضف WP_SECRETS_KEY

    انسخ السطر من Site Health إلى wp-config.php تحت الـ salts.

  2. احفظ المفتاح في مكان آمن

    تغييره بعد التخزين يجعل كل الأسرار غير مقروءة.

  3. تحقق من التوفر

    wp_is_secrets_api_available() قبل نقل أي مفتاح قديم.

  4. انقل وفرّق الأخطاء

    null يعني لم يُخزَّن، وWP_Error يعني تعذّرت القراءة الآن.

ابن موقعك قطعة قطعةarabcms.com

كيف تستخدمها في إضافتك

الدالتان الأساسيتان هما wp_set_secret() وwp_get_secret(). هذا المثال من الوثيقة الرسمية كما هو:

$result = wp_set_secret( 'my-plugin/api-key', $api_key );

if ( is_wp_error( $result ) ) {
    // Nothing was stored. Keep the existing storage.
}

$secret = wp_get_secret( 'my-plugin/api-key' );

if ( $secret instanceof WP_Secret ) {
    $client->authenticate( $secret->reveal() );
} elseif ( null === $secret ) {
    // Never stored. Ask the user for it.
} else {
    // WP_Error: it exists but can't be read right now. Don't ask for a new one.
}

أهم نقطة في الكود: فرّق بين null وWP_Error. القيمة null تعني أن السر لم يُخزَّن أصلًا، فاطلبه من المستخدم. أما WP_Error فتعني أنه موجود لكن تعذّرت قراءته الآن، لأن المفتاح أو قاعدة البيانات غير متاحين. لو طلبت مفتاحًا جديدًا في هذه الحالة فقد تستبدل مفتاحًا ما زال يعمل.

قواعد التسمية

الاسم يكون على شكل plugin-slug/secret-name، بحروف إنجليزية صغيرة وأرقام وشرطات وشرطات سفلية فقط، وبشرطة مائلة واحدة. لا يبدأ الاسم أو ينتهي بشرطة أو شرطة سفلية. الاسم بلا شرطة مائلة يعمل لكنه يطلق تحذير _doing_it_wrong()، والبادئة هدفها منع الإضافات من الكتابة فوق أسرار بعضها.

الكائن WP_Secret لا يكشف نفسه

لو طبعت الكائن أو سجّلته في ملف log أو حوّلته إلى JSON أو مررته إلى var_dump()، ستظهر عبارة مثل [secret:my-plugin/api-key] لا القيمة. التسلسل (serialize) أو النسخ (clone) يطلق استثناء. القيمة الحقيقية تخرج فقط عبر reveal()، فاستدعها في السطر الذي تحتاجها فيه ولا تخزّن الكائن في كاش.

بقية الدوال

الدالةماذا تفعل
wp_delete_secret()تحذف السر
wp_list_secrets()تعرض الأسماء والبيانات الوصفية، لا القيم أبدًا
wp_import_option_as_secret()تنسخ خيارًا قديمًا إلى سر، وتعلّمه للتدوير
wp_retire_secret_version()تحذف النسخة السابقة بعد انتهاء التدوير
wp_set_network_secret() وأخواتهاأسرار على مستوى شبكة Multisite كاملة

دوال الموقع ودوال الشبكة لا ترجع إلى بعضها عند الفشل، فاختر المستوى الصحيح من البداية.

ملاحظة: الدوال لا تتحقق من الصلاحيات لأنها يجب أن تعمل من cron ومن REST. ضع current_user_can() بنفسك في أي نموذج يُدخل فيه المستخدم مفتاحًا أو يديره.

نقل مفتاح قديم من wp_options

لو إضافتك تخزّن مفتاحًا في الخيارات اليوم، لا تنقله قبل التأكد أن الواجهة جاهزة على هذا الموقع بالذات:

if ( wp_is_secrets_api_available() ) {
    // Move the credential.
} else {
    // Keep the existing storage.
}

الدالة تعيد false في ثلاث حالات: فشل تحميل ملف secrets.php، أو غياب WP_SECRETS_KEY أو عدم صلاحيته، أو تغيير المفتاح بعد تخزين أسرار بحيث لم يعد يفك المفتاح الجذري.

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

التدوير بدون توقف الخدمة

كل كتابة لقيمة جديدة تنقل القيمة الحالية إلى WP_Secret_Version::PREVIOUS، فتستطيع الرجوع إليها أثناء انتشار المفتاح الجديد. حفظ القيمة نفسها مرة أخرى، كما يحدث عند الضغط على «حفظ» في صفحة إعدادات، لا يمسح النسخة السابقة. وعندما تتأكد أن المفتاح الجديد يعمل، احذف القديم بـ wp_retire_secret_version().

الحدث wp_secret_changed يُطلق عند الإنشاء أو التحديث أو الاستيراد أو الحذف أو الإحالة للتقاعد، ويمرّر بصمات (fingerprints) لا قيمًا، فتستطيع تسجيل التغييرات في سجل تدقيق بأمان.

للاستضافات: ملف secrets.php

الاستضافة تستطيع استبدال جزء من الواجهة بـ الإضافة الضمنية (Drop-in) wp-content/secrets.php، التي تُحمَّل مثل db.php. ثلاثة أجزاء قابلة للاستبدال، كل واحد قطعة مستقلة تركّبها حسب بنيتك:

  • WP_Secrets_Keyring: يحفظ المفتاح الذي يشفّر المفتاح الجذري في KMS أو HSM.
  • WP_Secrets_Store: يحفظ السجلات المشفّرة خارج جداول الخيارات.
  • WP_Secrets_Provider: يسلّم الواجهة كلها لمدير أسرار خارجي.
<?php
// wp-content/secrets.php

class My_Host_Keyring extends WP_Secrets_Keyring {
    public function wrap( #[\SensitiveParameter] string $key_material ) { /* … */ }
    public function unwrap( string $wrapped ) { /* … */ }
    public function get_key_source(): string { return 'My Host KMS'; }
}

$wp_secrets_keyring = new My_Host_Keyring();

لو فشل تحميل الملف أو عيّن نوعًا خاطئًا، تعيد كل الاستدعاءات خطأ ولا ترجع الواجهة للإعداد الافتراضي، لأن خلفية معطوبة يجب ألا تبدو كخزنة فارغة. وانتبه: PHP لا يورّث السمات (attributes)، فأضف #[\SensitiveParameter] بنفسك لكل معامل يستقبل مفتاحًا أو قيمة. ولمزوّد للقراءة فقط، تعيد wp_secrets_provider_is_writable() القيمة false.

لو جرّبت النسخ التجريبية: تغييرات كاسرة

من اختبر 7.2 Beta 1 أو كتب secrets.php مبكرًا يحتاج انتباهًا قبل الترقية:

  • WP_SECRETS_KEY أصبح إلزاميًا. في البيتا كان الموقع بلا مفتاح يشتق واحدًا من LOGGED_IN_KEY وLOGGED_IN_SALT، فتدوير الـ salts كان يكسر كل الأسرار. هذا البديل أُلغي.
  • الواجهات صارت أصنافًا مجرّدة (abstract classes). غيّر implements إلى extends في ملفك وانشره مع 7.2 في نفس الوقت. ملف ما زال يقول implements يسبب خطأ قاتلًا في كل طلب.
  • دوال wp_secrets_site_health_*() والمفتاح secrets-api من البيتا حُذفت؛ الفحوص الآن دوال داخل WP_Site_Health، وقسم المعلومات تحت المفتاح wp-secrets-api.

لو خزّنت أسرارًا في البيتا بلا مفتاح، فهي ما زالت موجودة لكن «صحة الموقع» ستقول إنها غير قابلة للفك. لا تلمس الـ salts قبل إنهاء هذه الخطوات، لأن المفتاح القديم مشتق منها:

  1. ولّد مفتاحًا جديدًا وأضفه مع المفتاح القديم إلى wp-config.php تحت الـ salts:
define( 'WP_SECRETS_KEY', '...your new key...' );
define( 'WP_SECRETS_KEY_PREVIOUS', LOGGED_IN_KEY . LOGGED_IN_SALT );
  1. أعد تشفير المفتاح الجذري بالمفتاح الجديد عبر WP-CLI:
wp eval '$r = ( new WP_Secrets_Key_Manager() )->rotate_site_key( new WP_Secrets_Config_Key_Provider( true ), new WP_Secrets_Config_Key_Provider() ); echo is_wp_error( $r ) ? $r->get_error_message() : "Done.";'
  1. احذف سطر WP_SECRETS_KEY_PREVIOUS.

لا يُعاد كتابة أي سر مخزَّن، وفي Multisite يكفي تشغيل واحد للشبكة كلها. ونفس الخطوات تصلح لاستبدال أي مفتاح لاحقًا: ضع القيمة القديمة في WP_SECRETS_KEY_PREVIOUS.

ماذا ستعرض «صحة الموقع»؟

ثلاثة فحوص جديدة تحت قسم الأمان:

الفحصمتى يظهر
مصدر مفتاح Secrets API«موصى به» عند غياب المفتاح، و«حرج» إن لم يفك المفتاح الجذري
أسرار غير قابلة للفك«حرج» عند وجود أي سر لا يمكن فكه
بيانات اعتماد بانتظار التدوير«موصى به» عند وجود أسرار معلّمة، مثل المستوردة

وفي تبويب المعلومات قسم كامل يعرض المزوّد وحلقة المفاتيح ومصدر المفتاح وعدد الأسرار.

بصراحة: هذا المقال قراءة لملاحظة المطورين الرسمية، ولم نختبر الواجهة على موقع إنتاجي بعد. الجزء الذي يستحق تجربة قبل تحديث مواقع العملاء هو نقل موقع بين استضافتين والتأكد أن الأسرار ما زالت مقروءة بعده.

الأسئلة الشائعة

س: هل أحتاج تعديل موقعي فورًا بعد الترقية إلى ووردبريس 7.2؟
ج: لو لا توجد إضافة على موقعك تستخدم الواجهة بعد، فلا شيء سينكسر. لكن أضف WP_SECRETS_KEY من «صحة الموقع» مبكرًا، حتى تجد الإضافات الواجهة جاهزة حين تبدأ في استخدامها.

س: ماذا يحدث لو غيّرت WP_SECRETS_KEY بالخطأ؟
ج: تصبح كل الأسرار غير مقروءة حتى تعيد القيمة القديمة. لاستبدال المفتاح عن قصد، ضع القديم في WP_SECRETS_KEY_PREVIOUS وشغّل أمر rotate_site_key ثم احذفه.

س: هل تحمي الواجهة مفاتيحي من إضافة خبيثة؟
ج: لا. أي كود يعمل على الموقع يستطيع قراءة أي سر. الحماية موجّهة ضد تسريب قاعدة البيانات والنسخ الاحتياطية والتصديرات.

س: هل أستطيع ترك مفتاح API القديم في wp_options بعد نقله؟
ج: الدالة wp_import_option_as_secret() تتركه لتحذفه أنت بنفسك. احذفه بعد التأكد أن الإضافة تقرأ من السر، ثم ولّد مفتاحًا جديدًا من الخدمة لأن القديم موجود في نسخك الاحتياطية.

الخلاصة

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

مرجع المقال (بالإنجليزية): The Secrets API in WordPress 7.2
المقال ده مش ترجمة حرفية: متكيّف للقارئ العربي ومضاف عليه سياق ووردبريس وElementor وJetEngine.

اترك تعليقاً