Initial stack: FreeRADIUS + Node API + docker-compose
This commit is contained in:
38
docker-compose.yml
Normal file
38
docker-compose.yml
Normal file
@@ -0,0 +1,38 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
node:
|
||||
build: ./node-api
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- VLAN_ID=2
|
||||
- MAX_UP=10000000
|
||||
- MAX_DOWN=10000000
|
||||
networks:
|
||||
- radius_net
|
||||
|
||||
freeradius:
|
||||
image: freeradius/freeradius-server:3.2.2
|
||||
depends_on:
|
||||
- node
|
||||
ports:
|
||||
- "1812:1812/udp"
|
||||
- "1813:1813/udp"
|
||||
environment:
|
||||
- REST_ENDPOINT=http://node:3000
|
||||
- RADIUS_CLIENTS_CIDR=${RADIUS_CLIENTS_CIDR:-0.0.0.0/0}
|
||||
- RADIUS_SHARED_SECRET=${RADIUS_SHARED_SECRET:-testing123}
|
||||
volumes:
|
||||
- ./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
|
||||
- ./freeradius/clients.conf:/etc/freeradius/clients.conf:ro
|
||||
command: ["-X"]
|
||||
networks:
|
||||
- radius_net
|
||||
|
||||
networks:
|
||||
radius_net:
|
||||
driver: bridge
|
||||
|
||||
7
freeradius/clients.conf
Normal file
7
freeradius/clients.conf
Normal file
@@ -0,0 +1,7 @@
|
||||
client unifi {
|
||||
ipaddr = %{env:RADIUS_CLIENTS_CIDR}
|
||||
secret = %{env:RADIUS_SHARED_SECRET}
|
||||
require_message_authenticator = no
|
||||
nastype = other
|
||||
}
|
||||
|
||||
22
freeradius/mods-available/rest
Normal file
22
freeradius/mods-available/rest
Normal file
@@ -0,0 +1,22 @@
|
||||
rest {
|
||||
# Timeouts
|
||||
connect_timeout = 4
|
||||
read_timeout = 8
|
||||
|
||||
# Authorize: llama al API Node
|
||||
authorize {
|
||||
uri = "%{env:REST_ENDPOINT:-http://node:3000}/authorize"
|
||||
method = "post"
|
||||
body = "json"
|
||||
# send_all = yes -> envía todos los atributos del paquete
|
||||
# por defecto rlm_rest ya serializa atributos en JSON
|
||||
}
|
||||
|
||||
# Accounting: opcional
|
||||
accounting {
|
||||
uri = "%{env:REST_ENDPOINT:-http://node:3000}/accounting"
|
||||
method = "post"
|
||||
body = "json"
|
||||
}
|
||||
}
|
||||
|
||||
43
freeradius/sites-enabled/default
Normal file
43
freeradius/sites-enabled/default
Normal file
@@ -0,0 +1,43 @@
|
||||
server default {
|
||||
listen {
|
||||
type = auth
|
||||
ipaddr = *
|
||||
port = 1812
|
||||
}
|
||||
|
||||
listen {
|
||||
type = acct
|
||||
ipaddr = *
|
||||
port = 1813
|
||||
}
|
||||
|
||||
authorize {
|
||||
# Llama a la API REST para decidir y añadir atributos
|
||||
rest
|
||||
|
||||
# Si la API no estableció Auth-Type, aceptamos por defecto (demo)
|
||||
if (!&control:Auth-Type) {
|
||||
update control {
|
||||
Auth-Type := Accept
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
authenticate {
|
||||
# Aceptar todo cuando control:Auth-Type := Accept
|
||||
Auth-Type Accept {
|
||||
ok
|
||||
}
|
||||
}
|
||||
|
||||
accounting {
|
||||
rest
|
||||
ok
|
||||
}
|
||||
|
||||
post-auth {
|
||||
# Aquí podríamos volver a llamar a REST para atributos dinámicos
|
||||
# rest
|
||||
}
|
||||
}
|
||||
|
||||
11
node-api/Dockerfile
Normal file
11
node-api/Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production || npm i --only=production
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["node", "index.js"]
|
||||
|
||||
56
node-api/index.js
Normal file
56
node-api/index.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import express from 'express';
|
||||
import morgan from 'morgan';
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use(morgan('dev'));
|
||||
|
||||
const VLAN_ID = process.env.VLAN_ID || '2';
|
||||
const MAX_UP = process.env.MAX_UP || '10000000'; // bits per second
|
||||
const MAX_DOWN = process.env.MAX_DOWN || '10000000'; // bits per second
|
||||
|
||||
// Helper: standard Accept with VLAN + bandwidth
|
||||
function buildAcceptPayload(extra = {}) {
|
||||
return {
|
||||
control: {
|
||||
'Auth-Type': 'Accept',
|
||||
...extra.control,
|
||||
},
|
||||
reply: {
|
||||
'Tunnel-Type': 'VLAN',
|
||||
'Tunnel-Medium-Type': 'IEEE-802',
|
||||
'Tunnel-Private-Group-Id': String(VLAN_ID),
|
||||
'WISPr-Bandwidth-Max-Down': String(MAX_DOWN),
|
||||
'WISPr-Bandwidth-Max-Up': String(MAX_UP),
|
||||
...extra.reply,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Authorize endpoint: FreeRADIUS rlm_rest calls this in authorize {}
|
||||
app.post('/authorize', (req, res) => {
|
||||
console.log('--- RADIUS Authorize Request ---');
|
||||
console.log(JSON.stringify(req.body, null, 2));
|
||||
|
||||
// Por ahora aprobamos todas las solicitudes válidas (si traen User-Name)
|
||||
const attrs = (req.body && (req.body.attributes || req.body.request)) || {};
|
||||
if (!attrs['User-Name'] && !attrs['User-Name*0']) {
|
||||
// Responder vacío -> no cambia nada; o devolver 204
|
||||
return res.status(200).json({});
|
||||
}
|
||||
|
||||
return res.status(200).json(buildAcceptPayload());
|
||||
});
|
||||
|
||||
// Accounting endpoint (opcional)
|
||||
app.post('/accounting', (req, res) => {
|
||||
console.log('--- RADIUS Accounting ---');
|
||||
console.log(JSON.stringify(req.body, null, 2));
|
||||
return res.status(200).json({});
|
||||
});
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
app.listen(port, () => {
|
||||
console.log(`Node RADIUS REST API listening on :${port}`);
|
||||
});
|
||||
|
||||
12
node-api/package.json
Normal file
12
node-api/package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "radius-rest-api",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"express": "^4.19.2",
|
||||
"morgan": "^1.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user