Sign inSign up

richard52ru/openconnect-gateway-mikrotik

By richard52ru

•Updated 2 months ago

Mikrotik container with ovpn-client,ssh and socks proxy

Image
0

906

richard52ru/openconnect-gateway-mikrotik repository overview

⁠OpenConnect VPN Gateway for MikroTik Containers (ARM32/ARM64)

A lightweight, production-ready Alpine-based container tailored specifically for MikroTik RouterOS v7+ (Container feature). It acts as an automated L3 network gateway and proxy server, encapsulating incoming traffic into an OpenConnect (AnyConnect) VPN tunnel with built-in auto-reconnection and smart routing fallback.

⁠🌟 Key Features
  • Tailored for MikroTik RouterOS: Fully compatible with MikroTik's minimal kernel environment and handles legacy xtables architecture automatically without throwing network namespace or kernel module errors.
  • Smart Anti-Asymmetric Routing: Dynamically detects the container's parent interface name (even with RouterOS dynamic naming index like OPENCONNECT@if47) and injects static return paths for internal networks to prevent traffic from being swallowed by the VPN's default gateway.
  • Selective Outbound Masquerading: Performs NAT and routing rules inside the container only for subnets specified via environment variables.
  • Persistent Auto-Reconnect: Runs OpenConnect in an infinite user-space wrapper loop with integrated network-level drop recovery (--reconnect-timeout).
  • Multi-Server Failover: Supports a comma-separated list of VPN servers. Automatically cycles through them if the connection drops for longer than the specified timeout.
  • SOCKS5 Proxy Onboard: Includes a low-overhead Dante server listening on port 1080 routed straight into the VPN tunnel.
  • SSH Server Access: Secure dynamic root shell provisioning via either passwords or Public Keys (authorized_keys).

⁠🚀 Quick Start & Environment Variables

Pass the following parameters inside your MikroTik RouterOS container configuration environment (/container/envs):

VariableDescriptionExample
VPN_SERVERRequired. URL or IP of your VPN server. Supports comma-separated list for failover.vpn1.company.com, vpn2.company.com
VPN_USERRequired. VPN Authentication username.network_admin
VPN_PASSRequired. VPN Authentication password.my_secret_pass
LOCAL_NETWORKSComma-separated internal subnets allowed to use the gateway.192.168.88.0/24,192.168.89.0/24
RECONNECT_TIMEOUT(Optional) Timeout in seconds to hold failed session before cycling servers. Default: 300 (5 mins).60
SSH_PASSSet password for the container's root SSH shell.secure_ssh_root_pass
SSH_KEY(Optional) Standard Public Key string for passwordless SSH.ssh-ed25519 AAAAC3NzaC1l...
VPN_OPTIONS(Optional) Any additional native OpenConnect CLI flags.--no-cert-check --useragent=AnyConnect

⁠📄 Project Configuration Files
⁠1. Dockerfile
FROM alpine:3.21

RUN apk update && apk add --no-cache \
    openconnect \
    openssh-server \
    dante-server \
    iptables \
    ip6tables \
    iptables-legacy \
    iproute2 \
    bash \
    ca-certificates \
    curl

RUN rm -f /usr/sbin/iptables && ln -s /usr/sbin/iptables-legacy /usr/sbin/iptables 
RUN rm -f /usr/sbin/ip6tables && ln -s /usr/sbin/ip6tables-legacy /usr/sbin/ip6tables

RUN ssh-keygen -A && \
    sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \
    sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config

RUN cat <<EOF > /etc/sockd.conf
logoutput: stderr
internal: 0.0.0.0 port = 1080
external: tun0
clientmethod: none
socksmethod: none
user.notprivileged: nobody
client pass { from: 0.0.0.0/0 to: 0.0.0.0/0 log: connect disconnect error }
socks pass { from: 0.0.0.0/0 to: 0.0.0.0/0 log: connect disconnect error }
EOF

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

EXPOSE 22 1080
CMD ["/bin/bash", "/entrypoint.sh"]
⁠2. entrypoint.sh
#!/bin/bash

# 1. SSH Authentication Setup
if [ -n "\$SSH_PASS" ]; then
    echo "root:\$SSH_PASS" | chpasswd
    echo "[SSH] Password for root successfully set."
fi

if [ -n "\$SSH_KEY" ]; then
    mkdir -p /root/.ssh
    echo "\$SSH_KEY" > /root/.ssh/authorized_keys
    chmod 700 /root/.ssh
    chmod 600 /root/.ssh/authorized_keys
    echo "[SSH] Authorized public key added."
fi

# 2. Start SSH Server
/usr/sbin/sshd
echo "[SSH] Server successfully started."

# 3. Enable IP Forwarding
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1

