ircu2/tools/iauthd-ts/README.md

478 lines
16 KiB
Markdown

# iauthd-ts
A TypeScript implementation of an IAuth daemon for Nefarious IRCd. This daemon performs real-time DNS-based blacklist (DNSBL) lookups on connecting clients and can block, mark, or whitelist users based on the results.
This is a port of the original Perl `iauthd.pl` to TypeScript/Node.js.
## Features
- **DNSBL Lookups**: Check connecting clients against multiple DNS blacklists
- **Flexible Matching**: Match by index value or bitmask
- **Caching**: DNS results are cached to reduce lookup overhead
- **SASL Authentication**: Handle SASL PLAIN authentication directly in IAuth (no services required)
- **SASL/LOC Support**: Exempt authenticated users from blocks
- **WEBIRC Support**: Re-check real IP for trusted proxy connections
- **Marking**: Tag users with marks visible to operators
- **Class Assignment**: Assign users to connection classes based on DNSBL results
- **Whitelisting**: Exempt users matching certain DNSBLs from all blocks
## How It Works
```
┌─────────────┐ stdin/stdout ┌─────────────┐
│ Nefarious │ ◄──────────────────► │ iauthd-ts │
│ IRCd │ IAuth Protocol │ │
└─────────────┘ └──────┬──────┘
│ DNS queries
┌─────────────┐
│ DNSBL │
│ Servers │
└─────────────┘
```
1. **Client Connects**: IRCd sends client info to iauthd-ts via the IAuth protocol
2. **DNSBL Lookup**: iauthd-ts reverses the client IP and queries configured DNSBLs
3. **Result Processing**: Responses are checked against configured index/bitmask rules
4. **Decision**: Client is accepted (with optional marks/class) or rejected
5. **Response**: iauthd-ts sends the decision back to IRCd
### IAuth Protocol
iauthd-ts communicates with Nefarious via stdin/stdout using the IAuth protocol:
| Direction | Message | Description |
|-----------|---------|-------------|
| IRCd → iauthd | `C <id> <ip> <port> <serverip> <serverport>` | Client introduction |
| IRCd → iauthd | `H <id> <class>` | Hurry up (registration timeout approaching) |
| IRCd → iauthd | `R <id> <account>` | Client authenticated via SASL/LOC |
| IRCd → iauthd | `D <id>` | Client disconnected |
| iauthd → IRCd | `D <id> <ip> <port> [class]` | Accept client |
| iauthd → IRCd | `k <id> <ip> <port> :<reason>` | Reject client |
| iauthd → IRCd | `m <id> <ip> <port> MARK <data>` | Mark client |
| IRCd → iauthd | `A <id> <ip> <port> S <mechanism> [:<certfp>]` | SASL auth start |
| IRCd → iauthd | `A <id> <ip> <port> H :<user@host:ip>` | SASL host info |
| IRCd → iauthd | `a <id> <ip> <port> :<data>` | SASL auth data |
| iauthd → IRCd | `c <id> <ip> <port> :<challenge>` | SASL challenge |
| iauthd → IRCd | `L <id> <ip> <port> <account>` | SASL login success |
| iauthd → IRCd | `f <id> <ip> <port>` | SASL auth failed |
| iauthd → IRCd | `l <id> <ip> <port> :<mechanisms>` | SASL mechanism list |
| iauthd → IRCd | `Z <id> <ip> <port>` | SASL auth complete |
## Requirements
- Node.js 18 or later
- npm
## Installation
### From Source
```bash
cd tools/iauthd-ts
npm install
npm run build
```
### Docker
iauthd-ts is automatically built as part of the Nefarious Docker image. The compiled files are located at `/home/nefarious/ircd/iauthd-ts/`.
## Configuration
iauthd-ts reads configuration from `#IAUTH` directives in the config file. These can be embedded in your `ircd.conf` since IRCd ignores lines starting with `#`.
### Directives
#### POLICY
```
#IAUTH POLICY RTAWUwFr
```
Sets the IAuth policy flags. See `doc/readme.iauth` for details.
#### DNSTIMEOUT
```
#IAUTH DNSTIMEOUT 5
```
Seconds to wait for DNSBL lookups. Default: 5
#### CACHETIME
```
#IAUTH CACHETIME 86400
```
Seconds to cache DNSBL results. Default: 86400 (24 hours)
#### BLOCKMSG
```
#IAUTH BLOCKMSG Sorry! Your connection has been rejected due to poor reputation.
```
Message shown to blocked users.
#### DEBUG
```
#IAUTH DEBUG
```
Enable debug output.
#### SASLDB
```
#IAUTH SASLDB /path/to/users
```
Path to the SASL users file. When set, iauthd-ts will handle SASL PLAIN authentication directly instead of routing to services. The policy must include 'S' for this to work.
#### SASLFAILMSG
```
#IAUTH SASLFAILMSG Authentication failed
```
Message shown when SASL authentication fails.
#### DNSBL
```
#IAUTH DNSBL server=<server> [options...]
```
Options:
| Option | Description |
|--------|-------------|
| `server=<host>` | DNSBL server hostname (required) |
| `index=<n,n,...>` | Match if response equals any of these values |
| `bitmask=<n>` | Match if response AND bitmask is non-zero |
| `mark=<tag>` | Apply this mark to matching clients |
| `block=all` | Block all matching clients |
| `block=anonymous` | Block matching clients unless SASL authenticated |
| `class=<name>` | Assign matching clients to this connection class |
| `whitelist` | Matching clients are exempt from all blocks |
| `cachetime=<n>` | Override cache time for this DNSBL |
### Example Configuration
```
# IAuth Configuration
#IAUTH POLICY RTAWUwFr
#IAUTH CACHETIME 86400
#IAUTH DNSTIMEOUT 5
#IAUTH BLOCKMSG Sorry! Your connection has been rejected due to poor reputation.
# Block open proxies and drones (unless authenticated)
#IAUTH DNSBL server=dnsbl.dronebl.org index=2,3,5,6,7,8,9,10,13,14,15 mark=dronebl block=anonymous
# Mark Tor exit nodes but don't block
#IAUTH DNSBL server=rbl.efnetrbl.org index=4 mark=tor
# Block other bad actors
#IAUTH DNSBL server=rbl.efnetrbl.org index=1,2,3,5 mark=efnetrbl block=anonymous
# Whitelist from a private DNSBL
#IAUTH DNSBL server=whitelist.example.com whitelist cachetime=3600
```
### SASL Authentication Configuration
To enable SASL authentication handling in iauthd-ts (instead of routing to services):
1. **Create a users file** (`users` or any path you prefer):
```
# Format: username:passwordhash
# Use genhash.ts to create hashes
admin:$5$salt$hashedpassword
user1:$6$salt$hashedpassword
```
2. **Generate password hashes**:
```bash
npx tsx src/genhash.ts mypassword sha256
# Output: $5$randomsalt$hashedpasswordvalue
```
3. **Configure iauthd-ts**:
```
#IAUTH POLICY RTAWUwFrS
#IAUTH SASLDB /path/to/users
#IAUTH SASLFAILMSG Invalid username or password
```
The `S` in the policy enables SASL handling.
4. **Supported hash formats**:
- `$5$salt$hash` - SHA-256 (recommended)
- `$6$salt$hash` - SHA-512
- `$1$salt$hash` - MD5 (legacy)
- Plain text (for testing only)
The users file is automatically reloaded when modified (checked on each SASL attempt).
### Modular Authentication Providers
iauthd-ts supports a modular authentication system with multiple backends. Providers are tried in priority order (lower number = tried first) until one succeeds.
#### AUTH Directive
```
#IAUTH AUTH provider=<type> [options...]
```
#### File Provider
Uses a static users file (same as SASLDB but with explicit provider syntax):
```
#IAUTH AUTH provider=file path=/path/to/users priority=50
```
#### LDAP Provider
> **Note**: LDAP support has not yet been tested with real LDAP servers. Please report any issues.
Two modes are supported:
**Direct Bind Mode** - Binds directly as the user:
```
#IAUTH AUTH provider=ldap uri=ldap://ldap.example.com:389 mode=direct userdn=uid=%s,ou=users,dc=example,dc=com
```
**Search Mode** - Admin bind, search for user, then bind as user:
```
#IAUTH AUTH provider=ldap uri=ldaps://ldap.example.com:636 mode=search basedn=ou=users,dc=example,dc=com binddn=cn=admin,dc=example,dc=com bindpass=secret userfilter=(uid=%s) groupdn=cn=ircusers,ou=groups,dc=example,dc=com
```
LDAP Options:
| Option | Required | Mode | Description |
|--------|----------|------|-------------|
| `uri` | Yes | Both | LDAP server URI (ldap:// or ldaps://) |
| `mode` | Yes | Both | `direct` or `search` |
| `userdn` | Yes | direct | DN template with `%s` for username |
| `basedn` | Yes | search | Base DN for user search |
| `binddn` | Yes | search | Admin bind DN |
| `bindpass` | Yes | search | Admin bind password |
| `userfilter` | Yes | search | Search filter with `%s` for username |
| `groupdn` | No | search | Group DN for membership check |
| `accountattr` | No | Both | Attribute to use as account name |
| `timeout` | No | Both | Connection timeout in ms (default: 5000) |
| `priority` | No | Both | Provider priority (default: 100) |
#### Keycloak Provider
Authenticates via Keycloak using Resource Owner Password Credentials (ROPC) grant:
```
#IAUTH AUTH provider=keycloak url=https://keycloak.example.com realm=myrealm clientid=irc-client
```
With client secret (for confidential clients):
```
#IAUTH AUTH provider=keycloak url=https://keycloak.example.com realm=myrealm clientid=irc-client clientsecret=mysecret
```
Keycloak Options:
| Option | Required | Description |
|--------|----------|-------------|
| `url` | Yes | Keycloak server URL |
| `realm` | Yes | Realm name |
| `clientid` | Yes | Client ID |
| `clientsecret` | No | Client secret (for confidential clients) |
| `accountattr` | No | JWT claim to use as account name (default: preferred_username) |
| `timeout` | No | Request timeout in ms (default: 5000) |
| `priority` | No | Provider priority (default: 100) |
#### Multiple Providers (Fallback Chain)
Configure multiple providers with different priorities. Lower priority = tried first:
```
#IAUTH AUTH provider=ldap uri=ldaps://ldap.example.com mode=direct userdn=uid=%s,ou=users,dc=example,dc=com priority=10
#IAUTH AUTH provider=keycloak url=https://keycloak.example.com realm=irc clientid=irc-client priority=20
#IAUTH AUTH provider=file path=/path/to/local-users priority=100
```
## Usage
### Command Line
```bash
node dist/index.js -c <configfile> [-v] [-d]
Options:
-c, --config Config file to read (required)
-v, --verbose Enable verbose output in iauthd
-d, --debug Enable debug output in IRCd
-h, --help Show help
```
### IRCd Configuration
Add to your `ircd.conf`:
```
IAuth {
program = "node" "/path/to/iauthd-ts/dist/index.js" "-v" "-c" "/path/to/ircd.conf";
};
```
For Docker deployments:
```
IAuth {
program = "node" "/home/nefarious/ircd/iauthd-ts/index.js" "-v" "-c" "/home/nefarious/ircd/ircd.conf";
};
```
### Monitoring
To see IAuth debug messages as an operator:
```
/quote mode YourNick +s 262144
```
### Statistics
IAuth statistics are available via `/stats iauth` and include:
- Uptime
- Cache size
- Total passed/rejected clients
- Per-DNSBL hit counts
## Development
### Project Structure
```
iauthd-ts/
├── src/
│ ├── index.ts # CLI entry point
│ ├── iauth.ts # Main IAuth daemon class
│ ├── config.ts # Configuration parser
│ ├── dnsbl.ts # DNSBL lookup and caching
│ ├── sasl.ts # SASL authentication handling
│ ├── genhash.ts # Password hash generator utility
│ └── types.ts # TypeScript interfaces
├── users.example # Example SASL users file
├── tests/
│ ├── config.test.ts # Config parser tests
│ ├── dnsbl.test.ts # DNSBL function tests
│ ├── iauth.test.ts # Integration tests
│ └── stress.ts # Memory stress test
├── dist/ # Compiled JavaScript (after build)
├── package.json
├── tsconfig.json
└── vitest.config.ts
```
### Building
```bash
npm install # Install dependencies
npm run build # Compile TypeScript to dist/
```
### Running Tests
```bash
npm test # Run all tests
npm run test:watch # Watch mode (re-run on changes)
npm run test:coverage # Generate coverage report
```
### Test Coverage
The test suite includes:
- **Unit tests** (45 tests): Config parsing, IP handling, DNSBL matching, caching
- **Protocol integration tests** (17 tests): IAuth protocol simulation
- **DNSBL integration tests** (32 tests): Full pipeline with mocked DNS responses
#### DNSBL Integration Tests
The DNSBL integration tests (`tests/dnsbl-integration.test.ts`) use a mock DNS resolver to test realistic scenarios:
| Category | Tests |
|----------|-------|
| Index matching | Exact match, multiple indices, no match, NXDOMAIN |
| Bitmask matching | Single bit, multiple bits, no bits match |
| block=all | Blocks both anonymous and authenticated |
| block=anonymous | Blocks anonymous, allows SASL-authenticated |
| Mark only | Accepts client but applies mark |
| Whitelist | Overrides subsequent blocking DNSBLs |
| Multiple DNSBLs | Accumulates marks, any block triggers rejection |
| Same server/different indices | AfterNET pattern (whitelist=2, rbl=250, cloud=251) |
| Class assignment | Assigns connection class from matching DNSBL |
| Real-world simulation | Full AfterNET production config scenarios |
### Stress Testing
The stress test simulates thousands of concurrent connections to verify:
- Memory stability (no leaks in client tracking)
- Cache behavior
- Performance under load
```bash
npm run stress # Default: 5000 clients, 120s max
npm run stress -- --clients=10000 # More clients
npm run stress -- --duration=60 # Shorter duration
npm run stress -- -v # Verbose output
```
Example output:
```
============================================================
iauthd-ts Memory Stress Test
============================================================
Clients to simulate: 5000
Max concurrent: 100
Max duration: 120s
============================================================
Sent: 5000 | Accepted: 4994 | Rejected: 6 | Active: 0 | Cache: 5000 | Rate: 520.3/s
============================================================
STRESS TEST RESULTS
============================================================
Total clients sent: 5000
Total clients accepted: 4994
Total clients rejected: 6
Unprocessed clients: 0
Total time: 9.61s
Average rate: 520.29 clients/s
MEMORY ANALYSIS
------------------------------------------------------------
✅ Memory usage appears stable.
✅ All clients were processed correctly.
============================================================
```
### Development Mode
Run directly from TypeScript without compiling:
```bash
npm run dev -- -c /path/to/config
```
## Comparison with iauthd.pl
| Feature | iauthd.pl (Perl) | iauthd-ts (TypeScript) |
|---------|------------------|------------------------|
| Runtime | Perl + POE | Node.js |
| Async Model | POE event loop | Native async/await |
| Type Safety | None | Full TypeScript types |
| Testing | None | 62 tests + stress test |
| Dependencies | 5 CPAN modules | 0 runtime deps |
## Troubleshooting
### iauthd-ts not starting
- Check that Node.js 18+ is installed: `node --version`
- Verify config file path is correct and readable
- Check for syntax errors in `#IAUTH` directives
### Clients timing out
- Increase `DNSTIMEOUT` if DNSBL servers are slow
- Check network connectivity to DNSBL servers
- Verify DNS resolution is working: `host 2.0.0.127.dnsbl.example.com`
### High memory usage
- Reduce `CACHETIME` to expire entries sooner
- Monitor with stress test: `npm run stress`
- Check for many unique IPs (each gets cached)
### Debug output
Run with `-v` flag and set operator snomask:
```
/quote mode YourNick +s 262144
```
## License
GPL-2.0 - Same as Nefarious IRCd