OpenBSD sur AWS : Un Voyage Inattendu

Il a quelques mois lorsque j’ai rejoint D2SI, je me suis retrouvé jeté en pâture à AWS. Je m’amusais bien à jouer au Cow-boy du Cloud, mais il me manquait quelque chose : mon système d’exploitation favori OpenBSD n’était pas disponible ! A savoir qu’il n’existait aucune AMI (Amazon Machine Image) publique/communautaire/non-supportée permettant de jouer avec.

C’est un scandale !

Pour ceux d’entre vous qui ne connaîtraient pas OpenBSD, il s’agit d’un système d’exploitation de type UNIX basé sur 44BSD, multi-plateforme et axé sur la sécurité et la simplicité. Il est intéressant de mentionner le fait que le projet OpenBSD est entre autres responsable du logiciel OpenSSH; une liste détaillée des autres logiciels développés et/ou maintenus par le projet est disponible ici.

Pour résumer, il s’agit d’un système d’exploitation exceptionnel et je me retrouvais sans mon jouet favori !

Il est vivant, il est vivant !.. ah non

J’ai donc commencé à réfléchir à la création d’AMI à partir de rien. A ma surprise, cette procédure fut finalement assez facile :

  • créer un monter en localement une image de type RAW contenant un système de fichiers UFS
  • extraire les archives du système de base OpenBSD et copier le noyau
  • activer la sortie console (afin de voir la sortie de la commande « aws ec2 get-console-output »)
  • installer le chargeur de démarrage dans l’image
  • utiliser les outils ec2 afin d’importer l’image RAW vers S3, la convertir en volume (ec2-import-volume) dont on prendra un instantané (ec2-create-snapshot) à partir duquel une AMI sera créée (ec2-register)

Un script automatisant cette procédure est disponible sur GitHub. Malgré le fait qu’il ne soit pas totalement sec, il reste fonctionnel.

$ ./create-ami.sh -h 
./create-ami.sh[260]: -h: unknown option
usage: create-ami.sh [-in]
 -i /path/to/image
 -n only create the RAW image (not the AMI)
 -s image/AMI size (in GB; default to 1)

$ ./create-ami.sh 
===> creating image container
===> creating image filesystem
===> mounting image
===> fetching sets (can take some time)
===> fetching ec2-init
===> extracting sets
===> installing MP kernel
===> installing ec2-init
===> removing downloaded files
===> creating devices
===> storing entropy for the initial boot
===> installing master boot record
===> configuring the image
===> unmounting the image
===> image available at:
     /tmp/aws-ami.yIWPLf9Iu2/openbsd-20160210T223138Z

===> uploading image to S3 (can take some time)
Requesting volume size: 1 GB
<snip>
Uploading the manifest file
Uploading 1073741824 bytes across 103 parts
<snip>
The disk image for import-vol-fgd2p1en has been uploaded to Amazon S3 where it is being converted into an EBS volume. You may monitor the progress of this task by running ec2-describe-conversion-tasks. When the task is completed, you may use ec2-delete-disk-image to remove the image from S3.

===> converting image to volume (can take some time)

===> creating snapshot (can take some time)
<snip>

===> registering new AMI: openbsd-20160210T223138Z-20160210T223446Z
IMAGE ami-45358536

En utilisant cette AMI nouvellement créée, la machine a démarré sans souci et… sans connexion. Aucune interface réseau n’ayant été détectée, il était impossible de s’y connecter. Bien que l’hypervision d’AWS soit basé sur Xen, il ne permet pas de configurer quoi que ce soit au niveau de l’hyperviseur (pour des raisons évidentes) et il n’était donc pas possible de modifier la configuration pour exposer un chipset réseau standard. Et oui, il semblerait que ce soit bien la raison pour laquelle aucune AMI n’était disponible à cette période… 😉

Il est frais mon réseau, il est frais !

N’étant pas développeur noyau ni de pilote de périphériques, ma pauvre tentative pour ramener mon jouet préféré sur AWS échoua rapidement et lamentablement.

C’est à ce moment là que je me suis souvenu de plusieurs discussions informelles avec certains de mes collègues développeurs OpenBSD au sujet du support natif Xen domU. Armé de mon plus beau regard façon Chat Botté dans Shrek, j’ai commencé à errer à de gauche à droite pour finalement me rendre compte que Mike Belopuhov du projet OpenBSD avait déja commencé à travailler dessus. Ce développement est sponsorisé par Esdenera Networks (société allemande qui fournit des pare-feu basés sur OpenBSD pour les SDN – Software Defined Networks).

Moins de deux mois plus tard, Mike fit le cadeau suivant à la communauté (extrait de la page de manuel de xen(4)):

