Télémétrie publique#
Pourquoi#
Publier un instantané de l’état du serveur (chrony_stats.txt, servi par
nginx) permet à quiconque d’auditer la qualité du service sans accès SSH,
transparence appréciable pour un service de référence temporelle. Mais
cette publication doit être conçue avec une contrainte de sécurité
absolue : ne jamais exposer de données de position. Un récepteur GNSS
capte aussi la latitude/longitude/altitude ; publier ces informations, même
approximatives, révélerait l’emplacement physique exact du serveur.
Comment#
Génération atomique du fichier#
cat > /opt/stats/stats.sh <<'EOF'
#!/bin/bash
set -e
OUT=/var/time/chrony_stats.txt
TMP=$(mktemp /var/time/.stats/chrony_stats.XXXXXX)
trap 'rm -f "$TMP"' EXIT
CHRONYC="/usr/bin/chronyc -h /run/chrony/chronyd.sock"
{
echo "--- NTP ---"
$CHRONYC -m tracking serverstats 'sources -v'
echo "--- PTP ---"
PHC_MEDIAN=$(/usr/local/sbin/phc-offset-median /dev/ptp0 5)
[ -n "$PHC_MEDIAN" ] && echo "offset ${PHC_MEDIAN}ns (médiane)"
echo "--- GNSS ---"
timeout 10 gpspipe -w -n 8 2>/dev/null | /opt/stats/gnss_sky.sh
} > "$TMP"
chmod 0644 "$TMP"
mv "$TMP" "$OUT"
EOF
chmod +x /opt/stats/stats.shL’écriture passe par un fichier temporaire renommé atomiquement
(mktemp + mv) : nginx ne voit jamais un fichier à moitié écrit,
puisque rename() est une opération atomique du système de fichiers.
Le filtre GNSS : jamais de position#
cat > /opt/stats/gnss_sky.sh <<'EOF'
#!/bin/bash
# N'isole QUE le rapport JSON "SKY" de gpsd (santé de réception :
# satellites, DOP). Ignore délibérément le rapport "TPV" (position/vitesse).
set -euo pipefail
SKY=$(jq -c 'select(.class == "SKY")' | head -n1)
[ -z "$SKY" ] && { echo "GNSS : rapport SKY indisponible"; exit 0; }
HAS_SAT_ARRAY=$(jq -r 'if .satellites then "1" else "0" end' <<<"$SKY")
if [ "$HAS_SAT_ARRAY" = "1" ]; then
N_USED=$(jq -r '[.satellites[] | select(.used == true)] | length' <<<"$SKY")
else
N_USED=$(jq -r '.uSat // empty' <<<"$SKY")
fi
HDOP=$(jq -r '.hdop // empty' <<<"$SKY")
echo "Satellites utilisés : ${N_USED:-indisponible}"
[ -n "$HDOP" ] && echo "HDOP : $HDOP"
EOF
chmod +x /opt/stats/gnss_sky.shLe script ne lit jamais les champs lat, lon, alt, track,
speed, même par accident, puisqu’il isole exclusivement le rapport
SKY (santé de réception), pas TPV (position/vitesse).
Publication via nginx#
location = /chrony_stats.txt {
allow all;
access_log off;
add_header Cache-Control "public, max-age=300";
}Régénération toutes les 5 minutes via un timer systemd (stats.timer +
stats.service), plutôt qu’un calcul à la demande, évite qu’une requête
cliente ne déclenche une lecture chrony coûteuse.
Erreurs à éviter#
Les angles azimut/élévation par satellite ne doivent jamais être publiés, même s’ils semblent inoffensifs. Combinés à l’éphéméride publique des satellites GNSS (position connue de chaque satellite à tout instant) et à un horodatage précis, ces angles permettraient de reconstituer une position approximative par triangulation inverse. C’est une catégorie de risque à part entière, “Localisation”, distincte des risques de performance ou de diagnostic habituels : toujours refuser par défaut, quelle que soit la métrique demandée.
- Supposer que le rapport JSON de gpsd a toujours la même forme.
Certains récepteurs (notamment ceux utilisant NMEA plutôt que le
protocole binaire propriétaire) émettent un rapport
SKYminimal, sans le tableausatellites[]détaillé, seulement des champs agrégés au niveau racine (uSat,hdop). Un script qui suppose la présence du tableausatellites[]produira un décompte de “0 satellite” trompeur au lieu du vrai chiffre, silencieusement : un script robuste doit gérer les deux formats. - Dimensionner mal le nombre de lignes lues vs le timeout. Demander
trop de lignes (
gpspipe -n 30) avec un timeout trop court peut provoquer une troncature systématique, alors qu’une seule ligneSKYcomplète suffit. Mieux vaut une petite valeur-nqui se termine naturellement, avec untimeoutlarge en pur filet de sécurité, que l’inverse. - Utiliser
printfpour formater des nombres extraits de JSON. Sur un système en locale non-anglophone (fr_FRpar exemple),printfinterprète le séparateur décimal comme une virgule et rejette un nombre au format JSON (point), combiné àset -e, cela tronque silencieusement la sortie. Toujours déléguer le formatage numérique àjq, qui est garanti indépendant de la locale.
Vérifier#
sudo systemctl start stats.service
cat /var/time/chrony_stats.txt
# Vérification statique : aucune fuite de position dans le code
grep -iEw 'lat|lon|alt|track|speed|az|el' /opt/stats/gnss_sky.sh
# Ne doit renvoyer AUCUNE ligne exécutable (seulement, éventuellement,
# des commentaires explicatifs)
curl -A "Mozilla/5.0" https://time.example.org/chrony_stats.txtnginx bloque les User-Agent
curl/wgetpar défaut (chapitre NGINX), pour tester manuellement l’accès public, toujours passer un User-Agent de navigateur (-A "Mozilla/5.0"), sinon le test échoue pour une raison qui n’a rien à voir avec la télémétrie elle-même.