Esta pagina se ve mejor con JavaScript habilitado

Máquinas virtuales bhyve bajo vm-bhyve

 ·  🎃 kr0m

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

Instalamos todo lo necesario:

pkg install vm-bhyve qemu-tools bhyve-firmware grub2-bhyve cdrkit-genisoimage tmux

Creamos un pool ZFS donde se almacenarán las máquinas virtuales:

zfs create zroot/vm

Habilitamos el servicio y le indicamos donde estarán alojadas las máquinas virtuales:

sysrc vm_enable="YES"
sysrc vm_dir="zfs:zroot/vm"

Inicializamos vm-bhyve:

vm init

Copiamos los templates de ejemplo:

cp /usr/local/share/examples/vm-bhyve/* /zroot/vm/.templates/

Creamos un bridge para conectar las máquinas virtuales con la interfaz de red física:

vm switch create public

Añadimos la interfaz física al switch:

vm switch add public bge0

Podemos ver los bridges disponibles:

vm switch list

NAME    TYPE      IFACE      ADDRESS  PRIVATE  MTU  VLAN  PORTS
public  standard  vm-public  -        no       -    -     bge0

Cuando arranquemos una máquina virtual la interfaz tap correspondiente a la máquina se añadirá de forma automática al bridge.

Nos bajamos la imágenes de los sistemas operativos que queramos instalar, en mi caso Kali-linux:

ls -la /zroot/vm/.iso/kali-linux-2022.1-installer-amd64.iso
-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:

vm create -c 2 -m 2G -s 15G -t linux-zvol kali

Instalamos desde la ISO:

vm install -f kali kali-linux-2022.1-installer-amd64.iso

Veremos el instalador en la propia consola:

Podemos consultar información de la máquina virtual mediante el siguiente comando:

vm info kali

------------------------
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:

vm list

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:

vm create -c 2 -m 2G -s 15G -t linux-zvol kali2

La configuramos para que el loader sea uefi y definimos los parámetros de configuración VNC:

vm configure kali2

loader="grub" -> loader="uefi"
graphics="yes"
graphics_listen="192.168.69.180"
graphics_port="5900"
graphics_res="1600x900"
graphics_wait="yes" --> Deja la VM en pausa hasta que conectemos por VNC, brutal

Iniciamos la instalación:

vm install kali2 kali-linux-2022.1-installer-amd64.iso

Arrancamos la máquina:

vm start kali2

Al consultar las máquinas virtuales veremos que esta última tiene asociado un socket VNC:

vm list

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)

Si tenemos el tráfico de red filtrado por IPFW las reglas de acceso al socket VNC serían:

# BHYVE VNC installation
$cmd 01311 allow tcp from $LAN to me 5900 in via $wanif
$cmd 01311 allow tcp from me 5900 to $LAN out via $wanif

Accedemos al instalador vía VNC:

vinagre 192.168.69.180:5900


Cloud image

Creamos un fichero con las pubkeys a autorizar en la máquina virtual:

mkdir pubkeys
vi pubkeys/kr0m.pub

Desgraciadamente para Kali solo hay una imagen cloud compatible con Amazon EC2 por lo tanto no arrancará, se queda en el bootloader:

kali3  default    grub    2    2G      -                    No         Bootloader (42532)

Para hacer la prueba nos bajamos la versión cloud de Ubuntu:

Podemos ver la imagen descargada:

vm img

DATASTORE           FILENAME
default             ubuntu-22.04-server-cloudimg-amd64.img

Creamos la máquina virtual:

vm create -c 2 -m 2G -s 15G -t linux-zvol -i ubuntu-22.04-server-cloudimg-amd64.img -C -k ~/pubkeys/kr0m.pub ubuntu-cloud

No sabemos que dirección ip va a recibir por DHCP por lo tanto no podemos definir reglas de IPFW, además no he sido capaz de permitir el tráfico DHCP, por lo tanto deshabilitamos el firewall durante el arranque de la máquina:

service ipfw stop

NOTA: No recomiendo este tipo de imágenes ya que el firewall estará deshabilitado un momento, pero puede llegar a ser problemático en algunos escenarios.

Arrancamos la imagen:

vm start ubuntu-cloud

El problema de estas imágenes es que el cloud-ini de vm-bhyve está muy limitado tan solo nos deje meter las keys ssh, pero no asignar un ip estática, por lo tanto la VM obtendrá una por DHCP que tendremos que descubrir:

  • Consultando la MAC de la VM: vm info ubuntu-cloud y accediendo al servidor DHCP para consultar los logs.
  • Realizando un nmap ping antes y después de arrancar la VM para comparar si hay alguna ip nueva activa.

Una vez arrancada ya podemos volver a levantar el forewall:

service ipfw start

Accedemos vía ssh ya que nuestra pubkey debe estar autorizada:

Consultamos la información básica:

ubuntu@ubuntu-cloud:~$ uname -a

Linux ubuntu-cloud 5.15.0-27-generic #28-Ubuntu SMP Thu Apr 14 04:55:28 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

El siguiente paso sería reconfigurar la red con la ip estática y asignar reglas de IPFW.


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:

vi /etc/sysctl.conf

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

Pero si nos empeñamos en filtrar en el padre también podemos hacerlo(no haría falta modificar el /etc/sysctl.conf) pero las reglas de IPFW se convierten en un infierno, como podemos ver el tráfico de red debe atravesar distintas interfaces ya que el flujo de datos desde internet a la máquina virtual es:

INET -> IN-WAN -> IN-BRIDGE-OUT -> IN-TAP-OUT -> eth0-VM

Mientras que desde la máquina virtual a internet es:

eth0-VM -> IN-TAP-OUT -> IN-BRIDGE-OUT -> WAN-OUT -> INET

En el siguiente ejemplo vemos como permitir tráfico saliente y tráfico entrante:

wanif="bge0"
bhyvebridge="vm-public"

# INET -> IN-WAN -> IN-BRIDGE-OUT -> IN-TAP-OUT -> eth0-VM
# eth0-VM -> IN-TAP-OUT -> IN-BRIDGE-OUT -> WAN-OUT -> INET

VM_NAME=kali
VM_IP=192.168.69.170
tapif=$(/usr/local/sbin/vm info $VM_NAME | grep 'active-device'|awk '{print$2}')

# Allow outgoing HTTP
$cmd 01303 allow tcp from $VM_IP to any 80 in via $tapif
$cmd 01303 allow tcp from $VM_IP to any 80 out via $tapif
$cmd 01303 allow tcp from $VM_IP to any 80 in via $bhyvebridge
$cmd 01303 allow tcp from $VM_IP to any 80 out via $bhyvebridge
$cmd 01303 allow tcp from $VM_IP to any 80 out via $wanif

$cmd 01303 allow tcp from any 80 to $VM_IP in via $wanif
$cmd 01303 allow tcp from any 80 to $VM_IP in via $bhyvebridge
$cmd 01303 allow tcp from any 80 to $VM_IP out via $bhyvebridge
$cmd 01303 allow tcp from any 80 to $VM_IP in via $tapif
$cmd 01303 allow tcp from any 80 to $VM_IP out via $tapif

# Allow incoming 7777 port
$cmd 01310 allow tcp from any to $VM_IP 7777 in via $wanif
$cmd 01310 allow tcp from any to $VM_IP 7777 in via $bhyvebridge
$cmd 01310 allow tcp from any to $VM_IP 7777 out via $bhyvebridge
$cmd 01310 allow tcp from any to $VM_IP 7777 in via $tapif
$cmd 01310 allow tcp from any to $VM_IP 7777 out via $tapif

$cmd 01310 allow tcp from $VM_IP 7777 to any in via $tapif
$cmd 01310 allow tcp from $VM_IP 7777 to any out via $tapif
$cmd 01310 allow tcp from $VM_IP 7777 to any in via $bhyvebridge
$cmd 01310 allow tcp from $VM_IP 7777 to any out via $bhyvebridge
$cmd 01310 allow tcp from $VM_IP 7777 to any out via $wanif

Troubleshooting

En algunas ocasiones las máquinas virtuales terminan en estado “Locked”:

vm list

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:

rm /zroot/vm/kali00/run.lock

Ahora aparecerá como “Stopped”:

vm list

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:

vm destroy kali00

/usr/local/sbin/vm: WARNING: kali00 appears to be running locally (vmm exists)

Liberamos los recursos:

bhyvectl --vm kali00 --destroy

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:

vm list

NAME    DATASTORE  LOADER  CPU  MEMORY  VNC  AUTOSTART  STATE
kali00  default    uefi    1    2048M   -    No         Stopped
vm destroy kali00
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:

zfs destroy -r zroot/vm/kali00

Finalmente eliminamos la máquina virtual:

vm destroy kali00

Are you sure you want to completely remove this virtual machine (y/n)? y

Comprobamos que no quede rastro de ella:

vm list

NAME  DATASTORE  LOADER  CPU  MEMORY  VNC  AUTOSTART  STATE
Si te ha gustado el artículo puedes invitarme a un RedBull aquí