المصادقة

KernelHost API

ترويسات الطلب المطلوبة

كل طلب (باستثناء /v1/health) يجب أن يحمل هذه الترويسات الأربع. في حال غياب أو عدم صلاحية أي منها، يرفض الخادم الطلب بـ HTTP 401.

HeaderFormatالمعنى
KH-Keykh_live_[A-Z0-9]{32}معرّف المفتاح العام. ليس سراً، ويجوز ظهوره في السجلات.
KH-Timestampثوانٍ Unix (10 خانات)الطابع الزمني الحالي. نافذة التسامح ±300 ثانية.
KH-Nonce22-44 حرف base64urlقيمة أحادية الاستخدام لكل طلب. تُخزَّن لمدة 600 ثانية ثم يجوز إعادة استخدامها.
KH-Signature64 hexHMAC-SHA256(السر، سلسلة التوقيع)، مُرمَّزة كـ hex.

بناء سلسلة التوقيع

تتكوّن السلسلة المراد توقيعها من خمسة مكونات مفصولة بسطر جديد (\n).

signing_string = METHOD + "\n"
               + PATH    + "\n"
               + TIMESTAMP + "\n"
               + NONCE   + "\n"
               + SHA256_HEX(BODY)

signature = HEX( HMAC_SHA256(secret, signing_string) )

يمثّل PATH المسار بدءًا من /v1، مثل /v1/orders وليس /cp/kernelhost_api/v1/orders. وإذا تضمّن الطلب سلسلة استعلام، فهي جزء من PATH (مثل /v1/services?limit=10&offset=0). أما BODY فهو جسم الطلب الخام؛ وفي الطلبات التي لا جسم لها يكون BODY سلسلة فارغة، وتجزئة SHA256 الخاصة بها هي القيمة الثابتة المعروفة e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.

تطبيقات مرجعية

PHP:

$method = 'POST';
$path   = '/v1/orders';
$body   = json_encode(['product_id' => 42, 'billing_cycle' => 'monthly', 'hostname' => 'web01.example.com']);
$idem   = 'order-web01-' . date('Ymd');
$ts     = (string) time();
$nonce  = bin2hex(random_bytes(16));

$signing = "{$method}\n{$path}\n{$ts}\n{$nonce}\n" . hash('sha256', $body);
$sig = hash_hmac('sha256', $signing, getenv('KH_SECRET'));

$ch = curl_init('https://www.kernelhost.com/cp/kernelhost_api/v1/orders');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'POST',
    CURLOPT_POSTFIELDS     => $body,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'KH-Key: ' . getenv('KH_KEY'),
        "KH-Timestamp: {$ts}",
        "KH-Nonce: {$nonce}",
        "KH-Signature: {$sig}",
        "Idempotency-Key: {$idem}",
    ],
]);
$res = curl_exec($ch);

Node.js:

import crypto from 'node:crypto';

const method = 'POST';
const path   = '/v1/orders';
const body   = JSON.stringify({ product_id: 42, billing_cycle: 'monthly', hostname: 'web01.example.com' });
const idem   = 'order-web01-' + new Date().toISOString().slice(0, 10).replaceAll('-', '');
const ts     = Math.floor(Date.now() / 1000).toString();
const nonce  = crypto.randomBytes(16).toString('hex');

const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const signing  = `${method}\n${path}\n${ts}\n${nonce}\n${bodyHash}`;
const sig      = crypto.createHmac('sha256', process.env.KH_SECRET).update(signing).digest('hex');

const res = await fetch('https://www.kernelhost.com/cp/kernelhost_api/v1/orders', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'KH-Key': process.env.KH_KEY,
        'KH-Timestamp': ts,
        'KH-Nonce': nonce,
        'KH-Signature': sig,
        'Idempotency-Key': idem,
    },
    body,
});

الحماية من إعادة الإرسال

لا يمكن إعادة إرسال طلب موقّع بنجاح. يحفظ الخادم كل Nonce لمدة 600 ثانية في قاعدة البيانات؛ ويُرفَض أي طلب ثانٍ بنفس الـ Nonce برسالة replay_detected. كذلك يُرفَض أي طلب يبتعد طابعه الزمني أكثر من 300 ثانية عن وقت الخادم.

نموذج النطاقات

لكل مفتاح API قائمة نطاقات صريحة. تتحقق المسارات من النطاق المطلوب؛ وإن كان مفقوداً، يُرَد HTTP 403 forbidden_scope. يجب تفعيل النطاقات الكتابية والحساسة صراحةً عند إنشاء مفتاح API.

  • read:products, read:orders, read:services, read:billing, read:webhooks
  • read:credentials (قراءة بيانات اعتماد الخدمات (اسم المضيف، وعناوين IP، واسم المستخدم، وكلمة المرور). ويُنشئ كل استدعاء إدخال تدقيق إضافيًا باسم credentials.read.)
  • write:orders (إنشاء الطلبات ودفعها.)
  • write:services (تنفيذ إجراءات الخدمة (start وstop وreboot، إضافة إلى الإلغاء في نهاية فترة الفوترة والتراجع عنه).)
  • write:webhooks (تعيين عنوان Webhook.)