Aller au contenu

Webhooks ​

Un webhook signale à votre système qu'un dépôt a changé, sans qu'il ait à interroger le serveur. Le worker d'Arkvory envoie un POST HTTP à votre URL pour chaque événement du flux de changements du dépôt. Utilisez-le pour lancer un déploiement à la publication d'un build ou pour rafraîchir un cache quand un chemin reçoit une nouvelle version.

L'administrateur définit les abonnements dans un fichier. Il n'y a pas encore d'API ni de page de console pour cela. Vous pouvez aussi lire le flux vous-même avec GET /api/v1/repositories/<repository>/changes.

Fonctionnement de la livraison ​

  • Un abonnement suit un dépôt et envoie vers une URL.
  • Les événements arrivent dans l'ordre, un par un. Le suivant attend que le destinataire ait répondu avec un statut 2xx.
  • La livraison a lieu au moins une fois. Après un plantage ou une réponse perdue, le même événement peut arriver de nouveau. Chaque événement a un id stable : dédupliquez avec lui.
  • Un nouvel abonnement ne reçoit que les nouveaux événements. Ceux d'avant sa création ne sont pas envoyés.
  • Un destinataire hors service ne retarde que son propre abonnement. L'événement attend. Arkvory réessaie après 12 secondes, double la pause jusqu'à une heure et envoie les événements dans l'ordre quand le destinataire répond de nouveau. Les envois et téléchargements n'attendent jamais un webhook.

Configurer un abonnement ​

Vous avez besoin d'un accès aux fichiers du serveur et du droit de redémarrer le worker. Voir Configuration.

  1. Créez un fichier avec le secret de signature : au moins 16 caractères imprimables aléatoires, par exemple /root/ci.secret. Le destinataire a besoin du même secret.
  2. Lancez configure avec l'abonnement. La commande vérifie votre saisie, copie le secret dans config/webhooks, écrit le fichier d'abonnements et le réglage, redémarre les services et rétablit la configuration précédente s'ils ne deviennent pas prêts. Elle ne contacte pas le destinataire.
bash
sudo arkvory configure --root /opt/proanima-arkvory \
  --webhook ci --webhook-repository releases \
  --webhook-url https://ci.example.com/hooks/arkvory \
  --webhook-secret-file /root/ci.secret \
  --webhook-actions artifact.publish
  1. Cherchez webhook.started dans le journal du worker, publiez un fichier et regardez votre destinataire.

La commande écrit config/webhooks/webhooks.json, que vous pouvez aussi écrire à la main et indiquer dans ARKVORY_WEBHOOKS_FILE. Pour modifier un abonnement, relancez la commande avec le même nom. --webhook-detach ci le supprime : ses fichiers sont effacés et sa position oubliée. Un appel modifie un abonnement et ne se combine pas avec d'autres options de configure.

Le fichier comporte ces champs :

json
{
  "webhooks": [
    {
      "id": "ci",
      "repository": "releases",
      "url": "https://ci.example.com/hooks/arkvory",
      "secretFile": "/opt/proanima-arkvory/config/webhooks/ci.secret",
      "actions": ["artifact.publish"]
    }
  ]
}
ChampSignification
idNom de l'abonnement : 1 à 64 caractères a-z, 0-9, _ et -. Sa progression est conservée sous ce nom.
repositoryLe dépôt dont le flux est envoyé.
urlLe destinataire. HTTPS, sans nom d'utilisateur, query ni fragment, 2048 caractères au plus. HTTP en clair n'est permis que pour localhost, 127.0.0.1 et [::1].
secretFileChemin absolu du fichier contenant le secret de signature.
nextSecretFileSecond secret facultatif pour une rotation.
actionsListe facultative des actions du flux à envoyer, par exemple artifact.publish, artifact.delete, asset.replace, stage.added et package.register. Sans elle, toutes les actions sont envoyées.

Un fichier incorrect arrête le worker au démarrage avec worker.unavailable. Jusqu'à 16 abonnements sont autorisés. Sur une installation Compose, la commande monte aussi config/webhooks dans le conteneur du worker.

