OpenVPN

En generell guide for å sette opp OpenVPN på openSUSE, ved bruk av EasyRSA med ECDSA (secp384r1) for sterk sikkerhet.

Oppsett av server med EasyRSA og ECDSA (secp384r1)

Introduksjon

EasyRSA er et kommandolinjeverktøy for å opprette og administrere en Public Key Infrastructure (PKI). Det lar deg sette opp din egen Certificate Authority (CA) (sertifikatutsteder) og generere sertifikater og nøkkelfiler for både servere og klienter.

ECDSA (Elliptic Curve Digital Signature Algorithm) er en digital signeringsalgoritme basert på elliptiske kurver, som gir sterk sikkerhet med kortere nøkkellengder enn tradisjonelle metoder som RSA.

secp384r1 er en elliptisk kurve som brukes i ECDSA, med en nøkkellengde på 384 bit for høy sikkerhet og ytelse. Den er bredt støttet i moderne sikkerhetssystemer, inkludert TLS/SSL (HTTPS), VPN-løsninger, SSH og digitale signaturer.

Installasjon og oppsett

# Oppdater pakkebrønnen og installer OpenVPN og EasyRSA
zypper refresh
zypper install openvpn easy-rsa

# Opprett nødvendige mapper
mkdir -p /etc/openvpn/easyrsa
mkdir -p /etc/openvpn/serverfiles

# Bytt til EasyRSA-mappen (alle operasjoner utføres herfra)
cd /etc/openvpn/easyrsa

Konfigurer EasyRSA for ECDSA (secp384r1)

Rediger EasyRSA-konfigurasjonen:

nano /etc/openvpn/easyrsa/vars

Legg til eller rediger følgende linjer:

set_var EASYRSA_ALGO "ec"
set_var EASYRSA_CURVE "secp384r1"

Initialiser PKI og generer sertifikater og nøkkler:

# Init EasyRSA
easyrsa init-pki

# Generer en oppdatert liste over tilbakekalte sertifikater (CRL)
# Denne kommandoen er nødvendig hvis du vil blokkere tidligere
# utstedte sertifikater (f.eks. hvis en klient ikke lenger skal ha tilgang)
# OpenVPN bruker crl.pem for å sjekke om tilkoblede klienter har gyldige sertifikater
easyrsa gen-crl

# Generer et CA-sertifikat og privat nøkkel (ca.crt og private/ca.key)
# CA brukes til å signere alle andre sertifikater i systemet
# nopass fjerner passordkrav for enklere automasjon
# Common name brukes for å identifisere CA'et selv, for eksempel openSUSE-CA
easyrsa build-ca nopass

# Lager en privat nøkkel (private/server.key) og en 
# sertifikatsøknad (CSR) (reqs/server.req), som serveren bruker for å 
# identifisere seg til klienter
easyrsa gen-req server nopass

# CA signerer serverens CSR, og det genererte sertifikatet (issued/server.crt) 
# brukes av serveren for kryptert kommunikasjon med klienter
easyrsa sign-req server server

# Genererer en TLS-Auth nøkkel (ta.key), som brukes til å beskytte 
# OpenVPN-serveren mot UDP-baserte angrep (f.eks. DoS) og forhindre 
# uautoriserte tilkoblingsforsøk.
openvpn --genkey secret /etc/openvpn/serverfiles/ta.key

# Kopier alle filene til serverfiles mappen for enklere tilgang
cp /etc/openvpn/easyrsa/pki/ca.crt             \
   /etc/openvpn/easyrsa/pki/crl.pem            \
   /etc/openvpn/easyrsa/pki/issued/server.crt  \
   /etc/openvpn/easyrsa/pki/private/server.key \
   /etc/openvpn/serverfiles/

For å verifisere at et sertifikat (.crt) eller nøkkel (.key) faktisk bruker ECDSA/secp384r1 kan openssl brukes:

# .crt - Skal returnere "ec". For eksempel: id-ecPublicKey 
openssl x509 -in file.crt -noout -text | grep "Public Key Algorithm"
# .key - Skal returnere "secp384r1"
openssl ec -in file.key -noout -text | grep "ASN1 OID"

Nå har skal vi ha alle nødvendige filer i serverfiles mappen:
ca.crt
crl.pem
server.crt
server.key
ta.key

