Authentification

KernelHost API

En-têtes de requête requis

Chaque requête (sauf /v1/health) doit porter ces quatre en-têtes. Si l'un est manquant ou invalide, le serveur rejette avec HTTP 401.

HeaderFormatSignification
KH-Keykh_live_[A-Z0-9]{32}Identifiant de clé publique. N'est pas un secret, peut apparaître dans les journaux.
KH-TimestampSecondes Unix (10 chiffres)Horodatage actuel. Fenêtre de tolérance de +-300s.
KH-Nonce22-44 caractères base64urlValeur à usage unique par requête. Mise en cache pendant 600s, puis réutilisable.
KH-Signature64 hexHMAC-SHA256(secret, signing_string), encodé en hexadécimal.

Construire la chaîne à signer

La chaîne à signer est composée de cinq éléments joints par un saut de ligne (\n).

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

signature = HEX( HMAC_SHA256(secret, signing_string) )

PATH désigne le chemin à partir de /v1, par exemple /v1/orders et non /cp/kernelhost_api/v1/orders. Une éventuelle chaîne de requête fait partie de PATH (par exemple /v1/services?limit=10&offset=0). BODY est le corps brut de la requête ; pour les requêtes sans corps, BODY est une chaîne vide dont le SHA256 est la constante bien connue e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.

Implémentations de référence

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,
});

Protection anti-rejeu

Une requête correctement signée NE peut PAS être rejouée. Le serveur stocke chaque nonce pendant 600s en base de données ; une deuxième requête portant le même nonce est rejetée avec replay_detected. De même, une requête dont l'horodatage s'écarte de plus de 300s de l'heure serveur est rejetée.

Modèle de permissions

Chaque clé possède une liste de permissions explicite. Les routes vérifient la permission requise ; si elle manque, HTTP 403 forbidden_scope. Les permissions d'écriture et sensibles doivent être activées explicitement à la création de la clé.

  • read:products, read:orders, read:services, read:billing, read:webhooks
  • read:credentials (Lire les identifiants de service (nom d'hôte, adresses IP, nom d'utilisateur, mot de passe). Chaque appel génère une entrée d'audit supplémentaire credentials.read.)
  • write:orders (Passer et payer des commandes.)
  • write:services (Exécuter des actions de service (start, stop, reboot, ainsi que la résiliation en fin de période de facturation et son annulation).)
  • write:webhooks (Définir l'URL du webhook.)