Páginas

Mostrando entradas con la etiqueta nfsv4. Mostrar todas las entradas
Mostrando entradas con la etiqueta nfsv4. Mostrar todas las entradas

martes, 1 de septiembre de 2026

Copia de Seguridad de un Conjunto de Datos ZFS Hacia un Dispositivo Remoto

Script de Replicación ZFS (zfsrep.sh)

shell zsh

Este script automatiza la copia de seguridad y replicación incremental de snapshots de ZFS desde el sistema local (solaris.local.com FreeBSD 14.4 ZFS) hacia un servidor remoto (tormenta.local.com FreeBSD 14.4 ZFS). Gestiona la creación de la snapshot diaria, el envío incremental a través de SSH y la purga automática de snapshots antiguas tanto en origen como en destino, respetando una retención de, (el valor de retention), 15 snapshots.

Descripción del funcionamiento

El script utiliza zfs send y zfs receive para transferir los datos. Detecta la última snapshot disponible en el destino para realizar un envío incremental (ahorrando ancho de banda), o realiza un envío completo si no encuentra una base común. Todo el proceso queda registrado en un archivo de log ubicado en /home/carlos/cronlog.

Contenido del script

#!/bin/sh
### BEGIN INFO
# PROVIDE:
# REQUIRE:
# KEYWORD:
### END INFO

### INICIO DEL GUION
# Estas variables se nombran primero porque están anidadas en otras variables.
snap_prefix=snap
retention=15

# Se necesitan rutas completas a estas utilidades al ejecutar el script desde cron.
date=/bin/date
cut=/usr/bin/cut
grep=/usr/bin/grep
sed=/usr/bin/sed
sort=/usr/bin/sort
xargs=/usr/bin/xargs
zfs=/sbin/zfs

src_0="zroot/home/carlos"
dst_0="zbackup/solaris/home/carlos"
host="root@192.168.88.160"
today="$snap_prefix-`$date +%Y%m%d`"
snap_today="$src_0@$today"

# Patrón grep con sufijo numérico para evitar falsos positivos.
snap_old=`$zfs list -t snapshot -o name \
  | $grep "${src_0}@${snap_prefix}-[0-9]" \
  | $sort -r \
  | $sed 1,${retention}d \
  | $sort \
  | $xargs -n 1`

# Detecta la snapshot más reciente del destino remoto para usarla como base.
# Todo el pipeline corre en tormenta vía SSH.
snap_base_name=`ssh $host "$zfs list -t snapshot -o name \
  | grep '${dst_0}@${snap_prefix}-[0-9]' \
  | sort -r \
  | sed 1q \
  | cut -d@ -f2"`
snap_base="${src_0}@${snap_base_name}"

# snap_old_dst_0 lista las snapshots > retention en el destino remoto.
snap_old_dst_0=`ssh $host "$zfs list -t snapshot -o name \
  | grep '${dst_0}@${snap_prefix}-[0-9]' \
  | sort -r \
  | sed 1,${retention}d \
  | sort"`

log=/home/carlos/cronlog

echo >> $log
echo "zfsrep.sh" >> $log
$date >> $log
echo >> $log

# Busca la instantánea de hoy en origen y, si no la encuentras, créala.
if $zfs list -H -o name -t snapshot \
  | $sort \
  | $grep "${snap_today}$" > /dev/null
then
	echo "La instantánea de hoy '$snap_today' ya existe." >> $log
else
	echo "Tomando la instantánea de hoy: $snap_today" >> $log
	$zfs snapshot -r $snap_today >> $log 2>&1
fi

echo >> $log

# Comprueba si la instantánea de hoy ya existe en el destino remoto.
# Si existe, no hay nada que enviar; saltar directamente a la purga.
if ssh $host "$zfs list -H -o name -t snapshot \
  | grep '${dst_0}@${today}$'" > /dev/null 2>&1
then
	echo "La instantánea de hoy ya existe en destino remoto. Nada que enviar." >> $log
else
	# Detecta la base del incremental y replica.
	# Permite recuperarse de huecos de sincronización sin intervención manual.
	if [ -n "$snap_base_name" ] && $zfs list -H -o name -t snapshot \
	  | $grep "${snap_base}$" > /dev/null
	then
		echo "Usando '$snap_base' como base del incremental..." >> $log
		$zfs send -R -i $snap_base $snap_today \
		  | ssh $host $zfs receive -vuF -x mountpoint $dst_0 >> $log 2>&1
		echo >> $log
		echo "Replicacion completada." >> $log
	else
		echo "No se encontró snapshot común entre origen y destino." >> $log
		echo "Realizando envío completo (puede tardar)..." >> $log
		$zfs send -R $snap_today \
		  | ssh $host $zfs receive -vuF -x mountpoint $dst_0 >> $log 2>&1
		echo >> $log
		echo "Envío completo finalizado." >> $log
	fi
fi

echo >> $log

# Eliminar instantáneas locales más antiguas que el valor asignado a $retention.
echo "Intentando destruir instantáneas antiguas $src_0..." >> $log

if [ -n "$snap_old" ]
then
	echo "Destruyendo las siguientes instantáneas antiguas:" >> $log
	echo "$snap_old" >> $log
	$zfs list -t snapshot -o name \
	  | $grep "${src_0}@${snap_prefix}-[0-9]" \
	  | $sort -r \
	  | $sed 1,${retention}d \
	  | $sort \
	  | $xargs -n 1 $zfs destroy -r >> $log 2>&1
else
	echo "No se pudo encontrar ninguna instantánea local para destruir." >> $log
fi

echo >> $log

# Eliminar instantáneas remotas más antiguas que el valor asignado a $retention.
# Todo el pipeline, incluido zfs destroy, corre en tormenta vía SSH.
echo "Intentando destruir instantáneas antiguas $dst_0..." >> $log

if [ -n "$snap_old_dst_0" ]
then
	echo "Destruyendo las siguientes instantáneas antiguas en $host:" >> $log
	echo "$snap_old_dst_0" >> $log
	ssh $host "$zfs list -t snapshot -o name \
	  | grep '${dst_0}@${snap_prefix}-[0-9]' \
	  | sort -r \
	  | sed 1,${retention}d \
	  | sort \
	  | xargs -n 1 $zfs destroy -r" >> $log 2>&1
else
	echo "No se pudo encontrar ninguna instantánea remota para destruir." >> $log
fi

echo "**********" >> $log

### FIN DEL SCRIPT

Estructura de zbackup (servidor)

carlos@tormenta:~ % zfs list -r zbackup
NAME                          USED  AVAIL  REFER  MOUNTPOINT
zbackup                      1008G   791G  92.5M  none
zbackup/dellhome             56.6G   791G  56.6G  none
zbackup/nfsv4                36.6G   791G    96K  none
zbackup/nfsv4/docs           7.80G   791G  7.80G  none
zbackup/nfsv4/secrets        28.8G   791G  28.8G  none
zbackup/recovery              755G   791G   104K  none
zbackup/recovery/solaris      282G   791G   282G  none
zbackup/recovery/tormenta     473G   791G   473G  none
zbackup/reserved               50G   841G    96K  none
zbackup/solaris              80.9G   791G    96K  none
zbackup/solaris/home         80.9G   791G    96K  none
zbackup/solaris/home/carlos  80.9G   791G  73.2G  none
zbackup/usr                   147M   791G   104K  none
zbackup/usr/home              147M   791G   140M  none

zfsrep.sh - Funcionamiento

Asigna rutas absolutas a los binarios (zfs, date, grep, etc.) para evitar problemas de $PATH al ejecutarse desde cron

date=/bin/date
cut=/usr/bin/cut
grep=/usr/bin/grep
sed=/usr/bin/sed
sort=/usr/bin/sort
xargs=/usr/bin/xargs
zfs=/sbin/zfs

Contexto

El script corre en solaris (cliente) y replica el dataset zroot/home/carlos hacia zbackup/solaris/home/carlos en tormenta (servidor), usando SSH como canal de transporte.

1 - Definición de variables

src_0="zroot/home/carlos"
dst_0="zbackup/solaris/home/carlos"
host="root@192.168.88.160"
today="snap-20260901"
snap_today="zroot/home/carlos@snap-fecha_de_hoy"

Se calculan los nombres de las snapshots y rutas antes de hacer nada. today se genera en este momento con $date, así que si el script corre a las 2:00 AM o a las 23:00 del mismo día, el nombre es idéntico - eso es lo que permite la comprobación de duplicado más adelante.