Konfigurering

En standard konfigurasjonsfil finnes i /usr/share som brukes som utgangspunkt:

cp /usr/share/doc/packages/openvpn/sample-config-files/server.conf /etc/openvpn/server.conf

Eksempel på konfigurering av server:

port 1194
proto udp4  
dev tun  

# Bruker subnet-topologi for enklere håndtering av klient-IP-er
topology subnet  

# Definerer VPN-nettverket og subnettmasken
# Med 255.255.255.240, eller 10.8.0.0/28 så blir oppsettet slik:
# 10.8.0.0 - Nettverksaddresse
# 10.8.0.1 - Server
# 10.8.0.2-14 - Klienter
# 10.8.0.15 - Broadcast addresse
server 10.8.0.0 255.255.255.240  

# Ruter all klienttrafikk gjennom VPN-serveren
# redirect-gateway - Ruter all klienttrafikk gjennom VPN-serveren  
# def1 - Beholder eksisterende gateway, men legger til en ny standardrute med høyere prioritet når VPN er tilkoblet  
# bypass-dhcp - Beholder klientens DHCP-innstillinger slik at den fortsatt har tilgang til lokale enheter, f.eks. skrivere
push "redirect-gateway def1 bypass-dhcp"  
# Setter Cloudflare DNS for klienter
push "dhcp-option DNS 1.1.1.1"  
push "dhcp-option DNS 1.0.0.1"  

# Sender en keepalive-pakke til klienten hvert 10. sekund  
# Hvis det ikke er noen respons innen 60 sekunder, restart tilkoblingen  
keepalive 10 60  

# Mappen "ccd" brukes for klientspesifikke konfigurasjoner
# Som for eksempel statisk IP-addresse
client-config-dir ccd  

user openvpn  
group users  
persist-key  
persist-tun  

# Bruker CRL-listen for å blokkere tilbakekalte sertifikater
crl-verify serverfiles/crl.pem  
# Laster inn CA-sertifikatet for å validere klientsertifikater
ca serverfiles/ca.crt  
# Laster inn serverens sertifikat
cert serverfiles/server.crt  
# Laster inn serverens private nøkkel (må holdes hemmelig)
key serverfiles/server.key  
# Bruker TLS-kryptering for å beskytte handshake-prosessen
tls-crypt serverfiles/ta.key  
# Tvinger bruk av minst TLS 1.2 for økt sikkerhet
tls-version-min 1.2  
# Angir elliptiske kurver som kan brukes for nøkkelutveksling
tls-groups X25519:secp384r1  
# Krypteringsalgoritmer for datatrafikk
data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305  
# Bruker AES-256-GCM som fallback hvis klienten ikke støtter de andre
data-ciphers-fallback AES-256-GCM  
# Bruker secp384r1 som elliptisk kurve for nøkkelutveksling
ecdh-curve secp384r1  
# Deaktiverer Diffie-Hellman-parametere (bruker kun ECDH for bedre sikkerhet)
dh none  
# Logger tilkoblingsstatus til en fil
status openvpn-status.log  
# Logger hendelser og feilmeldinger til en fil
log openvpn.log  
# Logger detaljer på nivå 3 (medium detaljert logging)
verb 3  

# Varsler klienter om at serveren avsluttes (spesielt for UDP)
explicit-exit-notify 2  
IPv4 forwarding

Dette må slås på for å tillate videresending/ruting av trafikk mellom virituelle og fysiske nettverkskort.

sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf
Brannmur

nftables

# input chain:
# godta traffikk på udp/1194
ct state new udp dport 1194 accept

# forward chain:
# send traffikk mellom eth0 og tun0
iifname "eth0" oifname "tun0" accept
iifname "tun0" oifname "eth0" accept

# postrouting chain:
# masquerade
oifname "eth0" ip saddr 10.8.0.0/24 masquerade

firewall-cmd

firewall-cmd --zone=public --add-service=openvpn --permanent
firewall-cmd --zone=public --add-masquerade --permanent
firewall-cmd --reload
Start serveren
systemctl enable openvpn@server
systemctl start openvpn@server

Oppsett av klient på server med EasyRSA og ECDSA (secp384r1)

