auth: enhance OIDC client with TLS and proxy configuration options (#4990)

pull/4997/head
fatedier 2025-09-25 10:19:19 +08:00 committed by GitHub
parent 7cfa546b55
commit abf4942e8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 108 additions and 8 deletions

View File

@ -1,3 +1,4 @@
## Features ## 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. * 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.

View File

@ -149,9 +149,15 @@ func NewService(options ServiceOptions) (*Service, error) {
} }
webServer = ws webServer = ws
} }
authSetter, err := auth.NewAuthSetter(options.Common.Auth)
if err != nil {
return nil, err
}
s := &Service{ s := &Service{
ctx: context.Background(), ctx: context.Background(),
authSetter: auth.NewAuthSetter(options.Common.Auth), authSetter: authSetter,
webServer: webServer, webServer: webServer,
common: options.Common, common: options.Common,
configFilePath: options.ConfigFilePath, configFilePath: options.ConfigFilePath,

View File

@ -55,6 +55,20 @@ auth.token = "12345678"
# auth.oidc.additionalEndpointParams.audience = "https://dev.auth.com/api/v2/" # auth.oidc.additionalEndpointParams.audience = "https://dev.auth.com/api/v2/"
# auth.oidc.additionalEndpointParams.var1 = "foobar" # 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 # Set admin address for control frpc's action by http api such as reload
webServer.addr = "127.0.0.1" webServer.addr = "127.0.0.1"
webServer.port = 7400 webServer.port = 7400

View File

@ -27,16 +27,19 @@ type Setter interface {
SetNewWorkConn(*msg.NewWorkConn) error SetNewWorkConn(*msg.NewWorkConn) error
} }
func NewAuthSetter(cfg v1.AuthClientConfig) (authProvider Setter) { func NewAuthSetter(cfg v1.AuthClientConfig) (authProvider Setter, err error) {
switch cfg.Method { switch cfg.Method {
case v1.AuthMethodToken: case v1.AuthMethodToken:
authProvider = NewTokenAuth(cfg.AdditionalScopes, cfg.Token) authProvider = NewTokenAuth(cfg.AdditionalScopes, cfg.Token)
case v1.AuthMethodOIDC: case v1.AuthMethodOIDC:
authProvider = NewOidcAuthSetter(cfg.AdditionalScopes, cfg.OIDC) authProvider, err = NewOidcAuthSetter(cfg.AdditionalScopes, cfg.OIDC)
default: if err != nil {
panic(fmt.Sprintf("wrong method: '%s'", cfg.Method)) return nil, err
} }
return authProvider default:
return nil, fmt.Errorf("unsupported auth method: %s", cfg.Method)
}
return authProvider, nil
} }
type Verifier interface { type Verifier interface {

View File

@ -16,23 +16,72 @@ package auth
import ( import (
"context" "context"
"crypto/tls"
"crypto/x509"
"fmt" "fmt"
"net/http"
"net/url"
"os"
"slices" "slices"
"github.com/coreos/go-oidc/v3/oidc" "github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials" "golang.org/x/oauth2/clientcredentials"
v1 "github.com/fatedier/frp/pkg/config/v1" v1 "github.com/fatedier/frp/pkg/config/v1"
"github.com/fatedier/frp/pkg/msg" "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 { type OidcAuthProvider struct {
additionalAuthScopes []v1.AuthScope additionalAuthScopes []v1.AuthScope
tokenGenerator *clientcredentials.Config 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) eps := make(map[string][]string)
for k, v := range cfg.AdditionalEndpointParams { for k, v := range cfg.AdditionalEndpointParams {
eps[k] = []string{v} eps[k] = []string{v}
@ -50,14 +99,30 @@ func NewOidcAuthSetter(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCClien
EndpointParams: eps, EndpointParams: eps,
} }
return &OidcAuthProvider{ // Create custom HTTP client if needed
additionalAuthScopes: additionalAuthScopes, var httpClient *http.Client
tokenGenerator: tokenGenerator, 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) { 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 { if err != nil {
return "", fmt.Errorf("couldn't generate OIDC token for login: %v", err) return "", fmt.Errorf("couldn't generate OIDC token for login: %v", err)
} }

View File

@ -228,6 +228,17 @@ type AuthOIDCClientConfig struct {
// AdditionalEndpointParams specifies additional parameters to be sent // AdditionalEndpointParams specifies additional parameters to be sent
// this field will be transfer to map[string][]string in OIDC token generator. // this field will be transfer to map[string][]string in OIDC token generator.
AdditionalEndpointParams map[string]string `json:"additionalEndpointParams,omitempty"` 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 { type VirtualNetConfig struct {