2 - Inventario de snapshots antiguas a purgar (local)

snap_old=`$zfs list -t snapshot -o name
  | $grep "${src_0}@${snap_prefix}-[0-9]"
  | $sort -r
  | $sed 1,${retention}d
  | $sort
  | $xargs -n 1`

Se calcula en este momento, antes de crear la snapshot de hoy. El pipeline:

Lista todas las snapshots del sistema Filtra las de zroot/home/carlos@snap- seguido de dígito Las ordena de más reciente a más antigua (sort -r) Elimina las primeras 15 líneas (sed 1,15d) - las 15 más recientes se conservan Lo que queda son las candidatas a destruir

Este cálculo es previo a la snapshot de hoy para que retention=15 sea exacto.

3 - Inventario remoto: base del incremental y purga

snap_base_name=`ssh $host "...pipeline en tormenta..."`
snap_old_dst_0=`ssh $host "...pipeline en tormenta..."`

Se abren dos conexiones SSH a tormenta para:

snap_base_name: la snapshot más reciente que tiene zbackup/solaris/home/carlos. Será el punto de partida del incremental. Si el destino tiene snap-20260831, el incremental enviará solo los cambios entre ese día y hoy. snap_old_dst_0: lista de snapshots en destino que superan retention=15, candidatas a purga remota.

Todo el pipeline de grep/sort/sed corre dentro del SSH, en tormenta, para que el resultado que llega a solaris sea solo texto limpio.

4 - Snapshot de hoy en origen

if zfs list ... | grep "${snap_today}$"
then
    "ya existe"
else
    zfs snapshot -r $snap_today
fi

Comprueba si zroot/home/carlos@snap-20260901 ya existe. El flag -r crea la snapshot de forma recursiva - si hubiera datasets hijo bajo zroot/home/carlos, también se snapshotearían. Si ya existe (segunda ejecución del día), simplemente lo anota en el log y continúa.

5 - Comprobación de duplicado en destino

if ssh $host "zfs list ... | grep '${dst_0}@${today}$'"
then
    "Nada que enviar"
else
    # continuar con el send
fi

Pregunta a tormenta: ¿ya tienes zbackup/solaris/home/carlos@snap-20260901? Si la respuesta es sí, el script salta directamente a la purga - no hay nada que transferir. Esto evita el reenvío innecesario si cron lanza el script más de una vez al día.

6 - Replicación incremental (o completa)

Caso normal - base común encontrada:

zfs send -R -i $snap_base $snap_today \
  | ssh $host zfs receive -vuF -x mountpoint $dst_0

zfs send genera un stream binario con los bloques que cambiaron entre snap_base y snap_today. Ese stream se pasa directamente por el pipe a SSH, que lo entrega a zfs receive en tormenta. No se escribe nada en disco en solaris - es un flujo directo en memoria.

Los flags:

-R: incluye snapshots hijas recursivamente
-i: incremental entre dos snapshots
-v: verbose en el log
-u: no monta el dataset recibido
-F: destruye en destino lo que no existe en el stream (mantiene coherencia)
-x mountpoint: ignora la propiedad mountpoint del stream — el destino mantiene mountpoint=none

Caso de hueco - sin base común:

zfs send -R $snap_today | ssh $host zfs receive -vuF -x mountpoint $dst_0

Si no hay ninguna snapshot común entre origen y destino, se hace un envío completo. Más lento, pero garantiza que el destino queda sincronizado desde cero.

7 - Purga local

zfs list -t snapshot -o name
  | grep "${src_0}@${snap_prefix}-[0-9]"
  | sort -r | sed 1,15d | sort
  | xargs -n 1 zfs destroy -r

Mismo pipeline que en el paso 2, pero esta vez ejecutando zfs destroy -r sobre cada snapshot antigua. El -r destruye también las snapshots hijas si las hubiera. Las 15 más recientes quedan intactas.

8 - Purga remota

ssh $host "zfs list ... | grep ... | sort -r | sed 1,15d | sort | xargs -n 1 zfs destroy -r"

Ídem pero en tormenta. Todo el pipeline - incluido el zfs destroy - corre dentro del SSH. Esto es crítico: si el destroy corriera en local, intentaría destruir zbackup/... en solaris, donde ese dataset no existe.

Entrada en cron para ejecución diaria a las 2:00 AM

 crontab -e
# Ejecución diaria de la copia de seguridad ZFS a las 2:00 AM
0 2 * * * /home/carlos/bin/zfsrep.sh
Guardar y salir

Que sea ejcutable

chmod +x /home/carlos/bin/zfsrep.sh

Comprobar que el script funciona manualmente antes de programarlo

sh -x /home/carlos/bin/zfsrep.sh

Ejemplo de crontab completo (para root)

SHELL=/bin/sh
PATH=/bin:/usr/bin:/sbin:/usr/sbin
MAILTO=root

# Ejecución diaria de la copia de seguridad ZFS a las 2:00 AM
0 2 * * * /home/carlos/bin/zfsrep.sh

Notas Finales

Robustez: La lógica de incremental/completo permite que el script se recupere automáticamente si se borra una instantánea intermedia.

Eficiencia: La mejora que evita reenvíos el mismo día ahorra ancho de banda y CPU.

Mantenimiento: La limpieza automática en origen y destino evita que el espacio se llene con instantáneas antiguas.

Portabilidad: Al usar rutas absolutas, el script funciona correctamente desde cron.

Con este sistema, tus datos están respaldados diariamente en un dispositivo remoto, con una política de retención clara y la capacidad de recuperación ante fallos de sincronización.

FreeBSD es genial!.

lunes, 19 de enero de 2026

Crear conjunto de datos cifrado ZFS NFSv4 (tls)

Conjunto de datos cifrado

=> Cifrado NFS sobre TLS <=

En el servidor (tormenta.local.com)

Crear el keyfile para cifrado automático

Generar un keyfile seguro de 32 bytes (requerido para keyformat=raw):

dd if=/dev/urandom of=/etc/zfs/secifrado.key bs=32 count=1
chmod 400 /etc/zfs/secifrado.key

Este archivo contiene la clave binaria. Respaldar en un lugar seguro, su pérdida implica perder permanentemente el acceso al dataset.

Creación del dataset padre ZFS (no cifrado)

zfs create -o mountpoint=/nfsv4 zroot/nfsv4

A continuación crear el dataset hijo cifrado

zfs create -o encryption=aes-256-gcm -o \
keyformat=raw -o keylocation=file:///etc/zfs/secifrado.key -o \
mountpoint=/nfsv4/secifrado zroot/nfsv4/secifrado

Utiliza AES-256-GCM (recomendado por seguridad y rendimiento). El comando cargará la clave automáticamente durante la creación.

Verificar con el comando siguiente

zfs get encryption,keyformat,keylocation,mountpoint zroot/nfsv4/secifrado
NAME                   PROPERTY     VALUE                          SOURCE
zroot/nfsv4/secifrado  encryption   aes-256-gcm                    -
zroot/nfsv4/secifrado  keyformat    raw                            -
zroot/nfsv4/secifrado  keylocation  file:///etc/zfs/secifrado.key  local
zroot/nfsv4/secifrado  mountpoint   /nfsv4/secifrado               local

Configurar el desbloqueo automático en el arranque

Script rc.d personalizado que cargará la clave y montará el dataset después de importar los pools ZFS, pero antes de iniciar los servicios NFS (evita la intervención manual)

vim /usr/local/etc/rc.d/zfskeys
#############################################################
#!/bin/sh

# PROVIDE: zfskeys
# REQUIRE: zfs
# BEFORE: mountd
# KEYWORD: 

. /etc/rc.subr

name=zfskeys
rcvar=zfskeys_enable
start_cmd="${name}_start"

zfskeys_start() {
    zfs load-key zroot/nfsv4/secifrado
    zfs mount -a  # Montar cualquier dataset despues de cargar la clave
}

load_rc_config $name
run_rc_command "$1"
#############################################################

Convertir el archivo en ejecutable y habilitarlo

chmod +x /usr/local/etc/rc.d/zfskeys
sysrc zfskeys_enable="YES"

Esto permite que la clave se cargue desde el keyfile automáticamente al arranque, asumiendo que zroot raíz no está cifrado y el keyfile es accesible al inicio.

En caso de que zroot esté cifrado, ajustar el keyfile a un dataset no cifrado.

Archivo /etc/exports

