tacacs-plus
v1.2.3
Published
tacacs+ helpers for node
Downloads
305
Readme
tacacs-plus [TACACS+]
This is a simple TACACS+ library to help with basic encoding and decoding of TACACS+ authentication and authorization packets.
More information on TACACS+ can be found here, https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-05.
Basic Usage
var tacacs = require('tacacs-plus');
// receive or send raw TCP packet (port 49) to a TACACS+ server or client
var decoded = tacacs.decodePacket({ packet: raw_data, secret: 'your_key' });
The decoded object, depending on the sequence of packets, should be something along the lines of this.
{
"header": {
"majorVersion": 12,
"minorVersion": 0,
"versionByte": 193,
"type": 1,
"sequenceNumber": 1,
"flags": 1,
"is_encrypted": false,
"is_singleConnect": false,
"sessionId": 1,
"length": 34
},
"rawData": {
"type": "Buffer",
"data": [ ... ]
},
"data": {
"action": 1,
"privLvl": 0,
"authenType": 0,
"authenService": 0,
"userLen": 9,
"portLen": 4,
"remAddrLen": 13,
"dataLen": 0,
"user": "your_user_name",
"port": "tty10",
"remAddr": "your_location"
}
}
In certain instances, the data element may not be populated if there is an issue with the type of messages or the sequence number. You can manually decode a message body using the decode functions in the library.
Creating a Simple Auth Start
If you are creating a client, to create a simple auth start to send to a server, simply do something along the lines of the following code snippit.
var tacacs = require('tacacs-plus');
// create the auth start body
var authStart = tacacs.createAuthStart({
action: tacacs.TAC_PLUS_AUTHEN_LOGIN,
privLvl: tacacs.TAC_PLUS_PRIV_LVL_MAX,
authenAype: tacacs.TAC_PLUS_AUTHEN_TYPE_ASCII,
authenAervice: tacacs.TAC_PLUS_AUTHEN_SVC_LOGIN,
user: 'your_user_name',
port: 'tty10',
remAddr: 'your_location',
data: null
});
// create the tacacs+ header
var header = tacacs.createHeader({
majorVersion: tacacs.TAC_PLUS_MAJOR_VER,
minorVersion: tacacs.TAC_PLUS_MINOR_VER_DEFAULT,
type: tacacs.TAC_PLUS_AUTHEN,
sequenceNumber: 0x1,
flags: tacacs.TAC_PLUS_UNENCRYPTED_FLAG,
sessionId: 0x1,
length: authStart.length
});
// combine the header and body
var authStartPacket = Buffer.concat([header, authStart]);
// open a connection and send the raw packet via TCP to the server (this example is not using encryption)
- All decode processes take Buffers that are then converted to objects.
- All create processes take objects and return Buffers of data.
Encryption
You can use the encodeByteData
and decodeByteData
functions to encrypt and decrypt data packets.
Using encryption requires a shared secret key as well as cryptographically secure random Session ID values.
var crypto = require('crypto');
var tacacs = require('tacacs-plus');
// Generate a random 32-bit session
var sessionIdBytes = crypto.randomBytes(4);
var sessionId = Math.abs(sessionIdBytes.readInt32BE(0));
// create the auth start body
var authStart = tacacs.createAuthStart({
action: tacacs.TAC_PLUS_AUTHEN_LOGIN,
privLvl: tacacs.TAC_PLUS_PRIV_LVL_MAX,
authenType: tacacs.TAC_PLUS_AUTHEN_TYPE_ASCII,
authenService: tacacs.TAC_PLUS_AUTHEN_SVC_LOGIN,
user: 'your_user_name',
port: 'tty10',
remAddr: 'your_location',
data: null
});
var version = tacacs.createVersion(tacacs.TAC_PLUS_MAJOR_VER, tacacs.TAC_PLUS_MINOR_VER_DEFAULT);
var sequenceNumber = 1;
var encryptedAuthStart = tacacs.encodeByteData(sessionId, 'your_key', version, sequenceNumber, authStart);
// create the tacacs+ header
var headerOptions = {
majorVersion: tacacs.TAC_PLUS_MAJOR_VER,
minorVersion: tacacs.TAC_PLUS_MINOR_VER_DEFAULT,
type: tacacs.TAC_PLUS_AUTHEN,
sequenceNumber: sequenceNumber,
flags: 0x0, // setting this to zero assumes encryption is being used
sessionId: sessionId,
length: authStart.length
}
var header = tacacs.createHeader(headerOptions);
var packetToSend = Buffer.concat([header, encryptedAuthStart]);
// open a connection and send the packet via TCP to the server
Sample Communications
Here is a very simple client that sends a auth start packet to a server, then the server responds to the client... this is a very simple "getting started" sample, that requires a lot more development to implement a full workflow, but it illustrates how to start.
For a more complete client example, see examples/client.js
.
var crypto = require('crypto');
var tacacs = require('tacacs-plus');
// SAMPLE SERVER
var server = net.createServer(function (c) {
console.log('Server: Connection opened.');
c.on('error', function (err) {
console.log(err);
});
c.on('end', function () {
console.log('Server: Connection closed.');
});
c.on('data', function (data) {
var replyOptions = {};
var replyHeader = {
majorVersion: tacacs.TAC_PLUS_MAJOR_VER,
minorVersion: tacacs.TAC_PLUS_MINOR_VER_DEFAULT,
type: tacacs.TAC_PLUS_AUTHEN,
sequenceNumber: 1,
flags: 0x0,
sessionId: 0x0,
length: 0x0
};
console.log('Server: Received ', (data ? data.length : 0), ' bytes.');
console.log('Server: ' + data.toString('hex'));
var decodedPacket = tacacs.decodePacket({ packet: data, key: 'your_key' });
console.log('Server: Decoded TACACS+ request.');
console.log(JSON.stringify(decodedPacket));
decodedPacket.header = decodedPacket.header || {};
var replyHeader = decodedPacket.header;
replyHeader.sequenceNumber++;
replyHeader.sessionId = decodedPacket.header.sessionId;
// build the auth reply (this is all the server should ever send for auth)
// in this example we will send a get password command (TAC_PLUS_AUTHEN_STATUS_GETPASS)
var replyOptions = {
status: tacacs.TAC_PLUS_AUTHEN_STATUS_GETPASS,
flags: 0x0,
message: 'Please enter your password: ',
data: null
};
var replyBytes = tacacs.createAuthReply(replyOptions);
replyHeader.length = replyBytes.length;
var headerBytes = tacacs.createHeader(replyHeader);
var encryptedResponse = tacacs.encodeByteData(replyHeader.sessionId, 'your_key', tacacs.createVersion(replyHeader.majorVersion, replyHeader.minorVersion), replyHeader.sequenceNumber, replyBytes);
replyBytes = Buffer.concat([headerBytes, encryptedResponse]);
c.write(replyBytes);
});
});
server.on('error', function (err) {
console.log('Server: ' + err);
});
server.listen({ port: 49 }, function () {
console.log('Server: listening...');
});
// SIMPLE CLIENT
var client = net.connect(49, '127.0.0.1', function () {
console.log('Client connected!');
// now that we've connected, send the first auth packet
var sessionIdBytes = crypto.randomBytes(4);
var sessionId = Math.abs(sessionIdBytes.readInt32BE(0));
// create the auth start body
var authStart = tacacs.createAuthStart({
action: tacacs.TAC_PLUS_AUTHEN_LOGIN,
privLvl: tacacs.TAC_PLUS_PRIV_LVL_MAX,
authenType: tacacs.TAC_PLUS_AUTHEN_TYPE_ASCII,
authenService: tacacs.TAC_PLUS_AUTHEN_SVC_LOGIN,
user: 'your_user_name',
port: 'tty10',
remAddr: 'your_location',
data: null
});
var version = tacacs.createVersion(tacacs.TAC_PLUS_MAJOR_VER, tacacs.TAC_PLUS_MINOR_VER_DEFAULT);
var sequenceNumber = 1;
var encryptedAuthStart = tacacs.encodeByteData(sessionId, 'your_key', version, sequenceNumber, authStart);
// create the tacacs+ header
var headerOptions = {
majorVersion: tacacs.TAC_PLUS_MAJOR_VER,
minorVersion: tacacs.TAC_PLUS_MINOR_VER_DEFAULT,
type: tacacs.TAC_PLUS_AUTHEN,
sequenceNumber: sequenceNumber,
flags: 0x0, // setting this to zero assumes encryption is being used
sessionId: sessionId,
length: authStart.length
}
var header = tacacs.createHeader(headerOptions);
var packetToSend = Buffer.concat([header, encryptedAuthStart]);
// send the auth start packet to the server
console.log('Client: Sending: ' + packetToSend.length + ' bytes.');
console.log('Client: ' + packetToSend.toString('hex'));
client.write(packetToSend);
});
client.on('error', function (err) { console.log(err); });
client.on('data', function (data) {
if (data) {
console.log('Client: Received Data: ' + data.toString('hex'));
// decode response
var resp = tacacs.decodePacket({ packet: data, key: 'your_key' });
console.log('Client: Decoded Response: ' + JSON.stringify(resp, null, 2));
}
else {
console.log('Client: No data!');
}
});
Authorization
simple authorization request and responses can also be created by using the createAuthorizationRequest and createAuthorizationResponse and their associated decode processes.
const tacacs = require('tacacs-plus');
var authorReq = tacacs.createAuthorizationRequest({
authenMethod: tacacs.TAC_PLUS_AUTHEN_METH_NOT_SET,
privLvl: tacacs.TAC_PLUS_PRIV_LVL_USER,
authenType: tacacs.TAC_PLUS_AUTHEN_TYPE_ASCII,
authenService: tacacs.TAC_PLUS_AUTHEN_TYPE_NOT_SET,
user: 'user',
port: 'port',
remAddr: 'rem addr',
args: ['test=123', 'test1=456']
});
console.log('Author Request: ' + authorReq.toString('hex'));
var decodedReq = tacacs.decodeAuthorizationRequest(authorReq);
console.log('Author Request: ' + JSON.stringify(decodedReq));
console.log('---------');
var authorResp = tacacs.createAuthorizationResponse({
status: tacacs.TAC_PLUS_AUTHOR_STATUS_ERROR,
args: ['test=123', 'test1=456'],
serverMessage: 'Test Message',
data: 'Test Data'
});
console.log('Author Response: ' + authorResp.toString('hex'));
var decodedResp = tacacs.decodeAuthorizationResponse(authorResp);
console.log('Author Response: ' + JSON.stringify(decodedResp));
Testing Server
A good and easy to spin up testing server is tac_plus running in a docker container. If you have docker setup, simply run the following to start the tac_plus container. More information is available here, https://hub.docker.com/r/dchidell/docker-tacacs.
sudo docker run -it --rm -p 49:49 dchidell/docker-tacacs
Then you can point your client to the docker server IP on port 49 and use the shared key 'ciscotacacskey' and the user 'iosuser' with the password 'cisco'.