1
0
Fork 0

Add detailed plugin documentation for Cleanup, Cloudflare, Deny Pages, IP Whitelist, JWT Validator, and FastCGI plugins

- Added individual markdown files with examples, configuration options, and HAProxy outputs for each plugin.
- Updated `README.md` to link plugin-specific documentation.
- Enhanced `plugins.md` to summarize plugin features and usage.
- Included Docker, Kubernetes, Swarm, and static configuration examples for all plugins.
This commit is contained in:
Joao Gilberto Magalhaes 2025-12-01 13:36:11 -05:00
parent 883521e7e1
commit ae6eb1b55a
10 changed files with 2431 additions and 1317 deletions

74
docs/plugins/cleanup.md Normal file
View file

@ -0,0 +1,74 @@
# Cleanup Plugin
**Type:** Global Plugin
**Runs:** Once per discovery cycle
## Overview
The Cleanup plugin performs cleanup tasks during each discovery cycle, such as removing old temporary files.
## Why Use It
Prevents disk space issues by automatically cleaning up temporary files created by EasyHAProxy.
## Configuration Options
| Option | Description | Default |
|----------------------|----------------------------------------------|---------|
| `enabled` | Enable/disable plugin | `true` |
| `max_idle_time` | Maximum age in seconds before deleting files | `300` |
| `cleanup_temp_files` | Enable temp file cleanup | `true` |
## Configuration Examples
### Static YAML Configuration
```yaml
# /etc/haproxy/static/config.yaml
plugins:
enabled: [cleanup]
config:
cleanup:
max_idle_time: 600
cleanup_temp_files: true
```
### Environment Variables
```bash
EASYHAPROXY_PLUGINS_ENABLED=cleanup
EASYHAPROXY_PLUGIN_CLEANUP_MAX_IDLE_TIME=600
```
### Custom Idle Time (1 hour)
```yaml
# /etc/haproxy/static/config.yaml
plugins:
enabled: [cleanup]
config:
cleanup:
enabled: true
max_idle_time: 3600 # 1 hour
```
## How It Works
The cleanup plugin:
- Runs once during each discovery cycle
- Scans temporary directories for old files
- Removes files older than `max_idle_time` seconds
- Helps maintain disk space efficiency
## Important Notes
- This is a **global plugin** - it runs once per discovery cycle, not per domain
- Does not generate HAProxy configuration
- Performs maintenance operations in the background
- Safe to enable in production environments
## Related Documentation
- [Plugin System Overview](../plugins.md)
- [Environment Variables Reference](../environment-variable.md)
- [Static Configuration Reference](../static.md)

View file