#############################################################
V4: /nfsv4 -tls
---
/nfsv4/docs 	    -maproot=root -network 192.168.88.0/24
/nfsv4/secifrado    -maproot=root 192.168.88.51
---
#############################################################

Recargar configuración y reiniciar servicios

service mountd reload
service nfsd restart
service tlsservd restart

En el cliente (solaris.local.com)

service tlsclntd restart

Montar el recurso compartido NFSv4 con tls

mkdir /nizs4
mount -t nfs -o nfsv4,tls tormenta:/secifrado /nizs4

Montaje automático agregar en /etc/fstab

tormenta:/secifrado  /nizs4   nfs   rw,nfsv4,tls,noatime,bg   0  0

Seguridad: El keyfile permite desbloqueo sin intervención, mejor proteger (permisos 400, backups). Si el servidor es sustraido, el keyfile en disco permite acceso, considera almacenarlo en un dispositivo externo removible.

Reiniciar el servidor, comprobar que el dataset está montado y accesible via NFS desde el cliente.

Si TLS falla, comprobar logs (dmesg | grep tls, /var/log/messages). Verificar que los puertos NFS (2049/TCP) están abiertos en el firewall PF. Para depuración usar mount -vv

zfs mount | grep "secifrado" 
nfsstat -m
---
tormenta:/secifrado on /nizs4
nfsv4,minorversion=2,tcp,resvport,tls,nconnect=1,hard,cto,sec=sys,acdirmin=3,\
acdirmax=60,acregmin=5,acregmax=60,nametimeo=60,negnametimeo=60,rsize=65536,\
wsize=65536,readdirsize=65536,readahead=1,wcommitsize=16777216,timeout=120,\
retrans=2147483647
---

Archivo /etc/exports

V4: /nfsv4 -tls
...
/nfsv4/secifrado     -maproot=root 192.168.88.51
FreeBSD es genial!.

martes, 16 de diciembre de 2025

Cifrado por Envoltorio (túnel) del tráfico NFSv4 con Wireguard FreeBSD 14.3

Implementar cifrado por envoltorio (Wireguard) y proteger conexiones NFS entre un servidor y su(s) cliente(s).

Objetivos:

NFSv4 puro y limpio (solo puerto 2049).
Acceso exclusivo a través del túnel WireGuard (cifrado punto a punto).
Imposible acceder a los recursos desde la LAN física (bloqueado por PF).
Montajes persistentes y estables en el cliente.
Todo seguro, mínimo y sin componentes innecesarios corriendo.

Cómo implementar el cifrado por envoltorio: WireGuard

El servidor NFSv4 corre FreeBSD 14.3 ZFS, red 192.168.88.0/24, dominio local.com, nombre de host tormenta.local.com, dirección IP 192.168.88.160, interfaz re0, cortafuegos PF.

El cliente es un portátil con sistema operativo FreeBSD 14.3 ZFS, dirección IP 192.168.88.51, nombre de host solaris.local.com, interfaz de red em0. El archivo /etc/exports actual

V4: /nfsv4 -tls
/nfsv4/dellhome     -maproot=root -network 192.168.88.0/24 
/nfsv4/poolrecovery -maproot=root -network 192.168.88.0/24 
/nfsv4/docs         -maproot=root -network 192.168.88.0/24 
/nfsv4/confsolaris  -maproot=root -network 192.168.88.0/24 
/nfsv4/conftormenta -maproot=root -network 192.168.88.0/24
root@tormenta:~ # zfs list | grep /nfsv4 
zroot/nfsv4 103G 212G 7.80G /nfsv4 
zroot/nfsv4/confsolaris 283M 212G 4.76M /nfsv4/confsolaris 
zroot/nfsv4/conftormenta 916K 212G 852K /nfsv4/conftormenta 
zroot/nfsv4/dellhome 56.6G 212G 56.6G /nfsv4/dellhome 
zroot/nfsv4/docs 7.80G 212G 7.80G /nfsv4/docs 
zroot/nfsv4/poolrecovery 1.88G 212G 1.88G /nfsv4/poolrecovery 

La idea es crear un túnel VPN punto a punto entre el servidor (tormenta.local.com, 192.168.88.160) y el cliente (solaris.local.com, 192.168.88.51). De esta forma, el tráfico NFS se enruta exclusivamente a través del túnel encriptado de WireGuard, y configurar el servidor NFS para que solo escuche en la IP del túnel (evitando accesos directos por la LAN).

Forzar el cifrado para NFS, ya que ambos hosts están en la misma red LAN (192.168.88.0/24). Usar IPs privadas para el túnel: 10.66.66.1/32 para el servidor y 10.66.66.2/32 para el cliente. No es necesario forwarding ni NAT, ya que es un túnel simple para cifrar el tráfico local.

Verificar que no haya conflictos con reglas PF existentes. Si se usa NFSv4 puro, no se necesita rpcbind ni mountd, lo que simplifica las cosas


Detener servicios TLS

root@tormenta:/home/carlos # service tlsservd status
tlsservd is running as pid 1278.
root@tormenta:/home/carlos # service tlsservd stop
Stopping tlsservd.
Waiting for PIDS: 1278.

root@solaris:~# service tlsclntd status
tlsclntd is running as pid 1596.
root@solaris:~# service tlsclntd stop
Stopping tlsclntd.
Waiting for PIDS: 1596, 1596.

Desactivar TLS

root@tormenta:/home/carlos # sysrc tlsservd_enable="NO"
tlsservd_enable: YES -> NO

root@solaris:~# sysrc tlsclntd_enable="NO"
tlsclntd_enable: YES -> NO

Instalar wireguard-tools en el servidor tormenta y el cliente solaris

pkg install wireguard-tools
sysrc wireguard_enable="YES"
sysrc wireguard_interfaces="wg0"

En el servidor tormenta Generar claves privadas y públicas

mkdir /usr/local/etc/wireguard
cd /usr/local/etc/wireguard

umask 077
wg genkey | tee server_priv.key | wg pubkey > server_pub.key
wg genkey | tee client_priv.key | wg pubkey > client_pub.key

ls -l /usr/local/etc/wireguard
-rw-------  1 root wheel  45 Dec 11 18:28 client_priv.key
-rw-r--r--  1 root wheel  45 Dec 11 18:28 client_pub.key
-rw-------  1 root wheel  45 Dec 11 18:27 server_priv.key
-rw-r--r--  1 root wheel  45 Dec 11 18:27 server_pub.key

Esto genera claves para servidor y cliente

Transferir las claves al cliente solaris (IP 192.168.88.51), usando scp

scp root@tormenta:/usr/local/etc/wireguard/client_priv.key /usr/local/etc/wireguard/client_priv.key
scp root@tormenta:/usr/local/etc/wireguard/client_pub.key /usr/local/etc/wireguard/client_pub.key

Asegurar permisos estrictos (chmod 600 *.key)

cd /usr/local/etc/wireguard/
chmod 600 *.key

ls -l /usr/local/etc/wireguard/
-rw-------  1 root wheel  45 Dec 11 18:30 client_priv.key
-rw-------  1 root wheel  45 Dec 11 18:31 client_pub.key

Configurar WireGuard en el servidor tormenta

Archivo /usr/local/etc/wireguard/wg0.conf

###########################################
[Interface]
Address = 10.66.66.1/32
PrivateKey = <contenido de server_priv.key>
ListenPort = 51820

[Peer]
PublicKey = <contenido de client_pub.key>
AllowedIPs = 10.66.66.2/32

Reemplazar las claves por las claves reales

[Interface]
Address = 10.66.66.1/32
PrivateKey = oPtSkHSk8zNe9d3Z6gExZro9Cf8BELoahgZDsrc/7mk=
ListenPort = 51820

# conexion con clientes
[Peer]
PublicKey = tuu6a6EAPrQTC0tPatWJx2qcS6fEPcg4Z0u/8vY1pTs= 
AllowedIPs = 10.66.66.2/32
###########################################

Habilitar e iniciar el servicio

sysrc wireguard_interfaces="wg0"
sysrc wireguard_enable="YES"
service wireguard start
|

Acvitar desactivar interfaz wg0

wg-quick up wg0
wg-quick down wg0

Comprobar con wg show, debería mostrar la interfaz wg0 levantada

root@tormenta:/home/carlos # wg show
interface: wg0
  public key: UYbDhuNsq0geqJdsVKThSnNLzdc2oHWgUT8Gb0G3VXU=
  private key: (hidden)
  listening port: 51820

