Using OpenVPN and client authentication certificates to connect to Azure VPN Gateway

10/23/2022

Azure VPN Gateway is Microsoft's managed cloud VPN service. The value proposition is that with just a few clicks we can let Microsoft worry about security and compliance and get a highly scalable VPN that's easily integrated with our Azure infrastructure. And what's more, if we configure Azure VPN Gateway to use an OpenVPN tunnel, we can also streamline user authentication via Azure Active Directory (AAD). AAD authentication works largely as promised if we only need to support Windows and MacOS clients; but if we also need to support Linux clients, we have a problem: AAD authentication works through the Azure VPN Client, which Microsoft makes available only for Windows and MacOS.

On Linux, users will need to connect via an OpenVPN client, which does not support AAD authentication. To allow such users to connect, we need to enable certificate authentication, which works side-by-side with AAD authentication.

While the Microsoft documentation does a good job describing the general approach, it skips over a few important details and provides PowerShell examples. Obviously on Linux, we'd prefer to use standard tools like OpenSSL instead. The plan is simple:

  1. Generate a point-to-site (P2S) certificate authority (CA) to sign user certificates with
  2. Upload the CA certificate to Azure VPN Gateway's P2S configuration (we can have up to 10 such certificates enabled simultaneously)
  3. Generate a certificate signing request (CSR) for each user
  4. Sign the CSR and generate an OpenVPN authentication certificate for each user
  5. Distribute certificates to allow users to connect to Azure VPG Gateway via OpenVPN

First, we'll generate a key:

openssl genrsa -des3 -out P2S_RootCert_1.key 4096

And we'll use it to generate the actual root certificate:

openssl req -x509 -new -sha256 \
    -key P2S_RootCert_1.key \
    -days 9999 \
    -out P2S_RootCert_1.crt

In the next step, we upload this certificate to the Azure VPN Gateway's P2S configuration area. Once we've done this, any valid certificate signed by this one will be allowed access to the VPN.

Then, each user generates a CSR using their AAD id as the common name:

openssl req -new \
    -subj "/CN=ismailzai" \
    -newkey rsa:2048 -nodes \
    -keyout ismailzai.key \
    -out ismailzai.csr

The command above also generates a private key, which the user uses to communicate with the VPN.

All that is left is to generate a client authentication certificate using the CSR:

openssl x509 -req -days 9999 \
    -extensions v3_ca -extfile <(cat /etc/ssl/openssl.cnf <(printf "[v3_ca]\nsubjectAltName=email:[email protected]\nextendedKeyUsage=critical,clientAuth\nkeyUsage=critical,nonRepudiation,digitalSignature,keyEncipherment\n")) \
    -CA VR_P2S_RootCert_1.crt \
    -CAkey VR_P2S_RootCert_1.key \
    -CAcreateserial \
    -in ismailzai.csr \
    -out ismailzai.crt

The gotcha here is that if the certificate does not have the correct extensions associated with it, Azure VPN Gateway will kill our connection. Specifically, we need to specify the subject alternative name (SAN) and the necessary key usages. Frustratingly, there is a bug in OpenSSL that strips extensions from the CSR, so we have to append them when generating the final certificate. The command above ensures that all the necessary extensions are attached to the signed certificate, which we can validate using OpenSSL:

openssl x509 -in ismailzai.crt -text

The output will include a section like this:

X509v3 extensions:
    X509v3 Subject Key Identifier:
        XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
    X509v3 Authority Key Identifier:
        keyid:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
    X509v3 Basic Constraints: critical
        CA:TRUE
    X509v3 Subject Alternative Name:
        email:[email protected]
    X509v3 Extended Key Usage: critical
        TLS Web Client Authentication
    X509v3 Key Usage: critical
        Digital Signature, Non Repudiation, Key Encipherment

With our user certificate verified, we can now use it to connect to Azure VPN Gateway using the OpenVPN CLI:

sudo openvpn --config vpnconfig_cert.ovpn --cert ismailzai.crt --key ismailzai.key

Here, the vpnconfig_cert.ovpn file is automatically generated by clicking the Download VPN client button in Azure VPN Gateway's P2S configuration area. This configuration is safe to distribute to all users who need to connect via OpenVPN because it does not include any user-specific information.

Of course, all of this can be streamlined for our users. For instance, we could build a web portal that requires AD authentication, and once verified, generates an OpenVPN configuration file with all the necessary certificates embedded in it, which enables users to use any OpenVPN GUI to connect.