1
0
Fork 0

Add JWT Validator Plugin and corresponding tests

- Introduced `JwtValidatorPlugin` (DOMAIN): Validates JWT tokens for protected API endpoints using HAProxy's JWT functionality.
- Added configuration options: `enabled`, `algorithm`, `issuer`, `audience`, `pubkey_path`, and `pubkey`.
- Documented the plugin setup and usage in `plugins.md` and `plugin-development.md`.
- Created fixtures for `services-with-jwt-validator`.
- Added extensive test cases to validate configuration and HAProxy output generation.
- Ensured seamless integration into the plugin framework alongside existing domain plugins.
This commit is contained in:
Joao Gilberto Magalhaes 2025-11-27 18:48:10 -05:00
parent 57387f3e32
commit ebc4b2bf7a
5 changed files with 554 additions and 8 deletions

View file

@ -47,7 +47,7 @@ This guide explains how to create custom plugins for EasyHAProxy. For informatio
## Built-in Plugins Reference
EasyHAProxy includes four built-in plugins that serve as both functional tools and reference implementations for plugin development.
EasyHAProxy includes five built-in plugins that serve as both functional tools and reference implementations for plugin development.
### CloudflarePlugin (DOMAIN)
@ -151,14 +151,59 @@ http-request deny deny_status 403 if !whitelisted_ip
**Usage:** See [IP Whitelist Plugin documentation](plugins.md#ip-whitelist-plugin-domain)
### JwtValidatorPlugin (DOMAIN)
**Purpose:** Validate JWT authentication tokens
**Type:** DOMAIN - Executes once per domain
**Source:** `src/plugins/builtin/jwt_validator.py`
**Configuration Options:**
- `enabled` (bool) - Enable/disable plugin (default: `true`)
- `algorithm` (str) - JWT signing algorithm (default: `RS256`)
- `issuer` (str) - Expected JWT issuer (optional, `none`/`null` skips validation)
- `audience` (str) - Expected JWT audience (optional, `none`/`null` skips validation)
- `pubkey_path` (str) - Path to public key file (required if `pubkey` not provided)
- `pubkey` (str) - Public key content as string (required if `pubkey_path` not provided)
**What it does:**
- Validates JWT tokens using HAProxy's JWT functionality
- Checks Authorization header presence
- Validates algorithm, issuer, audience, signature, and expiration
- Optionally skips issuer/audience validation if set to "none"
**HAProxy config generated:**
```
# JWT Validator - Validate JWT tokens
http-request deny content-type 'text/html' string 'Missing Authorization HTTP header' unless { req.hdr(authorization) -m found }
# Extract JWT header and payload
http-request set-var(txn.alg) http_auth_bearer,jwt_header_query('$.alg')
http-request set-var(txn.iss) http_auth_bearer,jwt_payload_query('$.iss')
http-request set-var(txn.aud) http_auth_bearer,jwt_payload_query('$.aud')
http-request set-var(txn.exp) http_auth_bearer,jwt_payload_query('$.exp','int')
# Validate JWT
http-request deny content-type 'text/html' string 'Unsupported JWT signing algorithm' unless { var(txn.alg) -m str RS256 }
http-request deny content-type 'text/html' string 'Invalid JWT signature' unless { http_auth_bearer,jwt_verify(txn.alg,"/etc/haproxy/jwt_keys/api_pubkey.pem") -m int 1 }
# Validate expiration
http-request set-var(txn.now) date()
http-request deny content-type 'text/html' string 'JWT has expired' if { var(txn.exp),sub(txn.now) -m int lt 0 }
```
**Usage:** See [JWT Validator Plugin documentation](plugins.md#jwt-validator-plugin-domain)
### Summary Table
| Plugin | Type | Purpose | Config Generated |
|----------------|--------|------------------------------|------------------|
| `cloudflare` | DOMAIN | Restore original visitor IPs | ✅ Yes |
| `cleanup` | GLOBAL | Clean up temp files | ❌ No |
| `deny_pages` | DOMAIN | Block specific paths | ✅ Yes |
| `ip_whitelist` | DOMAIN | Restrict to specific IPs | ✅ Yes |
| Plugin | Type | Purpose | Config Generated |
|-----------------|--------|------------------------------|------------------|
| `cloudflare` | DOMAIN | Restore original visitor IPs | ✅ Yes |
| `cleanup` | GLOBAL | Clean up temp files | ❌ No |
| `deny_pages` | DOMAIN | Block specific paths | ✅ Yes |
| `ip_whitelist` | DOMAIN | Restrict to specific IPs | ✅ Yes |
| `jwt_validator` | DOMAIN | Validate JWT tokens | ✅ Yes |
### Learning from Built-in Plugins
@ -184,11 +229,18 @@ http-request deny deny_status 403 if !whitelisted_ip
- Negated ACLs (`if !whitelisted_ip`)
- CIDR range support
5. **JwtValidatorPlugin** shows:
- Complex HAProxy JWT validation
- Optional validation (skip with "none"/"null")
- Dynamic file path generation based on domain
- Multiple configuration options
**View the source code:**
- [cloudflare.py](https://github.com/byjg/docker-easy-haproxy/blob/master/src/plugins/builtin/cloudflare.py)
- [cleanup.py](https://github.com/byjg/docker-easy-haproxy/blob/master/src/plugins/builtin/cleanup.py)
- [deny_pages.py](https://github.com/byjg/docker-easy-haproxy/blob/master/src/plugins/builtin/deny_pages.py)
- [ip_whitelist.py](https://github.com/byjg/docker-easy-haproxy/blob/master/src/plugins/builtin/ip_whitelist.py)
- [jwt_validator.py](https://github.com/byjg/docker-easy-haproxy/blob/master/src/plugins/builtin/jwt_validator.py)
## Plugin API Reference

View file

@ -165,6 +165,75 @@ http-request deny deny_status 403 if !whitelisted_ip
**Important:** This blocks ALL IPs except those in the whitelist. Make sure to include your own IP!
### JWT Validator Plugin (Domain)
Validates JWT (JSON Web Token) authentication tokens using HAProxy's built-in JWT functionality.
**Why use it:** Protect APIs and services with JWT authentication without needing application-level code.
**Configuration options:**
- `enabled` - Enable/disable plugin (default: `true`)
- `algorithm` - JWT signing algorithm (default: `RS256`)
- `issuer` - Expected JWT issuer (optional, set to `none`/`null` to skip validation)
- `audience` - Expected JWT audience (optional, set to `none`/`null` to skip validation)
- `pubkey_path` - Path to public key file (required if `pubkey` not provided)
- `pubkey` - Public key content as string (required if `pubkey_path` not provided)
**Enable via container label:**
```yaml
services:
api:
labels:
easyhaproxy.http.host: api.example.com
easyhaproxy.http.plugins: jwt_validator
easyhaproxy.http.plugin.jwt_validator.algorithm: RS256
easyhaproxy.http.plugin.jwt_validator.issuer: https://auth.example.com/
easyhaproxy.http.plugin.jwt_validator.audience: https://api.example.com
easyhaproxy.http.plugin.jwt_validator.pubkey_path: /etc/haproxy/jwt_keys/api_pubkey.pem
volumes:
- ./pubkey.pem:/etc/haproxy/jwt_keys/api_pubkey.pem:ro
```
**Skip issuer/audience validation:**
```yaml
labels:
easyhaproxy.http.plugin.jwt_validator.issuer: none
easyhaproxy.http.plugin.jwt_validator.audience: none
easyhaproxy.http.plugin.jwt_validator.pubkey_path: /etc/haproxy/jwt_keys/api_pubkey.pem
```
**HAProxy config generated:**
```
# JWT Validator - Validate JWT tokens
http-request deny content-type 'text/html' string 'Missing Authorization HTTP header' unless { req.hdr(authorization) -m found }
# Extract JWT header and payload
http-request set-var(txn.alg) http_auth_bearer,jwt_header_query('$.alg')
http-request set-var(txn.iss) http_auth_bearer,jwt_payload_query('$.iss')
http-request set-var(txn.aud) http_auth_bearer,jwt_payload_query('$.aud')
http-request set-var(txn.exp) http_auth_bearer,jwt_payload_query('$.exp','int')
# Validate JWT
http-request deny content-type 'text/html' string 'Unsupported JWT signing algorithm' unless { var(txn.alg) -m str RS256 }
http-request deny content-type 'text/html' string 'Invalid JWT issuer' unless { var(txn.iss) -m str https://auth.example.com/ }
http-request deny content-type 'text/html' string 'Invalid JWT audience' unless { var(txn.aud) -m str https://api.example.com }
http-request deny content-type 'text/html' string 'Invalid JWT signature' unless { http_auth_bearer,jwt_verify(txn.alg,"/etc/haproxy/jwt_keys/api_pubkey.pem") -m int 1 }
# Validate expiration
http-request set-var(txn.now) date()
http-request deny content-type 'text/html' string 'JWT has expired' if { var(txn.exp),sub(txn.now) -m int lt 0 }
```
**What it validates:**
- ✅ Authorization header presence
- ✅ JWT signing algorithm (RS256, RS512, etc.)
- ✅ JWT issuer (if configured)
- ✅ JWT audience (if configured)
- ✅ JWT signature using public key
- ✅ JWT expiration time
**Important:** Requires HAProxy 2.5+ with JWT support. Mount public key file as read-only volume.
## Configuration Methods
Plugins can be configured using three methods, listed in order of precedence (highest to lowest):
@ -240,6 +309,23 @@ EASYHAPROXY_PLUGIN_CLOUDFLARE_IP_LIST_PATH=/etc/haproxy/cloudflare_ips.lst
## Common Use Cases
### Protect API with JWT Authentication
Secure your API endpoints with JWT token validation:
```yaml
services:
api:
labels:
easyhaproxy.http.host: api.example.com
easyhaproxy.http.plugins: jwt_validator
easyhaproxy.http.plugin.jwt_validator.issuer: https://auth0.myapp.com/
easyhaproxy.http.plugin.jwt_validator.audience: https://api.example.com
easyhaproxy.http.plugin.jwt_validator.pubkey_path: /etc/haproxy/jwt_keys/api_pubkey.pem
volumes:
- ./auth_pubkey.pem:/etc/haproxy/jwt_keys/api_pubkey.pem:ro
```
### Restrict Admin Panel to Office IPs
Protect admin panel by only allowing access from office network: