From abf4942e8a48dacf3689982d41c23a0bfc3470a9 Mon Sep 17 00:00:00 2001 From: fatedier Date: Thu, 25 Sep 2025 10:19:19 +0800 Subject: [PATCH] auth: enhance OIDC client with TLS and proxy configuration options (#4990) --- Release.md | 1 + client/service.go | 8 ++++- conf/frpc_full_example.toml | 14 ++++++++ pkg/auth/auth.go | 11 +++--- pkg/auth/oidc.go | 71 +++++++++++++++++++++++++++++++++++-- pkg/config/v1/client.go | 11 ++++++ 6 files changed, 108 insertions(+), 8 deletions(-) diff --git a/Release.md b/Release.md index e0f5084e..742c9847 100644 --- a/Release.md +++ b/Release.md @@ -1,3 +1,4 @@ ## Features * Add NAT traversal configuration options for XTCP proxies and visitors. Support disabling assisted addresses to avoid using slow VPN connections during NAT hole punching. +* Enhanced OIDC client configuration with support for custom TLS certificate verification and proxy settings. Added `trustedCaFile`, `insecureSkipVerify`, and `proxyURL` options for OIDC token endpoint connections. diff --git a/client/service.go b/client/service.go index 9e1833b9..f906e4d0 100644 --- a/client/service.go +++ b/client/service.go @@ -149,9 +149,15 @@ func NewService(options ServiceOptions) (*Service, error) { } webServer = ws } + + authSetter, err := auth.NewAuthSetter(options.Common.Auth) + if err != nil { + return nil, err + } + s := &Service{ ctx: context.Background(), - authSetter: auth.NewAuthSetter(options.Common.Auth), + authSetter: authSetter, webServer: webServer, common: options.Common, configFilePath: options.ConfigFilePath, diff --git a/conf/frpc_full_example.toml b/conf/frpc_full_example.toml index dfae9573..6b86907e 100644 --- a/conf/frpc_full_example.toml +++ b/conf/frpc_full_example.toml @@ -55,6 +55,20 @@ auth.token = "12345678" # auth.oidc.additionalEndpointParams.audience = "https://dev.auth.com/api/v2/" # auth.oidc.additionalEndpointParams.var1 = "foobar" +# OIDC TLS and proxy configuration +# Specify a custom CA certificate file for verifying the OIDC token endpoint's TLS certificate. +# This is useful when the OIDC provider uses a self-signed certificate or a custom CA. +# auth.oidc.trustedCaFile = "/path/to/ca.crt" + +# Skip TLS certificate verification for the OIDC token endpoint. +# INSECURE: Only use this for debugging purposes, not recommended for production. +# auth.oidc.insecureSkipVerify = false + +# Specify a proxy server for OIDC token endpoint connections. +# Supports http, https, socks5, and socks5h proxy protocols. +# If not specified, no proxy is used for OIDC connections. +# auth.oidc.proxyURL = "http://proxy.example.com:8080" + # Set admin address for control frpc's action by http api such as reload webServer.addr = "127.0.0.1" webServer.port = 7400 diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index ae706986..b954fc80 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -27,16 +27,19 @@ type Setter interface { SetNewWorkConn(*msg.NewWorkConn) error } -func NewAuthSetter(cfg v1.AuthClientConfig) (authProvider Setter) { +func NewAuthSetter(cfg v1.AuthClientConfig) (authProvider Setter, err error) { switch cfg.Method { case v1.AuthMethodToken: authProvider = NewTokenAuth(cfg.AdditionalScopes, cfg.Token) case v1.AuthMethodOIDC: - authProvider = NewOidcAuthSetter(cfg.AdditionalScopes, cfg.OIDC) + authProvider, err = NewOidcAuthSetter(cfg.AdditionalScopes, cfg.OIDC) + if err != nil { + return nil, err + } default: - panic(fmt.Sprintf("wrong method: '%s'", cfg.Method)) + return nil, fmt.Errorf("unsupported auth method: %s", cfg.Method) } - return authProvider + return authProvider, nil } type Verifier interface { diff --git a/pkg/auth/oidc.go b/pkg/auth/oidc.go index 40ce060f..c5f63640 100644 --- a/pkg/auth/oidc.go +++ b/pkg/auth/oidc.go @@ -16,23 +16,72 @@ package auth import ( "context" + "crypto/tls" + "crypto/x509" "fmt" + "net/http" + "net/url" + "os" "slices" "github.com/coreos/go-oidc/v3/oidc" + "golang.org/x/oauth2" "golang.org/x/oauth2/clientcredentials" v1 "github.com/fatedier/frp/pkg/config/v1" "github.com/fatedier/frp/pkg/msg" ) +// createOIDCHTTPClient creates an HTTP client with custom TLS and proxy configuration for OIDC token requests +func createOIDCHTTPClient(trustedCAFile string, insecureSkipVerify bool, proxyURL string) (*http.Client, error) { + // Clone the default transport to get all reasonable defaults + transport := http.DefaultTransport.(*http.Transport).Clone() + + // Configure TLS settings + if trustedCAFile != "" || insecureSkipVerify { + tlsConfig := &tls.Config{ + InsecureSkipVerify: insecureSkipVerify, + } + + if trustedCAFile != "" && !insecureSkipVerify { + caCert, err := os.ReadFile(trustedCAFile) + if err != nil { + return nil, fmt.Errorf("failed to read OIDC CA certificate file %q: %w", trustedCAFile, err) + } + + caCertPool := x509.NewCertPool() + if !caCertPool.AppendCertsFromPEM(caCert) { + return nil, fmt.Errorf("failed to parse OIDC CA certificate from file %q", trustedCAFile) + } + + tlsConfig.RootCAs = caCertPool + } + transport.TLSClientConfig = tlsConfig + } + + // Configure proxy settings + if proxyURL != "" { + parsedURL, err := url.Parse(proxyURL) + if err != nil { + return nil, fmt.Errorf("failed to parse OIDC proxy URL %q: %w", proxyURL, err) + } + transport.Proxy = http.ProxyURL(parsedURL) + } else { + // Explicitly disable proxy to override DefaultTransport's ProxyFromEnvironment + transport.Proxy = nil + } + + return &http.Client{Transport: transport}, nil +} + type OidcAuthProvider struct { additionalAuthScopes []v1.AuthScope tokenGenerator *clientcredentials.Config + httpClient *http.Client } -func NewOidcAuthSetter(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCClientConfig) *OidcAuthProvider { +func NewOidcAuthSetter(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCClientConfig) (*OidcAuthProvider, error) { eps := make(map[string][]string) for k, v := range cfg.AdditionalEndpointParams { eps[k] = []string{v} @@ -50,14 +99,30 @@ func NewOidcAuthSetter(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCClien EndpointParams: eps, } + // Create custom HTTP client if needed + var httpClient *http.Client + if cfg.TrustedCaFile != "" || cfg.InsecureSkipVerify || cfg.ProxyURL != "" { + var err error + httpClient, err = createOIDCHTTPClient(cfg.TrustedCaFile, cfg.InsecureSkipVerify, cfg.ProxyURL) + if err != nil { + return nil, fmt.Errorf("failed to create OIDC HTTP client: %w", err) + } + } + return &OidcAuthProvider{ additionalAuthScopes: additionalAuthScopes, tokenGenerator: tokenGenerator, - } + httpClient: httpClient, + }, nil } func (auth *OidcAuthProvider) generateAccessToken() (accessToken string, err error) { - tokenObj, err := auth.tokenGenerator.Token(context.Background()) + ctx := context.Background() + if auth.httpClient != nil { + ctx = context.WithValue(ctx, oauth2.HTTPClient, auth.httpClient) + } + + tokenObj, err := auth.tokenGenerator.Token(ctx) if err != nil { return "", fmt.Errorf("couldn't generate OIDC token for login: %v", err) } diff --git a/pkg/config/v1/client.go b/pkg/config/v1/client.go index a830df99..c6cf97a6 100644 --- a/pkg/config/v1/client.go +++ b/pkg/config/v1/client.go @@ -228,6 +228,17 @@ type AuthOIDCClientConfig struct { // AdditionalEndpointParams specifies additional parameters to be sent // this field will be transfer to map[string][]string in OIDC token generator. AdditionalEndpointParams map[string]string `json:"additionalEndpointParams,omitempty"` + + // TrustedCaFile specifies the path to a custom CA certificate file + // for verifying the OIDC token endpoint's TLS certificate. + TrustedCaFile string `json:"trustedCaFile,omitempty"` + // InsecureSkipVerify disables TLS certificate verification for the + // OIDC token endpoint. Only use this for debugging, not recommended for production. + InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"` + // ProxyURL specifies a proxy to use when connecting to the OIDC token endpoint. + // Supports http, https, socks5, and socks5h proxy protocols. + // If empty, no proxy is used for OIDC connections. + ProxyURL string `json:"proxyURL,omitempty"` } type VirtualNetConfig struct {