@ -0,0 +1,91 @@
# Cloudflare Plugin
**Type:** Domain Plugin
**Runs:** Once for each discovered domain/host
## Overview
The Cloudflare plugin restores the original visitor IP address when requests come through Cloudflare's CDN.
## Why Use It
Cloudflare replaces the visitor's IP with its own. This plugin restores the original IP from the `CF-Connecting-IP` header.
## Configuration Options
| Option | Description | Default |
|----------------|----------------------------|-----------------------------------|
| `enabled` | Enable/disable plugin | `true` |
| `ip_list_path` | Path to Cloudflare IP list | `/etc/haproxy/cloudflare_ips.lst` |
## Configuration Examples
### Docker/Docker Compose (Basic)
```yaml
services:
myapp:
labels:
easyhaproxy.http.host: example.com
easyhaproxy.http.plugins: cloudflare
```
### Docker/Docker Compose (Custom IP List Path)
```yaml
labels:
easyhaproxy.http.plugins: cloudflare
easyhaproxy.http.plugin.cloudflare.ip_list_path: /custom/path/cf_ips.lst
```
### Kubernetes Annotations
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
easyhaproxy.plugins: "cloudflare"
easyhaproxy.plugin.cloudflare.ip_list_path: "/etc/haproxy/cloudflare_ips.lst"
spec:
rules:
- host: example.com
http:
paths:
- path: /
backend:
service:
name: myapp
port:
number: 80
```
### Static YAML Configuration
```yaml
# /etc/haproxy/static/config.yaml
plugins:
config:
cloudflare:
enabled: true
ip_list_path: /etc/haproxy/cloudflare_ips.lst
```
## Generated HAProxy Configuration
```haproxy
# Cloudflare - Restore original visitor IP
acl from_cloudflare src -f /etc/haproxy/cloudflare_ips.lst
http-request set-header X-Forwarded-For %[req.hdr(CF-Connecting-IP)] if from_cloudflare
```
## Important Notes
- **Required:** Download Cloudflare IP list from [Cloudflare documentation](https://support.cloudflare.com/hc/en-us/articles/200170786)
- The plugin runs once per domain during the discovery cycle
- Ensure the IP list file is mounted and accessible to HAProxy
## Related Documentation
- [Plugin System Overview](../plugins.md)
- [Container Labels Reference](../container-labels.md)

113
docs/plugins/deny-pages.md Normal file
View file

@ -0,0 +1,113 @@
# Deny Pages Plugin
**Type:** Domain Plugin
**Runs:** Once for each discovered domain/host
## Overview
The Deny Pages plugin blocks access to specific paths for a domain, returning a configurable HTTP status code.
## Why Use It
Protect admin panels, internal APIs, or debugging endpoints from public access.
## Configuration Options
| Option | Description | Default |
|---------------|----------------------------------------|------------|
| `enabled` | Enable/disable plugin | `true` |
| `paths` | Comma-separated list of paths to block | (required) |
| `status_code` | HTTP status code to return | `403` |
## Configuration Examples
### Docker/Docker Compose (Basic)
```yaml
services:
webapp:
labels:
easyhaproxy.http.host: example.com
easyhaproxy.http.plugins: deny_pages
easyhaproxy.http.plugin.deny_pages.paths: /admin,/private,/debug
easyhaproxy.http.plugin.deny_pages.status_code: 404
```
### WordPress Protection
```yaml
labels:
easyhaproxy.http.host: wordpress.example.com
easyhaproxy.http.plugins: deny_pages
easyhaproxy.http.plugin.deny_pages.paths: /wp-admin,/wp-login.php,/.env
easyhaproxy.http.plugin.deny_pages.status_code: 404
```
### Kubernetes Annotations
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
easyhaproxy.plugins: "deny_pages"
easyhaproxy.plugin.deny_pages.paths: "/admin,/private"
easyhaproxy.plugin.deny_pages.status_code: "403"
spec:
rules:
- host: example.com
http:
paths:
- path: /
backend:
service:
name: webapp
port:
number: 80
```
### Static YAML Configuration
```yaml
# /etc/haproxy/static/config.yaml
easymapping:
- host: example.com
port: 80
container: webapp:80
plugins:
- deny_pages
plugin_config:
deny_pages:
paths: /admin,/private,/debug
status_code: 403
```
### Multiple Plugins (with Cloudflare)
```yaml
labels:
easyhaproxy.http.host: secure-app.com
easyhaproxy.http.plugins: cloudflare,deny_pages
easyhaproxy.http.plugin.deny_pages.paths: /admin,/config
easyhaproxy.http.plugin.deny_pages.status_code: 403
```
## Generated HAProxy Configuration
```haproxy
# Deny Pages - Block specific paths
acl denied_path path_beg /admin /private /debug
http-request deny deny_status 404 if denied_path
```
## Important Notes
- The plugin runs once per domain during the discovery cycle
- Path matching uses `path_beg` (prefix matching), so `/admin` blocks `/admin/*` too
- Consider using `404` instead of `403` to hide the existence of blocked paths
- Works well in combination with other security plugins
## Related Documentation
- [Plugin System Overview](../plugins.md)
- [Container Labels Reference](../container-labels.md)

159
docs/plugins/fastcgi.md Normal file
View file

