From adc8a5165756f379abd856c06e33065d1187e5b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Mon, 5 Oct 2026 16:20:03 +0200 Subject: [PATCH] Don't use the same secret for signing sessions and reva tokens --- opencloud/pkg/init/init.go | 4 +- opencloud/pkg/init/structs.go | 7 +++- services/auth-guest/README.md | 5 ++- services/auth-guest/pkg/command/server.go | 2 +- services/auth-guest/pkg/config/config.go | 3 +- .../pkg/config/defaults/defaultconfig.go | 6 ++- .../auth-guest/pkg/config/parser/parse.go | 14 +++++++ .../pkg/config/parser/parse_test.go | 38 +++++++++++++++++++ services/auth-guest/pkg/revaconfig/config.go | 2 +- 9 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 services/auth-guest/pkg/config/parser/parse_test.go diff --git a/opencloud/pkg/init/init.go b/opencloud/pkg/init/init.go index 4e50d396c5..07380b004a 100644 --- a/opencloud/pkg/init/init.go +++ b/opencloud/pkg/init/init.go @@ -104,7 +104,7 @@ func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword return fmt.Errorf("could not generate random secret for urlSigningSecret: %s", err) } } - authGuestJWTSecret = oldCfg.AuthGuest.TokenManager.JWTSecret + authGuestJWTSecret = oldCfg.AuthGuest.JWT.Secret if authGuestJWTSecret == "" { authGuestJWTSecret, err = generators.GenerateRandomPassword(passwordLength) if err != nil { @@ -226,7 +226,7 @@ func CreateConfig(insecure, forceOverwrite, diff bool, configPath, adminPassword }, AuthGuest: AuthGuest{ ServiceAccount: serviceAccount, - TokenManager: TokenManager{JWTSecret: authGuestJWTSecret}, + JWT: AuthGuestJWT{Secret: authGuestJWTSecret}, }, Users: UsersAndGroupsService{ Drivers: LdapBasedService{ diff --git a/opencloud/pkg/init/structs.go b/opencloud/pkg/init/structs.go index eec147250b..b0f63d84f2 100644 --- a/opencloud/pkg/init/structs.go +++ b/opencloud/pkg/init/structs.go @@ -57,7 +57,12 @@ type Activitylog struct { // AuthGuest is the configuration for the auth-guest service type AuthGuest struct { ServiceAccount ServiceAccount `yaml:"service_account"` - TokenManager TokenManager `yaml:"token_manager"` + JWT AuthGuestJWT `yaml:"jwt"` +} + +// AuthGuestJWT is the configuration for the guest session tokens +type AuthGuestJWT struct { + Secret string `yaml:"secret"` } // App is the configuration for the collaboration service diff --git a/services/auth-guest/README.md b/services/auth-guest/README.md index 1e763f7dbf..ea6c7326ff 100644 --- a/services/auth-guest/README.md +++ b/services/auth-guest/README.md @@ -41,7 +41,7 @@ sequenceDiagram Redeem->>+Reva: Get Share Reva->>-Redeem: Share Note right of Redeem: Validate Share, Mark Token used - Redeem->>-Web: Set Cookie, return shareid + Redeem->>-Web: Set Cookie, return shareid Note right of Web: HTTP only cookie with signed JWT (JWT lifetime 24h) Web->>+Proxy: "/graph/me/drives/sharedWithMe" Proxy->>+Reva: validate token extracted from JWT @@ -91,7 +91,8 @@ the event consumer, set `AUTH_GUEST_HTTP_DISABLED=true`. Relevant options: -- `AUTH_GUEST_JWT_SECRET` — secret used to sign session tokens. +- `AUTH_GUEST_SESSION_JWT_SECRET` — secret used to sign guest session tokens. + It must differ from `OC_JWT_SECRET`. - `AUTH_GUEST_JWT_COOKIE_NAME`, `AUTH_GUEST_JWT_TTL` — session cookie name and lifetime. - `AUTH_GUEST_TOKENS_STORAGE_ROOT` — where guest link token records are stored. diff --git a/services/auth-guest/pkg/command/server.go b/services/auth-guest/pkg/command/server.go index 110199eea1..9139031b79 100644 --- a/services/auth-guest/pkg/command/server.go +++ b/services/auth-guest/pkg/command/server.go @@ -79,7 +79,7 @@ func Server(cfg *config.Config) *cobra.Command { tokenSvc := token.NewTokenService() store := storage.NewFileManager(cfg.Storage.RootDirectory) - jwtService := jwt.NewJwtService(cfg.TokenManager.JWTSecret, cfg.JWT.TTL) + jwtService := jwt.NewJwtService(cfg.JWT.Secret, cfg.JWT.TTL) authGuest := authguest.NewAuthGuestService(tokenSvc, store, authguest.GatewaySelector(gatewaySelector), diff --git a/services/auth-guest/pkg/config/config.go b/services/auth-guest/pkg/config/config.go index 4c0bbb3d48..100daa16c5 100644 --- a/services/auth-guest/pkg/config/config.go +++ b/services/auth-guest/pkg/config/config.go @@ -89,11 +89,12 @@ type Storage struct { // TokenManager is the config for using the reva token manager type TokenManager struct { - JWTSecret string `yaml:"jwt_secret" env:"AUTH_GUEST_JWT_SECRET" desc:"The secret to mint and validate jwt tokens." introductionVersion:"%%NEXT%%"` + JWTSecret string `yaml:"jwt_secret" env:"OC_JWT_SECRET;AUTH_GUEST_JWT_SECRET" desc:"The secret to mint and validate jwt tokens." introductionVersion:"%%NEXT%%"` } // JWT defines the configuration for guest session tokens. type JWT struct { + Secret string `yaml:"secret" env:"AUTH_GUEST_SESSION_JWT_SECRET" desc:"The secret used to sign and validate guest session tokens. It must differ from OC_JWT_SECRET." introductionVersion:"%%NEXT%%" mask:"password"` CookieName string `yaml:"cookie_name" env:"AUTH_GUEST_JWT_COOKIE_NAME" desc:"The name of the session cookie set when a guest token is redeemed." introductionVersion:"%%NEXT%%"` TTL time.Duration `yaml:"ttl" env:"AUTH_GUEST_JWT_TTL" desc:"The lifetime of a redeemed guest session token." introductionVersion:"%%NEXT%%"` } diff --git a/services/auth-guest/pkg/config/defaults/defaultconfig.go b/services/auth-guest/pkg/config/defaults/defaultconfig.go index 2d5ffccc51..42068ba7e8 100644 --- a/services/auth-guest/pkg/config/defaults/defaultconfig.go +++ b/services/auth-guest/pkg/config/defaults/defaultconfig.go @@ -79,7 +79,11 @@ func EnsureDefaults(cfg *config.Config) { cfg.GRPC.TLS = structs.CopyOrZeroValue(cfg.Commons.GRPCServiceTLS) } - if cfg.TokenManager == nil { + if cfg.TokenManager == nil && cfg.Commons != nil && cfg.Commons.TokenManager != nil { + cfg.TokenManager = &config.TokenManager{ + JWTSecret: cfg.Commons.TokenManager.JWTSecret, + } + } else if cfg.TokenManager == nil { cfg.TokenManager = &config.TokenManager{} } diff --git a/services/auth-guest/pkg/config/parser/parse.go b/services/auth-guest/pkg/config/parser/parse.go index b2add76af3..38b2d27192 100644 --- a/services/auth-guest/pkg/config/parser/parse.go +++ b/services/auth-guest/pkg/config/parser/parse.go @@ -5,8 +5,10 @@ package parser import ( "errors" + "fmt" occfg "github.com/opencloud-eu/opencloud/pkg/config" + ocdefaults "github.com/opencloud-eu/opencloud/pkg/config/defaults" "github.com/opencloud-eu/opencloud/pkg/shared" "github.com/opencloud-eu/opencloud/services/auth-guest/pkg/config" "github.com/opencloud-eu/opencloud/services/auth-guest/pkg/config/defaults" @@ -41,5 +43,17 @@ func Validate(cfg *config.Config) error { if cfg.TokenManager == nil || cfg.TokenManager.JWTSecret == "" { return shared.MissingJWTTokenError(cfg.Service.Name) } + if cfg.JWT.Secret == "" { + return fmt.Errorf("the guest session secret has not been set properly in your config for %s. "+ + "Make sure your %s config contains the proper values "+ + "(e.g. by using 'opencloud init --diff' and applying the patch or setting a value manually in "+ + "the config/corresponding environment variable AUTH_GUEST_SESSION_JWT_SECRET)", + cfg.Service.Name, ocdefaults.BaseConfigPath()) + } + // The guest session token and the reva access token are both HS256 JWTs. Signing them + // with the same key would make them interchangeable. + if cfg.JWT.Secret == cfg.TokenManager.JWTSecret { + return fmt.Errorf("the guest session secret (AUTH_GUEST_SESSION_JWT_SECRET) of %s must differ from the jwt secret (OC_JWT_SECRET)", cfg.Service.Name) + } return nil } diff --git a/services/auth-guest/pkg/config/parser/parse_test.go b/services/auth-guest/pkg/config/parser/parse_test.go new file mode 100644 index 0000000000..1852027318 --- /dev/null +++ b/services/auth-guest/pkg/config/parser/parse_test.go @@ -0,0 +1,38 @@ +// Copyright 2026 OpenCloud GmbH +// SPDX-License-Identifier: Apache-2.0 + +package parser + +import ( + "testing" + + "github.com/opencloud-eu/opencloud/services/auth-guest/pkg/config" +) + +func TestValidate(t *testing.T) { + tests := []struct { + name string + jwtSecret string + sessionSecret string + wantErr bool + }{ + {name: "distinct secrets", jwtSecret: "reva-secret", sessionSecret: "session-secret"}, + {name: "missing jwt secret", sessionSecret: "session-secret", wantErr: true}, + {name: "missing session secret", jwtSecret: "reva-secret", wantErr: true}, + {name: "shared secret", jwtSecret: "same-secret", sessionSecret: "same-secret", wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &config.Config{ + TokenManager: &config.TokenManager{JWTSecret: tt.jwtSecret}, + JWT: config.JWT{Secret: tt.sessionSecret}, + } + + err := Validate(cfg) + if (err != nil) != tt.wantErr { + t.Fatalf("Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/services/auth-guest/pkg/revaconfig/config.go b/services/auth-guest/pkg/revaconfig/config.go index b4e796905b..4cb0ae745b 100644 --- a/services/auth-guest/pkg/revaconfig/config.go +++ b/services/auth-guest/pkg/revaconfig/config.go @@ -27,7 +27,7 @@ func GuestLinksConfigFromStruct(cfg *config.Config) map[string]any { "auth_managers": map[string]any{ "guestlinks": map[string]any{ "gateway_addr": cfg.RevaGateway, - "jwt_secret": cfg.TokenManager.JWTSecret, + "jwt_secret": cfg.JWT.Secret, "service_account_id": cfg.ServiceAccount.ServiceAccountID, "service_account_secret": cfg.ServiceAccount.ServiceAccountSecret, },