1
0
Fork 0

Add IP Whitelist Plugin and corresponding tests

- Introduced `IpWhitelistPlugin` (DOMAIN): Restricts access to specific IP addresses or CIDR ranges.
- Added configuration options: `enabled`, `allowed_ips`, and `status_code`.
- Updated `plugins.md` and `plugin-development.md` to document `IpWhitelistPlugin`.
- Created fixtures for `services-with-ip-whitelist`.
- Added extensive test cases for `IpWhitelistPlugin` to validate configuration and HAProxy output generation.
- Ensured seamless integration into the plugin framework alongside other domain plugins.
This commit is contained in:
Joao Gilberto Magalhaes 2025-11-27 18:35:37 -05:00
parent 91f8ff5d08
commit 57387f3e32
5 changed files with 1549 additions and 428 deletions

File diff suppressed because it is too large Load diff

View file

@ -2,42 +2,57 @@
sidebar_position: 16 sidebar_position: 16
--- ---
# Plugins # Using Plugins
EasyHAProxy supports a flexible plugin system that allows you to extend HAProxy configuration with custom functionality. Plugins are automatically invoked during the discovery cycle and can inject HAProxy configuration directives or modify discovery data. EasyHAProxy supports a plugin system that extends HAProxy configuration with custom functionality. This guide explains how to use and configure plugins.
## What are Plugins?
Plugins automatically run during the discovery cycle and can:
- Add HAProxy configuration directives
- Perform maintenance tasks
- Modify discovery data
- Integrate with external services
## Plugin Types ## Plugin Types
### Global Plugins ### Global Plugins
Global plugins execute **once per discovery cycle**, regardless of how many domains are discovered. They're ideal for:
Execute **once per discovery cycle** regardless of how many domains are discovered.
**Use cases:**
- Cleanup tasks - Cleanup tasks
- Global monitoring - Global monitoring
- DNS updates - DNS updates
- Log management - Log management
**Example:** `cleanup` plugin
### Domain Plugins ### Domain Plugins
Domain plugins execute **once for each discovered domain/host**. They're ideal for:
Execute **once for each discovered domain/host**.
**Use cases:**
- Domain-specific configuration - Domain-specific configuration
- IP restoration (e.g., Cloudflare) - IP restoration (Cloudflare)
- Path blocking - Path blocking
- Custom headers per domain - Custom headers per domain
**Examples:** `cloudflare`, `deny_pages`
## Built-in Plugins ## Built-in Plugins
### Cloudflare (Domain Plugin) ### Cloudflare Plugin (Domain)
Restores the original visitor IP address from Cloudflare's `CF-Connecting-IP` header when requests come through Cloudflare's CDN. Restores the original visitor IP address when requests come through Cloudflare's CDN.
**Configuration:** **Why use it:** Cloudflare replaces the visitor's IP with its own. This plugin restores the original IP from the `CF-Connecting-IP` header.
```yaml
# /etc/haproxy/static/config.yaml
plugins:
cloudflare:
enabled: true
ip_list_path: /etc/haproxy/cloudflare_ips.lst
```
**Container Label:** **Configuration options:**
- `enabled` - Enable/disable plugin (default: `true`)
- `ip_list_path` - Path to Cloudflare IP list (default: `/etc/haproxy/cloudflare_ips.lst`)
**Enable via container label:**
```yaml ```yaml
services: services:
myapp: myapp:
@ -46,101 +61,117 @@ services:
easyhaproxy.http.plugins: cloudflare easyhaproxy.http.plugins: cloudflare
``` ```
**Environment Variable:** **Custom IP list path:**
```bash ```yaml
EASYHAPROXY_PLUGINS_ENABLED=cloudflare labels:
EASYHAPROXY_PLUGIN_CLOUDFLARE_IP_LIST_PATH=/etc/haproxy/cloudflare_ips.lst easyhaproxy.http.plugins: cloudflare
easyhaproxy.http.plugin.cloudflare.ip_list_path: /custom/path/cf_ips.lst
``` ```
### Cleanup (Global Plugin) **HAProxy config generated:**
```
# Cloudflare - Restore original visitor IP
acl from_cloudflare src -f /etc/haproxy/cloudflare_ips.lst
http-request set-header X-Forwarded-For %[req.hdr(CF-Connecting-IP)] if from_cloudflare
```
**Required:** Download Cloudflare IP list from [Cloudflare documentation](https://support.cloudflare.com/hc/en-us/articles/200170786).
### Cleanup Plugin (Global)
Performs cleanup tasks during each discovery cycle, such as removing old temporary files. Performs cleanup tasks during each discovery cycle, such as removing old temporary files.
**Configuration:** **Why use it:** Prevents disk space issues by automatically cleaning up temporary files created by EasyHAProxy.
**Configuration options:**
- `enabled` - Enable/disable plugin (default: `true`)
- `max_idle_time` - Maximum age in seconds before deleting files (default: `300`)
- `cleanup_temp_files` - Enable temp file cleanup (default: `true`)
**Enable via YAML:**
```yaml ```yaml
# /etc/haproxy/static/config.yaml # /etc/haproxy/static/config.yaml
plugins: plugins:
cleanup: enabled: [cleanup]
enabled: true config:
max_idle_time: 300 # seconds cleanup:
cleanup_temp_files: true max_idle_time: 600
cleanup_temp_files: true
``` ```
**Environment Variable:** **Enable via environment variable:**
```bash ```bash
EASYHAPROXY_PLUGINS_ENABLED=cleanup EASYHAPROXY_PLUGINS_ENABLED=cleanup
EASYHAPROXY_PLUGIN_CLEANUP_MAX_IDLE_TIME=600 EASYHAPROXY_PLUGIN_CLEANUP_MAX_IDLE_TIME=600
``` ```
### Deny Pages (Domain Plugin) ### Deny Pages Plugin (Domain)
Blocks access to specific paths for a domain. Blocks access to specific paths for a domain, returning a configurable HTTP status code.
**Configuration:** **Why use it:** Protect admin panels, internal APIs, or debugging endpoints from public access.
```yaml
# /etc/haproxy/static/config.yaml
plugins:
deny_pages:
enabled: true
paths: /admin,/private,/internal
status_code: 403
```
**Container Label:** **Configuration options:**
- `enabled` - Enable/disable plugin (default: `true`)
- `paths` - Comma-separated list of paths to block (e.g., `/admin,/private`)
- `status_code` - HTTP status code to return (default: `403`)
**Enable via container label:**
```yaml ```yaml
services: services:
myapp: webapp:
labels: labels:
easyhaproxy.http.host: example.com easyhaproxy.http.host: example.com
easyhaproxy.http.plugins: deny_pages easyhaproxy.http.plugins: deny_pages
easyhaproxy.http.plugin.deny_pages.paths: /admin,/private easyhaproxy.http.plugin.deny_pages.paths: /admin,/private,/debug
easyhaproxy.http.plugin.deny_pages.status_code: 403 easyhaproxy.http.plugin.deny_pages.status_code: 404
``` ```
**HAProxy config generated:**
```
# Deny Pages - Block specific paths
acl denied_path path_beg /admin /private /debug
http-request deny deny_status 404 if denied_path
```
### IP Whitelist Plugin (Domain)
Restricts access to a domain to only specific IP addresses or CIDR ranges.
**Why use it:** Restrict access to internal tools, admin panels, or staging environments to only trusted IP addresses.
**Configuration options:**
- `enabled` - Enable/disable plugin (default: `true`)
- `allowed_ips` - Comma-separated list of IPs/CIDR ranges to allow (e.g., `192.168.1.0/24,10.0.0.1`)
- `status_code` - HTTP status code to return for blocked IPs (default: `403`)
**Enable via container label:**
```yaml
services:
admin:
labels:
easyhaproxy.http.host: admin.example.com
easyhaproxy.http.plugins: ip_whitelist
easyhaproxy.http.plugin.ip_whitelist.allowed_ips: 192.168.1.0/24,10.0.0.5
easyhaproxy.http.plugin.ip_whitelist.status_code: 403
```
**HAProxy config generated:**
```
# IP Whitelist - Only allow specific IPs
acl whitelisted_ip src 192.168.1.0/24 10.0.0.5
http-request deny deny_status 403 if !whitelisted_ip
```
**Important:** This blocks ALL IPs except those in the whitelist. Make sure to include your own IP!
## Configuration Methods ## Configuration Methods
Plugins can be configured using three methods. Configuration from YAML takes precedence over environment variables. Plugins can be configured using three methods, listed in order of precedence (highest to lowest):
### 1. Static YAML Configuration ### 1. Container Labels (Domain Plugins Only)
Configure plugins in `/etc/haproxy/static/config.yaml`: Enable and configure domain plugins for specific containers:
```yaml
plugins:
# Global settings
abort_on_error: false # Log and continue on plugin errors (default)
# Plugin-specific configuration
cloudflare:
enabled: true
ip_list_path: /etc/haproxy/cloudflare_ips.lst
cleanup:
enabled: true
max_idle_time: 300
deny_pages:
enabled: false # Disable globally, can still be enabled per domain
```
### 2. Environment Variables
Configure plugins via environment variables:
```bash
# Global settings
EASYHAPROXY_PLUGINS_ABORT_ON_ERROR=false
EASYHAPROXY_PLUGINS_ENABLED=cloudflare,cleanup
# Plugin-specific configuration
EASYHAPROXY_PLUGIN_CLOUDFLARE_ENABLED=true
EASYHAPROXY_PLUGIN_CLOUDFLARE_IP_LIST_PATH=/etc/haproxy/cloudflare_ips.lst
EASYHAPROXY_PLUGIN_CLEANUP_MAX_IDLE_TIME=600
```
### 3. Container Labels (Domain Plugins Only)
Enable domain plugins for specific containers:
```yaml ```yaml
services: services:
@ -148,194 +179,239 @@ services:
image: myapp:latest image: myapp:latest
labels: labels:
easyhaproxy.http.host: example.com easyhaproxy.http.host: example.com
easyhaproxy.http.port: 80
# Enable multiple plugins
easyhaproxy.http.plugins: cloudflare,deny_pages easyhaproxy.http.plugins: cloudflare,deny_pages
# Configure deny_pages plugin
easyhaproxy.http.plugin.deny_pages.paths: /admin,/api/internal easyhaproxy.http.plugin.deny_pages.paths: /admin,/api/internal
easyhaproxy.http.plugin.deny_pages.status_code: 403
```
**Label format:**
- Enable plugins: `easyhaproxy.<definition>.plugins: plugin1,plugin2`
- Configure plugin: `easyhaproxy.<definition>.plugin.<plugin_name>.<config_key>: value`
**Where `<definition>` is:** `http`, `https`, `tcp`, etc.
### 2. Static YAML Configuration
Configure plugins globally in `/etc/haproxy/static/config.yaml`:
```yaml
plugins:
# Global settings
abort_on_error: false # Log and continue on errors (recommended)
# Enable global plugins
enabled: [cleanup]
# Configure individual plugins
config:
cloudflare:
enabled: true
ip_list_path: /etc/haproxy/cloudflare_ips.lst
cleanup:
enabled: true
max_idle_time: 600
deny_pages:
enabled: false # Disable globally, enable per-container via labels
```
### 3. Environment Variables
Configure plugins via environment variables:
```bash
# Global settings
EASYHAPROXY_PLUGINS_ENABLED=cleanup
EASYHAPROXY_PLUGINS_ABORT_ON_ERROR=false
# Plugin-specific configuration
EASYHAPROXY_PLUGIN_CLEANUP_ENABLED=true
EASYHAPROXY_PLUGIN_CLEANUP_MAX_IDLE_TIME=600
EASYHAPROXY_PLUGIN_CLOUDFLARE_IP_LIST_PATH=/etc/haproxy/cloudflare_ips.lst
```
**Variable format:**
- Enable plugins: `EASYHAPROXY_PLUGINS_ENABLED=plugin1,plugin2`
- Configure plugin: `EASYHAPROXY_PLUGIN_<PLUGIN_NAME>_<CONFIG_KEY>=value`
## Common Use Cases
### Restrict Admin Panel to Office IPs
Protect admin panel by only allowing access from office network:
```yaml
labels:
easyhaproxy.http.host: admin.example.com
easyhaproxy.http.plugins: ip_whitelist
easyhaproxy.http.plugin.ip_whitelist.allowed_ips: 203.0.113.0/24,198.51.100.42
```
### Protect Admin Paths
Block access to WordPress admin and other sensitive paths:
```yaml
labels:
easyhaproxy.http.host: wordpress.example.com
easyhaproxy.http.plugins: deny_pages
easyhaproxy.http.plugin.deny_pages.paths: /wp-admin,/wp-login.php,/.env
easyhaproxy.http.plugin.deny_pages.status_code: 404
```
### Cloudflare IP Restoration
Restore original visitor IPs for applications behind Cloudflare:
```yaml
labels:
easyhaproxy.http.host: myapp.com
easyhaproxy.http.plugins: cloudflare
```
### Multiple Plugins Together
Combine multiple plugins for one domain:
```yaml
labels:
easyhaproxy.http.host: secure-app.com
easyhaproxy.http.plugins: cloudflare,deny_pages
easyhaproxy.http.plugin.deny_pages.paths: /admin,/config
easyhaproxy.http.plugin.deny_pages.status_code: 403
```
### Automatic Cleanup
Keep your system clean with automatic temp file removal:
```yaml
# /etc/haproxy/static/config.yaml
plugins:
enabled: [cleanup]
config:
cleanup:
enabled: true
max_idle_time: 3600 # 1 hour
``` ```
## Error Handling ## Error Handling
### Log and Continue (Default) ### Log and Continue (Recommended)
By default, plugin errors are logged as warnings and the discovery cycle continues: By default, plugin errors are logged as warnings and discovery continues:
```yaml ```yaml
plugins: plugins:
abort_on_error: false # Default abort_on_error: false # Default
``` ```
This ensures that a failing plugin doesn't prevent HAProxy configuration updates. **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
For critical plugins, you can stop the discovery cycle on errors: Stop discovery cycle if any plugin fails:
```yaml ```yaml
plugins: plugins:
abort_on_error: true abort_on_error: true
``` ```
With this setting, any plugin error will halt configuration generation and the previous HAProxy config remains active until the issue is resolved. **When to use:** Critical plugins where failure should halt deployment.
## Custom Plugins **Behavior:**
- Plugin error stops discovery
- Previous HAProxy config remains active
- No configuration changes until issue is resolved
You can create custom plugins by placing Python files in `/etc/haproxy/plugins/`. ## Troubleshooting
### Simple Custom Plugin Example ### Enable Debug Logging
Create `/etc/haproxy/plugins/custom_header.py`: See detailed plugin execution information:
```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 CustomHeaderPlugin(PluginInterface):
def __init__(self):
self.enabled = True
self.header_name = "X-Custom-App"
self.header_value = "EasyHAProxy"
@property
def name(self) -> str:
return "custom_header"
@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 "header_name" in config:
self.header_name = config["header_name"]
if "header_value" in config:
self.header_value = config["header_value"]
def process(self, context: PluginContext) -> PluginResult:
if not self.enabled:
return PluginResult()
haproxy_config = f'http-request set-header {self.header_name} "{self.header_value}"'
return PluginResult(
haproxy_config=haproxy_config,
metadata={"domain": context.domain}
)
```
### Enable Your Custom Plugin
**YAML:**
```yaml
plugins:
custom_header:
enabled: true
header_name: X-My-Header
header_value: MyValue
```
**Container Label:**
```yaml
labels:
easyhaproxy.http.plugins: custom_header
easyhaproxy.http.plugin.custom_header.header_value: CustomValue
```
## Plugin Development
For detailed information on developing plugins, see the [Plugin Developer Guide](plugin-development.md).
Key points:
- Plugins must inherit from `PluginInterface`
- Implement required methods: `name`, `plugin_type`, `configure`, `process`
- Return `PluginResult` with HAProxy config snippets
- Use `loggerEasyHaproxy` for logging
- Handle errors gracefully
## Use Cases
### 1. Cloudflare IP Restoration
Restore original visitor IPs when using Cloudflare:
```yaml
plugins:
cloudflare:
enabled: true
```
Requires Cloudflare IP list at `/etc/haproxy/cloudflare_ips.lst`. See [Cloudflare documentation](https://support.cloudflare.com/hc/en-us/articles/200170786-Restoring-original-visitor-IPs).
### 2. Path Protection
Block sensitive paths from public access:
```yaml
labels:
easyhaproxy.http.host: myapp.com
easyhaproxy.http.plugins: deny_pages
easyhaproxy.http.plugin.deny_pages.paths: /admin,/config,/debug
```
### 3. Automatic Cleanup
Keep your system clean:
```yaml
plugins:
cleanup:
enabled: true
max_idle_time: 3600 # 1 hour
```
### 4. Custom Authentication
Create a plugin to add basic auth:
```python
def process(self, context: PluginContext) -> PluginResult:
return PluginResult(
haproxy_config="""http-request auth realm MyApp unless { http_auth(user_list) }"""
)
```
## Debugging
Enable debug logging to see plugin execution:
```bash ```bash
EASYHAPROXY_LOG_LEVEL=DEBUG EASYHAPROXY_LOG_LEVEL=DEBUG
``` ```
Look for log messages: **Look for:**
``` ```
INFO: Loaded builtin plugin: cloudflare (domain) INFO: Loaded builtin plugin: cloudflare (domain)
INFO: Loaded builtin plugin: cleanup (global)
DEBUG: Executing domain plugin: cloudflare for domain: example.com DEBUG: Executing domain plugin: cloudflare for domain: example.com
DEBUG: Plugin cloudflare metadata: {'domain': 'example.com'} DEBUG: Plugin cloudflare metadata: {'domain': 'example.com', 'ip_list_path': '/etc/haproxy/cloudflare_ips.lst'}
``` ```
### Plugin Not Loading
**Check:**
1. Plugin file exists in `/etc/haproxy/plugins/` or builtin directory
2. Python syntax is valid
3. Plugin class inherits from `PluginInterface`
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
**Check precedence order:**
1. Container labels (highest)
2. YAML configuration
3. Environment variables (lowest)
Container labels override YAML and env vars.
### 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. **Test plugins independently** before deploying to production 1. **Start with log-and-continue mode** - Use `abort_on_error: false` until you're confident plugins are stable
2. **Use log-and-continue mode** unless a plugin is critical 2. **Use container labels for domain-specific config** - Easier to manage per-service
3. **Keep plugin logic simple** - one plugin should do one thing well 3. **Use YAML/env for global config** - Better for global plugins and defaults
4. **Document configuration options** in plugin docstrings 4. **Enable debug logging during testing** - Helps identify configuration issues
5. **Handle errors gracefully** - return empty PluginResult on errors 5. **Test plugin changes in staging first** - Avoid production surprises
6. **Use sensible defaults** so plugins work out of the box 6. **Keep plugin configurations simple** - Use defaults when possible
## Limitations ## Limitations
- Plugins are Python-only (no shell scripts) - Plugins must be written in Python
- Plugins cannot modify the HAProxy template structure - Domain plugins execute for each domain, so keep them lightweight
- Domain plugins are executed for each domain, so keep them lightweight - Plugins cannot modify the Jinja2 template structure directly
- Plugin errors in abort mode will halt configuration updates - Plugin errors in abort mode prevent all configuration updates
## Creating Custom Plugins
To create your own plugins, see the [Plugin Developer Guide](plugin-development.md).
## Further Reading ## Further Reading
- [Plugin Developer Guide](plugin-development.md) - [Plugin Developer Guide](plugin-development.md) - Create custom plugins
- [Container Labels](container-labels.md) - [Container Labels](container-labels.md) - Label configuration reference
- [Environment Variables](environment-variable.md) - [Environment Variables](environment-variable.md) - Environment variable reference
- [Static Configuration](static.md) - [Static Configuration](static.md) - YAML configuration reference

View file

@ -0,0 +1,107 @@
"""
IP Whitelist Plugin for EasyHAProxy
This plugin restricts access to a domain to only specific IP addresses or CIDR ranges.
It runs as a DOMAIN plugin (once per domain).
Configuration:
- enabled: Enable/disable the plugin (default: true)
- allowed_ips: Comma-separated list of IPs/CIDR ranges to allow
- status_code: HTTP status code to return for blocked IPs (default: 403)
Example YAML config:
plugins:
ip_whitelist:
enabled: true
allowed_ips: "192.168.1.0/24,10.0.0.1,172.16.0.0/16"
status_code: 403
Example Container Label:
easyhaproxy.http.plugins: "ip_whitelist"
easyhaproxy.http.plugin.ip_whitelist.allowed_ips: "192.168.1.0/24,10.0.0.1"
easyhaproxy.http.plugin.ip_whitelist.status_code: 403
HAProxy Config Generated:
# IP Whitelist - Only allow specific IPs
acl whitelisted_ip src 192.168.1.0/24 10.0.0.1
http-request deny deny_status 403 if !whitelisted_ip
"""
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
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:
"""
Configure the plugin
Args:
config: Dictionary with configuration options
- enabled: Whether plugin is enabled
- allowed_ips: Comma-separated list of IPs/CIDR ranges
- status_code: HTTP status code to return for denied requests
"""
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:
"""
Generate HAProxy config to whitelist specific IPs
Args:
context: Plugin execution context with domain information
Returns:
PluginResult with HAProxy configuration snippet
"""
if not self.enabled or not self.allowed_ips:
return PluginResult()
# Create space-separated list of IPs for ACL
ips_str = " ".join(self.allowed_ips)
# Generate HAProxy config snippet
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,
modified_easymapping=None,
metadata={
"domain": context.domain,
"allowed_ips": self.allowed_ips,
"status_code": self.status_code
}
)

View file

@ -0,0 +1,10 @@
{
"192.168.1.40": {
"easyhaproxy.http.host": "secure.example.com",
"easyhaproxy.http.port": "80",
"easyhaproxy.http.localport": "8080",
"easyhaproxy.http.plugins": "ip_whitelist",
"easyhaproxy.http.plugin.ip_whitelist.allowed_ips": "192.168.1.0/24,10.0.0.5",
"easyhaproxy.http.plugin.ip_whitelist.status_code": "403"
}
}

View file

@ -20,6 +20,7 @@ from plugins import PluginManager, PluginContext
from plugins.builtin.cloudflare import CloudflarePlugin from plugins.builtin.cloudflare import CloudflarePlugin
from plugins.builtin.cleanup import CleanupPlugin from plugins.builtin.cleanup import CleanupPlugin
from plugins.builtin.deny_pages import DenyPagesPlugin from plugins.builtin.deny_pages import DenyPagesPlugin
from plugins.builtin.ip_whitelist import IpWhitelistPlugin
import easymapping import easymapping
@ -320,6 +321,110 @@ class TestDenyPagesPlugin:
assert "http-request deny deny_status 404 if denied_path" in haproxy_config assert "http-request deny deny_status 404 if denied_path" in haproxy_config
class TestIpWhitelistPlugin:
"""Test cases for IpWhitelistPlugin (DOMAIN plugin)"""
def test_ip_whitelist_plugin_initialization(self):
"""Test plugin initializes with correct defaults"""
plugin = IpWhitelistPlugin()
assert plugin.name == "ip_whitelist"
assert plugin.enabled is True
assert plugin.allowed_ips == []
assert plugin.status_code == 403
def test_ip_whitelist_plugin_configuration(self):
"""Test plugin configuration"""
plugin = IpWhitelistPlugin()
# Test allowed IPs
plugin.configure({"allowed_ips": "192.168.1.0/24,10.0.0.1,172.16.0.0/16"})
assert plugin.allowed_ips == ["192.168.1.0/24", "10.0.0.1", "172.16.0.0/16"]
# Test status code
plugin.configure({"status_code": "404"})
assert plugin.status_code == 404
# Test enabled
plugin.configure({"enabled": "false"})
assert plugin.enabled is False
def test_ip_whitelist_plugin_generates_config(self):
"""Test plugin generates correct HAProxy config"""
plugin = IpWhitelistPlugin()
plugin.configure({
"allowed_ips": "192.168.1.0/24,10.0.0.1",
"status_code": "403"
})
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 "IP Whitelist" in result.haproxy_config
assert "acl whitelisted_ip src 192.168.1.0/24 10.0.0.1" in result.haproxy_config
assert "http-request deny deny_status 403 if !whitelisted_ip" in result.haproxy_config
assert result.metadata["domain"] == "example.com"
assert result.metadata["allowed_ips"] == ["192.168.1.0/24", "10.0.0.1"]
assert result.metadata["status_code"] == 403
def test_ip_whitelist_plugin_disabled(self):
"""Test plugin returns empty config when disabled"""
plugin = IpWhitelistPlugin()
plugin.configure({"enabled": "false", "allowed_ips": "192.168.1.0/24"})
context = PluginContext(
parsed_object={},
easymapping=[],
container_env={},
domain="example.com"
)
result = plugin.process(context)
assert result.haproxy_config == ""
assert result.metadata == {}
def test_ip_whitelist_plugin_no_ips(self):
"""Test plugin returns empty config when no IPs configured"""
plugin = IpWhitelistPlugin()
context = PluginContext(
parsed_object={},
easymapping=[],
container_env={},
domain="example.com"
)
result = plugin.process(context)
assert result.haproxy_config == ""
def test_ip_whitelist_plugin_in_haproxy_config(self):
"""Test IP Whitelist plugin integration in full HAProxy config generation"""
# Use fixture with ip_whitelist plugin enabled via labels
line_list = load_fixture("services-with-ip-whitelist")
result = {
"customerrors": False,
"certbot": {"email": "test@example.com"},
"stats": {"port": 0}
}
cfg = easymapping.HaproxyConfigGenerator(result)
haproxy_config = cfg.generate(line_list)
# Verify IP Whitelist config is in the output
assert "IP Whitelist - Only allow specific IPs" in haproxy_config
assert "acl whitelisted_ip src 192.168.1.0/24 10.0.0.5" in haproxy_config
assert "http-request deny deny_status 403 if !whitelisted_ip" in haproxy_config
class TestPluginManager: class TestPluginManager:
"""Test cases for PluginManager""" """Test cases for PluginManager"""
@ -332,15 +437,17 @@ class TestPluginManager:
assert "cloudflare" in manager.plugins assert "cloudflare" in manager.plugins
assert "cleanup" in manager.plugins assert "cleanup" in manager.plugins
assert "deny_pages" in manager.plugins assert "deny_pages" in manager.plugins
assert "ip_whitelist" in manager.plugins
# Verify plugin types # Verify plugin types
assert len(manager.global_plugins) == 1 # cleanup assert len(manager.global_plugins) == 1 # cleanup
assert len(manager.domain_plugins) == 2 # cloudflare, deny_pages assert len(manager.domain_plugins) == 3 # cloudflare, deny_pages, ip_whitelist
# Verify plugin instances # Verify plugin instances
assert manager.plugins["cloudflare"].name == "cloudflare" assert manager.plugins["cloudflare"].name == "cloudflare"
assert manager.plugins["cleanup"].name == "cleanup" assert manager.plugins["cleanup"].name == "cleanup"
assert manager.plugins["deny_pages"].name == "deny_pages" assert manager.plugins["deny_pages"].name == "deny_pages"
assert manager.plugins["ip_whitelist"].name == "ip_whitelist"
def test_plugin_manager_executes_global_plugins(self): def test_plugin_manager_executes_global_plugins(self):
"""Test plugin manager executes global plugins correctly""" """Test plugin manager executes global plugins correctly"""