Yes!


Two provisioners are needed: One for OpenVPN server certificates, and one for OpenVPN client certificates.

First, the client provisioner. In this case we will use an OIDC provisioner so that our clients can get certificate via SSO:


cat <<EOF > openvpn-client.tpl
{
  "subject": {"commonName": {{ toJson .Insecure.CR.Subject.CommonName }}},
  "sans": {{ toJson .SANs }},
  "keyUsage": ["digitalSignature", "keyAgreement"],
  "extKeyUsage": ["clientAuth"]
}
EOF

step beta ca provisioner add "OpenVPN Client" --create \\
        --type OIDC \\
        --x509-default-dur 24h \\
        --x509-template openvpn-client.tpl \\
        --client-id 1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com \\
        --client-secret udTrOT3gzrO7W9fDPgZQLfYJ \\
        --configuration-endpoint <https://accounts.google.com/.well-known/openid-configuration>


Be sure to adjust client-id, client-secret, and configuration-endpoint for your environment.

Now let's get a test certificate and inspect it:


$ step ca certificate carl+openvpn@smallstep.com client.crt client.key
..
.. SSO flow happens ..
..
✔ CA: <https://your.team.ca.smallstep.com>
✔ Certificate: client.crt
✔ Private Key: client.key

$ step certificate inspect client.crt
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 288020726091407945952925267215046898938 (0xd8aec62b4f62a0092ea741ad72af74fa)
    Signature Algorithm: ECDSA-SHA256
        Issuer: CN=Smallstep Intermediate CA
        Validity
            Not Before: Nov 8 20:13:16 2021 UTC
            Not After : Nov 9 20:14:16 2021 UTC
        Subject: CN=carl+openvpn@smallstep.com
        Subject Public Key Info:
            Public Key Algorithm: ECDSA
                Public-Key: (256 bit)
                X:
                    2a:f0:6e:7e:09:a9:67:d8:e9:05:93:a2:db:82:3b:
                    97:73:02:8f:10:18:98:f5:11:fe:c7:b5:bd:97:b4:
                    7f:1e
                Y:
                    ef:a9:b4:63:d2:b3:e2:71:e2:05:81:a7:39:c2:c4:
                    29:e3:1c:dc:c1:74:1b:38:fb:9c:9f:b3:48:ef:ca:
                    b2:df
                Curve: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Agreement
            X509v3 Extended Key Usage:
                Client Authentication
            X509v3 Subject Key Identifier:
                F4:18:7C:8B:6E:9F:32:29:E5:56:3F:33:73:6C:15:C5:44:A0:D1:FA
            X509v3 Authority Key Identifier:
                keyid:96:4B:06:B3:08:06:3E:AD:25:78:8C:66:F3:42:14:39:D2:9A:40:47
            X509v3 Subject Alternative Name:
                <email:carl+openvpn@smallstep.com>
            X509v3 Step Provisioner:
                Type: JWK
                Name: OpenVPN Client
                CredentialID: T3km0338Q1EU6sUXugHKayRVvY4a20nD4yqSgbRkQOE
    Signature Algorithm: ECDSA-SHA256
         30:45:02:21:00:e1:ca:00:ef:cd:7b:f9:f4:3a:68:41:56:96:
         56:59:b8:f9:17:e2:7f:3c:90:eb:9b:0e:49:a4:e9:a3:3d:89:
         47:02:20:10:91:fa:1a:c5:7b:1d:4a:e3:93:3c:ff:3e:46:7e:
         17:57:51:e1:85:e0:a3:94:82:ff:5f:f1:12:0c:b7:7c:95 

Crucially, OpenVPN requires that the client certificate only have "Client Authentication" Extended Key Usage (EKU). And, similarly, the server certificate will only have "Server Authentication" EKU.

Let's create the server certificate provisioner now.

cat <<EOF > openvpn-server.tpl
{
  "subject": {{ toJson .Subject }},
  "sans": {{ toJson .SANs }},
  "keyUsage": ["digitalSignature", "keyEncipherment", "keyAgreement"],
  "extKeyUsage": ["serverAuth"]
}
EOF

step beta ca provisioner add "OpenVPN Server" --create \\
        --type ACME \\
        --x509-default-dur 2160h \\
        --x509-template openvpn-server.tpl


? ACME is the preferred provisioner type here, but you could alternatively use JWK.



Finally, create a server certificate:


step ca certificate --provisioner="OpenVPN Server" \\
                    vpn.smallstep.com server.crt server.key

There's a couple of important caveats when it comes to renewal of this server certificate:

  • Because the server certificate doesn't have Client Authentication enabled (TLS server certificates typically do), this certificate won't be renewable via step ca renew, which uses mutual TLS and authenticates the new certificate using the old one. So, you'll need a fresh certificate and private key (via step ca certificate, or any ACME client) when your current certificate is approaching expiry.
  • OpenVPN server doesn't appear to have a way to reload certificate files without restarting the server and potentially impacting client connections. To minimize the impact of that, you may want to adjust the default certificate duration to account for that.

To configure the OpenVPN side of things, here's a starting point.


⚠️ These are test configurations, not for production use.

Client Configuration:



# client
proto udp
remote vpn.smallstep.com

# authentication
tls-client
key client.key
cert client.crt
ca root_ca.crt
tls-crypt myvpn.tlsauth
cipher AES-256-GCM
verify-x509-name vpn.smallstep.com name

# network
dev tun
topology subnet
pull

Server Configuration:


# server
proto udp
local 0.0.0.0

# authentication
tls-server
key server.key
cert server.crt
ca root_ca.crt
dh dh2048.pem
remote-cert-eku "TLS Web Client Authentication"
tls-crypt myvpn.tlsauth
cipher AES-256-GCM

# network
dev tun
topology subnet
server 10.8.0.0 255.255.255.0


  • myvpn.tlsauth is a shared secret, created by running openvpn --genkey --secret myvpn.tlsauth
  • root_ca.crt is your CA’s root certificate (fetch it with step ca root > root_ca.crt)
  • dh2048.pem is created by running openssl dhparam -out dh2048.pem 2048


Helpful reading as you move this configuration to production: