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:
Ralf Haferkamp
2023-08-24 17:17:15 +02:00
committed by Ralf Haferkamp
parent c23e0433cf
commit 684f5c07e5
12 changed files with 68 additions and 164 deletions

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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."`
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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),

View File

@@ -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) {

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}
}