From 5844afa5d4b7d8ef1297cd29465a7689e556cb60 Mon Sep 17 00:00:00 2001 From: n/a Date: Fri, 26 Jun 2026 16:13:32 +0200 Subject: [PATCH 1/5] token fix --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7feb3e6..c149989 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/gorilla/mux v1.8.1 github.com/k8shell-io/api-server v0.15.0 - github.com/k8shell-io/common v0.29.4 + github.com/k8shell-io/common v0.29.8 github.com/pkg/sftp v1.13.10 github.com/rs/zerolog v1.34.0 github.com/spf13/cobra v1.9.1 diff --git a/go.sum b/go.sum index 1adba49..c9b7fec 100644 --- a/go.sum +++ b/go.sum @@ -45,10 +45,10 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/k8shell-io/api-server v0.15.0 h1:FyBgblQAE5FJfPLDf+0khW9zESxCf5WrwzMtPH8hoW8= github.com/k8shell-io/api-server v0.15.0/go.mod h1:OIUI93twcGf0qQVdWCRTK+rkDFYLhTnkC+SDxfa5AqU= -github.com/k8shell-io/common v0.21.0 h1:EOwaQOFnHQJsHcDLDVEAwNnJJe7uvQCqcOiI6BTu3GE= -github.com/k8shell-io/common v0.21.0/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= github.com/k8shell-io/common v0.29.4 h1:patjuhCWs3g/JVNmx7SVa+wgvu46Z5RYAkD7YY6A4tY= github.com/k8shell-io/common v0.29.4/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.29.8 h1:juBGZAQdjY7cHVYq0fs5ggKa3GICrwjS77TGRzB3994= +github.com/k8shell-io/common v0.29.8/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= From 64bdd45e126b5d3008230429201241a0d960bcc5 Mon Sep 17 00:00:00 2001 From: n/a Date: Thu, 2 Jul 2026 16:57:26 +0200 Subject: [PATCH 2/5] uid gid update fix --- internal/models/user.go | 4 ---- internal/server/identity.go | 11 +++++++++++ internal/server/server.go | 2 ++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/internal/models/user.go b/internal/models/user.go index 88e5fb6..ab5473b 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -86,10 +86,6 @@ func (u *User) Update(claims *authz.UserClaims, token string) (bool, error) { if claims.Source != u.claims.Source { return false, fmt.Errorf("cannot update user source from %s to %s", u.claims.Source, claims.Source) } - if claims.UID != u.uid || claims.GID != u.gid { - return false, fmt.Errorf("cannot update user UID/GID from %d/%d to %d/%d", u.uid, u.gid, claims.UID, claims.GID) - } - u.claims = claims u.previousToken = u.userToken u.userToken = token diff --git a/internal/server/identity.go b/internal/server/identity.go index 8d8c971..768f240 100644 --- a/internal/server/identity.go +++ b/internal/server/identity.go @@ -158,6 +158,17 @@ func (s *Server) renewIdentityTokenIfNeeded(ctx context.Context) error { return fmt.Errorf("verify token: %w", err) } + if !s.uidGIDMismatchWarned { + snap := s.user.ClaimsSnapshot() + if claims.UID != s.user.GetUID() || claims.GID != s.user.GetGID() { + s.logger.Warn().Msgf( + "Refreshed token has different UID/GID (%d/%d → %d/%d); keeping existing OS identity, not changing workspace user", + snap.UID, snap.GID, claims.UID, claims.GID, + ) + s.uidGIDMismatchWarned = true + } + } + _, err = s.user.Update(claims, tokenStr) if err != nil { return fmt.Errorf("update user from refresh token: %w", err) diff --git a/internal/server/server.go b/internal/server/server.go index 6e1409a..e9ea8f6 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -52,6 +52,8 @@ type Server struct { appManager *apps.AppManager jwtVerifier *authz.JWTVerifier initTracker *models.InitTracker + + uidGIDMismatchWarned bool } func NewServer(cfg *config.Config, restApiUnixSocketPath string, testMode bool) (*Server, error) { From ba1f1a919b6ccd62ce4efcc34d4edd1af4317936 Mon Sep 17 00:00:00 2001 From: n/a Date: Thu, 2 Jul 2026 18:24:27 +0200 Subject: [PATCH 3/5] common version bump --- go.mod | 3 ++- go.sum | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index c149989..fad0379 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/gorilla/mux v1.8.1 github.com/k8shell-io/api-server v0.15.0 - github.com/k8shell-io/common v0.29.8 + github.com/k8shell-io/common v0.30.7 github.com/pkg/sftp v1.13.10 github.com/rs/zerolog v1.34.0 github.com/spf13/cobra v1.9.1 @@ -29,6 +29,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/fs v0.1.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect diff --git a/go.sum b/go.sum index c9b7fec..7bf2be2 100644 --- a/go.sum +++ b/go.sum @@ -45,10 +45,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/k8shell-io/api-server v0.15.0 h1:FyBgblQAE5FJfPLDf+0khW9zESxCf5WrwzMtPH8hoW8= github.com/k8shell-io/api-server v0.15.0/go.mod h1:OIUI93twcGf0qQVdWCRTK+rkDFYLhTnkC+SDxfa5AqU= -github.com/k8shell-io/common v0.29.4 h1:patjuhCWs3g/JVNmx7SVa+wgvu46Z5RYAkD7YY6A4tY= -github.com/k8shell-io/common v0.29.4/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= -github.com/k8shell-io/common v0.29.8 h1:juBGZAQdjY7cHVYq0fs5ggKa3GICrwjS77TGRzB3994= -github.com/k8shell-io/common v0.29.8/go.mod h1:E8dsb9ta4v3ne61AJgtRyTTbTkMMmKeCMAcXD+/9+cY= +github.com/k8shell-io/common v0.30.7 h1:rraXjV+njEThfdzPRQ7fR7o0W53yZ3/KjMu2s5ldJNM= +github.com/k8shell-io/common v0.30.7/go.mod h1:40c5GkpS7Y0/aOFa37Lq8z/mLUn3k3GV/AHtFJFL28k= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= From 621f8d81732235ca84c12bc823ba3a9d965c9657 Mon Sep 17 00:00:00 2001 From: n/a Date: Thu, 2 Jul 2026 18:49:24 +0200 Subject: [PATCH 4/5] uid gid not in eq --- internal/models/user.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/models/user.go b/internal/models/user.go index ab5473b..5a72f5b 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -107,9 +107,10 @@ func (u *User) TokenEqual(token string) bool { if err2 != nil { return false } - // previous token might be expired, but if the claims match then we can consider it equal - eq = claims1.Subject == claims2.Subject && claims1.Source == claims2.Source && - claims1.UID == claims2.UID && claims1.GID == claims2.GID + // previous token might be expired, but if the claims match then we can consider it equal. + // UID/GID are excluded: they are POSIX attributes that can legitimately change on renewal + // (see User.Update). Subject+Source are sufficient to identify the workspace user. + eq = claims1.Subject == claims2.Subject && claims1.Source == claims2.Source } return eq From 69085f04d3a2fcb6cbfddb462b98694cd8640ce0 Mon Sep 17 00:00:00 2001 From: n/a Date: Thu, 2 Jul 2026 19:19:40 +0200 Subject: [PATCH 5/5] token comp fix --- internal/models/user.go | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/internal/models/user.go b/internal/models/user.go index 5a72f5b..5114560 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -36,9 +36,8 @@ type User struct { groups []Group // Mutable — replaced atomically on token renewal; requires mu. - claims *authz.UserClaims - userToken string - previousToken string + claims *authz.UserClaims + userToken string } // NewUser creates a User from a verified JWT claims set and the raw token string. @@ -87,7 +86,6 @@ func (u *User) Update(claims *authz.UserClaims, token string) (bool, error) { return false, fmt.Errorf("cannot update user source from %s to %s", u.claims.Source, claims.Source) } u.claims = claims - u.previousToken = u.userToken u.userToken = token return true, nil } @@ -95,25 +93,19 @@ func (u *User) Update(claims *authz.UserClaims, token string) (bool, error) { func (u *User) TokenEqual(token string) bool { u.mu.RLock() defer u.mu.RUnlock() - eq := token == u.userToken - if !eq { - // token was verified before calling TokenEqual - claims1, err1 := authz.ParseUnverifiedClaims(token, true) - if err1 != nil { - return false - } - claims2, err2 := authz.ParseUnverifiedClaims(u.previousToken, false) - if err2 != nil { - return false - } - // previous token might be expired, but if the claims match then we can consider it equal. - // UID/GID are excluded: they are POSIX attributes that can legitimately change on renewal - // (see User.Update). Subject+Source are sufficient to identify the workspace user. - eq = claims1.Subject == claims2.Subject && claims1.Source == claims2.Source + if token == u.userToken { + return true } - return eq + // The caller's token was already verified (signature + expiry) by the interceptor. + // Accept any valid token whose Subject+Source match the workspace identity. + // Both fields are immutable: Subject is set in NewUser; Source is validated in Update. + claims, err := authz.ParseUnverifiedClaims(token, true) + if err != nil { + return false + } + return claims.Subject == u.username && claims.Source == u.claims.Source } // HasRole checks if the user has a specific role.