From c77ec9943b6c46607196fe165aadeb6de417351a Mon Sep 17 00:00:00 2001 From: josedario87 Date: Wed, 24 Sep 2025 16:08:16 -0600 Subject: [PATCH] =?UTF-8?q?listo=20funcionmiento=20por=20usuario=20y=20con?= =?UTF-8?q?trase=C3=B1a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 2 ++ freeradius/mods-available/eap | 41 ++++++++++++++++++++++++++ freeradius/mods-available/rest | 7 +++++ freeradius/mods-config/files/authorize | 3 ++ freeradius/mods-enabled/eap | 2 ++ freeradius/sites-enabled/default | 17 +++++++---- freeradius/sites-enabled/inner-tunnel | 29 ++++++++++++++++++ node-api/index.js | 40 +++++++++++++++++++++++++ node-api/public/index.html | 7 +++-- 9 files changed, 140 insertions(+), 8 deletions(-) create mode 100644 freeradius/mods-available/eap create mode 100644 freeradius/mods-config/files/authorize create mode 100644 freeradius/mods-enabled/eap create mode 100644 freeradius/sites-enabled/inner-tunnel diff --git a/docker-compose.yml b/docker-compose.yml index 0b524db..3ff0e99 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,6 +30,8 @@ services: - ./freeradius/mods-available/rest:/etc/freeradius/mods-available/rest:ro - ./freeradius/mods-available/rest:/etc/freeradius/mods-enabled/rest:ro - ./freeradius/sites-enabled/default:/etc/freeradius/sites-enabled/default:ro + # inner-tunnel: usar el archivo por defecto del contenedor + - ./freeradius/mods-config/files/authorize:/etc/freeradius/mods-config/files/authorize:ro - ./freeradius/clients.conf:/etc/freeradius/clients.conf:ro command: ["-X"] networks: diff --git a/freeradius/mods-available/eap b/freeradius/mods-available/eap new file mode 100644 index 0000000..58ab96e --- /dev/null +++ b/freeradius/mods-available/eap @@ -0,0 +1,41 @@ +eap { + default_eap_type = peap + timer_expire = 60 + ignore_unknown_eap_types = no + cisco_accounting_username_bug = no + max_sessions = ${max_requests} + + tls-config tls-common { + private_key_password = whatever + private_key_file = ${certdir}/server.pem + certificate_file = ${certdir}/server.pem + ca_file = ${cadir}/ca.pem + dh_file = ${certdir}/dh + random_file = /dev/urandom + fragment_size = 1024 + include_length = yes + auto_chain = yes + } + + tls { + tls = tls-common + } + + ttls { + tls = tls-common + default_eap_type = pap + virtual_server = "inner-tunnel" + } + + peap { + tls = tls-common + default_eap_type = mschapv2 + copy_request_to_tunnel = yes + use_tunneled_reply = yes + virtual_server = "inner-tunnel" + } + + mschapv2 { + } +} + diff --git a/freeradius/mods-available/rest b/freeradius/mods-available/rest index cfc739c..248221e 100644 --- a/freeradius/mods-available/rest +++ b/freeradius/mods-available/rest @@ -18,4 +18,11 @@ rest { method = "post" body = "json" } + + # Autorize para inner-tunnel (EAP) + authorize_inner_tunnel { + uri = "http://node:3000/authorize-inner" + method = "post" + body = "json" + } } diff --git a/freeradius/mods-config/files/authorize b/freeradius/mods-config/files/authorize new file mode 100644 index 0000000..8d59f9d --- /dev/null +++ b/freeradius/mods-config/files/authorize @@ -0,0 +1,3 @@ +user1 Cleartext-Password := "contra1" +user2 Cleartext-Password := "contra2" + diff --git a/freeradius/mods-enabled/eap b/freeradius/mods-enabled/eap new file mode 100644 index 0000000..3eb3d47 --- /dev/null +++ b/freeradius/mods-enabled/eap @@ -0,0 +1,2 @@ +$INCLUDE /etc/freeradius/mods-available/eap + diff --git a/freeradius/sites-enabled/default b/freeradius/sites-enabled/default index c7d46dd..b75fbe7 100644 --- a/freeradius/sites-enabled/default +++ b/freeradius/sites-enabled/default @@ -12,16 +12,23 @@ server default { } authorize { - # Llama a la API REST para decidir y añadir atributos + # Si es EAP (WPA-Enterprise), procesar EAP y salir para no interferir + if (&EAP-Message) { + eap + return + } + # MAC-Auth / Portal: Llama a la API REST para decidir y añadir atributos rest - # Para laboratorio: aceptar todo siempre (MAC-Auth / pruebas) + # Laboratorio: aceptar todo en flujos no EAP update control { Auth-Type := Accept } } authenticate { - # Aceptar todo cuando control:Auth-Type := Accept + # EAP para WPA-Enterprise + eap + # Aceptar todo cuando control:Auth-Type := Accept (no EAP) Auth-Type Accept { ok } @@ -33,7 +40,7 @@ server default { } post-auth { - # Aquí podríamos volver a llamar a REST para atributos dinámicos - # rest + # Añadir atributos dinámicos (VLAN/BW) también en EAP (outer reply) + rest } } diff --git a/freeradius/sites-enabled/inner-tunnel b/freeradius/sites-enabled/inner-tunnel new file mode 100644 index 0000000..d261615 --- /dev/null +++ b/freeradius/sites-enabled/inner-tunnel @@ -0,0 +1,29 @@ +server inner-tunnel { + listen { + type = auth + ipaddr = 127.0.0.1 + port = 18120 + } + + authorize { + # Primero obtenemos credenciales del usuario desde el API + rest.authorize_inner_tunnel + # Luego dejamos que EAP procese (PEAP/MSCHAPv2) + eap + # mschap puede establecer Auth-Type si procede + mschap + } + + authenticate { + # Autenticación EAP (PEAP/MSCHAPv2) + eap + Auth-Type MS-CHAP { + mschap + } + } + + post-auth { + # Aquí podríamos añadir lógica adicional de auditoría si se desea + # No agregamos atributos de reply aquí; se añadirán en el outer post-auth + } +} diff --git a/node-api/index.js b/node-api/index.js index 015f114..ec49268 100644 --- a/node-api/index.js +++ b/node-api/index.js @@ -99,6 +99,46 @@ app.post('/accounting', (req, res) => { return res.status(200).json({}); }); +// Authorize inner-tunnel (EAP): devolver Cleartext-Password para el usuario +app.post('/authorize-inner', (req, res) => { + console.log('--- RADIUS Authorize (inner) ---'); + console.log(JSON.stringify(req.body, null, 2)); + + const attrs = normalizeAttributes(req.body); + const users = { + 'user1': 'contra1', + 'user2': 'contra2', + }; + const username = (attrs['User-Name'] || '').toString(); + const password = users[username]; + + if (!password) { + pushRequest({ + id: Date.now() + ':' + Math.random().toString(16).slice(2), + ts: new Date().toISOString(), + type: 'authorize-inner', + attrs, + decision: 'notfound', + }); + // No devolvemos nada -> FreeRADIUS seguirá su flujo y probablemente rechace + return res.status(200).json({}); + } + + pushRequest({ + id: Date.now() + ':' + Math.random().toString(16).slice(2), + ts: new Date().toISOString(), + type: 'authorize-inner', + attrs, + decision: 'provide-password', + }); + + return res.status(200).json({ + control: { + 'Cleartext-Password': password, + }, + }); +}); + // API: recent requests app.get('/api/requests', (req, res) => { res.json({ items: requests.slice(-MAX_REQUESTS) }); diff --git a/node-api/public/index.html b/node-api/public/index.html index 7025b8b..eeb1dc1 100644 --- a/node-api/public/index.html +++ b/node-api/public/index.html @@ -58,16 +58,17 @@ let history = []; function renderItem(ev) { const div = document.createElement('div'); - const isAuth = ev.type === 'authorize'; + const isAuth = ev.type === 'authorize' || ev.type === 'authorize-inner'; div.className = 'item ' + (isAuth ? 'type-auth' : 'type-acct'); const user = ev.attrs?.['User-Name'] || ev.attrs?.['User-Name*0'] || '-'; const nas = ev.attrs?.['NAS-IP-Address'] || '-'; const calling = ev.attrs?.['Calling-Station-Id'] || '-'; const called = ev.attrs?.['Called-Station-Id'] || '-'; + const kind = ev.type === 'authorize-inner' ? 'EAP Inner' : (isAuth ? 'Authorize' : 'Accounting'); div.innerHTML = ` -
${isAuth ? 'Authorize' : 'Accounting'}${new Date(ev.ts).toLocaleString()}
+
${kind}${new Date(ev.ts).toLocaleString()}
Usuario: ${user} • NAS: ${nas} • STA: ${calling} • AP: ${called}
- ${isAuth ? `
Decisión: ${ev.decision} • VLAN: ${ev.vlan} • BW: ${(ev.bandwidth?.down/1e6)||10}↓ / ${(ev.bandwidth?.up/1e6)||10}↑ Mbps
` : ''} + ${isAuth ? `
Decisión: ${ev.decision||'-'}${ev.vlan?` • VLAN: ${ev.vlan}`:''}${ev.bandwidth?` • BW: ${(ev.bandwidth?.down/1e6)||10}↓ / ${(ev.bandwidth?.up/1e6)||10}↑ Mbps`:''}
` : ''}
${JSON.escape ? JSON.escape(JSON.stringify(ev.attrs, null, 2)) : JSON.stringify(ev.attrs, null, 2)}
`; list.appendChild(div);