Category Archives: computers

De Debian à NixOs

Voici un apperçu de pourquoi j’ai été vite convaincu de passer définitivement à NixOs après plus de 20 ans de satisfaction avec Debian. Sans aucun regret.

Quand j’étais jeune le débat était: programmation spaghetti vs programmation structurée. Ça parait irréel mais quand on a 1ko de mémoire et 8ko de rom l’art du jonglage est plus prisé que l’art de la structure. Aujourd’hui le débat est programmation procédurale vs programmation fonctionnelle pure. Ce ne sera plus un débat dans quelques dizaines d’années et on se demandera comme les gens ne voyait pas cette évidence.

On vit la même chose pour les distributions: ça a commencé par pas grand chose de plus que le référencement de fichiers tar pour devenir de plus en plus conceptuel afin d’apporter de la cohérence, de la sécurité et de l’ergonomie. Un saut majeur est réalisé par NixOs. Le même saut que le passage de la programmation procédurale à la programmation fonctionnelle pure (qui est au cœur de NixOs).

NixOs a une représentation conceptuelle de la distribution: une installation est le résultat d’une fonction pure qui prend en argument la description de ce que vous désirez avoir. En pratique vous décrivez ce que vous désirez avoir dans des fichiers dans /etc/nixos et nixos fait le travail.

