Aller au contenu
Métriques

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.crt

Le 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
# 200

Lorsque 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étriqueTypeDescription
githubsts_http_requests_totalCounterRequêtes HTTP par méthode, chemin, statut
githubsts_http_request_duration_secondsHistogramLatence des requêtes HTTP
githubsts_http_requests_in_flightGaugeRequêtes concurrentes

Métriques d’échange de jeton

MétriqueTypeDescription
githubsts_token_exchanges_totalCounterTentatives d’échange par github_app, github_app_instance, scope, identity, issuer, result
githubsts_token_exchange_duration_secondsHistogramLatence d’échange par github_app, github_app_instance, scope, identity, issuer
githubsts_oidc_validation_errors_totalCounterÉchecs OIDC par issuer, reason

Prévention de rejeu JTI

MétriqueTypeDescription
githubsts_jti_replay_attempts_totalCounterTentatives de rejeu JTI détectées
githubsts_jti_cache_errors_totalCounterErreurs d’opération du cache JTI par error_type

Métriques de politique

MétriqueTypeDescription
githubsts_policy_loads_totalCounterChargements de politique par github_app, backend, result
githubsts_policy_cache_hits_totalCounterSuccès du cache par github_app
githubsts_policy_cache_misses_totalCounterÉchecs du cache par github_app

Métriques de l’API GitHub

MétriqueTypeDescription
githubsts_github_api_calls_totalCounterAppels à l’API GitHub par github_app, github_app_instance, api_endpoint, result
githubsts_github_tokens_issued_totalCounterJetons émis par github_app, github_app_instance, scope, permissions, narrowed
githubsts_github_token_permission_divergence_totalCounterPermissions 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_remainingGaugeLimite de débit restante par github_app, github_app_instance, resource
githubsts_github_rate_limit_limitGaugeRequêtes maximales autorisées dans la fenêtre courante, par github_app, github_app_instance, resource
githubsts_github_rate_limit_usedGaugeRequêtes utilisées dans la fenêtre courante, par github_app, github_app_instance, resource
githubsts_github_rate_limit_reset_timestampGaugeHorodatage Unix du redémarrage de la fenêtre, par github_app, github_app_instance, resource
githubsts_github_rate_limit_remaining_percentGaugePourcentage de la limite de débit restant, par github_app, github_app_instance, resource
githubsts_github_rate_limit_observed_timestampGaugeHorodatage 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_totalCounterRésultats des sondes de limite de débit, par github_app, github_app_instance, result
githubsts_github_rate_limit_exceeded_totalCounterÉ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_totalCounterÉvénements de limite de débit secondaire (abus), par github_app, github_app_instance, caller
githubsts_github_secondary_rate_limit_retry_after_secondsGaugeValeur 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_enabled est activé, chaque réplique sonde toutes les installations de chaque membre du pool une fois par rate_limit_poll_interval. Il envoie une requête conditionnelle GET /emojis avec un jeton qu’il émet pour lui-même, restreint à metadata:read. La première requête par jeton renvoie 200 et consomme une requête ; les sondes suivantes renvoient 304 Not Modified et ne consomment rien, jusqu’à ce que la réponse mise en cache soit plus ancienne que rate_limit_poll_force_counted_interval, auquel cas le sondeur abandonne l’en-tête conditionnel et paie pour un nouveau 200 décompté plutôt que de continuer à faire confiance à un 304 non 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 403 et 429 sont tout de même comptées dans githubsts_github_rate_limit_exceeded_total et githubsts_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étriqueTypeDescription
githubsts_github_reachableGaugeAccessibilité de l’API GitHub (1/0) par github_app, github_app_instance
githubsts_github_reachability_check_duration_secondsHistogramLatence des sondes d’accessibilité, par github_app, github_app_instance
githubsts_github_reachability_failures_totalCounterÉ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étriqueTypeDescription
githubsts_app_pool_instancesGaugeTaille du pool configurée, par github_app
githubsts_app_pool_selection_totalCounterRésultats de sélection, par github_app, github_app_instance, outcome
githubsts_app_pool_exhausted_totalCounterRequê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étriqueTypeDescription
githubsts_audit_events_logged_totalCounterÉvénements d’audit journalisés par result
githubsts_audit_log_errors_totalCounterErreurs d’écriture du journal d’audit par backend
githubsts_audit_events_dropped_totalCounterÉvénements d’audit abandonnés (tampon plein)

Autres métriques

MétriqueTypeDescription
githubsts_rate_limit_rejections_totalCounterRequêtes rejetées par la limitation de débit par IP
githubsts_readyGaugePré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]) > 0

Restriction 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 des 403 inattendus 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