Páginas

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

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!.

martes, 26 de agosto de 2025

Poudriere en FreeBSD ZFS 14.2

Instalación y configuración de poudriere FreeBSD 14.2 ZFS


Referencias bibliográficas:

https://freebsdfoundation.org/wp-content/uploads/2023/11/reushling_poudrier.pdf
https://www.digitalocean.com/community/tutorials/how-to-set-up-a-poudriere-build-system-to-create-packages-for-your-freebsd-servers


Intel core i5-8350U 1.7 GHz
Procesador 4 cores
Procesador 8 hilos
RAM 16 GB
SWAP 16 GB
NVMe Pcie 4.0

ZPOOL=zroot
ZROOTFS=/poudriere
BASEFS=/usr/local/poudriere

FreeBSD 14.2-RELEASE (amd64) instalado El dataset raíz de poudriere estará en zroot/poudriere y se montará en /usr/local/poudriere

Paquetes mínimos

pkg install -y poudriere \
	       git-lite
	       vim
	       screen
	       curl
	       ca_root_nss

Crear datasets ZFS.

Ventajas: posibilidad de hacer snapshots, rollback y compression por separado

zfs create -o mountpoint=/usr/local/poudriere -o compression=lz4 zroot/poudriere
zfs create zroot/poudriere/jails
zfs create zroot/poudriere/ports
zfs create zroot/poudriere/data
zfs create zroot/poudriere/data/packages
zfs create zroot/poudriere/data/logs

Archivo /usr/local/etc/poudriere.conf

ZPOOL=zroot
ZROOTFS=/poudriere
FREEBSD_HOST=ftp://ftp.freebsd.org
RESOLV_CONF=/etc/resolv.conf
BASEFS=/usr/local/poudriere
POUDRIERE_DATA=${BASEFS}/data
USE_TMPFS="wrkdir data localbase"
DISTFILES_CACHE=/usr/ports/distfiles
CHECK_CHANGED_OPTIONS=verbose
CHECK_CHANGED_DEPS=yes
CCACHE_DIR=/var/cache/ccache
ALLOW_MAKE_JOBS_PACKAGES="llvm* rust firefox gcc*"
PARALLEL_JOBS=2 # 2 hilos logicos
KEEP_OLD_PACKAGES=yes
MAX_JOBS=2
NOHANG_TIME=7200
NOCOLOR=no

Descargar y crear la jail base (14.2-RELEASE)

poudriere jail -c -j 142amd64 -v 14.2-RELEASE -a amd64

Esto descarga sets.txz y crea un dataset zroot/poudriere/jails/142amd64

Crear de nuevo arbol de puertos (quarterly)

poudriere ports -c -p default -m git+https -U \
https://git.FreeBSD.org/ports.git -B 2026Q1     

poudriere ports -l

PORTSTREE METHOD    TIMESTAMP           PATH
default   git+https 2025-08-24 18:50:19 /usr/local/poudriere/ports/default

poudriere jails -l

JAILNAME VERSION         ARCH  METHOD TIMESTAMP           PATH
142amd64 14.2-RELEASE-p5 amd64 http   2025-08-24 18:42:25 /usr/local/poudriere/jails/142amd64

Ajustes globales

Archivo /usr/local/etc/poudriere.d/142amd64-make.conf

# Opciones generales para la jaula 142amd64
DEFAULT_VERSIONS+=ssh=openssl
OPTIONS_UNSET+=DOCS NLS EXAMPLES IPV6
DISABLE_LICENSES=yes

Crear listas de paquetes

Archivo /usr/local/etc/poudriere.d/port-list.txt

security/sudo

Archivo de configuracion de /var/cache/ccache/ccache.conf

max_size = 5.0G
cache_dir = /var/cache/ccache
compression = true
compression_level = 1
sloppiness = file_stat_matches, time_macros, include_file_mtime
hash_dir = false
run_second_cpp = false

Primer bulk build

poudriere bulk -j 142amd64 -p default -f /usr/local/etc/poudriere.d/port-list.txt

