How to Assign Static Client IPs in OpenVPN When Using Shared Certificates (Duplicate-CN Setup)


3 views

In industrial IoT deployments with OpenWRT devices, we often face a scenario where hundreds of devices share the same OpenVPN certificate (with duplicate-cn enabled) for simplified management. This creates a roadblock when trying to assign static IPs through traditional client-config-dir methods.

First, configure your OpenVPN server to use a pool with sequential IPs:

server 172.16.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
client-config-dir ccd
duplicate-cn

We'll use the --ipaddr hack in the client's custom up script. Create /etc/openvpn/client-ip.sh:

#!/bin/sh
# Extract device ID from hostname (e.g., "sensor-006" → 6)
DEVICE_ID=$(hostname | grep -o '[0-9]\+$')
REQUESTED_IP="172.16.0.$DEVICE_ID"

# Create temp config
echo "ifconfig-push $REQUESTED_IP 255.255.255.0" > /tmp/vpn_ip_$USER.ovpn

# Connect with custom config
exec /usr/sbin/openvpn --script-security 2 \
--config "$1" \
--ipaddr "$REQUESTED_IP" \
--up /usr/local/bin/ip-enforcer

Create /usr/local/bin/ip-enforcer:

#!/bin/bash
ping -c 1 -W 2 $4 &>/dev/null
if [ $? -eq 0 ]; then
  logger "OpenVPN IP conflict detected for $4"
  exit 1
fi
exit 0

For LuCI-based configuration, modify your VPN interface:

config interface 'vpn'
  option proto 'openvpn'
  option config '/etc/openvpn/client.conf'
  option up_script '/etc/openvpn/client-ip.sh'
  option enabled '1'

Add this to your OpenVPN server's client-connect script to validate IP requests:

#!/bin/bash
REQUESTED_IP=$(env | grep '^ipaddr=' | cut -d= -f2)

# Validate IP format and range
if [[ $REQUESTED_IP =~ ^172\.16\.0\.([0-9]{1,3})$ ]]; then
  DEVICE_ID=${BASH_REMATCH[1]}
  if [ $DEVICE_ID -lt 1 ] || [ $DEVICE_ID -gt 254 ]; then
    echo "Invalid device ID range" >&2
    exit 1
  fi
else
  echo "Invalid IP format" >&2
  exit 1
fi

When managing multiple IoT devices (like OpenWRT routers) in a VPN network, organizations often prefer using a single client certificate with duplicate-cn enabled for simplified certificate management. However, this creates a roadblock for assigning static IPs through traditional methods like client-config-dir and ifconfig-push.

In industrial IoT deployments, maintaining consistent IP-to-device mapping (where device ID matches last IP octet) is crucial for:

  • Predictable network troubleshooting
  • Automated configuration management
  • Device-specific firewall rules

While clients can't directly request IPs in OpenVPN, we can implement a workaround using client-specific configuration files with unique common names:

# /etc/openvpn/server.conf
client-config-dir /etc/openvpn/ccd
duplicate-cn

Then create CCD files named after your device IDs:

# /etc/openvpn/ccd/device6
ifconfig-push 172.16.0.6 172.16.0.1

For dynamic environments, use this bash script to generate CCD files:

#!/bin/bash
BASE_IP="172.16.0"
for i in {1..50}; do
  echo "ifconfig-push ${BASE_IP}.${i} ${BASE_IP}.1" > "/etc/openvpn/ccd/device$i"
done

On OpenWRT devices, modify the client config to include device identification:

# /etc/config/openvpn
config openvpn 'client'
    option config '/etc/openvpn/client.conf'
    option enabled '1'
    option device_id '6'  # Matches CCD filename

Check assigned IPs with:

openvpn --status /var/run/openvpn.status 10
cat /var/run/openvpn.status

Common issues to watch for:

  • CCD file permissions (must be readable by OpenVPN user)
  • IP address conflicts in the VPN subnet
  • Missing duplicate-cn directive in server config

For more dynamic assignments, implement a client-connect script that:

#!/bin/bash
# /etc/openvpn/client-connect.sh
DEVICE_ID=$(echo $common_name | sed 's/device//')
echo "ifconfig-push 172.16.0.$DEVICE_ID 172.16.0.1" > $1
exit 0

Configure in server.conf:

script-security 2
client-connect /etc/openvpn/client-connect.sh