xen driver performs HVM domU guest initialization, provides abstraction for virtual Xen interrupts, access to the XenStore configuration storage as well as a device probing facility for paravirtualized devices such as disk and network interfaces.

C’est l’heure de la récré \o/

En selle !

Il ne restait plus qu’une dernière étape avant de pouvoir profiter pleinement de notre nouvelle installation d’OpenBSD : copier la clef SSH publique dans l’instance afin de pouvoir s’y connecter. Pour se faire, la plupart des distributions Linux utilisent cloud-init. Après un rapide coup d’oeil au code source, j’ai décidé qu’il ne valait pas la peine de le porter sous OpenBSD. Le code était déja suffisamment énorme du fait qu’il devait supporter la plupart des distributions et la plupart des fonctionnalité n’étaient pas à l’ordre du jour. Sans compter le fait que cloud-init est écrit en Python, ce qui aurait signifié rajouter des paquets externes à l’image de base par défaut.

A la place, j’ai créé un petit script tout simple qui va récupérer les informations nécessaires sur le serveur de meta-données d’AWS. Sous Linux cela reviendrait à faire pointer curl(1) ou wget(1) à l’URL http://169.254.169.254/latest/…

Le code est disponible sur GitHub et ne supporte que les fonctionnalités suivantes pour le moment :

  • réglage du nom de l’hôte
  • ajout de la clef SSH publique dans /root/.ssh/authorized_keys
  • exécution du « user-data » (si celui-ci débute avec un shebang)
  • affichage des empreintes SSH de l’hôte (comme le fait cloud-init)

J’ai également importé boto3, awscli ainsi que les outils d’API EC2 dans les ports d’OpenBSD afin qu’ils puissent être installés avec le gestionnaire de package du système.

$ aws ec2 describe-instances \
  --filters "Name=tag:Name,Values=openbsd" \
  --query "Reservations[].Instances[].InstanceId" \
  --output text
i-0dd5a20a9d3e65767
$ aws ec2 get-console-output --instance-id i-0dd5a20a9d3e65767
i-0dd5a20a9d3e65767 +2173200+267272+0+647168 [72+577248+384230]=0xa6ce90
entry point at 0x1001000 [7205c766, 34000004, 24448b12, e040a304]
[ using 962192 bytes of bsd ELF symbol table ]
Copyright (c) 1982, 1986, 1989, 1991, 1993
The Regents of the University of California. All rights reserved.
Copyright (c) 1995-2016 OpenBSD. All rights reserved. http://www.OpenBSD.org