Servidor de paquetes local (nginx)

pkg install nginx
sysrc nginx_enable=YES

Archivo de configuración de nginx

$ sed -e '/^[ ]*#/d' -e '/^$/d' /usr/local/etc/nginx/nginx.conf
    
    server {
        listen       80 default;
        server_name  localhost;
	    root /usr/local/share/poudriere/html;
        
        location /data {
            alias  /usr/local/poudriere/data/logs/bulk;
            autoindex on;
        }
	
	location /packages {
		root /usr/local/poudriere/data;
		autoindex on;
	}
}

Configurar pkg en la máquina anfitriona

mkdir /usr/local/etc/pkg/repos

Archivo /usr/local/etc/pkg/repos/poudriere.conf

poudriere {
  url: "http://127.0.0.1/packages/142amd64-default/",
  enabled = yes,
  mirror_type: "http",
  signature_type: "pubkey",
  pubkey: "/usr/local/etc/poudriere/poudriere.pub",
}

Generar clave (una sola vez)

Crear el directorio donde se guardarán las claves

mkdir -p /usr/local/etc/poudriere

Generar la clave privada (4096 bits)

openssl genrsa -out /usr/local/etc/poudriere/poudriere.key 4096
chmod 600 /usr/local/etc/poudriere/poudriere.key

Extraer la clave pública

openssl rsa -in /usr/local/etc/poudriere/poudriere.key -pubout -out \
/usr/local/etc/poudriere/poudriere.pub
chmod 644 /usr/local/etc/poudriere/poudriere.pub

Asegúrarse de que poudiere la use al firmar.

Añadir esta línea al archivo /usr/local/etc/poudriere.conf

PKG_REPO_SIGNING_KEY=/usr/local/etc/poudriere/poudriere.key

El archivo /usr/local/etc/poudriere.conf queda así:

ZPOOL=zroot
ZROOTFS=/poudriere
FREEBSD_HOST=ftp://ftp.freebsd.org
RESOLV_CONF=/etc/resolv.conf
BASEFS=/usr/local/poudriere
POUDRIERE_DATA=${BASEFS}/data
USE_TMPFS=all # usa RAM + swap para las builds
DISTFILES_CACHE=/usr/ports/distfiles
CHECK_CHANGED_OPTIONS=verbose
CHECK_CHANGED_DEPS=yes
ALLOW_MAKE_JOBS_PACKAGES="pkg ccache py* rust llvm*"
PARALLEL_JOBS=8 # 8 hilos logicos
PKG_REPO_SIGNING_KEY=/usr/local/etc/poudriere/poudriere.key

Firmar el repositorio con la clave nueva

poudriere bulk -j 142amd64 -p default -f /usr/local/etc/poudriere.d/port-list.txt

Monitorear durante la compilación


poudriere status

SET PORTS JAIL BUILD STATUS  QUEUE BUILT FAIL SKIP IGNORE FETCH REMAIN TIME     LOGS
- default 142amd64 2025-08-26_08h57m21s parallel_build  3  0  0  0 0 0  3 00:00:22 \
/usr/local/poudriere/data/logs/bulk/142amd64-default/2025-08-26_08h57m21s

poudriere jail -l

JAILNAME VERSION         ARCH  METHOD TIMESTAMP           PATH
142amd64 14.2-RELEASE-p5 amd64 http   2025-08-25 23:10:54 /usr/local/poudriere/jails/142amd64

poudriere ports -l

PORTSTREE METHOD    TIMESTAMP           PATH
default   git+https 2025-08-26 08:55:04 /usr/local/poudriere/ports/default
local     null      2025-08-24 16:41:27 /usr/local/poudriere/ports/default
root@solaris:/usr/local/etc/pkg/repos# poudriere 
FreeBSD.conf        packagesite.yaml    
poudriere.conf     

Logs en tiempo real

poudriere log -f -j 142amd64 -p default 

Configurar pkg en los clientes, (o en la misma máquina), para que use la clave pública

