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,