OpenBSD 5.9 (GENERIC.MP) #1869: Thu Feb 4 09:50:59 MST 2016
deraadt@amd64.openbsd.org:/usr/src/sys/arch/amd64/compile/GENERIC.MP
real mem = 1056964608 (1008MB)
avail mem = 1020796928 (973MB)
mpath0 at root
scsibus0 at mpath0: 256 targets
mainbus0 at root
bios0 at mainbus0: SMBIOS rev. 2.4 @ 0xeb01f (11 entries)
bios0: vendor Xen version "4.2.amazon" date 12/07/2015
bios0: Xen HVM domU
acpi0 at bios0: rev 2
acpi0: sleep states S3 S4 S5
acpi0: tables DSDT FACP APIC HPET WAET SSDT SSDT
acpi0: wakeup devices
acpitimer0 at acpi0: 3579545 Hz, 32 bits
acpimadt0 at acpi0 addr 0xfee00000: PC-AT compat
ioapic0 at mainbus0: apid 1 pa 0xfec00000, version 11, 48 pins
ioapic0: misconfigured as apic 0, remapped to apid 1
cpu0 at mainbus0: apid 0 (boot processor)
cpu0: Intel(R) Xeon(R) CPU E5-2676 v3 @ 2.40GHz, 2400.32 MHz
cpu0: FPU,VME,DE,PSE,TSC,MSR,PAE,MCE,CX8,APIC,SEP,MTRR,PGE,MCA,CMOV,PAT,PSE36,CFLUSH,MMX,FXSR,SSE,SSE2,HTT,SSE3,PCLMUL,SSSE3,FMA3,CX16,PCID,SSE4.1,SSE4.2,MOVBE,POPCNT,DEADLINE,AES,XSAVE,AVX,F16C,RDRAND,HV,NXE,LONG,LAHF,ABM,FSGSBASE,BMI1,AVX2,SMEP,BMI2,ERMS,INVPCID
cpu0: 256KB 64b/line 8-way L2 cache
cpu0: smt 0, core 0, package 0
mtrr: Pentium Pro MTRR support, 8 var ranges, 88 fixed ranges
cpu0: apic clock running at 100MHz
acpihpet0 at acpi0: 62500000 Hz
acpiprt0 at acpi0: bus 0 (PCI0)
acpicpu0 at acpi0: C1(@1 halt!)
pvbus0 at mainbus0: Xen 4.2
xen0 at pvbus0: features 0x705, 32 grant table frames, event channel 3
"vfb" at xen0: device/vfb/0 not configured
"vbd" at xen0: device/vbd/768 not configured
xnf0 at xen0: event channel 5, address 06:da:1c:c0:fe:13
"console" at xen0: device/console/0 not configured
pci0 at mainbus0 bus 0
pchb0 at pci0 dev 0 function 0 "Intel 82441FX" rev 0x02
pcib0 at pci0 dev 1 function 0 "Intel 82371SB ISA" rev 0x00
pciide0 at pci0 dev 1 function 1 "Intel 82371SB IDE" rev 0x00: DMA, channel 0 wired to compatibility, channel 1 wired to compatibility
wd0 at pciide0 channel 0 drive 0:
wd0: 16-sector PIO, LBA48, 10240MB, 20971520 sectors
wd0(pciide0:0:0): using PIO mode 0, DMA mode 2
pciide0: channel 1 disabled (no drives)
piixpm0 at pci0 dev 1 function 3 "Intel 82371AB Power" rev 0x01: SMBus disabled
vga1 at pci0 dev 2 function 0 "Cirrus Logic CL-GD5446" rev 0x00
wsdisplay0 at vga1 mux 1: console (80x25, vt100 emulation)
wsdisplay0: screen 1-5 added (80x25, vt100 emulation)
xspd0 at pci0 dev 3 function 0 "XenSource Platform Device" rev 0x01
isa0 at pcib0
isadma0 at isa0
fdc0 at isa0 port 0x3f0/6 irq 6 drq 2
fd0 at fdc0 drive 0: density unknown
fd1 at fdc0 drive 1: density unknown
com0 at isa0 port 0x3f8/8 irq 4: ns16550a, 16 byte fifo
com0: console
pckbc0 at isa0 port 0x60/5 irq 1 irq 12
pckbd0 at pckbc0 (kbd slot)
wskbd0 at pckbd0: console keyboard, using wsdisplay0
pms0 at pckbc0 (aux slot)
wsmouse0 at pms0 mux 0
pcppi0 at isa0 port 0x61
spkr0 at pcppi0
nvram: invalid checksum
vscsi0 at root
scsibus1 at vscsi0: 256 targets
softraid0 at root
scsibus2 at softraid0: 256 targets
root on wd0a (8f65337136a44ad6.a) swap on wd0b dump on wd0b
clock: unknown CMOS layout
Automatic boot in progress: starting file system checks.
/dev/rwd0a: file system is clean; not checking
setting tty flags
pf enabled
starting network
DHCPDISCOVER on xnf0 - interval 3
DHCPOFFER from 172.31.16.1 (06:90:d1:8f:bf:e1)
DHCPREQUEST on xnf0 to 255.255.255.255
DHCPACK from 172.31.16.1 (06:90:d1:8f:bf:e1)
bound to 172.31.16.127 -- renewal in 1800 seconds.
openssl: generating isakmpd/iked RSA keys... done.
ssh-keygen: generating new host keys: RSA DSA ECDSA ED25519
starting early daemons: syslogd pflogd ntpd.
starting RPC daemons:.
savecore: /dev/wd0b: Device not configured
checking quotas: done.
clearing /tmp
kern.securelevel: 0 -> 1
creating runtime link editor directory cache.
preserving editor files.
starting network daemons: sshd smtpd.
ec2: #############################################################
ec2: -----BEGIN SSH HOST KEY FINGERPRINTS-----
ec2: 1024 SHA256:ec6LNxkAlF5zZrdg8q01D2TCZ7rEeM6hVE17UM/y+PM root@ip-172-31-16-127.eu-west-1.compute.internal (DSA)
ec2: 256 SHA256:pbxs2mSzBsUbD48UwJeVkDFrN9TfHk5/6fDGLO0bouA root@ip-172-31-16-127.eu-west-1.compute.internal (ECDSA)
ec2: 256 SHA256:8jo+VAjDuQSZZ79ReNRTt6xdCwkdoJgUVewEcgcWFyc root@ip-172-31-16-127.eu-west-1.compute.internal (ED25519)
ec2: 2048 SHA256:4um8T6zdyq/jD+dYz8+PXL3285ja/WQy0K/SqE8XQY8 root@ip-172-31-16-127.eu-west-1.compute.internal (RSA)
ec2: -----END SSH HOST KEY FINGERPRINTS-----
ec2: #############################################################
starting local daemons: cron.
Wed Feb 10 10:40:29 UTC 2016

