Una contraseña para toda la casa
Una contraseña de WiFi la comparte todo lo que se ha conectado alguna vez. Los portátiles, los móviles, la tele, la cámara del timbre con un firmware que nadie ha actualizado desde que se instaló, el invitado al que se la diste en Navidad, y todos los móviles que ese invitado ha tenido desde entonces. Todos están en la misma red. Cada uno puede hablar con todos los demás.
Separar eso de la forma habitual significa más SSID. Uno para el personal, uno para IoT, uno para invitados, cada uno con su contraseña y su VLAN. Funciona. Luego se filtra una contraseña, y te toca ir por todos los dispositivos de ese SSID con una nueva, que es justo el trabajo que nunca se hace.
Un puerto cableado libre es peor. Ninguna contraseña. Enchufas y estás en la VLAN que tuviera el puerto.
802.1X con certificados le da la vuelta a eso. Cada dispositivo lleva su propio certificado y demuestra qué es antes de que pase un solo paquete, y luego es la red, no el dispositivo ni quien repartió la contraseña, la que decide adónde va. Un SSID. Todos los puertos del switch iguales. El portátil cae en la VLAN del personal, la cámara en la de IoT, y un dispositivo al que nunca le has emitido un certificado no cae en ninguna.
| Una contraseña compartida | Un SSID por VLAN | 802.1X, un SSID | |
|---|---|---|---|
| Quién entra | Cualquiera que tenga la contraseña | Cualquiera que tenga la contraseña de ese SSID | Solo un dispositivo con un certificado que emitiste tú |
| Qué VLAN | Todos en una | La del SSID al que se unieron | La que diga el servidor RADIUS para ese dispositivo |
| Dejar fuera un dispositivo perdido | Cambiar la contraseña en todas partes | Cambiar la contraseña de ese SSID en todas partes | Revocar un certificado |
| Puertos cableados | Abiertos | Abiertos | La misma comprobación que el WiFi |
| SSID que emitir | 1 | Uno por VLAN | 1 |
Eso es lo que construye esta entrada. step-ca emite los certificados y escribe en ellos el rol de cada dispositivo, FreeRADIUS comprueba cada certificado contra la CA y su lista de revocación, lee el rol y responde con una VLAN, y el switch o el punto de acceso pone el dispositivo donde le han dicho. El equipo es Netgear o TP-Link, hablando con FreeRADIUS sobre TLS donde puede. Todo funcionó aquí antes de acercarse a un equipo real: la CA y el servidor RADIUS en contenedores, y el puerto del switch y la red WiFi en simuladores.
Cuatro piezas, y solo una decide
802.1X nombra tres papeles1. El suplicante es el dispositivo que pide entrar. El autenticador es el puerto del switch o el punto de acceso. El servidor de autenticación es RADIUS. Añade la CA que emitió los certificados y ese es el sistema entero.
El switch y el AP no deciden nada. Retransmiten. Hasta que el dispositivo se ha autenticado, el puerto deja pasar EAP y nada más. El autenticador envuelve ese EAP en RADIUS, lo manda al servidor y actúa según la respuesta. Un Access-Accept abre el puerto. Si el Accept lleva atributos de túnel, el puerto o el cliente WiFi va a la VLAN que nombran. El RFC 3580 fija cuáles son esos tres atributos, tomados de los atributos de túnel de RADIUS23:
| Atributo | Valor | Significado |
|---|---|---|
Tunnel-Type | VLAN (13) | Este túnel es una VLAN |
Tunnel-Medium-Type | IEEE-802 (6) | En una red 802 |
Tunnel-Private-Group-Id | "10" | El ID de la VLAN, enviado como cadena |
Así que la marca del switch importa menos de lo que pensarías. La decisión vive en una máquina que llevas tú. Todos los fabricantes que hacen VLAN dinámica leen los mismos tres atributos. El RFC dice claramente que el ID de la VLAN va «encoded as a string»3, y por eso está entre comillas.
Por qué certificados, y por qué solo TLS 1.3
Hay varios métodos EAP, y la mayoría de los que se usan son una contraseña envuelta en TLS. Eso sirve para una persona. Para una cámara no sirve de nada, porque no tiene a nadie que teclee, y una contraseña compartida por mil cámaras vuelve a ser la contraseña de la WiFi.
| Método | Qué identifica al dispositivo | Si el dispositivo se fía del servidor equivocado | Dejar fuera un dispositivo |
|---|---|---|---|
| EAP-TLS | Su propio certificado y su clave privada | El servidor se entera del certificado, que es público de todos modos | Revocar el certificado |
| PEAP con MS-CHAPv2 | Un usuario y una contraseña | El servidor captura un intercambio que se puede descifrar hasta la contraseña | Cambiar la contraseña |
| EAP-TTLS con PAP | Un usuario y una contraseña | Al servidor se le envía la contraseña tal cual | Cambiar la contraseña |
Esa columna del medio es la razón de EAP-TLS. MS-CHAPv2 lleva más de una década roto. El propio aviso de Microsoft dice que «an attacker who obtained the MS-CHAP v2 authentication traffic could then use the exploit code to decrypt a user’s credentials»4. Envolverlo en PEAP solo lo protege si todos los dispositivos comprueban con quién hablan, y como muestra la sección de dispositivos, muchos no lo hacen. Con EAP-TLS no hay ningún secreto en el intercambio que robar. La clave privada no sale nunca del dispositivo5. Nada que descifrar.
TLS 1.2 pone el certificado en el aire
EAP-TLS sobre TLS 1.2 tiene un fallo fácil de pasar por alto. El dispositivo manda su certificado antes de que empiece el cifrado. En claro. Cualquiera que escuche la WiFi puede leerlo, y como esta entrada escribe el rol del dispositivo en el certificado, también puede leer eso.
Medido aquí, buscando el nombre del dispositivo y su rol en cada mensaje EAP que envió el dispositivo:
| Prueba | TLS | Nombre del dispositivo en claro | Rol OU=staff en claro |
|---|---|---|---|
Identidad laptop1 | 1.2 | Sí | Sí |
Identidad laptop1 | 1.3 | Sí, en la identidad | No |
Identidad anonymous | 1.3 | No | No |
TLS 1.3 lo arregla. El RFC 9190 dice que con TLS 1.3 «the use of privacy is mandatory and does not cause any additional round trips»6. La identidad exterior sigue yendo en claro, así que tampoco debería nombrar al dispositivo. El RFC recomienda una identidad anónima6. Aquí nada necesita una real, porque FreeRADIUS lee quién es el dispositivo en el certificado. Con anonymous por fuera, la última fila es lo que se lleva quien escucha: nada.
Así que esta construcción funciona solo con TLS 1.3. FreeRADIUS está fijado a esa versión en los dos extremos. Un dispositivo que no la hable no entra. Eso tiene un coste, y se cuenta en la sección de dispositivos en vez de esconderlo. La propia configuración de FreeRADIUS todavía te dice que te quedes en 1.2, en mayúsculas, porque «most EAP supplicants WILL NOT DO TLS 1.3 PROPERLY»7. Hay que reconocerlo: esa configuración es la documentación más honesta de toda esta entrada, y casi todas las trampas de más abajo están escritas en ella en algún sitio si lees los comentarios. En una red donde eliges tú los dispositivos, es un motivo para elegir mejores dispositivos.
Meter el rol en el certificado
La VLAN se decide por lo que es el dispositivo, y el certificado es donde va eso. step-ca puede escribirlo ahí con una plantilla, el mismo mecanismo que usa la entrada de certificados de cliente para el punto de distribución de la CRL. Poner en marcha la propia CA se cuenta en step-ca en un contenedor, y esto sigue desde ahí con una CA llamada Home Network CA.
Cada certificado de dispositivo lleva su rol en la unidad organizativa (OU) de su sujeto. La plantilla toma el rol de la petición y rechaza todo lo que no esté en la lista8:
{{- $roles := list "staff" "iot" "media" }}
{{- if not (has .Insecure.User.role $roles) }}
{{- fail "role must be one of staff, iot or media" }}
{{- end }}
{
"subject": {
"commonName": {{ toJson .Subject.CommonName }},
"organizationalUnit": {{ toJson .Insecure.User.role }}
},
"sans": {{ toJson .SANs }},
"keyUsage": ["digitalSignature"],
"extKeyUsage": ["clientAuth"],
"crlDistributionPoints": ["http://ca.home.example/1.0/crl"]
}
Dale un aprovisionador propio, para que los dispositivos no salgan nunca del admin de uso general, con un año como el certificado más largo que firmará:
step ca provisioner add network --type JWK --create \
--x509-max-dur 8760h --x509-default-dur 24h \
--x509-template templates/device.tpl
--x509-template copia la plantilla dentro de ca.json en vez de apuntar al archivo, así que editar device.tpl después no cambia nada hasta que vuelvas a ejecutar step ca provisioner update network --x509-template templates/device.tpl. Luego emite el certificado de cada dispositivo con su rol:
step ca certificate laptop1 laptop1.crt laptop1.key \
--provisioner network --not-after 8760h --set role=staff
$ openssl x509 -in laptop1.crt -noout -subject -ext extendedKeyUsage
subject=OU=staff, CN=laptop1
X509v3 Extended Key Usage:
TLS Web Client Authentication
Un rol que falta o uno desconocido no consigue nada:
$ step ca certificate norole norole.crt norole.key --provisioner network
The request could not be completed: role must be one of staff, iot or media.
.Insecure.User se llama inseguro porque es lo que mande quien hace la petición. Aquí no pasa nada. El aprovisionador network tiene contraseña, solo la tienes tú, y quien pueda firmar con él ya puede hacer el certificado que le dé la gana, así que un rol que haya tecleado él mismo no le da nada de más.
El servidor RADIUS también necesita un certificado, para que los dispositivos puedan comprobar con quién hablan. Sale del aprovisionador admin como cualquier otro certificado de servidor, y dura las 24 horas por defecto, renovado con step ca renew --daemon igual que hace la entrada de step-ca para cualquier servicio que no sepa ACME. Los dispositivos comprueban el nombre y la CA, no el número de serie. Una renovación diaria no les cambia nada.
| Certificado | Aprovisionador | Duración | Uso extendido de clave | Lleva |
|---|---|---|---|---|
radius.home.example | admin | 24 horas, renovado a diario | Servidor y cliente | El nombre que comprueban los dispositivos |
laptop1 | network | 1 año | Solo cliente | OU=staff |
cam1 | network | 1 año | Solo cliente | OU=iot |
tv1 | network | 1 año | Solo cliente | OU=media |
switch1, ap1 | nas | 1 año | Solo cliente | OU=nas, para RadSec; ver más abajo |
FreeRADIUS: decidir la VLAN
FreeRADIUS 3.2.10, en la imagen de contenedor del propio proyecto. Tres trozos de configuración hacen el trabajo: el módulo EAP, una política que elige la VLAN, y una lista de excepciones.
El módulo EAP
Solo EAP-TLS, solo TLS 1.3, y la CRL comprobada en cada inicio de sesión:
eap {
default_eap_type = tls
tls-config tls-common {
certificate_file = ${certdir}/eaptls/radius.crt
private_key_file = ${certdir}/eaptls/radius.key
ca_path = ${certdir}/eaptls/ca
ca_file = ${certdir}/eaptls/ca/root_ca.crt
check_crl = yes
ca_path_reload_interval = 300
tls_min_version = "1.3"
tls_max_version = "1.3"
}
tls {
tls = tls-common
}
}
| Archivo | Contiene |
|---|---|
radius.crt | El certificado del servidor, luego el intermedio de step-ca, tal como lo escribe step ca certificate |
radius.key | Su clave privada; como todo lo de aquí, legible por el usuario freerad o FreeRADIUS no arranca |
ca/ | La raíz, el intermedio y la CRL del intermedio, con hash hecho por openssl rehash ca/ |
Esa fila del medio paró el primer arranque aquí, aunque no por la clave: Permission denied en el certificado raíz, porque todos los archivos habían salido de la CA legibles solo por su dueño. FreeRADIUS se ejecuta como freerad, así que los tres tienen que ser legibles por él.
check_crl = yes comprueba el certificado del dispositivo contra la CRL del intermedio. No comprueba el propio intermedio, así que, a diferencia de nginx en la entrada de certificados de cliente, FreeRADIUS no necesita CRL de la raíz. check_all_crl lo activa si lo quieres7. ca_path_reload_interval importa más de lo que parece, y tiene una sección propia más abajo.
La política
Cualquier certificado que haya emitido esta CA pasaría la comprobación TLS. Incluidos los servidores web que consiguieron el suyo por ACME. Así que la política tira todo lo que no esté hecho para este trabajo: sin uso de autenticación de cliente, o sin rol, y se rechaza.
vlan_by_certificate {
if (&TLS-Client-Cert-X509v3-Extended-Key-Usage-OID !~ /1\.3\.6\.1\.5\.5\.7\.3\.2/) {
reject
}
device_vlan
if (!&reply:Tunnel-Private-Group-Id) {
if (&TLS-Client-Cert-Subject =~ /\/OU=([^\/]+)/) {
switch "%{1}" {
case "staff" {
update reply { &Tunnel-Private-Group-Id := "10" }
}
case "iot" {
update reply { &Tunnel-Private-Group-Id := "20" }
}
case "media" {
update reply { &Tunnel-Private-Group-Id := "30" }
}
case {
reject
}
}
}
else {
reject
}
}
update reply {
&Tunnel-Type := VLAN
&Tunnel-Medium-Type := IEEE-802
}
}
Eso va en policy.d/vlan, y una línea, vlan_by_certificate, va al principio de la sección post-auth de sites-available/default. Post-auth es donde están disponibles los datos del certificado, una vez terminado el saludo TLS9.
La lista de excepciones
El rol decide la mayoría de los dispositivos. No todos. La lista es para los pocos que no debería: una cámara que ha empezado a portarse raro y quieres poner en cuarentena antes de averiguar por qué, o un dispositivo prestado una semana. Es un módulo passwd que lee un archivo de dos columnas, con el nombre común del certificado como clave:
passwd device_vlan {
filename = ${modconfdir}/device_vlan/devices
format = "*TLS-Client-Cert-Common-Name:=Tunnel-Private-Group-Id"
delimiter = ","
}
# certificate CN,VLAN
cam1,99
El = delante del atributo lo pone en la respuesta en vez de en los elementos de comprobación10. Mover un dispositivo es una línea en un archivo de texto, sin certificado que volver a emitir y sin tocar nada en el dispositivo.
| VLAN | Rol | Qué vive ahí |
|---|---|---|
| 10 | staff | Portátiles y móviles de confianza |
| 20 | iot | Cámaras, sensores, todo lo que llama a casa |
| 30 | media | Teles y reproductores |
| 99 | Solo la lista | Cuarentena: internet como mucho, nada local |
Demostrarlo antes de tocar un switch
eapol_test viene con wpa_supplicant y hace de dispositivo y de switch a la vez, hablando directamente con el servidor RADIUS. Úsalo primero. Cuando algo no funcione más tarde, sabrás qué mitad mirar.
network={
key_mgmt=WPA-EAP
eap=TLS
phase1="tls_disable_tlsv1_3=0"
identity="anonymous"
ca_cert="/etc/pki/home/root_ca.crt"
domain_match="radius.home.example"
client_cert="/etc/pki/home/laptop1.crt"
private_key="/etc/pki/home/laptop1.key"
}
eapol_test -c laptop1.conf -a 192.0.2.10 -s <shared secret>
Las respuestas, del propio registro de depuración de FreeRADIUS:
(8) Sent Access-Accept
(8) Tunnel-Private-Group-Id = "10"
(8) Tunnel-Type = VLAN
(8) Tunnel-Medium-Type = IEEE-802
| Dispositivo | Rol en el certificado | En la lista | Respuesta | VLAN |
|---|---|---|---|---|
laptop1 | staff | No | Accept | 10 |
cam1 | iot | 99 | Accept | 99 |
tv1 | media | No | Accept | 30 |
laptop2 | staff, revocado | No | Reject, certificate revoked | Ninguna |
radius.home.example | Ninguno | No | Reject, sin rol | Ninguna |
laptop1 con TLS 1.3 desactivado | staff | No | Reject, protocol version | Ninguna |
Esa última fila conviene conocerla antes de ponerlo en producción. wpa_supplicant 2.11 todavía viene con TLS 1.3 desactivado para EAP, descrito como «experimental - disabled by default»11. Quita la línea phase1 y un portátil perfectamente válido se queda fuera.
Un puerto de switch, simulado
eapol_test se salta el switch. Para ver la mitad del switch, hostapd tiene un controlador cableado que convierte una interfaz Linux en un puerto 802.1X. Un cable virtual lo une a un wpa_supplicant que hace de portátil, y apunta al mismo FreeRADIUS.
interface=port1
driver=wired
ieee8021x=1
use_pae_group_addr=1
auth_server_addr=192.0.2.10
auth_server_port=1812
auth_server_shared_secret=<shared secret>
Lo que ve el puerto de un portátil válido y de uno revocado:
port1: CTRL-EVENT-EAP-SUCCESS2 4a:42:cf:cf:09:5a
port1: AP-STA-CONNECTED 4a:42:cf:cf:09:5a
port1: STA 4a:42:cf:cf:09:5a IEEE 802.1X: authorizing port
port1: CTRL-EVENT-EAP-FAILURE2 de:d8:8c:88:6e:f2
port1: IEEE 802.1X: Force disconnection of de:d8:8c:88:6e:f2 after EAP-Failure in 10 ms
Activa su gestión de VLAN dinámica y te dice la VLAN que le dieron, vlan=10 para el portátil y vlan=99 para la cámara en cuarentena, y luego dice que could not add dynamic VLAN interface. Es un puerto Linux siendo honesto: puede abrirse y cerrarse, pero no puede moverse él solo a una VLAN. Un switch gestionable hace esa parte en hardware, y esa es la mitad que cubren los ajustes de los fabricantes de más abajo.
Revocación: la CRL que FreeRADIUS ignora con un HUP
Un portátil perdido es el caso para el que existe todo esto. Revócalo en la CA. El aprovisionador admin firma un token de revocación para el número de serie, que funciona sin la clave del portátil, porque el portátil no lo vas a tener:
SERIAL=$(step certificate inspect laptop2.crt --format json | jq -r .serial_number)
TOKEN=$(step ca token "$SERIAL" --revoke --provisioner admin)
step ca revoke "$SERIAL" --token "$TOKEN" --reason "laptop lost"
Certificate with Serial Number 306546036599704679037436836531698019860 has been revoked.
step-ca reconstruye su CRL al momento cuando generateOnRevoke está activado, configurado como muestra la entrada de certificados de cliente. Cópiala al directorio ca/ de FreeRADIUS y el portátil queda fuera. Pues no. Volvió a entrar como si nada.
| Qué se hizo después de que la CRL nueva llegara al disco | Portátil revocado |
|---|---|
| Nada | Sigue entrando |
kill -HUP | Sigue entrando. El registro dice HUP - No files changed. Ignoring |
Esperar más allá de ca_path_reload_interval | Rechazado, certificate revoked |
| Reiniciar FreeRADIUS | Rechazado, certificate revoked |
Un HUP recarga archivos de configuración, y una CRL en ca_path no es uno de ellos, así que FreeRADIUS repasa su lista de archivos de configuración, no encuentra ninguno cambiado, y sigue con la CRL vieja en memoria. Lo que sí la recoge es ca_path_reload_interval. Viene desactivado en la configuración por defecto. Ponlo por debajo de 300 segundos y FreeRADIUS te enmienda al arrancar:
ca_path_reload_interval is set too low, reset it to 300
Así que 300. Una revocación tiene efecto en menos de cinco minutos desde que llega la CRL nueva. Descárgala con un temporizador, como la entrada de certificados de cliente ya hace para nginx, pero en este directorio. Reinicia FreeRADIUS si cinco minutos es demasiado para el dispositivo que acabas de perder.
La CRL también puede dejar a todo el mundo fuera de golpe. Las dos caras, entonces. Las dos se probaron con certificados válidos:
| Estado de la CRL | Qué dice FreeRADIUS | Quién entra |
|---|---|---|
| Vigente | Nada | Todos los dispositivos válidos |
Falta en ca/ | error 3 : unable to get certificate CRL | Nadie |
| Pasada su fecha de próxima actualización | error 12 : CRL has expired | Nadie |
La CRL de step-ca dura 24 horas por defecto. Si se para el temporizador que la descarga, todos los dispositivos de la red fallan al día siguiente. allow_expired_crl existe para suavizar eso7. No lo uses. Una red que deja entrar a todos porque una lista se quedó vieja ha dejado de comprobar nada, y como tal el arreglo es vigilar el temporizador, no apagar la comprobación.
RADIUS sigue siendo MD5 y un secreto compartido
Todo lo de hasta ahora protege la conversación entre el dispositivo y FreeRADIUS. EAP-TLS va de punta a punta, y el switch o el AP de en medio no pueden leerlo. El paquete RADIUS que lo lleva es otra historia.
El RADIUS clásico es UDP con un secreto compartido, y el secreto se usa mediante MD5, lo que importaría bastante menos si el paquete no llevara nada de valor, pero el Access-Accept de un cliente WiFi lleva las claves del tráfico de ese cliente. MS-MPPE-Recv-Key y MS-MPPE-Send-Key son la clave maestra que usa el AP para cifrar el tráfico de ese cliente, y el RFC 2548 las protege con b(1) = MD5(S + R + A) y un XOR, donde S es el secreto compartido12. Ahí no hay secreto hacia adelante. Captura hoy el tráfico RADIUS, consigue el secreto el año que viene de una copia de seguridad de configuración o de la configuración en marcha de un switch, y todas las claves capturadas se descifran, junto con el tráfico WiFi que protegían.
Así que la pregunta no es si el secreto es lo bastante largo. Es por qué las claves de la red inalámbrica cruzan el cable bajo MD5. No hace falta.
| Amenaza | RADIUS normal sobre UDP | Con Message-Authenticator obligatorio | RadSec, TLS 1.3, certificados mutuos |
|---|---|---|---|
| Access-Accept falsificado (BlastRADIUS) | Posible en peticiones sin EAP, MAB incluido | Parado | Parado |
| Claves WiFi leídas de una captura | Cualquiera que llegue a saber el secreto | Igual | Hacen falta las claves de sesión TLS, que nunca se guardan |
| Una máquina en la red de gestión haciéndose pasar por switch | Solo necesita el secreto | Igual | Necesita un certificado con el rol nas, desde una dirección conocida |
| Quién ve qué dispositivo inició sesión | Cualquiera en el camino | Igual | Nadie |
BlastRADIUS, y la línea que lo para
BlastRADIUS, CVE-2024-3596, deja que un atacante situado entre el switch y el servidor convierta un rechazo en una aceptación atacando el MD5 que firma la respuesta13. El arreglo es el atributo Message-Authenticator en cada petición y cada respuesta13. El RFC 3579 siempre lo ha exigido en todo lo que lleve EAP14, así que EAP-TLS nunca fue el punto débil. MAB sí, porque una comprobación de dirección MAC no lleva EAP y muchos switches lo mandaban sin él.
FreeRADIUS 3.2.10 lo exige de serie, con require_message_authenticator = yes en radiusd.conf. Probado aquí con una petición MAB hecha a mano:
| Petición | Respuesta |
|---|---|
Sin Message-Authenticator | Ninguna respuesta. Registrado como Packet does not contain required Message-Authenticator attribute |
Con Message-Authenticator | Una respuesta, Access-Reject para una MAC desconocida |
Deja ese ajuste como está. La configuración avisa de que poner ese y limit_proxy_state los dos a no «will allow MITM attackers to create fake Access-Accept packets to the NAS»15.
Eso cierra la falsificación. No hace nada por las claves.
RadSec: RADIUS dentro de TLS 1.3
El estándar más alto disponible es RadSec, RADIUS transportado sobre TLS con un certificado en cada extremo16. El switch o el AP demuestra quién es con un certificado, FreeRADIUS demuestra quién es con el suyo, y toda la conversación RADIUS, claves incluidas, va dentro de TLS 1.3 con secreto hacia adelante. El IETF lo está pasando a la vía de estándares; el borrador que sustituiría al RFC experimental de 2012 «Has enough positions to pass»17.
Los switches y los AP reciben certificados de step-ca como todo lo demás, de un tercer aprovisionador cuya plantilla graba OU=nas y nada más, lo que significa que un certificado de dispositivo no sirve para hacerse pasar por switch, y un certificado de switch, que no lleva ningún rol que reconozca la política de VLAN, tampoco sirve para entrar en la red.
listen {
ipaddr = *
port = 2083
type = auth
proto = tcp
virtual_server = default
clients = radsec
tls {
certificate_file = ${certdir}/eaptls/radius.crt
private_key_file = ${certdir}/eaptls/radius.key
ca_path = ${certdir}/eaptls/ca
ca_file = ${certdir}/eaptls/ca/root_ca.crt
require_client_cert = yes
check_crl = yes
ca_path_reload_interval = 300
verify {
tmpdir = ${run_dir}
client = "${raddbdir}/nas-only.sh %{TLS-Client-Cert-Filename}"
}
tls_min_version = "1.3"
tls_max_version = "1.3"
}
}
clients radsec {
client switch1 {
ipaddr = 192.0.2.21
proto = tls
secret = radsec
}
}
#!/bin/sh
# Accept a RadSec client certificate only if step-ca issued it with the nas role.
openssl x509 -in "$1" -noout -subject -nameopt compat | grep -q '/OU=nas/'
El secreto es de verdad la palabra radsec. En serio. Sobre TLS no hace nada, y el propio ejemplo de FreeRADIUS dice «TLS clients MUST have the shared secret set to “radsec”»18. Dos cosas que parecen naturales no funcionan, y las dos se descubrieron a las malas. check_cert_cn = %{client:shortname} se expande a nada en el momento del saludo, así que todos los switches se rechazan con Certificate CN (switch1) does not match specified value (); de ahí el script verify. Y el listener no arranca con freeradius -X, que apaga los hilos: Threading must be enabled for TLS sockets to function properly. Depúralo con freeradius -fxx -l stdout en su lugar.
El puerto de switch simulado de antes, reconstruido para usar RadSec, con las mismas pruebas:
| El switch presenta | Desde | Resultado |
|---|---|---|
switch1, OU=nas | Su dirección listada | TLS 1.3 arriba; laptop1 autorizado en la VLAN 10, laptop2 revocado rechazado |
El certificado de laptop1 | La dirección del switch | Rechazado en el saludo; el script verify devuelve 1 |
switch1, OU=nas | Una dirección no listada | Ignoring request … from unknown client |
Qué equipo puede hacerlo
| Autenticador | RadSec | Notas |
|---|---|---|
| hostapd 2.11 | Sí, si se compila con él | CONFIG_RADIUS_TLS=y está comentado en su defconfig19. El paquete de Alpine lo deja fuera y rechaza la configuración: unsupported RADIUS type 'TLS'. Compilado con él, funcionó a la primera |
| APs y switches TP-Link Omada | Desde Controller 6.1, con firmware actual | «Added support for RadSec (RADIUS over TLS) to secure SSID, MAC-Based, and 802.1X authentications on both APs and Switches»20 |
| Proxy RADIUS de TP-Link Omada | No | El propio proxy del controlador «does not support the RadSec feature»21 |
| Netgear M4250 y M4350 | No documentado | El manual de la CLI no menciona RadSec, y limita el secreto compartido: «The secret must be an alphanumeric value not exceeding 16 characters»22 |
| APs Netgear WAX y WBE | No documentado | Los manuales describen el puerto RADIUS como UDP, y el secreto por defecto como sharedsecret23 |
¿Puedo preguntar por qué un switch vendido como totalmente gestionable en 2026 no sabe hablar con su servidor RADIUS sobre TLS? No quién lo decidió. Qué en el proceso dejó que saliera así, cuando el controlador de TP-Link, hostapd y un proxy gratuito sí saben. Dieciséis caracteres alfanuméricos aleatorios son unos 95 bits, y no es eso lo que se rompe. Lo que pasa es que es la única protección que se ofrece: ninguna opción TLS, un esquema MD5 de 1999 guardando las claves WiFi, y un AP que sale de fábrica con el secreto puesto a la palabra sharedsecret. Con equipo Netgear, radsecproxy no es opcional.
radsecproxy, para el equipo que solo habla UDP
Donde el switch o el AP no sepa RadSec, pon radsecproxy al lado. Recibe RADIUS por UDP y saca RadSec24, así que el salto en claro son los pocos metros de VLAN de gestión entre el switch y el proxy, no todo el camino hasta el servidor:
ListenUDP *:1812
tls stepca {
CACertificateFile /etc/radsecproxy/root_ca.crt
CertificateFile /etc/radsecproxy/radsecproxy.crt
CertificateKeyFile /etc/radsecproxy/radsecproxy.key
TlsVersion TLS1_3
}
client switches {
host 192.0.2.32/27
type udp
secret <long random secret>
RequireMessageAuthenticator on
}
server freeradius {
host radius.home.example
port 2083
type tls
tls stepca
secret radsec
certificateNameCheck on
}
realm * {
server freeradius
}
Usa el nombre. No la dirección. Con host 10.89.0.100 el proxy rechazó el servidor, y con razón: No subjectaltname matching address 10.89.0.100. Con el nombre subió como TLSv1.3 with cipher TLS_AES_256_GCM_SHA384, y los mismos dispositivos recibieron las mismas respuestas a través de él: laptop1 VLAN 10, cam1 99, tv1 30, laptop2 rechazado. Una petición sin Message-Authenticator se quedó en el proxy: ignoring request from client switches … missing required message-authenticator.
Mantén honesto ese salto UDP. Corto, y vallado. La dirección de gestión del switch y el proxy están en una VLAN de gestión a la que ninguna VLAN de clientes puede enrutar, y el cortafuegos lo hace cumplir.
También la descarga de la CRL
La CRL va firmada, así que nadie puede falsificar una. Lo que sí pueden es reenviar una más antigua que siga en fecha, y eso esconde una revocación hecha después. Descárgala de step-ca por HTTPS con su propia raíz fijada, en vez de por HTTP normal:
curl -fsS --cacert /etc/freeradius/certs/eaptls/ca/root_ca.crt --tlsv1.3 \
-o /tmp/crl.der https://ca.home.example:9000/1.0/crl
Contra cualquier otra raíz, el mismo comando falla con el código de salida 60 de curl, y FreeRADIUS se queda con la lista que tiene.
Hacia dónde va
El RFC 9765, RADIUS/1.1, quita del todo MD5 de RADIUS cuando va sobre TLS: «the RADIUS shared secret is no longer used, and all MD5-based packet authentication and attribute obfuscation methods are removed»25. Es experimental. FreeRADIUS 3.2 lo tiene detrás de una opción de compilación, --with-radiusv11, «for testing interoperability»26, y la imagen oficial 3.2.10 no está compilada con ella; la opción no está en el binario. RadSec con TLS 1.3 es el estándar más alto que puedes desplegar hoy, y RADIUS/1.1 es adonde va después.
El switch: Netgear y TP-Link
Todo switch gestionable que valga la pena hace 802.1X con VLAN dinámicas, y los ajustes se reducen a las mismas cinco cosas diga lo que diga la marca. La diferencia entre estos dos está en el detalle, y un detalle del lado de TP-Link cambia cómo cableas un puesto de trabajo.
| Ajuste | Netgear M4250 y M4350 | Switches TP-Link Omada |
|---|---|---|
| Servidor RADIUS | Security > Management Security > RADIUS > Server Configuration27 | Un RADIUS Profile en el controlador Omada28 |
| 802.1X activado | Security > Port Authentication > Basic > 802.1X Configuration: Dot1x Admin Mode, Authentication Admin Mode27 | Authentication > 802.1X, Authentication Type «Port Based»28 |
| VLAN desde RADIUS | VLAN Assignment Mode, «The default is Disable»27 | «enable ‘VLAN Assignment’»28 |
| VLAN que falta | No se crea salvo que Dynamic VLAN Creation Mode esté activado, también apagado por defecto27 | «the switch will create the related VLAN automatically»29 |
| Puertos de acceso | Control Mode Auto | 802.1X Control mode Auto28 |
| Enlaces troncales, el puerto del servidor RADIUS, puertos de AP | Force Authorized | 802.1X desactivado: «Keep 802.1X authentication disabled on ports connected to the authentication server, the internet and the management computer»29 |
Crea las VLAN en el switch primero, en ambos casos. Una VLAN que existe porque la hizo aparecer un inicio de sesión es una VLAN que nadie configuró, y a la que nadie le puso una regla de cortafuegos.
Una VLAN por puerto en TP-Link
La propia guía de TP-Link dice que «VLAN Assignment takes effect only when the control type is Port Based»29. Basado en puerto significa que el primer dispositivo que se autentica fija la VLAN de todo el puerto. Pon en ese puerto un switch de mesa o un teléfono con un PC enchufado a través de él, y el segundo dispositivo entra en la VLAN del primero sin que nadie le pida nunca un certificado, lo que en un puerto de personal significa que cualquier cosa enchufada detrás de ese teléfono está en la VLAN del personal.
Así que en TP-Link: un dispositivo por puerto de acceso. Nada entre la pared y el dispositivo. El modo basado en MAC de Netgear autentica por separado cada dispositivo de un puerto; el manual del GS710TUP lo describe como que permite «multiple supplicants connected to the same port»30.
MAB, para la impresora que no sabe hacer nada de esto
Hay equipo que no tiene cliente 802.1X. MAC Authentication Bypass deja que el switch mande la dirección MAC del dispositivo a RADIUS como usuario y como contraseña, y los dos fabricantes lo admiten junto con 802.1X en el mismo puerto. Netgear prueba 802.1X primero y luego recurre a MAB: «if a client tries 802.1X authentication… and authentication fails, the interface proceeds with MAB authentication»27. TP-Link manda la MAC como «12 hexadecimal digits, all lowercase and no punctuation»29.
Una dirección MAC no es una identidad. Cualquiera puede poner su tarjeta de red con la MAC de la impresora en un comando, desde un portátil que ha traído, de pie al lado de la impresora cuyo cable acaba de tomar prestado. Así que MAB tiene una lista propia, y la política solo le dará la VLAN de IoT o la cuarentena:
mab {
if (!&EAP-Message && &User-Name =~ /^[0-9a-f]{12}$/ && &User-Password == &User-Name) {
mab_vlan
if (ok && &reply:Tunnel-Private-Group-Id =~ /^(20|99)$/) {
update control { &Auth-Type := Accept }
update reply {
&Tunnel-Type := VLAN
&Tunnel-Medium-Type := IEEE-802
}
}
else {
reject
}
}
}
Eso va primero en authorize, con un módulo passwd llamado mab_vlan que lee líneas mac,vlan exactamente como la lista de dispositivos. La política de certificados va envuelta en if (&EAP-Message) para que deje en paz a MAB. Probado:
| Petición MAB | Entrada en la lista | Respuesta |
|---|---|---|
aabbccddeeff | 20 | Accept, VLAN 20 |
112233445566 | Ninguna | Reject |
ccddeeff0011 | 10, la VLAN del personal | Reject. La política no pone una MAC en la del personal, diga lo que diga la lista |
Quien suplante la impresora consigue la VLAN de la impresora y nada más. Es lo máximo que debería comprar nunca una dirección MAC.
Los ajustes del switch Omada de arriba también se comprobaron contra un Omada Software Controller simulado, 6.2.14.11, en un contenedor sin hardware detrás. Las cadenas de su interfaz en inglés nombran cada ajuste usado aquí: la opción RadSec, donde «a TLS-encrypted transmission tunnel will be established between the client and the RADIUS server», «Enable VLAN Assignment for Wireless Network», «Require Message-Authenticator», «Limit Proxy-State», y la importación del certificado y la clave de cliente para RadSec. Un controlador configura; no pasa tráfico, así que esto demuestra que los ajustes existen en esa versión y nada más. Netgear no tiene nada equivalente que ejecutar, así que su columna sale de los propios manuales de Netgear y no se probó aquí.
El punto de acceso: un SSID, todas las VLAN
El punto de acceso es donde rinde el SSID único. Una red WPA-Enterprise, y cada cliente va a la VLAN que RADIUS nombra para él. El AP etiqueta el tráfico de ese cliente con su VLAN y lo manda cable arriba, así que el puerto del switch que alimenta el AP tiene que llevar, etiquetadas, todas las VLAN que RADIUS pueda repartir. Deja una fuera y el dispositivo se autentica perfectamente y nunca recibe una dirección.
Un SSID, simulado
Antes de fiarse de la palabra de cualquiera de los dos fabricantes, todo se ejecutó con radios simuladas. mac80211_hwsim de Linux crea tarjetas WiFi virtuales que hacen WPA-Enterprise de verdad. El kernel de esta máquina no trae el módulo, así que la prueba arrancó el mismo kernel en una pequeña máquina virtual KVM y lo cargó allí, desde el propio paquete firmado de Fedora, con hostapd 2.11 como punto de acceso y tres estaciones wpa_supplicant en sus propios espacios de nombres de red31:
interface=wlan0
driver=nl80211
ssid=Home
ieee8021x=1
wpa=2
wpa_key_mgmt=WPA-EAP
rsn_pairwise=CCMP
ieee80211w=1
auth_server_addr=192.0.2.10
auth_server_port=1812
auth_server_shared_secret=<shared secret>
dynamic_vlan=2
vlan_tagged_interface=trunk0
vlan_bridge=brvlan
dynamic_vlan=2 significa «obligatorio»: un inicio de sesión sin VLAN en la respuesta se rechaza en vez de dejarlo en una por defecto. Cada estación inició sesión como anonymous sobre TLS 1.3, y el propio registro del AP muestra lo que le dijo RADIUS:
wlan0: STA 02:00:00:00:01:00 RADIUS: VLAN ID 10
wlan0: STA 02:00:00:00:02:00 RADIUS: VLAN ID 99
wlan0: STA 02:00:00:00:03:00 RADIUS: VLAN ID 30
WPA: group state machine entering state GTK_INIT (VLAN-ID 10)
WPA: group state machine entering state GTK_INIT (VLAN-ID 30)
WPA: group state machine entering state GTK_INIT (VLAN-ID 99)
| Estación | Certificado | VLAN | Puente | Su propia puerta de enlace | La VLAN de la tele |
|---|---|---|---|---|---|
laptop1 | OU=staff | 10 | brvlan10 | Alcanzable | Sin respuesta |
cam1 | OU=iot, en la lista | 99 | brvlan99 | Alcanzable | |
tv1 | OU=media | 30 | brvlan30 | Alcanzable |
Las líneas de la clave de grupo son la parte que se le escapa a la gente. Las tramas de difusión y multidifusión en WiFi se cifran con una clave de grupo compartida por todos los de la red, y si las tres VLAN compartieran una, la cámara podría leer del aire las difusiones del portátil pensara lo que pensara el switch. hostapd mantiene una clave de grupo distinta por VLAN, y el registro lo muestra arrancando una para cada una. La misma prueba en WPA3-Enterprise, WPA-EAP-SHA256 con protección de tramas de gestión obligatoria, dio las mismas tres VLAN y las mismas tres claves de grupo.
| Ajuste | Netgear WAX y WBE | TP-Link Omada EAP |
|---|---|---|
| Seguridad | WPA2 Enterprise o WPA3 Enterprise | WPA2-Enterprise o WPA3-Enterprise, incluido un modo exclusivo con CCMP-128 |
| VLAN desde RADIUS | Dynamic VLAN, Enable23 | «Enable VLAN Assignment for Wireless Network»28 |
| Sin VLAN en la respuesta | El cliente va a la VLAN propia del SSID23 | No documentado; FreeRADIUS aquí nunca manda un Accept sin una |
| Transporte RADIUS | Solo UDP, así que radsecproxy | RadSec desde Controller 6.1 con firmware actual20 |
Dos detalles de Netgear conviene saberlos antes de que muerdan. A un cliente al que el servidor RADIUS no le da VLAN se le «automatically assigned the VLAN ID that you configured for the SSID»23, así que pon la VLAN propia del SSID en cuarentena, no en la del personal; FreeRADIUS aquí siempre manda una, pero la alternativa debería fallar cerrada de todos modos. Y la VLAN dinámica «is mutually exclusive with a captive portal, NAT mode, wireless client isolation, and a multicast DNS (mDNS) gateway»23, así que un portal de invitados en el mismo SSID queda descartado.
WPA3-Enterprise en el WAX620 es el modo de 192 bits: «the encryption is automatically set to GCMP256»32, con protección de tramas de gestión obligatoria. Los clientes que no sepan GCMP-256 no entrarán. Prueba tu dispositivo más viejo antes de cambiar, igual que con TLS 1.3.
Los dispositivos, y la comprobación que todos se saltan
Cada dispositivo necesita tres cosas: su propio certificado y clave, el certificado raíz, y el nombre del servidor RADIUS que debe esperar. La mayoría de las guías cubren las dos primeras. Si te dejas algo de la tercera, sigue funcionando, y ese es el problema.
La tercera es lo que impide que un dispositivo se fíe del servidor equivocado. Aquí se montó dos veces un servidor RADIUS falso: una con un certificado perfectamente válido de la misma CA, emitido a ha.home.example, y otra con un certificado autofirmado. Se apuntó un portátil a cada uno con tres configuraciones de cliente distintas:
| El cliente comprueba | Servidor real | Misma CA, otro nombre | Autofirmado |
|---|---|---|---|
| La CA y el nombre del servidor | Conecta | Rechazado, Domain mismatch | Rechazado |
| Solo la CA | Conecta | Conecta | Rechazado |
| Nada | Conecta | Conecta | Conecta |
Mira la fila del medio. Tu CA firma un certificado para cada servidor web de la casa. Un dispositivo que solo comprueba la CA se fiará de cualquiera de ellos como su servidor RADIUS, así que cualquiera que se haga con una de esas claves, de una máquina de pruebas olvidada o de un servidor web que nadie ha parcheado, puede montar un punto de acceso con tu SSID y tus dispositivos se unirán sin rechistar. Con EAP-TLS no entregan ninguna contraseña, pero están en la red del atacante y fiándose de ella. TLS 1.3 no cambia esto; las mismas tres filas salieron con 1.3. Solo lo cambia la comprobación del nombre.
Android llegó primero. La opción «Do not validate» se quitó «for security reasons»33, aunque avisa de que «saved enterprise configurations that are set up to turn off server certificate validation aren’t affected»33. Un móvil viejo todavía puede llevar una.
| Linux (NetworkManager) | macOS, iOS, iPadOS | Android | |
|---|---|---|---|
| Certificado y clave | 802-1x.client-cert, 802-1x.private-key | Carga de identidad en un perfil de configuración | Instalado como certificado de Wi-Fi |
| Certificado raíz | 802-1x.ca-cert | Carga de certificado, de confianza mediante PayloadCertificateAnchorUUID34 | Campo de certificado de CA |
| Nombre del servidor | 802-1x.domain-match35 | TLSTrustedServerNames34 | Campo de dominio; setDomainSuffixMatch en la API36 |
| TLS 1.3 | 802-1x.phase1-auth-flags tls-1-3-enable | TLSMinimumVersion y TLSMaximumVersion "1.3"; el máximo por defecto es "1.2"34 | setMinimumTlsVersion(TLS_V1_3), desde el nivel de API 34, donde el dispositivo lo admita36 |
En Linux todo es un comando. Se comprobó con nmcli --offline, que imprime la conexión que crearía sin tocar el sistema en marcha:
nmcli connection add type wifi con-name Home ifname '*' ssid Home \
wifi-sec.key-mgmt wpa-eap 802-1x.eap tls 802-1x.identity anonymous \
802-1x.ca-cert /etc/pki/home/root_ca.crt \
802-1x.domain-match radius.home.example \
802-1x.client-cert /etc/pki/home/laptop1.crt \
802-1x.private-key /etc/pki/home/laptop1.key \
802-1x.private-key-password-flags not-required \
802-1x.phase1-auth-flags tls-1-3-enable
Para un puerto cableado es lo mismo con type ethernet y sin SSID. Que la versión TLS máxima de Apple sea 1.2 por defecto es la trampa de ese lado: un perfil que la omite construye un Mac que esta red rechaza.
Lo que cuesta solo TLS 1.3
El límite honesto de esta construcción son los dispositivos que no pueden hacerlo. Una cámara o una tele con un suplicante embebido de hace años puede que sepa EAP-TLS sobre 1.2 y nunca sobre 1.3, y no hay ningún ajuste de tu lado que lo arregle. La versión TLS se acuerda antes de que FreeRADIUS haya visto el certificado, así que no puede hacer una excepción para el rol de IoT.
Así que prueba cada dispositivo antes de comprarlo. No después. Apúntalo a un FreeRADIUS solo 1.3 y lee el registro: recv TLS 1.3 Handshake, ClientHello o protocol version te dice todo lo que necesitas. Un dispositivo que falla va a un puerto cableado con MAB, o no entra en la red. Debilitar toda la red para que encaje lo peor que hay en ella es como empezó la contraseña compartida.
La configuración completa, archivo por archivo
Todo lo de arriba está recortado a las líneas que demuestran cada punto. Esto es el lote entero. Cada archivo de aquí se ejecutó en el laboratorio, desde un step-ca vacío y un árbol de FreeRADIUS de serie hasta un portátil en la VLAN 10, y las copias de abajo son esos mismos archivos con las direcciones del laboratorio cambiadas por las de la tabla. Llévatelos en una sola descarga, eap-tls-configs.tar.gz, o de uno en uno desde los enlaces.
| Nombre | Dirección | Qué es |
|---|---|---|
ca.home.example | Solo por nombre | step-ca, puerto 9000 |
radius.home.example | 192.0.2.10 | FreeRADIUS, RadSec en el 2083 |
radsecproxy | 192.0.2.11 | radsecproxy, UDP de entrada, RadSec de salida |
switch1, ap1 | 192.0.2.21, 192.0.2.31 | Equipo que habla RadSec |
| Equipo solo UDP | 192.0.2.32 a .63 | A través de radsecproxy; el AP Linux de abajo es ap2 en la .33 |
| Archivo | Va en | Su función |
|---|---|---|
step-ca/templates/device.tpl | /home/step/templates/ en la CA | El rol en la OU, solo autenticación de cliente |
step-ca/templates/nas.tpl | /home/step/templates/ en la CA | OU=nas, para RadSec |
step-ca/setup.sh | Se ejecuta una vez en la CA | Añade los provisioners network y nas |
step-ca/issue.sh | Se ejecuta donde step confía en la CA | Todos los certificados, y la receta de revocación |
freeradius/install.sh | Se ejecuta desde freeradius/ | Pone el resto en su sitio |
freeradius/mods-available/eap | RADDB/mods-available/ | EAP-TLS, TLS 1.3, CRL comprobada |
freeradius/mods-available/device_vlan | RADDB/mods-available/ | Lee la lista por dispositivo |
freeradius/mods-available/mab_vlan | RADDB/mods-available/ | Lee la lista de MAB |
freeradius/mods-config/device_vlan/devices | RADDB/mods-config/device_vlan/ | Nombre del certificado a VLAN |
freeradius/mods-config/device_vlan/mab | RADDB/mods-config/device_vlan/ | Dirección MAC a VLAN |
freeradius/policy.d/vlan | RADDB/policy.d/ | Elige la VLAN para un certificado |
freeradius/policy.d/mab | RADDB/policy.d/ | Elige la VLAN para una MAC, solo 20 o 99 |
freeradius/sites-available/default.patch | Se aplica a RADDB | Llama a las dos políticas |
freeradius/sites-available/tls | RADDB/sites-available/ | Escucha de RadSec y sus clientes |
freeradius/nas-only.sh | RADDB/ | Deja entrar solo certificados OU=nas |
freeradius/clients.conf | RADDB/ | UDP para pruebas en la propia máquina |
systemd/radius-crl-refresh | /usr/local/sbin/ | Descarga y comprueba la CRL |
systemd/radius-crl-refresh.service | /etc/systemd/system/ | Lo ejecuta |
systemd/radius-crl-refresh.timer | /etc/systemd/system/ | Cada hora |
systemd/radius-cert-renew.service | /etc/systemd/system/ | Renueva el certificado del servidor |
radsecproxy/radsecproxy.conf | /etc/radsecproxy.conf | UDP de entrada, RadSec de salida |
hostapd/hostapd-wired.conf | /etc/hostapd/ | Un puerto de switch 802.1X en Linux |
hostapd/hostapd-wifi.conf | /etc/hostapd/ | Un punto de acceso Linux, un SSID |
clients/wpa_supplicant-wired.conf | El dispositivo | Inicio de sesión cableado |
clients/wpa_supplicant-wifi.conf | El dispositivo | Inicio de sesión WiFi |
clients/nmcli.sh | El dispositivo | Lo mismo, a través de NetworkManager |
FreeRADIUS vive en un sitio distinto en cada distribución, y a sus archivos les da igual cuál, porque cada ruta dentro de ellos se construye a partir del directorio de configuración. install.sh recibe ese directorio como RADDB. Se ejecutó en las tres de abajo, y la comprobación de configuración del propio FreeRADIUS, -XC, pasó en cada una. Solo la imagen oficial pasó después todas las pruebas de esta entrada; en las otras dos esa comprobación es toda la evidencia, y en un contenedor, así que SELinux en una máquina Fedora real no entró en ella.
| Imagen oficial | Debian 13 | Fedora | |
|---|---|---|---|
| FreeRADIUS | 3.2.10 | 3.2.7 | 3.2.8 |
RADDB | /etc/freeradius | /etc/freeradius/3.0 | /etc/raddb |
| Se ejecuta como | freerad | freerad | radiusd |
| Servicio | El contenedor | freeradius | radiusd |
| Probado | Todo lo de esta entrada | install.sh y -XC | install.sh y -XC |
Las dos unidades de systemd nombran /etc/freeradius y el servicio freeradius. En Debian o Fedora, cambia las dos para que coincidan con la tabla. No se mueve nada más.
step-ca
La plantilla de dispositivo es la de Meter el rol en el certificado, línea por línea, con un comentario encima. La plantilla nas es la misma idea con el rol fijo:
{{- /* Certificates for switches, access points and radsecproxy, used
for RadSec. OU=nas is what FreeRADIUS checks for; it maps to no
VLAN, so a nas certificate cannot get a device on the network. */ -}}
{
"subject": {
"commonName": {{ toJson .Subject.CommonName }},
"organizationalUnit": "nas"
},
"sans": {{ toJson .SANs }},
"keyUsage": ["digitalSignature"],
"extKeyUsage": ["clientAuth"],
"crlDistributionPoints": ["http://ca.home.example/1.0/crl"]
}
setup.sh añade los dos provisioners. Ejecútalo una vez dentro del contenedor de step-ca. Pide una contraseña para cada uno, y el bloque de CRL que imprime va en ca.json a mano:
#!/bin/sh
# Run inside the step-ca container, as the step user, once.
# Adds the two provisioners this post uses. Each asks for a password for
# its own key; keep both.
set -e
step ca provisioner add network --type JWK --create \
--x509-max-dur 8760h --x509-default-dur 24h \
--x509-template /home/step/templates/device.tpl
step ca provisioner add nas --type JWK --create \
--x509-max-dur 8760h --x509-default-dur 24h \
--x509-template /home/step/templates/nas.tpl
# Then add this block at the top level of /home/step/config/ca.json and
# restart the container:
#
# "crl": {
# "enabled": true,
# "generateOnRevoke": true,
# "cacheDuration": "24h",
# "idpURL": "http://ca.home.example/1.0/crl"
# }
issue.sh hace todos los certificados de esta entrada, y guarda la receta de revocación de la sección de revocación como comentario para el día que la necesites. Esa receta también se ejecutó contra esta CA, con un laptop2 hecho a propósito:
#!/bin/sh
# Issue the certificates. Run anywhere the step CLI trusts the CA.
set -e
# The RADIUS server: default 24 hours, renewed daily by radius-cert-renew.service.
step ca certificate radius.home.example radius.crt radius.key --provisioner admin
# Devices: a year each, the role decides the VLAN.
step ca certificate laptop1 laptop1.crt laptop1.key --provisioner network --not-after 8760h --set role=staff
step ca certificate cam1 cam1.crt cam1.key --provisioner network --not-after 8760h --set role=iot
step ca certificate tv1 tv1.crt tv1.key --provisioner network --not-after 8760h --set role=media
# Switches, APs and radsecproxy, for RadSec.
step ca certificate switch1 switch1.crt switch1.key --provisioner nas --not-after 8760h
step ca certificate ap1 ap1.crt ap1.key --provisioner nas --not-after 8760h
step ca certificate radsecproxy radsecproxy.crt radsecproxy.key --provisioner nas --not-after 8760h
# A lost device: revoke by serial, no key needed.
# SERIAL=$(step certificate inspect laptop2.crt --format json | jq -r .serial_number)
# TOKEN=$(step ca token "$SERIAL" --revoke --provisioner admin)
# step ca revoke "$SERIAL" --token "$TOKEN" --reason "laptop lost"
FreeRADIUS
Ejecuta install.sh desde el directorio freeradius de la descarga. Copia los archivos, parchea el sitio por defecto y crea los enlaces. Es todo lo que hace:
#!/bin/sh
# Puts these files into a stock FreeRADIUS 3.2 tree. Run from this
# directory. RADDB is /etc/freeradius on the official image,
# /etc/freeradius/3.0 on Debian and Ubuntu, /etc/raddb on Fedora.
set -eu
RADDB=${RADDB:-/etc/freeradius}
cp mods-available/eap mods-available/device_vlan mods-available/mab_vlan "$RADDB/mods-available/"
mkdir -p "$RADDB/mods-config/device_vlan"
cp mods-config/device_vlan/devices mods-config/device_vlan/mab "$RADDB/mods-config/device_vlan/"
cp policy.d/vlan policy.d/mab "$RADDB/policy.d/"
cp sites-available/tls "$RADDB/sites-available/"
cp clients.conf "$RADDB/"
install -m 0755 nas-only.sh "$RADDB/nas-only.sh"
patch -d "$RADDB" -p0 --forward < sites-available/default.patch
for m in eap device_vlan mab_vlan; do
ln -sfn ../mods-available/$m "$RADDB/mods-enabled/$m"
done
ln -sfn ../sites-available/tls "$RADDB/sites-enabled/tls"
# The certificates go in $RADDB/certs/eaptls: radius.crt, radius.key,
# and ca/ holding root_ca.crt, intermediate_ca.crt and the CRL. All of
# it readable by the user FreeRADIUS runs as: freerad, or radiusd on
# Fedora.
mkdir -p "$RADDB/certs/eaptls/ca"
El parche son dos líneas en el sitio por defecto de serie. Se hizo contra 3.2.10 y se aplica a 3.2.7 y 3.2.8 sin nada peor que un desplazamiento:
freeradius/sites-available/default.patch
--- sites-available/default
+++ sites-available/default
@@ -301,6 +301,9 @@
# Make *sure* that 'preprocess' comes before any realm if you
# need to setup hints for the remote radius server
authorize {
+ # MAC Authentication Bypass, policy.d/mab.
+ mab
+
#
# Take a User-Name, and perform some checks on it, for spaces and other
# invalid characters. If the User-Name appears invalid, reject the
@@ -810,6 +813,9 @@
# Once we KNOW that the user has been authenticated, there are
# additional steps we can take.
post-auth {
+ # The VLAN for a certificate login, policy.d/vlan.
+ vlan_by_certificate
+
#
# If you need to have a State attribute, you can
# add it here. e.g. for later CoA-Request with
mods-available/eap sustituye al de serie. Entero:
# EAP for 802.1X: EAP-TLS only, TLS 1.3 only, CRL checked on every login.
# Replaces the stock mods-available/eap. mods-enabled/eap links to it.
eap {
default_eap_type = tls
timer_expire = 60
ignore_unknown_eap_types = no
max_sessions = ${max_requests}
tls-config tls-common {
# The server certificate followed by step-ca's intermediate,
# exactly as "step ca certificate" writes it.
certificate_file = ${certdir}/eaptls/radius.crt
private_key_file = ${certdir}/eaptls/radius.key
# Root, intermediate and the intermediate's CRL, hashed with
# "openssl rehash". radius-crl-refresh keeps the CRL current.
ca_path = ${certdir}/eaptls/ca
ca_file = ${certdir}/eaptls/ca/root_ca.crt
check_crl = yes
# A HUP does not reload a CRL. This does, at 300 s, the minimum.
ca_path_reload_interval = 300
tls_min_version = "1.3"
tls_max_version = "1.3"
cipher_list = "DEFAULT"
ecdh_curve = ""
cache {
enable = no
}
verify {
}
}
tls {
tls = tls-common
}
}
policy.d/vlan es la política de antes con el envoltorio if (&EAP-Message) que menciona la sección de MAB:
# Called first in post-auth. Picks the VLAN for a certificate login:
# the per-device list first, then the role in the certificate's OU.
vlan_by_certificate {
# Certificate logins only. MAB is decided in authorize.
if (&EAP-Message) {
# Only certificates made for this job: client auth, and a role.
if (&TLS-Client-Cert-X509v3-Extended-Key-Usage-OID !~ /1\.3\.6\.1\.5\.5\.7\.3\.2/) {
reject
}
# 1. The per-device list wins.
device_vlan
# 2. Otherwise the role in the certificate decides.
if (!&reply:Tunnel-Private-Group-Id) {
if (&TLS-Client-Cert-Subject =~ /\/OU=([^\/]+)/) {
switch "%{1}" {
case "staff" {
update reply { &Tunnel-Private-Group-Id := "10" }
}
case "iot" {
update reply { &Tunnel-Private-Group-Id := "20" }
}
case "media" {
update reply { &Tunnel-Private-Group-Id := "30" }
}
case {
reject
}
}
}
else {
reject
}
}
update reply {
&Tunnel-Type := VLAN
&Tunnel-Medium-Type := IEEE-802
}
}
}
policy.d/mab es la política de la sección de MAB, y mods-available/device_vlan y nas-only.sh son como se mostraron antes, los tres con un comentario añadido. El módulo de la lista de MAB y las dos listas:
freeradius/mods-available/mab_vlan
# MAC Authentication Bypass: MAC address to VLAN, for kit with no
# 802.1X client. The mab policy only accepts VLAN 20 or 99 from here.
# mods-enabled/mab_vlan links to it.
passwd mab_vlan {
filename = ${modconfdir}/device_vlan/mab
format = "*User-Name:=Tunnel-Private-Group-Id"
delimiter = ","
}
freeradius/mods-config/device_vlan/devices
# Certificate CN,VLAN. Overrides the role in the certificate.
# cam1 is quarantined while it is looked at.
cam1,99
freeradius/mods-config/device_vlan/mab
# MAC (12 hex digits, lowercase, no punctuation),VLAN. 20 or 99 only.
aabbccddeeff,20
sites-available/tls sustituye al ejemplo de RadSec de serie. El directorio temporal de verify es ${run_dir}, que ya existe y pertenece al usuario con el que se ejecuta FreeRADIUS, así que no hay nada que crear:
freeradius/sites-available/tls
# RadSec: RADIUS over TLS 1.3 on port 2083, for switches, access
# points and radsecproxy. Replaces the stock sites-available/tls;
# sites-enabled/tls links to it. Requests go to the default site.
listen {
ipaddr = *
port = 2083
type = auth
proto = tcp
virtual_server = default
clients = radsec
limit {
max_connections = 16
lifetime = 0
idle_timeout = 30
}
tls {
certificate_file = ${certdir}/eaptls/radius.crt
private_key_file = ${certdir}/eaptls/radius.key
ca_path = ${certdir}/eaptls/ca
ca_file = ${certdir}/eaptls/ca/root_ca.crt
require_client_cert = yes
check_crl = yes
ca_path_reload_interval = 300
# Only certificates from step-ca's nas provisioner.
verify {
# Where FreeRADIUS writes the certificate for the script to read.
tmpdir = ${run_dir}
client = "${raddbdir}/nas-only.sh %{TLS-Client-Cert-Filename}"
}
tls_min_version = "1.3"
tls_max_version = "1.3"
cipher_list = "DEFAULT"
ecdh_curve = ""
cache {
enable = no
}
}
}
# One entry per switch, AP and proxy, by management address. Over TLS
# the secret does nothing, and FreeRADIUS requires it to be "radsec".
clients radsec {
client switch1 {
ipaddr = 192.0.2.21
proto = tls
secret = radsec
}
client ap1 {
ipaddr = 192.0.2.31
proto = tls
secret = radsec
}
client radsecproxy {
ipaddr = 192.0.2.11
proto = tls
secret = radsec
}
}
clients.conf sustituye al de serie y deja entrar a eapol_test en la propia máquina y a nada más. Los switches y los AP entran por RadSec o a través del proxy:
# UDP clients. Kit talks RadSec (sites-available/tls) or goes through
# radsecproxy, so plain RADIUS is only for testing on this host with
# eapol_test. Replaces the stock clients.conf.
client localhost {
ipaddr = 127.0.0.1
proto = *
secret = CHANGE-ME-test-only
require_message_authenticator = yes
nas_type = other
}
Comprueba primero la configuración. Luego arráncalo en primer plano, con hilos activados para que RadSec funcione:
freeradius -XC
freeradius -fxx -l stdout
Mantenerlo al día
radius-crl-refresh descarga la CRL con la raíz fijada, y rechaza una que no esté firmada por el intermedio o que ya esté caducada, así que una descarga mala deja la lista vieja en su sitio. Nunca cambia una lista buena por basura:
#!/bin/sh
# Fetch step-ca's CRL over HTTPS with the root pinned, and put it where
# FreeRADIUS reads it. FreeRADIUS picks it up within ca_path_reload_interval.
# Installed as /usr/local/sbin/radius-crl-refresh.
set -eu
CA_URL="${CA_URL:-https://ca.home.example:9000}"
DIR="${DIR:-/etc/freeradius/certs/eaptls/ca}"
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT
curl -fsS --cacert "$DIR/root_ca.crt" --tlsv1.3 -o "$TMP/crl.der" "$CA_URL/1.0/crl"
openssl crl -inform DER -in "$TMP/crl.der" -out "$TMP/crl.pem"
# Refuse a list that is not signed by our intermediate, or already stale.
openssl crl -in "$TMP/crl.pem" -noout -CAfile "$DIR/intermediate_ca.crt" 2>&1 | grep -q 'verify OK'
next=$(date -d "$(openssl crl -in "$TMP/crl.pem" -noout -nextupdate | cut -d= -f2)" +%s)
[ "$next" -gt "$(date +%s)" ]
install -m 0644 "$TMP/crl.pem" "$DIR/intermediate.crl.pem.new"
mv -f "$DIR/intermediate.crl.pem.new" "$DIR/intermediate.crl.pem"
openssl rehash "$DIR"
systemd/radius-crl-refresh.service
[Unit]
Description=Fetch step-ca's CRL for FreeRADIUS
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/radius-crl-refresh
systemd/radius-crl-refresh.timer
[Unit]
Description=Fetch step-ca's CRL for FreeRADIUS every hour
[Timer]
OnBootSec=1min
OnUnitActiveSec=1h
RandomizedDelaySec=5min
Persistent=true
[Install]
WantedBy=timers.target
El certificado del servidor vive 24 horas, así que se renueva solo. La renovación aquí conservó la clave y los permisos de los dos archivos, lo que importa porque FreeRADIUS los lee como freerad. Reinicia FreeRADIUS en vez de mandarle un HUP. Por la misma razón que la CRL:
systemd/radius-cert-renew.service
# Keeps FreeRADIUS's 24-hour server certificate renewed, and restarts
# FreeRADIUS on each renewal so it loads the new one. Once, as root:
# step ca bootstrap --ca-url https://ca.home.example:9000 --fingerprint <root fingerprint>
# chgrp freerad radius.key && chmod 0640 radius.key
# Renewal rewrites radius.crt and keeps the key and both files' modes.
[Unit]
Description=Renew FreeRADIUS's server certificate from step-ca
Wants=network-online.target
After=network-online.target
[Service]
ExecStart=/usr/bin/step ca renew --daemon \
--exec "systemctl restart freeradius" \
/etc/freeradius/certs/eaptls/radius.crt /etc/freeradius/certs/eaptls/radius.key
Restart=on-failure
RestartSec=5min
[Install]
WantedBy=multi-user.target
radsecproxy
# radsecproxy beside switches and APs that only speak UDP RADIUS.
# Takes UDP in on the management VLAN, sends RadSec over TLS 1.3 out.
ListenUDP *:1812
LogLevel 3
LogDestination file:///var/log/radsecproxy.log
tls stepca {
CACertificateFile /etc/radsecproxy/root_ca.crt
CertificateFile /etc/radsecproxy/radsecproxy.crt
CertificateKeyFile /etc/radsecproxy/radsecproxy.key
TlsVersion TLS1_3
}
# The kit that only speaks UDP, from 192.0.2.32 to .63 on the management
# VLAN. Netgear caps the secret at 16 alphanumeric characters, so use all
# 16, random:
# openssl rand -base64 32 | tr -dc 'A-Za-z0-9' | head -c 16
client switches {
host 192.0.2.32/27
type udp
secret CHANGE-ME-16chars
RequireMessageAuthenticator on
}
server freeradius {
host radius.home.example
port 2083
type tls
tls stepca
secret radsec
certificateNameCheck on
}
realm * {
server freeradius
}
hostapd
Una máquina Linux que hace de puerto de switch, hablando RadSec. Este es el archivo que ejecutó el puerto simulado:
# A Linux box as an 802.1X wired port, talking RadSec to FreeRADIUS.
# Needs hostapd built with CONFIG_DRIVER_WIRED=y and CONFIG_RADIUS_TLS=y.
interface=eth1
driver=wired
ieee8021x=1
use_pae_group_addr=1
eap_reauth_period=3600
nas_identifier=switch1
auth_server_addr=192.0.2.10
auth_server_port=2083
auth_server_type=TLS
auth_server_shared_secret=radsec
auth_server_ca_cert=/etc/hostapd/root_ca.crt
auth_server_client_cert=/etc/hostapd/switch1.crt
auth_server_private_key=/etc/hostapd/switch1.key
logger_syslog=-1
logger_syslog_level=2
Y para un punto de acceso. Este pasa por radsecproxy sobre UDP, como lo haría un AP de Netgear, y es el archivo que ejecutaron las radios simuladas:
# A Linux access point: one WPA2/WPA3-Enterprise SSID, each client on the
# VLAN RADIUS names. This one talks UDP to radsecproxy, like the Netgear
# APs. For RadSec, build hostapd with CONFIG_RADIUS_TLS=y and use the
# auth_server_* block from hostapd-wired.conf instead.
interface=wlan0
driver=nl80211
ssid=Home
country_code=GB
hw_mode=g
channel=6
ieee8021x=1
eapol_version=2
wpa=2
# WPA-EAP for WPA2-Enterprise; WPA-EAP-SHA256 with ieee80211w=2 for WPA3-Enterprise.
wpa_key_mgmt=WPA-EAP WPA-EAP-SHA256
rsn_pairwise=CCMP
ieee80211w=1
nas_identifier=ap2
own_ip_addr=192.0.2.33
auth_server_addr=192.0.2.11
auth_server_port=1812
auth_server_shared_secret=CHANGE-ME-16chars
radius_require_message_authenticator=1
# 2 = required: no VLAN in the answer means no network.
dynamic_vlan=2
vlan_naming=1
vlan_tagged_interface=eth0
vlan_bridge=brvlan
ctrl_interface=/run/hostapd
logger_syslog=-1
logger_syslog_level=2
Los dispositivos
nmcli.sh es el comando de la sección de dispositivos y su gemelo cableado. Sin NetworkManager, wpa_supplicant usa estos:
clients/wpa_supplicant-wired.conf
# Linux client, wired 802.1X. Run: wpa_supplicant -D wired -i eth0 -c this-file
ctrl_interface=/run/wpa_supplicant
ap_scan=0
network={
key_mgmt=IEEE8021X
eapol_flags=0
eap=TLS
phase1="tls_disable_tlsv1_3=0"
identity="anonymous"
ca_cert="/etc/pki/home/root_ca.crt"
domain_match="radius.home.example"
client_cert="/etc/pki/home/laptop1.crt"
private_key="/etc/pki/home/laptop1.key"
}
clients/wpa_supplicant-wifi.conf
# Linux client, WiFi, without NetworkManager. TLS 1.3, anonymous identity,
# and the server name checked.
ctrl_interface=/run/wpa_supplicant
network={
ssid="Home"
key_mgmt=WPA-EAP WPA-EAP-SHA256
ieee80211w=1
eap=TLS
phase1="tls_disable_tlsv1_3=0"
identity="anonymous"
ca_cert="/etc/pki/home/root_ca.crt"
domain_match="radius.home.example"
client_cert="/etc/pki/home/laptop1.crt"
private_key="/etc/pki/home/laptop1.key"
}
Contra qué se probó cada archivo
| Archivos | Probado contra | Resultado |
|---|---|---|
step-ca/ | step-ca 0.30.2, una CA nueva | Los 7 certificados emitidos con sus roles; laptop2 emitido y revocado con la receta |
freeradius/ | 3.2.10, instalado con install.sh | laptop1 VLAN 10, cam1 99, tv1 30, un certificado nas rechazado; MAB aabbccddeeff VLAN 20, una MAC desconocida rechazada |
radius-crl-refresh | step-ca sobre TLS 1.3 | CRL instalada y con hash; el portátil revocado rechazado en menos de cinco minutos, sin reiniciar |
radius-cert-renew.service | step ca renew 0.30.2 | Certificado renovado; clave y permisos intactos |
radsecproxy.conf | radsecproxy 1.11.2 | EAP-TLS a través de él aceptado; MAB con Message-Authenticator VLAN 20, sin él descartado |
hostapd-wired.conf | hostapd 2.11, compilado con RadSec | VLAN 10, 99 y 30 sobre TLS 1.3 |
hostapd-wifi.conf, wpa_supplicant-wifi.conf | Radios simuladas, a través de radsecproxy | Tres estaciones en un SSID, VLAN 10, 99 y 30; el archivo de cliente tal cual y una estación solo WPA3 se unieron las dos |
wpa_supplicant-wired.conf | eapol_test 2.11 | Las mismas respuestas que la tabla de más arriba |
| Unidades de systemd | systemd-analyze verify | Ninguna queja salvo que los dos programas no están instalados aquí |
Lo que deja a todo el mundo fuera
Cada uno de estos se encontró o se probó al construir esto, salvo la VLAN que falta en un troncal, que necesita un switch real para verse. Casi todos fallan cerrados. Es la forma correcta de fallar, y aun así una mala mañana.
| Fallo | Lo que ves | Arreglo |
|---|---|---|
| CRL caducada o ausente | Todos los dispositivos rechazados; CRL has expired o unable to get certificate CRL | Descargar la CRL con un temporizador y vigilarlo |
| CRL nueva no recogida | Un dispositivo revocado sigue entrando | ca_path_reload_interval = 300, o reiniciar FreeRADIUS |
| El cliente tiene TLS 1.3 desactivado | protocol version, dispositivo rechazado | tls_disable_tlsv1_3=0, tls-1-3-enable o las claves del perfil de Apple |
Archivos de certificado no legibles por freerad | FreeRADIUS no arranca, Permission denied | Hacerlos legibles por freerad, la clave solo por él |
| Certificado sin rol | Rechazado por la política | Volver a emitirlo con el aprovisionador network y --set role= |
| VLAN que no está en el switch o en el troncal del AP | El dispositivo se autentica, no recibe dirección | Crear la VLAN y etiquetarla en todos los troncales a los que pueda llegar el dispositivo |
| Secreto compartido RADIUS equivocado | Ninguna respuesta; FreeRADIUS registra invalid Message-Authenticator! (Shared secret is incorrect.) y el switch reintenta hasta rendirse | El mismo secreto en clients.conf y en el switch o el AP |
| Dispositivo que no comprueba el nombre del servidor | Nada. Funciona, y también funcionaría con un impostor | Poner el nombre del servidor en todos los dispositivos |
| Petición sin Message-Authenticator | Descartada en silencio; el switch reintenta y se rinde | Activarlo en el switch, o poner radsecproxy delante |
Listener RadSec con freeradius -X | Threading must be enabled for TLS sockets | freeradius -fxx -l stdout |
| hostapd compilado sin RadSec | unsupported RADIUS type 'TLS' | Compilar con CONFIG_RADIUS_TLS=y, o usar radsecproxy |
| A radsecproxy se le da la dirección del servidor | No subjectaltname matching address | Usar radius.home.example |
¿Por qué no está esto en toda red corporativa por defecto?
Nada de esta entrada es nuevo. EAP-TLS se publicó en octubre de 199937. Los atributos de VLAN llegaron en septiembre de 20033. Los dos fabricantes de switches de arriba documentan la VLAN dinámica en sus propios manuales, y la CA, el servidor RADIUS y el proxy son software libre y no cuestan nada. Ve por la vía comercial y lo mismo se convierte en una partida del presupuesto: Cisco pone «RADIUS authentication, authorization, and accounting, including 802.1X, MAC authentication bypass» en el nivel Essentials de ISE, con licencia por «concurrent active endpoints», donde un endpoint puede ser una de las «printers, fax machines, or other types of network devices»38 de la red. En cualquier caso, la capacidad lleva más de veinte años metida en el equipo.
Entonces, ¿por qué una red corporativa en 2026 sigue funcionando con una contraseña que comparte todo el mundo?
¿Puedo preguntar por qué tantos administradores y MSP simplemente no se molestan? No quién. Qué deja que siga así. Hay tres respuestas honestas, porque hay tres casos.
Algunos no lo saben. Y ese es el fallo, no la excusa. Saber lo que puede hacer tu propio switch no es algo que te pasa. Asegurarte de que lo sabes es el trabajo, y el manual que lo dice se descarga gratis.
Algunos lo saben, y lo dejan. La contraseña compartida funciona. Nadie abre un ticket por ella. Un certificado por dispositivo es una serie de tareas que no lucen nada hasta el día que desaparece un portátil, y como tal nunca se gana un hueco en la lista. Para un MSP las cuentas son todavía más claras: una red plana con una contraseña sale más barata de mantener, ningún contrato lo mide, y el cliente nunca pregunta. La serie sobre los MSP recorre adónde llevan esos incentivos, y esto es el mismo patrón en una factura más pequeña. Este es el caso al que apunta la pregunta: el administrador que tiene el switch que lo hace, ha leído hasta aquí, y se queda con la clave compartida porque nadie le va a obligar a justificarla.
Algunos de verdad no pueden, todavía. La impresora sin suplicante, la tele que no sabe TLS 1.3, la máquina de negocio que su fabricante no va a actualizar. Es una limitación real y la respuesta es sí, es defendible: MAB a una VLAN vallada, el motivo por escrito, y una fecha al lado para cuando se vaya la máquina.
El fallo no es tener un hueco. Todo el mundo tiene huecos. Es llevar una red corporativa por encima de uno que nunca fuiste a cerrar, con un equipo que venía capaz de cerrarlo.
Todo el mundo llevó una contraseña compartida alguna vez. Mantener una en una red corporativa por elección es la parte que no se puede defender.
Por qué Cyber Essentials no lo pide
Porque nunca se pensó para eso. Cyber Essentials es la base mínima respaldada por el gobierno británico, y «a growing number of organisations require suppliers to be certified to bid for work»39. Está construido contra «the most common internet based cyber security threats»39, y sus requisitos son explícitos sobre dónde se detiene. El equipo inalámbrico queda fuera del alcance cuando un atacante no puede llegar a él desde internet, porque «the Cyber Essentials scheme isn’t concerned with attacks that can only be launched within the signal range of the wireless device»40. Sus cinco controles son cortafuegos, configuración segura, actualizaciones de seguridad, control de acceso de usuarios y protección contra malware40. Ninguno pregunta quién puede entrar en la red en primer lugar. Lo más cerca que llega es «change any default or guessable account passwords»40, y una clave WiFi impresa en el mostrador de recepción no es una cuenta.
Las VLAN sí aparecen, una vez. Un «sub-set» de una organización, uno «segregated from the rest of the organisation by a firewall or VLAN»40, se puede evaluar por separado. Así que el único sitio donde el esquema se encuentra con una VLAN es como forma de trazar la línea alrededor de menos red.
En sus propios términos, es justo. Es un suelo contra los ataques desde internet, y lo dice. El problema es lo que le pasa a un suelo una vez que tiene un certificado colgado en la pared. Se convierte en el objetivo, luego en el techo, y una empresa puede aprobarlo con todos los dispositivos en una red plana detrás de una contraseña que sabe hasta el personal de limpieza. Cualquiera que pase por delante del edificio con un portátil está dentro del modelo de amenaza que importa y fuera del que se evaluó.
Una contraseña es una promesa que nadie cumple
Lo que me llamó la atención, construyendo esto, es lo poco que tuvo de difícil. Cada trampa estaba escrita en algún sitio, en un comentario de configuración o en un manual de fabricante, y cada una cedió con una tarde de laboratorio.
Y aun así la casa funciona con una contraseña de WiFi apuntada en la nevera. No porque la alternativa sea difícil, sino porque la contraseña es lo de serie, y lo de serie nunca tiene que justificarse. Todo el mundo sabe que es compartida. Todo el mundo sabe que se filtra. Nadie la cambia, porque cambiarla significa ir por todos los dispositivos de la casa con la nueva, así que sigue igual durante años, a través de cada invitado, cada inquilino y cada móvil que acabó en el contenedor de reciclaje con ella todavía guardada, y deja entrar a todo el que la ha tenido alguna vez.
Un certificado por dispositivo es otra idea de para qué sirve la red. La red debería saber qué es una cosa, no solo que alguien tecleó una vez la palabra correcta. Ese es un estándar que te exiges a ti mismo sin que nadie lo compruebe, que es el único que vale la pena tener. La cámara no se sienta al lado del portátil porque resultara estar en el mismo SSID. Se sienta donde la pones tú, en equipo que es tuyo, bajo una regla que escribiste y puedes leer. Cuando se porta mal, se mueve con una línea en un archivo de texto. Cuando desaparece, un comando la deja fuera.
El precio son tareas. Una CRL con un temporizador detrás, certificados con una fecha en la agenda, dispositivos probados antes de comprarlos. Ese es el coste, y está en el orden correcto: pagas en un esfuerzo que se ve, en lugar de en una contraseña que lleva años dejando entrar a gente sin hacer ruido.
RFC 3748 — Extensible Authentication Protocol (EAP) — EAP y los papeles de par, autenticador y servidor de autenticación entre los que funciona. ↩︎
RFC 2868 — RADIUS Attributes for Tunnel Protocol Support — define los atributos de túnel que el RFC 3580 reutiliza para las VLAN. ↩︎
RFC 3580 — IEEE 802.1X RADIUS Usage Guidelines — sección 3.31, «Tunnel-Type=VLAN (13) Tunnel-Medium-Type=802 Tunnel-Private-Group-ID=VLANID», y «the VLANID integer value is encoded as a string». ↩︎ ↩︎ ↩︎
Microsoft Security Advisory 2743314 — «An attacker who obtained the MS-CHAP v2 authentication traffic could then use the exploit code to decrypt a user’s credentials.» ↩︎
RFC 5216 — The EAP-TLS Authentication Protocol — autenticación mutua por certificado sobre EAP. ↩︎
RFC 9190 — EAP-TLS 1.3 — «TLS 1.3 also encrypts certificates and some of the extensions. When using EAP-TLS with TLS 1.3, the use of privacy is mandatory and does not cause any additional round trips»; la sección 2.1.7 recomienda NAI anónimos en la respuesta de identidad. ↩︎ ↩︎
FreeRADIUS 3.2.10 — mods-available/eap —
check_crl,check_all_crl«Check if intermediate CAs have been revoked»,allow_expired_crl, y «most EAP supplicants WILL NOT DO TLS 1.3 PROPERLY». ↩︎ ↩︎ ↩︎Smallstep — Certificate templates — los datos de usuario pasados con
--setestán disponibles en la plantilla como.Insecure.User; envuelve siempre los valores entoJson. ↩︎FreeRADIUS 3.2.10 — dictionary.freeradius.internal — los atributos
TLS-Client-Cert-*que lee la política, incluidosTLS-Client-Cert-SubjectyTLS-Client-Cert-X509v3-Extended-Key-Usage-OID. ↩︎FreeRADIUS 3.2.10 — mods-available/passwd — «Attributes marked as ‘=’ are added to reply_items instead of default configure_items». ↩︎
wpa_supplicant — wpa_supplicant.conf — «tls_disable_tlsv1_3=0 - enable TLSv1.3 (experimental - disabled by default)», y
domain_match, la «Constraint for server domain name». ↩︎RFC 2548 — Microsoft Vendor-specific RADIUS Attributes —
MS-MPPE-Send-KeyyMS-MPPE-Recv-Key, cifradas como «b(1) = MD5(S + R + A) c(1) = p(1) xor b(1)», donde S es el secreto compartido. ↩︎BlastRADIUS — CVE-2024-3596; la mitigación recomendada es «to mandate that clients and servers always send and require Message-Authenticator attributes for all requests and responses». ↩︎ ↩︎
RFC 3579 — RADIUS Support For EAP — «the Message-Authenticator attribute MUST be used to protect all Access-Request, Access-Challenge, Access-Accept, and Access-Reject packets containing an EAP-Message attribute.» ↩︎
FreeRADIUS 3.2.10 — radiusd.conf —
require_message_authenticatorylimit_proxy_state; poner los dos a «no» «will allow MITM attackers to create fake Access-Accept packets to the NAS!» ↩︎RFC 6614 — TLS Encryption for RADIUS — RadSec, mayo de 2012, experimental. ↩︎
IETF — RadSec: RADIUS over TLS and DTLS — estado previsto Proposed Standard, deja obsoletos el RFC 6614 y el RFC 7360; estado en el IESG «Has enough positions to pass» cuando se comprobó el 13 de octubre de 2026. ↩︎
FreeRADIUS 3.2.10 — sites-available/tls — el listener RadSec,
require_client_cert,proto = tls, y «TLS clients MUST have the shared secret set to “radsec”». ↩︎hostapd 2.11 source —
hostapd/defconfig: «Include support fo RADIUS/TLS into the RADIUS client»,#CONFIG_RADIUS_TLS=y. ↩︎TP-Link — Omada Software Controller 6.1 release note — «Added support for RadSec (RADIUS over TLS) to secure SSID, MAC-Based, and 802.1X authentications on both APs and Switches», con el firmware más reciente de AP y switch como requisito. ↩︎ ↩︎
TP-Link Omada — 802.1X configuration — RADIUS Proxy: «It does not support the RadSec feature»; con RadSec, «it is recommended to disable Require Message-Authenticator». ↩︎
Netgear — M4250 and M4350 CLI manual —
radius server key: «The secret must be an alphanumeric value not exceeding 16 characters»; ninguna mención de RadSec. ↩︎Netgear — WAX630 user manual — «Enter the number of the UDP port on the access point that is used to access the RADIUS server», y «By default, the password is sharedsecret.» ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
radsecproxy — «made to be able to deploy RadSec (RADIUS over TLS) so that all RADIUS communication across network links could be done using TLS, without modifying existing RADIUS software». Probado aquí con la 1.11.2. ↩︎
RFC 9765 — RADIUS/1.1 — abril de 2025, experimental: «the RADIUS shared secret is no longer used, and all MD5-based packet authentication and attribute obfuscation methods are removed.» ↩︎
FreeRADIUS 3.2.10 — ChangeLog — RADIUS/1.1 está «Disabled by default, can be enabled by passing
--with-radiusv11to the configure script. For now, this is for testing interoperability.» ↩︎Netgear — M4250 user manual — VLAN Assignment Mode, «The default is Disable»; Dynamic VLAN Creation Mode; Authentication Order, «if a client tries 802.1X authentication (based on the user and client credentials) and authentication fails, the interface proceeds with MAB authentication.» El manual del M4350 tiene el mismo texto. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
TP-Link Omada — Dynamic VLAN with the VLAN Assignment function of RADIUS — switches: Authentication Type «Port Based», «enable ‘VLAN Assignment’», «make sure the 802.1X Control mode is Auto»; puntos de acceso: «Enable VLAN Assignment for Wireless Network» en el perfil RADIUS. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
TP-Link — Configuration guide for 802.1X VLAN assignment and MAB — «VLAN Assignment takes effect only when the control type is Port Based»; las VLAN que faltan se crean automáticamente; MAB manda la MAC como «12 hexadecimal digits, all lowercase and no punctuation». ↩︎ ↩︎ ↩︎ ↩︎
Netgear — GS710TUP user manual — el control de puerto basado en MAC «allows multiple supplicants connected to the same port». ↩︎
hostapd — hostapd.conf — VLAN dinámica «parsed from following RADIUS attributes based on RFC 3580 and RFC 2868»;
dynamic_vlan«2 = required; reject authentication if RADIUS server does not include VLAN ID»;vlan_tagged_interfaceyvlan_bridge. Leído del paquete hostapd 2.11. ↩︎Netgear — WAX620 user manual — WPA3 Enterprise: «the encryption is automatically set to GCMP256». ↩︎
Pixel Phone Help — Connect to Wi-Fi networks — «The ‘Do not validate’ setting option used in EAP-PEAP, EAP-TLS and EAP-TTLS configurations has been removed for security reasons.» ↩︎ ↩︎
Apple — WiFi.EAPClientConfiguration —
TLSTrustedServerNames, «If a server presents a certificate that isn’t in this list, the system doesn’t trust it»;TLSMaximumVersiones1.2por defecto. ↩︎ ↩︎ ↩︎NetworkManager — 802-1x settings —
domain-match: «Constraint for server domain name», además deca-cert,client-cert,private-keyyphase1-auth-flags. ↩︎Android Developers — WifiEnterpriseConfig —
setDomainSuffixMatch;TLS_V1_3«Added in API level 34», conWifiManager.isTlsV13Supported()diciendo «whether or not TLS v1.3 is supported». ↩︎ ↩︎RFC 2716 — PPP EAP TLS Authentication Protocol — la especificación original de EAP-TLS, octubre de 1999, sustituida después por el RFC 5216. ↩︎
Cisco — ISE licences — Essentials cubre «RADIUS authentication, authorization, and accounting, including 802.1X, MAC authentication bypass and easy connect»; «Concurrent active endpoints refer to the total number of supported users and devices… printers, fax machines, or other types of network devices.» ↩︎
NCSC — About Cyber Essentials — «A growing number of organisations require suppliers to be certified to bid for work»; protege contra «the most common internet based cyber security threats». ↩︎ ↩︎
NCSC — Cyber Essentials: Requirements for IT Infrastructure v3.3 — abril de 2026. Los dispositivos inalámbricos quedan fuera del alcance cuando no se les puede atacar desde internet: «the Cyber Essentials scheme isn’t concerned with attacks that can only be launched within the signal range of the wireless device»; los cinco controles técnicos; «change any default or guessable account passwords»; un sub-set «is part of the organisation whose network is segregated from the rest of the organisation by a firewall or VLAN». ↩︎ ↩︎ ↩︎ ↩︎