1
0
Fork 0

Documentation Refactor

This commit is contained in:
Joao Gilberto Magalhaes 2026-02-20 01:05:37 -05:00
parent 3b8818e636
commit a410b34521
47 changed files with 2065 additions and 3911 deletions

158
README.md
View file

@ -13,133 +13,48 @@
## Service discovery for HAProxy ## Service discovery for HAProxy
EasyHAProxy dynamically creates the `haproxy.cfg` based on metadata collected from your workloads (Docker labels, Swarm service labels, or Kubernetes ingress annotations). EasyHAProxy dynamically creates `haproxy.cfg` based on metadata from your workloads — Docker labels, Swarm service labels, or Kubernetes Ingress annotations. No HAProxy knowledge required.
EasyHAProxy can detect and configure HAProxy automatically on the following platforms: ## Features
- Docker - **Automatic service discovery** — Docker, Docker Swarm, Kubernetes, or static YAML
- Docker Swarm - **Zero-downtime HAProxy reload** — configuration updates happen without dropping connections
- Kubernetes - **Automatic TLS with ACME** — Let's Encrypt, ZeroSSL, BuyPass, and more
- Static YAML definitions (`EASYHAPROXY_DISCOVER=static`) - **Custom SSL certificates** — volume-mount or label-embed your own PEM files
- **TCP mode** — proxy any TCP service, not just HTTP
- **Plugin system** — JWT validation, IP whitelisting, Cloudflare IP restoration, FastCGI, path blocking, and custom plugins
- **HAProxy stats dashboard** — optional, password-protected
- **Balance algorithms** — roundrobin, leastconn, source, uri, and more
## Supported platforms
[![Kubernetes](docs/easyhaproxy_kubernetes.png)](docs/getting-started/kubernetes.md)
[![Docker Swarm](docs/easyhaproxy_swarm.png)](docs/getting-started/swarm.md)
[![Docker](docs/easyhaproxy_docker.png)](docs/getting-started/docker.md)
[![Static](docs/easyhaproxy_static.png)](docs/getting-started/static.md)
Install using tools:
[![Helm](docs/easyhaproxy_helm.png)](docs/guides/helm.md)
[![MicroK8s](docs/easyhaproxy_microk8s.png)](docs/guides/microk8s.md)
[![Dokku](docs/easyhaproxy_dokku.png)](docs/guides/dokku.md)
[![DigitalOcean](docs/easyhaproxy_digitalocean.png)](docs/guides/digitalocean.md)
## Documentation
| Section | Description |
|---------|-------------|
| **[Getting Started](docs/getting-started/index.md)** | Choose your runtime and discovery mode, minimal working setup |
| **[Guides](docs/guides/ssl.md)** | SSL, ACME, plugins, Helm, MicroK8s, Dokku, DigitalOcean |
| **[Concepts](docs/concepts/index.md)** | Service discovery, config pipeline, plugin model, TLS termination |
| **[Reference](docs/reference/environment-variables.md)** | Environment variables, container labels, CLI flags, volumes |
## Who is using? ## Who is using?
EasyHAProxy is part of some projects: EasyHAProxy is part of some projects:
- Dokku - [Dokku](docs/guides/dokku.md)
- MicroK8s - [MicroK8s](docs/guides/microk8s.md)
- DigitalOcean Marketplace - [DigitalOcean Marketplace](docs/guides/digitalocean.md)
See detailed instructions on how to install below.
## EasyHAProxy Mission
Easy to set up and low configuration to numerous features.
## Features
EasyHAProxy will discover services based on Docker (or Swarm) labels and Kubernetes ingress annotations, then dynamically build the `haproxy.cfg`. Below, EasyHAProxy main features:
- Support Automatic Certificate Management Environment (ACME) protocol compatible with Let's Encrypt and other CAs.
- Set your custom SSL certificates
- Balance traffic between multiple replicas
- Set SSL policies (`strict`, `default`, `loose`) via `EASYHAPROXY_SSL_MODE`.
- Set up HAProxy to listen to TCP.
- Add redirects.
- Enable/disable Stats on port 1936 with a custom password.
- Enable/disable custom errors.
Also, it is possible to set up HAProxy from a simple Yaml file instead of creating `haproxy.cfg` file.
## How Does It Work?
You don't need to change your current infrastructure and don't need to learn the HAProxy configuration.
The steps are:
- Run the EasyHAProxy container;
- Add some labels to the containers you want to be parsed by EasyHAProxy (see detailed instructions below);
- EasyHAProxy will automatically detect the containers, set up, and reload the HAProxy configurations for you without downtime.
## Detailed Instructions
For detailed instructions on how to use EasyHAProxy, follow the instructions for the platform you want to use:
[![Kubernetes](docs/easyhaproxy_kubernetes.png)](docs/kubernetes.md)
[![Docker Swarm](docs/easyhaproxy_swarm.png)](docs/swarm.md)
[![Docker](docs/easyhaproxy_docker.png)](docs/docker.md)
[![Static](docs/easyhaproxy_static.png)](docs/static.md)
Or you can install using tools:
[![Helm](docs/easyhaproxy_helm.png)](docs/helm.md)
[![MicroK8s](docs/easyhaproxy_microk8s.png)](docs/microk8s.md)
[![Dokku](docs/easyhaproxy_dokku.png)](docs/dokku.md)
[![DigitalOcean](docs/easyhaproxy_digitalocean.png)](docs/digitalocean.md)
## Special Topics
If you already set up the EasyHAProxy, is time to go deeper:
- [Custom SSL](docs/ssl.md)
- [Automatic Certificate Issuing](docs/acme.md) (e.g. Letsencrypt)
## Configuration Reference
Detailed configuration guides for advanced setups:
- [Container Labels](docs/container-labels.md) - Configure Docker/Swarm containers with labels
- [Environment Variables](docs/environment-variable.md) - Configure EasyHAProxy behavior
- [Volumes](docs/volumes.md) - Map volumes for certificates, config, and custom files
- [Plugins](docs/plugins.md) - Extend HAProxy with plugins ([Development Guide](docs/plugin-development.md))
- [JWT Validator](docs/Plugins/jwt-validator.md) - JWT authentication validation
- [FastCGI](docs/Plugins/fastcgi.md) - PHP-FPM and FastCGI application support
- [Cloudflare](docs/Plugins/cloudflare.md) - Restore visitor IP from Cloudflare CDN
- [IP Whitelist](docs/Plugins/ip-whitelist.md) - Restrict access to IPs/CIDR ranges
- [Deny Pages](docs/Plugins/deny-pages.md) - Block access to specific paths
- [Cleanup](docs/Plugins/cleanup.md) - Automatic cleanup of temporary files
- [Other Configurations](docs/other.md) - Additional configurations (ports, custom errors, etc.)
- [Limitations](docs/limitations.md) - Important limitations and considerations
## Development
### Requirements
- Python 3.11 or higher
- [uv](https://github.com/astral-sh/uv) package manager
### Installation for Development
```bash
# Install uv (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Clone the repository
git clone https://github.com/byjg/docker-easy-haproxy.git
cd docker-easy-haproxy
# Install dependencies (creates virtual environment automatically)
uv sync --dev
# Run tests
make test
# or directly: uv run pytest tests/ -vv
# Run linting
make lint
# Format code
make format
```
### Installing the Package
```bash
# Install with uv
uv pip install easymapping
# Or install from source
uv pip install -e ".[dev]"
```
## See EasyHAProxy in action ## See EasyHAProxy in action
@ -154,6 +69,5 @@ Click on the image to see the videos (use HD for better visualization)
[Here is the code](https://gist.github.com/byjg/e125e478a0562190176d69ea795fd3d4) applied in the test examples above. [Here is the code](https://gist.github.com/byjg/e125e478a0562190176d69ea795fd3d4) applied in the test examples above.
---- ----
[Open source ByJG](http://opensource.byjg.com) [Open source ByJG](http://opensource.byjg.com)

View file

@ -27,7 +27,7 @@ kubectl apply -f easyhaproxy-nodeport.yml
kubectl apply -f easyhaproxy-clusterip.yml kubectl apply -f easyhaproxy-clusterip.yml
``` ```
For more details, see the [Kubernetes documentation](../../docs/kubernetes.md). For more details, see the [Kubernetes documentation](../../docs/getting-started/kubernetes.md).
## Regenerating These Files ## Regenerating These Files

View file

@ -1,385 +0,0 @@
---
sidebar_position: 16
---
# 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.
## Generating JWT Keys
```bash
# Generate RSA key pair (idempotent - skips if exists)
[ -f jwt_private.pem ] || openssl genrsa -out jwt_private.pem 2048
[ -f jwt_pubkey.pem ] || openssl rsa -in jwt_private.pem -pubout -out jwt_pubkey.pem
```
## 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 (priority 1: explicit file path) | (optional) |
| `pubkey` | Public key content as base64-encoded string (priority 2: inline content) | (optional) |
| `k8s_secret.pubkey` | Kubernetes secret containing public key (priority 3: Kubernetes only - see below) | (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` |
| `allow_anonymous` | If `true`, allows requests without Authorization header (validates JWT if present) | `false` |
### Public Key Configuration Priority
When multiple public key options are configured, they are evaluated in this order:
1. **`pubkey_path`** - Direct file path (explicit configuration)
2. **`pubkey`** - Base64-encoded key content (inline configuration)
3. **`k8s_secret.pubkey`** - Kubernetes secret (recommended for Kubernetes deployments)
The first configured option is used; others are ignored.
## 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
## Anonymous Access Logic
- **`allow_anonymous=false` (default):** Requests without `Authorization` header are denied with "Missing Authorization HTTP header"
- **`allow_anonymous=true`:** Requests without `Authorization` header are allowed to pass through, but JWTs are validated if the header is present
**Use Cases for `allow_anonymous=true`:**
- Optional authentication (show different content for authenticated vs anonymous users)
- Mixed public/private content where some users have enhanced access with JWT
- Gradual JWT authentication rollout
- Public APIs that provide additional features to authenticated users
## 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/easyhaproxy/jwt_keys/api_pubkey.pem
volumes:
- ./pubkey.pem:/etc/easyhaproxy/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/easyhaproxy/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/easyhaproxy/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/easyhaproxy/jwt_keys/api_pubkey.pem
```
### Allow Anonymous Access (Optional JWT)
```yaml
services:
api:
labels:
easyhaproxy.http.host: api.example.com
easyhaproxy.http.plugins: jwt_validator
easyhaproxy.http.plugin.jwt_validator.pubkey_path: /etc/easyhaproxy/jwt_keys/api_pubkey.pem
easyhaproxy.http.plugin.jwt_validator.allow_anonymous: true
volumes:
- ./pubkey.pem:/etc/easyhaproxy/jwt_keys/api_pubkey.pem:ro
# Requests without Authorization header are allowed
# Requests with Authorization header are validated
# Invalid JWTs are rejected
```
### Kubernetes with Secrets (Recommended)
The recommended way to configure JWT public keys in Kubernetes is using Kubernetes Secrets with the `k8s_secret` pattern:
```yaml
---
# Create a secret with your JWT public key
apiVersion: v1
kind: Secret
metadata:
name: jwt-pubkey-secret
namespace: production
type: Opaque
stringData:
pubkey: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
---
# Reference it in your ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
namespace: production
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"
# Load public key from Kubernetes secret
easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "jwt-pubkey-secret"
easyhaproxy.plugin.jwt_validator.paths: "/api/admin,/api/users"
easyhaproxy.plugin.jwt_validator.only_paths: "false"
spec:
ingressClassName: easyhaproxy
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
```
**With explicit secret key name:**
```yaml
metadata:
annotations:
# Use custom key name from the secret
easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "jwt-pubkey-secret/rsa-public-key"
```
For complete details about the `k8s_secret` pattern, including auto-detect vs explicit key names, troubleshooting, and security considerations, see: [Loading Plugin Configuration from Kubernetes Secrets](../kubernetes.md#loading-plugin-configuration-from-kubernetes-secrets)
### Kubernetes with pubkey_path (Legacy)
You can also mount the public key file using ConfigMaps or volumes:
```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/easyhaproxy/jwt_keys/api_pubkey.pem"
easyhaproxy.plugin.jwt_validator.paths: "/api/admin,/api/users"
easyhaproxy.plugin.jwt_validator.only_paths: "false"
spec:
ingressClassName: easyhaproxy
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
```
**Note:** This requires mounting the public key file into the EasyHAProxy pod using ConfigMaps or volumes. Using `k8s_secret.pubkey` is recommended as it's simpler and more secure.
### Static YAML Configuration
```yaml
# /etc/easyhaproxy/static/config.yaml
containers:
"api.example.com:443":
ip: ["api-service:8080"]
ssl: true
plugins: [jwt_validator]
plugin:
jwt_validator:
algorithm: RS256
issuer: https://auth.example.com/
audience: https://api.example.com
pubkey_path: /etc/easyhaproxy/jwt_keys/api_pubkey.pem
```
### Environment Variables
Configure JWT Validator plugin defaults for all domains:
| Environment Variable | Config Key | Type | Default | Description |
|----------------------------------------------------|-------------------|---------|---------|---------------------------------------------|
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_ENABLED` | `enabled` | boolean | `true` | Enable/disable plugin for all domains |
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_ALGORITHM` | `algorithm` | string | `RS256` | JWT signing algorithm |
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_ISSUER` | `issuer` | string | - | Expected JWT issuer (optional) |
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_AUDIENCE` | `audience` | string | - | Expected JWT audience (optional) |
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_PUBKEY_PATH` | `pubkey_path` | string | - | Path to public key file |
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_PUBKEY` | `pubkey` | string | - | Public key as base64-encoded string |
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_PATHS` | `paths` | string | - | Comma-separated paths requiring JWT |
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_ONLY_PATHS` | `only_paths` | boolean | `false` | If true, only specified paths accessible |
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_ALLOW_ANONYMOUS` | `allow_anonymous` | boolean | `false` | Allow requests without Authorization header |
**Note:** Environment variables set defaults for ALL domains. To configure per-domain, use container labels or Kubernetes annotations.
## 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/easyhaproxy/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/easyhaproxy/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/easyhaproxy/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 }
```
### Allow Anonymous Access (allow_anonymous=true)
```haproxy
# JWT Validator - Validate JWT tokens
# Allow anonymous access - validate JWT only if Authorization header is present
# Extract JWT header and payload
http-request set-var(txn.alg) http_auth_bearer,jwt_header_query('$.alg') if { req.hdr(authorization) -m found }
http-request set-var(txn.iss) http_auth_bearer,jwt_payload_query('$.iss') if { req.hdr(authorization) -m found }
http-request set-var(txn.aud) http_auth_bearer,jwt_payload_query('$.aud') if { req.hdr(authorization) -m found }
http-request set-var(txn.exp) http_auth_bearer,jwt_payload_query('$.exp','int') if { req.hdr(authorization) -m found }
# Validate JWT (only if Authorization header is present)
http-request deny content-type 'text/html' string 'Unsupported JWT signing algorithm' unless { var(txn.alg) -m str RS256 } if { req.hdr(authorization) -m found }
http-request deny content-type 'text/html' string 'Invalid JWT issuer' unless { var(txn.iss) -m str https://auth.example.com/ } if { req.hdr(authorization) -m found }
http-request deny content-type 'text/html' string 'Invalid JWT audience' unless { var(txn.aud) -m str https://api.example.com } if { req.hdr(authorization) -m found }
http-request deny content-type 'text/html' string 'Invalid JWT signature' unless { http_auth_bearer,jwt_verify(txn.alg,"/etc/easyhaproxy/jwt_keys/api_pubkey.pem") -m int 1 } if { req.hdr(authorization) -m found }
# Validate expiration (only if Authorization header is present)
http-request set-var(txn.now) date() if { req.hdr(authorization) -m found }
http-request deny content-type 'text/html' string 'JWT has expired' if { var(txn.exp),sub(txn.now) -m int lt 0 } if { req.hdr(authorization) -m found }
```
## 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)

View file

@ -0,0 +1,4 @@
{
"label": "Concepts",
"position": 3
}

103
docs/concepts/index.md Normal file
View file

@ -0,0 +1,103 @@
---
sidebar_position: 1
sidebar_label: "Concepts"
---
# Concepts
This section explains how EasyHAProxy works under the hood — the mental models that help you understand why things are configured the way they are.
## Service Discovery
EasyHAProxy runs a polling loop every N seconds (default 10, configurable via `EASYHAPROXY_REFRESH_CONF`). On each tick it:
1. **Queries your runtime** — Docker API for containers/services, Kubernetes API for Ingress objects, or reads the static YAML file.
2. **Filters by label/annotation prefix** — only resources that carry the `easyhaproxy` prefix (or your custom `EASYHAPROXY_LABEL_PREFIX`) are considered.
3. **Builds an intermediate structure** — an in-memory list of (host, port, backend) tuples called `easymapping`.
4. **Runs plugins** — global plugins run once; domain plugins run once per host entry.
5. **Renders `haproxy.cfg`** from a Jinja2 template using the `easymapping` data.
6. **Reloads HAProxy** if the rendered config differs from the previous one (zero-downtime reload).
### Discovery mode comparison
| Mode | Source polled | Auth required |
|------|--------------|---------------|
| `docker` | Docker socket `/var/run/docker.sock` | Socket access |
| `swarm` | Docker socket (Swarm API) | Socket access |
| `kubernetes` | Kubernetes API server | RBAC (`get`/`list` on Ingress, Secret) |
| `static` | File on disk | None |
## Configuration Pipeline
```
Labels / Annotations / YAML file
Discovery (Docker / Swarm / K8s / Static)
parsed_object { IP → label map }
[GLOBAL PLUGINS] ← executed once
easymapping [ list of domain configs ]
For each domain:
[DOMAIN PLUGINS] ← executed per domain
PluginResult snippets collected
Jinja2 template render → haproxy.cfg
HAProxy reload (if config changed)
```
The key insight: **you never write `haproxy.cfg` by hand**. You express intent through labels/annotations/YAML, and EasyHAProxy translates that into valid HAProxy configuration on every discovery cycle.
## Plugin Execution Model
Plugins are Python classes that implement `PluginInterface`. They are discovered automatically from:
1. `/src/plugins/builtin/` — bundled plugins
2. `/etc/easyhaproxy/plugins/` — your custom plugins
**Two execution types:**
### GLOBAL plugins
- Run **once per discovery cycle**, before any domain processing.
- Receive the full `parsed_object` (all discovered services).
- Use cases: cleanup tasks, global monitoring, DNS updates.
- Example: `cleanup` plugin.
### DOMAIN plugins
- Run **once per discovered domain/host**.
- Receive domain-specific context: domain name, port, label/annotation map.
- Return `PluginResult` containing HAProxy config snippets to inject into the backend section.
- Use cases: IP whitelisting, JWT validation, Cloudflare IP restoration, path blocking.
- Examples: `cloudflare`, `deny_pages`, `jwt_validator`, `ip_whitelist`, `fastcgi`.
**Configuration precedence** (highest to lowest):
1. Container labels / Ingress annotations (per-domain)
2. Static YAML `plugins.config` block
3. Environment variables `EASYHAPROXY_PLUGIN_<NAME>_<KEY>`
See the [Plugin Developer Guide](../guides/plugin-development.md) for how to build your own plugin.
## SSL/TLS Termination Model
SSL termination happens **at HAProxy**, not in your backend containers.
```
Internet → HAProxy (TLS decryption) → Backend container (plain HTTP)
```
Your containers should only serve HTTP on their internal port. HAProxy handles all TLS on ports 80/443.
**Certificate sources** (evaluated in this order for each domain):
1. **ACME/Certbot** — if `certbot=true` label is set, EasyHAProxy runs Certbot HTTP-01 challenge automatically.
2. **Volume-mounted PEM** — files in `/etc/easyhaproxy/certs/haproxy/{domain}.pem`.
3. **Label-embedded PEM** — base64 certificate in the `sslcert` label.
Both ACME and manual certificates can coexist. Per domain, whichever source is configured takes precedence as shown above.
See [SSL setup](../guides/ssl.md) and [ACME/Let's Encrypt](../guides/acme.md) for step-by-step configuration.

View file

@ -1,5 +1,6 @@
--- ---
sidebar_position: 23 sidebar_position: 2
sidebar_label: "Limitations"
--- ---
# Limitations and Considerations # Limitations and Considerations

View file

@ -1,79 +0,0 @@
---
sidebar_position: 3
---
# Docker
## Setup Docker EasyHAProxy
This method involves using a standalone Docker installation to discover containers
and configure HAProxy.
EasyHAProxy inspects Docker containers and retrieves labels to configure HAProxy.
Once it identifies a container with at least the label 'easyhaproxy.http.host,'
it configures HAProxy to redirect traffic to that container.
To accomplish this, EasyHAProxy may need to attach the same network to its container.
It's recommended to create a network external to EasyHAProxy, although it's not mandatory.
:::warning Limitations
- You cannot mix Docker containers with Swarm containers.
- This method does not work with containers that use the `--network=host` option. See [limitations](limitations.md) for details.
:::
For example:
```bash title="Create EasyHAProxy network"
docker network create easyhaproxy
```
And then run the EasyHAProxy:
```bash title="Run EasyHAProxy container"
docker run -d \
--name easy-haproxy-container \
-v /var/run/docker.sock:/var/run/docker.sock \
-e EASYHAPROXY_DISCOVER="docker" \
# + Environment Variables \
-p 80:80 \
-p 443:443 \
-p 1936:1936 \
--network easyhaproxy
byjg/easy-haproxy
```
Mapping to `/var/run/docker.sock` is necessary to discover the docker containers and get the labels;
## Running containers
To make your containers "discoverable" by EasyHAProxy, this is the minimum configuration you need:
```bash title="Run container with EasyHAProxy labels"
docker run -d \
--label easyhaproxy.http.host=example.org \
--label easyhaproxy.http.port=80 \
--label easyhaproxy.http.localport=8080 \
--network easyhaproxy
my/image:tag
```
Once the container is running, EasyHAProxy will detect automatically and start to redirect all traffic from `example.org:80` to your container.
You don't need to expose any port in your container.
Please follow the [docker label configuration](container-labels.md) to see other configurations available.
## Setup the EasyHAProxy container
You can configure the behavior of the EasyHAProxy by setup specific environment variables. To get a list of the variables, please follow the [environment variable guide](environment-variable.md)
## Setup certificates with ACME (e.g. Letsencrypt)
Follow [this link](acme.md)
## Setup your own certificates
Follow [this link](ssl.md)
----
[Open source ByJG](http://opensource.byjg.com)

View file

@ -0,0 +1,4 @@
{
"label": "Getting Started",
"position": 1
}

View file

@ -0,0 +1,66 @@
---
sidebar_position: 3
sidebar_label: "Docker"
---
# Docker
EasyHAProxy inspects running Docker containers, reads their labels, and configures HAProxy automatically.
:::warning Limitations
- You cannot mix Docker containers with Swarm containers.
- This method does not work with containers that use the `--network=host` option. See [limitations](../concepts/limitations.md) for details.
:::
## Step 1 — Create a shared network
```bash
docker network create easyhaproxy
```
It's recommended to use an external network so EasyHAProxy and your app containers can communicate.
## Step 2 — Run EasyHAProxy
```bash
docker run -d \
--name easy-haproxy-container \
-v /var/run/docker.sock:/var/run/docker.sock \
-e EASYHAPROXY_DISCOVER="docker" \
-p 80:80 \
-p 443:443 \
-p 1936:1936 \
--network easyhaproxy \
byjg/easy-haproxy
```
Mounting `/var/run/docker.sock` is required so EasyHAProxy can query the Docker API.
## Step 3 — Label your container
```bash
docker run -d \
--label easyhaproxy.http.host=example.org \
--label easyhaproxy.http.port=80 \
--label easyhaproxy.http.localport=8080 \
--network easyhaproxy \
my/image:tag
```
EasyHAProxy detects this container automatically and routes traffic from `example.org:80` to port 8080 in your container. You do not need to expose any container ports.
## Step 4 — Verify
Open `http://example.org` in your browser (or `curl http://example.org`). Traffic should reach your container.
---
## Full options
- [Container label reference](../reference/container-labels.md) — all available labels
- [Environment variable reference](../reference/environment-variables.md) — configure EasyHAProxy behavior
- [SSL certificates](../guides/ssl.md) — add custom TLS
- [ACME / Let's Encrypt](../guides/acme.md) — automatic certificate issuing
----
[Open source ByJG](http://opensource.byjg.com)

View file

@ -0,0 +1,51 @@
---
sidebar_position: 1
sidebar_label: "Getting Started"
---
# Getting Started with EasyHAProxy
EasyHAProxy dynamically builds `haproxy.cfg` from metadata on your running workloads — no HAProxy knowledge required.
## Choose your runtime
EasyHAProxy runs in two ways:
| Runtime | When to choose |
|---------|---------------|
| **Docker container** (`byjg/easy-haproxy`) | You already run Docker, Swarm, or Kubernetes |
| **Native host** (`easy-haproxy` CLI) | You want HAProxy on the host with full OS control — no Docker needed |
## Choose your discovery mode
Once running, EasyHAProxy discovers your services in one of four ways:
| Mode | How it works | Label/annotation format |
|------|-------------|------------------------|
| **Docker** | Reads labels from running containers on a Docker host | Container labels |
| **Swarm** | Reads labels from services in a Docker Swarm cluster | Service labels |
| **Kubernetes** | Reads `ingressClassName: easyhaproxy` Ingress resources | Ingress annotations |
| **Static** | Reads a hand-written YAML file you provide | YAML file |
## Quick-start guides
Pick the guide that matches your environment:
### Container runtimes (Docker image)
- **[Docker](docker.md)** — standalone Docker host, container labels
- **[Docker Swarm](swarm.md)** — overlay network, service labels
- **[Kubernetes](kubernetes.md)** — Ingress controller, DaemonSet/NodePort
- **[Static YAML](static.md)** — any environment, config file
### Native host (pip/uv package)
- **[Native install](native.md)** — HAProxy on the host, `easy-haproxy` CLI
## What's next?
After you have traffic flowing:
- **[Guides](../guides/ssl.md)** — SSL certificates, ACME/Let's Encrypt, plugins
- **[Concepts](../concepts/index.md)** — how service discovery and the config pipeline work
- **[Reference](../reference/environment-variables.md)** — full environment variable and label tables

View file

@ -0,0 +1,256 @@
---
sidebar_position: 1
sidebar_label: "Kubernetes"
---
# Kubernetes
EasyHAProxy acts as an Ingress controller for Kubernetes — it watches Ingress resources with `ingressClassName: easyhaproxy` and configures HAProxy automatically.
:::info How it works
EasyHAProxy queries all ingress definitions with either the
`spec.ingressClassName: easyhaproxy` field (recommended) or the deprecated annotation
`kubernetes.io/ingress.class: easyhaproxy-ingress` (for backward compatibility).
:::
## Deployment Modes
Choose the deployment mode that fits your infrastructure:
| Mode | Workload | Exposed Ports | Node Label | Recommended When |
|-----------|------------|------------------------|------------|-------------------------------|
| NodePort | Deployment | 31080 / 31443 / 31936 | No | Most setups **(recommended)** |
| ClusterIP | Deployment | cluster-internal only | No | Behind a LoadBalancer **(recommended)** |
| DaemonSet | DaemonSet | 80 / 443 / 1936 (host) | Yes | Bare-metal, special cases |
**NodePort** and **ClusterIP** run as a Deployment — no node label needed, and they survive node replacements safely.
**DaemonSet** binds to host ports and requires a node label (`easyhaproxy/node=master`) via `nodeAffinity`. The label must be manually reapplied after any node replacement, which can cause outages. Use only for bare-metal or special-case setups.
EasyHAProxy detects its deployment mode automatically (`EASYHAPROXY_DEPLOYMENT_MODE=auto`). See the [environment variable reference](../reference/environment-variables.md#kubernetes) for manual override options.
## Step 1 — Install EasyHAProxy
```bash
kubectl create namespace easyhaproxy
```
### NodePort (recommended)
Exposes HAProxy on NodePort `31080` (HTTP), `31443` (HTTPS), and `31936` (stats). Point your DNS or external load balancer to any node IP on these ports.
```bash
kubectl apply -f \
https://raw.githubusercontent.com/byjg/docker-easy-haproxy/6.0.0/deploy/kubernetes/easyhaproxy-nodeport.yml
```
### ClusterIP (behind a LoadBalancer)
Cluster-internal only. Pair with an external cloud LoadBalancer or `kubectl port-forward` for local testing.
```bash
kubectl apply -f \
https://raw.githubusercontent.com/byjg/docker-easy-haproxy/6.0.0/deploy/kubernetes/easyhaproxy-clusterip.yml
```
### DaemonSet (special cases — requires node label)
:::warning Requires node label maintenance
The node label must be reapplied after any node replacement. Failing to do so will cause an outage.
:::
```bash
# Label the target node first
kubectl label nodes node-01 "easyhaproxy/node=master"
kubectl apply -f \
https://raw.githubusercontent.com/byjg/docker-easy-haproxy/6.0.0/deploy/kubernetes/easyhaproxy-daemonset.yml
```
If you need to configure environment variables (log levels, stats password, etc.), see the [environment variable reference](../reference/environment-variables.md).
## Step 2 — Create an Ingress
```yaml
kind: Ingress
metadata:
name: example-ingress
namespace: example
spec:
ingressClassName: easyhaproxy
rules:
- host: example.org
http:
paths:
- backend:
service:
name: example-service
port:
number: 8080
pathType: ImplementationSpecific
```
EasyHAProxy routes traffic from `example.org:80` to your service at port 8080. No container port exposure needed.
:::note Backward Compatibility
The deprecated annotation `kubernetes.io/ingress.class: easyhaproxy-ingress` is still supported but `spec.ingressClassName` is recommended for new deployments.
:::
## Step 3 — Verify
```bash
curl http://example.org
```
---
## Kubernetes Annotations
Customize EasyHAProxy behavior per-ingress using annotations. For the full annotations reference, see [Container Labels — Kubernetes Ingress Annotations](../reference/container-labels.md#kubernetes-ingress-annotations).
**Important**: Annotations apply to all hosts in the ingress configuration.
## Using Plugins
Add the `easyhaproxy.plugins` annotation with a comma-separated list of plugin names:
```yaml
metadata:
annotations:
easyhaproxy.plugins: "cloudflare,deny_pages"
easyhaproxy.plugin.deny_pages.paths: "/wp-admin,/wp-login.php"
easyhaproxy.plugin.deny_pages.status_code: "404"
```
For full plugin documentation, see the [Using Plugins](../guides/plugins.md) guide.
## Loading Plugin Configuration from Kubernetes Secrets
EasyHAProxy supports loading sensitive plugin configuration values from Kubernetes Secrets using the `k8s_secret` pattern:
```yaml
# Auto-detect key (tries common variations):
easyhaproxy.plugin.{plugin_name}.k8s_secret.{config_key}: "secret_name"
# Explicit key (no variations):
easyhaproxy.plugin.{plugin_name}.k8s_secret.{config_key}: "secret_name/key_name"
```
### How It Works
1. You create a Kubernetes Secret with your sensitive data
2. You reference the secret in your ingress annotation using the `k8s_secret` pattern
3. EasyHAProxy reads the secret from the same namespace as the ingress
4. EasyHAProxy transforms the annotation to inject the secret value
5. The plugin receives the value as if it was provided directly in the annotation
### Complete Example
```yaml
---
apiVersion: v1
kind: Secret
metadata:
name: jwt-pubkey-secret
namespace: production
type: Opaque
stringData:
pubkey: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
namespace: production
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.k8s_secret.pubkey: "jwt-pubkey-secret"
spec:
ingressClassName: easyhaproxy
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
```
### Priority Order
1. **Explicit annotation** (e.g., `easyhaproxy.plugin.jwt_validator.pubkey: "value"`)
2. **k8s_secret annotation** (e.g., `easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "secret"`)
### Security Considerations
- Secrets are read from the **same namespace** as the ingress (no cross-namespace access)
- EasyHAProxy needs RBAC permissions to read secrets (included in default deployment)
- Use Kubernetes RBAC to control which service accounts can read which secrets
## ACME / Let's Encrypt
Add the `easyhaproxy.certbot` annotation to enable automatic certificate issuing:
```yaml
kind: Ingress
metadata:
annotations:
easyhaproxy.certbot: 'true'
name: example-ingress
namespace: example
spec:
ingressClassName: easyhaproxy
```
More info in the [ACME guide](../guides/acme.md). Make sure ports 80 and 443 are publicly reachable.
## Custom SSL Certificates
Create a secret with your certificate and key and associate it with your ingress:
```yaml
---
apiVersion: v1
kind: Secret
metadata:
name: host2-tls
namespace: default
data:
tls.crt: base64 of your certificate
tls.key: base64 of your certificate private key
type: kubernetes.io/tls
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-example
namespace: default
spec:
ingressClassName: easyhaproxy
tls:
- hosts:
- host2.local
secretName: host2-tls
rules:
...
```
## Important Limitations
- The implementation doesn't support all ingress properties or wildcard domains.
- EasyHAProxy reads all `spec.rules[].host` values but parses only the **first path** per rule.
----
[Open source ByJG](http://opensource.byjg.com)

View file

@ -0,0 +1,130 @@
---
sidebar_position: 5
sidebar_label: "Native (pip/uv)"
---
# Native install (pip / uv)
EasyHAProxy can run directly on any Linux or macOS host without Docker, using the `easyhaproxy` Python package. HAProxy is installed on the host; EasyHAProxy manages it.
## Prerequisites
HAProxy must be installed and available in your system `PATH` before running `easy-haproxy`.
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs>
<TabItem value="debian" label="Debian / Ubuntu" default>
```bash
sudo apt install haproxy
```
</TabItem>
<TabItem value="rhel" label="RHEL / Fedora">
```bash
sudo dnf install haproxy
```
</TabItem>
<TabItem value="macos" label="macOS">
```bash
brew install haproxy
```
</TabItem>
</Tabs>
## Installation
### Recommended: `uv tool` (system-wide, isolated)
[`uv`](https://docs.astral.sh/uv/) installs `easyhaproxy` into its own isolated environment and exposes the `easy-haproxy` binary in `~/.local/bin/`.
```bash
# Install uv (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install easyhaproxy as a tool
uv tool install easyhaproxy
# Make sure ~/.local/bin is in PATH (one-time setup)
uv tool update-shell
```
### Alternative: `pip`
```bash
pip install easyhaproxy
```
:::note Virtual environments
When installing inside a virtual environment, `easy-haproxy` is only available while the environment is activated. For system-wide use, prefer `uv tool install`.
:::
## Quick start
### Static mode (bare-metal / VM)
```bash
mkdir -p ~/easyhaproxy/static
cat > ~/easyhaproxy/static/config.yml <<EOF
containers:
"myapp.example.com:80":
ip: ["127.0.0.1:3000"]
EOF
easy-haproxy --discover static
```
### Docker mode
```bash
easy-haproxy --discover docker
```
### Kubernetes mode
```bash
easy-haproxy --discover kubernetes
```
## Running as a systemd service
```ini title="/etc/systemd/system/easy-haproxy.service"
[Unit]
Description=EasyHAProxy
After=network.target
[Service]
ExecStart=/usr/local/bin/easy-haproxy --discover static --haproxy-password mysecret
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now easy-haproxy
```
:::tip Adjust ExecStart path
Run `which easy-haproxy` to get the correct binary path. If installed with `uv tool`, it is typically `/root/.local/bin/easy-haproxy` when running as root.
:::
---
## Full options
- [CLI Reference](../reference/cli.md) — all flags and environment variables
- [ACME / Let's Encrypt](../guides/acme.md) — automatic certificate issuing
- [Plugins](../guides/plugins.md) — extend functionality
----
[Open source ByJG](http://opensource.byjg.com)

View file

@ -0,0 +1,105 @@
---
sidebar_position: 4
sidebar_label: "Static YAML"
---
# Static YAML
Use this mode to configure EasyHAProxy from a hand-written YAML file. Works for any backend — Docker containers, VMs, bare-metal servers, or anything reachable by IP/hostname.
:::tip Live Reload
EasyHAProxy monitors this file for changes and automatically reconfigures HAProxy when any changes are detected.
:::
## Step 1 — Write a minimal config file
```yaml
containers:
"myapp.example.com:80":
ip: ["10.0.0.5:3000"]
```
Save this as `config.yml`.
## Step 2 — Run EasyHAProxy
```bash
docker run -d \
--name easy-haproxy-container \
-v /my/static/:/etc/easyhaproxy/static/ \
-e EASYHAPROXY_DISCOVER="static" \
-p 80:80 \
-p 443:443 \
-p 1936:1936 \
byjg/easy-haproxy
```
:::tip Docker Socket Optional
Mounting `/var/run/docker.sock` is not required in static discovery mode.
:::
## Step 3 — Verify
```bash
curl http://myapp.example.com
```
---
## Full YAML reference
```yaml
stats:
username: admin # Optional (default "admin")
password: password # If omitted, stats are public with no password
port: 1936 # Optional (default 1936)
customerrors: true # Optional (default false)
ssl_mode: default # Optional
logLevel:
certbot: DEBUG # Optional. Can be: TRACE,DEBUG,INFO,WARN,ERROR,FATAL
easyhaproxy: DEBUG # Optional. Can be: TRACE,DEBUG,INFO,WARN,ERROR,FATAL
haproxy: INFO # Optional. Can be: TRACE,DEBUG,INFO,WARN,ERROR,FATAL
certbot:
email: "acme@example.org" # If set, enables ACME/Certbot
autoconfig: "" # Well-known CA shorthand (e.g. letsencrypt)
eab_hmac_key: "" # Required by some CAs
eab_kid: "" # Required by some CAs
server: False # ACME endpoint URL (or False for Let's Encrypt)
retry_count: 60 # Retry after rate limit
containers:
# Format: "hostname:port"
"host1.com.br:80":
ip: ["container:5000"] # Endpoints: "address:localport"
certbot: true # Request certbot certificate
redirect_ssl: true # Redirect HTTP to HTTPS
mode: http # Default `http`. Can be http or tcp
# HTTPS version (SSL)
"host1.com.br:443":
ip: ["container:80"]
ssl: true # Enable SSL for this port
# Redirect www → main domain
"www.host1.com.br:80":
ip: ["container:5000"]
redirect_ssl: true
```
:::note SSL Certificates in Static Mode
The only way to provide SSL certificates in static configuration mode is to map the certificate files as a Docker volume. See the [SSL documentation](../guides/ssl.md) to learn how to configure this.
:::
---
## Full options
- [Container label reference](../reference/container-labels.md) — label semantics also apply to static YAML keys
- [Environment variable reference](../reference/environment-variables.md) — configure EasyHAProxy behavior
----
[Open source ByJG](http://opensource.byjg.com)

View file

@ -0,0 +1,100 @@
---
sidebar_position: 2
sidebar_label: "Docker Swarm"
---
# Docker Swarm
EasyHAProxy inspects Docker Swarm services, reads their labels, and configures HAProxy automatically across all nodes.
:::tip Docker Swarm Advantages
- **Container Discovery**: Docker Swarm facilitates the discovery of containers within the cluster.
- **Remote Node Management**: Manage containers across multiple nodes while EasyHAProxy configures HAProxy seamlessly.
:::
:::warning Limitations
- You cannot mix Docker containers with Swarm containers.
- This method does not work with containers that use the `--network=host` option. See [limitations](../concepts/limitations.md) for details.
:::
## Step 1 — Create an overlay network
```bash
docker network create -d overlay --attachable easyhaproxy
```
## Step 2 — Deploy EasyHAProxy as a Swarm stack
```yaml
services:
haproxy:
image: byjg/easy-haproxy:6.0.0
volumes:
- /var/run/docker.sock:/var/run/docker.sock
deploy:
replicas: 1
environment:
EASYHAPROXY_DISCOVER: swarm
EASYHAPROXY_SSL_MODE: "loose"
HAPROXY_CUSTOMERRORS: "true"
HAPROXY_USERNAME: admin
HAPROXY_PASSWORD: password
HAPROXY_STATS_PORT: 1936
ports:
- "80:80/tcp"
- "443:443/tcp"
- "1936:1936/tcp"
networks:
- easyhaproxy
networks:
easyhaproxy:
external: true
```
```bash
docker stack deploy --compose-file docker-compose.yml easyhaproxy
```
:::danger Single Replica Only
**Do not** add more than one replica for EasyHAProxy. To understand why, see the [limitations](../concepts/limitations.md) page.
:::
## Step 3 — Label your service
```yaml
services:
container:
image: my/image:tag
deploy:
replicas: 1
labels:
easyhaproxy.http.host: host1.local
easyhaproxy.http.port: 80
easyhaproxy.http.localport: 8080
networks:
- easyhaproxy
networks:
easyhaproxy:
external: true
```
EasyHAProxy detects this service automatically and routes traffic from `host1.local:80` to your container. You do not need to expose any container ports.
## Step 4 — Verify
```bash
curl http://host1.local
```
---
## Full options
- [Container label reference](../reference/container-labels.md) — all available labels
- [Environment variable reference](../reference/environment-variables.md) — configure EasyHAProxy behavior
- [Docker guide](docker.md) — more detailed Docker examples
----
[Open source ByJG](http://opensource.byjg.com)

View file

@ -0,0 +1,4 @@
{
"label": "Guides",
"position": 2
}

View file

@ -1,5 +1,6 @@
--- ---
sidebar_position: 11 sidebar_position: 2
sidebar_label: "ACME / Let's Encrypt"
--- ---
# SSL - Automatic Certificate Management Environment (ACME) # SSL - Automatic Certificate Management Environment (ACME)
@ -38,7 +39,7 @@ At a high level, ACME with Easy HAProxy works in two stages:
2. Enable ACME per domain (per service/app) 2. Enable ACME per domain (per service/app)
- Add the label `easyhaproxy.<definition>.certbot=true` to the service you want a certificate for. - Add the label `easyhaproxy.<definition>.certbot=true` to the service you want a certificate for.
- Ensure the service is exposed on HTTP port 80 from EasyHAProxys perspective (e.g., `easyhaproxy.<definition>.port=80`). ACME HTTP-01 will not work if the front port is not 80. - Ensure the service is exposed on HTTP port 80 from EasyHAProxy's perspective (e.g., `easyhaproxy.<definition>.port=80`). ACME HTTP-01 will not work if the front port is not 80.
- Provide the domain via `easyhaproxy.<definition>.host=yourdomain.tld` (and additional labels per your install method). - Provide the domain via `easyhaproxy.<definition>.host=yourdomain.tld` (and additional labels per your install method).
What happens under the hood What happens under the hood
@ -172,10 +173,6 @@ services:
EASYHAPROXY_CERTBOT_EMAIL: your-email@example.com EASYHAPROXY_CERTBOT_EMAIL: your-email@example.com
EASYHAPROXY_CERTBOT_AUTOCONFIG: letsencrypt EASYHAPROXY_CERTBOT_AUTOCONFIG: letsencrypt
# ACME/Certbot Configuration (Method 2: Manual)
# EASYHAPROXY_CERTBOT_EMAIL: your-email@example.com
# EASYHAPROXY_CERTBOT_SERVER: https://acme-v02.api.letsencrypt.org/directory
# Other settings # Other settings
EASYHAPROXY_SSL_MODE: "default" EASYHAPROXY_SSL_MODE: "default"
HAPROXY_CUSTOMERRORS: "true" HAPROXY_CUSTOMERRORS: "true"
@ -267,28 +264,5 @@ If you hit Let's Encrypt rate limits:
- Ensure `/etc/easyhaproxy/certs/certbot` volume is properly persisted - Ensure `/etc/easyhaproxy/certs/certbot` volume is properly persisted
- See: https://letsencrypt.org/docs/rate-limits/ - See: https://letsencrypt.org/docs/rate-limits/
### Using Both ACME and Manual Certificates
You can use both simultaneously:
1. Mount both volumes (`certs_certbot` and `certs_haproxy`)
2. Use `certbot=true` label for domains that should use ACME
3. Omit the label for domains using manual certificates
Example:
```yaml
services:
# This service uses ACME
app1:
labels:
easyhaproxy.http.host: auto.example.com
easyhaproxy.http.certbot: "true"
# This service uses manual certificate
app2:
labels:
easyhaproxy.http.host: manual.example.com
# No certbot label - will use /etc/easyhaproxy/certs/haproxy/manual.example.com.pem
```
---- ----
[Open source ByJG](http://opensource.byjg.com) [Open source ByJG](http://opensource.byjg.com)

View file

@ -1,5 +1,6 @@
--- ---
sidebar_position: 9 sidebar_position: 9
sidebar_label: "DigitalOcean"
--- ---
# DigitalOcean # DigitalOcean
@ -12,4 +13,3 @@ You can install it with a few clicks directly from the DigitalOcean dashboard.
## Installing EasyHAProxy on DigitalOcean ## Installing EasyHAProxy on DigitalOcean
Please refer the [EasyHAProxy page on DigitalOcean Marketplace](https://marketplace.digitalocean.com/apps/easyhaproxy-ingress-controller). Please refer the [EasyHAProxy page on DigitalOcean Marketplace](https://marketplace.digitalocean.com/apps/easyhaproxy-ingress-controller).

View file

@ -1,5 +1,6 @@
--- ---
sidebar_position: 8 sidebar_position: 8
sidebar_label: "Dokku"
--- ---
# Dokku # Dokku

50
docs/guides/helm.md Normal file
View file

@ -0,0 +1,50 @@
---
sidebar_position: 6
sidebar_label: "Helm"
---
# Helm 3
Helm is a package manager for Kubernetes. It allows you to install and manage applications on Kubernetes.
## Setup EasyHAProxy with Helm 3
### 1) Install EasyHAProxy
```bash title="Add the Helm repository"
helm repo add byjg https://opensource.byjg.com/helm
helm repo update byjg
kubectl create namespace easyhaproxy
```
```bash title="Install with Helm"
helm upgrade --install ingress byjg/easyhaproxy \
--namespace easyhaproxy \
--set resources.requests.cpu=100m \
--set resources.requests.memory=128Mi
```
### 2) Choose a deployment mode
By default, EasyHAProxy installs as a **DaemonSet** (`service.create: false`). To use the recommended **NodePort** or **ClusterIP** modes instead, set `service.create: true`:
```bash title="NodePort (recommended)"
helm upgrade --install ingress byjg/easyhaproxy \
--namespace easyhaproxy \
--set service.create=true \
--set service.type=NodePort
```
```bash title="ClusterIP (behind LoadBalancer)"
helm upgrade --install ingress byjg/easyhaproxy \
--namespace easyhaproxy \
--set service.create=true \
--set service.type=ClusterIP
```
See [Deployment Modes](../getting-started/kubernetes.md#deployment-modes) for a comparison of all three modes.
For the complete list of configurable values, see the [Helm Values reference](../reference/helm.md).
----
[Open source ByJG](http://opensource.byjg.com)

View file

@ -1,5 +1,6 @@
--- ---
sidebar_position: 7 sidebar_position: 7
sidebar_label: "MicroK8s"
--- ---
# Microk8s Add-ons # Microk8s Add-ons
@ -74,7 +75,7 @@ microk8s enable easyhaproxy --nodeport
You need to disable any ingress controller you have previously installed (e.g., nginx, traefik, etc.) before installing EasyHAProxy to avoid conflicts. You need to disable any ingress controller you have previously installed (e.g., nginx, traefik, etc.) before installing EasyHAProxy to avoid conflicts.
::: :::
For more parameters you can refer to the [Kubernetes](kubernetes.md) page. For more parameters you can refer to the [Kubernetes](../getting-started/kubernetes.md) page.
---- ----
[Open source ByJG](http://opensource.byjg.com) [Open source ByJG](http://opensource.byjg.com)

View file

@ -0,0 +1,695 @@
---
sidebar_position: 4
sidebar_label: "Plugin Development"
---
# Plugin Development Guide
This comprehensive guide covers everything you need to know about developing plugins for EasyHAProxy. Plugins extend HAProxy configuration with custom functionality and can be integrated seamlessly with Docker, Kubernetes, and Swarm environments.
## Table of Contents
1. [Overview](#overview)
2. [Plugin Architecture](#plugin-architecture)
3. [Quick Start Guide](#quick-start-guide)
4. [API Reference](#api-reference)
5. [Advanced Examples](#advanced-examples)
6. [Best Practices](#best-practices)
7. [Testing Guidelines](#testing-guidelines)
8. [Troubleshooting](#troubleshooting)
9. [Distribution](#distribution)
---
## Overview
### What is a Plugin?
A plugin is a Python class that implements the `PluginInterface` and extends HAProxy's configuration during the discovery cycle. Plugins can:
- **Inject HAProxy configuration** - Add custom HAProxy directives (ACLs, http-request rules, etc.)
- **Modify discovery data** - Transform the easymapping structure before HAProxy config generation
- **Perform maintenance tasks** - Execute cleanup, monitoring, or integration tasks
- **Integrate with external services** - Connect to APIs, databases, or third-party systems
### Why Build a Plugin?
Build a plugin when you need to:
- Add domain-specific HAProxy configuration based on labels/annotations
- Integrate with CDNs, load balancers, or security services
- Implement custom authentication or authorization logic
- Perform scheduled maintenance or monitoring tasks
- Extend EasyHAProxy without modifying core code
### Plugin System Benefits
- **Zero code changes** - Plugins don't modify EasyHAProxy core
- **Hot reload support** - Plugins reload on each discovery cycle
- **Configuration flexibility** - Configure via YAML, environment variables, or container labels
- **Error isolation** - Plugin errors don't crash the main application (configurable)
- **Easy distribution** - Share plugins as single Python files
---
## Plugin Architecture
### Plugin Types
EasyHAProxy supports two plugin execution models:
#### 1. GLOBAL Plugins
Execute **once per discovery cycle**, regardless of discovered domains.
**Execution timing:** After discovery, before domain processing
**Use cases:**
- Cleanup tasks (removing old temp files)
- Global monitoring (health checks, metrics)
- DNS updates (updating external DNS records)
- Log rotation or archiving
- Integration with global services
**Example:** CleanupPlugin - removes old temporary files once per cycle
#### 2. DOMAIN Plugins
Execute **once per discovered domain/host**.
**Execution timing:** During domain processing, before backend config generation
**Use cases:**
- Domain-specific HAProxy rules (IP whitelisting, rate limiting)
- CDN integration (Cloudflare IP restoration)
- Path-based controls (blocking specific URLs)
- Custom headers or redirects per domain
- JWT validation or authentication
**Example:** CloudflarePlugin - restores visitor IP for each Cloudflare-enabled domain
### Plugin Lifecycle
```
1. LOAD PHASE
├─ PluginManager scans plugins directory
├─ Imports plugin modules
├─ Instantiates plugin classes
└─ Categorizes by type (GLOBAL/DOMAIN)
2. CONFIGURE PHASE
├─ Loads configuration from YAML/env
├─ Calls plugin.configure(config) for each plugin
└─ Validates configuration (plugin responsibility)
3. INITIALIZE PHASE
├─ Calls plugin.initialize() for each plugin
├─ Plugins request file system resources (directories, files)
├─ PluginManager processes resource requests
└─ Creates directories and files as needed
4. EXECUTION PHASE (per discovery cycle)
├─ GLOBAL PLUGINS
│ └─ Executes all global plugins once
└─ DOMAIN PLUGINS
└─ For each discovered domain:
└─ Executes all domain plugins
5. RESULT PROCESSING
├─ Collects PluginResult from each plugin
├─ Injects haproxy_config into backend sections
├─ Injects global_configs into global section
├─ Injects defaults_configs into defaults section
├─ Applies modified_easymapping if provided
└─ Logs metadata for debugging
```
### Plugin Loading Order
1. **Builtin plugins** - Loaded from `/src/plugins/builtin/`
2. **External plugins** - Loaded from `/etc/easyhaproxy/plugins/`
Plugins are discovered automatically by filename (`*.py` excluding `__*.py`).
### Data Flow
```
Container Labels/Annotations
Discovery (Docker/K8s/Swarm)
parsed_object: {IP: labels}
[GLOBAL PLUGINS] ← PluginContext (parsed_object, easymapping, env)
easymapping: [list of domain configs]
For each domain:
[DOMAIN PLUGINS] ← PluginContext (domain, port, host_config, ...)
PluginResult → haproxy_config snippets
HAProxy Configuration File
HAProxy Reload
```
---
## Quick Start Guide
### Step 1: Create Plugin File
Create a new Python file in `/etc/easyhaproxy/plugins/` (or builtin location for core plugins):
```python
# /etc/easyhaproxy/plugins/my_plugin.py
import os
import sys
# Add parent directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from plugins import PluginInterface, PluginType, PluginContext, PluginResult
from functions import logger_easyhaproxy
class MyPlugin(PluginInterface):
"""My custom plugin description"""
def __init__(self):
# Initialize default configuration
self.enabled = True
self.my_setting = "default_value"
@property
def name(self) -> str:
"""Return unique plugin name"""
return "my_plugin"
@property
def plugin_type(self) -> PluginType:
"""Return plugin type (GLOBAL or DOMAIN)"""
return PluginType.DOMAIN
def configure(self, config: dict) -> None:
"""
Configure plugin from YAML/env/labels
Args:
config: Dictionary with plugin configuration
"""
if "enabled" in config:
self.enabled = str(config["enabled"]).lower() in ["true", "1", "yes"]
if "my_setting" in config:
self.my_setting = config["my_setting"]
def process(self, context: PluginContext) -> PluginResult:
"""
Process plugin logic and return result
Args:
context: PluginContext with execution data
Returns:
PluginResult with HAProxy config and metadata
"""
if not self.enabled:
return PluginResult()
# Generate HAProxy configuration
haproxy_config = f"""# My Plugin - Custom functionality
http-request set-header X-My-Header {self.my_setting}"""
return PluginResult(
haproxy_config=haproxy_config,
metadata={
"domain": context.domain,
"setting_value": self.my_setting
}
)
```
### Step 2: Enable Plugin
**Via container label (Docker):**
```yaml
services:
myapp:
labels:
easyhaproxy.http.host: example.com
easyhaproxy.http.plugins: my_plugin
easyhaproxy.http.plugin.my_plugin.my_setting: custom_value
```
**Via YAML configuration:**
```yaml
# /etc/easyhaproxy/static/config.yaml
plugins:
enabled: [my_plugin]
config:
my_plugin:
enabled: true
my_setting: custom_value
```
**Via environment variable:**
```bash
EASYHAPROXY_PLUGINS_ENABLED=my_plugin
EASYHAPROXY_PLUGIN_MY_PLUGIN_MY_SETTING=custom_value
```
### Step 3: Test Plugin
Restart EasyHAProxy and check logs:
```bash
docker-compose restart haproxy
docker-compose logs -f haproxy | grep my_plugin
```
Expected output:
```
[INFO] Loaded external plugin: my_plugin (domain)
[DEBUG] Configured plugin: my_plugin with config: {'my_setting': 'custom_value'}
[DEBUG] Executing domain plugin: my_plugin for domain: example.com
```
---
## API Reference
### PluginInterface
Base class all plugins must inherit from.
```python
class PluginInterface(ABC):
"""Base class all plugins must inherit"""
@property
@abstractmethod
def name(self) -> str:
"""Return the unique plugin name"""
pass
@property
@abstractmethod
def plugin_type(self) -> PluginType:
"""Return the plugin type (GLOBAL or DOMAIN)"""
pass
@abstractmethod
def configure(self, config: dict) -> None:
"""
Configure the plugin with settings from YAML/env/labels
Args:
config: Dictionary with plugin-specific configuration
"""
pass
def initialize(self) -> InitializationResult:
"""
Initialize plugin resources (new in v2.0)
Optional method to request file system resources.
Default implementation returns empty result (no-op).
Returns:
InitializationResult with resource requests
"""
return InitializationResult()
@abstractmethod
def process(self, context: PluginContext) -> PluginResult:
"""
Process the plugin logic and return result
Args:
context: PluginContext with all necessary data
Returns:
PluginResult with HAProxy config snippets and/or modified data
"""
pass
```
**Properties:**
- `name` - Unique identifier (used in configuration and logs)
- `plugin_type` - Execution model (`PluginType.GLOBAL` or `PluginType.DOMAIN`)
**Methods:**
- `configure(config)` - Receives plugin configuration during initialization
- `initialize()` - **[New in v2.0]** Request file system resources (optional)
- `process(context)` - Main execution logic, returns `PluginResult`
### PluginType
Enum defining plugin execution types.
```python
class PluginType(Enum):
"""Plugin execution types"""
GLOBAL = "global" # Execute once per discovery cycle
DOMAIN = "domain" # Execute per domain/host
```
### PluginContext
Container for all plugin execution data.
```python
@dataclass
class PluginContext:
"""Container for all plugin execution data"""
parsed_object: dict # {IP: labels} from discovery
easymapping: list # Current HAProxy mapping structure
container_env: dict # Environment configuration
domain: Optional[str] = None # Domain name (for DOMAIN plugins)
port: Optional[str] = None # Port (for DOMAIN plugins)
host_config: Optional[dict] = None # Domain-specific config
```
### PluginResult
Plugin execution result containing configuration and metadata.
```python
@dataclass
class PluginResult:
"""Plugin execution result"""
haproxy_config: str = "" # HAProxy config snippet to inject
modified_easymapping: Optional[list] = None # Modified easymapping structure
metadata: Dict[str, Any] = field(default_factory=dict) # Plugin metadata for logging
global_configs: list[str] = field(default_factory=list) # [New] Global-level configs
defaults_configs: list[str] = field(default_factory=list) # [New] Defaults-level configs
```
**Fields:**
- `haproxy_config` - HAProxy configuration snippet (injected into backend/frontend)
- `modified_easymapping` - Modified easymapping structure (optional, advanced use)
- `metadata` - Dictionary with debugging/logging information
- `global_configs` - **[New in v2.0]** List of global-level HAProxy configs (e.g., fcgi-app definitions)
- `defaults_configs` - **[New in v2.0]** List of defaults-level HAProxy configs (e.g., log-format)
### ResourceRequest
**[New in v2.0]** Request for file system resources during plugin initialization.
```python
@dataclass
class ResourceRequest:
"""Request for file system resources"""
resource_type: str # "directory" or "file"
path: str
content: str | None = None
overwrite: bool = False
```
### InitializationResult
**[New in v2.0]** Plugin initialization result with resource requests.
```python
@dataclass
class InitializationResult:
"""Plugin initialization result with resource requests"""
resources: list[ResourceRequest] = field(default_factory=list)
metadata: dict[str, Any] = field(default_factory=dict)
```
---
## Advanced Examples
### Example 1: IP Whitelist Plugin (DOMAIN)
Restrict access to specific IP addresses per domain.
```python
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from plugins import PluginInterface, PluginType, PluginContext, PluginResult
class IpWhitelistPlugin(PluginInterface):
"""Plugin to restrict access to specific IP addresses"""
def __init__(self):
self.enabled = True
self.allowed_ips = []
self.status_code = 403
@property
def name(self) -> str:
return "ip_whitelist"
@property
def plugin_type(self) -> PluginType:
return PluginType.DOMAIN
def configure(self, config: dict) -> None:
if "enabled" in config:
self.enabled = str(config["enabled"]).lower() in ["true", "1", "yes"]
if "allowed_ips" in config:
ips_str = str(config["allowed_ips"])
self.allowed_ips = [ip.strip() for ip in ips_str.split(",") if ip.strip()]
if "status_code" in config:
try:
self.status_code = int(config["status_code"])
except ValueError:
self.status_code = 403
def process(self, context: PluginContext) -> PluginResult:
if not self.enabled or not self.allowed_ips:
return PluginResult()
ips_str = " ".join(self.allowed_ips)
haproxy_config = f"""# IP Whitelist - Only allow specific IPs
acl whitelisted_ip src {ips_str}
http-request deny deny_status {self.status_code} if !whitelisted_ip"""
return PluginResult(
haproxy_config=haproxy_config,
metadata={
"domain": context.domain,
"allowed_ips": self.allowed_ips,
"status_code": self.status_code
}
)
```
### Example 2: Cleanup Plugin (GLOBAL)
Perform cleanup tasks during each discovery cycle.
```python
import os
import sys
import glob
import time
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from plugins import PluginInterface, PluginType, PluginContext, PluginResult
from functions import logger_easyhaproxy
class CleanupPlugin(PluginInterface):
"""Plugin to perform cleanup tasks during discovery cycle"""
def __init__(self):
self.enabled = True
self.max_idle_time = 300 # 5 minutes
self.cleanup_temp_files = True
@property
def name(self) -> str:
return "cleanup"
@property
def plugin_type(self) -> PluginType:
return PluginType.GLOBAL
def configure(self, config: dict) -> None:
if "enabled" in config:
self.enabled = str(config["enabled"]).lower() in ["true", "1", "yes"]
if "max_idle_time" in config:
try:
self.max_idle_time = int(config["max_idle_time"])
except ValueError:
logger_easyhaproxy.warning(f"Invalid max_idle_time value: {config['max_idle_time']}, using default")
if "cleanup_temp_files" in config:
self.cleanup_temp_files = str(config["cleanup_temp_files"]).lower() in ["true", "1", "yes"]
def process(self, context: PluginContext) -> PluginResult:
if not self.enabled:
return PluginResult()
cleanup_actions = []
if self.cleanup_temp_files:
temp_dirs = ["/tmp", "/var/tmp"]
current_time = time.time()
for temp_dir in temp_dirs:
if not os.path.exists(temp_dir):
continue
try:
pattern = os.path.join(temp_dir, "easyhaproxy_*")
for filepath in glob.glob(pattern):
try:
file_age = current_time - os.path.getmtime(filepath)
if file_age > self.max_idle_time:
os.remove(filepath)
cleanup_actions.append(f"Removed old temp file: {filepath}")
except Exception as e:
logger_easyhaproxy.warning(f"Failed to remove temp file {filepath}: {e}")
except Exception as e:
logger_easyhaproxy.warning(f"Failed to cleanup {temp_dir}: {e}")
return PluginResult(
haproxy_config="",
metadata={
"actions_performed": len(cleanup_actions),
"actions": cleanup_actions
}
)
```
---
## Best Practices
1. **Use `initialize()` for resource setup** - Request directories/files during init, not in `process()`
2. **Use typed result fields** - Use `global_configs` and `defaults_configs` instead of metadata for config injection
3. **Handle errors gracefully** - Use try/except and return `PluginResult()` on error
4. **Validate in `configure()`** - Don't validate at `process()` time
5. **Use metadata for debugging** - Include useful info in `metadata` dict
6. **Support multiple boolean formats** - `str(config["enabled"]).lower() in ["true", "1", "yes"]`
7. **Support list and string formats** - Handle both YAML lists and comma-separated strings
8. **Use descriptive names** - Clear plugin name, ACL names, and config keys
9. **Document your plugin** - Include docstring with YAML and label examples
10. **Return empty result when disabled** - Check `self.enabled` first
---
## Testing Guidelines
### Unit Testing
```python
from plugins import PluginContext
from plugins.builtin.my_plugin import MyPlugin
class TestMyPlugin:
def test_plugin_initialization(self):
plugin = MyPlugin()
assert plugin.name == "my_plugin"
assert plugin.enabled is True
def test_plugin_generates_config(self):
plugin = MyPlugin()
plugin.configure({"my_setting": "test_value"})
context = PluginContext(
parsed_object={},
easymapping=[],
container_env={},
domain="example.com",
port="80",
host_config={}
)
result = plugin.process(context)
assert result.haproxy_config is not None
assert "X-My-Header test_value" in result.haproxy_config
def test_plugin_disabled(self):
plugin = MyPlugin()
plugin.configure({"enabled": "false"})
context = PluginContext(
parsed_object={}, easymapping=[], container_env={}, domain="example.com"
)
result = plugin.process(context)
assert result.haproxy_config == ""
```
---
## Troubleshooting
### Plugin Not Loading
1. File not in plugins directory: `ls -la /etc/easyhaproxy/plugins/`
2. Invalid Python syntax: `python3 -m py_compile /etc/easyhaproxy/plugins/my_plugin.py`
3. Class doesn't inherit `PluginInterface`
4. Missing required imports
### Plugin Not Executing
1. Plugin not enabled in configuration
2. Wrong plugin type for use case
3. Plugin disabled via configuration
### HAProxy Configuration Invalid
```bash
# Test configuration manually:
haproxy -c -f /etc/easyhaproxy/haproxy/haproxy.cfg
```
---
## Distribution
### Option 1: Single File
```bash
cp my_plugin.py /etc/easyhaproxy/plugins/
```
### Option 2: Docker Image with Plugin
```dockerfile
FROM byjg/easy-haproxy:latest
COPY my_plugin.py /app/src/plugins/builtin/
```
### Contributing to EasyHAProxy
1. Fork the repository
2. Add your plugin to `src/plugins/builtin/`
3. Add tests in `src/tests/test_plugins.py`
4. Add documentation in `docs/reference/plugins/`
5. Create a pull request
---
For more examples, see the builtin plugins in `/src/plugins/builtin/`:
- `cloudflare.py` - Simple DOMAIN plugin
- `fastcgi.py` - Advanced DOMAIN plugin with complex config
- `jwt_validator.py` - Security plugin with path-based logic
- `ip_whitelist.py` - Access control plugin
- `cleanup.py` - GLOBAL plugin example

View file

@ -1,5 +1,6 @@
--- ---
sidebar_position: 15 sidebar_position: 3
sidebar_label: "Using Plugins"
--- ---
# Using Plugins # Using Plugins
@ -44,12 +45,12 @@ Execute **once for each discovered domain/host**.
EasyHAProxy includes several built-in plugins ready to use: EasyHAProxy includes several built-in plugins ready to use:
- [Cloudflare](Plugins/cloudflare.md) - Restore visitor IP from Cloudflare CDN - [Cloudflare](../reference/plugins/cloudflare.md) - Restore visitor IP from Cloudflare CDN
- [Cleanup](Plugins/cleanup.md) - Cleanup temporary files - [Cleanup](../reference/plugins/cleanup.md) - Cleanup temporary files
- [Deny Pages](Plugins/deny-pages.md) - Block specific paths - [Deny Pages](../reference/plugins/deny-pages.md) - Block specific paths
- [IP Whitelist](Plugins/ip-whitelist.md) - Restrict access to IPs/CIDR ranges - [IP Whitelist](../reference/plugins/ip-whitelist.md) - Restrict access to IPs/CIDR ranges
- [JWT Validator](Plugins/jwt-validator.md) - Validate JWT tokens - [JWT Validator](../reference/plugins/jwt-validator.md) - Validate JWT tokens
- [FastCGI](Plugins/fastcgi.md) - Configure PHP-FPM and FastCGI applications - [FastCGI](../reference/plugins/fastcgi.md) - Configure PHP-FPM and FastCGI applications
## Configuration Methods ## Configuration Methods
@ -96,7 +97,7 @@ spec:
- Enable plugins: `easyhaproxy.plugins: plugin1,plugin2` - Enable plugins: `easyhaproxy.plugins: plugin1,plugin2`
- Configure plugin: `easyhaproxy.plugin.<plugin_name>.<config_key>: value` - Configure plugin: `easyhaproxy.plugin.<plugin_name>.<config_key>: value`
See the [Kubernetes guide](kubernetes.md#using-plugins-with-kubernetes) for more examples. See the [Kubernetes guide](../getting-started/kubernetes.md) for more examples.
### 2. Container Labels (Docker/Docker Compose) ### 2. Container Labels (Docker/Docker Compose)
@ -181,11 +182,6 @@ EASYHAPROXY_PLUGIN_CLOUDFLARE_USE_BUILTIN_IPS=true
- Enable global plugins: `EASYHAPROXY_PLUGINS_ENABLED=plugin1,plugin2` - Enable global plugins: `EASYHAPROXY_PLUGINS_ENABLED=plugin1,plugin2`
- Configure plugin: `EASYHAPROXY_PLUGIN_<PLUGIN_NAME>_<CONFIG_KEY>=value` - Configure plugin: `EASYHAPROXY_PLUGIN_<PLUGIN_NAME>_<CONFIG_KEY>=value`
**Scope limitations:**
- **Global plugins**: Environment variables configure the single instance
- **Domain plugins**: Environment variables set defaults for ALL domains
- **Per-domain configuration**: Use container labels (Docker) or annotations (Kubernetes) instead
## Common Use Cases ## Common Use Cases
### Protect API with JWT Authentication ### Protect API with JWT Authentication
@ -205,42 +201,8 @@ services:
- ./auth_pubkey.pem:/etc/easyhaproxy/jwt_keys/api_pubkey.pem:ro - ./auth_pubkey.pem:/etc/easyhaproxy/jwt_keys/api_pubkey.pem:ro
``` ```
**Protect only admin/sensitive endpoints:**
```yaml
services:
api:
labels:
easyhaproxy.http.host: api.example.com
easyhaproxy.http.plugins: jwt_validator
easyhaproxy.http.plugin.jwt_validator.pubkey_path: /etc/easyhaproxy/jwt_keys/api_pubkey.pem
easyhaproxy.http.plugin.jwt_validator.paths: /api/admin,/api/users,/api/billing
easyhaproxy.http.plugin.jwt_validator.only_paths: false
volumes:
- ./auth_pubkey.pem:/etc/easyhaproxy/jwt_keys/api_pubkey.pem:ro
# /api/health, /api/docs, etc. remain publicly accessible
```
**Restrict API to only allow specific endpoints:**
```yaml
services:
api:
labels:
easyhaproxy.http.host: api.example.com
easyhaproxy.http.plugins: jwt_validator
easyhaproxy.http.plugin.jwt_validator.pubkey_path: /etc/easyhaproxy/jwt_keys/api_pubkey.pem
easyhaproxy.http.plugin.jwt_validator.paths: /api/v1,/api/v2
easyhaproxy.http.plugin.jwt_validator.only_paths: true
volumes:
- ./auth_pubkey.pem:/etc/easyhaproxy/jwt_keys/api_pubkey.pem:ro
# All paths except /api/v1 and /api/v2 are denied
```
### Restrict Admin Panel to Office IPs ### Restrict Admin Panel to Office IPs
Protect admin panel by only allowing access from office network:
```yaml ```yaml
labels: labels:
easyhaproxy.http.host: admin.example.com easyhaproxy.http.host: admin.example.com
@ -262,8 +224,6 @@ labels:
### Cloudflare IP Restoration ### Cloudflare IP Restoration
Restore original visitor IPs for applications behind Cloudflare:
```yaml ```yaml
labels: labels:
easyhaproxy.http.host: myapp.com easyhaproxy.http.host: myapp.com
@ -272,8 +232,6 @@ labels:
### Multiple Plugins Together ### Multiple Plugins Together
Combine multiple plugins for one domain:
```yaml ```yaml
labels: labels:
easyhaproxy.http.host: secure-app.com easyhaproxy.http.host: secure-app.com
@ -284,8 +242,6 @@ labels:
### Automatic Cleanup ### Automatic Cleanup
Keep your system clean with automatic temp file removal:
```yaml ```yaml
# /etc/easyhaproxy/static/config.yaml # /etc/easyhaproxy/static/config.yaml
plugins: plugins:
@ -307,14 +263,6 @@ plugins:
abort_on_error: false # Default abort_on_error: false # Default
``` ```
**When to use:** Most situations. Ensures a failing plugin doesn't prevent HAProxy updates.
**Behavior:**
- Plugin errors logged as warnings
- Discovery cycle continues
- Other plugins still execute
- HAProxy config is generated without the failed plugin
### Abort on Error ### Abort on Error
Stop discovery cycle if any plugin fails: Stop discovery cycle if any plugin fails:
@ -324,19 +272,10 @@ plugins:
abort_on_error: true abort_on_error: true
``` ```
**When to use:** Critical plugins where failure should halt deployment.
**Behavior:**
- Plugin error stops discovery
- Previous HAProxy config remains active
- No configuration changes until issue is resolved
## Troubleshooting ## Troubleshooting
### Enable Debug Logging ### Enable Debug Logging
See detailed plugin execution information:
```bash ```bash
EASYHAPROXY_LOG_LEVEL=DEBUG EASYHAPROXY_LOG_LEVEL=DEBUG
``` ```
@ -357,18 +296,6 @@ DEBUG: Plugin cloudflare metadata: {'domain': 'example.com', 'ip_list_path': '/e
3. Plugin class inherits from `PluginInterface` 3. Plugin class inherits from `PluginInterface`
4. Check logs for load errors 4. Check logs for load errors
### Plugin Not Executing
**For domain plugins:**
1. Check container has label: `easyhaproxy.http.plugins: plugin_name`
2. Verify plugin name is correct (case-sensitive)
3. Enable debug logging
**For global plugins:**
1. Check YAML config: `plugins.enabled: [plugin_name]`
2. Or env var: `EASYHAPROXY_PLUGINS_ENABLED=plugin_name`
3. Enable debug logging
### Configuration Not Applied ### Configuration Not Applied
**Check precedence order:** **Check precedence order:**
@ -383,16 +310,6 @@ For Docker deployments:
2. YAML configuration 2. YAML configuration
3. Environment variables (lowest) 3. Environment variables (lowest)
Per-ingress/per-container settings override global configuration.
### Plugin Output Missing
**Verify:**
1. Plugin is enabled (`enabled: true`)
2. Plugin configuration is correct
3. Plugin's `process()` method returns valid `PluginResult`
4. Check debug logs for plugin execution
## Best Practices ## Best Practices
1. **Start with log-and-continue mode** - Use `abort_on_error: false` until you're confident plugins are stable 1. **Start with log-and-continue mode** - Use `abort_on_error: false` until you're confident plugins are stable
@ -400,7 +317,6 @@ Per-ingress/per-container settings override global configuration.
3. **Use YAML/env for global config** - Better for global plugins and defaults 3. **Use YAML/env for global config** - Better for global plugins and defaults
4. **Enable debug logging during testing** - Helps identify configuration issues 4. **Enable debug logging during testing** - Helps identify configuration issues
5. **Test plugin changes in staging first** - Avoid production surprises 5. **Test plugin changes in staging first** - Avoid production surprises
6. **Keep plugin configurations simple** - Use defaults when possible
## Limitations ## Limitations
@ -416,7 +332,7 @@ Want to create your own plugins? See the [Plugin Developer Guide](plugin-develop
## Further Reading ## Further Reading
- [Plugin Developer Guide](plugin-development.md) - Create custom plugins - [Plugin Developer Guide](plugin-development.md) - Create custom plugins
- [Container Labels](container-labels.md) - Label configuration reference - [Container Labels](../reference/container-labels.md) - Label configuration reference
- [Environment Variables](environment-variable.md) - Environment variable reference - [Environment Variables](../reference/environment-variables.md) - Environment variable reference
- [Static Configuration](static.md) - YAML configuration reference - [Static Configuration](../getting-started/static.md) - YAML configuration reference
- [Kubernetes Guide](kubernetes.md) - Using plugins with Kubernetes - [Kubernetes Guide](../getting-started/kubernetes.md) - Using plugins with Kubernetes

View file

@ -1,5 +1,6 @@
--- ---
sidebar_position: 10 sidebar_position: 1
sidebar_label: "Custom SSL Certificates"
--- ---
# Setup custom certificates # Setup custom certificates

View file

@ -1,85 +0,0 @@
---
sidebar_position: 6
---
# Helm 3
Helm is a package manager for Kubernetes. It allows you to install and manage applications on Kubernetes.
## Setup EasyHAProxy with Helm 3
### 1) Identify the node where your EasyHAProxy container will run
:::warning Single Node Deployment
EasyHAProxy will be limited to a single node. To understand why, see the [limitations](limitations.md) page.
:::
```bash title="List available nodes"
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
node-01 Ready <none> 561d v1.21.13-3
node-02 Ready <none> 561d v1.21.13-3
```
Add the EasyHAProxy label to the node.
```bash title="Label the node for EasyHAProxy"
kubectl label nodes node-01 "easyhaproxy/node=master"
```
### 2) Install EasyHAProxy
Minimal configuration:
```bash title="Install with Helm (minimal)"
helm repo add byjg https://opensource.byjg.com/helm
helm repo update byjg
kubectl create namespace easyhaproxy
helm upgrade --install ingress byjg/easyhaproxy \
--namespace easyhaproxy \
--set resources.requests.cpu=100m \
--set resources.requests.memory=128Mi
```
Customizing Helm Values:
```yaml title="values.yaml"
easyhaproxy:
stats:
username: admin
password: password
refresh: "10"
customErrors: "true"
sslMode: loose
logLevel:
certbot: DEBUG
easyhaproxy: DEBUG
haproxy: DEBUG
certbot:
email: ""
service:
create: false # If false, it will create a DaemonSet with hostPort. The easiest.
type: ClusterIP # or NodePort
annotations: {}
binding:
ports:
http: 80
https: 443
stats: 1936
additionalPorts: []
# Make sure to create this
masterNode:
label: easyhaproxy/node
values:
- master
```
For more parameters you can refer to the [Kubernetes](kubernetes.md) page.
----
[Open source ByJG](http://opensource.byjg.com)

View file

@ -1,514 +0,0 @@
---
sidebar_position: 1
---
# Kubernetes
## Setup Kubernetes EasyHAProxy
:::info How it works
EasyHAProxy for Kubernetes operates by querying all ingress definitions with either the
`spec.ingressClassName: easyhaproxy` field (recommended) or the deprecated annotation
`kubernetes.io/ingress.class: easyhaproxy-ingress` (for backward compatibility). Upon finding
a matching ingress class, EasyHAProxy immediately sets up HAProxy and begins serving traffic.
:::
For Kubernetes installations, there are three available installation modes:
- DaemonSet: This mode exposes ports 80, 443, and 1936.
- NodePort: Ports 31080, 31443, and 31936 are exposed.
- ClusterIP: In this mode, no ports are exposed externally, and HAProxy is accessible only
within the cluster.
To install EasyHAProxy in your Kubernetes cluster, follow these steps:
### 1) Identify the node where your EasyHAProxy container will run
:::warning Single Node Deployment
EasyHAProxy will be limited to a single node. To understand why, see the [limitations](limitations.md) page.
:::
```bash title="List available nodes"
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
node-01 Ready <none> 561d v1.21.13-3
node-02 Ready <none> 561d v1.21.13-3
```
Add the EasyHAProxy label to the node.
```bash title="Label the node for EasyHAProxy"
kubectl label nodes node-01 "easyhaproxy/node=master"
```
### 2) Install EasyHAProxy with Kubernetes Manifest
```bash title="Install EasyHAProxy"
kubectl create namespace easyhaproxy
kubectl apply -f \
https://raw.githubusercontent.com/byjg/docker-easy-haproxy/6.0.0/deploy/kubernetes/easyhaproxy-daemonset.yml
```
If necessary, you can configure environment variables. To get a list of the variables, please follow the [environment variable guide](environment-variable.md)
## Running containers
Your container only requires creating an ingress with the `spec.ingressClassName: easyhaproxy` field pointing to your service.
e.g.
```yaml
kind: Ingress
metadata:
name: example-ingress
namespace: example
spec:
# Use ingressClassName (recommended)
ingressClassName: easyhaproxy
rules:
- host: example.org
http:
paths:
- backend:
service:
name: example-service
port:
number: 8080
pathType: ImplementationSpecific
```
:::note Backward Compatibility
The deprecated annotation `kubernetes.io/ingress.class: easyhaproxy-ingress` is still supported for backward compatibility, but `spec.ingressClassName` is the recommended approach for new deployments.
:::
Once the container is running, EasyHAProxy will detect automatically and start to redirect all traffic from `example.org:80` to your container at port 8080.
You don't need to expose any port in your container.
:::note Important Limitations
- The implementation doesn't support all ingress properties or wildcard domains at this time.
- The ingress will publish ports 80 and 443, plus 1936 if stats are enabled.
- EasyHAProxy will read all `spec.rules[].host` specifications, however it will parse only the **first path** `spec.rules[].http.paths[0].port.number` for each rule, and ignore the other paths.
:::
## Kubernetes annotations
| annotation | Description | Default | Example |
|-------------------------------------|-------------------------------------------------------------------------------------|--------------|----------------------------|
| kubernetes.io/ingress.class | (deprecated) Activate EasyHAProxy. Use `spec.ingressClassName` instead. | *optional* | easyhaproxy-ingress |
| easyhaproxy.redirect_ssl | (optional) Boolean. Force redirect all endpoints to HTTPS. | false | true or false |
| easyhaproxy.certbot | (optional) Boolean. It will request certbot certificates for the ingresses domains. | false | true or false |
| easyhaproxy.redirect | (optional) JSON. Key pair with a domain and its destination. | *empty* | \{"domain":"redirect_url"} |
| easyhaproxy.mode | (optional) Set the HTTP mode for that connection. | http | http or tcp |
| easyhaproxy.listen_port | (optional) Override the HTTP listen port created for that ingress | 80 | 8081 |
| easyhaproxy.plugins | (optional) Comma-separated list of plugins to enable for this ingress | *empty* | cloudflare,deny_pages |
| easyhaproxy.plugin.`{name}`.`{key}` | (optional) Plugin-specific configuration (see [Using Plugins](plugins.md)) | *varies* | See examples below |
**Important**: The annotations are per ingress and applied to all hosts in that ingress configuration.
## Using Plugins with Kubernetes
Plugins extend HAProxy configuration with additional functionality like JWT validation, IP whitelisting, or Cloudflare IP restoration. For a complete list of available plugins, see the [Using Plugins](plugins.md) guide.
### Enabling Plugins for an Ingress
Add the `easyhaproxy.plugins` annotation with a comma-separated list of plugin names:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
easyhaproxy.plugins: "cloudflare,deny_pages"
name: example-ingress
namespace: example
spec:
ingressClassName: easyhaproxy
rules:
- host: example.org
http:
paths:
- backend:
service:
name: example-service
port:
number: 8080
pathType: ImplementationSpecific
```
### Configuring Plugin Options
Use `easyhaproxy.plugin.{plugin_name}.{option}` annotations to configure individual plugins:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
easyhaproxy.plugins: "deny_pages"
easyhaproxy.plugin.deny_pages.paths: "/admin,/private,/config"
easyhaproxy.plugin.deny_pages.status_code: "403"
name: secure-app-ingress
namespace: production
spec:
ingressClassName: easyhaproxy
rules:
- host: myapp.example.com
http:
paths:
- backend:
service:
name: myapp-service
port:
number: 8080
pathType: ImplementationSpecific
```
### Common Plugin Examples
**Protect API with JWT validation:**
```yaml
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/easyhaproxy/jwt_keys/api_pubkey.pem"
spec:
ingressClassName: easyhaproxy
```
**Note:** For JWT validation, you'll need to mount the public key file into the EasyHAProxy pod. See [Using Plugins](plugins.md#protect-api-with-jwt-authentication) for details.
**Restrict access to specific IPs:**
```yaml
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:
ingressClassName: easyhaproxy
```
**Restore Cloudflare visitor IPs:**
```yaml
metadata:
annotations:
easyhaproxy.plugins: "cloudflare"
spec:
ingressClassName: easyhaproxy
```
**Multiple plugins together:**
```yaml
metadata:
annotations:
easyhaproxy.plugins: "cloudflare,deny_pages"
easyhaproxy.plugin.deny_pages.paths: "/wp-admin,/wp-login.php"
easyhaproxy.plugin.deny_pages.status_code: "404"
spec:
ingressClassName: easyhaproxy
```
### Global Plugin Configuration
Some plugins (like `cleanup`) are global and execute once per discovery cycle. Configure these via environment variables or YAML configuration:
**Using Helm values.yaml:**
```yaml
easyhaproxy:
plugins:
enabled: cleanup
config:
cleanup:
max_idle_time: 600
```
**Using environment variables:**
```yaml
env:
- name: EASYHAPROXY_PLUGINS_ENABLED
value: "cleanup"
- name: EASYHAPROXY_PLUGIN_CLEANUP_MAX_IDLE_TIME
value: "600"
```
For more information on plugin types and available plugins, see the [Using Plugins](plugins.md) guide.
## Loading Plugin Configuration from Kubernetes Secrets
EasyHAProxy supports loading sensitive plugin configuration values directly from Kubernetes Secrets using the `k8s_secret` pattern. This is a **generic, plugin-agnostic feature** that works with any plugin.
### Why Use Kubernetes Secrets?
- **Security**: Keep sensitive data (API keys, passwords, certificates) out of annotations
- **Best practices**: Follows Kubernetes conventions for managing sensitive data
- **Simplicity**: No need to mount volumes or ConfigMaps for secret data
- **Encryption**: Secrets are encrypted at rest in etcd
### Annotation Format
```yaml
# Auto-detect key (tries common variations):
easyhaproxy.plugin.{plugin_name}.k8s_secret.{config_key}: "secret_name"
# Explicit key (no variations):
easyhaproxy.plugin.{plugin_name}.k8s_secret.{config_key}: "secret_name/key_name"
```
### How It Works
1. **You create** a Kubernetes Secret with your sensitive data
2. **You reference** the secret in your ingress annotation using the `k8s_secret` pattern
3. **EasyHAProxy reads** the secret from the same namespace as the ingress
4. **EasyHAProxy transforms** the annotation to inject the secret value
5. **The plugin receives** the value as if it was provided directly in the annotation
**Example transformation:**
```yaml
# Input annotation:
easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "my-jwt-secret"
# EasyHAProxy reads the secret and transforms to:
easyhaproxy.plugin.jwt_validator.pubkey: "<base64-encoded-content>"
```
### Auto-Detect vs Explicit Key
#### Auto-Detect Key Format
When you use `"secret_name"` (without `/`), EasyHAProxy tries to find the key automatically:
```yaml
easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "my-jwt-secret"
```
EasyHAProxy will try these keys in order:
1. Exact match: `pubkey`
2. Common variations based on the config key name
**Auto-detect key variations:**
| Config Key | Tries (in order) |
|------------|---------------------------------------|
| `pubkey` | `pubkey`, `public-key`, `jwt.pub`, `tls.crt` |
| `password` | `password`, `pass`, `pwd` |
| `api_key` | `api_key`, `apikey`, `api-key`, `key` |
#### Explicit Key Format
When you use `"secret_name/key_name"` (with `/`), EasyHAProxy only tries the exact key name:
```yaml
easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "my-jwt-secret/rsa-public-key"
```
EasyHAProxy will **only** try: `rsa-public-key` (no variations)
**Use explicit key when:**
- Your secret uses a non-standard key name
- You want to be explicit and avoid ambiguity
- Multiple keys exist in the secret
### Complete Example
```yaml
---
# 1. Create a secret with your JWT public key
apiVersion: v1
kind: Secret
metadata:
name: jwt-pubkey-secret
namespace: production
type: Opaque
stringData:
pubkey: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
---
# 2. Reference it in your ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
namespace: production
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"
# Load pubkey from Kubernetes secret (auto-detect key)
easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "jwt-pubkey-secret"
spec:
ingressClassName: easyhaproxy
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
```
### Example with Explicit Key Name
```yaml
---
apiVersion: v1
kind: Secret
metadata:
name: app-credentials
namespace: production
type: Opaque
stringData:
# Custom key name
rsa-public-key: |
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
namespace: production
annotations:
easyhaproxy.plugins: "jwt_validator"
# Use explicit key name after the slash
easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "app-credentials/rsa-public-key"
spec:
ingressClassName: easyhaproxy
# ... rest of configuration
```
### Using with Any Plugin
The `k8s_secret` pattern works with **any plugin configuration**:
```yaml
# JWT Validator - load public key
easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "jwt-secret"
# Hypothetical API auth plugin - load API key
easyhaproxy.plugin.api_auth.k8s_secret.api_key: "api-credentials/key"
# Hypothetical basic auth plugin - load password
easyhaproxy.plugin.basic_auth.k8s_secret.password: "auth-secret/pwd"
```
### Priority Order
When multiple configuration methods are used, this is the priority (highest to lowest):
1. **Explicit annotation** (e.g., `easyhaproxy.plugin.jwt_validator.pubkey: "value"`)
2. **k8s_secret annotation** (e.g., `easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey: "secret"`)
Explicit annotations always take precedence over `k8s_secret` annotations.
### Troubleshooting
**Secret not found:**
```
WARNING: Ingress production/api-ingress - Failed to process k8s_secret annotation
'easyhaproxy.plugin.jwt_validator.k8s_secret.pubkey' with value 'jwt-secret': ...
```
- Verify the secret exists: `kubectl get secret jwt-secret -n production`
- Check the secret is in the same namespace as the ingress
**Key not found in secret:**
```
WARNING: Ingress production/api-ingress - Secret 'jwt-secret' found but no matching
key (tried: pubkey, public-key, jwt.pub, tls.crt)
```
- List secret keys: `kubectl get secret jwt-secret -n production -o jsonpath='{.data}'`
- Use explicit key format: `"jwt-secret/actual-key-name"`
**Check EasyHAProxy logs:**
```bash
kubectl logs -n easyhaproxy -l app=easyhaproxy --tail=100
```
Look for:
- `INFO: Loaded 'pubkey' from secret 'jwt-secret'` (success)
- `WARNING: Secret 'xyz' found but no matching key` (key not found)
### Security Considerations
- Secrets are read from the **same namespace** as the ingress (no cross-namespace access)
- EasyHAProxy needs RBAC permissions to read secrets (included in default deployment)
- Secrets are encrypted at rest in etcd
- Secret values are base64-encoded by Kubernetes automatically
- Use Kubernetes RBAC to control which service accounts can read which secrets
## Certbot / ACME / Letsencrypt
It is necessary to add the annotation `easyhaproxy.certbot` to the ingress configuration:
```yaml
kind: Ingress
metadata:
annotations:
easyhaproxy.certbot: 'true'
name: example-ingress
namespace: example
spec:
ingressClassName: easyhaproxy
....
```
More info [here](acme.md).
Make sure your cluster is accessible both through ports 80 and 443.
## Custom SSL Certificates
Create a secret with your certificate and key and associate them with your ingress.
```yaml
---
apiVersion: v1
kind: Secret
metadata:
name: host2-tls
namespace: default
data:
tls.crt: base64 of your certificate
tls.key: base64 of your certificate private key
type: kubernetes.io/tls
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-example
namespace: default
spec:
ingressClassName: easyhaproxy
tls:
- hosts:
- host2.local
secretName: host2-tls
rules:
...
```
----
[Open source ByJG](http://opensource.byjg.com)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,4 @@
{
"label": "Reference",
"position": 4
}

View file

@ -1,84 +1,19 @@
--- ---
sidebar_position: 5 sidebar_position: 1
sidebar_label: "CLI Reference"
--- ---
# Install via pip / uv # CLI Reference
EasyHAProxy can run directly on any Linux or macOS host without Docker, using the `easyhaproxy` Python package. The `easy-haproxy` command is the native binary installed via `pip` or `uv`. Every option can be set as a **CLI flag** or an **environment variable**. CLI flags take precedence over environment variables.
## Prerequisites
HAProxy must be installed and available in your system `PATH` before running `easy-haproxy`. EasyHAProxy will refuse to start with a clear error message if HAProxy is not found.
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs>
<TabItem value="debian" label="Debian / Ubuntu" default>
```bash
sudo apt install haproxy
```
</TabItem>
<TabItem value="rhel" label="RHEL / Fedora">
```bash
sudo dnf install haproxy
```
</TabItem>
<TabItem value="macos" label="macOS">
```bash
brew install haproxy
```
</TabItem>
</Tabs>
## Installation
### Recommended: `uv tool` (system-wide, isolated)
[`uv`](https://docs.astral.sh/uv/) installs `easyhaproxy` into its own isolated environment and exposes the `easy-haproxy` binary in `~/.local/bin/`, similar to `pipx`.
```bash
# Install uv (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install easyhaproxy as a tool
uv tool install easyhaproxy
# Make sure ~/.local/bin is in PATH (one-time setup)
uv tool update-shell
```
After installation:
```bash
easy-haproxy --help
```
### Alternative: `pip`
```bash
pip install easyhaproxy
```
:::note Virtual environments
When installing inside a virtual environment, `easy-haproxy` is only available while the environment is activated. For system-wide use, prefer `uv tool install` or install with `pip` at the system/user level.
:::
## CLI Reference
Every configuration option can be set via a CLI flag **or** an environment variable. CLI flags take precedence over environment variables.
``` ```
easy-haproxy [OPTIONS] easy-haproxy [OPTIONS]
``` ```
### Core For installation instructions, see [Native install](../getting-started/native.md).
## Core
| Flag | Environment Variable | Default | Description | | Flag | Environment Variable | Default | Description |
|--------------------------|----------------------------|------------------------------------------------------|-----------------------------------------------------------| |--------------------------|----------------------------|------------------------------------------------------|-----------------------------------------------------------|
@ -89,7 +24,7 @@ easy-haproxy [OPTIONS]
| `--refresh-conf SECONDS` | `EASYHAPROXY_REFRESH_CONF` | `10` | Polling interval for configuration changes | | `--refresh-conf SECONDS` | `EASYHAPROXY_REFRESH_CONF` | `10` | Polling interval for configuration changes |
| `--customer-errors BOOL` | `HAPROXY_CUSTOMERRORS` | `false` | Enable custom HAProxy HTML error pages | | `--customer-errors BOOL` | `HAPROXY_CUSTOMERRORS` | `false` | Enable custom HAProxy HTML error pages |
### Logging ## Logging
| Flag | Environment Variable | Default | Description | | Flag | Environment Variable | Default | Description |
|-----------------------------|-------------------------|----------|---------------------------| |-----------------------------|-------------------------|----------|---------------------------|
@ -99,7 +34,7 @@ easy-haproxy [OPTIONS]
Valid levels: `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL` Valid levels: `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`
### Stats Dashboard ## Stats Dashboard
| Flag | Environment Variable | Default | Description | | Flag | Environment Variable | Default | Description |
|--------------------------------------|-----------------------------|--------------|---------------------------------------------| |--------------------------------------|-----------------------------|--------------|---------------------------------------------|
@ -112,7 +47,7 @@ Valid levels: `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`
The stats dashboard is only enabled when `--haproxy-password` (or `HAPROXY_PASSWORD`) is set. The stats dashboard is only enabled when `--haproxy-password` (or `HAPROXY_PASSWORD`) is set.
::: :::
### ACME / Certbot (SSL certificates) ## ACME / Certbot (SSL certificates)
| Flag | Environment Variable | Default | Description | | Flag | Environment Variable | Default | Description |
|---------------------------------------|--------------------------------------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------| |---------------------------------------|--------------------------------------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
@ -125,18 +60,18 @@ The stats dashboard is only enabled when `--haproxy-password` (or `HAPROXY_PASSW
| `--certbot-preferred-challenges TYPE` | `EASYHAPROXY_CERTBOT_PREFERRED_CHALLENGES` | `http` | ACME challenge type | | `--certbot-preferred-challenges TYPE` | `EASYHAPROXY_CERTBOT_PREFERRED_CHALLENGES` | `http` | ACME challenge type |
| `--certbot-manual-auth-hook SCRIPT` | `EASYHAPROXY_CERTBOT_MANUAL_AUTH_HOOK` | *(none)* | Path to a manual auth hook script for certbot | | `--certbot-manual-auth-hook SCRIPT` | `EASYHAPROXY_CERTBOT_MANUAL_AUTH_HOOK` | *(none)* | Path to a manual auth hook script for certbot |
See the full [ACME documentation](acme.md) for details. See the full [ACME documentation](../guides/acme.md) for details.
### Plugins ## Plugins
| Flag | Environment Variable | Default | Description | | Flag | Environment Variable | Default | Description |
|---------------------------------|--------------------------------------|----------|-------------------------------------------| |---------------------------------|--------------------------------------|----------|-------------------------------------------|
| `--plugins-enabled LIST` | `EASYHAPROXY_PLUGINS_ENABLED` | *(none)* | Comma-separated list of plugins to enable | | `--plugins-enabled LIST` | `EASYHAPROXY_PLUGINS_ENABLED` | *(none)* | Comma-separated list of plugins to enable |
| `--plugins-abort-on-error BOOL` | `EASYHAPROXY_PLUGINS_ABORT_ON_ERROR` | `false` | Abort startup if a plugin fails to load | | `--plugins-abort-on-error BOOL` | `EASYHAPROXY_PLUGINS_ABORT_ON_ERROR` | `false` | Abort startup if a plugin fails to load |
See the [plugins documentation](plugins.md) for available plugins. See the [plugins guide](../guides/plugins.md) for available plugins.
### Kubernetes ## Kubernetes
| Flag | Environment Variable | Default | Description | | Flag | Environment Variable | Default | Description |
|--------------------------------------------|--------------------------------------|----------|----------------------------------------------| |--------------------------------------------|--------------------------------------|----------|----------------------------------------------|
@ -144,68 +79,3 @@ See the [plugins documentation](plugins.md) for available plugins.
| `--deployment-mode MODE` | `EASYHAPROXY_DEPLOYMENT_MODE` | `auto` | Deployment mode: `auto`, `single`, `cluster` | | `--deployment-mode MODE` | `EASYHAPROXY_DEPLOYMENT_MODE` | `auto` | Deployment mode: `auto`, `single`, `cluster` |
| `--external-hostname HOSTNAME` | `EASYHAPROXY_EXTERNAL_HOSTNAME` | *(none)* | External hostname reported in Ingress status | | `--external-hostname HOSTNAME` | `EASYHAPROXY_EXTERNAL_HOSTNAME` | *(none)* | External hostname reported in Ingress status |
| `--ingress-status-update-interval SECONDS` | `EASYHAPROXY_STATUS_UPDATE_INTERVAL` | `30` | Interval to update Ingress status | | `--ingress-status-update-interval SECONDS` | `EASYHAPROXY_STATUS_UPDATE_INTERVAL` | `30` | Interval to update Ingress status |
## Quick-start examples
### Static mode (bare-metal / VM)
```bash
mkdir -p ~/easyhaproxy/static
cat > ~/easyhaproxy/static/config.yml <<EOF
containers:
"myapp.example.com:80":
ip: ["127.0.0.1:3000"]
EOF
easy-haproxy --discover static
```
### Static mode with stats and HTTPS redirect
```bash
easy-haproxy \
--discover static \
--haproxy-password mysecret \
--ssl-mode default \
--log-level INFO
```
### Let's Encrypt (ACME)
```bash
easy-haproxy \
--discover static \
--certbot-email admin@example.com \
--certbot-autoconfig letsencrypt
```
## Running as a systemd service
To keep `easy-haproxy` running across reboots, create a systemd unit:
```ini title="/etc/systemd/system/easy-haproxy.service"
[Unit]
Description=EasyHAProxy
After=network.target
[Service]
ExecStart=/usr/local/bin/easy-haproxy --discover static --haproxy-password mysecret
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now easy-haproxy
```
:::tip Adjust ExecStart path
Run `which easy-haproxy` to get the correct binary path for `ExecStart`. If you installed with `uv tool`, it is typically `/root/.local/bin/easy-haproxy` when running as root.
:::
----
[Open source ByJG](http://opensource.byjg.com)

View file

@ -1,5 +1,6 @@
--- ---
sidebar_position: 12 sidebar_position: 3
sidebar_label: "Container Labels"
--- ---
# Container Labels # Container Labels
@ -16,7 +17,7 @@ sidebar_position: 12
| easyhaproxy.[definition].sslcert | (Optional) Cert PEM Base64 encoded. Do not use this if `certbot` is enabled. | *empty* | base64 cert + key | | easyhaproxy.[definition].sslcert | (Optional) Cert PEM Base64 encoded. Do not use this if `certbot` is enabled. | *empty* | base64 cert + key |
| easyhaproxy.[definition].ssl | (Optional) If `true` you need to provide certificate as a file. See below. Do not use with `sslcert`. | false | true or false | | easyhaproxy.[definition].ssl | (Optional) If `true` you need to provide certificate as a file. See below. Do not use with `sslcert`. | false | true or false |
| easyhaproxy.[definition].ssl-check | (Optional) `ssl`, enable health check via SSL in `mode tcp` | *empty* | ssl | | easyhaproxy.[definition].ssl-check | (Optional) `ssl`, enable health check via SSL in `mode tcp` | *empty* | ssl |
| easyhaproxy.[definition].certbot | (Optional) Generate certificate with certbot. Do not use with `sslcert` parameter. More info [here](acme.md). | false | true OR false | | easyhaproxy.[definition].certbot | (Optional) Generate certificate with certbot. Do not use with `sslcert` parameter. More info [here](../guides/acme.md). | false | true OR false |
| easyhaproxy.[definition].redirect_ssl | (Optional) Redirect all requests to https | false | true OR false | | easyhaproxy.[definition].redirect_ssl | (Optional) Redirect all requests to https | false | true OR false |
| easyhaproxy.[definition].clone_to_ssl | (Optional) It copies the configuration to HTTPS(443) and disable SSL from the current config. **Do not use** this with `ssl` or `certbot` parameters | false | true OR false | | easyhaproxy.[definition].clone_to_ssl | (Optional) It copies the configuration to HTTPS(443) and disable SSL from the current config. **Do not use** this with `ssl` or `certbot` parameters | false | true OR false |
| easyhaproxy.[definition].balance | (Optional) HAProxy balance algorithm. See [HAProxy documentation](https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#4.2-balance) | roundrobin | roundrobin, source, uri, url_param, hdr, rdp-cookie, leastconn, first, static-rr, rdp-cookie, hdr_dom, map-based | | easyhaproxy.[definition].balance | (Optional) HAProxy balance algorithm. See [HAProxy documentation](https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#4.2-balance) | roundrobin | roundrobin, source, uri, url_param, hdr, rdp-cookie, leastconn, first, static-rr, rdp-cookie, hdr_dom, map-based |
@ -146,5 +147,24 @@ docker run \
--label easyhaproxy.[definition].redirect='{"www.byjg.com.br":"http://byjg.com.br","byjg.com":"http://byjg.com.br"}' --label easyhaproxy.[definition].redirect='{"www.byjg.com.br":"http://byjg.com.br","byjg.com":"http://byjg.com.br"}'
``` ```
---
## Kubernetes Ingress Annotations
When using Kubernetes, configure EasyHAProxy behavior with these annotations on your Ingress resources. Annotations apply to **all hosts** in the ingress configuration.
| Annotation | Description | Default | Example |
|-------------------------------------|--------------------------------------------------------------------------------------|------------|-----------------------------|
| kubernetes.io/ingress.class | (deprecated) Activate EasyHAProxy. Use `spec.ingressClassName` instead. | *optional* | easyhaproxy-ingress |
| easyhaproxy.redirect_ssl | (optional) Boolean. Force redirect all endpoints to HTTPS. | false | true or false |
| easyhaproxy.certbot | (optional) Boolean. Request certbot certificates for the ingress domains. | false | true or false |
| easyhaproxy.redirect | (optional) JSON. Key pair with a domain and its destination. | *empty* | \{"domain":"redirect_url"} |
| easyhaproxy.mode | (optional) Set the HTTP mode for that connection. | http | http or tcp |
| easyhaproxy.listen_port | (optional) Override the HTTP listen port created for that ingress. | 80 | 8081 |
| easyhaproxy.plugins | (optional) Comma-separated list of plugins to enable for this ingress. | *empty* | cloudflare,deny_pages |
| easyhaproxy.plugin.`{name}`.`{key}` | (optional) Plugin-specific configuration (see [Using Plugins](../guides/plugins.md)) | *varies* | See plugin docs |
For annotation usage examples, see the [Kubernetes getting started guide](../getting-started/kubernetes.md).
---- ----
[Open source ByJG](http://opensource.byjg.com) [Open source ByJG](http://opensource.byjg.com)

View file

@ -1,5 +1,6 @@
--- ---
sidebar_position: 13 sidebar_position: 2
sidebar_label: "Environment Variables"
--- ---
# Docker environment variables # Docker environment variables
@ -9,7 +10,7 @@ sidebar_position: 13
| EASYHAPROXY_DISCOVER | How the services will be discovered to create `haproxy.cfg`: `static`, `docker`, `swarm` or `kubernetes` | **required** | | EASYHAPROXY_DISCOVER | How the services will be discovered to create `haproxy.cfg`: `static`, `docker`, `swarm` or `kubernetes` | **required** |
| EASYHAPROXY_LABEL_PREFIX | (Optional) The key will search for matching resources. | `easyhaproxy` | | EASYHAPROXY_LABEL_PREFIX | (Optional) The key will search for matching resources. | `easyhaproxy` |
| EASYHAPROXY_BASE_PATH | (Optional) Base directory for all EasyHAProxy files. All paths (config, certs, plugins, www) are constructed relative to this base. | `/etc/easyhaproxy` | | EASYHAPROXY_BASE_PATH | (Optional) Base directory for all EasyHAProxy files. All paths (config, certs, plugins, www) are constructed relative to this base. | `/etc/easyhaproxy` |
| EASYHAPROXY_CERTBOT_* | (Optional) Enable Let's Encrypt or any other ACME certificate. See more: [acme](acme.md) | *empty* | | EASYHAPROXY_CERTBOT_* | (Optional) Enable Let's Encrypt or any other ACME certificate. See more: [acme](../guides/acme.md) | *empty* |
| EASYHAPROXY_SSL_MODE | (Optional) `strict` supports only the most recent TLS version; `default` good SSL integration with recent browsers; `loose` supports all old SSL protocols for old browsers (not recommended). | `default` | | EASYHAPROXY_SSL_MODE | (Optional) `strict` supports only the most recent TLS version; `default` good SSL integration with recent browsers; `loose` supports all old SSL protocols for old browsers (not recommended). | `default` |
| EASYHAPROXY_REFRESH_CONF | (Optional) Check for new containers/services every N seconds. | 10 | | EASYHAPROXY_REFRESH_CONF | (Optional) Check for new containers/services every N seconds. | 10 |
| EASYHAPROXY_LOG_LEVEL | (Optional) The log level for EasyHAproxy messages. Available: TRACE,DEBUG,INFO,WARN,ERROR,FATAL | DEBUG | | EASYHAPROXY_LOG_LEVEL | (Optional) The log level for EasyHAproxy messages. Available: TRACE,DEBUG,INFO,WARN,ERROR,FATAL | DEBUG |
@ -26,7 +27,22 @@ Statistics are only configured when `HAPROXY_PASSWORD` is set. Without a passwor
::: :::
:::note ACME/Certbot Environment Variables :::note ACME/Certbot Environment Variables
For ACME/Certbot configuration (Let's Encrypt, ZeroSSL, etc.), see the [ACME documentation](acme.md#environment-variables) for the complete list of `EASYHAPROXY_CERTBOT_*` variables. For ACME/Certbot configuration (Let's Encrypt, ZeroSSL, etc.), see the [ACME documentation](../guides/acme.md#environment-variables) for the complete list of `EASYHAPROXY_CERTBOT_*` variables.
:::
## Kubernetes
These variables apply only when `EASYHAPROXY_DISCOVER=kubernetes`. They control how EasyHAProxy updates Ingress resources with load-balancer IP information.
| Environment Variable | Description | Default |
|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|---------|
| EASYHAPROXY_UPDATE_INGRESS_STATUS | Update Ingress resources with the load-balancer IP. Set to `false` to disable. | `true` |
| EASYHAPROXY_DEPLOYMENT_MODE | How to detect and report Ingress IPs: `auto`, `daemonset`, `nodeport`, or `clusterip`. `auto` inspects pod owner references and service type automatically. | `auto` |
| EASYHAPROXY_EXTERNAL_HOSTNAME | Hostname to report in Ingress status when using ClusterIP mode without a cloud LoadBalancer. | *(none)* |
| EASYHAPROXY_STATUS_UPDATE_INTERVAL | Seconds between Ingress status update cycles. | `30` |
:::tip Deployment mode auto-detection
`EASYHAPROXY_DEPLOYMENT_MODE=auto` is recommended. EasyHAProxy inspects its own pod owner references (DaemonSet vs Deployment) and Service type (NodePort vs ClusterIP) to determine the correct IP source. Override only if auto-detection gives wrong results.
::: :::
---- ----

115
docs/reference/helm.md Normal file
View file

@ -0,0 +1,115 @@
---
sidebar_position: 6
sidebar_label: "Helm Values"
---
# Helm Values Reference
Complete reference for all configurable values in the `byjg/easyhaproxy` Helm chart.
## Image Configuration
| Value | Description | Default |
|---|---|---|
| `image.repository` | Container image repository | `byjg/easy-haproxy` |
| `image.tag` | Image tag. Defaults to the chart `appVersion`. | `""` |
| `image.pullPolicy` | Image pull policy | `Always` |
| `imagePullSecrets` | List of image pull secret names | `[]` |
| `nameOverride` | Override the chart name | `""` |
| `fullnameOverride` | Override the full release name | `""` |
## Deployment Mode
Controls whether EasyHAProxy runs as a **Deployment** (with a Service) or a **DaemonSet** (binding to host ports).
| Value | Description | Default |
|---|---|---|
| `replicaCount` | Number of replicas (only used when `service.create: true`) | `1` |
| `service.create` | `true` → Deployment + Service (NodePort or ClusterIP). `false` → DaemonSet with host ports. | `false` |
| `service.type` | Service type when `service.create: true`: `NodePort` or `ClusterIP` | `ClusterIP` |
| `service.annotations` | Annotations to add to the Service resource | `{}` |
:::tip Recommended mode
Set `service.create: true` and `service.type: NodePort` for most setups. DaemonSet mode (`service.create: false`) requires maintaining a node label across node replacements, which can cause outages if forgotten.
:::
## Port Binding
| Value | Description | Default |
|---|---|---|
| `binding.ports.http` | HTTP port | `80` |
| `binding.ports.https` | HTTPS port | `443` |
| `binding.ports.stats` | HAProxy stats port | `1936` |
| `binding.additionalPorts` | List of additional ports to expose | `[]` |
## EasyHAProxy Settings
| Value | Description | Default |
|---|---|---|
| `easyhaproxy.stats.username` | HAProxy stats dashboard username | `admin` |
| `easyhaproxy.stats.password` | HAProxy stats dashboard password | `password` |
| `easyhaproxy.refresh` | Seconds between service discovery polls | `"10"` |
| `easyhaproxy.customErrors` | Enable custom HTML error pages | `"true"` |
| `easyhaproxy.sslMode` | TLS mode: `strict`, `default`, or `loose` | `loose` |
| `easyhaproxy.logLevel.certbot` | Log level for Certbot | `DEBUG` |
| `easyhaproxy.logLevel.easyhaproxy` | Log level for EasyHAProxy | `DEBUG` |
| `easyhaproxy.logLevel.haproxy` | Log level for HAProxy | `DEBUG` |
| `easyhaproxy.certbot.email` | Email address for Let's Encrypt / ACME registration | `""` |
## Ingress Status
Controls how EasyHAProxy updates Kubernetes Ingress resources with load-balancer IPs.
| Value | Description | Default |
|---|---|---|
| `ingressStatus.enabled` | Update Ingress resources with load-balancer IPs | `true` |
| `ingressStatus.deploymentMode` | How to detect/report IPs: `auto`, `daemonset`, `nodeport`, or `clusterip`. `auto` is recommended. | `auto` |
| `ingressStatus.externalHostname` | Hostname to report in Ingress status (for ClusterIP mode without a LoadBalancer) | `""` |
| `ingressStatus.updateInterval` | Seconds between Ingress status updates | `30` |
## DaemonSet Node Selection
:::note Only applies when `service.create: false`
These values are ignored when running as a Deployment (`service.create: true`).
:::
| Value | Description | Default |
|---|---|---|
| `masterNode.label` | Node label key used for `nodeAffinity` | `easyhaproxy/node` |
| `masterNode.values` | Accepted values for the node label | `["master"]` |
Label the target node before installing:
```bash
kubectl label nodes node-01 "easyhaproxy/node=master"
```
## Standard Kubernetes Fields
| Value | Description | Default |
|---|---|---|
| `podAnnotations` | Annotations added to the EasyHAProxy pod | `{}` |
| `resources` | CPU/memory requests and limits for the pod | `{}` |
| `nodeSelector` | Node selector for pod scheduling | `{}` |
| `tolerations` | Tolerations for pod scheduling | `[]` |
| `affinity` | Affinity rules for pod scheduling | `{}` |
| `podSecurityContext` | Pod-level security context | `{}` |
| `securityContext` | Container-level security context | `{}` |
## Service Account
| Value | Description | Default |
|---|---|---|
| `serviceAccount.create` | Create a dedicated ServiceAccount | `true` |
| `serviceAccount.annotations` | Annotations to add to the ServiceAccount | `{}` |
| `serviceAccount.name` | Name of the ServiceAccount. Auto-generated if empty. | `""` |
## Ingress Class
| Value | Description | Default |
|---|---|---|
| `ingressClass.create` | Create an `IngressClass` resource named `easyhaproxy` | `true` |
| `ingressClass.annotations` | Annotations to add to the IngressClass | `{}` |
----
[Open source ByJG](http://opensource.byjg.com)

View file

@ -1,5 +1,6 @@
--- ---
sidebar_position: 22 sidebar_position: 5
sidebar_label: "Other Configurations"
--- ---
# Other configurations # Other configurations

View file

@ -0,0 +1,4 @@
{
"label": "Plugins",
"position": 5
}

View file

@ -1,5 +1,6 @@
--- ---
sidebar_position: 21 sidebar_position: 6
sidebar_label: "Cleanup"
--- ---
# Cleanup Plugin # Cleanup Plugin
@ -39,8 +40,6 @@ plugins:
### Environment Variables ### Environment Variables
Configure the Cleanup plugin globally:
| Environment Variable | Config Key | Type | Default | Description | | Environment Variable | Config Key | Type | Default | Description |
|-------------------------------------------------|----------------------|----------|---------|----------------------------------------------| |-------------------------------------------------|----------------------|----------|---------|----------------------------------------------|
| `EASYHAPROXY_PLUGINS_ENABLED` | - | string | - | Enable cleanup plugin (value: `cleanup`) | | `EASYHAPROXY_PLUGINS_ENABLED` | - | string | - | Enable cleanup plugin (value: `cleanup`) |
@ -48,12 +47,9 @@ Configure the Cleanup plugin globally:
| `EASYHAPROXY_PLUGIN_CLEANUP_MAX_IDLE_TIME` | `max_idle_time` | integer | `300` | Maximum age in seconds before deleting files | | `EASYHAPROXY_PLUGIN_CLEANUP_MAX_IDLE_TIME` | `max_idle_time` | integer | `300` | Maximum age in seconds before deleting files |
| `EASYHAPROXY_PLUGIN_CLEANUP_CLEANUP_TEMP_FILES` | `cleanup_temp_files` | boolean | `true` | Enable temp file cleanup | | `EASYHAPROXY_PLUGIN_CLEANUP_CLEANUP_TEMP_FILES` | `cleanup_temp_files` | boolean | `true` | Enable temp file cleanup |
**Note:** This is a global plugin - configuration applies to the entire system.
### Custom Idle Time (1 hour) ### Custom Idle Time (1 hour)
```yaml ```yaml
# /etc/easyhaproxy/static/config.yaml
plugins: plugins:
enabled: [cleanup] enabled: [cleanup]
config: config:
@ -79,6 +75,6 @@ The cleanup plugin:
## Related Documentation ## Related Documentation
- [Plugin System Overview](../plugins.md) - [Plugin System Overview](../../guides/plugins.md)
- [Environment Variables Reference](../environment-variable.md) - [Environment Variables Reference](../environment-variables.md)
- [Static Configuration Reference](../static.md) - [Static Configuration Reference](../../getting-started/static.md)

View file

@ -1,5 +1,6 @@
--- ---
sidebar_position: 18 sidebar_position: 3
sidebar_label: "Cloudflare"
--- ---
# Cloudflare Plugin # Cloudflare Plugin
@ -38,8 +39,6 @@ services:
### Docker/Docker Compose (Custom IP List) ### Docker/Docker Compose (Custom IP List)
If you want to use your own IP list file instead of the built-in ranges:
```yaml ```yaml
labels: labels:
easyhaproxy.http.plugins: cloudflare easyhaproxy.http.plugins: cloudflare
@ -72,7 +71,6 @@ spec:
### Static YAML Configuration ### Static YAML Configuration
```yaml ```yaml
# /etc/easyhaproxy/static/config.yaml
plugins: plugins:
config: config:
cloudflare: cloudflare:
@ -82,16 +80,12 @@ plugins:
### Environment Variables ### Environment Variables
Configure Cloudflare plugin defaults for all domains:
| Environment Variable | Config Key | Type | Default | Description | | Environment Variable | Config Key | Type | Default | Description |
|-------------------------------------------------|-------------------|----------|---------------------------------------|---------------------------------------| |-------------------------------------------------|-------------------|----------|---------------------------------------|---------------------------------------|
| `EASYHAPROXY_PLUGIN_CLOUDFLARE_ENABLED` | `enabled` | boolean | `true` | Enable/disable plugin for all domains | | `EASYHAPROXY_PLUGIN_CLOUDFLARE_ENABLED` | `enabled` | boolean | `true` | Enable/disable plugin for all domains |
| `EASYHAPROXY_PLUGIN_CLOUDFLARE_USE_BUILTIN_IPS` | `use_builtin_ips` | boolean | `true` | Use built-in Cloudflare IP ranges | | `EASYHAPROXY_PLUGIN_CLOUDFLARE_USE_BUILTIN_IPS` | `use_builtin_ips` | boolean | `true` | Use built-in Cloudflare IP ranges |
| `EASYHAPROXY_PLUGIN_CLOUDFLARE_IP_LIST_PATH` | `ip_list_path` | string | `/etc/easyhaproxy/cloudflare_ips.lst` | Path to Cloudflare IP list file | | `EASYHAPROXY_PLUGIN_CLOUDFLARE_IP_LIST_PATH` | `ip_list_path` | string | `/etc/easyhaproxy/cloudflare_ips.lst` | Path to Cloudflare IP list file |
**Note:** Environment variables set defaults for ALL domains. To enable/disable per-domain, use container labels or Kubernetes annotations.
## Generated HAProxy Configuration ## Generated HAProxy Configuration
```haproxy ```haproxy
@ -114,16 +108,13 @@ The plugin includes the current Cloudflare IP ranges (22 ranges total):
- 2400:cb00::/32, 2606:4700::/32, 2803:f800::/32, 2405:b500::/32 - 2400:cb00::/32, 2606:4700::/32, 2803:f800::/32, 2405:b500::/32
- 2405:8100::/32, 2a06:98c0::/29, 2c0f:f248::/32 - 2405:8100::/32, 2a06:98c0::/29, 2c0f:f248::/32
These ranges are automatically written to `/etc/easyhaproxy/cloudflare_ips.lst` during each discovery cycle.
## Important Notes ## Important Notes
- ✅ **No manual configuration required** - Built-in Cloudflare IPs are included! - ✅ **No manual configuration required** - Built-in Cloudflare IPs are included!
- The plugin runs once per domain during the discovery cycle - The plugin runs once per domain during the discovery cycle
- IP list file is automatically created and updated - IP list file is automatically created and updated
- To update Cloudflare IPs in the future, simply update the plugin source code and rebuild
## Related Documentation ## Related Documentation
- [Plugin System Overview](../plugins.md) - [Plugin System Overview](../../guides/plugins.md)
- [Container Labels Reference](../container-labels.md) - [Container Labels Reference](../container-labels.md)

View file

@ -1,5 +1,6 @@
--- ---
sidebar_position: 20 sidebar_position: 5
sidebar_label: "Deny Pages"
--- ---
# Deny Pages Plugin # Deny Pages Plugin
@ -73,7 +74,6 @@ spec:
### Static YAML Configuration ### Static YAML Configuration
```yaml ```yaml
# /etc/easyhaproxy/static/config.yaml
containers: containers:
"example.com:80": "example.com:80":
ip: ["webapp:80"] ip: ["webapp:80"]
@ -96,16 +96,12 @@ labels:
### Environment Variables ### Environment Variables
Configure Deny Pages plugin defaults for all domains:
| Environment Variable | Config Key | Type | Default | Description | | Environment Variable | Config Key | Type | Default | Description |
|---------------------------------------------|---------------|---------|---------|----------------------------------------| |---------------------------------------------|---------------|---------|---------|----------------------------------------|
| `EASYHAPROXY_PLUGIN_DENY_PAGES_ENABLED` | `enabled` | boolean | `true` | Enable/disable plugin for all domains | | `EASYHAPROXY_PLUGIN_DENY_PAGES_ENABLED` | `enabled` | boolean | `true` | Enable/disable plugin for all domains |
| `EASYHAPROXY_PLUGIN_DENY_PAGES_PATHS` | `paths` | string | - | Comma-separated list of paths to block | | `EASYHAPROXY_PLUGIN_DENY_PAGES_PATHS` | `paths` | string | - | Comma-separated list of paths to block |
| `EASYHAPROXY_PLUGIN_DENY_PAGES_STATUS_CODE` | `status_code` | integer | `403` | HTTP status code to return | | `EASYHAPROXY_PLUGIN_DENY_PAGES_STATUS_CODE` | `status_code` | integer | `403` | HTTP status code to return |
**Note:** Environment variables set defaults for ALL domains. To configure per-domain, use container labels or Kubernetes annotations.
## Generated HAProxy Configuration ## Generated HAProxy Configuration
```haproxy ```haproxy
@ -123,5 +119,5 @@ http-request deny deny_status 404 if denied_path
## Related Documentation ## Related Documentation
- [Plugin System Overview](../plugins.md) - [Plugin System Overview](../../guides/plugins.md)
- [Container Labels Reference](../container-labels.md) - [Container Labels Reference](../container-labels.md)

View file

@ -1,5 +1,6 @@
--- ---
sidebar_position: 17 sidebar_position: 2
sidebar_label: "FastCGI"
--- ---
# FastCGI Plugin # FastCGI Plugin
@ -64,16 +65,6 @@ services:
- /run/php:/run/php - /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 ### Kubernetes Annotations
```yaml ```yaml
@ -117,22 +108,16 @@ easymapping:
### Environment Variables ### Environment Variables
Configure FastCGI plugin defaults for all domains: | Environment Variable | Config Key | Type | Default | Description |
|----------------------------------------------|-------------------|----------|------------------------|---------------------------------------|
| Environment Variable | Config Key | Type | Default | Description | | `EASYHAPROXY_PLUGIN_FASTCGI_ENABLED` | `enabled` | boolean | `true` | Enable/disable plugin for all domains |
|----------------------------------------------|-------------------|----------|-----------------|---------------------------------------|
| `EASYHAPROXY_PLUGIN_FASTCGI_ENABLED` | `enabled` | boolean | `true` | Enable/disable plugin for all domains |
| `EASYHAPROXY_PLUGIN_FASTCGI_DOCUMENT_ROOT` | `document_root` | string | `/etc/easyhaproxy/www` | Document root path | | `EASYHAPROXY_PLUGIN_FASTCGI_DOCUMENT_ROOT` | `document_root` | string | `/etc/easyhaproxy/www` | Document root path |
| `EASYHAPROXY_PLUGIN_FASTCGI_SCRIPT_FILENAME` | `script_filename` | string | `%[path]` | Custom pattern for SCRIPT_FILENAME | | `EASYHAPROXY_PLUGIN_FASTCGI_SCRIPT_FILENAME` | `script_filename` | string | `%[path]` | Custom pattern for SCRIPT_FILENAME |
| `EASYHAPROXY_PLUGIN_FASTCGI_INDEX_FILE` | `index_file` | string | `index.php` | Default index file | | `EASYHAPROXY_PLUGIN_FASTCGI_INDEX_FILE` | `index_file` | string | `index.php` | Default index file |
| `EASYHAPROXY_PLUGIN_FASTCGI_PATH_INFO` | `path_info` | boolean | `true` | Enable PATH_INFO support | | `EASYHAPROXY_PLUGIN_FASTCGI_PATH_INFO` | `path_info` | boolean | `true` | Enable PATH_INFO support |
**Note:** Environment variables set defaults for ALL domains. To configure per-domain, use container labels or Kubernetes annotations. Custom params (`custom_params`) cannot be configured via environment variables - use YAML or labels instead.
## Generated HAProxy Configuration ## Generated HAProxy Configuration
The plugin generates a top-level `fcgi-app` section and a `use-fcgi-app` directive in the backend:
```haproxy ```haproxy
# Top-level fcgi-app definition (added after defaults, before frontends/backends) # Top-level fcgi-app definition (added after defaults, before frontends/backends)
fcgi-app fcgi_phpapp_local fcgi-app fcgi_phpapp_local
@ -143,16 +128,11 @@ fcgi-app fcgi_phpapp_local
# Backend configuration (added to the backend section) # Backend configuration (added to the backend section)
backend srv_phpapp_local_80 backend srv_phpapp_local_80
use-fcgi-app fcgi_phpapp_local use-fcgi-app fcgi_phpapp_local
# TCP connection:
server srv-0 172.19.0.3:9000 proto fcgi 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 ## 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: The plugin configures:
- ✅ **SCRIPT_FILENAME** - Path to PHP script - ✅ **SCRIPT_FILENAME** - Path to PHP script
- ✅ **DOCUMENT_ROOT** - Document root directory - ✅ **DOCUMENT_ROOT** - Document root directory
@ -169,9 +149,8 @@ The plugin configures:
- **Required:** Use this plugin together with `proto: fcgi` parameter for complete PHP-FPM support - **Required:** Use this plugin together with `proto: fcgi` parameter for complete PHP-FPM support
- The plugin runs once per domain during the discovery cycle - The plugin runs once per domain during the discovery cycle
- HAProxy handles the actual FastCGI protocol communication and CGI parameter transmission
## Related Documentation ## Related Documentation
- [Plugin System Overview](../plugins.md) - [Plugin System Overview](../../guides/plugins.md)
- [Container Labels Reference](../container-labels.md) - [Container Labels Reference](../container-labels.md)

View file

@ -1,5 +1,6 @@
--- ---
sidebar_position: 19 sidebar_position: 4
sidebar_label: "IP Whitelist"
--- ---
# IP Whitelist Plugin # IP Whitelist Plugin
@ -72,7 +73,6 @@ spec:
### Static YAML Configuration ### Static YAML Configuration
```yaml ```yaml
# /etc/easyhaproxy/static/config.yaml
easymapping: easymapping:
- host: admin.example.com - host: admin.example.com
port: 443 port: 443
@ -87,16 +87,12 @@ easymapping:
### Environment Variables ### Environment Variables
Configure IP Whitelist plugin defaults for all domains:
| Environment Variable | Config Key | Type | Default | Description | | Environment Variable | Config Key | Type | Default | Description |
|-----------------------------------------------|---------------|----------|---------|--------------------------------------------------| |-----------------------------------------------|---------------|----------|---------|--------------------------------------------------|
| `EASYHAPROXY_PLUGIN_IP_WHITELIST_ENABLED` | `enabled` | boolean | `true` | Enable/disable plugin for all domains | | `EASYHAPROXY_PLUGIN_IP_WHITELIST_ENABLED` | `enabled` | boolean | `true` | Enable/disable plugin for all domains |
| `EASYHAPROXY_PLUGIN_IP_WHITELIST_ALLOWED_IPS` | `allowed_ips` | string | - | Comma-separated list of IPs/CIDR ranges to allow | | `EASYHAPROXY_PLUGIN_IP_WHITELIST_ALLOWED_IPS` | `allowed_ips` | string | - | Comma-separated list of IPs/CIDR ranges to allow |
| `EASYHAPROXY_PLUGIN_IP_WHITELIST_STATUS_CODE` | `status_code` | integer | `403` | HTTP status code to return for blocked IPs | | `EASYHAPROXY_PLUGIN_IP_WHITELIST_STATUS_CODE` | `status_code` | integer | `403` | HTTP status code to return for blocked IPs |
**Note:** Environment variables set defaults for ALL domains. To configure per-domain, use container labels or Kubernetes annotations.
## Generated HAProxy Configuration ## Generated HAProxy Configuration
```haproxy ```haproxy
@ -118,9 +114,8 @@ The plugin supports:
- The plugin runs once per domain during the discovery cycle - The plugin runs once per domain during the discovery cycle
- Test thoroughly before deploying to production - Test thoroughly before deploying to production
- Consider using VPN CIDR ranges for remote access - Consider using VPN CIDR ranges for remote access
- Works well with staging and admin environments
## Related Documentation ## Related Documentation
- [Plugin System Overview](../plugins.md) - [Plugin System Overview](../../guides/plugins.md)
- [Container Labels Reference](../container-labels.md) - [Container Labels Reference](../container-labels.md)

View file

@ -0,0 +1,196 @@
---
sidebar_position: 1
sidebar_label: "JWT Validator"
---
# 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.
## Generating JWT Keys
```bash
# Generate RSA key pair (idempotent - skips if exists)
[ -f jwt_private.pem ] || openssl genrsa -out jwt_private.pem 2048
[ -f jwt_pubkey.pem ] || openssl rsa -in jwt_private.pem -pubout -out jwt_pubkey.pem
```
## 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 (priority 1: explicit file path) | (optional) |
| `pubkey` | Public key content as base64-encoded string (priority 2: inline content) | (optional) |
| `k8s_secret.pubkey` | Kubernetes secret containing public key (priority 3: Kubernetes only) | (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` |
| `allow_anonymous` | If `true`, allows requests without Authorization header (validates JWT if present) | `false` |
### Public Key Configuration Priority
When multiple public key options are configured, they are evaluated in this order:
1. **`pubkey_path`** - Direct file path (explicit configuration)
2. **`pubkey`** - Base64-encoded key content (inline configuration)
3. **`k8s_secret.pubkey`** - Kubernetes secret (recommended for Kubernetes deployments)
## 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
## Anonymous Access Logic
- **`allow_anonymous=false` (default):** Requests without `Authorization` header are denied
- **`allow_anonymous=true`:** Requests without `Authorization` header are allowed to pass through, but JWTs are validated if the header is present
## 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/easyhaproxy/jwt_keys/api_pubkey.pem
volumes:
- ./pubkey.pem:/etc/easyhaproxy/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/easyhaproxy/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/easyhaproxy/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
```
### Kubernetes with Secrets (Recommended)
```yaml
---
apiVersion: v1
kind: Secret
metadata:
name: jwt-pubkey-secret
namespace: production
type: Opaque
stringData:
pubkey: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
namespace: production
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.k8s_secret.pubkey: "jwt-pubkey-secret"
easyhaproxy.plugin.jwt_validator.paths: "/api/admin,/api/users"
easyhaproxy.plugin.jwt_validator.only_paths: "false"
spec:
ingressClassName: easyhaproxy
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
```
### Static YAML Configuration
```yaml
# /etc/easyhaproxy/static/config.yaml
containers:
"api.example.com:443":
ip: ["api-service:8080"]
ssl: true
plugins: [jwt_validator]
plugin:
jwt_validator:
algorithm: RS256
issuer: https://auth.example.com/
audience: https://api.example.com
pubkey_path: /etc/easyhaproxy/jwt_keys/api_pubkey.pem
```
### Environment Variables
| Environment Variable | Config Key | Type | Default | Description |
|----------------------------------------------------|-------------------|---------|---------|---------------------------------------------|
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_ENABLED` | `enabled` | boolean | `true` | Enable/disable plugin for all domains |
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_ALGORITHM` | `algorithm` | string | `RS256` | JWT signing algorithm |
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_ISSUER` | `issuer` | string | - | Expected JWT issuer (optional) |
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_AUDIENCE` | `audience` | string | - | Expected JWT audience (optional) |
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_PUBKEY_PATH` | `pubkey_path` | string | - | Path to public key file |
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_PUBKEY` | `pubkey` | string | - | Public key as base64-encoded string |
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_PATHS` | `paths` | string | - | Comma-separated paths requiring JWT |
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_ONLY_PATHS` | `only_paths` | boolean | `false` | If true, only specified paths accessible |
| `EASYHAPROXY_PLUGIN_JWT_VALIDATOR_ALLOW_ANONYMOUS` | `allow_anonymous` | boolean | `false` | Allow requests without Authorization header |
## 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](../../guides/plugins.md)
- [Container Labels Reference](../container-labels.md)
- [Kubernetes Secrets](../../getting-started/kubernetes.md#loading-plugin-configuration-from-kubernetes-secrets)

View file

@ -1,5 +1,6 @@
--- ---
sidebar_position: 14 sidebar_position: 4
sidebar_label: "Volumes"
--- ---
# Volumes # Volumes
@ -79,15 +80,15 @@ The most commonly mapped volumes for persistence and customization:
| Volume | Purpose | Required | | Volume | Purpose | Required |
|-------------------------------------------|-------------------------------------------------------------------------------------------------------|----------| |-------------------------------------------|-------------------------------------------------------------------------------------------------------|----------|
| `/etc/easyhaproxy/static/` | [Static configuration](static.md) - mount your `config.yml` here | Optional | | `/etc/easyhaproxy/static/` | [Static configuration](../getting-started/static.md) - mount your `config.yml` here | Optional |
| `/etc/easyhaproxy/certs/haproxy/` | [SSL certificates](ssl.md) - user-provided certificates in PEM format | Optional | | `/etc/easyhaproxy/certs/haproxy/` | [SSL certificates](../guides/ssl.md) - user-provided certificates in PEM format | Optional |
| `/etc/easyhaproxy/certs/certbot/` | [ACME/Certbot certificates](acme.md) - auto-generated Let's Encrypt certificates | Optional | | `/etc/easyhaproxy/certs/certbot/` | [ACME/Certbot certificates](../guides/acme.md) - auto-generated Let's Encrypt certificates | Optional |
| `/etc/easyhaproxy/certs/live/` | Certbot live certificates - persist across container restarts | Optional | | `/etc/easyhaproxy/certs/live/` | Certbot live certificates - persist across container restarts | Optional |
| `/etc/easyhaproxy/haproxy/conf.d/` | [Custom HAProxy config](other.md) - additional `.cfg` files to include | Optional | | `/etc/easyhaproxy/haproxy/conf.d/` | [Custom HAProxy config](other.md) - additional `.cfg` files to include | Optional |
| `/etc/easyhaproxy/haproxy/errors-custom/` | [Custom error pages](other.md) - custom HTTP error pages (400, 403, 500, etc.) | Optional | | `/etc/easyhaproxy/haproxy/errors-custom/` | [Custom error pages](other.md) - custom HTTP error pages (400, 403, 500, etc.) | Optional |
| `/etc/easyhaproxy/plugins/` | [Custom plugins](plugins.md) - Python plugin files | Optional | | `/etc/easyhaproxy/plugins/` | [Custom plugins](../guides/plugins.md) - Python plugin files | Optional |
| `/etc/easyhaproxy/jwt_keys/` | [JWT public keys](Plugins/jwt-validator.md) - RSA public keys for JWT validation | Optional | | `/etc/easyhaproxy/jwt_keys/` | [JWT public keys](plugins/jwt-validator.md) - RSA public keys for JWT validation | Optional |
| `/etc/easyhaproxy/www/` | [FastCGI document root](Plugins/fastcgi.md) - PHP/FastCGI application files | Optional | | `/etc/easyhaproxy/www/` | [FastCGI document root](plugins/fastcgi.md) - PHP/FastCGI application files | Optional |
## Directory Details ## Directory Details
@ -165,19 +166,19 @@ volumes:
``` ```
Add custom Python plugins to extend EasyHAProxy functionality. Add custom Python plugins to extend EasyHAProxy functionality.
See [Plugin Development](plugin-development.md) for details. See [Plugin Development](../guides/plugin-development.md) for details.
#### JWT Public Keys #### JWT Public Keys
```bash ```bash
/etc/easyhaproxy/jwt_keys/*.pem /etc/easyhaproxy/jwt_keys/*.pem
``` ```
RSA public keys for [JWT token validation](Plugins/jwt-validator.md). RSA public keys for [JWT token validation](plugins/jwt-validator.md).
#### Cloudflare IP Ranges #### Cloudflare IP Ranges
```bash ```bash
/etc/easyhaproxy/cloudflare_ips.lst /etc/easyhaproxy/cloudflare_ips.lst
``` ```
Cloudflare IP ranges for the [Cloudflare plugin](Plugins/cloudflare.md) to restore real client IPs. Cloudflare IP ranges for the [Cloudflare plugin](plugins/cloudflare.md) to restore real client IPs.
### Error Pages ### Error Pages

View file

@ -1,140 +0,0 @@
---
sidebar_position: 4
---
# Static File
## Setup Docker EasyHAProxy
This method will use a static configuration, which is simpler and easier than creating a `haproxy.cfg`
You can use this configuration to set up external servers unrelated to docker or Kubernetes.
:::tip Live Reload
EasyHAProxy monitors this file for changes and automatically reconfigures HAProxy when any changes are detected.
:::
First, create a YAML configuration:
```yaml
stats:
username: admin
password: password
port: 1936 # Optional (default 1936)
customerrors: true # Optional (default false)
ssl_mode: default
logLevel:
haproxy: INFO
certbot:
email: "acme@example.org"
containers:
# HTTP with certbot + redirect to HTTPS
"host1.com.br:80":
ip: ["container:5000"]
certbot: true
redirect_ssl: true
# Additional HTTP host
"host2.com.br:80":
ip: ["other:3000"]
# Redirect www → main domain
"www.host1.com.br:80":
ip: ["container:5000"]
redirect_ssl: true
# HTTPS version
"host1.com.br:443":
ip: ["container:80"]
ssl: true
# Different host on different port
"host3.com.br:8080":
ip: ["domain:8181"]
```
:::info New Configuration Format
The `containers` format simplifies static configuration:
- **Flatter structure**: `"hostname:port"` keys instead of nested `easymapping``ports``hosts`
- **Better readability**: Port and localport embedded in keys (`"host:port"` and `"container:localport"`)
- **Plugin support**: Global and per-host plugin configuration
- **Clearer mapping**: Format mirrors internal Docker label structure
:::
Then map this file to `/etc/easyhaproxy/static/config.yml` in your EasyHAProxy container:
```bash title="Run EasyHAProxy with static configuration"
docker run -d \
--name easy-haproxy-container \
-v /my/static/:/etc/easyhaproxy/static/ \
-e EASYHAPROXY_DISCOVER="static" \
# + Environment Variables \
-p 80:80 \
-p 443:443 \
-p 1936:1936 \
--network easyhaproxy \
byjg/easy-haproxy
```
:::tip Docker Socket Optional
Mounting `/var/run/docker.sock` is not required in static discovery mode. Add it only if you are simultaneously discovering Docker containers.
:::
You can find other information on [docker label configuration](container-labels.md) and [environment variable guide](environment-variable.md)
## Yaml Definition
```yaml
stats:
username: admin # Optional (default "admin")
password: password # If stats or stats.password is omitted, stats will be public with no password
port: 1936 # Optional (default 1936)
customerrors: true # Optional (default false)
ssl_mode: default # Optional
logLevel:
certbot: DEBUG # Optional (default: DEBUG). Can be: TRACE,DEBUG,INFO,WARN,ERROR,FATAL
easyhaproxy: DEBUG # Optional (default: DEBUG). Can be: TRACE,DEBUG,INFO,WARN,ERROR,FATAL
haproxy: INFO # Optional (default: INFO). Can be: TRACE,DEBUG,INFO,WARN,ERROR,FATAL
certbot:
email: "acme@example.org" # If email is defined enable ACME/Certbot
autoconfig: "" # If empty use letsencrypt, otherwise try to set the CA defined.
eab_hmac_key: "" # If required by the CA, set here.
eab_kid: "" # If required by the CA, set here.
server: False # If empty/False uses Letsencrypt, otherwise the CA Endpoint defined here
retry_count: 60 # If the certificate reaches the Rate Limit, try again after 'n' iterations.
}
containers:
# Format: "hostname:port"
"host1.com.br:80":
ip: ["container:5000"] # Endpoints (ip, dns, container, etc) with format "address:localport"
certbot: true # Optional. Request a certbot certificate. Requires certbot.email set.
redirect_ssl: true # Optional. Redirect HTTP to HTTPS for this host.
mode: http # Optional. Default `http`. Can be http or tcp
# HTTPS version (SSL)
"host1.com.br:443":
ip: ["container:80"]
ssl: true # Enable SSL for this port
# Redirect www → main domain (using redirect_ssl with backend)
"www.host1.com.br:80":
ip: ["container:5000"]
redirect_ssl: true
```
:::note SSL Certificates in Static Mode
The only way to provide SSL certificates in static configuration mode is to map the certificates to EasyHAProxy as a docker volume. Refer to the [SSL documentation](ssl.md) to learn how to configure this.
:::
----
[Open source ByJG](http://opensource.byjg.com)

View file

@ -1,109 +0,0 @@
---
sidebar_position: 2
---
# Swarm
## Setup Docker EasyHAProxy
This method involves using a Docker Swarm installation to discover containers and configure HAProxy.
EasyHAProxy inspects Docker containers within the Swarm and retrieves labels to configure HAProxy. Once it identifies a container with at least the label 'easyhaproxy.http.host,' it configures HAProxy to redirect traffic to that container. To accomplish this, EasyHAProxy may need to attach the same network to its container.
:::tip Docker Swarm Advantages
- **Container Discovery**: Docker Swarm facilitates the discovery of containers within the cluster, streamlining the process of identifying services for HAProxy configuration.
- **Remote Node Management**: Docker Swarm allows for the management of containers across multiple nodes, providing flexibility and scalability in deploying services while ensuring seamless HAProxy configuration across the cluster.
:::
It's recommended to create a network external to EasyHAProxy.
:::warning Limitations
- You cannot mix Docker containers with Swarm containers.
- This method does not work with containers that use the `--network=host` option. See [limitations](limitations.md) for details.
:::
For example:
```bash title="Create overlay network"
docker network create -d overlay --attachable easyhaproxy
```
And then deploy the EasyHAProxy stack:
```yaml
services:
haproxy:
image: byjg/easy-haproxy:6.0.0
volumes:
- /var/run/docker.sock:/var/run/docker.sock
deploy:
replicas: 1
environment:
EASYHAPROXY_DISCOVER: swarm
EASYHAPROXY_SSL_MODE: "loose"
HAPROXY_CUSTOMERRORS: "true"
HAPROXY_USERNAME: admin
HAPROXY_PASSWORD: password
HAPROXY_STATS_PORT: 1936
ports:
- "80:80/tcp"
- "443:443/tcp"
- "1936:1936/tcp"
networks:
- easyhaproxy
networks:
easyhaproxy:
external: true
```
Deploy the stack:
```bash title="Deploy EasyHAProxy stack"
docker stack deploy --compose-file docker-compose.yml easyhaproxy
```
Mapping to `/var/run/docker.sock` is necessary to discover the docker containers and get the labels;
:::danger Single Replica Only
**Do not** add more than one replica for EasyHAProxy. To understand why, see the [limitations](limitations.md) page.
:::
## Running containers
To make your containers "discoverable" by EasyHAProxy, that is the minimum configuration you need:
```yaml
services:
container:
image: my/image:tag
deploy:
replicas: 1
labels:
easyhaproxy.http.host: host1.local
easyhaproxy.http.port: 80
easyhaproxy.http.localport: 8080
networks:
- easyhaproxy
networks:
easyhaproxy:
external: true
```
Once the container is running, EasyHAProxy will detect automatically and start to redirect all traffic from `example.org:80` to your container.
You don't need to expose any port in your container.
Please follow the [docker label configuration](container-labels.md) to see other configurations available.
## Setup the EasyHAProxy container
You can configure the behavior of the EasyHAProxy by setup specific environment variables. To get a list of the variables, please follow the [environment variable guide](environment-variable.md)
## More information
You can refer to the [Docker Documentation](docker.md) to get other detailed instructions.
----
[Open source ByJG](http://opensource.byjg.com)

View file

@ -46,7 +46,7 @@ Each docker-compose file contains:
## Additional Documentation ## Additional Documentation
- [Container Labels Reference](../../docs/container-labels.md) - [Container Labels Reference](../../docs/reference/container-labels.md)
- [Docker Configuration Guide](../../docs/docker.md) - [Docker Configuration Guide](../../docs/getting-started/docker.md)
- [Environment Variables](../../docs/environment-variable.md) - [Environment Variables](../../docs/reference/environment-variables.md)
- [Plugin Documentation](../../docs/plugins/) - [Plugin Documentation](../../docs/guides/plugins.md)

View file

@ -146,6 +146,6 @@ docker compose -f docker-compose-php-fpm.yml exec haproxy ping php-fpm
## Learn More ## Learn More
- [FastCGI Plugin Documentation](../../../docs/plugins.md#fastcgi-plugin) - [FastCGI Plugin Documentation](../../../docs/reference/plugins/fastcgi.md)
- [Container Labels Reference](../../../docs/container-labels.md) - [Container Labels Reference](../../../docs/reference/container-labels.md)
- [HAProxy FastCGI Documentation](https://docs.haproxy.org/2.8/configuration.html#5.2-proto) - [HAProxy FastCGI Documentation](https://docs.haproxy.org/2.8/configuration.html#5.2-proto)

View file

@ -44,7 +44,7 @@ Each YAML file contains:
## Additional Documentation ## Additional Documentation
- [Kubernetes Installation Guide](../../docs/kubernetes.md) - [Kubernetes Installation Guide](../../docs/getting-started/kubernetes.md)
- [Helm Installation](../../docs/helm.md) - [Helm Installation](../../docs/guides/helm.md)
- [Kubernetes Annotations Reference](../../docs/kubernetes.md#kubernetes-annotations) - [Kubernetes Annotations Reference](../../docs/getting-started/kubernetes.md#kubernetes-annotations)
- [Using Plugins with Kubernetes](../../docs/kubernetes.md#using-plugins-with-kubernetes) - [Using Plugins with Kubernetes](../../docs/getting-started/kubernetes.md#using-plugins-with-kubernetes)

View file

@ -89,6 +89,6 @@ See `conf/` directory for complete examples.
## Additional Documentation ## Additional Documentation
- [Static Configuration Guide](../../docs/static.md) - [Static Configuration Guide](../../docs/getting-started/static.md)
- [Using Plugins](../../docs/plugins/) - [Using Plugins](../../docs/guides/plugins.md)
- [Environment Variables](../../docs/environment-variable.md) - [Environment Variables](../../docs/reference/environment-variables.md)

View file

@ -61,7 +61,7 @@ labels:
## Additional Documentation ## Additional Documentation
- [Docker Swarm Guide](../../docs/swarm.md) - [Docker Swarm Guide](../../docs/getting-started/swarm.md)
- [Container Labels Reference](../../docs/container-labels.md) - [Container Labels Reference](../../docs/reference/container-labels.md)
- [Using Plugins](../../docs/plugins/) - [Using Plugins](../../docs/guides/plugins.md)
- [ACME/Let's Encrypt](../../docs/acme.md) - [ACME/Let's Encrypt](../../docs/guides/acme.md)