OpenBSD/amd64 (ip-172-31-16-127.eu-west-1.compute.internal) (tty00)

login:

Bien qu’il reste du chemin à faire et beaucoup de tests à effectuer, nous avons à présent un support natif de netfront, l’interface de paravirtualisation Xen HVMPV. Pour le moment, les devices en mode bloc (i.e. les disques durs) utilisent toujours la couche de compatibilité IDE exposée par Xen. Mais il ne fait aucun doute que le travail continuera pour supporter l’interface native blkfront.

– Du coup c’était juste pour rigoler ? – Non !

Il existe plusieurs raisons objectives pour vouloir faire tourner OpenBSD sur AWS et le Cloud en général.

Tout d’abord, il y a du bon dans la diversité : il ne faudrait pas que l’on se retrouve dans un monde où le seul UNIX restant serait Linux.

Un installation typique d’OpenBSD possède une empreinte très petite en terme de stockage et mémoire et en fait un candidat solide sur lequel basé une stack applicative.

D’autre part OpenBSD est déjà prêt pour le Cloud : VirtIO (KVM, QEMU, VirtualBox) et VMT (VMware Tools) sont supportés depuis plusieurs années. Le système vient avec IPsec et un daemon BGP par défaut ce qui en fait un outil idéal pour créer des VPNs ou interconnecter des VPCs inter-régionaux. Sans parler du support de vxlan(4) utile pour construire des réseaux overlay (réseau virtualisé niveau 2 encapsulé dans du niveau 3)…

Oh et en ce qui concerne une passerelle NAT (NAT gateway), j’échangerais iptables avec PF à n’importe quel prix ! (PF étant le système de filtrage de paquets IP d’OpenBSD, utilisé également par Mac OS X et actuellement en cours de portage vers Oracle Solaris).

De nos jours n’importe quel type de service en ligne ressemble à une machinerie complexe impliquant de nombreux composants permettant de gérer la résilience, la performance, la redondance et l’élasticité. OpenBSD promeut la simplicité et architecturer ses infrastructures sur une fondation solide et sécurisée s’avère généralement payant.

A vous de jouer !

Tu peux me prêter ton jouet ?

Au premier regard bien qu’il semble similaire, OpenBSD n’est pas une distribution Linux. Il s’agit d’un système d’exploitation totalement différent donc ne soyez pas surpris si certaines commandes standards ne se comportent pas exactement de la même façon. Après tout nous sommes sous BSD et non GNU. Cela étant, la plupart des logiciels opensource sont disponibles comme sous Linux via des paquets (e.g apache, nodejs, PHP, varnish, haproxy, squid, ansible, puppet, GNOME, KDE, chromium, firefox…). Sous OpenBSD (et les BSDs en règle générale) il existe une séparation claire et explicite entre le système dit « de base » et les paquets externes : le système de base contient le système d’exploitation dans son intégralité (le noyau, les commandes BSD, les daemons standards…) et contrairement à Linux, celui-ci est développé comme un tout et non pas à partir de multitudes projets.

Les paquets externes (additionnels) sont toujours installés dans /usr/local et les fichiers de configuration dans /etc.

Pour conclure, installons et démarrons un serveur web : nginx. Notez que nous devons configurer explicitement notre miroir de téléchargement car nous utilisons une version de développement.

# export PKG_PATH=http://ftp.fr.openbsd.org/pub/OpenBSD/snapshots/packages/amd64
# pkg_add nginx
quirks-2.197 signed on 2016-02-08T22:17:20Z
Ambiguous: choose package for nginx
a    0: <None>
      1: nginx-1.9.10
      2: nginx-1.9.10-lua
      3: nginx-1.9.10-naxsi
      4: nginx-1.9.10-passenger
Your choice: 1
nginx-1.9.10: ok
The following new rcscripts were installed: /etc/rc.d/nginx
See rcctl(8) for details.
Look in /usr/local/share/doc/pkg-readmes for extra documentation.
# find /etc/nginx/ -type f
/etc/nginx/fastcgi_params
/etc/nginx/koi-utf
/etc/nginx/koi-win
/etc/nginx/mime.types
/etc/nginx/nginx.conf
/etc/nginx/scgi_params
/etc/nginx/uwsgi_params
/etc/nginx/win-utf
# rcctl enable nginx
# rcctl start nginx
nginx(ok)
# echo 'Welcome to nginx on OpenBSD!' \
    >/var/www/htdocs/index.html
# ftp -MVo - http://localhost
Welcome to nginx on OpenBSD!

Amusez-vous bien !

Commentaires :

A lire également sur le sujet :