VYPR
Critical severity10.0GHSA Advisory· Published May 8, 2026· Updated May 13, 2026

CVE-2026-41070

CVE-2026-41070

Description

openvpn-auth-oauth2 is a plugin/management interface client for OpenVPN server to handle an OIDC based single sign-on (SSO) auth flows. From version 1.26.3 to before version 1.27.3, when openvpn-auth-oauth2 is deployed in the experimental plugin mode (shared library loaded by OpenVPN via the plugin directive), clients that do not support WebAuth/SSO (e.g., the openvpn CLI on Linux) are incorrectly admitted to the VPN despite being denied by the authentication logic. The default management-interface mode is not affected because it does not use the OpenVPN plugin return-code mechanism. This issue has been patched in version 1.27.3.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/jkroepke/openvpn-auth-oauth2Go
>= 1.26.3, < 1.27.31.27.3

Affected products

1

Patches

1
36f69a6c67c1

fix: plugin returns FUNC_SUCCESS on client-deny, allowing unauthenticated connections (#829)

2 files changed · +134 3
  • lib/openvpn-auth-oauth2/openvpn/handle.go+1 3 modified
    @@ -144,11 +144,9 @@ func (p *PluginHandle) handleAuthUserPassVerify(clientEnvList **c.Char, perClien
     			logger.ErrorContext(p.ctx, "write to auth file",
     				slog.Any("err", err),
     			)
    -
    -			return c.OpenVPNPluginFuncError
     		}
     
    -		return c.OpenVPNPluginFuncSuccess
    +		return c.OpenVPNPluginFuncError
     	case management.ClientAuthPending:
     		pendingRespCh, err := p.managementClient.RegisterPendingPoller(currentClientID)
     		if err != nil {
    
  • lib/openvpn-auth-oauth2/openvpn/plugin_test.go+133 0 modified
    @@ -271,6 +271,139 @@ func TestPlugin(t *testing.T) {
     	}
     }
     
    +// TestPluginDenyNonWebAuthClient verifies that a client not supporting webauth
    +// receives OPENVPN_PLUGIN_FUNC_ERROR so that OpenVPN rejects the connection.
    +// Previously the plugin returned OPENVPN_PLUGIN_FUNC_SUCCESS on denial, which
    +// caused OpenVPN to let the client in despite the management-interface deny.
    +func TestPluginDenyNonWebAuthClient(t *testing.T) {
    +	t.Parallel()
    +
    +	unixSocket, err := nettest.LocalPath()
    +	require.NoError(t, err)
    +
    +	passwordFile, err := os.CreateTemp(t.TempDir(), "openvpn-auth-oauth2-test-")
    +	require.NoError(t, err)
    +	t.Cleanup(func() {
    +		_ = passwordFile.Close()
    +	})
    +
    +	_, err = passwordFile.WriteString("password")
    +	require.NoError(t, err)
    +
    +	argv, cStrings := testutil.CreateCStringArray([]string{"openvpn-auth-oauth2", "unix://" + unixSocket, passwordFile.Name()})
    +
    +	t.Cleanup(func() {
    +		testutil.FreeCStringArray(argv, cStrings)
    +	})
    +
    +	openArgs := &c.OpenVPNPluginArgsOpenIn{
    +		Callbacks: testutil.Callbacks(),
    +		Argv:      argv,
    +	}
    +	openRet := &c.OpenVPNPluginArgsOpenReturn{}
    +
    +	status := PluginOpenV3(PluginStructVerMin, openArgs, openRet)
    +	require.Equal(t, c.OpenVPNPluginFuncSuccess, status)
    +
    +	t.Cleanup(func() {
    +		PluginCloseV1(openRet.Handle)
    +	})
    +
    +	args := &c.OpenVPNPluginArgsFuncIn{
    +		Handle: openRet.Handle,
    +		Argv:   argv,
    +		Type:   c.OpenVPNPluginUp,
    +	}
    +	ret := &c.OpenVPNPluginArgsFuncReturn{}
    +
    +	status = PluginFuncV3(PluginStructVerMin, args, ret)
    +	require.Equal(t, c.OpenVPNPluginFuncSuccess, status)
    +
    +	logger := testsuite.NewTestLogger()
    +
    +	// clientListener must not be closed, because it is used by the httpClientListener.
    +	clientListener, err := nettest.NewLocalListener("tcp")
    +	require.NoError(t, err)
    +
    +	_, resourceServerURL, clientCredentials, err := testutils.SetupResourceServer(t, clientListener, logger.Logger, nil)
    +	require.NoError(t, err)
    +
    +	conf := config.Defaults
    +	conf.OpenVPN.Addr = types.URL{URL: &url.URL{Scheme: "unix", Path: unixSocket}}
    +	conf.OpenVPN.Password = "password"
    +	conf.OAuth2.OpenVPNUsernameClaim = testsuite.SubjectClaim
    +	conf.HTTP.BaseURL = types.URL{URL: &url.URL{Scheme: "http", Host: clientListener.Addr().String()}}
    +	conf.HTTP.Secret = testsuite.Secret
    +	conf.OAuth2.Issuer = resourceServerURL
    +	conf.OAuth2.Nonce = true
    +	conf.OAuth2.RefreshNonce = config.OAuth2RefreshNonceEmpty
    +	conf.OAuth2.Client.ID = clientCredentials.ID
    +	conf.OAuth2.Client.Secret = clientCredentials.Secret
    +
    +	tokenStorage := tokenstorage.NewInMemory(testsuite.Secret, time.Hour)
    +	_, openVPNClient := testutils.SetupOpenVPNOAuth2Clients(t.Context(), t, conf, logger.Logger, http.DefaultClient, tokenStorage)
    +
    +	errOpenVPNClientCh := make(chan error, 1)
    +
    +	go func(errCh chan<- error) {
    +		errCh <- openVPNClient.Connect(t.Context())
    +	}(errOpenVPNClientCh)
    +
    +	select {
    +	case err := <-errOpenVPNClientCh:
    +		require.NoError(t, err)
    +	default:
    +	}
    +
    +	time.Sleep(50 * time.Millisecond)
    +
    +	clientContextPtr := PluginClientConstructorV1(openRet.Handle)
    +	require.NotNil(t, clientContextPtr)
    +
    +	authControlFile, err := os.CreateTemp(t.TempDir(), "auth_control_file")
    +	require.NoError(t, err)
    +	t.Cleanup(func() {
    +		require.NoError(t, authControlFile.Close())
    +	})
    +
    +	// Client without webauth support (no IV_SSO=webauth)
    +	envp, envCStrings := testutil.CreateCStringArray([]string{
    +		"n_clients=0",
    +		"password=",
    +		"session_id=SESSIONID",
    +		"untrusted_port=17016",
    +		"untrusted_ip=192.168.65.1",
    +		"common_name=user@example.com",
    +		"username=",
    +		"session_state=Initial",
    +		"auth_control_file=" + authControlFile.Name(),
    +	})
    +
    +	t.Cleanup(func() {
    +		testutil.FreeCStringArray(envp, envCStrings)
    +	})
    +
    +	args = &c.OpenVPNPluginArgsFuncIn{
    +		Handle:           openRet.Handle,
    +		Argv:             argv,
    +		Envp:             envp,
    +		Type:             c.OpenVPNPluginAuthUserPassVerify,
    +		PerClientContext: clientContextPtr,
    +	}
    +	ret = &c.OpenVPNPluginArgsFuncReturn{}
    +
    +	// A client without webauth support must be denied: the plugin must return ERROR.
    +	status = PluginFuncV3(PluginStructVerMin, args, ret)
    +	require.Equal(t, c.OpenVPNPluginFuncError, status, logger.String())
    +
    +	// The auth control file must reflect the denial.
    +	data, err := os.ReadFile(authControlFile.Name())
    +	require.NoError(t, err)
    +	require.Equal(t, "0", string(data))
    +
    +	PluginClientDestructorV1(args.Handle, clientContextPtr)
    +}
    +
     func TestPluginOpenV3_InvalidArgs(t *testing.T) {
     	t.Parallel()
     
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.