Initial CoreID changes to allow code-based integration
This commit is contained in:
parent
1149fc054a
commit
78c57d7747
2
.gitignore
vendored
2
.gitignore
vendored
@ -9,3 +9,5 @@ tsconfig.tsbuildinfo
|
|||||||
|
|
||||||
# custom certificates
|
# custom certificates
|
||||||
/ssl-*/
|
/ssl-*/
|
||||||
|
|
||||||
|
.idea
|
||||||
|
39
LICENSE
Normal file
39
LICENSE
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
@coreid/node-radius-server - CoreID maintained fork
|
||||||
|
Copyright (C) 2021 Simon Tretter, Garrett Mills
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
Note: this is a fork of Simon Tretter's node-radius-server package
|
||||||
|
modified for use in Starship CoreID. Per the terms of GPLv3, the
|
||||||
|
modifications made are documented below:
|
||||||
|
|
||||||
|
- Introduced the PackageInterface helper class
|
||||||
|
- enables integrating the library into code rather than as a
|
||||||
|
standalone CLI app
|
||||||
|
|
||||||
|
- Modified existing code to use configuration on the PackageInterface
|
||||||
|
class instead of global imports
|
||||||
|
|
||||||
|
- Modified existing code to log through PackageInterface.logger
|
||||||
|
|
||||||
|
- Modified existing code to introduce a credentialMiddleware method
|
||||||
|
to IPacket, which allows external code to customize the credentials
|
||||||
|
before they are sent
|
||||||
|
|
||||||
|
- Modified RadiusService to allow specifying a custom packet decoder
|
||||||
|
method on PackageInterface.
|
||||||
|
|
||||||
|
- Modified UDPServer class to expose a stop() method
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "radius-server",
|
"name": "@coreid/radius-server",
|
||||||
"description": "radius server for google LDAP and TTLS",
|
"description": "CoreID fork of radius server for google LDAP and TTLS",
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">13.10.1"
|
"node": ">13.10.1"
|
||||||
@ -14,7 +14,7 @@
|
|||||||
"dist",
|
"dist",
|
||||||
"ssl"
|
"ssl"
|
||||||
],
|
],
|
||||||
"homepage": "https://github.com/simllll/node-radius-server",
|
"homepage": "https://code.garrettmills.dev/starship/node-radius-server",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"release": "npm run build && standard-version",
|
"release": "npm run build && standard-version",
|
||||||
"debug": "DEBUG=radius:* node --tls-min-v1.0 dist/app.js",
|
"debug": "DEBUG=radius:* node --tls-min-v1.0 dist/app.js",
|
||||||
|
72
src/app.ts
72
src/app.ts
@ -6,39 +6,44 @@ import * as config from '../config';
|
|||||||
import { Authentication } from './auth';
|
import { Authentication } from './auth';
|
||||||
import { IAuthentication } from './types/Authentication';
|
import { IAuthentication } from './types/Authentication';
|
||||||
import { startTLSServer } from './tls/crypt';
|
import { startTLSServer } from './tls/crypt';
|
||||||
|
import PackageInterface from './interface';
|
||||||
|
|
||||||
/* test node version */
|
const packageInterface = PackageInterface.get();
|
||||||
const testSocket = startTLSServer();
|
|
||||||
if (typeof testSocket.tls.exportKeyingMaterial !== 'function') {
|
|
||||||
console.error(`UNSUPPORTED NODE VERSION (${process.version}) FOUND!!`);
|
|
||||||
|
|
||||||
console.log('min version supported is node js 14. run "sudo npx n 14"');
|
const prestartServer = () => {
|
||||||
process.exit(-1);
|
/* test node version */
|
||||||
}
|
const testSocket = startTLSServer();
|
||||||
|
if (typeof testSocket.tls.exportKeyingMaterial !== 'function') {
|
||||||
|
packageInterface.log(`UNSUPPORTED NODE VERSION (${process.version}) FOUND!!`);
|
||||||
|
|
||||||
const { argv } = yargs
|
packageInterface.log('min version supported is node js 14. run "sudo npx n 14"');
|
||||||
.usage('NODE RADIUS Server\nUsage: radius-server')
|
process.exit(-1);
|
||||||
.example('radius-server --port 1812 -s radiussecret', 'start on port 1812 with a secret')
|
}
|
||||||
.default({
|
|
||||||
port: config.port || 1812,
|
const { argv } = yargs
|
||||||
s: config.secret || 'testing123',
|
.usage('NODE RADIUS Server\nUsage: radius-server')
|
||||||
authentication: config.authentication,
|
.example('radius-server --port 1812 -s radiussecret', 'start on port 1812 with a secret')
|
||||||
authenticationOptions: config.authenticationOptions,
|
.default({
|
||||||
})
|
port: config.port || 1812,
|
||||||
.describe('port', 'RADIUS server listener port')
|
s: config.secret || 'testing123',
|
||||||
.alias('s', 'secret')
|
authentication: config.authentication,
|
||||||
.describe('secret', 'RADIUS secret')
|
authenticationOptions: config.authenticationOptions,
|
||||||
.number('port')
|
})
|
||||||
.string(['secret', 'authentication']) as {
|
.describe('port', 'RADIUS server listener port')
|
||||||
argv: { port?: number; secret?: string; authentication?: string; authenticationOptions?: any };
|
.alias('s', 'secret')
|
||||||
|
.describe('secret', 'RADIUS secret')
|
||||||
|
.number('port')
|
||||||
|
.string(['secret', 'authentication']) as {
|
||||||
|
argv: { port?: number; secret?: string; authentication?: string; authenticationOptions?: any };
|
||||||
|
};
|
||||||
|
|
||||||
|
packageInterface.log(`Listener Port: ${argv.port || 1812}`);
|
||||||
|
packageInterface.log(`RADIUS Secret: ${argv.secret}`);
|
||||||
|
packageInterface.log(`Auth ${argv.authentication}`);
|
||||||
|
packageInterface.log(`Auth Config: ${JSON.stringify(argv.authenticationOptions, undefined, 3)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(`Listener Port: ${argv.port || 1812}`);
|
const startServer = async () => {
|
||||||
console.log(`RADIUS Secret: ${argv.secret}`);
|
|
||||||
console.log(`Auth ${argv.authentication}`);
|
|
||||||
console.log(`Auth Config: ${JSON.stringify(argv.authenticationOptions, undefined, 3)}`);
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
/* configure auth mechansim */
|
/* configure auth mechansim */
|
||||||
let auth: IAuthentication;
|
let auth: IAuthentication;
|
||||||
try {
|
try {
|
||||||
@ -47,7 +52,7 @@ console.log(`Auth Config: ${JSON.stringify(argv.authenticationOptions, undefined
|
|||||||
];
|
];
|
||||||
auth = new AuthMechanismus(config.authenticationOptions);
|
auth = new AuthMechanismus(config.authenticationOptions);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('cannot load auth mechanismus', config.authentication);
|
packageInterface.log('cannot load auth mechanismus', config.authentication);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
// start radius server
|
// start radius server
|
||||||
@ -66,7 +71,7 @@ console.log(`Auth Config: ${JSON.stringify(argv.authenticationOptions, undefined
|
|||||||
rinfo.address,
|
rinfo.address,
|
||||||
(err, _bytes) => {
|
(err, _bytes) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log('Error sending response to ', rinfo);
|
packageInterface.log('Error sending response to ', rinfo);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
response.expectAcknowledgment
|
response.expectAcknowledgment
|
||||||
@ -76,4 +81,9 @@ console.log(`Auth Config: ${JSON.stringify(argv.authenticationOptions, undefined
|
|||||||
|
|
||||||
// start server
|
// start server
|
||||||
await server.start();
|
await server.start();
|
||||||
})();
|
};
|
||||||
|
|
||||||
|
if (packageInterface.start) {
|
||||||
|
prestartServer();
|
||||||
|
startServer();
|
||||||
|
}
|
||||||
|
18
src/auth.ts
18
src/auth.ts
@ -1,8 +1,11 @@
|
|||||||
import * as NodeCache from 'node-cache';
|
import * as NodeCache from 'node-cache';
|
||||||
import { Cache, ExpirationStrategy, MemoryStorage } from '@hokify/node-ts-cache';
|
import { Cache, ExpirationStrategy, MemoryStorage } from '@hokify/node-ts-cache';
|
||||||
import { IAuthentication } from './types/Authentication';
|
import { IAuthentication } from './types/Authentication';
|
||||||
|
import PackageInterface from './interface';
|
||||||
|
|
||||||
const cacheStrategy = new ExpirationStrategy(new MemoryStorage());
|
const cacheStrategy = new ExpirationStrategy(new MemoryStorage());
|
||||||
|
const packageInterface = PackageInterface.get();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this is just a simple abstraction to provide
|
* this is just a simple abstraction to provide
|
||||||
* an application layer for caching credentials
|
* an application layer for caching credentials
|
||||||
@ -12,18 +15,25 @@ export class Authentication implements IAuthentication {
|
|||||||
|
|
||||||
constructor(private authenticator: IAuthentication) {}
|
constructor(private authenticator: IAuthentication) {}
|
||||||
|
|
||||||
@Cache(cacheStrategy, { ttl: 60000 })
|
@Cache(cacheStrategy, { ttl: packageInterface.cacheTTL })
|
||||||
async authenticate(username: string, password: string): Promise<boolean> {
|
async authenticate(username: string, password: string): Promise<boolean> {
|
||||||
const cacheKey = `usr:${username}|pwd:${password}`;
|
const cacheKey = `usr:${username}|pwd:${password}`;
|
||||||
const fromCache = this.cache.get(cacheKey) as undefined | boolean;
|
const fromCache = this.cache.get(cacheKey) as undefined | boolean;
|
||||||
if (fromCache !== undefined) {
|
if (fromCache !== undefined) {
|
||||||
console.log(`Cached Auth Result for user ${username}`, fromCache ? 'SUCCESS' : 'Failure');
|
packageInterface.log(
|
||||||
|
`Cached Auth Result for user ${username}`,
|
||||||
|
fromCache ? 'SUCCESS' : 'Failure'
|
||||||
|
);
|
||||||
return fromCache;
|
return fromCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
const authResult = await this.authenticator.authenticate(username, password);
|
const authResult = await this.authenticator.authenticate(username, password);
|
||||||
console.log(`Auth Result for user ${username}`, authResult ? 'SUCCESS' : 'Failure');
|
packageInterface.log(`Auth Result for user ${username}`, authResult ? 'SUCCESS' : 'Failure');
|
||||||
this.cache.set(cacheKey, authResult, authResult ? 86400 : 60); // cache for one day on success, otherwise just for 60 seconds
|
this.cache.set(
|
||||||
|
cacheKey,
|
||||||
|
authResult,
|
||||||
|
authResult ? packageInterface.cacheSuccessTTL : packageInterface.cacheFailTTL
|
||||||
|
); // cache for one day on success, otherwise just for 60 seconds
|
||||||
|
|
||||||
return authResult;
|
return authResult;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { ClientOptions, createClient } from 'ldapjs';
|
import { ClientOptions, createClient } from 'ldapjs';
|
||||||
import debug from 'debug';
|
|
||||||
import * as tls from 'tls';
|
import * as tls from 'tls';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { IAuthentication } from '../types/Authentication';
|
import { IAuthentication } from '../types/Authentication';
|
||||||
|
import PackageInterface from '../interface';
|
||||||
|
|
||||||
|
const packageInterface = PackageInterface.get();
|
||||||
|
const log = (...args) => packageInterface.log(...args);
|
||||||
|
|
||||||
const usernameFields = ['posixUid', 'mail'];
|
const usernameFields = ['posixUid', 'mail'];
|
||||||
|
|
||||||
const log = debug('radius:auth:google-ldap');
|
|
||||||
// TLS:
|
// TLS:
|
||||||
// https://github.com/ldapjs/node-ldapjs/issues/307
|
// https://github.com/ldapjs/node-ldapjs/issues/307
|
||||||
|
|
||||||
@ -55,7 +57,7 @@ export class GoogleLDAPAuth implements IAuthentication {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.fetchDNs().catch((err) => {
|
this.fetchDNs().catch((err) => {
|
||||||
console.error('fatal error google ldap auth, cannot fetch DNs', err);
|
log('fatal error google ldap auth, cannot fetch DNs', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +66,7 @@ export class GoogleLDAPAuth implements IAuthentication {
|
|||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
const ldapDNClient = createClient(this.config).on('error', (error) => {
|
const ldapDNClient = createClient(this.config).on('error', (error) => {
|
||||||
console.error('Error in ldap', error);
|
log('Error in ldap', error);
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -92,7 +94,7 @@ export class GoogleLDAPAuth implements IAuthentication {
|
|||||||
});
|
});
|
||||||
|
|
||||||
res.on('error', (ldapErr) => {
|
res.on('error', (ldapErr) => {
|
||||||
console.error(`error: ${JSON.stringify(ldapErr)}`);
|
log(`error: ${JSON.stringify(ldapErr)}`);
|
||||||
reject(ldapErr);
|
reject(ldapErr);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -144,7 +146,7 @@ export class GoogleLDAPAuth implements IAuthentication {
|
|||||||
return this.authenticate(username, password, count, true);
|
return this.authenticate(username, password, count, true);
|
||||||
}
|
}
|
||||||
// console.log('this.allValidDNsCache', this.allValidDNsCache);
|
// console.log('this.allValidDNsCache', this.allValidDNsCache);
|
||||||
console.error(`invalid username, not found in DN: ${username}`); // , this.allValidDNsCache);
|
log(`invalid username, not found in DN: ${username}`); // , this.allValidDNsCache);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { IAuthentication } from '../types/Authentication';
|
import { IAuthentication } from '../types/Authentication';
|
||||||
|
import PackageInterface from '../interface';
|
||||||
|
|
||||||
|
const packageInterface = PackageInterface.get();
|
||||||
|
const log = (...args) => packageInterface.log(...args);
|
||||||
|
|
||||||
interface IHTTPAuthOptions {
|
interface IHTTPAuthOptions {
|
||||||
url: string;
|
url: string;
|
||||||
@ -30,7 +34,7 @@ export class HTTPAuth implements IAuthentication {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`HTTP authentication failed, response code: ${result.status}`);
|
log(`HTTP authentication failed, response code: ${result.status}`);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import * as imaps from 'imap-simple';
|
import * as imaps from 'imap-simple';
|
||||||
import { IAuthentication } from '../types/Authentication';
|
import { IAuthentication } from '../types/Authentication';
|
||||||
|
import PackageInterface from '../interface';
|
||||||
|
|
||||||
|
const packageInterface = PackageInterface.get();
|
||||||
|
const log = (...args) => packageInterface.log(...args);
|
||||||
|
|
||||||
interface IIMAPAuthOptions {
|
interface IIMAPAuthOptions {
|
||||||
host: string;
|
host: string;
|
||||||
@ -34,7 +38,7 @@ export class IMAPAuth implements IAuthentication {
|
|||||||
if (this.validHosts) {
|
if (this.validHosts) {
|
||||||
const domain = username.split('@').pop();
|
const domain = username.split('@').pop();
|
||||||
if (!domain || !this.validHosts.includes(domain)) {
|
if (!domain || !this.validHosts.includes(domain)) {
|
||||||
console.info('invalid or no domain in username', username, domain);
|
log('invalid or no domain in username', username, domain);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,7 +61,7 @@ export class IMAPAuth implements IAuthentication {
|
|||||||
|
|
||||||
connection.end();
|
connection.end();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('imap auth failed', err);
|
log('imap auth failed', err);
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import * as LdapAuth from 'ldapauth-fork';
|
import * as LdapAuth from 'ldapauth-fork';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { IAuthentication } from '../types/Authentication';
|
import { IAuthentication } from '../types/Authentication';
|
||||||
|
import PackageInterface from '../interface';
|
||||||
|
|
||||||
|
const packageInterface = PackageInterface.get();
|
||||||
|
const log = (...args) => packageInterface.log(...args);
|
||||||
|
|
||||||
interface ILDAPAuthOptions {
|
interface ILDAPAuthOptions {
|
||||||
/** ldap url
|
/** ldap url
|
||||||
@ -44,7 +48,7 @@ export class LDAPAuth implements IAuthentication {
|
|||||||
reconnect: true,
|
reconnect: true,
|
||||||
});
|
});
|
||||||
this.ldap.on('error', (err) => {
|
this.ldap.on('error', (err) => {
|
||||||
console.error('LdapAuth: ', err);
|
log('LdapAuth: ', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +57,7 @@ export class LDAPAuth implements IAuthentication {
|
|||||||
this.ldap.authenticate(username, password, (err, user) => {
|
this.ldap.authenticate(username, password, (err, user) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
resolve(false);
|
resolve(false);
|
||||||
console.error('ldap error', err);
|
log('ldap error', err);
|
||||||
// reject(err);
|
// reject(err);
|
||||||
}
|
}
|
||||||
if (user) resolve(user);
|
if (user) resolve(user);
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import { SMTPClient } from 'smtp-client';
|
import { SMTPClient } from 'smtp-client';
|
||||||
import { IAuthentication } from '../types/Authentication';
|
import { IAuthentication } from '../types/Authentication';
|
||||||
|
import PackageInterface from '../interface';
|
||||||
|
|
||||||
|
const log = (...args) => PackageInterface.get().log(...args);
|
||||||
|
|
||||||
interface ISMTPAuthOptions {
|
interface ISMTPAuthOptions {
|
||||||
host: string;
|
host: string;
|
||||||
@ -37,7 +40,7 @@ export class SMTPAuth implements IAuthentication {
|
|||||||
if (this.validHosts) {
|
if (this.validHosts) {
|
||||||
const domain = username.split('@').pop();
|
const domain = username.split('@').pop();
|
||||||
if (!domain || !this.validHosts.includes(domain)) {
|
if (!domain || !this.validHosts.includes(domain)) {
|
||||||
console.info('invalid or no domain in username', username, domain);
|
log('invalid or no domain in username', username, domain);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,7 +64,7 @@ export class SMTPAuth implements IAuthentication {
|
|||||||
|
|
||||||
s.close(); // runs QUIT command
|
s.close(); // runs QUIT command
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('imap auth failed', err);
|
log('imap auth failed', err);
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
47
src/interface.ts
Normal file
47
src/interface.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import * as radius from 'radius';
|
||||||
|
import * as config from '../config';
|
||||||
|
import { IPacket } from './types/PacketHandler';
|
||||||
|
|
||||||
|
export type PacketDecoder = (msg: Buffer) => {
|
||||||
|
packet?: radius.RadiusPacket & IPacket;
|
||||||
|
secret: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class PackageInterface {
|
||||||
|
private static _instance?: PackageInterface;
|
||||||
|
|
||||||
|
public static get(): PackageInterface {
|
||||||
|
if (!this._instance) {
|
||||||
|
this._instance = new PackageInterface();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public packetDecoder?: PacketDecoder;
|
||||||
|
|
||||||
|
public start = true;
|
||||||
|
|
||||||
|
public cacheTTL = 60000;
|
||||||
|
|
||||||
|
public cacheSuccessTTL = 86400;
|
||||||
|
|
||||||
|
public cacheFailTTL = 60;
|
||||||
|
|
||||||
|
public logger: (...any: unknown[]) => unknown = console.log; // eslint-disable-line no-console
|
||||||
|
|
||||||
|
private config: any = config;
|
||||||
|
|
||||||
|
public log(...any: unknown[]): void {
|
||||||
|
this.logger(...any);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getConfig(): any {
|
||||||
|
return this.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
|
public setConfig(inConfig: any) {
|
||||||
|
this.config = inConfig;
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,11 @@
|
|||||||
import * as radius from 'radius';
|
import * as radius from 'radius';
|
||||||
import { IAuthentication } from '../types/Authentication';
|
import { IAuthentication } from '../types/Authentication';
|
||||||
import { IPacketHandlerResult, PacketResponseCode } from '../types/PacketHandler';
|
import {IPacket, IPacketHandlerResult, PacketResponseCode} from '../types/PacketHandler';
|
||||||
|
|
||||||
import { PacketHandler } from './PacketHandler';
|
import { PacketHandler } from './PacketHandler';
|
||||||
|
import PackageInterface from '../interface';
|
||||||
|
|
||||||
|
const packageInterface = PackageInterface.get();
|
||||||
|
|
||||||
export class RadiusService {
|
export class RadiusService {
|
||||||
private packetHandler: PacketHandler;
|
private packetHandler: PacketHandler;
|
||||||
@ -11,13 +14,29 @@ export class RadiusService {
|
|||||||
this.packetHandler = new PacketHandler(authentication);
|
this.packetHandler = new PacketHandler(authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defaultDecoder(msg: Buffer): { packet?: radius.RadiusPacket & IPacket; secret: string } {
|
||||||
|
const packet = radius.decode({ packet: msg, secret: this.secret });
|
||||||
|
|
||||||
|
return {
|
||||||
|
packet,
|
||||||
|
secret: this.secret,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async handleMessage(
|
async handleMessage(
|
||||||
msg: Buffer
|
msg: Buffer
|
||||||
): Promise<{ data: Buffer; expectAcknowledgment?: boolean } | undefined> {
|
): Promise<{ data: Buffer; expectAcknowledgment?: boolean } | undefined> {
|
||||||
const packet = radius.decode({ packet: msg, secret: this.secret });
|
const { packet, secret } = packageInterface.packetDecoder
|
||||||
|
? packageInterface.packetDecoder(msg)
|
||||||
|
: this.defaultDecoder(msg);
|
||||||
|
|
||||||
|
if (!packet) {
|
||||||
|
packageInterface.log('Unable to parse packet from message.');
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (packet.code !== 'Access-Request') {
|
if (packet.code !== 'Access-Request') {
|
||||||
console.error('unknown packet type: ', packet.code);
|
packageInterface.log('unknown packet type: ', packet.code);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +52,7 @@ export class RadiusService {
|
|||||||
data: radius.encode_response({
|
data: radius.encode_response({
|
||||||
packet,
|
packet,
|
||||||
code: response.code,
|
code: response.code,
|
||||||
secret: this.secret,
|
secret,
|
||||||
attributes: response.attributes,
|
attributes: response.attributes,
|
||||||
}),
|
}),
|
||||||
// if message is accept or reject, we conside this as final message
|
// if message is accept or reject, we conside this as final message
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
// https://tools.ietf.org/html/rfc3748#section-4.1
|
// https://tools.ietf.org/html/rfc3748#section-4.1
|
||||||
|
|
||||||
import * as NodeCache from 'node-cache';
|
import * as NodeCache from 'node-cache';
|
||||||
import debug from 'debug';
|
|
||||||
import { makeid } from '../../helpers';
|
import { makeid } from '../../helpers';
|
||||||
import { IPacket, IPacketHandler, IPacketHandlerResult } from '../../types/PacketHandler';
|
import { IPacket, IPacketHandler, IPacketHandlerResult } from '../../types/PacketHandler';
|
||||||
import { IEAPMethod } from '../../types/EAPMethod';
|
import { IEAPMethod } from '../../types/EAPMethod';
|
||||||
import { buildEAPResponse, decodeEAPHeader } from './eap/EAPHelper';
|
import { buildEAPResponse, decodeEAPHeader } from './eap/EAPHelper';
|
||||||
|
import PackageInterface from '../../interface';
|
||||||
|
|
||||||
const log = debug('radius:eap');
|
const packageInterface = PackageInterface.get();
|
||||||
|
const log = (...args) => packageInterface.log(...args);
|
||||||
|
|
||||||
export class EAPPacketHandler implements IPacketHandler {
|
export class EAPPacketHandler implements IPacketHandler {
|
||||||
private identities = new NodeCache({ useClones: false, stdTTL: 60 }); // queue data maximum for 60 seconds
|
private identities = new NodeCache({ useClones: false, stdTTL: 60 }); // queue data maximum for 60 seconds
|
||||||
@ -66,15 +67,15 @@ export class EAPPacketHandler implements IPacketHandler {
|
|||||||
return buildEAPResponse(identifier, 3); // NAK
|
return buildEAPResponse(identifier, 3); // NAK
|
||||||
case 2: // notification
|
case 2: // notification
|
||||||
log('>>>>>>>>>>>> REQUEST FROM CLIENT: notification', {});
|
log('>>>>>>>>>>>> REQUEST FROM CLIENT: notification', {});
|
||||||
console.info('notification');
|
log('notification');
|
||||||
break;
|
break;
|
||||||
case 4: // md5-challenge
|
case 4: // md5-challenge
|
||||||
log('>>>>>>>>>>>> REQUEST FROM CLIENT: md5-challenge', {});
|
log('>>>>>>>>>>>> REQUEST FROM CLIENT: md5-challenge', {});
|
||||||
|
|
||||||
console.info('md5-challenge');
|
log('md5-challenge');
|
||||||
break;
|
break;
|
||||||
case 254: // expanded type
|
case 254: // expanded type
|
||||||
console.error('not implemented type', type);
|
log('not implemented type', type);
|
||||||
break;
|
break;
|
||||||
case 3: // nak
|
case 3: // nak
|
||||||
// console.log('got NAK', data);
|
// console.log('got NAK', data);
|
||||||
@ -118,7 +119,7 @@ export class EAPPacketHandler implements IPacketHandler {
|
|||||||
method.getEAPType()
|
method.getEAPType()
|
||||||
);
|
);
|
||||||
|
|
||||||
console.error('unsupported type', type, `requesting: ${serverSupportedMethods}`);
|
log('unsupported type', type, `requesting: ${serverSupportedMethods}`);
|
||||||
|
|
||||||
return buildEAPResponse(identifier, 3, Buffer.from(serverSupportedMethods));
|
return buildEAPResponse(identifier, 3, Buffer.from(serverSupportedMethods));
|
||||||
}
|
}
|
||||||
@ -135,7 +136,7 @@ export class EAPPacketHandler implements IPacketHandler {
|
|||||||
// silently ignore;
|
// silently ignore;
|
||||||
return {};
|
return {};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
log(
|
||||||
'decoding of (generic) EAP package failed',
|
'decoding of (generic) EAP package failed',
|
||||||
msg,
|
msg,
|
||||||
err,
|
err,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import debug from 'debug';
|
|
||||||
import { IAuthentication } from '../../types/Authentication';
|
import { IAuthentication } from '../../types/Authentication';
|
||||||
import {
|
import {
|
||||||
IPacket,
|
IPacket,
|
||||||
@ -6,8 +5,10 @@ import {
|
|||||||
IPacketHandlerResult,
|
IPacketHandlerResult,
|
||||||
PacketResponseCode,
|
PacketResponseCode,
|
||||||
} from '../../types/PacketHandler';
|
} from '../../types/PacketHandler';
|
||||||
|
import PackageInterface from '../../interface';
|
||||||
|
|
||||||
const log = debug('radius:user-pwd');
|
const packageInterface = PackageInterface.get();
|
||||||
|
const log = (...args) => packageInterface.log(...args);
|
||||||
|
|
||||||
export class UserPasswordPacketHandler implements IPacketHandler {
|
export class UserPasswordPacketHandler implements IPacketHandler {
|
||||||
constructor(private authentication: IAuthentication) {}
|
constructor(private authentication: IAuthentication) {}
|
||||||
@ -29,10 +30,11 @@ export class UserPasswordPacketHandler implements IPacketHandler {
|
|||||||
log('username', username, username.toString());
|
log('username', username, username.toString());
|
||||||
log('token', password, password.toString());
|
log('token', password, password.toString());
|
||||||
|
|
||||||
const authenticated = await this.authentication.authenticate(
|
const [strUsername, strPassword] = packet.credentialMiddleware
|
||||||
username.toString(),
|
? packet.credentialMiddleware(username.toString(), password.toString())
|
||||||
password.toString()
|
: [username.toString(), password.toString()];
|
||||||
);
|
|
||||||
|
const authenticated = await this.authentication.authenticate(strUsername, strPassword);
|
||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
// success
|
// success
|
||||||
return {
|
return {
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
// https://tools.ietf.org/html/rfc5281 TTLS v0
|
// https://tools.ietf.org/html/rfc5281 TTLS v0
|
||||||
// https://tools.ietf.org/html/draft-funk-eap-ttls-v1-00 TTLS v1 (not implemented)
|
// https://tools.ietf.org/html/draft-funk-eap-ttls-v1-00 TTLS v1 (not implemented)
|
||||||
/* eslint-disable no-bitwise */
|
/* eslint-disable no-bitwise */
|
||||||
import debug from 'debug';
|
|
||||||
import { IPacketHandlerResult, PacketResponseCode } from '../../../../types/PacketHandler';
|
import { IPacketHandlerResult, PacketResponseCode } from '../../../../types/PacketHandler';
|
||||||
import { IEAPMethod } from '../../../../types/EAPMethod';
|
import { IEAPMethod } from '../../../../types/EAPMethod';
|
||||||
import { IAuthentication } from '../../../../types/Authentication';
|
import { IAuthentication } from '../../../../types/Authentication';
|
||||||
import { buildEAPResponse, decodeEAPHeader } from '../EAPHelper';
|
import { buildEAPResponse, decodeEAPHeader } from '../EAPHelper';
|
||||||
|
import PackageInterface from '../../../../interface';
|
||||||
|
|
||||||
const log = debug('radius:eap:gtc');
|
const packageInterface = PackageInterface.get();
|
||||||
|
const log = (...args) => packageInterface.log(...args);
|
||||||
|
|
||||||
export class EAPGTC implements IEAPMethod {
|
export class EAPGTC implements IEAPMethod {
|
||||||
getEAPType(): number {
|
getEAPType(): number {
|
||||||
@ -56,7 +57,7 @@ export class EAPGTC implements IEAPMethod {
|
|||||||
attributes: (success && [['User-Name', username]]) || undefined,
|
attributes: (success && [['User-Name', username]]) || undefined,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('decoding of EAP-GTC package failed', msg, err);
|
log('decoding of EAP-GTC package failed', msg, err);
|
||||||
return {
|
return {
|
||||||
code: PacketResponseCode.AccessReject,
|
code: PacketResponseCode.AccessReject,
|
||||||
};
|
};
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
// https://tools.ietf.org/html/draft-funk-eap-ttls-v1-00 TTLS v1 (not implemented)
|
// https://tools.ietf.org/html/draft-funk-eap-ttls-v1-00 TTLS v1 (not implemented)
|
||||||
/* eslint-disable no-bitwise */
|
/* eslint-disable no-bitwise */
|
||||||
import { RadiusPacket } from 'radius';
|
import { RadiusPacket } from 'radius';
|
||||||
import debug from 'debug';
|
|
||||||
import { IPacketHandlerResult } from '../../../../types/PacketHandler';
|
import { IPacketHandlerResult } from '../../../../types/PacketHandler';
|
||||||
import { IEAPMethod } from '../../../../types/EAPMethod';
|
import { IEAPMethod } from '../../../../types/EAPMethod';
|
||||||
import { IAuthentication } from '../../../../types/Authentication';
|
import { IAuthentication } from '../../../../types/Authentication';
|
||||||
|
import PackageInterface from '../../../../interface';
|
||||||
|
|
||||||
|
const packageInterface = PackageInterface.get();
|
||||||
|
|
||||||
export class EAPMD5 implements IEAPMethod {
|
export class EAPMD5 implements IEAPMethod {
|
||||||
getEAPType(): number {
|
getEAPType(): number {
|
||||||
@ -27,7 +29,7 @@ export class EAPMD5 implements IEAPMethod {
|
|||||||
): Promise<IPacketHandlerResult> {
|
): Promise<IPacketHandlerResult> {
|
||||||
// not implemented
|
// not implemented
|
||||||
|
|
||||||
debug('eap md5 not implemented...');
|
packageInterface.log('eap md5 not implemented...');
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,6 @@ import * as NodeCache from 'node-cache';
|
|||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { attr_id_to_name, attr_name_to_id } from 'radius';
|
import { attr_id_to_name, attr_name_to_id } from 'radius';
|
||||||
import debug from 'debug';
|
|
||||||
|
|
||||||
import { encodeTunnelPW, ITLSServer, startTLSServer } from '../../../../tls/crypt';
|
import { encodeTunnelPW, ITLSServer, startTLSServer } from '../../../../tls/crypt';
|
||||||
import {
|
import {
|
||||||
IPacket,
|
IPacket,
|
||||||
@ -20,8 +18,10 @@ import { MAX_RADIUS_ATTRIBUTE_SIZE, newDeferredPromise } from '../../../../helpe
|
|||||||
import { IEAPMethod } from '../../../../types/EAPMethod';
|
import { IEAPMethod } from '../../../../types/EAPMethod';
|
||||||
import { IAuthentication } from '../../../../types/Authentication';
|
import { IAuthentication } from '../../../../types/Authentication';
|
||||||
import { secret } from '../../../../../config';
|
import { secret } from '../../../../../config';
|
||||||
|
import PackageInterface from '../../../../interface';
|
||||||
|
|
||||||
const log = debug('radius:eap:ttls');
|
const packageInterface = PackageInterface.get();
|
||||||
|
const log = (...args) => packageInterface.log(...args);
|
||||||
|
|
||||||
function tlsHasExportKeyingMaterial(tlsSocket): tlsSocket is {
|
function tlsHasExportKeyingMaterial(tlsSocket): tlsSocket is {
|
||||||
exportKeyingMaterial: (length: number, label: string, context?: Buffer) => Buffer;
|
exportKeyingMaterial: (length: number, label: string, context?: Buffer) => Buffer;
|
||||||
@ -276,7 +276,7 @@ export class EAPTTLS implements IEAPMethod {
|
|||||||
[[17, encodeTunnelPW(keyingMaterial.slice(0, 64), packet.authenticator, secret)]],
|
[[17, encodeTunnelPW(keyingMaterial.slice(0, 64), packet.authenticator, secret)]],
|
||||||
]); // MS-MPPE-Recv-Key
|
]); // MS-MPPE-Recv-Key
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
log(
|
||||||
'FATAL: no exportKeyingMaterial method available!!!, you need latest NODE JS, see https://github.com/nodejs/node/pull/31814'
|
'FATAL: no exportKeyingMaterial method available!!!, you need latest NODE JS, see https://github.com/nodejs/node/pull/31814'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -420,7 +420,7 @@ export class EAPTTLS implements IEAPMethod {
|
|||||||
// send response
|
// send response
|
||||||
return responseData; // this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, encryptedResponseData);
|
return responseData; // this.buildEAPTTLSResponse(identifier, 21, 0x00, stateID, encryptedResponseData);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('decoding of EAP-TTLS package failed', msg, err);
|
log('decoding of EAP-TTLS package failed', msg, err);
|
||||||
return {
|
return {
|
||||||
code: PacketResponseCode.AccessReject,
|
code: PacketResponseCode.AccessReject,
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,9 @@ import * as events from 'events';
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { newDeferredPromise } from '../helpers';
|
import { newDeferredPromise } from '../helpers';
|
||||||
import { IServer } from '../types/Server';
|
import { IServer } from '../types/Server';
|
||||||
|
import PackageInterface from '../interface';
|
||||||
|
|
||||||
|
const packageInterface = PackageInterface.get();
|
||||||
|
|
||||||
export class UDPServer extends events.EventEmitter implements IServer {
|
export class UDPServer extends events.EventEmitter implements IServer {
|
||||||
static MAX_RETRIES = 3;
|
static MAX_RETRIES = 3;
|
||||||
@ -53,7 +56,7 @@ export class UDPServer extends events.EventEmitter implements IServer {
|
|||||||
const startServer = newDeferredPromise();
|
const startServer = newDeferredPromise();
|
||||||
this.server.on('listening', () => {
|
this.server.on('listening', () => {
|
||||||
const address = this.server.address();
|
const address = this.server.address();
|
||||||
console.log(`radius server listening ${address.address}:${address.port}`);
|
packageInterface.log(`radius server listening ${address.address}:${address.port}`);
|
||||||
|
|
||||||
this.setupListeners();
|
this.setupListeners();
|
||||||
startServer.resolve();
|
startServer.resolve();
|
||||||
@ -72,6 +75,12 @@ export class UDPServer extends events.EventEmitter implements IServer {
|
|||||||
return startServer.promise;
|
return startServer.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stop(): Promise<void> {
|
||||||
|
return new Promise<void>((res) => {
|
||||||
|
this.server.close(() => res());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private setupListeners() {
|
private setupListeners() {
|
||||||
this.server.on('message', (message, rinfo) => this.emit('message', message, rinfo));
|
this.server.on('message', (message, rinfo) => this.emit('message', message, rinfo));
|
||||||
}
|
}
|
||||||
|
@ -3,33 +3,36 @@ import * as tls from 'tls';
|
|||||||
import { createSecureContext } from 'tls';
|
import { createSecureContext } from 'tls';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import * as DuplexPair from 'native-duplexpair';
|
import * as DuplexPair from 'native-duplexpair';
|
||||||
import debug from 'debug';
|
|
||||||
import * as NodeCache from 'node-cache';
|
import * as NodeCache from 'node-cache';
|
||||||
// import * as constants from 'constants';
|
// import * as constants from 'constants';
|
||||||
import * as config from '../../config';
|
import PackageInterface from '../interface';
|
||||||
|
|
||||||
const log = debug('radius:tls');
|
const packageInterface = PackageInterface.get();
|
||||||
|
|
||||||
|
const log = (...args) => packageInterface.log(...args);
|
||||||
|
|
||||||
// https://nodejs.org/api/tls.html
|
// https://nodejs.org/api/tls.html
|
||||||
const tlsOptions: tls.SecureContextOptions = {
|
export function getTLSSecureContext(): tls.SecureContext {
|
||||||
...config.certificate,
|
const tlsOptions: tls.SecureContextOptions = {
|
||||||
};
|
...packageInterface.getConfig().certificate,
|
||||||
log('tlsOptions', tlsOptions);
|
};
|
||||||
const secureContext = createSecureContext(tlsOptions);
|
|
||||||
|
return createSecureContext(tlsOptions);
|
||||||
|
}
|
||||||
|
|
||||||
export interface ITLSServer {
|
export interface ITLSServer {
|
||||||
events: events.EventEmitter;
|
events: events.EventEmitter;
|
||||||
tls: tls.TLSSocket;
|
tls: tls.TLSSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resumeSessions = new NodeCache({ stdTTL: 86400 }); // session reidentification maximum 1 day
|
const resumeSessions = new NodeCache({ stdTTL: packageInterface.cacheTTL }); // session reidentification maximum 1 day
|
||||||
|
|
||||||
export function startTLSServer(): ITLSServer {
|
export function startTLSServer(): ITLSServer {
|
||||||
const duplexpair = new DuplexPair();
|
const duplexpair = new DuplexPair();
|
||||||
const emitter = new events.EventEmitter();
|
const emitter = new events.EventEmitter();
|
||||||
|
|
||||||
const cleartext = new tls.TLSSocket(duplexpair.socket1, {
|
const cleartext = new tls.TLSSocket(duplexpair.socket1, {
|
||||||
secureContext,
|
secureContext: getTLSSecureContext(),
|
||||||
isServer: true,
|
isServer: true,
|
||||||
// enableTrace: true,
|
// enableTrace: true,
|
||||||
rejectUnauthorized: false,
|
rejectUnauthorized: false,
|
||||||
@ -133,14 +136,14 @@ export function encodeTunnelPW(key: Buffer, authenticator: Buffer, secret: strin
|
|||||||
// https://tools.ietf.org/html/rfc2548
|
// https://tools.ietf.org/html/rfc2548
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Salt
|
* Salt
|
||||||
The Salt field is two octets in length and is used to ensure the
|
The Salt field is two octets in length and is used to ensure the
|
||||||
uniqueness of the keys used to encrypt each of the encrypted
|
uniqueness of the keys used to encrypt each of the encrypted
|
||||||
attributes occurring in a given Access-Accept packet. The most
|
attributes occurring in a given Access-Accept packet. The most
|
||||||
significant bit (leftmost) of the Salt field MUST be set (1). The
|
significant bit (leftmost) of the Salt field MUST be set (1). The
|
||||||
contents of each Salt field in a given Access-Accept packet MUST
|
contents of each Salt field in a given Access-Accept packet MUST
|
||||||
be unique.
|
be unique.
|
||||||
*/
|
*/
|
||||||
const salt = crypto.randomBytes(2);
|
const salt = crypto.randomBytes(2);
|
||||||
|
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
@ -183,7 +186,7 @@ export function encodeTunnelPW(key: Buffer, authenticator: Buffer, secret: strin
|
|||||||
is performed in the following manner ('+' indicates
|
is performed in the following manner ('+' indicates
|
||||||
concatenation):
|
concatenation):
|
||||||
|
|
||||||
Zorn Informational [Page 21]
|
Zorn Informational [Page 21]
|
||||||
|
|
||||||
RFC 2548 Microsoft Vendor-specific RADIUS Attributes March 1999
|
RFC 2548 Microsoft Vendor-specific RADIUS Attributes March 1999
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ export interface IPacketAttributes {
|
|||||||
export interface IPacket {
|
export interface IPacket {
|
||||||
attributes: { [key: string]: string | Buffer };
|
attributes: { [key: string]: string | Buffer };
|
||||||
authenticator?: Buffer;
|
authenticator?: Buffer;
|
||||||
|
credentialMiddleware?: (username: string, password: string) => [string, string];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPacketHandler {
|
export interface IPacketHandler {
|
||||||
|
Loading…
Reference in New Issue
Block a user