@ -0,0 +1,159 @@
# FastCGI Plugin
**Type:** Domain Plugin
**Runs:** Once for each discovered domain/host
## Overview
The FastCGI plugin configures HAProxy to communicate with PHP-FPM and other FastCGI applications. It automatically generates the necessary HAProxy `fcgi-app` configuration that defines CGI parameters for proper PHP-FPM communication.
## Why Use It
Automatically generates HAProxy `fcgi-app` configuration that defines required CGI parameters for PHP-FPM communication without manual HAProxy configuration.
## Configuration Options
| Option | Description | Default |
|-------------------|-----------------------------------------|------------------------------------|
| `enabled` | Enable/disable plugin | `true` |
| `document_root` | Document root path | `/var/www/html` |
| `script_filename` | Custom pattern for SCRIPT_FILENAME | `%[path]` (uses HAProxy's default) |
| `index_file` | Default index file | `index.php` |
| `path_info` | Enable PATH_INFO support | `true` |
| `custom_params` | Dictionary of custom FastCGI parameters | (optional) |
## Configuration Examples
### Docker/Docker Compose (TCP connection)
```yaml
services:
php-fpm:
image: php:8.2-fpm
labels:
easyhaproxy.http.host: phpapp.local
easyhaproxy.http.port: 80
easyhaproxy.http.localport: 9000
easyhaproxy.http.proto: fcgi
easyhaproxy.http.plugins: fastcgi
easyhaproxy.http.plugin.fastcgi.document_root: /var/www/html
easyhaproxy.http.plugin.fastcgi.index_file: index.php
volumes:
- ./app:/var/www/html
```
### Docker/Docker Compose (Unix socket)
```yaml
services:
php-fpm:
image: php:8.2-fpm
labels:
easyhaproxy.http.host: phpapp.local
easyhaproxy.http.socket: /run/php/php-fpm.sock
easyhaproxy.http.proto: fcgi
easyhaproxy.http.plugins: fastcgi
easyhaproxy.http.plugin.fastcgi.document_root: /var/www/html
easyhaproxy.http.plugin.fastcgi.index_file: index.php
volumes:
- ./app:/var/www/html
- /run/php:/run/php
```
### Custom Document Root and Index File
```yaml
labels:
easyhaproxy.http.plugins: fastcgi
easyhaproxy.http.plugin.fastcgi.document_root: /var/www/myapp/public
easyhaproxy.http.plugin.fastcgi.index_file: app.php
easyhaproxy.http.plugin.fastcgi.path_info: true
```
### Kubernetes Annotations
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
easyhaproxy.plugins: "fastcgi"
easyhaproxy.plugin.fastcgi.document_root: "/var/www/html"
easyhaproxy.plugin.fastcgi.index_file: "index.php"
spec:
rules:
- host: phpapp.example.com
http:
paths:
- path: /
backend:
service:
name: php-fpm
port:
number: 9000
```
### Static YAML Configuration
```yaml
# /etc/haproxy/static/config.yaml
easymapping:
- host: phpapp.local
port: 80
container: php-fpm:9000
proto: fcgi
plugins:
- fastcgi
plugin_config:
fastcgi:
document_root: /var/www/html
index_file: index.php
path_info: true
```
## Generated HAProxy Configuration
The plugin generates a top-level `fcgi-app` section and a `use-fcgi-app` directive in the backend:
```haproxy
# Top-level fcgi-app definition (added after defaults, before frontends/backends)
fcgi-app fcgi_phpapp_local
docroot /var/www/html
index index.php
path-info ^(/.+\.php)(/.*)?$
# Backend configuration (added to the backend section)
backend srv_phpapp_local_80
use-fcgi-app fcgi_phpapp_local
# TCP connection:
server srv-0 172.19.0.3:9000 proto fcgi
# OR Unix socket:
# server srv-0 /run/php/php-fpm.sock proto fcgi
```
## CGI Parameters
**Note:** HAProxy automatically sets standard CGI parameters based on the `fcgi-app` configuration when communicating with PHP-FPM via the FastCGI protocol.
The plugin configures:
- ✅ **SCRIPT_FILENAME** - Path to PHP script
- ✅ **DOCUMENT_ROOT** - Document root directory
- ✅ **SCRIPT_NAME** - Script name from URL
- ✅ **REQUEST_URI** - Full request URI with query string
- ✅ **QUERY_STRING** - URL query parameters
- ✅ **REQUEST_METHOD** - HTTP method (GET, POST, etc.)
- ✅ **CONTENT_TYPE & CONTENT_LENGTH** - Request body info
- ✅ **SERVER_NAME & SERVER_PORT** - Server details
- ✅ **HTTPS** - SSL/TLS status
- ✅ **PATH_INFO** - Path information (optional)
## Important Notes
- **Required:** Use this plugin together with `proto: fcgi` parameter for complete PHP-FPM support
- The plugin runs once per domain during the discovery cycle
- HAProxy handles the actual FastCGI protocol communication and CGI parameter transmission
## Related Documentation
- [Plugin System Overview](../plugins.md)
- [Container Labels Reference](../container-labels.md)

View file

@ -0,0 +1,110 @@
# IP Whitelist Plugin
**Type:** Domain Plugin
**Runs:** Once for each discovered domain/host
## Overview
The IP Whitelist plugin restricts access to a domain to only specific IP addresses or CIDR ranges.
## Why Use It
Restrict access to internal tools, admin panels, or staging environments to only trusted IP addresses.
## Configuration Options
| Option | Description | Default |
|---------------|--------------------------------------------------|------------|
| `enabled` | Enable/disable plugin | `true` |
| `allowed_ips` | Comma-separated list of IPs/CIDR ranges to allow | (required) |
| `status_code` | HTTP status code to return for blocked IPs | `403` |
## Configuration Examples
### Docker/Docker Compose (Basic)
```yaml
services:
admin:
labels:
easyhaproxy.http.host: admin.example.com
easyhaproxy.http.plugins: ip_whitelist
easyhaproxy.http.plugin.ip_whitelist.allowed_ips: 192.168.1.0/24,10.0.0.5
easyhaproxy.http.plugin.ip_whitelist.status_code: 403
```
### Office Network Access
```yaml
labels:
easyhaproxy.http.host: admin.example.com
easyhaproxy.http.plugins: ip_whitelist
easyhaproxy.http.plugin.ip_whitelist.allowed_ips: 203.0.113.0/24,198.51.100.42
```
### Kubernetes Annotations
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
easyhaproxy.plugins: "ip_whitelist"
easyhaproxy.plugin.ip_whitelist.allowed_ips: "192.168.1.0/24,10.0.0.5"
easyhaproxy.plugin.ip_whitelist.status_code: "403"
spec:
rules:
- host: admin.example.com
http:
paths:
- path: /
backend:
service:
name: admin-panel
port:
number: 80
```
### Static YAML Configuration
```yaml
# /etc/haproxy/static/config.yaml
easymapping:
- host: admin.example.com
port: 443
container: admin-panel:443
plugins:
- ip_whitelist
plugin_config:
ip_whitelist:
allowed_ips: 192.168.1.0/24,10.0.0.5
status_code: 403
```
## Generated HAProxy Configuration
```haproxy
# IP Whitelist - Only allow specific IPs
acl whitelisted_ip src 192.168.1.0/24 10.0.0.5
http-request deny deny_status 403 if !whitelisted_ip
```
## IP Address Formats
The plugin supports:
- **Single IPs:** `10.0.0.5`, `203.0.113.42`
- **CIDR ranges:** `192.168.1.0/24`, `10.0.0.0/8`
- **Multiple entries:** Comma-separated list of IPs and/or CIDR ranges
## Important Notes
- **Warning:** This blocks ALL IPs except those in the whitelist. Make sure to include your own IP!
- The plugin runs once per domain during the discovery cycle
- Test thoroughly before deploying to production
- Consider using VPN CIDR ranges for remote access
- Works well with staging and admin environments
## Related Documentation
- [Plugin System Overview](../plugins.md)
- [Container Labels Reference](../container-labels.md)

View file

@ -0,0 +1,226 @@
# JWT Validator Plugin
**Type:** Domain Plugin
**Runs:** Once for each discovered domain/host
## Overview
The JWT Validator plugin 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
| Option | Description | Default |
|---------------|----------------------------------------------------------------------------------------------|-------------|
| `enabled` | Enable/disable plugin | `true` |
| `algorithm` | JWT signing algorithm | `RS256` |
| `issuer` | Expected JWT issuer (optional, set to `none`/`null` to skip validation) | (optional) |
| `audience` | Expected JWT audience (optional, set to `none`/`null` to skip validation) | (optional) |
| `pubkey_path` | Path to public key file (required if `pubkey` not provided) | (required) |
| `pubkey` | Public key content as base64-encoded string (required if `pubkey_path` not provided) | (optional) |
| `paths` | List of paths that require JWT validation (optional) | (all paths) |
| `only_paths` | If `true`, only specified paths are accessible; if `false`, only specified paths require JWT | `false` |
## Path Validation Logic
- **No paths configured:** ALL requests to the domain require JWT validation (default behavior)
- **Paths configured + `only_paths=false`:** Only specified paths require JWT validation, other paths pass through without validation
- **Paths configured + `only_paths=true`:** Only specified paths are accessible (with JWT validation), all other paths are denied
## Configuration Examples
### Docker/Docker Compose (Protect All Paths)
```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
```
### Protect Specific Paths Only
```yaml
labels:
easyhaproxy.http.plugins: jwt_validator
easyhaproxy.http.plugin.jwt_validator.pubkey_path: /etc/haproxy/jwt_keys/api_pubkey.pem
easyhaproxy.http.plugin.jwt_validator.paths: /api/admin,/api/sensitive
easyhaproxy.http.plugin.jwt_validator.only_paths: false
# /api/health, /api/docs, etc. remain publicly accessible
```
### Only Allow Specific Paths
```yaml
labels:
easyhaproxy.http.plugins: jwt_validator
easyhaproxy.http.plugin.jwt_validator.pubkey_path: /etc/haproxy/jwt_keys/api_pubkey.pem
easyhaproxy.http.plugin.jwt_validator.paths: /api/public,/api/v1
easyhaproxy.http.plugin.jwt_validator.only_paths: true
# All paths except /api/public and /api/v1 are denied
```
### 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
```
### Kubernetes Annotations
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
easyhaproxy.plugins: "jwt_validator"
easyhaproxy.plugin.jwt_validator.algorithm: "RS256"
easyhaproxy.plugin.jwt_validator.issuer: "https://auth.example.com/"
easyhaproxy.plugin.jwt_validator.audience: "https://api.example.com"
easyhaproxy.plugin.jwt_validator.pubkey_path: "/etc/haproxy/jwt_keys/api_pubkey.pem"
easyhaproxy.plugin.jwt_validator.paths: "/api/admin,/api/users"
easyhaproxy.plugin.jwt_validator.only_paths: "false"
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
backend:
service:
name: api-service
port:
number: 8080
```
### Static YAML Configuration
```yaml
# /etc/haproxy/static/config.yaml
easymapping:
- host: api.example.com
port: 443
container: api-service:8080
plugins:
- jwt_validator
plugin_config:
jwt_validator:
algorithm: RS256
issuer: https://auth.example.com/
audience: https://api.example.com
pubkey_path: /etc/haproxy/jwt_keys/api_pubkey.pem
```
## Generated HAProxy Configuration
### All Paths Protected
```haproxy
# 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 }
```
### Specific Paths Only (only_paths=false)
```haproxy
# JWT Validator - Validate JWT tokens
# Define paths that require JWT validation
acl jwt_protected_path path_beg /api/admin
acl jwt_protected_path path_beg /api/sensitive
http-request deny content-type 'text/html' string 'Missing Authorization HTTP header' unless { req.hdr(authorization) -m found } if jwt_protected_path
# Extract JWT header and payload
http-request set-var(txn.alg) http_auth_bearer,jwt_header_query('$.alg') if jwt_protected_path
http-request set-var(txn.iss) http_auth_bearer,jwt_payload_query('$.iss') if jwt_protected_path
http-request set-var(txn.aud) http_auth_bearer,jwt_payload_query('$.aud') if jwt_protected_path
http-request set-var(txn.exp) http_auth_bearer,jwt_payload_query('$.exp','int') if jwt_protected_path
# Validate JWT (only on protected paths)
http-request deny content-type 'text/html' string 'Unsupported JWT signing algorithm' unless { var(txn.alg) -m str RS256 } if jwt_protected_path
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 } if jwt_protected_path
# Validate expiration
http-request set-var(txn.now) date() if jwt_protected_path
http-request deny content-type 'text/html' string 'JWT has expired' if { var(txn.exp),sub(txn.now) -m int lt 0 } if jwt_protected_path
```
### Specific Paths Only (only_paths=true)
```haproxy
# JWT Validator - Validate JWT tokens
# Define paths that require JWT validation
acl jwt_protected_path path_beg /api/public
acl jwt_protected_path path_beg /api/v1
# Deny access to paths not in the protected list
http-request deny content-type 'text/html' string 'Access denied' unless jwt_protected_path
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 (all requests at this point are on allowed paths)
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 }
```
## 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 Notes
- **Required:** HAProxy 2.5+ with JWT support
- Mount public key file as read-only volume
- The plugin runs once per domain during the discovery cycle
- Test thoroughly with your JWT provider before deploying to production
## Related Documentation
- [Plugin System Overview](../plugins.md)
- [Container Labels Reference](../container-labels.md)