Par exemple:

  • Pas de “apt-get install thunderbird”, vous écrivez “thunderbird” dans une liste dans un des fichiers dans /etc/nixos. Donc vous n’avez plus à mémoriser ce que vous avez installé ou configuré, tout est dans /etc/nixos. Gros SOULAGEMENT!
  • Vous pouvez raisonner par fonctionnalité, par exemple vous pouvez créer un /etc/nixos/lib/httpd.nix qui s’occupera de configurer les packages qui doivent être installés, les règles firewalls, les services systemd supplémentaires personnels etc.
  • Même si vous installez apache2, tout se fait dans /etc/nixos, mais vous pouvez reprendre des grandes parties de configuration apache, on peut voir NixOs comme, entre autres, une surcouche à toutes les configurations se trouvant dans etc.
  • Fini les problèmes de configuration du type “il faut mettre ça avant ça” ou “ce flag est à true donc on ne peut pas définir ce paramètre”: Le langage Nix est mathématique et les incohérences sont détectées ou impossibles.
  • NixOs fournit une archive de taille paramétrable des installations antérieures, donc si vous avez cassé quelque chose il suffit de booter sur une ancienne version (le menu du boot est mis à jour).
  • D’un ordinateur on peut en configurer et gérer d’autres (ou des conteneurs), autant que l’on veut. Fini l’IP de la gateway ou le nom du réseau local dupliqué: vous pouvez l’écrire à un endroit unique.
  • Dupliquer l’installation d’une mahine est simple puisqu’elle est entièrement et précisément décrite.
  • Quand la syntaxe de configuration d’un logiciel est archaïque (comme pour l’excellent rsnapshot), vous pouvez utiliser le langage Nix pour contourner les problèmes en générant des morcaux de la configuration par des fonctions nix.
  • NixOs compartimente les paquets, cela évite les problèmes d’incompatibilité et permet d’installer plusieurs versions (par exemple plusieurs PosgreSQL ou plusieurs apache2). Fini les “je veux bien t’installer ceci mais il faudra déinstaller cela”. Pour optimiser l’espace pris il permet quand même de partager par liens hard des fichiers identiques entre paquets.
  • Le côté très structuré favorise la sécurité. Par exemple créer un paquet est une description : url des sources, dépendances etc. Les possibilités d’introduire des vulnérabilités sont réduites. Par exemple pour apache2, ce fichier nix décrit la création du package (accessible par le lien “Source” sur https://search.nixos.org/packages). Il devient bien plus facile de vérifier que le mainteneur n’a pas introduit de vulnérabilité.
  • NixOs pousse à bien faire les choses, mais sans exagérer. Jusqu’à présent cela m’a aidé sans me gêner. Par exemple :
    • On est obligé d’utiliser git pour /etc/nixos
    • Il détecte les impuretés (l’utilisation d’éléments hors /etc/nixos non déclarés)
    • Il examine les parties de scripts bash dans la configuration nix et informe d’erreurs ou warnings.


Quelques concepts

Un fichier .nix

Votre répertoire /etc/nixos (le chemin est un standard mais il peut être ailleur) sera peuplé de fichiers “.nix” que vous organisez comme vous le souhaitez. Un “.nix” est simplement un bout de code en langage Nix même si souvent ça ressemble à un fichier de configuration. Et comme Nix est un langage fonctionnel le contenu d’un .nix sera une valeur (un nombre, un ensemble d’attributs etc) ou une fonction (retournant en général un ensemble d’attributs). Cela rend la description de vos machines très libre. Souvent un .nix est une fonction, par exemple:

{ host }:
{
        boot_partition="BOOT_RESC";
        root_partition="rescue";
        pruneGenerations = host.zone.pruneGenerations;
}

La partie “{ host }”: indique que c’est une fonction à un argument (host), le reste “{ …. }” est la valeur retournée par la fonction, ici un ensemble d’attributs.
Vous verrez souvent des .nix commençant par “{pkgs,lib,…}”, cela signifie que l’argument est un ensemble d’attributs devant définir pkgs et lib mais pouvant contenir d’autres attributs (le “,…”).
Mais ce .nix est aussi valide “{x,y}:x+y”, cette fonction prend en argument une ensemble d’attributs et retourne la somme des attributs x et y. On peut fournir les arguments sans passer par un ensemble d’attributs, par exemple “x : y : x+y” est une fonction prenant deux argument (x et y) et retournant la somme.

/etc/nixos/flake.nix

Quand on utilise Flakes (c’est optionnel mais devient un standard), le fichier flake.nix (nom conventionnel) contient la description des différents ordinateurs. Pour que NixOs fournisse une fonction pure (sans effet de bord) alors que les paquets peuvent changer, il faut fournir en argument les paramètres décrivant “de quoi on part”. C’est pourquoi un flake.nix comporte deux parties: inputs et outputs. C’est à dire : de quoi ont part et ce que l’on obtient. Donc vous dites que vous partez de telle version de NixOs (inputs) et dans outputs vous décrivez les machines que vous désirez.

Dans ce petit exemple on part de la distribution instable de NixOs et de “nur” puis ont décrit les machines (une seule ici). “mysystem” est le nom de la machine, elle est décrite dans “configuration.nix” et pourra utiliser “inputs” comme argument dans ses fonctions.

Pour que ce flake.nix soit une fonction pure il manque une chose: en effet la distribution change et produirait donc des installations différentes. C’est pourquoi un /etc/nixos/flake.lock est créé automatiquement à la première interprétation pour fixer la release exacte. Donc si vous recopier votre /etc/nixos sur une autre machine cela produira la même installation aux mêmes versions de logiciels.
Le principe de Flakes sert aussi à tout ce qui “produit à partir de”. Par exemple on peut l’utiliser pour produire un package.

Le type “chemin”

Le langage Nix a un type “chemin”. C’est logique et très pratique mais un peu “tricky” au début, car:

  • On peut ajouter un chemin plus une chaîne (pas l’inverse), par exemple ./. + “host” est le chemin ./host
  • Un chemin relatif est relatif à l’endroit du fichier qui le définit et … le reste! C’est étrange au début car inhabituel. Par exemple si dans host/miami/host.nix on définit “host_base=./.;”, un code nix dans /lib utilisant host_base récupèrera le chemin relatif à lui de host/miami et non simplement “./.” comme ça le serait dans d’autres langages.

Organisation

Nous avons vu que nous pouvons organiser la configuration de manière très libre. Voici une base d’organisation que je trouve confortable et puissante. Imaginons la gestion de toutes les machines de GreenPeace:

  • zone/<nom-zone>/host/<nom-machine>: le répertoire de configuration d’une machine. On découpe en zones, par exemple “paris” pour le siège à Paris ou “public” pour les serveurs publiques.
  • lib/ : vos .nix partagés entre machines
  • store/ : vos fichiers non .nix partagés

Chaque zone pourra aussi avoir un dossier lib, store etc si il y a des spécificités.

Vous avez aussi besoin de paramètres, au niveau du host, de la zone et globalement. Exemple de paramètres: l’email de l’administrateur principal ou des IPs de DNS. C’est une très bonne chose de séparer les paramètres du reste car ils peuvent être utilisés par vos librairies. On utilisera le même découpage:

  • organisation.nix : le fichier principal de configuration de greenpeace (comme le nom, l’email de l’administrateur principal etc).
  • zone/<nom-zone>/zone.nix : les paramètres de la zone.
  • zone/<nom-zone>/host/<nom-machine>/host.nix : les paramètres de la machine.

Pour éviter que les librairies aient trois arguments (orga,zone,host) et pour rendre le système plus souple je trouve préférable que chaque host.nix reprenne les paramètres de la zone (par une variable “zone”) et la zone de l’organisation. Ainsi “host” contient tous les paramètres.

Quelques liens

Unix/Linux safe remote commands

Do you need to execute some commands from batch scripts on a remote host but don’t want to allow full shell access? For a single command, you can use sshd’s ForceCommand, but it allows only one command per account. This text explains how you can execute safe, remote, limited multiple commands.

The trick :

  • Create a special, unique, passwordless user for remote commands, such as “sudo_lover”.
  • ForceCommand‘ runs a Python script named “use_sudo,” which acts like a safe version of “sudo $SSH_ORIGINAL_COMMAND”. You can configure allowed commands with sudo.
  • use_sudo‘ logs commands (auth.log), making it easy to add a command by almost a copy and paste (for example, configuring rsync without the log would be almost impossible).

One drawback is the difficulty in redirecting STDOUT/STDERR. To redirect STDOUT, I use a second program named “out_to,” which takes the file to redirect to and the command to execute.

cat use_sudo
#!/usr/bin/python3
import os
import shlex
import logging
import logging.handlers
from logging.handlers import SysLogHandler

# logger configuration : auth.info
syslogHandler = logging.handlers.SysLogHandler(address='/dev/log', facility="auth")
formatter = logging.Formatter('use_sudo: %(levelname)s - %(message)s')
syslogHandler.setFormatter(formatter)

logger = logging.getLogger('use_sudo')
logger.setLevel(logging.INFO)
logger.addHandler(syslogHandler)

command_from_env = os.environ.get("SSH_ORIGINAL_COMMAND")
if(not command_from_env):
    print("SSH_ORIGINAL_COMMAND is undefined.")
    exit(1)
command_with_arguments = shlex.split(command_from_env)
logger.info(str(command_with_arguments))

command_for_sudo = [f"/usr/bin/sudo -n {command_with_arguments[0]} ...",'-n'] + command_with_arguments
os.execv("/usr/bin/sudo", command_for_sudo)

cat out_to
#!/usr/bin/python3
import os
import sys
import subprocess

arguments = sys.argv
outfile=arguments[1]
command = arguments[2:]
with open(outfile, 'w') as outfile_h:
    try:
        subprocess.run(command, stdin=sys.stdin, stdout=outfile_h, stderr=sys.stderr, check=True)
    except subprocess.CalledProcessError as e:
        print(f"Error while executing : {e}")

How to route specific traffic through a VPN on Linux with nftables

Introduction

Here I explain a strong solution to route traffic to a VPN using the group of the processes. We discuss security at the end. After the configuration, if you want to run ktorrent using the VPN you just have to run sudo -g vpn_euro ktorrent. You will be able to use serveral VPN and no VPN the same time, per process.

Continue reading How to route specific traffic through a VPN on Linux with nftables