From f53d0b3d7f696b27c6bcf3177018b9a96c002276 Mon Sep 17 00:00:00 2001 From: Christoph Haas Date: Sun, 12 Oct 2025 15:09:40 +0200 Subject: [PATCH] add the possibility to debug oauth or oidc login issues (#541) --- docs/documentation/configuration/overview.md | 30 ++++++++++------ internal/app/auth/auth_oauth.go | 30 ++++++++++------ internal/app/auth/auth_oidc.go | 38 ++++++++++++++------ internal/config/auth.go | 8 +++++ 4 files changed, 76 insertions(+), 30 deletions(-) diff --git a/docs/documentation/configuration/overview.md b/docs/documentation/configuration/overview.md index 4f1f49a..75331f7 100644 --- a/docs/documentation/configuration/overview.md +++ b/docs/documentation/configuration/overview.md @@ -512,13 +512,18 @@ Below are the properties for each OIDC provider entry inside `auth.oidc`: - `admin_group_regex`: A regular expression to match the `user_groups` claim. Each entry in the `user_groups` claim is checked against this regex. #### `registration_enabled` -- **Default:** *(empty)* +- **Default:** `false` - **Description:** If `true`, a new user will be created in WireGuard Portal if not already present. #### `log_user_info` -- **Default:** *(empty)* +- **Default:** `false` - **Description:** If `true`, OIDC user data is logged at the trace level upon login (for debugging). +#### `log_sensitive_info` +- **Default:** `false` +- **Description:** If `true`, sensitive OIDC user data, such as tokens and raw responses, will be logged at the trace level upon login (for debugging). +- **Important:** Keep this setting disabled in production environments! Remove logs once you finished debugging authentication issues. + --- ### OAuth @@ -585,13 +590,18 @@ Below are the properties for each OAuth provider entry inside `auth.oauth`: - `admin_group_regex`: A regular expression to match the `user_groups` claim. Each entry in the `user_groups` claim is checked against this regex. #### `registration_enabled` -- **Default:** *(empty)* +- **Default:** `false` - **Description:** If `true`, new users are created automatically on successful login. #### `log_user_info` -- **Default:** *(empty)* +- **Default:** `false` - **Description:** If `true`, logs user info at the trace level upon login. +#### `log_sensitive_info` +- **Default:** `false` +- **Description:** If `true`, sensitive OIDC user data, such as tokens and raw responses, will be logged at the trace level upon login (for debugging). +- **Important:** Keep this setting disabled in production environments! Remove logs once you finished debugging authentication issues. + --- ### LDAP @@ -608,11 +618,11 @@ Below are the properties for each LDAP provider entry inside `auth.ldap`: - **Description:** The LDAP server URL (e.g., `ldap://srv-ad01.company.local:389`). #### `start_tls` -- **Default:** *(empty)* +- **Default:** `false` - **Description:** If `true`, use STARTTLS to secure the LDAP connection. #### `cert_validation` -- **Default:** *(empty)* +- **Default:** `false` - **Description:** If `true`, validate the LDAP server’s TLS certificate. #### `tls_certificate_path` @@ -682,19 +692,19 @@ Below are the properties for each LDAP provider entry inside `auth.ldap`: ``` #### `disable_missing` -- **Default:** *(empty)* +- **Default:** `false` - **Description:** If `true`, any user **not** found in LDAP (during sync) is disabled in WireGuard Portal. #### `auto_re_enable` -- **Default:** *(empty)* +- **Default:** `false` - **Description:** If `true`, users that where disabled because they were missing (see `disable_missing`) will be re-enabled once they are found again. #### `registration_enabled` -- **Default:** *(empty)* +- **Default:** `false` - **Description:** If `true`, new user accounts are created in WireGuard Portal upon first login. #### `log_user_info` -- **Default:** *(empty)* +- **Default:** `false` - **Description:** If `true`, logs LDAP user data at the trace level upon login. --- diff --git a/internal/app/auth/auth_oauth.go b/internal/app/auth/auth_oauth.go index 56d53c5..92b170f 100644 --- a/internal/app/auth/auth_oauth.go +++ b/internal/app/auth/auth_oauth.go @@ -19,15 +19,16 @@ import ( // PlainOauthAuthenticator is an authenticator that uses OAuth for authentication. // User information is retrieved from the specified user info endpoint. type PlainOauthAuthenticator struct { - name string - cfg *oauth2.Config - userInfoEndpoint string - client *http.Client - userInfoMapping config.OauthFields - userAdminMapping *config.OauthAdminMapping - registrationEnabled bool - userInfoLogging bool - allowedDomains []string + name string + cfg *oauth2.Config + userInfoEndpoint string + client *http.Client + userInfoMapping config.OauthFields + userAdminMapping *config.OauthAdminMapping + registrationEnabled bool + userInfoLogging bool + sensitiveInfoLogging bool + allowedDomains []string } func newPlainOauthAuthenticator( @@ -57,6 +58,7 @@ func newPlainOauthAuthenticator( provider.userAdminMapping = &cfg.AdminMapping provider.registrationEnabled = cfg.RegistrationEnabled provider.userInfoLogging = cfg.LogUserInfo + provider.sensitiveInfoLogging = cfg.LogSensitiveInfo provider.allowedDomains = cfg.AllowedDomains return provider, nil @@ -110,6 +112,10 @@ func (p PlainOauthAuthenticator) GetUserInfo( response, err := p.client.Do(req) if err != nil { + if p.sensitiveInfoLogging { + slog.Debug("OAuth: failed to get user info", "endpoint", p.userInfoEndpoint, + "token", token, "error", err) + } return nil, fmt.Errorf("failed to get user info: %w", err) } defer internal.LogClose(response.Body) @@ -121,11 +127,15 @@ func (p PlainOauthAuthenticator) GetUserInfo( var userFields map[string]any err = json.Unmarshal(contents, &userFields) if err != nil { + if p.sensitiveInfoLogging { + slog.Debug("OAuth: failed to parse user info", "endpoint", p.userInfoEndpoint, + "token", token, "contents", contents, "error", err) + } return nil, fmt.Errorf("failed to parse user info: %w", err) } if p.userInfoLogging { - slog.Debug("OAuth user info", + slog.Debug("OAuth: user info debug", "source", p.name, "info", string(contents)) } diff --git a/internal/app/auth/auth_oidc.go b/internal/app/auth/auth_oidc.go index 0a4ecb0..571fd40 100644 --- a/internal/app/auth/auth_oidc.go +++ b/internal/app/auth/auth_oidc.go @@ -16,15 +16,16 @@ import ( // OidcAuthenticator is an authenticator for OpenID Connect providers. type OidcAuthenticator struct { - name string - provider *oidc.Provider - verifier *oidc.IDTokenVerifier - cfg *oauth2.Config - userInfoMapping config.OauthFields - userAdminMapping *config.OauthAdminMapping - registrationEnabled bool - userInfoLogging bool - allowedDomains []string + name string + provider *oidc.Provider + verifier *oidc.IDTokenVerifier + cfg *oauth2.Config + userInfoMapping config.OauthFields + userAdminMapping *config.OauthAdminMapping + registrationEnabled bool + userInfoLogging bool + sensitiveInfoLogging bool + allowedDomains []string } func newOidcAuthenticator( @@ -58,6 +59,7 @@ func newOidcAuthenticator( provider.userAdminMapping = &cfg.AdminMapping provider.registrationEnabled = cfg.RegistrationEnabled provider.userInfoLogging = cfg.LogUserInfo + provider.sensitiveInfoLogging = cfg.LogSensitiveInfo provider.allowedDomains = cfg.AllowedDomains return provider, nil @@ -102,24 +104,40 @@ func (o OidcAuthenticator) GetUserInfo(ctx context.Context, token *oauth2.Token, ) { rawIDToken, ok := token.Extra("id_token").(string) if !ok { + if o.sensitiveInfoLogging { + slog.Debug("OIDC: token does not contain id_token", "token", token, "nonce", nonce) + } return nil, errors.New("token does not contain id_token") } idToken, err := o.verifier.Verify(ctx, rawIDToken) if err != nil { + if o.sensitiveInfoLogging { + slog.Debug("OIDC: failed to validate id_token", "token", token, "id_token", rawIDToken, "nonce", nonce, + "error", + err) + } return nil, fmt.Errorf("failed to validate id_token: %w", err) } if idToken.Nonce != nonce { + if o.sensitiveInfoLogging { + slog.Debug("OIDC: id_token nonce mismatch", "token", token, "id_token", idToken, "nonce", nonce) + } return nil, errors.New("nonce mismatch") } var tokenFields map[string]any if err = idToken.Claims(&tokenFields); err != nil { + if o.sensitiveInfoLogging { + slog.Debug("OIDC: failed to parse extra claims", "token", token, "id_token", idToken, "nonce", nonce, + "error", + err) + } return nil, fmt.Errorf("failed to parse extra claims: %w", err) } if o.userInfoLogging { contents, _ := json.Marshal(tokenFields) - slog.Debug("OIDC user info", + slog.Debug("OIDC: user info debug", "source", o.name, "info", string(contents)) } diff --git a/internal/config/auth.go b/internal/config/auth.go index ef1b994..6f3892b 100644 --- a/internal/config/auth.go +++ b/internal/config/auth.go @@ -211,6 +211,10 @@ type OpenIDConnectProvider struct { // If LogUserInfo is set to true, the user info retrieved from the OIDC provider will be logged in trace level. LogUserInfo bool `yaml:"log_user_info"` + + // If LogSensitiveInfo is set to true, sensitive information retrieved from the OIDC provider will be logged in trace level. + // This also includes OAuth tokens! Keep this disabled in production! + LogSensitiveInfo bool `yaml:"log_sensitive_info"` } // OAuthProvider contains the configuration for the OAuth provider. @@ -252,6 +256,10 @@ type OAuthProvider struct { // If LogUserInfo is set to true, the user info retrieved from the OAuth provider will be logged in trace level. LogUserInfo bool `yaml:"log_user_info"` + + // If LogSensitiveInfo is set to true, sensitive information retrieved from the OAuth provider will be logged in trace level. + // This also includes OAuth tokens! Keep this disabled in production! + LogSensitiveInfo bool `yaml:"log_sensitive_info"` } // WebauthnConfig contains the configuration for the WebAuthn authenticator.