mkdir -p /usr/local/etc/pkg/repos/
$ cat /usr/local/etc/pkg/repos/poudriere.conf
poudriere {
  url: "http://127.0.0.1/packages/142amd64-default/",
  enabled = yes,
  mirror_type: "http",
  signature_type: "pubkey",
  pubkey: "/usr/local/etc/poudriere/poudriere.pub",
}

Tras esto, pkg install validará las firmas sin errores.

Comandos útiles de rescate. Limpiar paquetes obsoletos

poudriere pkgclean -j 142amd64 -p default -f port-list.txt

Destruir jail y volver a crear (por ejemplo, tras nueva RELEASE)

poudriere jail -d -j 142amd64
poudriere jail -c -j 142amd64 -v 14.3-RELEASE -a amd64

Actualizar paquetes

pkg update -f
Updating FreeBSD repository catalogue...
Fetching meta.conf: 100%    179 B   0.2kB/s    00:01    
Fetching data.pkg: 100%   10 MiB  10.4MB/s    00:01    
Processing entries: 100%
FreeBSD repository update completed. 35669 packages processed.
Updating poudriere repository catalogue...
Fetching meta.conf: 100%    179 B   0.2kB/s    00:01    
Fetching data.pkg: 100%    2 KiB   2.0kB/s    00:01    
Processing entries: 100%
poudriere repository update completed. 3 packages processed.
All repositories are up to date.
pkg upgrade
curl -I http://127.0.0.1/
HTTP/1.1 200 OK
Server: nginx/1.28.0
Date: Tue, 26 Aug 2025 12:07:38 GMT
Content-Type: text/html
Content-Length: 13036
Last-Modified: Sat, 09 Aug 2025 09:58:45 GMT
Connection: keep-alive
ETag: "68971bd5-32ec"
Accept-Ranges: bytes

Actualizar el árbol existente (lo mantiene, solo descarga nuevos commits)

poudriere ports -u -p default

Listo para compilar y servir paquetes locales con poudriere en FreeBSD 14.2


Opciones

Poudriere no guarda automáticamente las elecciones que se hacen al ejecutar poudriere options, pero le indicamos con la bandera -z myset que lo guarde.

Ejecutar una vez poudriere options para el port que quiera

Las opciones seleccionadas se guardan en /usr/local/etc/poudriere.d/myset-options

poudriere options -z myset -c security/sudo

el flag -c fuerza que aparezca el menú incluso si ya existen opciones guardadas.


Un directorio "myset-options" es generado automaticamente por make config

/usr/local/etc/poudriere.d/myset-options/

y dentro del directorio los directorios categoria_paquete

ls /usr/local/etc/poudriere.d/myset-options
converters_libiconv/ devel_autoconf/      devel_m4/            lang_tcl86/
databases_sqlite3/   devel_kyua/          lang_lua54/          security_sudo/
devel_atf/           devel_lutok/         lang_perl5.42/

y dentro de esos directorio un archivo llamado options contiene las opciones de configuración.


cat /usr/local/etc/poudriere.d/myset-options/security_sudo/options
# This file is auto-generated by 'make config'.
# Options for sudo-1.9.17p2
_OPTIONS_READ=sudo-1.9.17p2
_FILE_COMPLETE_OPTIONS_LIST=AUDIT DISABLE_AUTH DISABLE_ROOT_SUDO DOCS EXAMPLES \
INSULTS LDAP NLS NOARGS_SHELL OPIE PAM PYTHON SSL GSSAPI_BASE GSSAPI_HEIMDAL \
GSSAPI_MIT
OPTIONS_FILE_SET+=AUDIT
OPTIONS_FILE_UNSET+=DISABLE_AUTH
OPTIONS_FILE_UNSET+=DISABLE_ROOT_SUDO
OPTIONS_FILE_UNSET+=DOCS
OPTIONS_FILE_UNSET+=EXAMPLES
OPTIONS_FILE_SET+=INSULTS
OPTIONS_FILE_UNSET+=LDAP
OPTIONS_FILE_UNSET+=NLS
OPTIONS_FILE_UNSET+=NOARGS_SHELL
OPTIONS_FILE_UNSET+=OPIE
OPTIONS_FILE_SET+=PAM
OPTIONS_FILE_UNSET+=PYTHON
OPTIONS_FILE_SET+=SSL
OPTIONS_FILE_UNSET+=GSSAPI_BASE
OPTIONS_FILE_UNSET+=GSSAPI_HEIMDAL
OPTIONS_FILE_UNSET+=GSSAPI_MIT