Last ned nyeste klient fra OpenVPN sine nettsider.
Modifiser og bruk scriptet under for å administrere klienter effektivt. Etterpå, last inn .ovpn filen som blir generert i klient-programmet.

Men en standard .ovpn fil ser ca sånn ut:

client
dev tun
proto $PROTOCOL
remote $SERVER_IP $PORT
data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305
tls-version-min 1.2
tls-groups X25519:secp384r1
cipher AES-256-GCM
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server

<ca>
$(cat "$CA_CERT")
</ca>

<cert>
$(cat "$CLIENT_DIR/$CLIENT_NAME.crt")
</cert>

<key>
$(cat "$CLIENT_DIR/$CLIENT_NAME.key")
</key>

<tls-crypt>
$(cat "$TLS_CRYPT_KEY")
</tls-crypt>

Script:

#!/bin/bash

# Configuration
SERVER_IP="my.vpn.server.com"
PORT="1194"
PROTOCOL="udp"
CLIENT_DIR="/etc/openvpn/clients"
EASYRSA_DIR="/etc/openvpn/easyrsa"
CCD_DIR="/etc/openvpn/ccd"
CA_CERT="/etc/openvpn/serverfiles/ca.crt"
TLS_CRYPT_KEY="/etc/openvpn/serverfiles/ta.key"
MANAGEMENT_LOG="/etc/openvpn/openvpn_manager.log"
VPN_SUBNET="10.8.0"

# Define the allowed user
REQUIRED_USER="openvpn"
# Get the current user
CURRENT_USER=$(whoami)
# Check if the script is run by the required user
if [[ "$CURRENT_USER" != "$REQUIRED_USER" ]]; then
    echo "❌ ERROR: This script must be run as '$REQUIRED_USER'."
    echo "Try running: sudo -u $REQUIRED_USER $0"
    exit 1
fi

# Ensure necessary directories exist
mkdir -p "$CLIENT_DIR"
mkdir -p "$CCD_DIR"

# Log
# log_action "Client '$CLIENT_NAME' added with IP $STATIC_IP"
log_action()
{
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $MANAGEMENT_LOG
}

# Function to list all clients
list_clients()
{
    echo "📋 Listing all OpenVPN clients:"
    ls -1 "$EASYRSA_DIR/pki/issued" | sed 's/.crt//g' | grep -v '^server$'
}

check_ip_conflict()
{
    local ip_to_check=$1
    grep -r "$ip_to_check" "$CCD_DIR" 2>/dev/null
    return $?  # Returns 0 if found, 1 if not found
}

get_client_cn()
{
    local CLIENT_CERT="$EASYRSA_DIR/pki/issued/$1.crt"
    if [[ -f "$CLIENT_CERT" ]]; then
        openssl x509 -noout -subject -in "$CLIENT_CERT" | sed -n 's/^.*CN = //p'
    else
        echo ""
    fi
}

