mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-03-09 07:18:40 -05:00
Use service user for role assignment and autoprovisioning
This gets us a rid of the need to configure the reva jwt secret in the proxy. Also we no longer need to fake an internal admin user for autoprovsioning user and/or assigning the roles to users from oidc claims.
This commit is contained in:
committed by
Ralf Haferkamp
parent
c23e0433cf
commit
684f5c07e5
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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."`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
newctx := context.Background()
|
||||
token, err := c.autoProvsionCreator.GetAutoProvisionAdminToken(newctx)
|
||||
gatewayClient, err := c.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
c.logger.Error().Err(err).Msg("Error generating token for autoprovisioning user.")
|
||||
c.logger.Error().Err(err).Msg("could not select next gateway client")
|
||||
return nil, err
|
||||
}
|
||||
lgClient, err := c.setupLibregraphClient(ctx, token)
|
||||
newctx := context.Background()
|
||||
authRes, err := gatewayClient.Authenticate(newctx, &gateway.AuthenticateRequest{
|
||||
Type: "serviceaccounts",
|
||||
ClientId: c.serviceAccount.ServiceAccountID,
|
||||
ClientSecret: c.serviceAccount.ServiceAccountSecret,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user