...

Compilar paquetes

poudriere bulk -j 142amd64 -p default -z myset -f /usr/local/etc/poudriere.d/port-list.txt

Para que pkg use los paquetes que acaba de compilar poudriere necesitas:
- Que poudriere haya terminado de firmar y publicar los paquetes.
- Un repositorio local servido por nginx (u otro)
- Un fichero /usr/local/etc/pkg/repos/poudriere.conf correctamente escrito

fichero /usr/local/etc/pkg/repos/poudriere.conf

poudriere {
  url: "http://127.0.0.1/packages/142amd64-default/",
  # url = "file:///usr/local/poudriere/data/packages/142amd64-default",
  enabled = yes,
  mirror_type: "http",
  signature_type: "pubkey",
  pubkey: "/usr/local/etc/poudriere/poudriere.pub",
}
root@solaris:/usr/local/poudriere/data/packages/142amd64-default/All# pkg repo .
Creating repository in .: 100%
Packing files for repository: 100%
root@solaris:/usr/local/poudriere/data/packages/142amd64-default/All# ls
data.pkg            meta.conf           pkg-2.2.2.pkg
data.tzst           packagesite.pkg     pkgconf-2.4.3,1.pkg
meta                packagesite.tzst    sudo-1.9.17p2.pkg

Desactivar el repositorio oficial (opcional)

#
# To disable this repository, instead of modifying or removing this file,
# create a /usr/local/etc/pkg/repos/FreeBSD.conf file:
#
#   mkdir -p /usr/local/etc/pkg/repos
#   echo "FreeBSD: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf
#

FreeBSD: {
 url: "pkg+https://pkg.FreeBSD.org/${ABI}/quarterly",
# url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
 mirror_type: "srv",
 signature_type: "fingerprints",
 fingerprints: "/usr/share/keys/pkg",
 enabled: no
}

Otra opción. La prioridad define el orden en que se utilizan estos repositorios

Por defecto, se utiliza el repositorio upstream de FreeBSD.org. Pero como queremos que nuestros propios paquetes tengan precedencia, le daremos una prioridad más alta que cero para que use nuestro repositorio de paquetes primero.

Quedaría así:

#
# To disable this repository, instead of modifying or removing this file,
# create a /usr/local/etc/pkg/repos/FreeBSD.conf file:
#
#   mkdir -p /usr/local/etc/pkg/repos
#   echo "FreeBSD: { enabled: no }" > /usr/local/etc/pkg/repos/FreeBSD.conf
#

FreeBSD: {
 url: "pkg+https://pkg.FreeBSD.org/${ABI}/quarterly",
# url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest",
 mirror_type: "srv",
 signature_type: "fingerprints",
 fingerprints: "/usr/share/keys/pkg",
 enabled: yes
}

Archivo /usr/local/etc/pkg/repos/poudriere.conf

poudriere {
  url: "http://127.0.0.1/packages/142amd64-default/",
  # url = "file:///usr/local/poudriere/data/packages/142amd64-default",
  enabled = yes,
  priority: 23,
  mirror_type: "http",
  signature_type: "pubkey",
  pubkey: "/usr/local/etc/poudriere/poudriere.pub",
}

Veamos en acción

 pkg update
