listo funcionmiento por usuario y contraseña
This commit is contained in:
@@ -30,6 +30,8 @@ services:
|
|||||||
- ./freeradius/mods-available/rest:/etc/freeradius/mods-available/rest:ro
|
- ./freeradius/mods-available/rest:/etc/freeradius/mods-available/rest:ro
|
||||||
- ./freeradius/mods-available/rest:/etc/freeradius/mods-enabled/rest:ro
|
- ./freeradius/mods-available/rest:/etc/freeradius/mods-enabled/rest:ro
|
||||||
- ./freeradius/sites-enabled/default:/etc/freeradius/sites-enabled/default: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
|
- ./freeradius/clients.conf:/etc/freeradius/clients.conf:ro
|
||||||
command: ["-X"]
|
command: ["-X"]
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
41
freeradius/mods-available/eap
Normal file
41
freeradius/mods-available/eap
Normal file
@@ -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 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -18,4 +18,11 @@ rest {
|
|||||||
method = "post"
|
method = "post"
|
||||||
body = "json"
|
body = "json"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Autorize para inner-tunnel (EAP)
|
||||||
|
authorize_inner_tunnel {
|
||||||
|
uri = "http://node:3000/authorize-inner"
|
||||||
|
method = "post"
|
||||||
|
body = "json"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
freeradius/mods-config/files/authorize
Normal file
3
freeradius/mods-config/files/authorize
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
user1 Cleartext-Password := "contra1"
|
||||||
|
user2 Cleartext-Password := "contra2"
|
||||||
|
|
||||||
2
freeradius/mods-enabled/eap
Normal file
2
freeradius/mods-enabled/eap
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
$INCLUDE /etc/freeradius/mods-available/eap
|
||||||
|
|
||||||
@@ -12,16 +12,23 @@ server default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
authorize {
|
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
|
rest
|
||||||
# Para laboratorio: aceptar todo siempre (MAC-Auth / pruebas)
|
# Laboratorio: aceptar todo en flujos no EAP
|
||||||
update control {
|
update control {
|
||||||
Auth-Type := Accept
|
Auth-Type := Accept
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticate {
|
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 {
|
Auth-Type Accept {
|
||||||
ok
|
ok
|
||||||
}
|
}
|
||||||
@@ -33,7 +40,7 @@ server default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
post-auth {
|
post-auth {
|
||||||
# Aquí podríamos volver a llamar a REST para atributos dinámicos
|
# Añadir atributos dinámicos (VLAN/BW) también en EAP (outer reply)
|
||||||
# rest
|
rest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
29
freeradius/sites-enabled/inner-tunnel
Normal file
29
freeradius/sites-enabled/inner-tunnel
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,6 +99,46 @@ app.post('/accounting', (req, res) => {
|
|||||||
return res.status(200).json({});
|
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
|
// API: recent requests
|
||||||
app.get('/api/requests', (req, res) => {
|
app.get('/api/requests', (req, res) => {
|
||||||
res.json({ items: requests.slice(-MAX_REQUESTS) });
|
res.json({ items: requests.slice(-MAX_REQUESTS) });
|
||||||
|
|||||||
@@ -58,16 +58,17 @@ let history = [];
|
|||||||
|
|
||||||
function renderItem(ev) {
|
function renderItem(ev) {
|
||||||
const div = document.createElement('div');
|
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');
|
div.className = 'item ' + (isAuth ? 'type-auth' : 'type-acct');
|
||||||
const user = ev.attrs?.['User-Name'] || ev.attrs?.['User-Name*0'] || '-';
|
const user = ev.attrs?.['User-Name'] || ev.attrs?.['User-Name*0'] || '-';
|
||||||
const nas = ev.attrs?.['NAS-IP-Address'] || '-';
|
const nas = ev.attrs?.['NAS-IP-Address'] || '-';
|
||||||
const calling = ev.attrs?.['Calling-Station-Id'] || '-';
|
const calling = ev.attrs?.['Calling-Station-Id'] || '-';
|
||||||
const called = ev.attrs?.['Called-Station-Id'] || '-';
|
const called = ev.attrs?.['Called-Station-Id'] || '-';
|
||||||
|
const kind = ev.type === 'authorize-inner' ? 'EAP Inner' : (isAuth ? 'Authorize' : 'Accounting');
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
<div><strong>${isAuth ? 'Authorize' : 'Accounting'}</strong> • <small>${new Date(ev.ts).toLocaleString()}</small></div>
|
<div><strong>${kind}</strong> • <small>${new Date(ev.ts).toLocaleString()}</small></div>
|
||||||
<div>Usuario: <code>${user}</code> • NAS: <code>${nas}</code> • STA: <code>${calling}</code> • AP: <code>${called}</code></div>
|
<div>Usuario: <code>${user}</code> • NAS: <code>${nas}</code> • STA: <code>${calling}</code> • AP: <code>${called}</code></div>
|
||||||
${isAuth ? `<div>Decisión: <strong>${ev.decision}</strong> • VLAN: <code>${ev.vlan}</code> • BW: <code>${(ev.bandwidth?.down/1e6)||10}↓ / ${(ev.bandwidth?.up/1e6)||10}↑ Mbps</code></div>` : ''}
|
${isAuth ? `<div>Decisión: <strong>${ev.decision||'-'}</strong>${ev.vlan?` • VLAN: <code>${ev.vlan}</code>`:''}${ev.bandwidth?` • BW: <code>${(ev.bandwidth?.down/1e6)||10}↓ / ${(ev.bandwidth?.up/1e6)||10}↑ Mbps</code>`:''}</div>` : ''}
|
||||||
<div class="attrs">${JSON.escape ? JSON.escape(JSON.stringify(ev.attrs, null, 2)) : JSON.stringify(ev.attrs, null, 2)}</div>
|
<div class="attrs">${JSON.escape ? JSON.escape(JSON.stringify(ev.attrs, null, 2)) : JSON.stringify(ev.attrs, null, 2)}</div>
|
||||||
`;
|
`;
|
||||||
list.appendChild(div);
|
list.appendChild(div);
|
||||||
|
|||||||
Reference in New Issue
Block a user