Warning
This is unstable and has not been broadly validated against available TACACS+ clients, please use with caution!
Look! It's an Axe! No, a hatchet!
... it's a rusty hatchet!
... it's RATCHET!
⭐ Featured on alternativeto.net
Note
This is only the lightweight configurable AAA backend, if you want a fully-integrated solution with Docker and a Frontend check out https://github.com/meltyness/ratchet-cycle
This is a TACACS+ server.
It implements PAP, ASCII Authentication, and a basic policy evaluation that seems to support Command Authorization.
It's written in a memory-safe language, and has very few dependencies.
There's a frontend available here, just add glue, middleware, or Nullsoft Installer; a docker example is available.
See https://github.com/meltyness/ratchet-cycle for a neat way to deploy the whole thing.
This server implements a slim portion of the TACACS+ protocol.
It's very fast and lightweight.
Performance impact hierarchy:
- Credential hashing / crypto
- I/O blocking / contention
There's no external dependencies, so you can just do
cargo run --release
But realistically you'll need a script with the following form:
#!/bin/bash
cargo build --release
# I know Rust is 'memory safe' but let's not go crazy, here.
sudo setcap CAP_NET_BIND_SERVICE=+ep ./target/release/ratchet
RATCHET_READ_CLIENTS="./clients_cmd.sh" \
RATCHET_READ_CREDS="./creds_cmd.sh" \
RATCHET_USER_CMD_POLICY="./user_cmd_policy_cmd.sh" \
RATCHET_LONG_POLL="sleep inf"
RATCHET_CUST_HOSTPORT="[::]:49" \
./target/release/ratchet
Where clients_cmd
, creds_cmd
correspond to a script that puts a UTF-8 encoded CSV list onto stdout with the following formal-looking line format:
CLIENTS
:= (
V4_CIDR_SUBNET|V6_CIDR_SUBNET
,
PLAINTEXT KEY
\n
)+
CREDS
:= (
COMMALESS_USERNAME
,
BCRYPT_PASSWORD_HASH
\n
)+
Don't repeat keys (i.e., specific subnets, specific usernames), sorry.
RATCHET_LONG_POLL
is a command that should complete when an upstream server wants to signal to ratchet that clients, creds, etc. have updates.
- There's also a trivial long-polling protocol involving a serial that can be used to provide a minimal asynchronous update with ratchet as the subscriber to any available changes.
RATCHET_USER_CMD_POLICY
can, similarly, be used to incorporate a file for Command Authorization processing:
So the RATCHET_USER_CMD_POLICY
should deliver a file with the following formal-looking file format:
USER_CMD_POLICY
:=
$
a_list_of_users
user1
user2
admin
(
<POLICY_ACE>
<POLICY_ACE>
<POLICY_ACE>
...
)
$
a_different_list_of_users
user3
user4
guest
(
<POLICY_ACE>
<POLICY_ACE>
<POLICY_ACE>
...
)
...
So $
on a line, alone, breaks processing into subjects
and the (
, )
denote the actual set of policy access control entries (ACE).
Each policy ACE for a matched user is processed in-order on a first-matched basis, and in the absence of a match, authorization is default permitted. (see below for an example of default-deny)
Failure modes:
- If users are duplicated in multiple policies, they will inherit the last-defined policy.
- If any policy is not completed, or invalid, then no policy update will take place.
- To include invalid usernames, misplaced field symbols, unknown field symbols, not enough commas.
- So in short, during updates and initialization, if an invalid policy is provided; then, the last valid policy provided remains in place.
POLICY_ACE
:= PRECEDENCE
,POLICY_OUTCOME
,CRITERIA
,BLANK/RESERVED
,TEXT_DATA
PRECEDENCE
:= An integer usize
, for when device policies are incorporated.
POLICY_OUTCOME
:= acc
| rej
CRITERIA
:= <
, >
, =
begins with, ends with, and contains; respectively. Use contains sparingly.
BLANK/RESERVED
:= should be blank, ignored, maybe used in the future.
TEXT_DATA
:= An arbitrary string to match against.
$
username
(
10,acc,<,,show
20,rej,<,,reload
20,rej,<,,ping
20,rej,=,,
)
Translation: The user username
can run commands starting with show
, cannot run commands starting with reload
or ping
, and finally, may not run any other command.
Protocol Interoperability Note: The User Command Authorization Policy is only evaluated in the case where the Authorization request contains the RFC8907 Section 8.2 referenced cmd
Authorization argument.
In other words, arguments other than cmd
and cmd-arg
from clients are disregarded, for example priv-lvl
, service
, or protocol
. And Authorization Requests that do not contain a cmd
are going to be, as a consequence, permitted by default.
If you want to do something cheeky like make RATCHET_READ_CLIENTS="echo '10.10.2.20/32,testing123'
or RATCHET_READ_CREDS=cat clients_lists.txt
that's fine, I'm easy.
ratchet defines the following defaults:
"cat /dev/null",
"cat /dev/null",
"sleep inf",
"echo",
corresponding to no clients, no creds, polling that never progresses, and a blank policy, so any environment variables that aren't defined have safe reasonable defaults.
In order to run the unit tests you'll need something like.
sudo apt install libauthen-tacacsplus-perl
They tests are not comprehensive, and only provide a basic sanity check for TACACS authentication.
There's future plans to possibly leverage GNS3 so that this system can be tested automatically against arbitrary/proprietary/emulated TACACS clients, but for now some manual testing has it sort of working mostly.
- Logging
- Unauthenticated command authorization policy
- Device-based command authorization
- Central command logging based on a policy
- CRUD config changes (currently leads to a RwLock order user-count)
- Benchmarks / Higher-order integration testing
- Rest of the protocol /
- ... CHAP / MSCHAPv2
- ... single connection mode
- Support for complete set of security controls
- ... and automated auditing
- ... and 8907 future recs
- fail2ban-like mechanism to protect server from misbehaving clients
- argonaut / argon for password hashing
- assess md5 performance / simd?
- authorization
- command authorization policy definition
-
beautifulminimalist front-end- Containerized distribution
- complete memory-hardening
- ... and authenticating against unrecoverable passwords
- assess or implement async to retain maximum performance
- Config interfaces
- Support for shadowed passwords instead of,... that.
- Make sure that the CSV escaping makes sense, maybe switch to TSV
- Multi-threaded implementation
- the ASCII protocol couples too tightly, yikes!
- ASCII Authentication
- Configuration
- Configurable user list
- Configurable clients list
- Configurable port
- Constructively walk through LLVM opt flag configurations 'rustc passes'
- Something to meaningfully improve using GNS3 to run tests over this
- ... or maybe setup like a dynaMIPS thing some way how
I discovered that when using perf
to profile ratchet
that the pipeline was 30% faster, which was,... odd.
After some intense digging, I still don't know for sure, however I did discover that:
echo "50" | sudo tee /proc/sys/net/core/busy_poll
echo "50" | sudo tee /proc/sys/net/core/busy_read
(corresponding to a pretty aggressive polling cycle) yielded approximately the same benefit.
😵