Updating FreeBSD repository catalogue...
FreeBSD repository is up to date.
Updating poudriere repository catalogue...
Fetching meta.conf: 100%    179 B   0.2kB/s    00:01    
Fetching data.pkg: 100%    2 KiB   2.0kB/s    00:01    
Processing entries: 100%
poudriere repository update completed. 3 packages processed.
All repositories are up to date.
root@solaris:/usr/local/etc/pkg/repos# pkg upgrade
Updating FreeBSD repository catalogue...
FreeBSD repository is up to date.
Updating poudriere repository catalogue...
poudriere repository is up to date.
All repositories are up to date.
Checking for upgrades (206 candidates): 100%
Processing candidates (206 candidates): 100%
The following 4 package(s) will be affected (of 0 checked):

New packages to be INSTALLED:
	p5-Crypt-URandom: 0.54 [FreeBSD]

Installed packages to be UPGRADED:
	nss: 3.115 -> 3.115.1 [FreeBSD]
	p5-Authen-SASL: 2.18 -> 2.1900 [FreeBSD]
	perl5: 5.40.2_2 -> 5.40.3_2 [FreeBSD]

Number of packages to be installed: 1
Number of packages to be upgraded: 3

18 MiB to be downloaded.

Proceed with this action? [y/N]: y
[1/4] Fetching nss-3.115.1.pkg: 100%    2 MiB   2.3MB/s    00:01    
[2/4] Fetching p5-Crypt-URandom-0.54.pkg: 100%   17 KiB  17.6kB/s    00:01    
[3/4] Fetching perl5-5.40.3_2.pkg: 100%   16 MiB  16.4MB/s    00:01    
[4/4] Fetching p5-Authen-SASL-2.1900.pkg: 100%   44 KiB  44.7kB/s    00:01    
Checking integrity... done (0 conflicting)
[1/6] Deinstalling p5-Authen-SASL-2.18...
[1/6] Deleting files for p5-Authen-SASL-2.18: 100%
[2/6] Deinstalling perl5-5.40.2_2...
[2/6] Deleting files for perl5-5.40.2_2: 100%
[3/6] Upgrading nss from 3.115 to 3.115.1...
[3/6] Extracting nss-3.115.1: 100%
[4/6] Installing perl5-5.40.3_2...
[4/6] Extracting perl5-5.40.3_2: 100%
[5/6] Installing p5-Crypt-URandom-0.54...
[5/6] Extracting p5-Crypt-URandom-0.54: 100%
[6/6] Installing p5-Authen-SASL-2.1900...
[6/6] Extracting p5-Authen-SASL-2.1900: 100%
tree /usr/local/poudriere/data/packages/
/usr/local/poudriere/data/packages/
└── 142amd64-default
    ├── All -> .latest/All
    ├── Latest -> .latest/Latest
    ├── data
    ├── data.pkg -> .latest/data.pkg
    ├── data.tzst -> .latest/data.tzst
    ├── logs -> .latest/logs
    ├── meta -> .latest/meta
    ├── meta.conf -> .latest/meta.conf
    ├── packagesite.pkg -> .latest/packagesite.pkg
    ├── packagesite.tzst -> .latest/packagesite.tzst
    └── packagesite.yaml

5 directories, 8 files

Instalar paquete desde repositorio local

pkg install sudo
Updating FreeBSD repository catalogue...
FreeBSD repository is up to date.
Updating poudriere repository catalogue...
poudriere repository is up to date.
All repositories are up to date.
Checking integrity... done (0 conflicting)
The following 1 package(s) will be affected (of 0 checked):

New packages to be INSTALLED:
	sudo: 1.9.17p2 [poudriere]

Number of packages to be installed: 1

The process will require 2 MiB more space.

Proceed with this action? [y/N]: y
[1/1] Installing sudo-1.9.17p2...
[1/1] Extracting sudo-1.9.17p2: 100%
Comprobar security con INSULTS activado:
sudo vim /etc/rc.conf                 
Password:
Quizás si usaras más de dos dedos...
Password:
¡Este hombre no sabe cuándo está vencido! Tampoco sabe cuándo está ganando. 
No tiene... una especie de... aparato sensorial...
Password:
FreeBSD es genial!.