diff --git a/changelog/unreleased/proxy-use-service-account.md b/changelog/unreleased/proxy-use-service-account.md new file mode 100644 index 00000000000..08327d9f8fc --- /dev/null +++ b/changelog/unreleased/proxy-use-service-account.md @@ -0,0 +1,8 @@ +Enhancement: Proxy uses service accounts for provisioning + +The proxy service now uses a service account for provsioning task, like role +assignment and user auto-provisioning. This cleans up some technical debt that +required us to mint reva tokes inside the proxy service. + +https://github.com/owncloud/ocis/pull/7240 +https://github.com/owncloud/ocis/issues/5550 diff --git a/changelog/unreleased/service-accounts.md b/changelog/unreleased/service-accounts.md index 9042c9cf5a1..b3b4ed36b78 100644 --- a/changelog/unreleased/service-accounts.md +++ b/changelog/unreleased/service-accounts.md @@ -3,3 +3,4 @@ Enhancement: Introduce service accounts Introduces service accounts to avoid impersonating users in async processes https://github.com/owncloud/ocis/pull/6427 +https://github.com/owncloud/ocis/issues/5550 diff --git a/ocis/pkg/init/init.go b/ocis/pkg/init/init.go index 6a1fa4809d7..570f77ef518 100644 --- a/ocis/pkg/init/init.go +++ b/ocis/pkg/init/init.go @@ -31,9 +31,10 @@ type InsecureService struct { Insecure bool } -type InsecureProxyService struct { +type ProxyService struct { OIDC InsecureProxyOIDC `yaml:"oidc"` InsecureBackends bool `yaml:"insecure_backends"` + ServiceAccount ServiceAccount `yaml:"service_account"` } type InsecureProxyOIDC struct { @@ -72,6 +73,10 @@ type IdmService struct { ServiceUserPasswords ServiceUserPasswordsSettings `yaml:"service_user_passwords"` } +type SettingsService struct { + ServiceAccountIDAdmin string `yaml:"service_account_id_admin"` +} + type FrontendService struct { Archiver InsecureService ServiceAccount ServiceAccount `yaml:"service_account"` @@ -181,7 +186,7 @@ type OcisConfig struct { Graph GraphService Idp LdapBasedService Idm IdmService - Proxy InsecureProxyService + Proxy ProxyService Frontend FrontendService AuthBasic AuthbasicService `yaml:"auth_basic"` AuthBearer AuthbearerService `yaml:"auth_bearer"` @@ -191,6 +196,7 @@ type OcisConfig struct { Thumbnails ThumbnailService Search Search Audit Audit + Settings SettingsService `yaml:"settings"` Sharing Sharing StorageUsers StorageUsers `yaml:"storage_users"` Notifications Notifications @@ -389,6 +395,12 @@ func CreateConfig(insecure, forceOverwrite bool, configPath, adminPassword strin Clientlog: Clientlog{ ServiceAccount: serviceAccount, }, + Proxy: ProxyService{ + ServiceAccount: serviceAccount, + }, + Settings: SettingsService{ + ServiceAccountIDAdmin: serviceAccount.ServiceAccountID, + }, } if insecure { @@ -406,11 +418,12 @@ func CreateConfig(insecure, forceOverwrite bool, configPath, adminPassword strin cfg.StorageUsers.Events = _insecureEvents cfg.Nats.Nats.TLSSkipVerifyClientCert = true cfg.Ocdav = _insecureService - cfg.Proxy = InsecureProxyService{ + cfg.Proxy = ProxyService{ InsecureBackends: true, OIDC: InsecureProxyOIDC{ Insecure: true, }, + ServiceAccount: serviceAccount, } cfg.Thumbnails.Thumbnail.WebdavAllowInsecure = true diff --git a/services/graph/pkg/middleware/requireadmin.go b/services/graph/pkg/middleware/requireadmin.go index 15d679041c5..ef3dd64fe46 100644 --- a/services/graph/pkg/middleware/requireadmin.go +++ b/services/graph/pkg/middleware/requireadmin.go @@ -12,8 +12,8 @@ import ( // RequireAdmin middleware is used to require the user in context to be an admin / have account management permissions func RequireAdmin(rm *roles.Manager, logger log.Logger) func(next http.Handler) http.Handler { - return func(next http.Handler) http.Handler { + l := logger.With().Str("middleware", "requireAdmin").Logger() return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { u, ok := revactx.ContextGetUser(r.Context()) if !ok { @@ -27,15 +27,16 @@ func RequireAdmin(rm *roles.Manager, logger log.Logger) func(next http.Handler) // get roles from context roleIDs, ok := roles.ReadRoleIDsFromContext(r.Context()) if !ok { - logger.Debug().Str("userid", u.Id.OpaqueId).Msg("No roles in context, contacting settings service") + l.Debug().Str("userid", u.Id.OpaqueId).Msg("No roles in context, contacting settings service") var err error roleIDs, err = rm.FindRoleIDsForUser(r.Context(), u.Id.OpaqueId) if err != nil { - logger.Err(err).Str("userid", u.Id.OpaqueId).Msg("failed to get roles for user") + l.Error().Err(err).Str("userid", u.Id.OpaqueId).Msg("Failed to get roles for user") errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized") return } if len(roleIDs) == 0 { + l.Error().Err(err).Str("userid", u.Id.OpaqueId).Msg("No roles assigned to user") errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized") return } diff --git a/services/proxy/pkg/autoprovision/creator.go b/services/proxy/pkg/autoprovision/creator.go deleted file mode 100644 index 7714d78c19f..00000000000 --- a/services/proxy/pkg/autoprovision/creator.go +++ /dev/null @@ -1,97 +0,0 @@ -package autoprovision - -import ( - "context" - "encoding/json" - - cs3 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/v2/pkg/auth/scope" - "github.com/cs3org/reva/v2/pkg/token" - settingsService "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0" -) - -// Creator provides an interface to get a user or reva token with admin privileges -type Creator interface { - // GetAutoProvisionAdmin returns a user with the Admin role assigned - GetAutoProvisionAdmin() (*cs3.User, error) - // GetAutoProvisionAdminToken returns a reva token with admin privileges - GetAutoProvisionAdminToken(ctx context.Context) (string, error) -} - -// Options defines the available options for this package. -type Options struct { - tokenManager token.Manager -} - -// Option defines a single option function. -type Option func(o *Options) - -// WithTokenManager sets the reva token manager -func WithTokenManager(t token.Manager) Option { - return func(o *Options) { - o.tokenManager = t - } -} - -type creator struct { - Options -} - -// NewCreator returns a new Creator instance -func NewCreator(opts ...Option) creator { - opt := Options{} - for _, o := range opts { - o(&opt) - } - - return creator{ - Options: opt, - } -} - -// This returns an hardcoded internal User, that is privileged to create new User via -// the Graph API. This user is needed for autoprovisioning of users from incoming OIDC -// claims. -func (c creator) GetAutoProvisionAdmin() (*cs3.User, error) { - roleIDsJSON, err := json.Marshal([]string{settingsService.BundleUUIDRoleAdmin}) - if err != nil { - return nil, err - } - - autoProvisionUserCreator := &cs3.User{ - DisplayName: "Autoprovision User", - Username: "autoprovisioner", - Id: &cs3.UserId{ - Idp: "internal", - OpaqueId: "autoprov-user-id00-0000-000000000000", - }, - Opaque: &types.Opaque{ - Map: map[string]*types.OpaqueEntry{ - "roles": { - Decoder: "json", - Value: roleIDsJSON, - }, - }, - }, - } - return autoProvisionUserCreator, nil -} - -func (c creator) GetAutoProvisionAdminToken(ctx context.Context) (string, error) { - userCreator, err := c.GetAutoProvisionAdmin() - if err != nil { - return "", err - } - - s, err := scope.AddOwnerScope(nil) - if err != nil { - return "", err - } - - token, err := c.tokenManager.MintToken(ctx, userCreator, s) - if err != nil { - return "", err - } - return token, nil -} diff --git a/services/proxy/pkg/command/server.go b/services/proxy/pkg/command/server.go index c7b9fe3b623..2421e91a8ca 100644 --- a/services/proxy/pkg/command/server.go +++ b/services/proxy/pkg/command/server.go @@ -20,7 +20,6 @@ import ( "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/store" - "github.com/cs3org/reva/v2/pkg/token/manager/jwt" "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" "github.com/owncloud/ocis/v2/ocis-pkg/log" pkgmiddleware "github.com/owncloud/ocis/v2/ocis-pkg/middleware" @@ -32,7 +31,6 @@ import ( policiessvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/policies/v0" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" storesvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/store/v0" - "github.com/owncloud/ocis/v2/services/proxy/pkg/autoprovision" "github.com/owncloud/ocis/v2/services/proxy/pkg/config" "github.com/owncloud/ocis/v2/services/proxy/pkg/config/parser" "github.com/owncloud/ocis/v2/services/proxy/pkg/logging" @@ -282,14 +280,10 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, if err != nil { logger.Fatal().Err(err).Msg("Failed to get gateway selector") } - tokenManager, err := jwt.New(map[string]interface{}{ - "secret": cfg.TokenManager.JWTSecret, - }) if err != nil { logger.Fatal().Err(err). Msg("Failed to create token manager") } - autoProvsionCreator := autoprovision.NewCreator(autoprovision.WithTokenManager(tokenManager)) var userProvider backend.UserBackend switch cfg.AccountBackend { case "cs3": @@ -298,7 +292,7 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, backend.WithRevaGatewaySelector(gatewaySelector), backend.WithMachineAuthAPIKey(cfg.MachineAuthAPIKey), backend.WithOIDCissuer(cfg.OIDC.Issuer), - backend.WithAutoProvisonCreator(autoProvsionCreator), + backend.WithServiceAccount(cfg.ServiceAccount), ) default: logger.Fatal().Msgf("Invalid accounts backend type '%s'", cfg.AccountBackend) @@ -317,7 +311,8 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config, userroles.WithLogger(logger), userroles.WithRolesClaim(cfg.RoleAssignment.OIDCRoleMapper.RoleClaim), userroles.WithRoleMapping(cfg.RoleAssignment.OIDCRoleMapper.RolesMap), - userroles.WithAutoProvisonCreator(autoProvsionCreator), + userroles.WithRevaGatewaySelector(gatewaySelector), + userroles.WithServiceAccount(cfg.ServiceAccount), ) default: logger.Fatal().Msgf("Invalid role assignment driver '%s'", cfg.RoleAssignment.Driver) diff --git a/services/proxy/pkg/config/config.go b/services/proxy/pkg/config/config.go index 7b541961d21..0a21185a030 100644 --- a/services/proxy/pkg/config/config.go +++ b/services/proxy/pkg/config/config.go @@ -27,7 +27,7 @@ type Config struct { RoleQuotas map[string]uint64 `yaml:"role_quotas"` Policies []Policy `yaml:"policies"` OIDC OIDC `yaml:"oidc"` - TokenManager *TokenManager `mask:"struct" yaml:"token_manager"` + ServiceAccount ServiceAccount `yaml:"service_account"` RoleAssignment RoleAssignment `yaml:"role_assignment"` PolicySelector *PolicySelector `yaml:"policy_selector"` PreSignedURL PreSignedURL `yaml:"pre_signed_url"` @@ -160,11 +160,6 @@ type StaticSelectorConf struct { Policy string `yaml:"policy"` } -// TokenManager is the config for using the reva token manager -type TokenManager struct { - JWTSecret string `mask:"password" yaml:"jwt_secret" env:"OCIS_JWT_SECRET;PROXY_JWT_SECRET" desc:"The secret to mint and validate JWT tokens."` -} - // PreSignedURL is the config for the presigned url middleware type PreSignedURL struct { AllowedHTTPMethods []string `yaml:"allowed_http_methods"` @@ -192,3 +187,9 @@ type RegexRuleConf struct { Match string `yaml:"match"` Policy string `yaml:"policy"` } + +// ServiceAccount is the configuration for the used service account +type ServiceAccount struct { + ServiceAccountID string `yaml:"service_account_id" env:"OCIS_SERVICE_ACCOUNT_ID;PROXY_SERVICE_ACCOUNT_ID" desc:"The ID of the service account the service should use. See the 'auth-service' service description for more details."` + ServiceAccountSecret string `yaml:"service_account_secret" env:"OCIS_SERVICE_ACCOUNT_SECRET;PROXY_SERVICE_ACCOUNT_SECRET" desc:"The service account secret."` +} diff --git a/services/proxy/pkg/config/defaults/defaultconfig.go b/services/proxy/pkg/config/defaults/defaultconfig.go index 4c561e66e5e..333ab6d7a06 100644 --- a/services/proxy/pkg/config/defaults/defaultconfig.go +++ b/services/proxy/pkg/config/defaults/defaultconfig.go @@ -268,14 +268,6 @@ func EnsureDefaults(cfg *config.Config) { cfg.OIDC.UserinfoCache = &config.Cache{} } - 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{} - } - if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey } diff --git a/services/proxy/pkg/config/parser/parse.go b/services/proxy/pkg/config/parser/parse.go index 59553b42eaf..3baa6491ad4 100644 --- a/services/proxy/pkg/config/parser/parse.go +++ b/services/proxy/pkg/config/parser/parse.go @@ -34,10 +34,6 @@ func ParseConfig(cfg *config.Config) error { } func Validate(cfg *config.Config) error { - if cfg.TokenManager.JWTSecret == "" { - return shared.MissingJWTTokenError(cfg.Service.Name) - } - if cfg.MachineAuthAPIKey == "" { return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) } diff --git a/services/proxy/pkg/middleware/account_resolver_test.go b/services/proxy/pkg/middleware/account_resolver_test.go index 32af9351964..e83c5f7c554 100644 --- a/services/proxy/pkg/middleware/account_resolver_test.go +++ b/services/proxy/pkg/middleware/account_resolver_test.go @@ -12,7 +12,6 @@ import ( "github.com/cs3org/reva/v2/pkg/token/manager/jwt" "github.com/owncloud/ocis/v2/ocis-pkg/log" "github.com/owncloud/ocis/v2/ocis-pkg/oidc" - "github.com/owncloud/ocis/v2/services/proxy/pkg/config" "github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend" "github.com/owncloud/ocis/v2/services/proxy/pkg/user/backend/mocks" userRoleMocks "github.com/owncloud/ocis/v2/services/proxy/pkg/userroles/mocks" @@ -194,7 +193,6 @@ func newMockAccountResolver(userBackendResult *userv1beta1.User, userBackendErr Logger(log.NewLogger()), UserProvider(&ub), UserRoleAssigner(&ra), - TokenManagerConfig(config.TokenManager{JWTSecret: "secret"}), SkipUserInfo(false), UserOIDCClaim(oidcclaim), UserCS3Claim(cs3claim), diff --git a/services/proxy/pkg/middleware/options.go b/services/proxy/pkg/middleware/options.go index 2392c21b95f..d66c6b1e7b5 100644 --- a/services/proxy/pkg/middleware/options.go +++ b/services/proxy/pkg/middleware/options.go @@ -27,8 +27,6 @@ type Option func(o *Options) type Options struct { // Logger to use for logging, must be set Logger log.Logger - // TokenManagerConfig for communicating with the reva token manager - TokenManagerConfig config.TokenManager // PolicySelectorConfig for using the policy selector PolicySelector config.PolicySelector // HTTPClient to use for communication with the oidcAuth provider @@ -97,13 +95,6 @@ func Logger(l log.Logger) Option { } } -// TokenManagerConfig provides a function to set the token manger config option. -func TokenManagerConfig(cfg config.TokenManager) Option { - return func(o *Options) { - o.TokenManagerConfig = cfg - } -} - // PolicySelectorConfig provides a function to set the policy selector config option. func PolicySelectorConfig(cfg config.PolicySelector) Option { return func(o *Options) { diff --git a/services/proxy/pkg/user/backend/cs3.go b/services/proxy/pkg/user/backend/cs3.go index 6a006fb9cda..70cb9cb8a40 100644 --- a/services/proxy/pkg/user/backend/cs3.go +++ b/services/proxy/pkg/user/backend/cs3.go @@ -17,7 +17,7 @@ import ( "github.com/owncloud/ocis/v2/ocis-pkg/oidc" "github.com/owncloud/ocis/v2/ocis-pkg/registry" "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0/errorcode" - "github.com/owncloud/ocis/v2/services/proxy/pkg/autoprovision" + "github.com/owncloud/ocis/v2/services/proxy/pkg/config" "go-micro.dev/v4/selector" ) @@ -31,11 +31,11 @@ type Option func(o *Options) // Options defines the available options for this package. type Options struct { - logger log.Logger - gatewaySelector pool.Selectable[gateway.GatewayAPIClient] - machineAuthAPIKey string - oidcISS string - autoProvsionCreator autoprovision.Creator + logger log.Logger + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + machineAuthAPIKey string + oidcISS string + serviceAccount config.ServiceAccount } // WithLogger sets the logger option @@ -66,10 +66,10 @@ func WithOIDCissuer(oidcISS string) Option { } } -// WithAutoProvisonCreator configures the autoprovision creator to use -func WithAutoProvisonCreator(c autoprovision.Creator) Option { +// WithServiceAccount configures the service account creator to use +func WithServiceAccount(c config.ServiceAccount) Option { return func(o *Options) { - o.autoProvsionCreator = c + o.serviceAccount = c } } @@ -145,13 +145,25 @@ func (c *cs3backend) Authenticate(ctx context.Context, username string, password // user. If the user already exist this is not considered an error and the // function will just return the existing user. func (c *cs3backend) CreateUserFromClaims(ctx context.Context, claims map[string]interface{}) (*cs3.User, error) { + gatewayClient, err := c.gatewaySelector.Next() + if err != nil { + c.logger.Error().Err(err).Msg("could not select next gateway client") + return nil, err + } newctx := context.Background() - token, err := c.autoProvsionCreator.GetAutoProvisionAdminToken(newctx) + authRes, err := gatewayClient.Authenticate(newctx, &gateway.AuthenticateRequest{ + Type: "serviceaccounts", + ClientId: c.serviceAccount.ServiceAccountID, + ClientSecret: c.serviceAccount.ServiceAccountSecret, + }) if err != nil { - c.logger.Error().Err(err).Msg("Error generating token for autoprovisioning user.") return nil, err } - lgClient, err := c.setupLibregraphClient(ctx, token) + if authRes.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK { + return nil, fmt.Errorf("error authenticating service user: %s", authRes.Status.Message) + } + + lgClient, err := c.setupLibregraphClient(newctx, authRes.Token) if err != nil { c.logger.Error().Err(err).Msg("Error setting up libregraph client.") return nil, err diff --git a/services/proxy/pkg/userroles/oidcroles.go b/services/proxy/pkg/userroles/oidcroles.go index a5db4c10cd8..3a94b2067b3 100644 --- a/services/proxy/pkg/userroles/oidcroles.go +++ b/services/proxy/pkg/userroles/oidcroles.go @@ -7,7 +7,6 @@ import ( "time" cs3 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/utils" "github.com/owncloud/ocis/v2/ocis-pkg/middleware" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" @@ -127,19 +126,18 @@ func (ra oidcRoleAssigner) ApplyUserRole(ctx context.Context, user *cs3.User) (* } func (ra oidcRoleAssigner) prepareAdminContext() (context.Context, error) { - newctx := context.Background() - autoProvisionUser, err := ra.autoProvsionCreator.GetAutoProvisionAdmin() + gatewayClient, err := ra.gatewaySelector.Next() if err != nil { + ra.logger.Error().Err(err).Msg("could not select next gateway client") return nil, err } - token, err := ra.autoProvsionCreator.GetAutoProvisionAdminToken(newctx) + newctx, err := utils.GetServiceUserContext(ra.serviceAccount.ServiceAccountID, gatewayClient, ra.serviceAccount.ServiceAccountSecret) if err != nil { - ra.logger.Error().Err(err).Msg("Error generating token for provisioning role assignments.") + ra.logger.Error().Err(err).Msg("Error preparing request context for provisioning role assignments.") return nil, err } - newctx = revactx.ContextSetToken(newctx, token) - newctx = metadata.Set(newctx, middleware.AccountID, autoProvisionUser.Id.OpaqueId) - newctx = metadata.Set(newctx, middleware.RoleIDs, string(autoProvisionUser.Opaque.Map["roles"].Value)) + + newctx = metadata.Set(newctx, middleware.AccountID, ra.serviceAccount.ServiceAccountID) return newctx, nil } diff --git a/services/proxy/pkg/userroles/userroles.go b/services/proxy/pkg/userroles/userroles.go index 994ab318ad6..f72c51bad8b 100644 --- a/services/proxy/pkg/userroles/userroles.go +++ b/services/proxy/pkg/userroles/userroles.go @@ -3,10 +3,11 @@ package userroles import ( "context" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" cs3 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/owncloud/ocis/v2/ocis-pkg/log" settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0" - "github.com/owncloud/ocis/v2/services/proxy/pkg/autoprovision" "github.com/owncloud/ocis/v2/services/proxy/pkg/config" ) @@ -25,11 +26,12 @@ type UserRoleAssigner interface { // Options defines the available options for this package. type Options struct { - roleService settingssvc.RoleService - rolesClaim string - roleMapping []config.RoleMapping - autoProvsionCreator autoprovision.Creator - logger log.Logger + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + roleService settingssvc.RoleService + rolesClaim string + roleMapping []config.RoleMapping + serviceAccount config.ServiceAccount + logger log.Logger } // Option defines a single option function. @@ -63,10 +65,17 @@ func WithRoleMapping(roleMap []config.RoleMapping) Option { } } -// WithAutoProvisonCreator configures the autoprovision creator to use -func WithAutoProvisonCreator(c autoprovision.Creator) Option { +// WithRevaGatewaySelector set the gatewaySelector option +func WithRevaGatewaySelector(selectable pool.Selectable[gateway.GatewayAPIClient]) Option { return func(o *Options) { - o.autoProvsionCreator = c + o.gatewaySelector = selectable + } +} + +// WithServiceAccount configures the service account creator to use +func WithServiceAccount(c config.ServiceAccount) Option { + return func(o *Options) { + o.serviceAccount = c } }