openvpn-auth-oauth2: putting an identity provider in front of an OpenVPN server
openvpn-auth-oauth2 is a plugin/management interface client for OpenVPN server to handle an OIDC based single sign-on (SSO) auth flows
At a glance
- What is it?
- openvpn-auth-oauth2 is a Go program that lets an OpenVPN server authenticate users against any OIDC provider, from Microsoft Entra ID to GitHub, Okta and Keycloak. It hooks in either as a stable plugin or through the OpenVPN management interface, and it exposes a configuration file where access rules can be written as expressions.
- Who is it for?
- openvpn-auth-oauth2 suits an operator who already runs an OpenVPN server and an identity provider, and who wants per user access rules evaluated from OIDC claims instead of from certificates or static user files.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A management client that speaks OIDC to your VPN
OpenVPN classically authenticates with certificates or with a static user/password file. Both are awkward in an organisation that already manages identities in a directory or a developer identity provider, because the VPN ends up with a second copy of who works there. openvpn-auth-oauth2 exists to remove that copy.
The project describes itself as a management client for OpenVPN that handles the single sign-on authentication against various OIDC providers. The provider list in the README is broad: Microsoft Entra ID, formerly Azure AD, GitHub, Okta, Google Workspace, Authentik, Zitadel, DigitalOcean, Keycloak, and any other OIDC compatible auth server, with a wiki page listing the supported providers.
The Go module path says how the project is structured. It is github.com/jkroepke/openvpn-auth-oauth2/v2, which means the major version is part of the import path, and the module requires Go 1.27.2. The direct dependencies describe the shape of the work: golang.org/x/oauth2 for the OAuth2 dance, the zitadel oidc library for OIDC, go-jose for JSON Web Signature handling, and cel.dev/cel-go, the Common Expression Language implementation, which is what lets access rules be written as expressions over claims rather than as hand coded conditionals.
The repository layout follows the same split. cmd/ holds the entry points, internal/ the implementation, lib/ the reusable pieces, tests/ the suite, packaging/ the distribution definitions, and config.example.yaml sits at the root as the annotated starting point for configuration.
Two ways into OpenVPN: a .so plugin or the management interface
The integration choice is the first decision an operator makes, and the README is explicit that there are two supported options.
One is the stable plugin, described as connecting through OpenVPN on Linux AMD64, Linux ARM64 and FreeBSD AMD64. The other is to connect through the OpenVPN management interface directly, with no shared object loaded into the server process. The Getting Started guide compares both options, and the plugin path is what the Makefile is built around.
The difference matters for how the project is deployed and how it is upgraded. The plugin model means a native library is loaded into the OpenVPN server, so the binary has to match the server platform, and the plugin has to be rebuilt when the project releases a new version. The management interface model keeps the two programs separate: openvpn-auth-oauth2 runs as its own process and talks to the server over the management channel, which makes rolling the upgrade a matter of replacing a binary and restarting a service.
The plugin build is handled in the Makefile, which sets a PLUGIN_PATH and a PLUGIN_HEADER_PATH for the shared object and its header. The build is conditional on the host: on Linux it sets CGO_ENABLED=1, and on other hosts it cross compiles with zig cc, targeting x86_64-linux-gnu.2.17 for AMD64 and aarch64-linux-gnu.2.17 for ARM64. That the plugin needs cgo and a C toolchain, while the standalone binary is built with CGO_ENABLED=0 for a static result, is a fair summary of the two deployment shapes.
Access rules written as expressions, not conditionals
The reason to run a separate authentication program rather than just doing an OAuth handshake is that something has to decide who is allowed in. That decision is where most VPN authentication tools get vague, and it is where this one is most concrete.
The dependency on cel.dev/cel-go puts a real expression engine inside the binary. CEL is the Common Expression Language, a small language designed to be embedded and evaluated safely without becoming a general purpose scripting environment. Combined with the OIDC and OAuth2 libraries, it means the properties carried in an identity token can be evaluated against policy in the configuration file rather than against a pile of nested if statements in Go code.
The configuration itself is a YAML file, and the repository ships config.example.yaml at the top level as the template. The Configuration Guide in the wiki is where the full key set lives, and the README deliberately keeps the surface small, pointing readers at the wiki for anything specific. The README does not reproduce the rule syntax, so anyone evaluating this project should treat the example file and the wiki as the specification rather than the README.
The other dependencies hint at what the program does beyond policy. github.com/moby/moby/client and the moby API packages suggest it can work against a container runtime, and the integration test image defined in the Makefile as ghcr.io/jkroepke/openvpn-auth-oauth2/it:latest points the same way. github.com/madflojo/testcerts generates test certificates for the integration suite, and stretchr/testify is the assertion library in use.
Container image and downstream packages
The Dockerfile in the repository is small and deliberate, and its header says it is used by goreleaser for the dockers_v2 image, with the binaries built by goreleaser rather than inside the image:
FROM gcr.io/distroless/static-debian13:nonroot
ARG TARGETPLATFORM
COPY ${TARGETPLATFORM}/openvpn-auth-oauth2 /usr/bin/openvpn-auth-oauth2
COPY LICENSE.txt /usr/share/doc/openvpn-auth-oauth2/LICENSE.txt
COPY 3rdpartylicenses/ /usr/share/doc/openvpn-auth-oauth2/3rdpartylicenses/
EXPOSE 9000
ENTRYPOINT ["/usr/bin/openvpn-auth-oauth2"]Four things are worth noting. The base image is a distroless static Debian 13 running as the nonroot user, so there is no shell and no package manager inside the container. The binary is copied from the platform directory that goreleaser produced, which means the image is built after the cross compilation rather than compiling on the target. The licence and the third party licences are copied into the image at /usr/share/doc/openvpn-auth-oauth2, which is how a distroless image can still carry its licence text. And the service exposes port 9000 and runs the binary as its entrypoint.
Beyond containers, the README points to a repology badge, which tracks how many distribution repositories package the project, and the repository has a packaging/ directory for those definitions. There is also a .goreleaser.yaml at the root driving the multi platform builds, and renovate.json for dependency updates.
For anyone evaluating the deployment, the practical takeaway is that both a container image and native packages exist, and the choice is a matter of how the OpenVPN server itself is managed.
First deployment goes through the wiki and a Docker Compose demo
The README does not contain an install command, and it says where to look instead. The Getting Started section lists three links in order: check the OpenVPN server and client requirements, try the project locally with the Docker Compose demo, then follow Getting Started for a first deployment. The complete wiki is organised by task and reachable from the documentation home, which is also the project's homepage.
That structure is the right one for a component with this many moving parts, since the choices involved are which OpenVPN integration to use, which provider to point at, and how the container or package is deployed. The Installation Guide covers package and source build options, the Configuration Guide covers the settings, the OpenVPN Guide covers version requirements, and the Demo page covers the local trial.
For a first look, the Docker Compose demo is the lowest cost path, because it stands up an OpenVPN server and an identity provider on one host without touching production. Since the repository already carries an integration test image and the module depends on testcontainers-go, the same machinery is what the test suite uses to exercise a real server, which is a good sign that the demo path matches the tested path.
The project also keeps an ADOPTERS.md file at the root, and the README invites organisations running it in production to add their name and a short story through a pull request. The list of sponsors at the end of the README includes both recurring and one time contributions, and the acknowledgements name JetBrains IDEs and Sparklabs as supporters of the open source work.
Testing against a real OpenVPN, not a mock
The test strategy is the most telling detail in the repository, and it is a deliberate one. The go.mod file includes github.com/testcontainers-go, which starts real containers for tests rather than simulating them, and the Makefile defines the image used for the integration test as IT_IMAGE := ghcr.io/jkroepke/openvpn-auth-oauth2/it:latest.
The Makefile also shows how the plugin gets built for that container. On a Linux host it builds with CGO_ENABLED=1, and on other hosts it cross compiles the plugin with zig cc, using x86_64-linux-gnu.2.17 for AMD64 and aarch64-linux-gnu.2.17 for ARM64, so the integration test can exercise the plugin path on a developer machine that is not itself Linux.
That means the tests cover the hard part: a plugin loaded into an actual OpenVPN server, with test certificates generated by madflojo/testcerts, talking to a real OIDC provider. The other quality signals in the tree support the same reading. There is a .golangci.yaml for linting, a .codecov.yml with a codecov badge in the README, a .codespellrc for spell checking in CI, an .editorconfig with a matching checker configuration, and a CI workflow badge for the main branch.
The downside of this approach is that the suite is slower and needs a container runtime available, so a contributor without Docker or Podman cannot run the integration tests locally. The README does not document a way to run only the unit tests, so that friction falls on the developer.
Where it fits against the older plugins, and what it costs
The README names three related projects, and they mark the progression of the problem. openvpn-auth-aad by CyberNinjas and openvpn-oidc by vitaliy-sn are earlier attempts at the same idea from other authors, and openvpn-auth-azure-ad by the same author as this project is the predecessor that predates the move to a general OIDC implementation. The difference in approach between them and this project is provider independence: the older tools are tied to one identity provider, while this one targets the protocol rather than a vendor, which is why the provider list includes GitHub, Google Workspace, Authentik, Zitadel and DigitalOcean alongside the enterprise directories.
The limitations follow from that design. It needs an OIDC provider, so a team with no identity provider has nothing to point it at. The stable plugin is offered for Linux AMD64, Linux ARM64 and FreeBSD AMD64, so a Windows or macOS OpenVPN server has to use the management interface path, which changes the deployment shape. OpenVPN version requirements are a separate wiki page, which means the server side has to be new enough for the chosen integration, and the README keeps that requirement out of the main document deliberately.
On maintenance, the record is active. The last push was on 2026-10-16, and the recent releases are v2.3.0 on 2026-10-15, v2.2.3 on 2026-10-08 and v2.2.2 on 2026-10-03, which is a steady release rhythm for a network component. The module is at major version 2, which appears in the import path, so the upgrade from a v1 deployment is a module path change rather than a configuration tweak. The licence is MIT, in the LICENSE.txt file at the root, and the container image carries both that file and the third party licences, so redistribution obligations are visible in the artefact itself. The repository also has a SECURITY.md for reporting problems.
Editorial conclusion
openvpn-auth-oauth2 suits an operator who already runs an OpenVPN server and an identity provider, and who wants per user access rules evaluated from OIDC claims instead of from certificates or static user files. It does not suit a team without an OIDC provider to point it at, a Windows or macOS OpenVPN server, since the stable plugin is offered for Linux AMD64, Linux ARM64 and FreeBSD AMD64 only, and it does not suit anyone who needs the classic username and password flow without a rewrite of the authentication path. Verify first by reading the OpenVPN version requirements page in the wiki, since the server side has to be new enough for the chosen integration, and by running the Docker Compose demo before touching production configuration.
Frequently asked questions
Which identity providers can openvpn-auth-oauth2 use?
The README lists Microsoft Entra ID, GitHub, Okta, Google Workspace, Authentik, Zitadel, DigitalOcean and Keycloak, along with any other OIDC compatible authentication server, and the wiki has a Providers page with the details.
Does openvpn-auth-oauth2 need to be loaded into the OpenVPN server?
No. The README describes two supported options: connecting through the stable plugin on Linux AMD64, Linux ARM64 and FreeBSD AMD64, or connecting directly through the OpenVPN management interface. The Getting Started guide compares both.
How do I install openvpn-auth-oauth2?
The README does not give an install command and points to the Installation Guide in the wiki for package and source build options. There is also a Docker Compose demo described in the wiki for a local trial, and a repology badge tracking distribution packages.
Can access rules be based on OIDC claims?
The module depends on cel.dev/cel-go, the Common Expression Language implementation, which allows policy to be evaluated as expressions, and configuration is done through a YAML file with config.example.yaml at the root as the template. The README does not reproduce the rule syntax, so the wiki Configuration Guide is the reference.
What container image does the project publish?
The Dockerfile builds on gcr.io/distroless/static-debian13:nonroot, copies in the goreleaser built binary, copies LICENSE.txt and the third party licences into /usr/share/doc, exposes port 9000 and runs the binary as its entrypoint.
How does the test suite run?
The go.mod file includes github.com/testcontainers-go and the Makefile defines the integration test image as ghcr.io/jkroepke/openvpn-auth-oauth2/it:latest, so tests run against real containers, including an OpenVPN server with the plugin built for the target platform using CGO or zig cc.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jkroepke-openvpn-auth-oauth2)Was this useful?