# Function to add a new client
add_client()
{
    read -p "Enter Client Username: " CLIENT_NAME

    # Check if client already exists
    if [[ -f "$EASYRSA_DIR/pki/issued/$CLIENT_NAME.crt" ]]; then
        echo "❌ ERROR: Client '$CLIENT_NAME' already exists!"
        echo "Returning to main menu..."
        return
    fi

    while true; do
        read -p "Enter Static IP (e.g., $VPN_SUBNET.2, range is 1-14) or press Enter to auto-assign: " STATIC_IP

        # If no static IP is provided, OpenVPN will assign one
        if [[ -z "$STATIC_IP" ]]; then
            echo "ℹ️ No static IP provided. OpenVPN will assign one dynamically."
            break
        fi

        # Check if the static IP is already assigned
        if check_ip_conflict "$STATIC_IP"; then
            echo "❌ ERROR: IP $STATIC_IP is already assigned to another client!"
            echo "Returning to main menu..."
            return  # Exit function and return to main menu
        else
            echo "✅ IP $STATIC_IP is available."
            break
        fi
    done

    # Move into EasyRSA directory
    cd "$EASYRSA_DIR" || exit

    # Initialize PKI if not already done
    if [ ! -d "$EASYRSA_DIR/pki" ]; then
        easyrsa init-pki
    fi

    # Generate client certificates
    easyrsa gen-req "$CLIENT_NAME" nopass
    easyrsa sign-req client "$CLIENT_NAME"

    # Create client directory
    mkdir -p "$CLIENT_DIR/$CLIENT_NAME"

    # Copy required files
    cp "$EASYRSA_DIR/pki/ca.crt" "$CLIENT_DIR/$CLIENT_NAME/"
    cp "$EASYRSA_DIR/pki/issued/$CLIENT_NAME.crt" "$CLIENT_DIR/$CLIENT_NAME/"
    cp "$EASYRSA_DIR/pki/private/$CLIENT_NAME.key" "$CLIENT_DIR/$CLIENT_NAME/"
    cp "$TLS_CRYPT_KEY" "$CLIENT_DIR/$CLIENT_NAME/"

    # Get the Common Name, to use for CCD file
    CN_NAME=$(get_client_cn "$CLIENT_NAME")

    # Assign static IP if provided
    if [[ -n "$STATIC_IP" ]]; then
        echo "Assigning static IP: $STATIC_IP"
        echo "Common name (CN): $CN_NAME"
        echo "ifconfig-push $STATIC_IP 255.255.255.240" > "$CCD_DIR/$CN_NAME"
    fi

    update_client_config "$CLIENT_NAME"

    log_action "Client '$CLIENT_NAME' added with CN '$CN_NAME' and IP '$STATIC_IP'"

    echo "✅ Client '$CLIENT_NAME' created successfully!"
    echo "📂 OVPN file: $CLIENT_DIR/$CLIENT_NAME/$CLIENT_NAME.ovpn"
}

# Function to delete a client
delete_client()
{
    read -p "Enter the client name to delete: " CLIENT_NAME

    if [[ "$CLIENT_NAME" == "server" ]]; then
        echo "❌ ERROR: The server certificate cannot be deleted!"
        return
    fi

    # Confirm deletion
    read -p "⚠️ Are you sure you want to delete '$CLIENT_NAME'? (y/N): " CONFIRM
    if [[ "$CONFIRM" != "y" ]]; then
        echo "❌ Operation canceled."
        return
    fi

    # Check if the client exists before proceeding
    if [[ ! -f "$EASYRSA_DIR/pki/issued/$CLIENT_NAME.crt" ]]; then
        echo "❌ ERROR: Client '$CLIENT_NAME' does not exist!"
        return
    fi

    # Get common name
    CN_NAME=$(get_client_cn "$CLIENT_NAME")

    echo "🗑 Revoking and deleting client: $CLIENT_NAME ($CN_NAME)"

    # Revoke certificate
    cd "$EASYRSA_DIR" || exit
    easyrsa revoke "$CLIENT_NAME"
    easyrsa gen-crl
    # Move CRL file to OpenVPN directory
    cp "$EASYRSA_DIR/pki/crl.pem" /etc/openvpn/serverfiles/crl.pem

    # Remove EasyRSA-generated files
    rm -f "$EASYRSA_DIR/pki/issued/$CLIENT_NAME.crt"
    rm -f "$EASYRSA_DIR/pki/private/$CLIENT_NAME.key"
    rm -f "$EASYRSA_DIR/pki/reqs/$CLIENT_NAME.req"
    # Remove CCD (static IP)
    rm -f "$CCD_DIR/$CN_NAME"
    # Remove client configuration directory
    rm -rf "$CLIENT_DIR/$CLIENT_NAME"

    log_action "Client '$CLIENT_NAME' (CN: '$CN_NAME') deleted"

    echo "✅ Client '$CLIENT_NAME' has been deleted. Run command systemctl restart openvpn@server for all changes to take effect"
}

