Linux Capabilities Setup for NFTables.Port
View SourceOverview
NFTables.Port requires the CAP_NET_ADMIN Linux capability to perform netlink operations that communicate with the kernel's nftables subsystem. This document explains what capabilities are, why they're needed, and how to configure them for different deployment scenarios.
What are Linux Capabilities?
Linux capabilities divide the privileges traditionally associated with root into distinct units. Instead of running as root (which grants all privileges), a process can be granted only the specific capabilities it needs. This follows the principle of least privilege and reduces security risks.
Why NFTables.Port Needs CAP_NET_ADMIN
The CAP_NET_ADMIN capability allows a process to:
- Open
NETLINK_NETFILTERsockets - Send nftables configuration commands to the kernel
- Receive responses from the kernel's nftables subsystem
Without this capability, operations like creating tables, chains, and rules will fail with EPERM (Operation not permitted).
NFTables.Port Security Model
When the NFTables.Port port process starts, it:
- Drops all capabilities except CAP_NET_ADMIN (principle of least privilege)
- Sets PR_SET_NO_NEW_PRIVS to prevent gaining additional privileges
- Sets PR_SET_DUMPABLE=0 to prevent debugging/core dumps
- Drops CAP_NET_ADMIN on shutdown for clean exit
This ensures the process runs with the absolute minimum privileges needed.
Quick Start (Development)
The fastest way to get NFTables.Port working with kernel operations:
# Compile the project
mix compile
# Set capabilities on the binary you're using
# For the recommended main port:
sudo setcap cap_net_admin+ep priv/port_nftables
# Or for other ports:
sudo setcap cap_net_admin+ep priv/port_nftables # JSON strings only
sudo setcap cap_net_admin+ep priv/port_nftables # Elixir terms only
sudo setcap cap_net_admin+ep priv/port_nftables # Legacy libnftables
# Verify capabilities are set
getcap priv/port_nftables
# Should show: priv/port_nftables cap_net_admin=ep
# Run your application (no sudo needed!)
iex -S mix
Note: You'll need to re-run setcap after each recompilation, as the build process creates new binaries.
Setup Methods
Method 1: File Capabilities (Recommended for Production)
File capabilities are stored in the binary's extended attributes and are the most secure approach for production deployments.
Setup
# After compilation, set capabilities on the binary
sudo setcap cap_net_admin+ep /path/to/nftables/priv/port_nftables
# Verify it worked
getcap /path/to/nftables/priv/port_nftables
# Output: /path/to/nftables/priv/port_nftables cap_net_admin=ep
Capability Flags Explained
cap_net_admin- The capability being granted+ep- The flags:e(effective) - Capability is active immediatelyp(permitted) - Process has permission to use this capability
Advantages
- ✅ Process doesn't need to run as root
- ✅ Most secure - only one specific capability granted
- ✅ Transparent to the application
- ✅ Standard Linux security mechanism
Disadvantages
- ❌ Must be reapplied after each build
- ❌ Requires sudo to set up
- ❌ Some filesystems don't support extended attributes (e.g., NFS, some Docker volumes)
Automation
You can automate this in your deployment scripts:
#!/bin/bash
# deploy.sh
# Build the release
MIX_ENV=prod mix release
# Set capabilities
sudo setcap cap_net_admin+ep _build/prod/rel/my_app/lib/nftables-0.1.0/priv/port_nftables
# Verify
getcap _build/prod/rel/my_app/lib/nftables-0.1.0/priv/port_nftables || {
echo "Failed to set capabilities!"
exit 1
}
echo "Capabilities set successfully"
Method 2: Running with Sudo (Development/Testing)
For development and testing, you can run the entire Elixir application with sudo.
sudo iex -S mix
Advantages
- ✅ Simple - no capability configuration needed
- ✅ Works immediately
- ✅ Good for quick testing
Disadvantages
- ❌ Entire Elixir VM runs as root (security risk)
- ❌ All capabilities available (not least privilege)
- ❌ Not suitable for production
- ❌ Can cause file permission issues
Method 3: Ambient Capabilities (Advanced)
Ambient capabilities are inherited by child processes. This allows the Elixir VM to pass CAP_NET_ADMIN to the port process.
# Set ambient capability and run
sudo capsh --caps="cap_net_admin+eip cap_setpcap,cap_setuid,cap_setgid+ep" \
--keep=1 --user=$USER --addamb=cap_net_admin -- \
-c "iex -S mix"
Advantages
- ✅ Doesn't require setcap on binary
- ✅ Runs as regular user (not root)
- ✅ Capability inherited by children
Disadvantages
- ❌ Complex command line
- ❌ Less portable
- ❌ Harder to automate
Method 4: Running Without Capabilities (Limited Functionality)
NFTables.Port can run without CAP_NET_ADMIN, but with reduced functionality.
# Just run normally
iex -S mix
What Works
- ✅ All resource allocation operations (table_alloc, chain_alloc, etc.)
- ✅ Setting attributes on resources
- ✅ Building batches
- ✅ Tests pass (they don't require kernel operations)
What Doesn't Work
- ❌ Opening netlink sockets
- ❌ Sending batches to the kernel
- ❌ Any actual kernel nftables operations
You'll see this warning in the logs:
warning: Failed to apply capabilities to process (continuing without CAP_NET_ADMIN)
warning: Note: Netlink operations requiring CAP_NET_ADMIN will failAnd operations will fail with:
{:error, "Permission denied (EACCES)"}Verification
Check if Capabilities are Set on Binary
getcap priv/port_nftables
Expected output:
priv/port_nftables cap_net_admin=epIf not set:
# (empty output or "No capabilities")Check if Process Has the Capability
While NFTables.Port is running:
# Find the process ID
ps aux | grep port_nftables
# Check its capabilities
grep Cap /proc/<PID>/status
# Or use getpcaps
getpcaps <PID>
Test Netlink Operations
From IEx:
{:ok, pid} = NFTables.Port.start_link()
# Try to open a netlink socket
case NFTables.Port.Kernel.Netlink.socket_open(pid) do
{:ok, socket_id} ->
IO.puts("✓ CAP_NET_ADMIN is working!")
NFTables.Port.Kernel.Netlink.socket_close(pid, socket_id)
{:error, reason} ->
IO.puts("✗ Failed: #{reason}")
IO.puts(" (Likely missing CAP_NET_ADMIN)")
endTroubleshooting
Error: "Operation not permitted (EPERM)"
Symptom: Netlink operations fail with EPERM error.
Cause: Process doesn't have CAP_NET_ADMIN.
Solutions:
- Check if capabilities are set on binary:
getcap priv/port_nftables - If not set, run:
sudo setcap cap_net_admin+ep priv/port_nftables - If you just recompiled, capabilities were removed - set them again
Check filesystem supports extended attributes:
mount | grep "$(df priv/port_nftables | tail -1 | awk '{print $1}')"
Error: "Failed to set capabilities on process"
Symptom: Zig process logs error during startup.
Cause: Process tried to set capabilities it doesn't have.
Solutions:
- Set file capabilities as shown above
- Run with sudo (development only)
- Or accept limited functionality without kernel operations
Capabilities Lost After Recompilation
Symptom: It worked before, now fails after mix compile.
Cause: Compilation creates a new binary, losing file capabilities.
Solution: Re-run setcap after each build:
# Add to your workflow
mix compile && sudo setcap cap_net_admin+ep priv/port_nftables
Capabilities Not Supported on Filesystem
Symptom: setcap fails or capabilities aren't persisted.
Cause: Filesystem doesn't support extended attributes (NFS, some Docker volumes, etc.).
Solutions:
- Use a local filesystem that supports extended attributes (ext4, xfs, btrfs)
- In Docker, use a bind mount from host filesystem
- Use ambient capabilities method instead
- Or run with sudo (not recommended for production)
Running in Docker
Special considerations for Docker:
# Dockerfile
FROM elixir:1.19
# Install libcap tools
RUN apt-get update && apt-get install -y libcap2-bin
# Copy and build your app
WORKDIR /app
COPY . .
RUN mix deps.get && mix compile
# Set capabilities (requires --cap-add=SETFCAP when running)
RUN setcap cap_net_admin+ep priv/port_nftables
# Run as non-root user
USER nobody
CMD ["mix", "run", "--no-halt"]Run the container with:
docker run --cap-add=NET_ADMIN --cap-add=SETFCAP my-nftables_port-app
Production Deployment
Systemd Service
Example systemd unit file:
[Unit]
Description=NFTables.Port Application
After=network.target
[Service]
Type=simple
User=nftables_port
Group=nftables_port
WorkingDirectory=/opt/nftables_port
Environment="MIX_ENV=prod"
# Set capabilities before starting
ExecStartPre=/usr/sbin/setcap cap_net_admin+ep /opt/nftables_port/priv/port_nftables
# Start the application
ExecStart=/opt/nftables_port/bin/my_app start
# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/nftables_port
[Install]
WantedBy=multi-user.targetElixir Release
When building an Elixir release:
# Build release
MIX_ENV=prod mix release
# Set capabilities on the port binary in the release
sudo setcap cap_net_admin+ep _build/prod/rel/my_app/lib/nftables-0.1.0/priv/port_nftables
# The release can now run as a regular user
_build/prod/rel/my_app/bin/my_app start
Security Checklist
- [ ] Use file capabilities, not sudo
- [ ] Run application as non-root user
- [ ] Set only CAP_NET_ADMIN, no other capabilities
- [ ] Use systemd security features (NoNewPrivileges, PrivateTmp, etc.)
- [ ] Monitor logs for capability warnings
- [ ] Keep libcap and kernel up to date
- [ ] Regularly audit capability grants
Additional Resources
Summary
For Development:
mix compile
sudo setcap cap_net_admin+ep priv/port_nftables
iex -S mix
For Production:
- Build release
- Set file capabilities on
priv/port_nftables - Run as non-root user with systemd
- Monitor for permission errors
Without Capabilities:
- Application runs but kernel operations fail
- Useful for testing non-kernel functionality
- Tests will pass