Métriques
Toutes les métriques sont exposées à GET /metrics au format texte Prometheus avec le préfixe githubsts_.
Mise à niveau depuis une version sans pools d’Apps ? Chaque métrique de GitHub App, de limite de débit et d’accessibilité ci-dessous porte désormais une étiquette instance supplémentaire ; voir Migration : étiquette instance des métriques de pool pour la liste des métriques concernées, les requêtes PromQL avant/après et une liste de contrôle étape par étape.
Authentifier les collectes
L’authentification par secret partagé Bearer est la seule méthode propre à GET /metrics. Définissez GITHUBSTS_METRICS_AUTH_TOKEN pour l’activer :
export GITHUBSTS_METRICS_AUTH_TOKEN="remplacer-par-un-secret-aléatoire"Cet exemple suppose que github-sts sert HTTPS sur le port 8443 avec un certificat approuvé par l’hôte Prometheus. Consultez TLS natif et mTLS pour configurer le serveur.
Configurez la tâche de collecte Prometheus avec le même jeton. Préférez un fichier monté depuis votre gestionnaire de secrets plutôt que de stocker le jeton directement dans prometheus.yml :
scrape_configs:
- job_name: github-sts
scheme: https
static_configs:
- targets: ["github-sts:8443"]
authorization:
type: Bearer
credentials_file: /etc/prometheus/secrets/github-sts-metrics-token
tls_config:
ca_file: /etc/prometheus/certs/github-sts-ca.crtLe fichier d’identifiants doit contenir uniquement la valeur du jeton. Lorsque le paramètre n’est pas vide, vérifiez qu’une requête sans jeton renvoie 401 et qu’une requête avec le jeton renvoie 200 :
curl --cacert /chemin/vers/github-sts-ca.crt -o /dev/null -s -w '%{http_code}\n' \
https://github-sts:8443/metrics
# 401
curl --cacert /chemin/vers/github-sts-ca.crt -o /dev/null -s -w '%{http_code}\n' \
-H "Authorization: Bearer $GITHUBSTS_METRICS_AUTH_TOKEN" \
https://github-sts:8443/metrics
# 200Lorsque le paramètre est vide, le point de terminaison reste sans authentification. HTTPS protège le jeton Bearer en transit, mais n’authentifie pas le collecteur. Le mTLS natif peut authentifier les clients pour l’ensemble du serveur, y compris /health et /ready. Un proxy inverse ou un maillage de services peut fournir d’autres méthodes d’authentification en dehors de github-sts.
Métriques HTTP
| Métrique | Type | Description |
|---|---|---|
githubsts_http_requests_total | Counter | Requêtes HTTP par méthode, chemin, statut |
githubsts_http_request_duration_seconds | Histogram | Latence des requêtes HTTP |
githubsts_http_requests_in_flight | Gauge | Requêtes concurrentes |
Métriques d’échange de jeton
| Métrique | Type | Description |
|---|---|---|
githubsts_token_exchanges_total | Counter | Tentatives d’échange par github_app, github_app_instance, scope, identity, issuer, result |
githubsts_token_exchange_duration_seconds | Histogram | Latence d’échange par github_app, github_app_instance, scope, identity, issuer |
githubsts_oidc_validation_errors_total | Counter | Échecs OIDC par issuer, reason |
Prévention de rejeu JTI
| Métrique | Type | Description |
|---|---|---|
githubsts_jti_replay_attempts_total | Counter | Tentatives de rejeu JTI détectées |
githubsts_jti_cache_errors_total | Counter | Erreurs d’opération du cache JTI par error_type |
Métriques de politique
| Métrique | Type | Description |
|---|---|---|
githubsts_policy_loads_total | Counter | Chargements de politique par github_app, backend, result |
githubsts_policy_cache_hits_total | Counter | Succès du cache par github_app |
githubsts_policy_cache_misses_total | Counter | Échecs du cache par github_app |
Métriques de l’API GitHub
| Métrique | Type | Description |
|---|---|---|
githubsts_github_api_calls_total | Counter | Appels à l’API GitHub par github_app, github_app_instance, api_endpoint, result |
githubsts_github_tokens_issued_total | Counter | Jetons émis par github_app, github_app_instance, scope, permissions, narrowed |
githubsts_github_token_permission_divergence_total | Counter | Permissions où l’attribution réelle de GitHub diffère de la requête, par github_app, github_app_instance, permission, direction |
githubsts_github_rate_limit_remaining | Gauge | Limite de débit restante par github_app, github_app_instance, resource |
githubsts_github_rate_limit_limit | Gauge | Requêtes maximales autorisées dans la fenêtre courante, par github_app, github_app_instance, resource |
githubsts_github_rate_limit_used | Gauge | Requêtes utilisées dans la fenêtre courante, par github_app, github_app_instance, resource |
githubsts_github_rate_limit_reset_timestamp | Gauge | Horodatage Unix du redémarrage de la fenêtre, par github_app, github_app_instance, resource |
githubsts_github_rate_limit_remaining_percent | Gauge | Pourcentage de la limite de débit restant, par github_app, github_app_instance, resource |
githubsts_github_rate_limit_observed_timestamp | Gauge | Horodatage Unix de l’observation rapportée par les jauges de limite de débit, par github_app, github_app_instance, resource |
githubsts_github_rate_limit_probe_total | Counter | Résultats des sondes de limite de débit, par github_app, github_app_instance, result |
githubsts_github_rate_limit_exceeded_total | Counter | Événements de dépassement de la limite de débit primaire, par github_app, github_app_instance, resource, caller |
githubsts_github_secondary_rate_limit_total | Counter | Événements de limite de débit secondaire (abus), par github_app, github_app_instance, caller |
githubsts_github_secondary_rate_limit_retry_after_seconds | Gauge | Valeur retry-after actuelle en secondes, par github_app, github_app_instance |
Chaque instance du pool (étiquette instance) a sa propre série de limite de débit : une App avec 3 instances rapporte 3 séries githubsts_github_rate_limit_remaining indépendantes, pas une seule agrégée. Une App non poolée (une seule instance) porte quand même l’étiquette, avec instance égale à son unique instance normalisée.
Origine des valeurs de limite de débit
Les jauges décrivent le compteur de l’installation de chaque membre du pool. Deux sources les alimentent :
- Le sondeur de limite de débit. Quand
rate_limit_poll_enabledest activé, chaque réplique sonde toutes les installations de chaque membre du pool une fois parrate_limit_poll_interval. Il envoie une requête conditionnelleGET /emojisavec un jeton qu’il émet pour lui-même, restreint àmetadata:read. La première requête par jeton renvoie200et consomme une requête ; les sondes suivantes renvoient304 Not Modifiedet ne consomment rien, jusqu’à ce que la réponse mise en cache soit plus ancienne querate_limit_poll_force_counted_interval, auquel cas le sondeur abandonne l’en-tête conditionnel et paie pour un nouveau200décompté plutôt que de continuer à faire confiance à un304non fiable (voir ci-dessous). Le sondeur sonde aussi un membre juste après la réinitialisation d’un compteur entamé, et dans les secondes qui suivent une réponse de limite de débit sur ce membre. - Les appels de github-sts faits avec ses propres jetons d’installation, comme la résolution du dépôt cible pendant un échange.
Seule une réponse qui a consommé une requête est enregistrée. Pour les jetons d’installation, GitHub répond aux requêtes qui ne consomment rien (GET /rate_limit et 304 Not Modified) avec un compteur distinct et inutilisé : used vaut 0 et la réinitialisation est environ une heure plus tard. github-sts ignore un tel relevé dès qu’il détient un relevé décompté pour une fenêtre pas encore réinitialisée. Entre deux réponses décomptées, un relevé peut donc surestimer ce qui reste, et githubsts_github_rate_limit_observed_timestamp indique quand il a été pris ; rate_limit_poll_force_counted_interval borne la durée possible de cet écart, au prix d’une requête supplémentaire consommée par instance chaque fois qu’il force une relecture.
Ce que ces jauges ne montrent pas :
- Les compteurs dépensés uniquement par des jetons clients. github-sts ne conserve ni ne sonde jamais un jeton émis pour un client. Si GitHub place un jeton client très sollicité sur son propre compteur, ce compteur reste invisible ici.
- Le compteur du JWT de l’App. Les en-têtes des appels faits avec le JWT de l’App (recherche d’installation, émission de jetons) ne sont pas enregistrés : ces appels n’utilisent pas de jeton d’installation et ne décrivent donc pas le compteur d’une installation. Leurs réponses
403et429sont tout de même comptées dansgithubsts_github_rate_limit_exceeded_totaletgithubsts_github_secondary_rate_limit_total.
Quand une App est installée sur plusieurs organisations, chaque jauge rapporte l’installation qui a le moins de requêtes restantes.
L’étiquette result de githubsts_github_rate_limit_probe_total vaut not_modified, ok, rate_limited, unauthorized, incomplete_headers ou error. ok est une sonde décomptée et le seul résultat dont les en-têtes décrivent le compteur de l’installation. not_modified ne consomme rien et, pour les jetons d’installation, ne porte aucun relevé exploitable. unauthorized, incomplete_headers et error signifient que le sondeur ne produit pas de relevés.
Accessibilité de GitHub
| Métrique | Type | Description |
|---|---|---|
githubsts_github_reachable | Gauge | Accessibilité de l’API GitHub (1/0) par github_app, github_app_instance |
githubsts_github_reachability_check_duration_seconds | Histogram | Latence des sondes d’accessibilité, par github_app, github_app_instance |
githubsts_github_reachability_failures_total | Counter | Échecs des sondes d’accessibilité, par github_app, github_app_instance, reason |
Métriques de pool d’Apps
Visibilité sur la sélection d’instance pour une App poolée (apps.<name>.instances ; voir Configuration). Une App non poolée est un pool d’une instance et émet quand même ces métriques, avec instances=1 et chaque résultat de sélection à selected. L’étiquette instance sur cette page identifie une GitHub App physique au sein d’un pool, pas un réplica du serveur github-sts.
| Métrique | Type | Description |
|---|---|---|
githubsts_app_pool_instances | Gauge | Taille du pool configurée, par github_app |
githubsts_app_pool_selection_total | Counter | Résultats de sélection, par github_app, github_app_instance, outcome |
githubsts_app_pool_exhausted_total | Counter | Requêtes où toutes les instances du pool ont échoué, par github_app |
L’étiquette outcome de githubsts_app_pool_selection_total vaut selected, failover, skipped_unreachable ou skipped_rate_limited. skipped_rate_limited n’apparaît qu’avec rotation.strategy: rate_limit_aware : elle compte un membre que la requête a placé en fin d’ordre pour quota bas et n’a jamais tenté. Voir Configuration.
githubsts_app_pool_exhausted_total est le signal à surveiller par alerte : il signifie que toutes les instances du pool de cette App ont échoué pour une requête. Qu’une seule instance voie sa limite de débit chuter ne signifie pas en soi que des requêtes échouent : le pool a déjà basculé autour d’elle.
Métriques d’audit
| Métrique | Type | Description |
|---|---|---|
githubsts_audit_events_logged_total | Counter | Événements d’audit journalisés par result |
githubsts_audit_log_errors_total | Counter | Erreurs d’écriture du journal d’audit par backend |
githubsts_audit_events_dropped_total | Counter | Événements d’audit abandonnés (tampon plein) |
Autres métriques
| Métrique | Type | Description |
|---|---|---|
githubsts_rate_limit_rejections_total | Counter | Requêtes rejetées par la limitation de débit par IP |
githubsts_ready | Gauge | Préparation de l’instance (1/0) |
Alertes recommandées
# Sustained OIDC validation errors — possible misconfiguration or attack
rate(githubsts_oidc_validation_errors_total[5m]) > 0
# JTI replay attempts — investigate immediately
rate(githubsts_jti_replay_attempts_total[5m]) > 0
# GitHub API unreachable — tokens will fail
githubsts_github_reachable == 0
# Audit events dropped — increase buffer or sink throughput
rate(githubsts_audit_events_dropped_total[5m]) > 0
# Exchange latency regression
histogram_quantile(0.99, rate(githubsts_token_exchange_duration_seconds_bucket[5m])) > 1
# Rate limit approaching exhaustion
githubsts_github_rate_limit_remaining_percent < 10
# Rate limit reading is stale: the poller has not refreshed it
time() - githubsts_github_rate_limit_observed_timestamp > 300
# Secondary rate limit active
githubsts_github_secondary_rate_limit_total > 0
# App pool exhausted: every instance failed a request
rate(githubsts_app_pool_exhausted_total[5m]) > 0Restriction des permissions
githubsts_github_tokens_issued_total étiquette permissions avec le plafond de la politique de confiance, et non avec l’ensemble de permissions réellement utilisé pour émettre le jeton. Les appelants peuvent restreindre une requête à n’importe quel sous-ensemble de ce plafond, à n’importe quel niveau inférieur : étiqueter selon l’ensemble effectif créerait une série par combinaison demandable, sous le contrôle de quiconque détient une identité valide. L’étiquette bornée narrowed indique si une requête a demandé moins que ce que la politique accorde :
# part des échanges ayant demandé moins que ce que leur politique accorde
sum(rate(githubsts_github_tokens_issued_total{narrowed="true"}[1h]))
/ sum(rate(githubsts_github_tokens_issued_total[1h]))Les ensembles de permissions exacts sont enregistrés pour chaque échange dans le journal d’audit (policy_permissions, requested_permissions, granted_permissions), qui n’a pas de budget de cardinalité à protéger.
githubsts_github_token_permission_divergence_total se déclenche lorsque l’attribution renvoyée par GitHub dans sa réponse de création de jeton diffère de la requête. Les deux directions n’ont pas la même gravité :
direction="above_requested"signifie que le jeton peut faire plus que ce que l’appelant a demandé. C’est une défaillance de la frontière de privilèges et cela mérite une alerte : la restriction n’est pas honorée en amont, et tout jeton restreint est plus large qu’il ne le prétend.direction="below_requested"signifie que le jeton peut faire moins. Le comportement est sûr, mais les appelants verront des403inattendus de l’API GitHub.
Les permissions que GitHub ajoute implicitement (actuellement metadata) sont exclues : un broker sain reste donc à zéro dans les deux directions.
# toute sur-attribution mérite une alerte
sum(increase(githubsts_github_token_permission_divergence_total{direction="above_requested"}[15m])) > 0