diff --git a/weed/iam/oidc/oidc_provider.go b/weed/iam/oidc/oidc_provider.go index f39a74a34..1f5db8605 100644 --- a/weed/iam/oidc/oidc_provider.go +++ b/weed/iam/oidc/oidc_provider.go @@ -5,12 +5,16 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" + "crypto/tls" + "crypto/x509" "encoding/base64" "encoding/json" "errors" "fmt" "math/big" "net/http" + "os" + "path/filepath" "strings" "time" @@ -58,6 +62,13 @@ type OIDCConfig struct { // JWKSCacheTTLSeconds sets how long to cache JWKS before refresh (default 3600 seconds) JWKSCacheTTLSeconds int `json:"jwksCacheTTLSeconds,omitempty"` + + // TLSCACert is the path to the CA certificate file for custom/self-signed certificates + TLSCACert string `json:"tlsCaCert,omitempty"` + + // TLSInsecureSkipVerify controls whether to skip TLS verification. + // WARNING: Should only be used in development/testing environments. Never use in production. + TLSInsecureSkipVerify bool `json:"tlsInsecureSkipVerify,omitempty"` } // JWKS represents JSON Web Key Set @@ -124,6 +135,45 @@ func (p *OIDCProvider) Initialize(config interface{}) error { p.jwksTTL = time.Hour } + // Configure HTTP client with TLS settings + tlsConfig := &tls.Config{ + InsecureSkipVerify: oidcConfig.TLSInsecureSkipVerify, + MinVersion: tls.VersionTLS12, // Prevent TLS downgrade attacks + } + + if oidcConfig.TLSInsecureSkipVerify { + glog.Warningf("OIDC provider %q is configured to skip TLS verification. This is insecure and should not be used in production.", p.name) + } + + if oidcConfig.TLSCACert != "" { + // Validate that the CA cert path is absolute to prevent reading unintended files + if !filepath.IsAbs(oidcConfig.TLSCACert) { + return fmt.Errorf("TLSCACert must be an absolute path, got: %s", oidcConfig.TLSCACert) + } + + caCert, err := os.ReadFile(oidcConfig.TLSCACert) + if err != nil { + return fmt.Errorf("failed to read CA cert file: %w", err) + } + // Start with the system cert pool to trust public CAs, then add the custom one. + rootCAs, _ := x509.SystemCertPool() + if rootCAs == nil { + rootCAs = x509.NewCertPool() + } + if !rootCAs.AppendCertsFromPEM(caCert) { + return fmt.Errorf("failed to append CA cert from file: %s", oidcConfig.TLSCACert) + } + tlsConfig.RootCAs = rootCAs + } + + transport := &http.Transport{ + TLSClientConfig: tlsConfig, + } + p.httpClient = &http.Client{ + Timeout: 30 * time.Second, + Transport: transport, + } + // For testing, we'll skip the actual OIDC client initialization return nil } diff --git a/weed/iam/sts/constants.go b/weed/iam/sts/constants.go index 1f74668eb..b54951903 100644 --- a/weed/iam/sts/constants.go +++ b/weed/iam/sts/constants.go @@ -49,8 +49,10 @@ const ( ConfigFieldClientSecret = "clientSecret" ConfigFieldJWKSUri = "jwksUri" ConfigFieldScopes = "scopes" - ConfigFieldUserInfoUri = "userInfoUri" - ConfigFieldRedirectUri = "redirectUri" + ConfigFieldUserInfoUri = "userInfoUri" + ConfigFieldRedirectUri = "redirectUri" + ConfigFieldTLSCACert = "tlsCaCert" + ConfigFieldTLSInsecureSkipVerify = "tlsInsecureSkipVerify" ) // Error Messages diff --git a/weed/iam/sts/provider_factory.go b/weed/iam/sts/provider_factory.go index 0733afdba..83808c58f 100644 --- a/weed/iam/sts/provider_factory.go +++ b/weed/iam/sts/provider_factory.go @@ -115,6 +115,14 @@ func (f *ProviderFactory) convertToOIDCConfig(configMap map[string]interface{}) config.Scopes = scopes } + if tlsCaCert, ok := configMap[ConfigFieldTLSCACert].(string); ok { + config.TLSCACert = tlsCaCert + } + + if tlsInsecureSkipVerify, ok := configMap[ConfigFieldTLSInsecureSkipVerify].(bool); ok { + config.TLSInsecureSkipVerify = tlsInsecureSkipVerify + } + // Convert claims mapping if claimsMapInterface, ok := configMap["claimsMapping"]; ok { claimsMap, err := f.convertToStringMap(claimsMapInterface)