8.6 KiB
Executable File
Plugin Security Guide
This guide covers security configuration and deployment patterns for Charon's plugin system. For general plugin installation and usage, see Custom Plugins.
Overview
Charon supports external DNS provider plugins via Go's plugin system. Because plugins execute in-process with full memory access, they must be treated as trusted code. This guide explains how to:
- Configure signature-based allowlisting
- Deploy plugins securely in containers
- Mitigate common attack vectors
Plugin Signature Allowlisting
Charon supports SHA-256 signature verification to ensure only approved plugins are loaded.
Environment Variable
CHARON_PLUGIN_SIGNATURES='{"pluginname": "sha256:..."}'
Key format: Plugin name without the .so extension.
Behavior Matrix
CHARON_PLUGIN_SIGNATURES Value |
Behavior |
|---|---|
Unset or empty ("") |
Permissive mode — All plugins are loaded (backward compatible) |
Set to {} |
Strict block-all — No external plugins are loaded |
| Set with entries | Allowlist mode — Only listed plugins with matching signatures are loaded |
Examples
Permissive mode (default):
# Unset — all plugins load without verification
unset CHARON_PLUGIN_SIGNATURES
Strict block-all:
# Empty object — no external plugins will load
export CHARON_PLUGIN_SIGNATURES='{}'
Allowlist specific plugins:
# Only powerdns and custom-provider plugins are allowed
export CHARON_PLUGIN_SIGNATURES='{"powerdns": "sha256:a1b2c3d4...", "custom-provider": "sha256:e5f6g7h8..."}'
Generating Plugin Signatures
To add a plugin to your allowlist, compute its SHA-256 signature:
sha256sum myplugin.so | awk '{print "sha256:" $1}'
Example output:
sha256:a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
Use this value in your CHARON_PLUGIN_SIGNATURES JSON:
export CHARON_PLUGIN_SIGNATURES='{"myplugin": "sha256:a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2"}'
⚠️ Important: The key is the plugin name without
.so. Usemyplugin, notmyplugin.so.
Container Deployment Recommendations
Read-Only Plugin Mount (Critical)
Always mount the plugin directory as read-only in production:
# docker-compose.yml
services:
charon:
image: charon:latest
volumes:
- ./plugins:/app/plugins:ro # Read-only mount
environment:
- CHARON_PLUGINS_DIR=/app/plugins
- CHARON_PLUGIN_SIGNATURES={"powerdns": "sha256:..."}
This prevents runtime modification of plugin files, mitigating:
- Time-of-check to time-of-use (TOCTOU) attacks
- Malicious plugin replacement after signature verification
Non-Root Execution
Run Charon as a non-root user:
# docker-compose.yml
services:
charon:
image: charon:latest
user: "1000:1000" # Non-root user
# ...
Or in Dockerfile:
FROM charon:latest
USER charon
Directory Permissions
Plugin directories must not be world-writable. Charon enforces this at startup.
| Permission | Result |
|---|---|
0755 or stricter |
✅ Allowed |
0777 (world-writable) |
❌ Rejected — plugin loading disabled |
Set secure permissions:
chmod 755 /path/to/plugins
chmod 644 /path/to/plugins/*.so # Or 755 for executable
Complete Secure Deployment Example
# docker-compose.production.yml
services:
charon:
image: charon:latest
user: "1000:1000"
read_only: true
security_opt:
- no-new-privileges:true
volumes:
- ./plugins:/app/plugins:ro
- ./data:/app/data
environment:
- CHARON_PLUGINS_DIR=/app/plugins
- CHARON_PLUGIN_SIGNATURES={"powerdns": "sha256:abc123..."}
tmpfs:
- /tmp
TOCTOU Mitigation
Time-of-check to time-of-use (TOCTOU) vulnerabilities occur when a file is modified between signature verification and loading. Mitigate with:
1. Read-Only Mounts (Primary Defense)
Mount the plugin directory as read-only (:ro). This prevents modification after startup.
2. Atomic File Replacement for Updates
When updating plugins, use atomic operations to avoid partial writes:
# 1. Copy new plugin to temporary location
cp new_plugin.so /tmp/plugin.so.new
# 2. Atomically replace the old plugin
mv /tmp/plugin.so.new /app/plugins/plugin.so
# 3. Restart Charon to reload plugins
docker compose restart charon
⚠️ Warning:
cpfollowed by direct write to the plugin directory is not atomic and creates a window for exploitation.
3. Signature Re-Verification on Reload
After updating plugins, always update your CHARON_PLUGIN_SIGNATURES with the new hash before restarting.
Troubleshooting
Checking if a Plugin Loaded
Check startup logs:
docker compose logs charon | grep -i plugin
Expected success output:
INFO Loaded DNS provider plugin type=powerdns name="PowerDNS" version="1.0.0"
INFO Loaded 1 external DNS provider plugins (0 failed)
If using allowlist:
INFO Plugin signature allowlist enabled with 2 entries
Via API:
curl http://localhost:8080/api/admin/plugins \
-H "Authorization: Bearer YOUR-TOKEN"
Common Error Messages
plugin not in allowlist
Cause: The plugin filename (without .so) is not in CHARON_PLUGIN_SIGNATURES.
Solution: Add the plugin to your allowlist:
# Get the signature
sha256sum powerdns.so | awk '{print "sha256:" $1}'
# Add to environment
export CHARON_PLUGIN_SIGNATURES='{"powerdns": "sha256:YOUR_HASH_HERE"}'
signature mismatch for plugin
Cause: The plugin file's SHA-256 hash doesn't match the allowlist.
Solution:
- Verify you have the correct plugin file
- Re-compute the signature:
sha256sum plugin.so - Update
CHARON_PLUGIN_SIGNATURESwith the correct hash
plugin directory has insecure permissions
Cause: The plugin directory is world-writable (mode 0777 or similar).
Solution:
chmod 755 /path/to/plugins
chmod 644 /path/to/plugins/*.so
invalid CHARON_PLUGIN_SIGNATURES JSON
Cause: Malformed JSON in the environment variable.
Solution: Validate your JSON:
echo '{"powerdns": "sha256:abc123"}' | jq .
Common issues:
- Missing quotes around keys or values
- Trailing commas
- Single quotes instead of double quotes
Permission denied when loading plugin
Cause: File permissions too restrictive or ownership mismatch.
Solution:
# Check current permissions
ls -la /path/to/plugins/
# Fix permissions
chmod 644 /path/to/plugins/*.so
chown charon:charon /path/to/plugins/*.so
Debugging Checklist
-
Is the plugin directory configured?
echo $CHARON_PLUGINS_DIR -
Does the plugin file exist?
ls -la $CHARON_PLUGINS_DIR/*.so -
Are directory permissions secure?
stat -c "%a %n" $CHARON_PLUGINS_DIR # Should be 755 or stricter -
Is the signature correct?
sha256sum $CHARON_PLUGINS_DIR/myplugin.so -
Is the JSON valid?
echo "$CHARON_PLUGIN_SIGNATURES" | jq .
Security Implications
What Plugins Can Access
Plugins run in-process with Charon and have access to:
| Resource | Access Level |
|---|---|
| System memory | Full read/write |
| Database credentials | Full access |
| API tokens and secrets | Full access |
| File system | Charon's permissions |
| Network | Unrestricted outbound |
Risk Assessment
| Risk | Mitigation |
|---|---|
| Malicious plugin code | Signature allowlisting, code review |
| Plugin replacement attack | Read-only mounts, atomic updates |
| World-writable directory | Automatic permission verification |
| Supply chain compromise | Verify plugin source, pin signatures |
Best Practices Summary
- ✅ Enable signature allowlisting in production
- ✅ Mount plugin directory read-only (
:ro) - ✅ Run as non-root user
- ✅ Use strict directory permissions (
0755or stricter) - ✅ Verify plugin source before deployment
- ✅ Update signatures after plugin updates
- ❌ Never use permissive mode in production
- ❌ Never install plugins from untrusted sources
See Also
- Custom Plugins — Plugin installation and usage
- Security Policy — Security reporting and policies
- Plugin Development Guide — Building custom plugins