La requête ​

En-têteValeur
Content-Typeapplication/json
X-Arkvory-DeliveryL'id de l'événement. Il est identique à chaque répétition d'un événement.
X-Arkvory-EventL'action du flux, par exemple artifact.publish.
X-Arkvory-TimestampHeure Unix en secondes à laquelle la requête a été signée.
X-Arkvory-Signaturesha256=<hex>. Pendant une rotation, il y a deux valeurs séparées par une virgule.

Le corps est du JSON :

json
{
  "id": "releases:128",
  "repository": "releases",
  "sequence": "128",
  "action": "artifact.publish",
  "artifactId": "00000000-0000-4000-8000-000000000001",
  "detail": null
}

sequence est la position dans le flux, une chaîne décimale. detail est le chemin du fichier ou l'étape pour les actions qui en ont une. Le corps ne contient ni auteur, ni contenu de fichier, ni métadonnées : utilisez artifactId pour lire l'état actuel via l'API.

Répondez avec un statut 2xx en moins de 10 secondes. Un statut 3xx (les redirections ne sont pas suivies), 4xx, 5xx, une erreur de connexion ou un délai dépassé compte comme un échec. Arkvory lit au plus 4 Kio de la réponse et l'ignore.

Vérifier la signature ​

La signature est un HMAC-SHA256 avec votre secret sur le texte <timestamp>.<body>, écrit sha256= suivi du condensat hexadécimal. Vérifiez-la avant de faire confiance à une requête :

  1. Lisez le corps brut, avant tout décodage JSON.
  2. Refusez la requête si l'horodatage diffère de votre horloge de plus de 5 minutes.
  3. Calculez la signature et comparez-la avec une fonction à temps constant. Si l'en-tête a deux valeurs, acceptez l'une ou l'autre.

Node.js:

js
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(secret, headers, rawBody) {
  const timestamp = headers['x-arkvory-timestamp'];
  if (!/^\d{1,12}$/.test(timestamp ?? '')) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = Buffer.from(
    'sha256=' + createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex'),
  );
  return String(headers['x-arkvory-signature'] ?? '')
    .split(',')
    .some((given) => {
      const actual = Buffer.from(given.trim());
      return actual.length === expected.length && timingSafeEqual(actual, expected);
    });
}

Python:

python
import hashlib, hmac, time