peer: tuu6a6EAPrPTC0tPatWJx2qcS6fEPcg4Z0u/8vY1pTs=
  allowed ips: 10.66.66.2/32

Configurar WireGuard en el cliente (solaris)

Archivo /usr/local/etc/wireguard/wg0.conf
###########################################
[Interface]
Address = 10.66.66.2/32
PrivateKey = <contenido de client_priv.key>

[Peer]
PublicKey = <contenido de server_pub.key>
AllowedIPs = 10.66.66.1/32
Endpoint = 192.168.88.160:51820

Reemplazar con las claves reales

[Interface]
Address = 10.66.66.2/32
PrivateKey = sAvZnwh/8xrLLNTxBu75UfvUPB/AhFQTJsUGqYTfUU0=

[Peer]
PublicKey = UYbDhuNsq0geqWdsVKXhSnNLzdc2oHWgUT8Zb0G3VXU=
AllowedIPs = 10.66.66.1/32
Endpoint = 192.168.88.160:51820
###########################################
sysrc wireguard_interfaces="wg0"
sysrc wireguard_enable="YES"
service wireguard start

Levantar interfaz wg0

# wg-quick up wg0
[#] ifconfig wg create name wg0
[#] wg setconf wg0 /dev/stdin
[#] ifconfig wg0 inet 10.66.66.2/32 alias
[#] ifconfig wg0 mtu 1420
[#] ifconfig wg0 up
[#] route -q -n add -inet 10.66.66.1/32 -interface wg0
[+] Backgrounding route monitor

Comprobar wg show debería mostrar el peer conectado.

wg show
interface: wg0
  public key: tuu6a6EAPrPTC0tPatWJx2qcS6fEPcg4Z0u/8vY1pTs=
  listening port: 26481

peer: UYbDhuNsq0geqWdsVKThSnNLzdc2oHWgUT8Gb0G3VXU=
  endpoint: 192.168.88.160:51820
  allowed ips: 10.66.66.1/32
  

Reglas cortafuegos PF (tormenta)

# Regla PF Wireguard antes Anti-spoofing + bogons - permite solo desde el cliente
pass in quick on $lan_if proto udp from 192.168.88.51 to 192.168.88.160 port 51820 keep state
# permite todo dentro del tunel
pass in quick on wg0 keep state # <-- esta es la importante <-p>

Recargar PF

service pf reload 
pfctl -f /etc/pf.conf

Verificar ping, desde el cliente solaris

root@solaris:~# ping 10.66.66.1
PING 10.66.66.1 (10.66.66.1): 56 data bytes
64 bytes from 10.66.66.1: icmp_seq=0 ttl=64 time=0.463 ms
64 bytes from 10.66.66.1: icmp_seq=1 ttl=64 time=0.524 ms

--- 10.66.66.1 ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.463/0.494/0.524/0.031 ms

Verificar ping, desde el servidor tormenta

root@tormenta:~# ping -c2 10.66.66.2
PING 10.66.66.2 (10.66.66.2): 56 data bytes
64 bytes from 10.66.66.2: icmp_seq=0 ttl=64 time=0.429 ms
64 bytes from 10.66.66.2: icmp_seq=1 ttl=64 time=0.526 ms

--- 10.66.66.2 ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.429/0.478/0.526/0.049 ms

ping bidireccional exitoso.

Configurar el cortafuegos PF en el servidor (tormenta)

Para permitir el tráfico WireGuard (UDP puerto 51820), agregar reglas a /etc/pf.conf. Asume que ya tienes PF configurado.

pass in quick on re0 proto udp from 192.168.88.51 to 192.168.88.160 port 51820 keep state

Recargar PF

service pf reload
pfctl -p /etc/pf.conf

Configurar NFSv4 para que solo escuche en la IP del túnel


En el servidor (tormenta), asegurar NFSv4 puro en /etc/rc.conf

nsf_server_enable="YES"
nfsv4_server_enable="YES"
nfs_server_flags="-h 10.66.66.1" # Bind sólo a la IP del túnel
nfsuserd_enable="YES" # necesario si se usa dominio

Iniciar servicios

service nfsd start
service nfsuserd start

Es es para que nfsd sólo escuche en 10.66.66.1, (no en 192.168.88.160).

Deshabilitar rpcbind_enable y mountd_enable, no son necesarios para NFSv4 puro.

Realizar los cambios en /etc/exports para restringir accesos sólo al cliente del túnel
(cambiar de red a host -network a -host).

############################################
V4: /nfsv4
/nfsv4/dellhome -maproot=root 10.66.66.2
/nfsv4/poolrecovery -maproot=root 10.66.66.2
/nfsv4/docs -maproot=root 10.66.66.2
/nfsv4/confsolaris -maproot=root 10.66.66.2
/nfsv4/conftormenta -maproot=root 10.66.66.2
############################################

Esto permite solo desde 10.66.66.2 (el cliente vía túnel).

Reiniciar NFS y nfsuserd

service nfsd restart
service nfsuserd restart

Montar NFS en el cliente (solaris) a través del tŕunel


En el cliente montar usando la IP del túnel del servidor, por ejemplo:

mount -t nfs -o nfsv4 10.66.66.1:/docs /mnt/docs

Para montajes persistentes, se utiliza /etc/fstab

10.66.66.1:/poolrecovery     /nisc4    nfs     rw,nfsv4,noatime,bg    0  	0
10.66.66.1:/confsolaris      /nics4    nfs     rw,nfsv4,noatime,bg	  0  	0
10.66.66.1:/conftormenta     /nits4    nfs     rw,nfsv4,noatime,bg	  0  	0
10.66.66.1:/docs             /nids4    nfs     rw,nfsv4,noatime,bg    0  	0
10.66.66.1:/dellhome 	     /nihs4    nfs     ro,nfsv4,noatime,bg	  0  	0

Consideraciones adicionales

Asi queda el archivo /etc/rc.conf (solaris)

$ gsed -e '/^#/d' -e '/^\s*$/d'

syslogd_flags="-ss"
clear_tmp_enable="YES"
hostname="solaris.local.com"
gateway_enable="NO"
ifconfig_em0="inet 192.168.88.51 netmask 255.255.255.0"
defaultrouter="192.168.88.1"
background_dhclient=YES
defaultroute_delay=3
defaultroute_carrier_delay=3
sshd_enable="NO"
ntpd_enable="YES"
ntpd_sync_on_start="YES"
powerd_enable="YES"
powerd_flags="-n hadp"
loader_conf_files="/boot/loader.conf /boot/loader.conf.local"
boot_efi_enable="YES"
webcamd_enable="YES"
moused_port=/dev/ums0
moused_enable="YES"
dumpdev="NO"
zfs_enable="YES"
dbus_enable="YES"
kld_list="i915kms"
nfs_client_enable="YES"
nfsuserd_enable="YES"
nfsv4_client_enable="YES"
nfsv4_domain="local.com"
tlsclntd_enable="NO"
hostid_enable="YES"
hostid_file="/etc/hostid"
autofs_enable="YES"
devd_enable="YES"
devfs_system_ruleset="system"
pf_enable="NO"
pf_rules="/etc/pf.conf"
pflog_enable="YES"
pflog_logfile="/var/log/pflog"
pflog_flags=""
wireguard_enable="YES"
wireguard_interface="wg0"
sendmail_enable="NO"
sendmail_submit_enable="NO"
sendmail_outbound_enable="NO"
sendmail_msp_queue_enable="NO"
lightdm_enable="YES"
zerotier_enable="YES"
vm_dir="zfs:zroot/vm"
vm_list=""
vm_delay="3"
poudriered_enable="YES"
nginx_enable="YES"
memcached_enable="YES"
memcached_flags="-l localhost -m 8192"
jackett_enable="YES"
linux_enable="NO"
ubuntu_enable="YES"
Asi queda /etc/pf.conf
lan_if   = "re0"
lan_net  = "192.168.88.0/24"
lan_ip   = "192.168.88.160"  
ssh_port     = "{ 22 }"
vpn_if="wg0"

set block-policy return         # responde ICMP/TCP-RST a paquetes bloqueados
set ruleset-optimization none   # en FreeBSD 14 no usa "basic" ni "high-latency"
set skip on lo0

match in all scrub (no-df random-id max-mss 1440)
table <bruteforce> persist       # aquí meterá sshguard, pf-badhost, etc.

pass in quick on $lan_if from $lan_net to $lan_ip keep state
pass in quick on $lan_if proto udp from 192.168.88.51 to 192.168.88.160 port 51820 keep state
pass in quick on wg0 keep state # <-- esta es la importante

block in quick on ! $lan_if from $lan_net to any
block in quick on ! $lan_if from 192.168.0.0/16 to any
block in quick on ! $lan_if from 172.16.0.0/12 to any
block in quick on ! $lan_if from 10.0.0.0/8 to any
block in quick from any to { 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8, ::/128, 0.0.0.0/8 }
block in quick from <bruteforce>

pass quick on lo0all
pass out quick inet keep state
pass in on $lan_if inet proto icmp from $lan_net to $lan_ip icmp-type echoreq keep state
pass out on $lan_if inet proto icmp from $lan_ip to $lan_net icmp-type echorep keep state
pass in on $lan_if proto tcp from $lan_net to $lan_ip port $ssh_port flags S/SFRA keep state
pass quick on $lan_if proto { tcp udp } from $lan_net to $lan_ip port 53
pass out proto udp from any to port 123 keep state
block log all

Asi queda /etc/exports

$ gsed -e '/^#/d' -e '/^\s*$/d' /etc/exports
V4: /nfsv4
/nfsv4/dellhome		-maproot=root 10.66.66.2
/nfsv4/poolrecovery 	-maproot=root 10.66.66.2
/nfsv4/docs 		-maproot=root 10.66.66.2
/nfsv4/confsolaris 	-maproot=root 10.66.66.2
/nfsv4/conftormenta 	-maproot=root 10.66.66.2

Commprobaciones finales En el servidor tormenta no debe aparecer rpcbind ni statd

root@tormenta:/etc # ps aux | grep -E "(statd|rpcbind|nfs)"
root    1192   0.0  0.0 14140  2380  -  Is   09:34      0:00.00 nfsuserd: master (nfsuserd)
root    1194   0.0  0.0 14140  2384  -  I    09:34      0:00.02 nfsuserd: server (nfsuserd)
root    1195   0.0  0.0 14140  2384  -  S    09:34      0:00.02 nfsuserd: server (nfsuserd)
root    1196   0.0  0.0 14140  2380  -  I    09:34      0:00.00 nfsuserd: server (nfsuserd)
root    1197   0.0  0.0 14140  2384  -  I    09:34      0:00.01 nfsuserd: server (nfsuserd)
root    2398   0.0  0.0 13756  2472  -  Ss   12:23      0:00.01 nfsd: master (nfsd)
root    2399   0.0  0.0 13756  2756  -  S    12:23      0:00.08 nfsd: server (nfsd)

En el cliente solaris remontar directorios

root@solaris:~# umount /nisc4 /nics4 /nits4 /nids4 /nihs4
root@solaris:~# mount -a

root@solaris:~# mount | grep 10.66.66.1
10.66.66.1:/poolrecovery on /nisc4 (nfs, noatime, nfsv4acls)
10.66.66.1:/confsolaris on /nics4 (nfs, noatime, nfsv4acls)
10.66.66.1:/conftormenta on /nits4 (nfs, noatime, nfsv4acls)
10.66.66.1:/docs on /nids4 (nfs, noatime, nfsv4acls)

NFSv4 puro, solo puerto 2049 a través del túnel WireGuard, sin dependencias de rpcbind ni demonios extra.

FreeBSD es genial!.

sábado, 13 de diciembre de 2025

NFS sobre TLS FreeBSD 14.3

Configurar Cifrado NFS sobre TLS


El tráfico NFS está cifrado. Autenticación sigue siendo AUTH_SYS (UID/GID)

NFS sobre TLS proporciona cifrado en tránsito (cifrado del tráfico RPC/TLS) sin necesidad de Kerberos. Utiliza certificados X.509 (pueden ser auto-firmados para entornos locales).

Preparación (en el servidor y cliente) Resolución DNS correcta (tormenta.local.com -> 192.168.88.160)
El kernel en FreeBSD 14.3 soporta KTLS (está en Generic desde FreeBSD 13+)


En ambos dos editar /etc/rc.conf para habilitar los daemons TLS:

tlsservd_enable="YES"  # Demonio servidor TLS (necesario para mounts TLS)
tlsclntd_enable="YES"  # Demonio cliente TLS


En el servidor tormenta.local.com - 192.168.88.160


Instalar y configurar Unbound en el servidor


Generación y Colocación de Certificados


Generar los certificados y moverlos a la carpeta que tlsservd exige por defecto.

Crear directorio temporal para trabajar

mkdir -p /root/certs-work
cd /root/certs-work

Crear CA

openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj "/CN=HomeLabRootCA"

Crear Certificado Servidor (tormenta)

openssl genrsa -out server.key 4096
openssl req -new -key server.key -out server.csr -subj "/CN=tormenta.local.com"
openssl x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt

Crear Certificado Cliente (solaris) - se usará luego

openssl genrsa -out client.key 4096
openssl req -new -key client.key -out client.csr -subj "/CN=solaris.local.com"
openssl x509 -req -days 3650 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 02 -out client.crt

Instalacion en rutas por defecto del servidor


Crear el directorio

mkdir /etc/rpc.tlsservd

Copiar archivos con los nombres que FreeBSD espera:

cp server.crt /etc/rpc.tlsservd/cert.pem
cp server.key /etc/rpc.tlsservd/certkey.pem
cp ca.crt     /etc/rpc.tlsservd/cacert.pem

Crear lista de revocación vacía (necesaria por defecto)

touch /etc/rpc.tlsservd/crl.pem

Permisos estrictos

chmod 700 /etc/rpc.tlsservd
chmod 600 /etc/rpc.tlsservd/certkey.pem
chmod 444 /etc/rpc.tlsservd/cert.pem
chmod 444 /etc/rpc.tlsservd/cacert.pem

En el cliente (solaris.local.com) IP 192.168.88.51


Preparar directorios y certificados

mkdir /etc/rpc.tlsclntd

Copiar los archivos generados en tormenta hacia solaris y renombrarlos

scp tormenta.local.com:/root/certs-work/client.crt /etc/rpc.tlsclntd/cert.pem
scp tormenta.local.com:/root/certs-work/client.key /etc/rpc.tlsclntd/certkey.pem
scp tormenta.local.com:/root/certs-work/ca.crt /etc/rpc.tlsclntd/cacert.pem

Aplicar permisos estrictos

chmod 700 /etc/rpc.tlsclntd
chmod 600 /etc/rpc.tlsclntd/certkey.pem
chmod 444 /etc/rpc.tlsclntd/cert.pem
chmod 444 /etc/rpc.tlsclntd/cacert.pem
chmod 644 /etc/rpc.tlsclntd/crl.pem

client.crt Es el certificado público del cliente. Identifica quién es el cliente. client.key Es la clave privada del cliente. Nunca debe compartirse. Sirve para firmar la comunicación. cacert.pem Es el certificado de la Autoridad Certificadora. Sirve para que el cliente verifique que el servidor es confiable.

Es importante saber que cambiar la extensión del archivo no cambia su contenido, pero ayuda al software a entender qué formato esperar.

PEM (Privacy Enhanced Mail): Es el formato estándar de texto (Base64) que empieza con 
-----BEGIN CERTIFICATE-----

Verificar que la Clave Privada y el Certificado coinciden

Este es el paso más crítico. Ambos archivos deben tener el mismo "Módulo" (un identificador matemático único). Si los códigos hash que obtienes son idénticos, son pareja.

Ejecuta estos dos comandos:

# Extraer el módulo del certificado público
openssl x509 -noout -modulus -in cert.pem | openssl md5

# Extraer el módulo de la clave privada
openssl rsa -noout -modulus -in certkey.pem | openssl md5

Resultado esperado: Ambos comandos deben devolver exactamente la misma cadena de caracteres (ej. (stdin)= 68b19a0d...). Si son diferentes, esa clave no pertenece a ese certificado.

Verificar que el Certificado fue firmado por la CA

Ahora comprobaremos la cadena de confianza. Queremos confirmar que cert.pem es "hijo" legítimo de cacert.pem. Ejecutando

openssl verify -CAfile cacert.pem cert.pem
cert.pem: OK

Resultado esperado: Deberías ver un mensaje final que diga: cert.pem: OK

Comprobar validez de las fechas

openssl x509 -noout -dates -in cert.pem
notBefore=Dec  9 20:38:11 2025 GMT
notAfter=Dec  7 20:38:11 2035 GMT

Crear lista de revocación vacía (necesaria por defecto)

touch /etc/rpc.tlsservd/crl.pem

Modificar /etc/exports para requerir TLS

Solo la linea V4: puede tener opciones de seguridad.

V4: /nfsv4 -tls
/nfsv4/dellhome		-maproot=root -network 192.168.88.0/24
/nfsv4/poolrecovery 	-maproot=root -network 192.168.88.0/24
/nfsv4/docs 		-maproot=root -network 192.168.88.0/24
/nfsv4/confsolaris 	-maproot=root -network 192.168.88.0/24
/nfsv4/conftormenta 	-maproot=root -network 192.168.88.0/24

Aplicar cambios

service nfsuserd restart
service mountd restart
service nfsd restart

Cliente solaris.local.com (192.168.88.51)


Montar con TLS (temporal)

mount -t nfs -o nfsv4,tls tormenta.local.com:/docs /punto/de/montaje

Permanente (en /etc/fstab)

...
# nfs
tormenta:/poolrecovery    /nisc4    nfs     rw,nfsv4,tls,noatime,bg    	0  	0
tormenta:/confsolaris     /nics4    nfs     rw,nfsv4,tls,noatime,bg	0  	0
tormenta:/docs            /nids4    nfs     rw,nfsv4,tls,noatime,bg    	0  	0
tormenta:/dellhome 	  /nihs4    nfs     ro,nfsv4,tls,noatime,bg	0  	0
mount -a

Servidor - configuración de servicios /etc/rc.conf


NFS y RPC

nfs_server_enable="YES"
nfsv4_server_enable="YES"
nfsuserd_enable="YES"

TLS demonio (servidor)

sysrc tlsservd_enable="YES"

Habilitar Kernel TLS

sysctl kern.ipc.tls.enable=1
echo 'kern.ipc.tls.enable=1' >> /etc/sysctl.conf

Recomendación: La seguridad debe ser en capas (Defensa en profundidad): Firewall + Restricción de IP NFS + Certificados TLS.

sysrc statd_enable="YES"   # Para bloqueo de archivos (opcional pero recomendado)
sysrc lockd_enable="YES    # Para bloqueo de archivos 
sysrc mountd_flags="-r"    # Recarga automatica al compartir datasets

Iniciar servicios

service tlsservd start
service nfsuserd restart
service mountd restart
service nfsd restart

Cliente (solaris.local.com) Configuración (/etc/rc.conf)

nfs_client_enable="YES"
nfsv4_client_enable="YES"
nfsuserd_enable="YES"

# TLS demonio (cliente)
tlsclntd_enable="YES"

Iniciar servicios

service nfsclient start
service nfsuserd start

Habilitar Kernel TLS

sysctl kern.ipc.tls.enable=1
echo 'kern.ipc.tls.enable=1' >> /etc/sysctl.conf

Iniciar cliente

service nfsuserd start
service tlsclntd start

Prueba de montaje

mkdir -p /mnt/docs
mount -t nfs -o nfsv4,tls 192.168.88.160:/docs /mnt/docs

Verificar cifrado

root@solaris:~# nfsstat -m
tormenta:/docs on /nids4
nfsv4,minorversion=2,tcp,resvport,tls,nconnect=1,hard,cto,sec=sys,acdirmin=3,acdirmax=60,acregmin=5,acregmax=60,nametimeo=60,negnametimeo=60,rsize=65536,wsize=65536,readdirsize=65536,readahead=1,wcommitsize=16777216,timeout=120,retrans=2147483647

Debe aparecer tls en las banderas (flags)

nfsstat -m
root@solaris:~# nfsstat -m
tormenta:/poolrecovery on /nisc4
nfsv4,minorversion=2,tcp,resvport,tls,nconnect=1,hard,cto,sec=sys,acdirmin=3,acdirmax=60,acregmin=5,acregmax=60,nametimeo=60,negnametimeo=60,rsize=65536,wsize=65536,readdirsize=65536,readahead=1,wcommitsize=16777216,timeout=120,retrans=2147483647

tormenta:/confsolaris on /nics4
nfsv4,minorversion=2,tcp,resvport,tls,nconnect=1,hard,cto,sec=sys,acdirmin=3,acdirmax=60,acregmin=5,acregmax=60,nametimeo=60,negnametimeo=60,rsize=65536,wsize=65536,readdirsize=65536,readahead=1,wcommitsize=16777216,timeout=120,retrans=2147483647

tormenta:/docs on /nids4
nfsv4,minorversion=2,tcp,resvport,tls,nconnect=1,hard,cto,sec=sys,acdirmin=3,acdirmax=60,acregmin=5,acregmax=60,nametimeo=60,negnametimeo=60,rsize=65536,wsize=65536,readdirsize=65536,readahead=1,wcommitsize=16777216,timeout=120,retrans=2147483647

tormenta:/dellhome on /nihs4
nfsv4,minorversion=2,tcp,resvport,tls,nconnect=1,hard,cto,sec=sys,acdirmin=3,acdirmax=60,acregmin=5,acregmax=60,nametimeo=60,negnametimeo=60,rsize=65536,wsize=65536,readdirsize=65536,readahead=1,wcommitsize=16777216,timeout=120,retrans=2147483647

Regla simple:

tlsservd (TLS Server Daemon): Es para la máquina que OFRECE los archivos (el Servidor). Escucha las peticiones de cifrado entrantes.
tlsclntd (TLS Client Daemon): Es para la máquina que MONTA los archivos (el Cliente). Inicia la petición de cifrado hacia afuera.

Verificar con tcpdump que el tráfico NFS sobre TLS está cifrado

Una vez que el montaje NFS con TLS funciona correctamente, se puede confirmar que el tráfico está realmente cifrado ejecutando tcpdump en el servidor o en el cliente.

Comando básico (en servidor)

tcpdump -i re0 -nn port 2049

O más específico, ver sólo NFS

tcpdump -i re0 -nn port 2049 and host 192.168.88.51

Qué esperar

Si TLS está funcionado correctamente - cifrado activo Verás paquetes TCP en el puerto 2049 Después del handshake TLS inicial, todo el payload de los paquetes estará cifrado.

...
13:35:28.349050 IP 192.168.88.160.2049 > 192.168.88.51.773: Flags [P.], seq 469255:469785, ack 251188, win 29128, options [nop,nop,TS val 505937474 ecr 417283551], length 530
13:35:28.349301 IP 192.168.88.51.773 > 192.168.88.160.2049: Flags [.], ack 469785, win 4344, options 
...

No verás texto legible como nombre de archivos, rutas, ni datos de ficheros. Todo será binario cifrado.

Ejemplo típico de texto claro

NFS request xid ... read fh .../poolrecovery/backup-2025-12-10.gz offset 0 count 32768
Comando más detallado para ver el handshake TLS
tcpdump -i re0 -nn -s 0 -A port 2049
-i <interfaz>
-A imprime cada paquete en formato ASCII (crucial para leer contenido)
-n No convierte direcciones IP a nombres de host (más rápido)

En el cliente solaris echo 'Garantizar cifrado' > /nids4/prueba.txt no podrás ver ni el nombre del archivo ni su contenido.

...
14:10:09.830256 IP 192.168.88.160.2049 > 192.168.88.51.890: Flags [P.], seq 1:115, ack 162, win 29128, options [nop,nop,TS val 3871689747 ecr 1810578769], length 114
E....V..@.....X...X3...z.C........q.2......
...
tcpdump -i re0 -nn port 2049 -c 50

Genera activida en el cliente solaris

ls -ltr /nihs4
...
14:12:35.627829 IP 192.168.88.160.2049 > 192.168.88.51.698: Flags [.], ack 230, win 29128, options [nop,nop,TS val 1928012123 ecr 3657311778], length 0
14:12:35.628036 IP 192.168.88.160.2049 > 192.168.88.51.698: Flags [P.], seq 1:303, ack 230, win 
...

Si solo ves números de secuencia y longitudes, sin nombres de archivos -> cifrado Si ves rutas y operaciones NFS -> sin cifrado (TLS inactivo).

FreeBSD es genial!.

viernes, 11 de abril de 2025

Configuración de Zerotier Acceso a Servidor LAN vía SSH FreeBSD 14.2

Configuración para conectar clientes a un servidor FreeBSD via Zerotier y SSH

Instalación y configuración de Zerotier en el servidor FreeBSD

Instalación y puesta en marcha de Zerotier

 pkg install net/zerotier
 sysrc zerotier_enable=YES
 service zerotier start

Unirse a la Red Zerotier (192.168.192.0-24):

 zerotier-cli join <Network ID>

Reemplaza <Network ID> con el ID de tu red Zerotier (puedes verlo en Zerotier central).

Autorizar el servidor en Zerotier, aparecerá en la lista de miembros con su ID

Asigna una IP fija al servidor en la red Zerotier (por ej: 192.168.192.133).

Configuración del cortafuegos PF en el servidor FreeBSD

Habilitar IP Forwarding (Reenvío de paquetes):

echo "net.inet.ip.forwarding=1" >> /etc/sysctl.conf

Aplicar los cambios:

sysctl net.inet.ip.forwarding=1
net.inet.ip.forwarding: 1

Reglas PF (/etc/pf.conf)

# Macros
wan_if = "re0"               # Interfaz conectada al MikroTik (LAN)
lan_net = "192.168.88.0/24"  # Red local
zt_if = "zt1ocu1pr8e2sac"    # Interfaz ZeroTier
zt_net = "192.168.192.0/24"  # Red ZeroTier

# Opciones generales
set block-policy drop
set skip on { lo0, $zt_if }  # ZeroTier no necesita filtrado estricto
scrub in all fragment reassemble

# Reglas predeterminadas
block all
pass out quick keep state

# Permitir tráfico entre redes LAN y ZeroTier
pass quick from $lan_net to $zt_net
pass quick from $zt_net to $lan_net

# Permitir servicios desde la LAN (Samba, NFSv4, SSH)
pass in on $wan_if proto { tcp, udp } from $lan_net to any port { 139, \
445, 111, 2049, 22 }

# Permitir SSH desde ZeroTier (clientes externos)
pass in on $zt_if proto tcp from $zt_net to ($wan_if) port 22

# Reglas para ZeroTier (puerto UDP 9993 y comunicación entre peers)
pass in on $zt_if proto udp from any to any port 9993

# Permitir ICMP (pings y mensajes de error)
pass in inet proto icmp all icmp-type { echoreq, unreach }

Configuración de clientes Zerotier:

Instalar Zerotier en los dispositivos cliente

Unirlos a la red Zerotier

zerotier-cli join <Network ID> o mediante la app

Autoriza los clientes en Zerotier y asígnales IPs fijas si es necesario

Acceso SSH al servidor

Conectarse desde un cliente usando la IP de Zerotier del servidor

 ssh usuario@192.168.192.133

Ruta estática entre la red local (192.168.88.0/24) y Zerotier (192.168.192.0/24)

¿Por qué es necesaria?

Los clientes en Zerotier (192.168.192.0/24) no saben como llegar a la red local del servidor (192.168.88.0/24) por defecto.

La ruta estática indica a Zerotier que el servidor actúa como puerta de enlace para la red local.

Configuración en Zerotier Central:

Ir a su red en Zerotier
En Advanced > Managed Routes,
añadir:

 - Destination: 192.168.88.0/24
 - Via: 192.168.192.133 (IP Zerotier del servidor)

Explicación Técnica

Zerotier como VPN Layer 2:

Crea una red virtual donde todos los dispositivos aparecen como si estuvieran en la misma LAN, evitando la necesidad de port forwarding

Ruta estática:

Dirige el tráfico desde Zerotier hacia la red local a través del servidor, que actúa como router.

Regla de Cortafuegos en Mikrotik

Agregar una regla que permita el tráfico SSH desde la red ZeroTier hacia el servidor FreeBSD

# En Mikrotik (RouterOS CLI)

/ip firewall filter add chaind=filter action=accept protocol=tcp \
dst-port=22 src-address=192.168.192.0/24 comment="Permitir SSH desde ZeroTier"

El servidor usa la IP de ZeroTier para SSH, asegúrese de que SSH está configurado para escuchar en esa IP

# En /etc/ssh/sshd_config
ListenAddress 192.168.192.133

Reiniciar SSH después

service sshd restart

Verificaión:

Desde un cliente Zerotier, por ej: desactivo la conexión wifi de mi teléfono móvil y activo los datos para conectarme desde una red externa:

 ssh carlos@192.168.192.133


Captura de tráfico

 tcpdump -i zt1ocu1pr8e2sac

Optimizar ZeroTier:

- En /usr/local/etc/zerotier-one/zerotier-one.conf, configura allowManaged y allowGlobal para controlar el acceso a rutas:

   {
     "settings": {
       "allowManaged": true,
       "allowGlobal": false
     }
   }

Diagrama de Flujo

Esta configuración garantiza que solo los servicios necesarios estén expuestos, y la comunicación entre redes se realice de manera segura y controlada.

FreeBSD es genial!.

jueves, 26 de octubre de 2023

NFSv4 FreeBSD 14.2

NFSv4 - NFS Version 4 Protocol FreeBSD 14.2

Como administrador de un servidor NFS, puede configurarlo para que sólo admita NFSv4, lo que minimiza el número de puertos abiertos y servicios en ejecución en el sistema.

Opcionalmente, también puede deshabilitar la escucha de las llamadas de protocolo RPCBIND, MOUNT, y NSM, que no son necesarias en el caso de sólo NFSv4.

En este momento, es posible habilitar NFSv3 y/o NFSv4 sobre TCP, pero no NFSv3 sobre UDP, de todas maneras, NFSv3 sobre UDP está en desuso. Esto significa que la opción "-u" No se puede especificar para nfs_server_flags en el archivo /etc/rc.conf.

sharenfs=off|on Controla si el sistema de archivos se comparte a través de NFS y que acciones van a ser utilzadas. Un sistema de archivos con una sharenfs=off es administrado con el comando exportfs y entradas en el archivo /etc/exports. Por ejemplo

zfs get sharenfs zroot/usr/backup/dellhome
NAME                       PROPERTY  VALUE     SOURCE
zroot/usr/backup/dellhome  sharenfs  off       local

El archivo exports especifica puntos de montaje remotos para el protocolo NFS. Cada linea especifica el punto de montaje e indicadores de exportacion dentro de un sistema de archivos del servidor local o la raiz del arbol NFSv4 para uno o mas hosts.

En el servidor NFSv4 archivo /etc/rc.conf

sysrc nfs_server_enable="YES"
sysrc nfsv4_server_enable="YES"
sysrc nfs_server_flags="-u -t -n 24"
sysrc hostid_enable="YES"
sysrc hostid_file="/etc/hostid"
sysrc rpcbind_enable="YES"
sysrc rpcbind_flags="-h 192.168.88.160"
sysrc mountd_enable="YES"
sysrc mountd_flags="-r -p 892"
sysrc statd_flags="-p 32766"
sysrc lockd_flags="-p 32767"

Iniciar servicio hostid

service hostid start

Parametros des sysctl (/etc/sysctl.conf)

sysctl vfs.nfs.enable_uidtostring="1"      # Hacer que NFS siempre envíe nombres de propietario numéricos
sysctl vfs.nfsd.enable_stringtouid="1"     # Habilitar nfsd para aceptar nombres de propietario numéricos
sysctl vfs.nfsd.server_max_nfsvers="4"     # Fuerza NFSv4 max
sysctl vfs.nfsd.server_min_nfsvers="4"     # Fuerza NFSv4 min
sysctl vfs.nfsd.issue_delegations="1"      # Habilita delegaciones (mejora rendimiento)
sysctl vfs.nfsd.enable_locallocks="1"      # Bloqueos locales (compatible con ZFS)
sysctl kern.ipc.somaxconn="1024"           # Max conexciones pendientes (por defecto 128)
sysctl net.inet.tcp.delayed_ack="0"        # Mejora rendimiento TCP

Hacer que los cambios sean permanentes

echo vfs.nfs.enable_uidtostring="1" >> /etc/sysctl.conf
echo vfs.nfsd.enable_stringtouid="1" >> /etc/sysctl.conf
echo vfs.nfsd.server_max_nfsvers="4" >> /etc/sysctl.conf
echo vfs.nfsd.server_min_nfsvers="4" >> /etc/sysctl.conf
echo vfs.nfsd.issue_delegations="1" >> /etc/sysctl.conf
echo vfs.nfsd.enable_locallocks="1" >> /etc/sysctl.conf
echo kern.ipc.somaxconn="1024" >> /etc/sysctl.conf
echo net.inet.tcp.delayed_ack="0" >> /etc/sysctl.conf

Usamos el archivo /etc/hosts como resolución de nombres en la red local

...
192.168.88.160		tormenta
192.168.88.51		solaris
...

Configurar PF para permitir el tráfico NFS

...
lan_net ="{ 192.168.88.0/24 10.10.10.0/24 }"
nfs_puertos="{ 111, 892, 2049 }"
statd_lockd_puertos="{ 32766, 32767 }"

pass in proto { tcp } from $lan_net to any port ssh
pass in proto { tcp udp } from $lan_net to any port $statd_lockd_puertos
pass in proto { tcp udp } from $lan_net to any port $samba_puertos
pass in quick proto { tcp udp } from $lan_net to any port $nfs_puertos
...

Reiniciar servicios

tormenta: # service nfsd restart
Stopping nfsd.
Waiting for PIDS: 1391 1394.
Starting nfsd.

tormenta: # service rpcbind restart
Stopping rpcbind.
Waiting for PIDS: 1285.
Starting rpcbind.

tormenta: # service mountd restart
Stopping mountd.
Waiting for PIDS: 1382.
Starting mountd.

Crear los conjunto de datos en el servidor


Configurar canmount=off en zroot/nfsv4 para convertirlo en un contenedor puro (sin montaje, reduce cruces de fs crossings):

zfs set canmount=off zroot/nfsv4
zfs create -o mountpoint=/nfsv4/dellhome -p zroot/nfsv4/dellhome
zfs create -o mountpoint=/nfsv4/poolrecovery -p zroot/nfsv4/poolrecovery
zfs create -o mountpoint=/nfsv4/docs -p zroot/nfsv4/docs
zfs create -o mountpoint=/nfsv4/desarrollo -p zroot/nfsv4/desarrollo
zfs create -o mountpoint=/nfsv4/confsolaris -p zroot/nfsv4/confsolaris
zfs create -o mountpoint=/nfsv4/conftormenta -p zroot/nfsv4/conftormenta

Archivo de ejemplo /etc/exports, directorios compartidos en redes LAN

tormenta: # cat /etc/exports
V4: /nfsv4 # Define /nfsv4 como raiz global
/nfsv4/dellhome       -maproot=root -network 192.168.88.0/24
/nfsv4/poolrecovery   -maproot=root -network 192.168.88.0/24
/nfsv4/docs           -maproot=root -network 192.168.88.0/24 network 10.10.10.0/24
/nfsv4/desarrollo     -maproot=root -network 192.168.88.0/24 network 10.10.10.0/24
/nfsv4/confsolaris    -maproot=root -network 192.168.88.0/24
/nfsv4/conftormenta   -maproot=root -network 192.168.88.0/24

Puertos a la escucha (en el servidor)

root@tormenta:~ # netstat -an | grep -i listen     
tcp6       0      0 *.2049                 *.*                    LISTEN     
tcp4       0      0 *.2049                 *.*                    LISTEN     
tcp4       0      0 *.892                  *.*                    LISTEN     
tcp6       0      0 *.892                  *.*                    LISTEN          
tcp4       0      0 *.111                  *.*                    LISTEN     
tcp6       0      0 *.111                  *.*                    LISTEN 
carlos@tormenta:~ % sockstat -l4 | grep -E 'nfsd|rpcbind|mountd'
root     nfsd        1391 5   tcp4   *:2049                *:*
root     mountd      1382 8   udp4   *:892                 *:*
root     mountd      1382 9   tcp4   *:892                 *:*
root     rpcbind     1285 9   udp4   *:111                 *:*
root     rpcbind     1285 10  udp4   *:898                 *:*
root     rpcbind     1285 11  tcp4   *:111                 *:*

Programar mantenimiento

# crontab -e
@daily /sbin/service mountd reload
@daily /sbin/service nfsd restart

Logs de errorres:

 tail /var/log/messages | grep nfs

Capturar tráfico NFS:

 tcpdump -i re0 port 2049 -w nfs-captura.pcap

Leer paquetes capturados del archivo nfs-captura.pcap

root@tormenta:~ # tcpdump -r nfs-captura.pcap
...
21:14:42.658371 IP solaris.960 > tormenta.nfsd: Flags [P.], seq 976297794:976297922, \
ack 2292617495, win 4352, options [nop,nop,TS val 2858673986 ecr 2537679775], length 128: \
NFS request xid 755722369 124 getattr fh 0,5/2
21:14:42.658440 IP tormenta.nfsd > solaris.960: Flags [.], ack 128, win 29128, options \
[nop,nop,TS val 2537739395 ecr 2858673986], length 0
21:14:42.658497 IP tormenta.nfsd > solaris.960: Flags [P.], seq 1:93, ack 128, win 29128, \
options [nop,nop,TS val 2537739395 ecr 2858673986], length 92: NFS reply xid 755722369 \
reply ok 88 getattr LNK 12231267145 ids 1/53 sz 0
21:14:42.658797 IP solaris.960 > tormenta.nfsd: Flags [.], ack 93, win 4352, options \
[nop,nop,TS val 2858673987 ecr 2537739395], length 0
...

Configurar cliente FreeBSD

cat /etc/hosts...
192.168.88.160		tormenta
192.168.88.51		solaris
...

Para que el servicion nfs client se inicie con el sistema

solaris:~ # sysrc nfs_client_enable=YES
solaris:~ # sysrc nfsv4_client_enable="YES"
solaris:~ # sysrc hostid_enable="YES"
solaris:~ # sysrc hostid_file="/etc/hostid"
solaris:~ # sysrc rpc_statd_enable="YES"
solaris:~ # sysrc rpc_lockd_enable="YES"

Iniciar servicio hostid

service hostid start

Iniciar el servicio nfsclient

solaris:~ # service nfsclient start
NFS access cache time=60
solaris:~ nc -v tormenta 2049
  Connection to tormenta 2049 port [tcp/nfsd] succeeded!
solaris:~ nc -v tormenta 111
Connection to tormenta 111 port [tcp/sunrpc] succeeded!

Montar manualmente un directorio compartido (en el cliente)

solaris:~ # mkdir /mids4
# mount -t nfs -o nfsv4 tormenta:/docs /mids4

Archivo /etc/fstab para un montaje permanente y automático

# Device	Mountpoint	FStype	Options		Dump	Pass#
/dev/gpt/efiboot0	/boot/efi	msdosfs	rw		2	2
/dev/nvd0p3	 none	  	swap	sw			0	0
# 
fdesc		/dev/fd			fdescfs	rw		0	0
#
proc			/proc		procfs	rw		0	0
# nfsv4 shared
tormenta:/poolrecovery     /nisc4    nfs     rw,nfsv4,noatime,bg    0  0
tormenta:/development      /nixs4    nfs     rw,nfsv4,noatime,bg    0  0
tormenta:/docs             /nids4    nfs     rw,nfsv4,noatime,bg    0  0
tormenta:/dellhome 	   /nihs4    nfs     ro,nfsv4,noatime,bg    0  0

Archivo /etc/auto_master

cat /etc/auto_master
#
# Automounter master map, see auto_master(5) for details.
#
/net		-hosts		-nobrowse,nosuid,intr

Configurar el montaje automatico para montar dinámicamente cuando alguien acceda al recurso compartido NFS.

echo 'autofs_enable="YES"' >> /etc/rc.conf
service automountd start

Vamos a comprobar que podemos escribir en el directorio

 cd /nids4
 vim script-while-files.sh 
#!/bin/sh
i=0
while [ $i -le 10 ]
do
	 echo 'creando archivos con contenido'
	i=$(($i+1))
	echo Hola > "file$i.txt"
done
:wq

 chmod +x script-while-files.sh
 ./script-while-files.sh
 ls
 file3.txt             file8.txt
file1.txt             file4.txt             file9.txt
file10.txt            file5.txt             script-while-files.sh
file11.txt            file6.txt
file2.txt             file7.txt

solaris:~ % mount

...
map -hosts on /net (autofs)
tormenta:/poolrecovery on /nisc4 (nfs, nfsv4acls)
tormenta:/dellhome on /nihs4 (nfs, read-only, nfsv4acls)
tormenta:/docs on /nids4 (nfs, nfsv4acls)
tormenta:/development on /nics4 (nfs, nfsv4acls)
...

Verificar PF y reglas en tiempo real

 pfctl -f /etc/pf.conf
 pfctl -s rules
FreeBSD es genial!.