This is an automated email from the ASF dual-hosted git repository.

klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new dbd4ade71 fix(server): ignore Basic auth header when AUTH_ENABLED=true 
(#9147)
dbd4ade71 is described below

commit dbd4ade71c4e429954ed02012a768f72215a1643
Author: Oleksii Milchenko <[email protected]>
AuthorDate: Sun Sep 20 10:25:37 2026 +0300

    fix(server): ignore Basic auth header when AUTH_ENABLED=true (#9147)
    
    OAuth2ProxyAuthentication turns any "Authorization: Basic user:pass"
    header into a user without checking the password. That was fine while
    nginx in config-ui verified the credentials against ADMIN_USER/ADMIN_PASS
    before proxying, and the lake only kept the username for audit fields.
    
    #8854 put auth.RequireAuth behind it. With AUTH_ENABLED=true that gate now
    accepts the unverified Basic user, so any username:password pair reaches
    every non-public endpoint (blueprints, connections, pipelines) on a lake
    that is exposed directly. #8880 closed the same hole for X-Forwarded-User
    with FORWARDED_USER_SECRET but left the Basic branch untouched.
    
    Only consult the Basic header when AUTH_ENABLED is off, which is the
    legacy nginx mode. With AUTH_ENABLED the lake authenticates callers
    itself: session cookie, API key, or forwarded headers with the shared
    secret, exactly what auth.go already logs as the supported set.
    
    Upstream: not yet submitted.
---
 backend/server/api/middlewares.go                  | 11 +++-
 backend/server/api/middlewares_basicauth_test.go   | 59 ++++++++++++++++++++++
 .../server/api/middlewares_forwardsecret_test.go   |  6 ++-
 3 files changed, 73 insertions(+), 3 deletions(-)

diff --git a/backend/server/api/middlewares.go 
b/backend/server/api/middlewares.go
index 567e3a9f4..b460c6764 100644
--- a/backend/server/api/middlewares.go
+++ b/backend/server/api/middlewares.go
@@ -93,7 +93,14 @@ func getBasicAuthUserInfo(c *gin.Context, basicRes 
context.BasicRes) (*common.Us
 
 func OAuth2ProxyAuthentication(basicRes context.BasicRes) gin.HandlerFunc {
        logger := basicRes.GetLogger()
-       forwardedUserSecret := 
strings.TrimSpace(basicRes.GetConfigReader().GetString("FORWARDED_USER_SECRET"))
+       cfg := basicRes.GetConfigReader()
+       forwardedUserSecret := 
strings.TrimSpace(cfg.GetString("FORWARDED_USER_SECRET"))
+       // A Basic header is only an identity nginx (config-ui) has already 
verified
+       // against ADMIN_USER/ADMIN_PASS; the lake never checks the password. 
With
+       // AUTH_ENABLED the lake is the authenticator (session cookie, API key, 
or
+       // forwarded headers with the shared secret), so an unverified Basic 
header
+       // must not become a user, or any username:password passes RequireAuth.
+       trustBasicAuth := !(cfg.IsSet("AUTH_ENABLED") && 
cfg.GetBool("AUTH_ENABLED"))
        return func(c *gin.Context) {
                _, exist := c.Get(common.USER)
                if !exist {
@@ -101,7 +108,7 @@ func OAuth2ProxyAuthentication(basicRes context.BasicRes) 
gin.HandlerFunc {
                        if err != nil {
                                logger.Warn(err, "rejected forwarded user 
headers")
                        }
-                       if user == nil || user.Name == "" {
+                       if (user == nil || user.Name == "") && trustBasicAuth {
                                // fetch with basic auth header
                                user, err = getBasicAuthUserInfo(c, basicRes)
                                if err != nil {
diff --git a/backend/server/api/middlewares_basicauth_test.go 
b/backend/server/api/middlewares_basicauth_test.go
new file mode 100644
index 000000000..40b1c253b
--- /dev/null
+++ b/backend/server/api/middlewares_basicauth_test.go
@@ -0,0 +1,59 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+       "encoding/base64"
+       "testing"
+
+       "github.com/spf13/viper"
+)
+
+func basicAuthHeader(username, password string) map[string]string {
+       return map[string]string{
+               "Authorization": "Basic " + 
base64.StdEncoding.EncodeToString([]byte(username+":"+password)),
+       }
+}
+
+func TestOAuth2ProxyAuthenticationIgnoresBasicAuthWhenAuthEnabled(t 
*testing.T) {
+       cfg := viper.New()
+       cfg.Set("AUTH_ENABLED", true)
+       router := newProxyAuthRouterWithConfig(cfg)
+       body := performProxyAuthRequest(t, router, basicAuthHeader("anyone", 
"anything"))
+       if body.Authenticated {
+               t.Fatalf("expected Basic auth header to be ignored with 
AUTH_ENABLED=true, got %+v", body)
+       }
+}
+
+func TestOAuth2ProxyAuthenticationTrustsBasicAuthWhenAuthDisabled(t 
*testing.T) {
+       cases := map[string]func(*viper.Viper){
+               "AUTH_ENABLED unset":    func(*viper.Viper) {},
+               "AUTH_ENABLED explicit": func(cfg *viper.Viper) { 
cfg.Set("AUTH_ENABLED", false) },
+       }
+       for name, configure := range cases {
+               t.Run(name, func(t *testing.T) {
+                       cfg := viper.New()
+                       configure(cfg)
+                       router := newProxyAuthRouterWithConfig(cfg)
+                       body := performProxyAuthRequest(t, router, 
basicAuthHeader("admin", "secret"))
+                       if !body.Authenticated || body.Name != "admin" {
+                               t.Fatalf("expected Basic auth user to be kept 
without AUTH_ENABLED, got %+v", body)
+                       }
+               })
+       }
+}
diff --git a/backend/server/api/middlewares_forwardsecret_test.go 
b/backend/server/api/middlewares_forwardsecret_test.go
index 4b390aa52..5b8063d40 100644
--- a/backend/server/api/middlewares_forwardsecret_test.go
+++ b/backend/server/api/middlewares_forwardsecret_test.go
@@ -57,9 +57,13 @@ type proxyAuthResponse struct {
 }
 
 func newProxyAuthRouter(secret string) *gin.Engine {
-       gin.SetMode(gin.TestMode)
        cfg := viper.New()
        cfg.Set("FORWARDED_USER_SECRET", secret)
+       return newProxyAuthRouterWithConfig(cfg)
+}
+
+func newProxyAuthRouterWithConfig(cfg *viper.Viper) *gin.Engine {
+       gin.SetMode(gin.TestMode)
        basicRes := &proxyAuthTestBasicRes{
                cfg:    cfg,
                logger: logruslog.Global,

Reply via email to