def verify(secret: bytes, headers, raw_body: bytes) -> bool:
    timestamp = headers.get("X-Arkvory-Timestamp", "")
    if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
        return False
    digest = hmac.new(secret, timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    expected = "sha256=" + digest
    given = headers.get("X-Arkvory-Signature", "").split(",")
    return any(hmac.compare_digest(part.strip(), expected) for part in given)

Gérer les livraisons répétées ​

  • Conservez l'id de chaque événement traité et ignorez une répétition.
  • Répondez vite : mettez le travail en file et renvoyez 204. Un destinataire lent retarde tous les événements suivants de son abonnement.
  • Une répétition ne signifie pas que l'opération a eu lieu deux fois. Traitez l'événement comme un indice et lisez l'état actuel depuis l'API.

Renouveler le secret ​

  1. Relancez la même commande avec --webhook-secret-file pour le secret actuel et --webhook-next-secret-file pour le nouveau. Chaque requête porte alors deux signatures.
  2. Passez le destinataire au nouveau secret. Un destinataire qui accepte l'une ou l'autre signature continue de fonctionner entre-temps.
  3. Relancez la commande avec le nouveau fichier de secret dans --webhook-secret-file et sans --webhook-next-secret-file.

Destinataires privés et certificats ​

  • Arkvory refuse les destinataires sur des adresses loopback, privées, link-local et de métadonnées cloud, ainsi que les noms qui y mènent. Le serveur ne peut ainsi pas servir à atteindre des services internes.
  • Pour envoyer vers un destinataire de votre réseau, ajoutez ce réseau avec --webhook-allow-private, par exemple --webhook-allow-private 10.20.0.0/16 (ARKVORY_WEBHOOKS_ALLOW_PRIVATE). Il reste jusqu'à la suppression du dernier abonnement.
  • Si le certificat du destinataire vient de votre propre autorité, ajoutez un fichier PEM de cette autorité avec --webhook-ca-file (ARKVORY_WEBHOOKS_CA_FILE). Le certificat est toujours vérifié.

Surveiller les webhooks ​

Le worker écrit webhook.step_failed avec un errorCode quand une livraison échoue, et webhook.recovered quand elle refonctionne. Les métriques arkvory_webhook_failing et arkvory_webhook_last_success_timestamp_seconds et l'alerte ArkvoryWebhookFailing sont décrites dans Surveillance. webhook.stopped marque la fin de la boucle d'un abonnement : à l'arrêt ou quand le worker perd la propriété du stockage. webhook.prune_failed signifie que le worker n'a pas pu oublier les abonnements supprimés au démarrage ; il réessaie au démarrage suivant. Les codes unavailable et internal n'apparaissent que dans le journal, pas dans arkvory_webhook_failing.

Un abonnement qui n'avance plus ​

Arkvory ne saute jamais un événement : c'est ce qui rend la livraison fiable. Un récepteur qui refuse sans cesse un même événement, par exemple avec 400 parce qu'il ne sait pas le lire, arrête donc tout son abonnement. Aucun événement suivant de cet abonnement n'est envoyé ; les autres abonnements continuent. Le worker écrit webhook.step_failed avec http_4xx et réessaie avec des pauses allant jusqu'à une heure, arkvory_webhook_failing vaut 1 et l'alerte ArkvoryWebhookFailing se déclenche au bout de 15 minutes.

  1. Corrigez le récepteur pour qu'il réponde 2xx à l'événement. Arkvory l'envoie au prochain essai, au plus une heure plus tard environ, puis tous les événements en attente, dans l'ordre. Relancer configure avec le même nom et le même dépôt conserve la position.
  2. Si l'événement ne peut jamais être accepté, supprimez l'abonnement avec --webhook-detach et ajoutez-le de nouveau. Il repart de la fin du flux : les événements en attente ne sont pas envoyés. Lisez-les dans le flux des changements après le dernier sequence traité par votre récepteur.

Dépannage ​

errorCodeCause et que faire
blockedL'adresse du destinataire n'est pas autorisée (loopback, privée, link-local ou métadonnées). Utilisez une adresse publique ou ajoutez le réseau à ARKVORY_WEBHOOKS_ALLOW_PRIVATE.
timeoutPas de réponse en 10 secondes. Répondez plus vite et mettez le travail en file.
networkConnexion refusée, nom introuvable ou connexion réinitialisée. Vérifiez l'URL, le DNS et le pare-feu depuis le serveur.
tlsLe certificat n'est pas approuvé, a expiré ou porte un mauvais nom. Corrigez-le ou définissez ARKVORY_WEBHOOKS_CA_FILE.
redirectLe destinataire a répondu 3xx. Les redirections ne sont pas suivies : utilisez l'URL finale.
http_4xxLe destinataire a refusé la requête. Vérifiez sa vérification de signature, son chemin et sa clé.
http_5xxLe destinataire est en panne. Arkvory continue de réessayer, avec jusqu'à une heure entre les essais.
secretLe fichier de secret est absent, illisible ou plus court que 16 octets.
unavailableLe worker n'a pas pu lire le flux ou l'état de l'abonnement dans la base de données. Rien n'a été envoyé ; il réessaie après 5 secondes. Vérifiez la base de données.
internalUne erreur inattendue dans le worker. Rien n'a été envoyé ; il réessaie après 5 secondes. Signalez-la avec le journal du worker.

Rien n'arrive ? Vérifiez que webhook.started figure dans le journal du worker, que ARKVORY_WEBHOOKS_FILE est défini, le nom du dépôt et le filtre actions. Un nouvel abonnement n'envoie que les événements postérieurs à sa première étape.

Arkvory · ProAnimaStudio