Files
Charon/docs/features/plugin-security.md
akanealw eec8c28fb3
Some checks are pending
Go Benchmark / Performance Regression Check (push) Waiting to run
Cerberus Integration / Cerberus Security Stack Integration (push) Waiting to run
Upload Coverage to Codecov / Backend Codecov Upload (push) Waiting to run
Upload Coverage to Codecov / Frontend Codecov Upload (push) Waiting to run
CodeQL - Analyze / CodeQL analysis (go) (push) Waiting to run
CodeQL - Analyze / CodeQL analysis (javascript-typescript) (push) Waiting to run
CrowdSec Integration / CrowdSec Bouncer Integration (push) Waiting to run
Docker Build, Publish & Test / build-and-push (push) Waiting to run
Docker Build, Publish & Test / Security Scan PR Image (push) Blocked by required conditions
Quality Checks / Auth Route Protection Contract (push) Waiting to run
Quality Checks / Codecov Trigger/Comment Parity Guard (push) Waiting to run
Quality Checks / Backend (Go) (push) Waiting to run
Quality Checks / Frontend (React) (push) Waiting to run
Rate Limit integration / Rate Limiting Integration (push) Waiting to run
Security Scan (PR) / Trivy Binary Scan (push) Waiting to run
Supply Chain Verification (PR) / Verify Supply Chain (push) Waiting to run
WAF integration / Coraza WAF Integration (push) Waiting to run
changed perms
2026-04-22 18:19:14 +00:00

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. Use myplugin, not myplugin.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: cp followed 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:

  1. Verify you have the correct plugin file
  2. Re-compute the signature: sha256sum plugin.so
  3. Update CHARON_PLUGIN_SIGNATURES with 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

  1. Is the plugin directory configured?

    echo $CHARON_PLUGINS_DIR
    
  2. Does the plugin file exist?

    ls -la $CHARON_PLUGINS_DIR/*.so
    
  3. Are directory permissions secure?

    stat -c "%a %n" $CHARON_PLUGINS_DIR
    # Should be 755 or stricter
    
  4. Is the signature correct?

    sha256sum $CHARON_PLUGINS_DIR/myplugin.so
    
  5. 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

  1. Enable signature allowlisting in production
  2. Mount plugin directory read-only (:ro)
  3. Run as non-root user
  4. Use strict directory permissions (0755 or stricter)
  5. Verify plugin source before deployment
  6. Update signatures after plugin updates
  7. Never use permissive mode in production
  8. Never install plugins from untrusted sources

See Also