Bhyve es el hypervisor de virtualización oficial de FreeBSD a partir de la versión 10.0-RELEASE, en este artículo utilizaremos vm-bhyve, un wrapper que nos facilitará mucho la gestion de máquinas y de la red.
- Instalación
- Puerto serie
- VNC
- Cloud image
- Cloud image: Provisioning
- Cloud image: cloud-init/CIDATA
- Cloud image: cloud-init/Ficheros locales
- Cloud image: cloud-init/IMDS
- Firewall
- Wifi
- Troubleshooting
Instalación
Instalamos todo lo necesario:
Creamos un pool ZFS donde se almacenarán las máquinas virtuales:
zfs set recordsize=64K zroot/vm
Habilitamos el servicio y le indicamos donde estarán alojadas las máquinas virtuales:
sysrc vm_dir="zfs:zroot/vm"
Inicializamos vm-bhyve:
Copiamos los templates de ejemplo:
Creamos un switch para conectar las máquinas virtuales con la interfaz de red física:
Añadimos la interfaz física al switch:
Podemos ver los switches disponibles:
NAME TYPE IFACE ADDRESS PRIVATE MTU VLAN PORTS
public standard vm-public - no - - bge0
Cuando arranquemos una máquina virtual la interfaz tap correspondiente de la máquina se añadirá de forma automática al bridge: vm-public asociado con el switch: public.
Una interfaz física solo puede estar en un bridge, así que si algún software previo ya la está utilizando en algún bridge tendremos que reutilizar el bridge existente.
En mi caso el bridge existente se llama bridge0, por lo tanto creo un switch de vm-bhyve llamado bridge0:
NAME TYPE IFACE ADDRESS PRIVATE MTU VLAN PORTS
public standard vm-public - no - - -
bridge0 manual bridge0 n/a no n/a n/a n/a
Para que las máquinas utilicen dicho switch debemos modificar el template que vayamos a utilizar:
/zroot/vm/.templates/alpine.conf:network0_switch="public"
/zroot/vm/.templates/arch.conf:network0_switch="public"
/zroot/vm/.templates/centos6.conf:network0_switch="public"
/zroot/vm/.templates/centos7.conf:network0_switch="public"
/zroot/vm/.templates/coreos.conf:network0_switch="public"
/zroot/vm/.templates/debian.conf:network0_switch="public"
/zroot/vm/.templates/default.conf:network0_switch="public"
/zroot/vm/.templates/dragonfly.conf:network0_switch="public"
/zroot/vm/.templates/freebsd-zvol.conf:network0_switch="public"
/zroot/vm/.templates/freepbx.conf:network0_switch="public"
/zroot/vm/.templates/linux-zvol.conf:network0_switch="public"
/zroot/vm/.templates/netbsd.conf:network0_switch="public"
/zroot/vm/.templates/openbsd.conf:network0_switch="public"
/zroot/vm/.templates/resflash.conf:network0_switch="public"
/zroot/vm/.templates/ubuntu.conf:network0_switch="public"
/zroot/vm/.templates/windows.conf:network0_switch="public"
Nos bajamos la imágenes de los sistemas operativos que queramos instalar, en mi caso Kali-linux:
-rw-r--r-- 1 root wheel 2994323456 Feb 7 19:46 /zroot/vm/.iso/kali-linux-2022.1-installer-amd64.iso
Una VM Linux puede arrancar con distintos loaders grub/uefi, según el que utilicemos accederemos a la VM de un modo u otro, mi consejo es utilizar grub y acceder mediante puerto serie a las VMs ya que no precisa de ningún software adicional, además si necesitamos arrancar alguna aplicación gráfica siempre podremos forwardear las X.
Puerto serie
Creamos la máquina virtual:
Instalamos desde la ISO:
Veremos el instalador en la propia consola:
Podemos consultar información de la máquina virtual mediante el siguiente comando:
------------------------
Virtual Machine: kali
------------------------
state: running (99075)
datastore: default
loader: grub
uuid: 05ced6c1-d3a7-11ec-875e-5065f37a62c0
uefi: default
cpu: 2
memory: 2G
memory-resident: 215412736 (205.433M)
network-interface
number: 0
emulation: virtio-net
virtual-switch: public
fixed-mac-address: 58:9c:fc:00:81:dd
fixed-device: -
active-device: tap0
desc: vmnet-kali-0-public
mtu: 1500
bridge: vm-public
bytes-in: 0 (0.000B)
bytes-out: 0 (0.000B)
virtual-disk
number: 0
device-type: sparse-zvol
emulation: virtio-blk
options: -
system-path: /dev/zvol/zroot/vm/kali/disk0
bytes-size: 16106127360 (15.000G)
bytes-used: 57344 (56.000K)
Si listamos las máquinas virtuales veremos que está corriendo:
NAME DATASTORE LOADER CPU MEMORY VNC AUTOSTART STATE
kali default grub 2 2G - No Running (99075)
Para salir de la consola debemos presionar:
~ + (CtrDerecha + d)
VNC
Creamos la máquina virtual:
La configuramos para que el loader sea uefi y definimos los parámetros de configuración VNC:
loader="grub" -> loader="uefi"
graphics="yes"
xhci_mouse="yes" -> Mejora la experiencia con el ratón
graphics_listen="IP_SERVER_PADRE"
graphics_port="5900"
graphics_res="1600x900"
graphics_wait="yes" --> Deja la VM en pausa hasta que conectemos por VNC, brutal. Recordad eliminar este parámetro una vez hayamos realizado la instalación inicial.
Iniciamos la instalación:
Arrancamos la máquina:
Al consultar las máquinas virtuales veremos que esta última tiene asociado un socket VNC:
NAME DATASTORE LOADER CPU MEMORY VNC AUTOSTART STATE
kali default grub 2 2G - No Running (42962)
kali2 default uefi 2 2G 192.168.69.180:5900 No Locked (odyssey.alfaexploit.com)
Accedemos al instalador vía VNC:
Cloud image
Para hacer la prueba nos bajamos la versión cloud de Ubuntu:
Podemos ver la imagen descargada:
DATASTORE FILENAME
default ubuntu-22.04-server-cloudimg-amd64.img
Creamos la máquina virtual:
Arrancamos la imagen:
El problema de estas imágenes es que el cloud-init de vm-bhyve está muy limitado tan solo nos deja meter las keys SSH, pero no asignar un ip estática, por lo tanto la VM obtendrá una por DHCP que tendremos que descubrir:
fixed-mac-address: 58:9c:fc:02:a4:96
MAC=$(vm info ubuntu-cloud|grep 'fixed-mac-address'|tr -d " "|awk -F "fixed-mac-address:" '{print$2}')
arp -a |grep $MAC
? (192.168.69.225) at 58:9c:fc:02:a4:96 on em0 expires in 1110 seconds [ethernet]
Accedemos vía SSH ya que nuestra pubkey debe estar autorizada:
Asignamos password a los usuarios para poder acceder por consola vm-bhyve:
passwd
passwd ubuntu
Ahora también podremos acceder por consola:
Consultamos la información básica:
Linux ubuntu-cloud 5.15.0-83-generic #92-Ubuntu SMP Mon Aug 14 09:30:42 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
Cloud image: Provisioning
El soporte de cloud-init en vm-bhyve es muy limitado, tan solo permite autorizar las keys SSH, para superar estas limitaciones tendremos que generar los ficheros de cloud-init nosotros mismos.
Para configurar correctamente cloud-init debemos comprender primero el proceso de configuración que sigue:
- cloud-init descubre el datasource y obtiene los siguientes ficheros de configuración:
- meta-data(Ex: pre-network): Información sobre la instancia, machine ID, hostname.
- network-config(Ex: pre-network): Configuración de red.
- vendro-data(Ex: post-network): Optimizaciones de hardware, este fichero es opcional.
- user-data(Ex: post-network): Usuarios, claves SSH, custom scripts, integración con sistemas como Puppet/Chef/Ansible.
- cloud-init configura la red, si no existe configuración pedirá ip por DHCP.
- cloud-init configura la instancia acorde a los ficheros vendor-data y user-data.
Cloud-init soporta varios datasources , en nuestro caso utilizaremos NoCloud que nos permite configurar máquinas virtuales a través de:
- Sistema de ficheros local con volid: CIDATA.
- Ficheros locales: SMBIOS serial number/Kernel command line.
- Servidor IMDS(Requiere servidor DHCP): SMBIOS serial number/Kernel command line.
Cloud image: cloud-init/CIDATA
Cloud-init leerá estos ficheros si la máquina virtual dispone de una unidad de cd-rom con el volid: CIDATA.
Instalamos cdrkit-genisoimage:
Creamos la máquina virtual:
Creamos los
ficheros de cloud-init
:
cd /zroot/vm/ubuntu-cloud2/
cat << EOF > .cloud-init/meta-data
instance-id: Alfaexploit/Ubuntu-CloudImg2
local-hostname: Ubuntu-CloudImg2
EOF
cat << EOF > .cloud-init/user-data
#cloud-config
users:
# Following entry create user1 and assigns password specified in plain text.
# Please not use of plain text password is not recommended from security best
# practises standpoint
- name: user1
groups: sudo
sudo: ALL=(ALL) NOPASSWD:ALL
plain_text_passwd: PASSWORD
lock_passwd: false
# Following entry creates user2 and attaches a hashed passwd to the user. Hashed
# passwords can be generated with:
# python -c 'import crypt,getpass; print(crypt.crypt(getpass.getpass(), crypt.mksalt(crypt.METHOD_SHA512)))'
- name: user2
passwd: $PASS_HASH
lock_passwd: false
# Following entry creates user3, disables password based login and enables an SSH public key
- name: user3
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCiF8zV98gaV87zVDwhZ8IDxUXs3Xq0NKVT1HY2t3XMjMU6/Sv6WeMeKQtJn/Py5PPXhYkOQ92in5+70WE6fQPt0shH6Hh4ZwY8lKDboLh96XLiCsxZc/IMq0h86QeT2E7FjAXcGo/2gziSYvbnp2Z+JAe5uBm4HtV3419mlEmaRAXGtjQxqcnhoUsLj4gD53ak1H/KUro+GArVDPUoMdSsqxvL0BHGbbcFOIaA3H3GwnGp09bHBNd0fR+Ip6CHRlRIu9oohezOAU+/3ul+FFbZuVlJ8zssaaFOzxjwEhD/2Ghsae3+z6tkrbhEOYN7HvBDejK9WySeI0+bMRqfSaID kr0m@DirtyCow
lock_passwd: true
# Install some basic software
packages:
- htop
- screen
EOF
cat << EOF > .cloud-init/network-config
version: 2
ethernets:
enp0s5:
addresses: [192.168.69.30/24]
routes:
- to: default
via: 192.168.69.200
nameservers:
addresses: [1.1.1.1,8.8.8.8]
EOF
Generamos la imagen semilla
con el volid: cidata.
Añadimos la imagen cd-rom a la máquina virtual:
disk1_type="ahci-cd"
disk1_dev="custom"
disk1_name="/zroot/vm/ubuntu-cloud2/seed.iso"
EOF
Arrancamos la máquina virtual, esta leerá la imagen semilla
e interpretará la configuración cloud-init:
vm console ubuntu-cloud2
Veremos como configura la red:
[ 10.606424] cloud-init[649]: Cloud-init v. 23.2.2-0ubuntu0~22.04.1 running 'init' at Wed, 27 Sep 2023 20:13:02 +0000. Up 10.59 seconds.
[ 10.615655] cloud-init[649]: ci-info: ++++++++++++++++++++++++++++++++++++++++++++++Net device info++++++++++++++++++++++++++++++++++++++++++++++
[ 10.616879] cloud-init[649]: ci-info: +--------+------+--------------------------------------------+---------------+--------+-------------------+
[ 10.618050] cloud-init[649]: ci-info: | Device | Up | Address | Mask | Scope | Hw-Address |
[ 10.619210] cloud-init[649]: ci-info: +--------+------+--------------------------------------------+---------------+--------+-------------------+
[ 10.620362] cloud-init[649]: ci-info: | enp0s5 | True | 192.168.69.30 | 255.255.255.0 | global | 58:9c:fc:02:5c:df |
[ 10.621520] cloud-init[649]: ci-info: | enp0s5 | True | 2a0c:5a86:d204:ca00:5a9c:fcff:fe02:5cdf/64 | . | global | 58:9c:fc:02:5c:df |
[ 10.622819] cloud-init[649]: ci-info: | enp0s5 | True | fe80::5a9c:fcff:fe02:5cdf/64 | . | link | 58:9c:fc:02:5c:df |
[ 10.624148] cloud-init[649]: ci-info: | lo | True | 127.0.0.1 | 255.0.0.0 | host | . |
[ 10.625316] cloud-init[649]: ci-info: | lo | True | ::1/128 | . | host | . |
[ 10.626463] cloud-init[649]: ci-info: +--------+------+--------------------------------------------+---------------+--------+-------------------+
Podemos ver que ha generado los usuarios tal y como le hemos indicado.
Usuario user1 local ya que por defecto no acepta login SSH mediante password y no hemos autorizado ninguna key:
Ubuntu-CloudImg2 login: user1
$ id
uid=1000(user1) gid=1000(user1) groups=1000(user1),27(sudo)
Usuario user2 local ya que por defecto no acepta login SSH mediante password y no hemos autorizado ninguna key, pero el password se había definido mediante hash a diferencia de user1:
Ubuntu-CloudImg2 login: user2
$ id
uid=1001(user2) gid=1001(user2) groups=1001(user2)
Usuario user3 con acceso solo por SSH, sin login local:
$ id
uid=1002(user3) gid=1002(user3) groups=1002(user3)
Cloud image: cloud-init/Ficheros locales
Los pasos a seguir para utilizar ficheros locales son:
- Generar los ficheros cloud-init.
- Copiar los ficheros a la imagen del sistema operativo a desplegar.
- Arrancar la máquina virtual indicando el path de los ficheros mediante el parámetro serial de SMBIOS : ds=nocloud;s=file://path/to/directory/;h=node-42 o mediante parámetros del kernel .
Mediante SMBIOS serial number:
Creamos los
ficheros de cloud-init
:
cd ~/ubuntu-cloud3
Por alguna razón no es posible dejar la configuración de red en el
fichero network-config
como hemos hecho con la ISO semilla
si no que hay que meterla en el meta-data:
cat << EOF > ~/ubuntu-cloud3/meta-data
instance-id: Alfaexploit/Ubuntu-CloudImg3
local-hostname: Ubuntu-CloudImg3
network-interfaces: |
iface enp0s5 inet static
address 192.168.69.30
network 192.168.69.0
netmask 255.255.255.0
broadcast 192.168.69.255
gateway 192.168.69.200
EOF
cat << EOF > ~/ubuntu-cloud3/user-data
#cloud-config
users:
# Following entry create user1 and assigns password specified in plain text.
# Please not use of plain text password is not recommended from security best
# practises standpoint
- name: user1
groups: sudo
sudo: ALL=(ALL) NOPASSWD:ALL
plain_text_passwd: PASSWORD
lock_passwd: false
# Following entry creates user2 and attaches a hashed passwd to the user. Hashed
# passwords can be generated with:
# python -c 'import crypt,getpass; print(crypt.crypt(getpass.getpass(), crypt.mksalt(crypt.METHOD_SHA512)))'
- name: user2
passwd: $PASS_HASH
lock_passwd: false
# Following entry creates user3, disables password based login and enables an SSH public key
- name: user3
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCiF8zV98gaV87zVDwhZ8IDxUXs3Xq0NKVT1HY2t3XMjMU6/Sv6WeMeKQtJn/Py5PPXhYkOQ92in5+70WE6fQPt0shH6Hh4ZwY8lKDboLh96XLiCsxZc/IMq0h86QeT2E7FjAXcGo/2gziSYvbnp2Z+JAe5uBm4HtV3419mlEmaRAXGtjQxqcnhoUsLj4gD53ak1H/KUro+GArVDPUoMdSsqxvL0BHGbbcFOIaA3H3GwnGp09bHBNd0fR+Ip6CHRlRIu9oohezOAU+/3ul+FFbZuVlJ8zssaaFOzxjwEhD/2Ghsae3+z6tkrbhEOYN7HvBDejK9WySeI0+bMRqfSaID kr0m@DirtyCow
lock_passwd: true
# Install some basic software
packages:
- htop
- screen
EOF
Convertimos la imagen a formato RAW para poder montarla:
Generamos un memory disk a partir de la imagen:
Podemos ver las particiones que contiene:
=> 34 4612029 md0 GPT (2.2G)
34 2014 - free - (1.0M)
2048 8192 14 bios-boot (4.0M)
10240 217088 15 efi (106M)
227328 4384735 1 linux-data (2.1G)
Montamos la / del sistema Linux:
Copiamos los ficheros:
Desmontamos:
Eliminamos el memory disk:
Creamos la máquina virtual a partir de la imagen modificada:
vm-bhyve nos permite pasarle argumentos directamente a Bhyve mediante el parámetro
bhyve_options
, a su vez Bhyve permite configurar
variables desde CLI: -o
.
La variable que necesitamos modificar es
system.serial_number
, identificar la variable correcta ha sido un proceso de prueba/error.
Añadimos las opciones Bhyve:
bhyve_options="-o system.serial_number=ds=nocloud;s=file://ubuntu-cloud3/"
EOF
Arrancamos la máquina virtual:
Los usuarios se habrán generado tal y como indican los ficheros de configuración, además podemos ver la información inyectada en SMBIOS:
System Information
Manufacturer: FreeBSD
Product Name: BHYVE
Version: 1.0
Serial Number: ds=nocloud;s=file://ubuntu-cloud3/
UUID: b7ff0817-5d72-11ee-b808-3497f636bf45
Wake-up Type: Power Switch
SKU Number: None
Family: Virtual Machine
Mediante parámetros del kernel:
Creamos los
ficheros de cloud-init
:
cd ~/ubuntu-cloud4
Por alguna razón no es posible dejar la configuración de red en el fichero network-config como hemos hecho con la ISO `semilla`` si no que hay que meterla en el meta-data:
cat << EOF > ~/ubuntu-cloud4/meta-data
instance-id: Alfaexploit/Ubuntu-CloudImg4
local-hostname: Ubuntu-CloudImg4
network-interfaces: |
iface enp0s5 inet static
address 192.168.69.30
network 192.168.69.0
netmask 255.255.255.0
broadcast 192.168.69.255
gateway 192.168.69.200
EOF
cat << EOF > ~/ubuntu-cloud4/user-data
#cloud-config
users:
# Following entry create user1 and assigns password specified in plain text.
# Please not use of plain text password is not recommended from security best
# practises standpoint
- name: user1
groups: sudo
sudo: ALL=(ALL) NOPASSWD:ALL
plain_text_passwd: PASSWORD
lock_passwd: false
# Following entry creates user2 and attaches a hashed passwd to the user. Hashed
# passwords can be generated with:
# python -c 'import crypt,getpass; print(crypt.crypt(getpass.getpass(), crypt.mksalt(crypt.METHOD_SHA512)))'
- name: user2
passwd: $PASS_HASH
lock_passwd: false
# Following entry creates user3, disables password based login and enables an SSH public key
- name: user3
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCiF8zV98gaV87zVDwhZ8IDxUXs3Xq0NKVT1HY2t3XMjMU6/Sv6WeMeKQtJn/Py5PPXhYkOQ92in5+70WE6fQPt0shH6Hh4ZwY8lKDboLh96XLiCsxZc/IMq0h86QeT2E7FjAXcGo/2gziSYvbnp2Z+JAe5uBm4HtV3419mlEmaRAXGtjQxqcnhoUsLj4gD53ak1H/KUro+GArVDPUoMdSsqxvL0BHGbbcFOIaA3H3GwnGp09bHBNd0fR+Ip6CHRlRIu9oohezOAU+/3ul+FFbZuVlJ8zssaaFOzxjwEhD/2Ghsae3+z6tkrbhEOYN7HvBDejK9WySeI0+bMRqfSaID kr0m@DirtyCow
lock_passwd: true
# Install some basic software
packages:
- htop
- screen
EOF
Convertimos la imagen a formato RAW para poder montarla:
Generamos un memory disk a partir de la imagen:
Podemos ver las particiones que contiene:
=> 34 4612029 md0 GPT (2.2G)
34 2014 - free - (1.0M)
2048 8192 14 bios-boot (4.0M)
10240 217088 15 efi (106M)
227328 4384735 1 linux-data (2.1G)
Montamos la / del sistema Linux:
Copiamos los ficheros:
Desmontamos:
Eliminamos el memory disk:
Creamos la máquina virtual a partir de la imagen modificada:
Añadimos las opciones Bhyve:
grub_run_partition="gpt1"
grub_run0="linux /boot/vmlinuz root=/dev/vda1 rw ds='nocloud;s=file://ubuntu-cloud4/'"
grub_run1="initrd /boot/initrd.img"
EOF
Arrancamos la máquina virtual:
vm console ubuntu-cloud4
Podemos ver que ha generado los usuarios tal y como le hemos indicado, además podemos consultar los parámetros recibidos por el kernel:
[ 0.038996] Kernel command line: console=ttyS0 BOOT_IMAGE=/boot/vmlinuz root=/dev/vda1 rw ds=nocloud;s=file://ubuntu-cloud4/
Cloud image: cloud-init/IMDS
La ventaja de utilizar un servidor IMDS(Instance Metadata Service) es que centralizaremos todos los ficheros de configuración, esto resulta útil para eliminar una máquina virtual en un nodo físico y redesplegarla en otro sin tener que copiar o regenerar los ficheros: meta-data/user-data/vendor-data.
Pero también impone una dependencia de los servidores DHCP/WebServer que pueden llegar a fallar(incluso montándolos en alta disponibilidad) situación que debería evitarse a toda costa cuando estamos hablando de servidores en producción, yo personalmente recomiendo utilizar la ISO semilla
ya que se puede scriptar fácilmente y no tiene dependencias.
Para poder utilizar un servidor IMDS debemos proceder del siguiente modo:
- Montar un servidor DHCP configurado de forma estática para que siempre asigne la misma dirección ip a la MAC de cada máquina virtual.
- Montar un servidor web IMDS que servirá los ficheros cloud-init.
- Arrancar la máquina virtual indicando la ip del servidor IMDS mediante el parámetro serial de SMBIOS : -smbios type=1,serial=ds=‘nocloud;s=http://10.0.2.2:8000/’ o mediante parámetros del kernel .
Mediante SMBIOS serial number:
Creamos la máquina virtual:
Generamos los ficheros a servir:
cd ~/IMDS/ubuntu-cloud5
cat << EOF > meta-data
instance-id: Alfaexploit/Ubuntu-CloudImg5
local-hostname: Ubuntu-CloudImg5
EOF
cat << EOF > user-data
#cloud-config
users:
# Following entry create user1 and assigns password specified in plain text.
# Please not use of plain text password is not recommended from security best
# practises standpoint
- name: user1
groups: sudo
sudo: ALL=(ALL) NOPASSWD:ALL
plain_text_passwd: PASSWORD
lock_passwd: false
# Following entry creates user2 and attaches a hashed passwd to the user. Hashed
# passwords can be generated with:
# python -c 'import crypt,getpass; print(crypt.crypt(getpass.getpass(), crypt.mksalt(crypt.METHOD_SHA512)))'
- name: user2
passwd: $PASS_HASH
lock_passwd: false
# Following entry creates user3, disables password based login and enables an SSH public key
- name: user3
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCiF8zV98gaV87zVDwhZ8IDxUXs3Xq0NKVT1HY2t3XMjMU6/Sv6WeMeKQtJn/Py5PPXhYkOQ92in5+70WE6fQPt0shH6Hh4ZwY8lKDboLh96XLiCsxZc/IMq0h86QeT2E7FjAXcGo/2gziSYvbnp2Z+JAe5uBm4HtV3419mlEmaRAXGtjQxqcnhoUsLj4gD53ak1H/KUro+GArVDPUoMdSsqxvL0BHGbbcFOIaA3H3GwnGp09bHBNd0fR+Ip6CHRlRIu9oohezOAU+/3ul+FFbZuVlJ8zssaaFOzxjwEhD/2Ghsae3+z6tkrbhEOYN7HvBDejK9WySeI0+bMRqfSaID kr0m@DirtyCow
lock_passwd: true
# Install some basic software
packages:
- htop
- screen
EOF
Arrancamos el servidor web:
python -m http.server 8000
vm-bhyve nos permite pasarle argumentos directamente a Bhyve mediante el parámetro
bhyve_options
, a su vez Bhyve permite configurar
variables desde CLI: -o
.
La variable que necesitamos modificar es
system.serial_number
, identificar la variable correcta ha sido un proceso de prueba/error.
Añadimos las opciones Bhyve:
bhyve_options="-o system.serial_number=ds=nocloud;s=http://192.168.69.4:8000/ubuntu-cloud5/"
EOF
Arrancamos la máquina virtual:
En la consola del servidor web veremos las peticiones recibidas:
Serving HTTP on :: port 8000 (http://[::]:8000/) ...
::ffff:192.168.69.233 - - [27/Sep/2023 22:33:36] "GET /ubuntu-cloud5/meta-data HTTP/1.1" 200 -
::ffff:192.168.69.233 - - [27/Sep/2023 22:33:36] "GET /ubuntu-cloud5/user-data HTTP/1.1" 200 -
::ffff:192.168.69.233 - - [27/Sep/2023 22:33:36] "GET /ubuntu-cloud5/vendor-data HTTP/1.1" 200 -
Los usuarios se habrán generado tal y como indican los ficheros de configuración, además podemos ver la información inyectada en SMBIOS:
System Information
Manufacturer: FreeBSD
Product Name: BHYVE
Version: 1.0
Serial Number: ds=nocloud;s=http://192.168.69.4:8000/ubuntu-cloud5/
UUID: 9ce6a5f8-5d74-11ee-b808-3497f636bf45
Wake-up Type: Power Switch
SKU Number: None
Family: Virtual Machine
Mediante parámetros del kernel:
Creamos la máquina virtual:
Generamos los ficheros a servir:
cd ~/IMDS/ubuntu-cloud6
cat << EOF > meta-data
instance-id: Alfaexploit/Ubuntu-CloudImg6
local-hostname: Ubuntu-CloudImg6
EOF
cat << EOF > user-data
#cloud-config
users:
# Following entry create user1 and assigns password specified in plain text.
# Please not use of plain text password is not recommended from security best
# practises standpoint
- name: user1
groups: sudo
sudo: ALL=(ALL) NOPASSWD:ALL
plain_text_passwd: PASSWORD
lock_passwd: false
# Following entry creates user2 and attaches a hashed passwd to the user. Hashed
# passwords can be generated with:
# python -c 'import crypt,getpass; print(crypt.crypt(getpass.getpass(), crypt.mksalt(crypt.METHOD_SHA512)))'
- name: user2
passwd: $PASS_HASH
lock_passwd: false
# Following entry creates user3, disables password based login and enables an SSH public key
- name: user3
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCiF8zV98gaV87zVDwhZ8IDxUXs3Xq0NKVT1HY2t3XMjMU6/Sv6WeMeKQtJn/Py5PPXhYkOQ92in5+70WE6fQPt0shH6Hh4ZwY8lKDboLh96XLiCsxZc/IMq0h86QeT2E7FjAXcGo/2gziSYvbnp2Z+JAe5uBm4HtV3419mlEmaRAXGtjQxqcnhoUsLj4gD53ak1H/KUro+GArVDPUoMdSsqxvL0BHGbbcFOIaA3H3GwnGp09bHBNd0fR+Ip6CHRlRIu9oohezOAU+/3ul+FFbZuVlJ8zssaaFOzxjwEhD/2Ghsae3+z6tkrbhEOYN7HvBDejK9WySeI0+bMRqfSaID kr0m@DirtyCow
lock_passwd: true
# Install some basic software
packages:
- htop
- screen
EOF
Arrancamos el servidor web:
python -m http.server 8000
Añadimos las opciones Bhyve:
grub_run_partition="gpt1"
grub_run0="linux /boot/vmlinuz root=/dev/vda1 rw ds='nocloud;s=http://192.168.69.4:8000/ubuntu-cloud6/'"
grub_run1="initrd /boot/initrd.img"
EOF
Arrancamos la máquina virtual:
vm console ubuntu-cloud6
Veremos en la consola del servidor web como llegan peticiones:
Serving HTTP on :: port 8000 (http://[::]:8000/) ...
::ffff:192.168.69.234 - - [27/Sep/2023 22:36:07] "GET /ubuntu-cloud6/meta-data HTTP/1.1" 200 -
::ffff:192.168.69.234 - - [27/Sep/2023 22:36:07] "GET /ubuntu-cloud6/user-data HTTP/1.1" 200 -
::ffff:192.168.69.234 - - [27/Sep/2023 22:36:07] "GET /ubuntu-cloud6/vendor-data HTTP/1.1" 200 -
Podemos ver que ha generado los usuarios tal y como le hemos indicado, además podemos consultar los parámetros recibidos por el kernel:
[ 0.039752] Kernel command line: console=ttyS0 BOOT_IMAGE=/boot/vmlinuz root=/dev/vda1 rw ds=nocloud;s=http://192.168.69.4:8000/ubuntu-cloud6/
Firewall
Lo mas sencillo es deshabilitar el filtrado de tráfico en todos los bridges del host padre y filtrar de forma específica dentro de cada VM:
net.inet.ip.forwarding=1 # Enable IP forwarding between interfaces
net.link.bridge.pfil_onlyip=0 # Only pass IP packets when pfil is enabled
net.link.bridge.pfil_bridge=0 # Packet filter on the bridge interface
net.link.bridge.pfil_member=0 # Packet filter on the member interface
Wifi
Si queremos conectar las VMs por wifi se complica un poco ya que tendremos que NATear una red interna de VMs.
Primero habilitamos el forwarding:
sysctl net.inet6.ip6.forwarding=1
Nos aseguramos de que si reiniciamos, siga habilitado:
sysrc ipv6_gateway_enable=YES
Definimos las reglas de
PF
/
PF_NAT
:
ext_if = "wlan0"
int_if = "vm-public:"
localnet = "10.0.0.0/8"
set block-policy return
scrub in on $ext_if all fragment reassemble
set skip on lo
nat on $ext_if from {$localnet} to any -> ($ext_if)
antispoof for $ext_if inet
block log all
pass out
pass in
Habilitamos el servicio PF:
sysrc pf_rules="/etc/pf.conf"
sysrc pflog_enable=YES
sysrc pflog_logfile="/var/log/pflog"
Arrancamos PF:
Añadimos la interfaz wifi al bridge:
Configuramos una ip de la red interna al bridge, esta actuará como gateway de las VMs:
Comprobamos que todo esté en orden:
NAME TYPE IFACE ADDRESS PRIVATE MTU VLAN PORTS
public standard vm-public 10.0.0.1/8 no - - wlan0
Creamos la VM:
Instalamos el sistema operativo:
Seguimos los pasos del instalador:
La configuración de red será la siguiente:
IP: 10.0.0.2
GW: 10.0.0.1
DNS: 8.8.8.8,1.1.1.1
Cuando termine la instalación, reiniciamos y accedemos a la VM:
GRUB no arranca ya que /boot no se instaló en: hd0, partition 1
https://github-wiki-see.page/m/churchers/vm-bhyve/wiki/Configuring-Grub-Guests
Desde la interfaz de Grub vamos a averiguar que partición es la correcta:
(hd0) (hd0,gpt3) (hd0,gpt2) (hd0,gpt1) (host) (lvm/ubuntu--vg-ubuntu--lv)
Podemos listar el contenido de la partición:
lost+found/ config-5.15.0-84-generic initrd.img.old vmlinuz.old grub/ System.ma
p-5.15.0-84-generic vmlinuz-5.15.0-84-generic initrd.img vmlinuz initrd.img-5.1
5.0-84-generic
Ahora que ya sabemos la partición donde está el kernel, la partición raíz y la localización del initramfs, procedemos con el arranque:
initrd (hd0,gpt2)/initrd.img
boot
Si arranca correctamente apagamos el sistema operativo para configurarlo desde Bhyve sin tener que estar tocando el Grub en cada arranque:
grub_run_partition="gpt2"
grub_run0="linux /vmlinuz root=/dev/mapper/ubuntu--vg-ubuntu--lv rw"
grub_run1="initrd /initrd.img"
Ya podemos acceder sin problemas por SSH:
Por supuesto la VM saldrá NATeada por la interfaz wifi.
Troubleshooting
Si estamos haciendo pruebas y cerrando las conexiones al puerto serie de forma incorrecta(cerrando el terminal), veremos que se acumulan procesos ‘cu’ y cuando conectamos de nuevo por puerto serie aparecerán carácteres extraños, para solventarlo mataremos los ‘cus’ huérfanos, yo suelo matarlos todos y reconectar:
vm console VM_NAME
Si queremos probar la configuración de cloud-init con Qemu tal como indica la
documentación oficial
, podemos hacerlo:
qemu-system-x86_64 -net nic -net user -m 2048 -nographic -hda /zroot/vm/.img/ubuntu-22.04-server-cloudimg-amd64.img -smbios type=1,serial=ds=‘nocloud;s=http://192.168.69.4:8000/ubuntu-cloud3/’
Si tenemos algún problema con el arranque o con los parámetros pasados a Bhyve, podemos chequear los siguientes ficheros de log:
cat /zroot/vm/ubuntu-cloud3/grub.cfg
Si queremos debugear todavía mas el arranque, habilitamos el debug:
debug="yes"
cat /zroot/vm/ubuntu-cloud3/bhyve.log
Si tenemos algún problema con la imagen semilla
podemos acceder a su contenido mediante los siguientes comandos:
cp seed.iso decompressed
cd decompressed
tar xzf seed.iso
Podemos comprobar que cloud-init se haya ejecutado correctamente:
cloud-init query userdata
cloud-init schema –system
También podemos re-ejecutar cloud-init manualmente:
cloud-init -d init
cloud-init -d modules --mode final
En algunas ocasiones las máquinas virtuales no se paran, le damos botonazo:
En algunas ocasiones las máquinas virtuales terminan en estado “Locked”:
NAME DATASTORE LOADER CPU MEMORY VNC AUTOSTART STATE
kali00 default uefi 1 2048M 0.0.0.0:5900 No Locked (odyssey.alfaexploit.com)
Para solventarlo primero debemos eliminar el fichero run.lock:
Ahora aparecerá como “Stopped”:
NAME DATASTORE LOADER CPU MEMORY VNC AUTOSTART STATE
kali00 default uefi 1 2048M - No Stopped
Si intentamos eliminarla y nos causa problemas, tendremos que liberar los recursos desde bhyvectl:
/usr/local/sbin/vm: WARNING: kali00 appears to be running locally (vmm exists)
Liberamos los recursos:
La función de bhyvectl --destroy es:
After the VM has been shutdown, its resources can be reclaimed
Incluso con los recursos liberados puede que el dataset ZFS se resista:
NAME DATASTORE LOADER CPU MEMORY VNC AUTOSTART STATE
kali00 default uefi 1 2048M - No Stopped
Are you sure you want to completely remove this virtual machine (y/n)? y
/usr/local/sbin/vm: ERROR: failed to destroy ZFS dataset zroot/vm/kali00
En tal caso lo eliminamos manualmente:
Finalmente eliminamos la máquina virtual:
Are you sure you want to completely remove this virtual machine (y/n)? y
Comprobamos que no quede rastro de ella:
NAME DATASTORE LOADER CPU MEMORY VNC AUTOSTART STATE