systemd
La unidad
Sección titulada «La unidad»[Unit]Description=ghchronicle, GitHub metrics collectorAfter=network-online.targetWants=network-online.target
[Service]Type=simpleUser=ghchronicleGroup=ghchronicleEnvironmentFile=/etc/ghchronicle/ghchronicle.envExecStart=/usr/local/bin/ghchronicle -config /etc/ghchronicle/config.yamlRestart=alwaysRestartSec=30s
# This is the only process on the host holding a GitHub token, so it gets# nothing it does not need.NoNewPrivileges=truePrivateTmp=truePrivateDevices=trueProtectSystem=strictProtectHome=trueProtectKernelTunables=trueProtectKernelModules=trueProtectControlGroups=trueProtectClock=trueProtectHostname=trueProtectProc=invisibleRestrictNamespaces=trueRestrictRealtime=trueRestrictSUIDSGID=trueLockPersonality=trueMemoryDenyWriteExecute=trueSystemCallArchitectures=nativeSystemCallFilter=@system-serviceCapabilityBoundingSet=AmbientCapabilities=RestrictAddressFamilies=AF_INET AF_INET6
StateDirectory=ghchronicleReadWritePaths=/var/lib/ghchronicle
[Install]WantedBy=multi-user.targetPor qué está blindada
Sección titulada «Por qué está blindada»El modelo de amenaza es corto y es toda la justificación: este es muy probablemente el único proceso de la máquina que guarda un token de GitHub con acceso de lectura a todos los repositorios de una cuenta. Un token es una credencial al portador. Cualquier cosa que pueda leer la memoria de este proceso o su fichero de entorno tiene la cuenta.
Así que la unidad le da al proceso exactamente lo que necesita, que resulta ser casi nada: un socket TCP saliente y un directorio escribible.
| Directiva | Qué quita |
|---|---|
CapabilityBoundingSet=, AmbientCapabilities= | Todas las capacidades de Linux. No abre puertos privilegiados ni posee dispositivos |
No | Cualquier vía para ganar privilegios mediante exec, incluidos los binarios setuid |
Protect | El acceso de escritura a todo el sistema de ficheros, salvo ReadWritePaths |
Protect | Todos los directorios personales, que es donde suelen vivir las credenciales interesantes de una máquina |
PrivateTmp=true, PrivateDevices=true | Los ficheros temporales compartidos y los nodos de dispositivo físicos |
Protect | La capacidad de ver los procesos de otros usuarios en /proc, así que no puede leer la línea de órdenes de otro proceso |
Restrict | Los sockets Unix y netlink. Habla HTTPS y nada más |
MemoryDenyWriteExecute=true, LockPersonality=true | Las primitivas habituales de shellcode |
System | Toda llamada al sistema fuera del conjunto ordinario de servicio, incluidas las de módulos y ajuste del núcleo |
ProtectKernelTunables, ProtectKernelModules, ProtectControlGroups, ProtectClock, ProtectHostname, RestrictNamespaces, RestrictRealtime, RestrictSUIDSGID | Todas las vías que quedan para cambiar la máquina desde dentro del servicio |
StateDirectory=ghchronicle hace que systemd cree /var/lib/ghchronicle con el
dueño correcto al arrancar, así que el fichero de estado tiene dónde vivir sin
un mkdir manual y un chown que alguien olvidará tras una reinstalación.
Ahí viven dos ficheros, no uno. Junto a state.json la pasada guarda su
registro de escrituras, state-written.bin por omisión, que es lo que evita
volver a escribir un punto que no ha cambiado; ReadWritePaths cubre el
directorio, así que los dos están ya permitidos. Si pones alguno en otro sitio,
esa ruta hay que añadirla aquí, y perder el registro cuesta una pasada de
reescritura:
solo se escribe lo que ha cambiado.
Instalarla
Sección titulada «Instalarla»-
Crea el usuario y los directorios.
Ventana de terminal sudo useradd --system --no-create-home --shell /usr/sbin/nologin ghchroniclesudo mkdir -p /etc/ghchronicle -
Pon la configuración en su sitio.
Directorio/etc/ghchronicle/
- config.yaml legible por todos, sin secretos dentro
- ghchronicle.env modo 600, los tokens
Directorio/var/lib/ghchronicle/
- state.json lo crea el servicio
- state-written.bin el registro de escrituras, al lado
-
Escribe el fichero de entorno, y nada más en él.
/etc/ghchronicle/ghchronicle.env GITHUB_TOKEN=github_pat_...INFLUX_TOKEN=...Ventana de terminal sudo chmod 600 /etc/ghchronicle/ghchronicle.envTodo en
config.yamllos lee mediante${VAR}, que es lo que permite que la configuración sea legible por todos y esté versionada mientras los secretos no lo están. -
Arráncalo.
Ventana de terminal sudo systemctl daemon-reloadsudo systemctl enable --now ghchroniclesudo systemctl status ghchronicle
Qué vigilar
Sección titulada «Qué vigilar»El log dice qué se escribió y dónde.
level=INFO msg=written sink=influxdb family=traffic points=629level=INFO msg="rate budget" bucket=core remaining=4354 limit=5000Dos avisos merecen una alerta:
rate limit reserve reachedsignifica que se saltó una familia para proteger el presupuesto. Una vez está bien; en cada pasada significa que las cadencias son demasiado rápidas para el número de repositorios.family failed everywhere, not marking it as runsignifica que todos los repositorios fallaron en una familia, así que se reintentará en vez de darla por hecha.
journalctl -u ghchronicle -fjournalctl -u ghchronicle -p warning --since todaycron en vez de un servicio
Sección titulada «cron en vez de un servicio»-once ejecuta una sola pasada y termina, que es todo lo que necesita un
planificador.
0 * * * * /usr/local/bin/ghchronicle -config /etc/ghchronicle/config.yaml -onceDeja el fichero de estado en una ruta persistente también en este modo. Es lo
que evita que el recorrido de estrellas y el relleno año por año del calendario
de contribuciones vuelvan a ocurrir en cada ejecución. Ten en cuenta que un cron
horario da a cada familia una cadencia horaria como mucho, así que el ritmo de
quince minutos de actions se pierde.