# Function to rotate a client certificate
# Call in batch mode: rotate_client "client1"
rotate_client()
{
    if [[ -z "$1" ]]; then
        read -p "Enter the client name to rotate: " CLIENT_NAME
    else
        CLIENT_NAME="$1"
    fi

    if [[ "$CLIENT_NAME" == "server" ]]; then
        echo "❌ ERROR: The server certificate cannot be rotated!"
        return
    fi

    # Check if the client exists before proceeding
    if [[ ! -f "$EASYRSA_DIR/pki/issued/$CLIENT_NAME.crt" ]]; then
        echo "❌ ERROR: Client '$CLIENT_NAME' does not exist!"
        return
    fi

    CN_NAME=$(get_client_cn "$CLIENT_NAME")
    echo "🔄 Rotating certificate for '$CLIENT_NAME' (CN: $CN_NAME)..."

    # Revoke old certificate
    cd "$EASYRSA_DIR" || exit
    easyrsa revoke "$CLIENT_NAME"
    easyrsa gen-crl
    cp "$EASYRSA_DIR/pki/crl.pem" /etc/openvpn/serverfiles/crl.pem

    # Kill client session (force disconnect)
    pkill -HUP -f "openvpn.*$CLIENT_NAME" 2>/dev/null

    # Generate a new certificate
    easyrsa gen-req "$CLIENT_NAME" nopass
    easyrsa sign-req client "$CLIENT_NAME"

    # Replace client certificate
    cp "$EASYRSA_DIR/pki/issued/$CLIENT_NAME.crt" "$CLIENT_DIR/$CLIENT_NAME/"
    cp "$EASYRSA_DIR/pki/private/$CLIENT_NAME.key" "$CLIENT_DIR/$CLIENT_NAME/"

    # Ensure the client's OVPN file is updated with the new key and cert
    update_client_config "$CLIENT_NAME"

    log_action "Client '$CLIENT_NAME' certificate rotated"

    echo "✅ Certificate for client '$CLIENT_NAME' has been successfully rotated."
    echo "📂 Updated OVPN file: $CLIENT_DIR/$CLIENT_NAME/$CLIENT_NAME.ovpn"
}

# Function to rotate all client certificates
rotate_all_clients()
{
    echo "🔄 Rotating all client certificates..."
    for CLIENT in $(ls -1 "$EASYRSA_DIR/pki/issued" | sed 's/.crt//g' | grep -v '^server$'); do
        echo "🔄 Rotating certificate for: $CLIENT"
        rotate_client "$CLIENT" || echo "⚠️ Failed to rotate $CLIENT"
    done
    echo "✅ All client certificates have been rotated."
}

# Update ovpn
update_client_config()
{
    local CLIENT_NAME="$1"
    local CLIENT_DIR="$CLIENT_DIR/$CLIENT_NAME"
    local OVPN_FILE="$CLIENT_DIR/$CLIENT_NAME.ovpn"

    cat > "$OVPN_FILE" <<EOF
client
dev tun
proto $PROTOCOL
remote $SERVER_IP $PORT
data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305
tls-version-min 1.2
tls-groups X25519:secp384r1
cipher AES-256-GCM
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server

<ca>
$(cat "$CA_CERT")
</ca>

<cert>
$(cat "$CLIENT_DIR/$CLIENT_NAME.crt")
</cert>

<key>
$(cat "$CLIENT_DIR/$CLIENT_NAME.key")
</key>

<tls-crypt>
$(cat "$TLS_CRYPT_KEY")
</tls-crypt>
EOF
}

# If script is run with arguments, execute the corresponding function
if [[ $# -gt 0 ]]; then
    case "$1" in
        "rotateall")
            rotate_all_clients
            exit 0
            ;;
        "rotate")
            if [[ -z "$2" ]]; then
                echo "❌ ERROR: Missing client name for rotation!"
                exit 1
            fi
            rotate_client "$2"
            exit 0
            ;;
        *)
            echo "❌ ERROR: Unknown command '$1'. Available commands: rotateall, rotate <client_name>"
            exit 1
            ;;
    esac
fi

# Menu
while true; do
    echo ""
    echo "🔹 OpenVPN Client Management 🔹"
    echo "1️⃣ List clients"
    echo "2️⃣ Add a client"
    echo "3️⃣ Delete a client"
    echo "4️⃣ Rotate a client certificate"
    echo "5️⃣ Rotate all client certificates"
    echo "6️⃣ Exit"
    read -p "Select an option: " OPTION

    case $OPTION in
        1) list_clients ;;
        2) add_client ;;
        3) delete_client ;;
        4) rotate_client ;;
        5) rotate_all_clients ;;
        6) echo "👋 Exiting..."; exit 0 ;;
        *) echo "❌ Invalid option. Please try again." ;;
    esac
done

Feilsøking

Øk verbositeten i server.conf. Sjekk feilmeldinger live med:

journalctl -u openvpn@server -f