# 4. Background routing & service initializer
(
    echo "[Services] Waiting for tun0 interface from OpenConnect..."
    while ! ip link show tun0 >/dev/null 2>&1; do 
        sleep 1
    done
    
    echo "[Routing] Analyzing network interfaces..."
    MAIN_IFACE=\$(ip -o link show | awk -F': ' '{print \$2}' | grep -v -E 'lo|tun|tap' | cut -d'@' -f1 | head -n 1)
    echo "[Routing] Main container interface: \$MAIN_IFACE"
    
    CONTAINER_GW=\$(ip -4 addr show dev "\$MAIN_IFACE" | grep inet | awk '{print \$2}' | cut -d/ -f1 | awk -F. '{print \$1"."\$2"."\$3".1"}')
    echo "[Routing] Detected MikroTik gateway IP: \$CONTAINER_GW"

    NETWORKS="\${LOCAL_NETWORKS:-192.168.88.0/24}"
    
    IFS=',' read -ra ADDR <<< "\$NETWORKS"
    for net in "\${ADDR[@]}"; do
        net=(echo "net" | xargs)
        echo "[Network: \$net] Injected rules..."
        
        # Inundate static routing back to avoid asymmetric flow issues
        ip route add "\$net" via "\(CONTAINER_GW" dev "\)MAIN_IFACE" metric 10 2>/dev/null || \
        ip route change "\$net" via "\(CONTAINER_GW" dev "\)MAIN_IFACE" metric 10
        echo "[Network: \$net] Return path locked via \(CONTAINER_GW on\)MAIN_IFACE."
        
        # Legacy-ready source NAT inside container namespace
        iptables -t nat -A POSTROUTING -s "\$net" -o tun0 -j MASQUERADE
        echo "[Network: \$net] Masquerading enabled successfully."
    done
    
    # Fire up SOCKS Proxy
    sockd -D
    echo "[Services] All sub-daemons (Routing, NAT, SOCKS5) operational."
) &

# 5. Multi-Server Failover OpenConnect Wrapper Loop
if [ -z "\$VPN_SERVER" ] || [ -z "\(VPN_USER" ] \vert{}\vert{} [ -z "\)VPN_PASS" ]; then
    echo "Error: Missing critical parameters (VPN_SERVER/VPN_USER/VPN_PASS)!"
    exit 1
fi

TIMEOUT_SEC="\${RECONNECT_TIMEOUT:-300}"
echo "[VPN] Reconnect failover timer configured for: \${TIMEOUT_SEC} seconds."

IFS=',' read -ra SERVER_LIST <<< "\$VPN_SERVER"
SERVER_COUNT=\${#SERVER_LIST[@]}
CURRENT_INDEX=0

echo "[VPN] Initialized. Pool count: \$SERVER_COUNT targets."

while true; do
    TARGET_SERVER=\$(echo "\({SERVER_LIST[\)CURRENT_INDEX]}" | xargs)
    echo "[VPN] Session attempt [\$((CURRENT_INDEX+1))/\(SERVER_COUNT] targeting:\)TARGET_SERVER..."
    
    echo "\(VPN_PASS" \vert{} openconnect "\)TARGET_SERVER" \
        -u "\$VPN_USER" \
        --passwd-on-stdin \
        --non-inter \
        --no-dtls \
        --reconnect-timeout "\$TIMEOUT_SEC" \
        \$VPN_OPTIONS
        
    echo "[VPN] Connection to \(TARGET_SERVER dropped or timeout breached (\){TIMEOUT_SEC}s)."
    
    if [ \$SERVER_COUNT -gt 1 ]; then
        CURRENT_INDEX=\$(( (CURRENT_INDEX + 1) % SERVER_COUNT ))
        echo "[VPN] Iterating pool index. Next hop in 5 seconds..."
    else
        echo "[VPN] Standalone target configuration. Retrying in 5 seconds..."
    fi
    sleep 5
done

⁠⚙️ MikroTik Deployment Guide (RouterOS CLI)
⁠1. Network Interface Setup
/interface/veth/add name=OPENCONNECT address=192.168.254.254/24 gateway=192.168.254.1
⁠2. Define Container Environment
/container/envs/add list=vpn_box_env key=VPN_SERVER value="vpn1.company.com, vpn2.company.com"
/container/envs/add list=vpn_box_env key=VPN_USER value="vpn_username"
/container/envs/add list=vpn_box_env key=VPN_PASS value="vpn_password"
/container/envs/add list=vpn_box_env key=LOCAL_NETWORKS value="192.168.88.0/24,192.168.89.0/24"
/container/envs/add list=vpn_box_env key=RECONNECT_TIMEOUT value="300"
/container/envs/add list=vpn_box_env key=SSH_PASS value="SuperSecureRootPass123"
⁠3. Instantiate and Launch Container
/container add remote-image=richard52ru/openconnect-gateway-mikrotik:latest interface=OPENCONNECT envlist=vpn_box_env root-dir=usb1/docker/openconnect start-on-boot=yes hostname=openconnect logging=yes
/container/start [find root-dir="disk1/vpn-gateway"]
⁠4. Policy Routing & Device Interception
/routing/table/add name=route-via-vpn fib
/ip/route/add dst-address=0.0.0.0/0 gateway=192.168.254.254 routing-table=route-via-vpn
/ip/firewall/mangle/add chain=prerouting src-address=192.168.88.50 action=mark-routing new-routing-mark=route-via-vpn passthrough=yes
/ip/firewall/mangle/add chain=prerouting out-interface=OPENCONNECT action=log log-prefix="[VPN-TRAFFIC]"

⁠🛠️ Multi-Arch Mac Compilation (buildx)

Run this inside your local Mac workstation folder containing Dockerfile and entrypoint.sh:

# Compile target and export manifest layer directly into a flawless tar container payload
docker buildx build --platform linux/arm64 -t vpn-box-arm64:latest --load .
docker save vpn-box-arm64:latest -o vpn-box-arm64.tar

Tag summary

Content type

Image

Digest

sha256:8a771e4dd…

Size

17.7 MB

Last updated

2 months ago

docker pull richard52ru/openconnect-gateway-mikrotik