[management] Skip JWT group evaluation for embedded-IdP local users (#6422)

When JWT group sync is enabled with a restrictive JWTAllowGroups list, the local owner of an embedded-IdP (Dex) deployment can get locked out. The allow-groups check runs account-wide but local password users do not receive
external IdP group claims, so they can't satisfy the allowed list.

This skips JWT group evaluation for local Dex users so the restriction and JWT group sync continue to apply to external-IdP users as intended.
This commit is contained in:
Bethuel Mmbaga
2026-06-15 12:01:54 +03:00
committed by GitHub
parent b19467e3af
commit cd777395f2
6 changed files with 101 additions and 3 deletions
+5 -1
View File
@@ -12,6 +12,7 @@ import (
"github.com/netbirdio/netbird/shared/auth"
"github.com/netbirdio/netbird/base62"
"github.com/netbirdio/netbird/idp/dex"
"github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types"
nbjwt "github.com/netbirdio/netbird/shared/auth/jwt"
@@ -74,7 +75,10 @@ func (m *manager) ValidateAndParseToken(ctx context.Context, value string) (auth
}
func (m *manager) EnsureUserAccessByJWTGroups(ctx context.Context, userAuth auth.UserAuth, token *jwt.Token) (auth.UserAuth, error) {
if userAuth.IsChild || userAuth.IsPAT {
// Child accounts and PAT-authenticated requests do not use JWT group access checks.
// Embedded-Dex local users also skip them because local password authentication
// does not provide external IdP group claims.
if userAuth.IsChild || userAuth.IsPAT || dex.IsLocalUserID(userAuth.UserId) {
return userAuth, nil
}
+38
View File
@@ -16,6 +16,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/idp/dex"
"github.com/netbirdio/netbird/management/server/auth"
"github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types"
@@ -206,6 +207,43 @@ func TestAuthManager_EnsureUserAccessByJWTGroups(t *testing.T) {
_, err = manager.EnsureUserAccessByJWTGroups(context.Background(), userAuth, token)
require.Error(t, err, "ensure user access is not in allowed groups")
})
t.Run("Local embedded-Dex user is exempt from JWT allow-groups", func(t *testing.T) {
account.Settings.JWTGroupsEnabled = true
account.Settings.JWTGroupsClaimName = "idp-groups"
account.Settings.JWTAllowGroups = []string{"not-a-group"}
err := store.SaveAccount(context.Background(), account)
require.NoError(t, err, "save account failed")
// Local Dex users have a "local" connector encoded in their user ID.
localUserAuth := nbauth.UserAuth{
AccountId: account.Id,
Domain: domain,
UserId: dex.EncodeDexUserID("local-owner", "local"),
}
localUserAuth, err = manager.EnsureUserAccessByJWTGroups(context.Background(), localUserAuth, token)
require.NoError(t, err, "local user must not be locked out by JWT allow-groups (issue #5337)")
require.Len(t, localUserAuth.Groups, 0, "JWT groups must not be evaluated for local users")
})
t.Run("Federated embedded-Dex user is still subject to JWT allow-groups", func(t *testing.T) {
account.Settings.JWTGroupsEnabled = true
account.Settings.JWTGroupsClaimName = "idp-groups"
account.Settings.JWTAllowGroups = []string{"not-a-group"}
err := store.SaveAccount(context.Background(), account)
require.NoError(t, err, "save account failed")
// A federated user (non-"local" connector) must remain restricted.
fedUserAuth := nbauth.UserAuth{
AccountId: account.Id,
Domain: domain,
UserId: dex.EncodeDexUserID("entra-user", "entra"),
}
_, err = manager.EnsureUserAccessByJWTGroups(context.Background(), fedUserAuth, token)
require.Error(t, err, "federated user must still be restricted by JWT allow-groups")
})
}
func TestAuthManager_ValidateAndParseToken(t *testing.T) {