From 0bc0972b0bd06a1dfc741939898fe8bee4cce4fb Mon Sep 17 00:00:00 2001 From: jkoberg Date: Thu, 1 Jun 2023 13:50:32 +0200 Subject: [PATCH 01/11] add admin service account Signed-off-by: jkoberg --- services/settings/README.md | 6 +++++- services/settings/pkg/config/config.go | 2 ++ .../settings/pkg/config/defaults/defaultconfig.go | 5 +++-- services/settings/pkg/store/defaults/defaults.go | 12 ++++++++++++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/services/settings/README.md b/services/settings/README.md index 7a8008c91..256cf4b29 100644 --- a/services/settings/README.md +++ b/services/settings/README.md @@ -8,7 +8,7 @@ The settings service is currently used for managing the: * possible user roles and their respective permissions, * assignment of roles to users. -As an example, user profile settings that can be changed in the Web UI must be persistent. +As an example, user profile settings that can be changed in the Web UI must be persistent. The settings service supports two different backends for persisting the data. The backend can be set via the `SETTINGS_STORE_TYPE` environment variable. Supported values are: @@ -67,3 +67,7 @@ Infinite Scale services can register *settings bundles* with the settings servic ## Settings Usage Services can set or query ocis *setting values* of a user from settings bundles. + +## Service Accounts + +The settings service needs to know the ID's of service accounts but it doesn't need their secrets. Currently only one service account can be configured which has the admin role. This can be set with the `SETTINGS_SERVICE_ACCOUNT_ID_ADMIN` envvar, but it will also pick up the global `OCIS_SERVICE_ACCOUNT_ID` envvar. Also see the 'auth-service' service description for additional details. diff --git a/services/settings/pkg/config/config.go b/services/settings/pkg/config/config.go index e606b1865..2ca13dff1 100644 --- a/services/settings/pkg/config/config.go +++ b/services/settings/pkg/config/config.go @@ -37,6 +37,8 @@ type Config struct { SetupDefaultAssignments bool `yaml:"set_default_assignments" env:"SETTINGS_SETUP_DEFAULT_ASSIGNMENTS;IDM_CREATE_DEMO_USERS" desc:"The default role assignments the demo users should be setup."` + ServiceAccountIDAdmin string `yaml:"service_account_id_admin" env:"OCIS_SERVICE_ACCOUNT_ID;SETTINGS_SERVICE_ACCOUNT_ID_ADMIN" desc:"The ID of the service account having the admin role. See the 'auth-service' service description for more details."` + Context context.Context `yaml:"-"` } diff --git a/services/settings/pkg/config/defaults/defaultconfig.go b/services/settings/pkg/config/defaults/defaultconfig.go index 64866ae3f..8bd1126fe 100644 --- a/services/settings/pkg/config/defaults/defaultconfig.go +++ b/services/settings/pkg/config/defaults/defaultconfig.go @@ -64,8 +64,9 @@ func DefaultConfig() *config.Config { TTL: time.Minute * 10, }, }, - BundlesPath: "", - Bundles: nil, + BundlesPath: "", + Bundles: nil, + ServiceAccountIDAdmin: "service-user-id", } } diff --git a/services/settings/pkg/store/defaults/defaults.go b/services/settings/pkg/store/defaults/defaults.go index f3de77394..96ca44acf 100644 --- a/services/settings/pkg/store/defaults/defaults.go +++ b/services/settings/pkg/store/defaults/defaults.go @@ -822,6 +822,11 @@ func DefaultRoleAssignments(cfg *config.Config) []*settingsmsg.UserRoleAssignmen AccountUuid: "534bb038-6f9d-4093-946f-133be61fa4e7", RoleId: BundleUUIDRoleSpaceAdmin, }, + { + // service user + AccountUuid: "service-user-id", + RoleId: BundleUUIDRoleAdmin, + }, } } @@ -833,5 +838,12 @@ func DefaultRoleAssignments(cfg *config.Config) []*settingsmsg.UserRoleAssignmen }) } + if cfg.ServiceAccountIDAdmin != "" { + assignments = append(assignments, &settingsmsg.UserRoleAssignment{ + AccountUuid: cfg.ServiceAccountIDAdmin, + RoleId: BundleUUIDRoleAdmin, + }) + } + return assignments } From 900afb9beeb8891d3ee073588cba6f659c8ea6c9 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Wed, 9 Aug 2023 14:14:41 +0200 Subject: [PATCH 02/11] use service accounts for userlog Signed-off-by: jkoberg --- services/userlog/pkg/config/config.go | 8 +++ .../pkg/config/defaults/defaultconfig.go | 4 ++ services/userlog/pkg/service/conversion.go | 58 +++++++++------- services/userlog/pkg/service/service.go | 67 ++++++------------- 4 files changed, 65 insertions(+), 72 deletions(-) diff --git a/services/userlog/pkg/config/config.go b/services/userlog/pkg/config/config.go index 782fb0791..0779c259a 100644 --- a/services/userlog/pkg/config/config.go +++ b/services/userlog/pkg/config/config.go @@ -32,6 +32,8 @@ type Config struct { GlobalNotificationsSecret string `yaml:"global_notifications_secret" env:"USERLOG_GLOBAL_NOTIFICATIONS_SECRET" desc:"The secret to secure the global notifications endpoint. Only system admins and users knowing that secret can call the global notifications POST/DELETE endpoints."` + ServiceAccount ServiceAccount `yaml:"service_account"` + Context context.Context `yaml:"-"` } @@ -75,3 +77,9 @@ type HTTP struct { type TokenManager struct { JWTSecret string `yaml:"jwt_secret" env:"OCIS_JWT_SECRET;USERLOG_JWT_SECRET" desc:"The secret to mint and validate jwt tokens."` } + +// ServiceAccount is the configuration for the used service account +type ServiceAccount struct { + ServiceAccountID string `yaml:"service_account_id" env:"OCIS_SERVICE_ACCOUNT_ID;USERLOG_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;USERLOG_SERVICE_ACCOUNT_SECRET" desc:"The service account secret."` +} diff --git a/services/userlog/pkg/config/defaults/defaultconfig.go b/services/userlog/pkg/config/defaults/defaultconfig.go index 1dfd6318c..294923d1d 100644 --- a/services/userlog/pkg/config/defaults/defaultconfig.go +++ b/services/userlog/pkg/config/defaults/defaultconfig.go @@ -52,6 +52,10 @@ func DefaultConfig() *config.Config { AllowCredentials: true, }, }, + ServiceAccount: config.ServiceAccount{ + ServiceAccountID: "service-user-id", + ServiceAccountSecret: "secret-string", + }, } } diff --git a/services/userlog/pkg/service/conversion.go b/services/userlog/pkg/service/conversion.go index df472094a..c589753fc 100644 --- a/services/userlog/pkg/service/conversion.go +++ b/services/userlog/pkg/service/conversion.go @@ -52,31 +52,32 @@ type OC10Notification struct { // Converter is responsible for converting eventhistory events to OC10Notifications type Converter struct { - locale string - gatewaySelector pool.Selectable[gateway.GatewayAPIClient] - machineAuthAPIKey string - serviceName string - translationPath string + locale string + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + serviceAccountID string + serviceAccountSecret string + serviceName string + translationPath string // cached within one request not to query other service too much - spaces map[string]*storageprovider.StorageSpace - users map[string]*user.User - resources map[string]*storageprovider.ResourceInfo - contexts map[string]context.Context + spaces map[string]*storageprovider.StorageSpace + users map[string]*user.User + resources map[string]*storageprovider.ResourceInfo + serviceAccountContext context.Context } // NewConverter returns a new Converter -func NewConverter(loc string, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], machineAuthAPIKey string, name string, translationPath string) *Converter { +func NewConverter(loc string, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], machineAuthAPIKey string, name string, translationPath string, serviceAccountID string, serviceAccountSecret string) *Converter { return &Converter{ - locale: loc, - gatewaySelector: gatewaySelector, - machineAuthAPIKey: machineAuthAPIKey, - serviceName: name, - translationPath: translationPath, - spaces: make(map[string]*storageprovider.StorageSpace), - users: make(map[string]*user.User), - resources: make(map[string]*storageprovider.ResourceInfo), - contexts: make(map[string]context.Context), + locale: loc, + gatewaySelector: gatewaySelector, + serviceAccountID: serviceAccountID, + serviceAccountSecret: serviceAccountSecret, + serviceName: name, + translationPath: translationPath, + spaces: make(map[string]*storageprovider.StorageSpace), + users: make(map[string]*user.User), + resources: make(map[string]*storageprovider.ResourceInfo), } } @@ -171,7 +172,7 @@ func (c *Converter) spaceMessage(eventid string, nt NotificationTemplate, execut return OC10Notification{}, err } - ctx, err := c.authenticate(usr) + ctx, err := c.authenticate() if err != nil { return OC10Notification{}, err } @@ -210,7 +211,7 @@ func (c *Converter) shareMessage(eventid string, nt NotificationTemplate, execut return OC10Notification{}, err } - ctx, err := c.authenticate(usr) + ctx, err := c.authenticate() if err != nil { return OC10Notification{}, err } @@ -327,13 +328,18 @@ func (c *Converter) deprovisionMessage(nt NotificationTemplate, deproDate string }, nil } -func (c *Converter) authenticate(usr *user.User) (context.Context, error) { - if ctx, ok := c.contexts[usr.GetId().GetOpaqueId()]; ok { - return ctx, nil +func (c *Converter) authenticate() (context.Context, error) { + if c.serviceAccountContext != nil { + return c.serviceAccountContext, nil } - ctx, err := authenticate(usr, c.gatewaySelector, c.machineAuthAPIKey) + + gatewayClient, err := c.gatewaySelector.Next() + if err != nil { + return nil, err + } + ctx, err := utils.GetServiceUserContext(c.serviceAccountID, gatewayClient, c.serviceAccountSecret) if err == nil { - c.contexts[usr.GetId().GetOpaqueId()] = ctx + c.serviceAccountContext = ctx } return ctx, err } diff --git a/services/userlog/pkg/service/service.go b/services/userlog/pkg/service/service.go index 7ebee7776..a9d18ce44 100644 --- a/services/userlog/pkg/service/service.go +++ b/services/userlog/pkg/service/service.go @@ -13,7 +13,6 @@ import ( user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/utils" @@ -29,7 +28,6 @@ import ( micrometadata "go-micro.dev/v4/metadata" "go-micro.dev/v4/store" "go.opentelemetry.io/otel/trace" - "google.golang.org/grpc/metadata" ) // UserlogService is the service responsible for user activities @@ -137,12 +135,12 @@ func (ul *UserlogService) processEvent(event events.Event) { users = append(users, e.ExecutingUser.GetId().GetOpaqueId()) default: return - } + // space related // TODO: how to find spaceadmins? case events.SpaceDisabled: executant = e.Executant - users, err = ul.findSpaceMembers(ul.impersonate(e.Executant), e.ID.GetOpaqueId(), viewer) + users, err = ul.findSpaceMembers(ul.mustAuthenticate(), e.ID.GetOpaqueId(), viewer) case events.SpaceDeleted: executant = e.Executant for u := range e.FinalMembers { @@ -150,22 +148,22 @@ func (ul *UserlogService) processEvent(event events.Event) { } case events.SpaceShared: executant = e.Executant - users, err = ul.resolveID(ul.impersonate(e.Executant), e.GranteeUserID, e.GranteeGroupID) + users, err = ul.resolveID(ul.mustAuthenticate(), e.GranteeUserID, e.GranteeGroupID) case events.SpaceUnshared: executant = e.Executant - users, err = ul.resolveID(ul.impersonate(e.Executant), e.GranteeUserID, e.GranteeGroupID) + users, err = ul.resolveID(ul.mustAuthenticate(), e.GranteeUserID, e.GranteeGroupID) case events.SpaceMembershipExpired: - users, err = ul.resolveID(ul.impersonate(e.SpaceOwner), e.GranteeUserID, e.GranteeGroupID) + users, err = ul.resolveID(ul.mustAuthenticate(), e.GranteeUserID, e.GranteeGroupID) // share related case events.ShareCreated: executant = e.Executant - users, err = ul.resolveID(ul.impersonate(e.Executant), e.GranteeUserID, e.GranteeGroupID) + users, err = ul.resolveID(ul.mustAuthenticate(), e.GranteeUserID, e.GranteeGroupID) case events.ShareRemoved: executant = e.Executant - users, err = ul.resolveID(ul.impersonate(e.Executant), e.GranteeUserID, e.GranteeGroupID) + users, err = ul.resolveID(ul.mustAuthenticate(), e.GranteeUserID, e.GranteeGroupID) case events.ShareExpired: - users, err = ul.resolveID(ul.impersonate(e.ShareOwner), e.GranteeUserID, e.GranteeGroupID) + users, err = ul.resolveID(ul.mustAuthenticate(), e.GranteeUserID, e.GranteeGroupID) } if err != nil { @@ -520,26 +518,6 @@ func (ul *UserlogService) resolveGroup(ctx context.Context, groupID string) ([]s return userIDs, nil } -func (ul *UserlogService) impersonate(uid *user.UserId) context.Context { - if uid == nil { - ul.log.Error().Msg("cannot impersonate nil user") - return nil - } - - u, err := getUser(context.Background(), uid, ul.gatewaySelector) - if err != nil { - ul.log.Error().Err(err).Msg("cannot get user") - return nil - } - - ctx, err := authenticate(u, ul.gatewaySelector, ul.cfg.MachineAuthAPIKey) - if err != nil { - ul.log.Error().Err(err).Str("userid", u.GetId().GetOpaqueId()).Msg("failed to impersonate user") - return nil - } - return ctx -} - func (ul *UserlogService) getUserLocale(userid string) string { resp, err := ul.valueClient.GetValueByUniqueIdentifiers( micrometadata.Set(context.Background(), middleware.AccountID, userid), @@ -560,29 +538,25 @@ func (ul *UserlogService) getUserLocale(userid string) string { } func (ul *UserlogService) getConverter(locale string) *Converter { - return NewConverter(locale, ul.gatewaySelector, ul.cfg.MachineAuthAPIKey, ul.cfg.Service.Name, ul.cfg.TranslationPath) + return NewConverter(locale, ul.gatewaySelector, ul.cfg.MachineAuthAPIKey, ul.cfg.Service.Name, ul.cfg.TranslationPath, ul.cfg.ServiceAccount.ServiceAccountID, ul.cfg.ServiceAccount.ServiceAccountSecret) } -func authenticate(usr *user.User, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], machineAuthAPIKey string) (context.Context, error) { +func (ul *UserlogService) mustAuthenticate() context.Context { + ctx, err := authenticate(ul.cfg.ServiceAccount.ServiceAccountID, ul.gatewaySelector, ul.cfg.ServiceAccount.ServiceAccountSecret) + if err != nil { + ul.log.Error().Err(err).Str("accountid", ul.cfg.ServiceAccount.ServiceAccountID).Msg("failed to impersonate service account") + return nil + } + return ctx +} + +func authenticate(serviceAccountID string, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], serviceAccountSecret string) (context.Context, error) { gatewayClient, err := gatewaySelector.Next() if err != nil { return nil, err } - ctx := revactx.ContextSetUser(context.Background(), usr) - authRes, err := gatewayClient.Authenticate(ctx, &gateway.AuthenticateRequest{ - Type: "machine", - ClientId: "userid:" + usr.GetId().GetOpaqueId(), - ClientSecret: machineAuthAPIKey, - }) - if err != nil { - return nil, err - } - if authRes.GetStatus().GetCode() != rpc.Code_CODE_OK { - return nil, fmt.Errorf("error impersonating user: %s", authRes.Status.Message) - } - - return metadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, authRes.Token), nil + return utils.GetServiceUserContext(serviceAccountID, gatewayClient, serviceAccountSecret) } func getSpace(ctx context.Context, spaceID string, gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) (*storageprovider.StorageSpace, error) { @@ -665,6 +639,7 @@ func getResource(ctx context.Context, resourceid *storageprovider.ResourceId, ga func listStorageSpaceRequest(spaceID string) *storageprovider.ListStorageSpacesRequest { return &storageprovider.ListStorageSpacesRequest{ + Opaque: utils.AppendPlainToOpaque(nil, "unrestricted", "true"), Filters: []*storageprovider.ListStorageSpacesRequest_Filter{ { Type: storageprovider.ListStorageSpacesRequest_Filter_TYPE_ID, From d8c2957c84841cb4fa461d9525bc82ad4e3df0bb Mon Sep 17 00:00:00 2001 From: jkoberg Date: Fri, 11 Aug 2023 14:10:10 +0200 Subject: [PATCH 03/11] use service accounts for search Signed-off-by: jkoberg --- services/search/pkg/config/config.go | 8 ++++- .../pkg/config/defaults/defaultconfig.go | 9 +++--- services/search/pkg/config/parser/parse.go | 4 --- services/search/pkg/search/search.go | 24 ++------------- services/search/pkg/search/service.go | 29 ++++++++----------- 5 files changed, 25 insertions(+), 49 deletions(-) diff --git a/services/search/pkg/config/config.go b/services/search/pkg/config/config.go index 9b66cd808..6b2399959 100644 --- a/services/search/pkg/config/config.go +++ b/services/search/pkg/config/config.go @@ -29,7 +29,13 @@ type Config struct { Extractor Extractor `yaml:"extractor"` ContentExtractionSizeLimit uint64 `yaml:"content_extraction_size_limit" env:"SEARCH_CONTENT_EXTRACTION_SIZE_LIMIT" desc:"Maximum file size in bytes that is allowed for content extraction."` - MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;SEARCH_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary for the access to resources from other services."` + ServiceAccount ServiceAccount `yaml:"service_account"` Context context.Context `yaml:"-"` } + +// ServiceAccount is the configuration for the used service account +type ServiceAccount struct { + ServiceAccountID string `yaml:"service_account_id" env:"OCIS_SERVICE_ACCOUNT_ID;SEARCH_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;SEARCH_SERVICE_ACCOUNT_SECRET" desc:"The service account secret."` +} diff --git a/services/search/pkg/config/defaults/defaultconfig.go b/services/search/pkg/config/defaults/defaultconfig.go index 50fe01b12..04c8d0a24 100644 --- a/services/search/pkg/config/defaults/defaultconfig.go +++ b/services/search/pkg/config/defaults/defaultconfig.go @@ -54,7 +54,10 @@ func DefaultConfig() *config.Config { EnableTLS: false, }, ContentExtractionSizeLimit: 20 * 1024 * 1024, // Limit content extraction to <20MB files by default - MachineAuthAPIKey: "", + ServiceAccount: config.ServiceAccount{ + ServiceAccountID: "service-user-id", + ServiceAccountSecret: "secret-string", + }, } } @@ -91,10 +94,6 @@ func EnsureDefaults(cfg *config.Config) { cfg.TokenManager = &config.TokenManager{} } - if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { - cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey - } - if cfg.Reva == nil && cfg.Commons != nil { cfg.Reva = structs.CopyOrZeroValue(cfg.Commons.Reva) } diff --git a/services/search/pkg/config/parser/parse.go b/services/search/pkg/config/parser/parse.go index fafad8f84..9b3c124ae 100644 --- a/services/search/pkg/config/parser/parse.go +++ b/services/search/pkg/config/parser/parse.go @@ -4,7 +4,6 @@ import ( "errors" ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" "github.com/owncloud/ocis/v2/services/search/pkg/config" "github.com/owncloud/ocis/v2/services/search/pkg/config/defaults" @@ -34,8 +33,5 @@ func ParseConfig(cfg *config.Config) error { } func Validate(cfg *config.Config) error { - if cfg.MachineAuthAPIKey == "" { - return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) - } return nil } diff --git a/services/search/pkg/search/search.go b/services/search/pkg/search/search.go index d8d595ea8..26213790a 100644 --- a/services/search/pkg/search/search.go +++ b/services/search/pkg/search/search.go @@ -8,18 +8,14 @@ import ( "strings" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" - "github.com/cs3org/reva/v2/pkg/errtypes" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" "github.com/owncloud/ocis/v2/ocis-pkg/log" searchmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/search/v0" "github.com/owncloud/ocis/v2/services/search/pkg/engine" - "google.golang.org/grpc/metadata" ) var scopeRegex = regexp.MustCompile(`scope:\s*([^" "\n\r]*)`) @@ -71,30 +67,14 @@ func logDocCount(engine engine.Engine, logger log.Logger) { logger.Debug().Interface("count", c).Msg("new document count") } -func getAuthContext(owner *user.User, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], secret string, logger log.Logger) (context.Context, error) { +func getAuthContext(serviceAccountID string, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], secret string, logger log.Logger) (context.Context, error) { gatewayClient, err := gatewaySelector.Next() if err != nil { logger.Error().Err(err).Msg("could not get reva gatewayClient") return nil, err } - ownerCtx := ctxpkg.ContextSetUser(context.Background(), owner) - authRes, err := gatewayClient.Authenticate(ownerCtx, &gateway.AuthenticateRequest{ - Type: "machine", - ClientId: "userid:" + owner.GetId().GetOpaqueId(), - ClientSecret: secret, - }) - - if err == nil && authRes.GetStatus().GetCode() != rpc.Code_CODE_OK { - err = errtypes.NewErrtypeFromStatus(authRes.Status) - } - - if err != nil { - logger.Error().Err(err).Interface("owner", owner).Interface("authRes", authRes).Msg("error using machine auth") - return nil, err - } - - return metadata.AppendToOutgoingContext(ownerCtx, ctxpkg.TokenHeader, authRes.Token), nil + return utils.GetServiceUserContext(serviceAccountID, gatewayClient, secret) } func statResource(ctx context.Context, ref *provider.Reference, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], logger log.Logger) (*provider.StatResponse, error) { diff --git a/services/search/pkg/search/service.go b/services/search/pkg/search/service.go index 43707a07f..1ce6a9c32 100644 --- a/services/search/pkg/search/service.go +++ b/services/search/pkg/search/service.go @@ -57,7 +57,9 @@ type Service struct { gatewaySelector pool.Selectable[gateway.GatewayAPIClient] engine engine.Engine extractor content.Extractor - secret string + + serviceAccountID string + serviceAccountSecret string } var errSkipSpace error @@ -67,9 +69,11 @@ func NewService(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], eng e var s = &Service{ gatewaySelector: gatewaySelector, engine: eng, - secret: cfg.MachineAuthAPIKey, logger: logger, extractor: extractor, + + serviceAccountID: cfg.ServiceAccount.ServiceAccountID, + serviceAccountSecret: cfg.ServiceAccount.ServiceAccountSecret, } return s @@ -291,21 +295,12 @@ func (s *Service) searchIndex(ctx context.Context, req *searchsvc.SearchRequest, return nil, err } - var ownerCtx context.Context - if space.Owner.Id.Type == user.UserType_USER_TYPE_SPACE_OWNER { - // We can't impersonate SPACE_OWNER users and have to fall back to using the user auth instead, - // which will not resolve the absolute path of the share in the space but only the part the user - // is allowed to see. - // In the future this problem can be solved using service accounts. - ownerCtx = ctx - } else { - ownerCtx, err = getAuthContext(&user.User{Id: space.Owner.Id}, s.gatewaySelector, s.secret, s.logger) - if err != nil { - return nil, err - } + serviceCtx, err := getAuthContext(s.serviceAccountID, s.gatewaySelector, s.serviceAccountSecret, s.logger) + if err != nil { + return nil, err } - gpRes, err := gatewayClient.GetPath(ownerCtx, &provider.GetPathRequest{ + gpRes, err := gatewayClient.GetPath(serviceCtx, &provider.GetPathRequest{ ResourceId: space.Root, }) if err != nil { @@ -380,7 +375,7 @@ func (s *Service) searchIndex(ctx context.Context, req *searchsvc.SearchRequest, // IndexSpace (re)indexes all resources of a given space. func (s *Service) IndexSpace(spaceID *provider.StorageSpaceId, uID *user.UserId) error { - ownerCtx, err := getAuthContext(&user.User{Id: uID}, s.gatewaySelector, s.secret, s.logger) + ownerCtx, err := getAuthContext(s.serviceAccountID, s.gatewaySelector, s.serviceAccountSecret, s.logger) if err != nil { return err } @@ -510,7 +505,7 @@ func (s *Service) MoveItem(ref *provider.Reference, uID *user.UserId) { } func (s *Service) resInfo(uID *user.UserId, ref *provider.Reference) (context.Context, *provider.StatResponse, string) { - ownerCtx, err := getAuthContext(&user.User{Id: uID}, s.gatewaySelector, s.secret, s.logger) + ownerCtx, err := getAuthContext(s.serviceAccountID, s.gatewaySelector, s.serviceAccountSecret, s.logger) if err != nil { return nil, nil, "" } From 0cd5ad64158ecb650dba4660a369b33060383364 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Wed, 16 Aug 2023 10:48:37 +0200 Subject: [PATCH 04/11] use service accounts for graph Signed-off-by: jkoberg --- services/graph/pkg/config/config.go | 10 ++++++++-- services/graph/pkg/config/defaults/defaultconfig.go | 8 ++++---- services/graph/pkg/service/v0/personaldata.go | 4 ++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/services/graph/pkg/config/config.go b/services/graph/pkg/config/config.go index 884cb0d26..5c54def3b 100644 --- a/services/graph/pkg/config/config.go +++ b/services/graph/pkg/config/config.go @@ -30,8 +30,8 @@ type Config struct { Identity Identity `yaml:"identity"` Events Events `yaml:"events"` - MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;USERLOG_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary to access resources from other services."` - Keycloak Keycloak `yaml:"keycloak"` + Keycloak Keycloak `yaml:"keycloak"` + ServiceAccount ServiceAccount `yaml:"service_account"` Context context.Context `yaml:"-"` } @@ -137,3 +137,9 @@ type Keycloak struct { UserRealm string `yaml:"user_realm" env:"OCIS_KEYCLOAK_USER_REALM;GRAPH_KEYCLOAK_USER_REALM" desc:"The realm users are defined."` InsecureSkipVerify bool `yaml:"insecure_skip_verify" env:"OCIS_KEYCLOAK_INSECURE_SKIP_VERIFY;GRAPH_KEYCLOAK_INSECURE_SKIP_VERIFY" desc:"Disable TLS certificate validation for Keycloak connections. Do not set this in production environments."` } + +// ServiceAccount is the configuration for the used service account +type ServiceAccount struct { + ServiceAccountID string `yaml:"service_account_id" env:"OCIS_SERVICE_ACCOUNT_ID;GRAPH_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;GRAPH_SERVICE_ACCOUNT_SECRET" desc:"The service account secret."` +} diff --git a/services/graph/pkg/config/defaults/defaultconfig.go b/services/graph/pkg/config/defaults/defaultconfig.go index b12410a86..cde17260c 100644 --- a/services/graph/pkg/config/defaults/defaultconfig.go +++ b/services/graph/pkg/config/defaults/defaultconfig.go @@ -105,6 +105,10 @@ func DefaultConfig() *config.Config { Cluster: "ocis-cluster", EnableTLS: false, }, + ServiceAccount: config.ServiceAccount{ + ServiceAccountID: "service-user-id", + ServiceAccountSecret: "secret-string", + }, } } @@ -159,10 +163,6 @@ func EnsureDefaults(cfg *config.Config) { cfg.HTTP.TLS = cfg.Commons.HTTPServiceTLS } - if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { - cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey - } - if cfg.Identity.LDAP.GroupCreateBaseDN == "" { cfg.Identity.LDAP.GroupCreateBaseDN = cfg.Identity.LDAP.GroupBaseDN } diff --git a/services/graph/pkg/service/v0/personaldata.go b/services/graph/pkg/service/v0/personaldata.go index 435dc417c..ac1098a30 100644 --- a/services/graph/pkg/service/v0/personaldata.go +++ b/services/graph/pkg/service/v0/personaldata.go @@ -99,7 +99,7 @@ func (g Graph) GatherPersonalData(usr *user.User, ref *provider.Reference, token } // the context might already be cancelled. We need to impersonate the acting user again - ctx, err := utils.ImpersonateUser(usr, gatewayClient, g.config.MachineAuthAPIKey) + ctx, err := utils.GetServiceUserContext(g.config.ServiceAccount.ServiceAccountID, gatewayClient, g.config.ServiceAccount.ServiceAccountSecret) if err != nil { g.logger.Error().Err(err).Str("userID", usr.GetId().GetOpaqueId()).Msg("cannot impersonate user") } @@ -162,7 +162,7 @@ func (g Graph) upload(u *user.User, data []byte, ref *provider.Reference, th str return err } - ctx, err := utils.ImpersonateUser(u, gatewayClient, g.config.MachineAuthAPIKey) + ctx, err := utils.GetServiceUserContext(g.config.ServiceAccount.ServiceAccountID, gatewayClient, g.config.ServiceAccount.ServiceAccountSecret) if err != nil { return err } From ab10e5e152dab7980d4c13199e32f608b2261b87 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Wed, 16 Aug 2023 11:04:56 +0200 Subject: [PATCH 05/11] use service accounts for notifications Signed-off-by: jkoberg --- services/notifications/pkg/command/server.go | 2 +- services/notifications/pkg/config/config.go | 12 +++-- .../pkg/config/defaults/defaultconfig.go | 7 +-- .../notifications/pkg/config/parser/parse.go | 5 -- services/notifications/pkg/service/service.go | 42 ++++++++------- .../notifications/pkg/service/service_test.go | 4 +- services/notifications/pkg/service/shares.go | 34 ++++++++---- services/notifications/pkg/service/spaces.go | 52 ++++++++++++++----- 8 files changed, 99 insertions(+), 59 deletions(-) diff --git a/services/notifications/pkg/command/server.go b/services/notifications/pkg/command/server.go index 49466fdf3..8887bf1ff 100644 --- a/services/notifications/pkg/command/server.go +++ b/services/notifications/pkg/command/server.go @@ -116,7 +116,7 @@ func Server(cfg *config.Config) *cli.Command { logger.Fatal().Err(err).Str("addr", cfg.Notifications.RevaGateway).Msg("could not get reva gateway selector") } valueService := settingssvc.NewValueService("com.owncloud.api.settings", grpcClient) - svc := service.NewEventsNotifier(evts, channel, logger, gatewaySelector, valueService, cfg.Notifications.MachineAuthAPIKey, cfg.Notifications.EmailTemplatePath, cfg.WebUIURL) + svc := service.NewEventsNotifier(evts, channel, logger, gatewaySelector, valueService, cfg.ServiceAccount.ServiceAccountID, cfg.ServiceAccount.ServiceAccountSecret, cfg.Notifications.EmailTemplatePath, cfg.WebUIURL) gr.Add(svc.Run, func(error) { cancel() diff --git a/services/notifications/pkg/config/config.go b/services/notifications/pkg/config/config.go index 064197958..170a6c94c 100644 --- a/services/notifications/pkg/config/config.go +++ b/services/notifications/pkg/config/config.go @@ -18,8 +18,9 @@ type Config struct { WebUIURL string `yaml:"ocis_url" env:"OCIS_URL;NOTIFICATIONS_WEB_UI_URL" desc:"The public facing URL of the oCIS Web UI, used e.g. when sending notification eMails"` - Notifications Notifications `yaml:"notifications"` - GRPCClientTLS shared.GRPCClientTLS `yaml:"grpc_client_tls"` + Notifications Notifications `yaml:"notifications"` + GRPCClientTLS shared.GRPCClientTLS `yaml:"grpc_client_tls"` + ServiceAccount ServiceAccount `yaml:"service_account"` Context context.Context `yaml:"-"` } @@ -28,7 +29,6 @@ type Config struct { type Notifications struct { SMTP SMTP `yaml:"SMTP"` Events Events `yaml:"events"` - MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;NOTIFICATIONS_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary to access resources from other services."` EmailTemplatePath string `yaml:"email_template_path" env:"OCIS_EMAIL_TEMPLATE_PATH;NOTIFICATIONS_EMAIL_TEMPLATE_PATH" desc:"Path to Email notification templates overriding embedded ones."` TranslationPath string `yaml:"translation_path" env:"OCIS_TRANSLATION_PATH,NOTIFICATIONS_TRANSLATION_PATH" desc:"(optional) Set this to a path with custom translations to overwrite the builtin translations. Note that file and folder naming rules apply, see the documentation for more details."` RevaGateway string `yaml:"reva_gateway" env:"OCIS_REVA_GATEWAY" desc:"CS3 gateway used to look up user metadata"` @@ -55,3 +55,9 @@ type Events struct { TLSRootCACertificate string `yaml:"tls_root_ca_certificate" env:"OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;NOTIFICATIONS_EVENTS_TLS_ROOT_CA_CERTIFICATE" desc:"The root CA certificate used to validate the server's TLS certificate. If provided NOTIFICATIONS_EVENTS_TLS_INSECURE will be seen as false."` EnableTLS bool `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;NOTIFICATIONS_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."` } + +// ServiceAccount is the configuration for the used service account +type ServiceAccount struct { + ServiceAccountID string `yaml:"service_account_id" env:"OCIS_SERVICE_ACCOUNT_ID;NOTIFICATIONS_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;NOTIFICATIONS_SERVICE_ACCOUNT_SECRET" desc:"The service account secret."` +} diff --git a/services/notifications/pkg/config/defaults/defaultconfig.go b/services/notifications/pkg/config/defaults/defaultconfig.go index f643baaa0..b7deaa018 100644 --- a/services/notifications/pkg/config/defaults/defaultconfig.go +++ b/services/notifications/pkg/config/defaults/defaultconfig.go @@ -44,6 +44,10 @@ func DefaultConfig() *config.Config { }, RevaGateway: shared.DefaultRevaConfig().Address, }, + ServiceAccount: config.ServiceAccount{ + ServiceAccountID: "service-user-id", + ServiceAccountSecret: "secret-string", + }, } } @@ -73,9 +77,6 @@ func EnsureDefaults(cfg *config.Config) { cfg.Tracing = &config.Tracing{} } - if cfg.Notifications.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { - cfg.Notifications.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey - } if cfg.Notifications.GRPCClientTLS == nil && cfg.Commons != nil { cfg.Notifications.GRPCClientTLS = structs.CopyOrZeroValue(cfg.Commons.GRPCClientTLS) } diff --git a/services/notifications/pkg/config/parser/parse.go b/services/notifications/pkg/config/parser/parse.go index 72ccfb22f..af45c2726 100644 --- a/services/notifications/pkg/config/parser/parse.go +++ b/services/notifications/pkg/config/parser/parse.go @@ -4,7 +4,6 @@ import ( "errors" ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" - "github.com/owncloud/ocis/v2/ocis-pkg/shared" "github.com/owncloud/ocis/v2/services/notifications/pkg/config" "github.com/owncloud/ocis/v2/services/notifications/pkg/config/defaults" @@ -34,9 +33,5 @@ func ParseConfig(cfg *config.Config) error { } func Validate(cfg *config.Config) error { - if cfg.Notifications.MachineAuthAPIKey == "" { - return shared.MissingMachineAuthApiKeyError(cfg.Service.Name) - } - return nil } diff --git a/services/notifications/pkg/service/service.go b/services/notifications/pkg/service/service.go index 9ac5ea291..a8a3bf0e7 100644 --- a/services/notifications/pkg/service/service.go +++ b/services/notifications/pkg/service/service.go @@ -42,32 +42,34 @@ func NewEventsNotifier( logger log.Logger, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], valueService settingssvc.ValueService, - machineAuthAPIKey, emailTemplatePath, ocisURL string) Service { + serviceAccountID, serviceAccountSecret, emailTemplatePath, ocisURL string) Service { return eventsNotifier{ - logger: logger, - channel: channel, - events: events, - signals: make(chan os.Signal, 1), - gatewaySelector: gatewaySelector, - valueService: valueService, - machineAuthAPIKey: machineAuthAPIKey, - emailTemplatePath: emailTemplatePath, - ocisURL: ocisURL, + logger: logger, + channel: channel, + events: events, + signals: make(chan os.Signal, 1), + gatewaySelector: gatewaySelector, + valueService: valueService, + serviceAccountID: serviceAccountID, + serviceAccountSecret: serviceAccountSecret, + emailTemplatePath: emailTemplatePath, + ocisURL: ocisURL, } } type eventsNotifier struct { - logger log.Logger - channel channels.Channel - events <-chan events.Event - signals chan os.Signal - gatewaySelector pool.Selectable[gateway.GatewayAPIClient] - valueService settingssvc.ValueService - machineAuthAPIKey string - emailTemplatePath string - translationPath string - ocisURL string + logger log.Logger + channel channels.Channel + events <-chan events.Event + signals chan os.Signal + gatewaySelector pool.Selectable[gateway.GatewayAPIClient] + valueService settingssvc.ValueService + emailTemplatePath string + translationPath string + ocisURL string + serviceAccountID string + serviceAccountSecret string } func (s eventsNotifier) Run() error { diff --git a/services/notifications/pkg/service/service_test.go b/services/notifications/pkg/service/service_test.go index 6b95c7414..056866391 100644 --- a/services/notifications/pkg/service/service_test.go +++ b/services/notifications/pkg/service/service_test.go @@ -77,7 +77,7 @@ var _ = Describe("Notifications", func() { cfg := defaults.FullDefaultConfig() cfg.GRPCClientTLS = &shared.GRPCClientTLS{} ch := make(chan events.Event) - evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gatewaySelector, vs, "", "", "") + evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gatewaySelector, vs, "", "", "", "") go evts.Run() ch <- ev @@ -275,7 +275,7 @@ var _ = Describe("Notifications X-Site Scripting", func() { cfg := defaults.FullDefaultConfig() cfg.GRPCClientTLS = &shared.GRPCClientTLS{} ch := make(chan events.Event) - evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gatewaySelector, vs, "", "", "") + evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gatewaySelector, vs, "", "", "", "") go evts.Run() ch <- ev diff --git a/services/notifications/pkg/service/shares.go b/services/notifications/pkg/service/shares.go index b6d7d844b..58ec8bec5 100644 --- a/services/notifications/pkg/service/shares.go +++ b/services/notifications/pkg/service/shares.go @@ -19,13 +19,13 @@ func (s eventsNotifier) handleShareCreated(e events.ShareCreated) { return } - ownerCtx, owner, err := utils.Impersonate(e.Sharer, gatewayClient, s.machineAuthAPIKey) + ctx, err := utils.GetServiceUserContext(s.serviceAccountID, gatewayClient, s.serviceAccountSecret) if err != nil { - logger.Error().Err(err).Msg("Could not impersonate sharer") + logger.Error().Err(err).Msg("Could not impersonate service user") return } - resourceInfo, err := s.getResourceInfo(ownerCtx, e.ItemID, &fieldmaskpb.FieldMask{Paths: []string{"name"}}) + resourceInfo, err := s.getResourceInfo(ctx, e.ItemID, &fieldmaskpb.FieldMask{Paths: []string{"name"}}) if err != nil { logger.Error(). Err(err). @@ -41,13 +41,19 @@ func (s eventsNotifier) handleShareCreated(e events.ShareCreated) { return } - granteeList := s.ensureGranteeList(ownerCtx, owner.GetId(), e.GranteeUserID, e.GranteeGroupID) + owner, err := utils.GetUser(e.Sharer, gatewayClient) + if err != nil { + logger.Error().Err(err).Msg("Could not get user") + return + } + + granteeList := s.ensureGranteeList(ctx, owner.GetId(), e.GranteeUserID, e.GranteeGroupID) if granteeList == nil { return } sharerDisplayName := owner.GetDisplayName() - recipientList, err := s.render(ownerCtx, email.ShareCreated, + recipientList, err := s.render(ctx, email.ShareCreated, "ShareGrantee", map[string]string{ "ShareSharer": sharerDisplayName, @@ -58,7 +64,7 @@ func (s eventsNotifier) handleShareCreated(e events.ShareCreated) { s.logger.Error().Err(err).Str("event", "ShareCreated").Msg("could not get render the email") return } - s.send(ownerCtx, recipientList) + s.send(ctx, recipientList) } func (s eventsNotifier) handleShareExpired(e events.ShareExpired) { @@ -73,13 +79,13 @@ func (s eventsNotifier) handleShareExpired(e events.ShareExpired) { return } - ownerCtx, owner, err := utils.Impersonate(e.ShareOwner, gatewayClient, s.machineAuthAPIKey) + ctx, err := utils.GetServiceUserContext(s.serviceAccountID, gatewayClient, s.serviceAccountSecret) if err != nil { logger.Error().Err(err).Msg("Could not impersonate sharer") return } - resourceInfo, err := s.getResourceInfo(ownerCtx, e.ItemID, &fieldmaskpb.FieldMask{Paths: []string{"name"}}) + resourceInfo, err := s.getResourceInfo(ctx, e.ItemID, &fieldmaskpb.FieldMask{Paths: []string{"name"}}) if err != nil { logger.Error(). Err(err). @@ -87,12 +93,18 @@ func (s eventsNotifier) handleShareExpired(e events.ShareExpired) { return } - granteeList := s.ensureGranteeList(ownerCtx, owner.GetId(), e.GranteeUserID, e.GranteeGroupID) + owner, err := utils.GetUser(e.ShareOwner, gatewayClient) + if err != nil { + logger.Error().Err(err).Msg("Could not get user") + return + } + + granteeList := s.ensureGranteeList(ctx, owner.GetId(), e.GranteeUserID, e.GranteeGroupID) if granteeList == nil { return } - recipientList, err := s.render(ownerCtx, email.ShareExpired, + recipientList, err := s.render(ctx, email.ShareExpired, "ShareGrantee", map[string]string{ "ShareFolder": resourceInfo.GetName(), @@ -102,5 +114,5 @@ func (s eventsNotifier) handleShareExpired(e events.ShareExpired) { s.logger.Error().Err(err).Str("event", "ShareExpired").Msg("could not get render the email") return } - s.send(ownerCtx, recipientList) + s.send(ctx, recipientList) } diff --git a/services/notifications/pkg/service/spaces.go b/services/notifications/pkg/service/spaces.go index 3922c81ba..32ed5bacc 100644 --- a/services/notifications/pkg/service/spaces.go +++ b/services/notifications/pkg/service/spaces.go @@ -19,7 +19,7 @@ func (s eventsNotifier) handleSpaceShared(e events.SpaceShared) { return } - executantCtx, executant, err := utils.Impersonate(e.Executant, gatewayClient, s.machineAuthAPIKey) + ctx, err := utils.GetServiceUserContext(s.serviceAccountID, gatewayClient, s.serviceAccountSecret) if err != nil { logger.Error(). Err(err). @@ -35,7 +35,7 @@ func (s eventsNotifier) handleSpaceShared(e events.SpaceShared) { return } - resourceInfo, err := s.getResourceInfo(executantCtx, &resourceID, nil) + resourceInfo, err := s.getResourceInfo(ctx, &resourceID, nil) if err != nil { logger.Error(). Err(err). @@ -51,16 +51,24 @@ func (s eventsNotifier) handleSpaceShared(e events.SpaceShared) { return } + executant, err := utils.GetUser(e.Executant, gatewayClient) + if err != nil { + logger.Error(). + Err(err). + Msg("could not get user") + return + } + // Note: We're using the 'executantCtx' (authenticated as the share executant) here for requesting // the Grantees of the shares. Ideally the notfication service would use some kind of service // user for this. - granteeList := s.ensureGranteeList(executantCtx, executant.GetId(), e.GranteeUserID, e.GranteeGroupID) + granteeList := s.ensureGranteeList(ctx, executant.GetId(), e.GranteeUserID, e.GranteeGroupID) if granteeList == nil { return } sharerDisplayName := executant.GetDisplayName() - recipientList, err := s.render(executantCtx, email.SharedSpace, + recipientList, err := s.render(ctx, email.SharedSpace, "SpaceGrantee", map[string]string{ "SpaceSharer": sharerDisplayName, @@ -71,7 +79,7 @@ func (s eventsNotifier) handleSpaceShared(e events.SpaceShared) { s.logger.Error().Err(err).Str("event", "SharedSpace").Msg("could not get render the email") return } - s.send(executantCtx, recipientList) + s.send(ctx, recipientList) } func (s eventsNotifier) handleSpaceUnshared(e events.SpaceUnshared) { @@ -86,7 +94,7 @@ func (s eventsNotifier) handleSpaceUnshared(e events.SpaceUnshared) { return } - executantCtx, executant, err := utils.Impersonate(e.Executant, gatewayClient, s.machineAuthAPIKey) + ctx, err := utils.GetServiceUserContext(s.serviceAccountID, gatewayClient, s.serviceAccountSecret) if err != nil { logger.Error().Err(err).Msg("could not handle space unshared event") return @@ -100,7 +108,7 @@ func (s eventsNotifier) handleSpaceUnshared(e events.SpaceUnshared) { return } - resourceInfo, err := s.getResourceInfo(executantCtx, &resourceID, nil) + resourceInfo, err := s.getResourceInfo(ctx, &resourceID, nil) if err != nil { logger.Error(). Err(err). @@ -116,16 +124,24 @@ func (s eventsNotifier) handleSpaceUnshared(e events.SpaceUnshared) { return } + executant, err := utils.GetUser(e.Executant, gatewayClient) + if err != nil { + logger.Error(). + Err(err). + Msg("could not get user") + return + } + // Note: We're using the 'executantCtx' (authenticated as the share executant) here for requesting // the Grantees of the shares. Ideally the notfication service would use some kind of service // user for this. - granteeList := s.ensureGranteeList(executantCtx, executant.GetId(), e.GranteeUserID, e.GranteeGroupID) + granteeList := s.ensureGranteeList(ctx, executant.GetId(), e.GranteeUserID, e.GranteeGroupID) if granteeList == nil { return } sharerDisplayName := executant.GetDisplayName() - recipientList, err := s.render(executantCtx, email.UnsharedSpace, + recipientList, err := s.render(ctx, email.UnsharedSpace, "SpaceGrantee", map[string]string{ "SpaceSharer": sharerDisplayName, @@ -136,7 +152,7 @@ func (s eventsNotifier) handleSpaceUnshared(e events.SpaceUnshared) { s.logger.Error().Err(err).Str("event", "UnsharedSpace").Msg("Could not get render the email") return } - s.send(executantCtx, recipientList) + s.send(ctx, recipientList) } func (s eventsNotifier) handleSpaceMembershipExpired(e events.SpaceMembershipExpired) { @@ -151,18 +167,26 @@ func (s eventsNotifier) handleSpaceMembershipExpired(e events.SpaceMembershipExp return } - ownerCtx, owner, err := utils.Impersonate(e.SpaceOwner, gatewayClient, s.machineAuthAPIKey) + ctx, err := utils.GetServiceUserContext(s.serviceAccountID, gatewayClient, s.serviceAccountSecret) if err != nil { logger.Error().Err(err).Msg("Could not impersonate sharer") return } - granteeList := s.ensureGranteeList(ownerCtx, owner.GetId(), e.GranteeUserID, e.GranteeGroupID) + owner, err := utils.GetUser(e.SpaceOwner, gatewayClient) + if err != nil { + logger.Error(). + Err(err). + Msg("could not get user") + return + } + + granteeList := s.ensureGranteeList(ctx, owner.GetId(), e.GranteeUserID, e.GranteeGroupID) if granteeList == nil { return } - recipientList, err := s.render(ownerCtx, email.MembershipExpired, + recipientList, err := s.render(ctx, email.MembershipExpired, "SpaceGrantee", map[string]string{ "SpaceName": e.SpaceName, @@ -172,5 +196,5 @@ func (s eventsNotifier) handleSpaceMembershipExpired(e events.SpaceMembershipExp s.logger.Error().Err(err).Str("event", "SpaceUnshared").Msg("could not get render the email") return } - s.send(ownerCtx, recipientList) + s.send(ctx, recipientList) } From e09ddc93ea75da985581c5c3734fc16a68cf2e88 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Wed, 16 Aug 2023 11:06:18 +0200 Subject: [PATCH 06/11] use service accounts for storage-user commands Signed-off-by: jkoberg --- services/storage-users/pkg/config/config.go | 7 +++ .../pkg/config/defaults/defaultconfig.go | 4 ++ services/storage-users/pkg/event/service.go | 8 +-- services/storage-users/pkg/task/trash_bin.go | 57 +++---------------- .../storage-users/pkg/task/trash_bin_test.go | 42 +------------- 5 files changed, 24 insertions(+), 94 deletions(-) diff --git a/services/storage-users/pkg/config/config.go b/services/storage-users/pkg/config/config.go index 5423259a0..88e326f42 100644 --- a/services/storage-users/pkg/config/config.go +++ b/services/storage-users/pkg/config/config.go @@ -38,6 +38,7 @@ type Config struct { ReadOnly bool `yaml:"readonly" env:"STORAGE_USERS_READ_ONLY" desc:"Set this storage to be read-only."` UploadExpiration int64 `yaml:"upload_expiration" env:"STORAGE_USERS_UPLOAD_EXPIRATION" desc:"Duration in seconds after which uploads will expire. Note that when setting this to a low number, uploads could be cancelled before they are finished and return a 403 to the user."` Tasks Tasks `yaml:"tasks"` + ServiceAccount ServiceAccount `yaml:"service_account"` Supervised bool `yaml:"-"` Context context.Context `yaml:"-"` @@ -278,3 +279,9 @@ type PurgeTrashBin struct { PersonalDeleteBefore time.Duration `yaml:"personal_delete_before" env:"STORAGE_USERS_PURGE_TRASH_BIN_PERSONAL_DELETE_BEFORE" desc:"Specifies the period of time in which items that have been in the personal trash-bin for longer than this value should be deleted. A value of 0 means no automatic deletion. The value is human-readable, valid values are '24h', '60m', '60s' etc."` ProjectDeleteBefore time.Duration `yaml:"project_delete_before" env:"STORAGE_USERS_PURGE_TRASH_BIN_PROJECT_DELETE_BEFORE" desc:"Specifies the period of time in which items that have been in the project trash-bin for longer than this value should be deleted. A value of 0 means no automatic deletion. The value is human-readable, valid values are '24h', '60m', '60s' etc."` } + +// ServiceAccount is the configuration for the used service account +type ServiceAccount struct { + ServiceAccountID string `yaml:"service_account_id" env:"OCIS_SERVICE_ACCOUNT_ID;STORAGE_USERS_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;STORAGE_USERS_SERVICE_ACCOUNT_SECRET" desc:"The service account secret."` +} diff --git a/services/storage-users/pkg/config/defaults/defaultconfig.go b/services/storage-users/pkg/config/defaults/defaultconfig.go index 9cb71e4f2..5f4009bbc 100644 --- a/services/storage-users/pkg/config/defaults/defaultconfig.go +++ b/services/storage-users/pkg/config/defaults/defaultconfig.go @@ -108,6 +108,10 @@ func DefaultConfig() *config.Config { PersonalDeleteBefore: 30 * 24 * time.Hour, }, }, + ServiceAccount: config.ServiceAccount{ + ServiceAccountID: "service-user-id", + ServiceAccountSecret: "secret-string", + }, } } diff --git a/services/storage-users/pkg/event/service.go b/services/storage-users/pkg/event/service.go index 91384f75d..cd2e9b967 100644 --- a/services/storage-users/pkg/event/service.go +++ b/services/storage-users/pkg/event/service.go @@ -4,7 +4,6 @@ import ( "time" apiGateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - apiUser "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/owncloud/ocis/v2/ocis-pkg/log" @@ -53,11 +52,6 @@ func (s Service) Run() error { executionTime = time.Now() } - executantID := ev.ExecutantID - if executantID == nil { - executantID = &apiUser.UserId{OpaqueId: s.config.Tasks.PurgeTrashBin.UserID} - } - tasks := map[task.SpaceType]time.Time{ task.Project: executionTime.Add(-s.config.Tasks.PurgeTrashBin.ProjectDeleteBefore), task.Personal: executionTime.Add(-s.config.Tasks.PurgeTrashBin.PersonalDeleteBefore), @@ -70,7 +64,7 @@ func (s Service) Run() error { continue } - if err = task.PurgeTrashBin(executantID, deleteBefore, spaceType, s.gatewaySelector, s.config.Commons.MachineAuthAPIKey); err != nil { + if err = task.PurgeTrashBin(s.config.ServiceAccount.ServiceAccountID, deleteBefore, spaceType, s.gatewaySelector, s.config.ServiceAccount.ServiceAccountSecret); err != nil { errs = append(errs, err) } } diff --git a/services/storage-users/pkg/task/trash_bin.go b/services/storage-users/pkg/task/trash_bin.go index daf49fad1..e7bb2cb63 100644 --- a/services/storage-users/pkg/task/trash_bin.go +++ b/services/storage-users/pkg/task/trash_bin.go @@ -1,11 +1,9 @@ package task import ( - "fmt" "time" apiGateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - apiUser "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" apiRpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" apiProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/errtypes" @@ -17,18 +15,18 @@ import ( // the provided executantID must have space access. // removeBefore specifies how long an item must be in the trash-bin to be deleted, // items that stay there for a shorter time are ignored and kept in place. -func PurgeTrashBin(executantID *apiUser.UserId, deleteBefore time.Time, spaceType SpaceType, gatewaySelector pool.Selectable[apiGateway.GatewayAPIClient], machineAuthAPIKey string) error { +func PurgeTrashBin(serviceAccountID string, deleteBefore time.Time, spaceType SpaceType, gatewaySelector pool.Selectable[apiGateway.GatewayAPIClient], serviceAccountSecret string) error { gatewayClient, err := gatewaySelector.Next() if err != nil { return err } - executantCtx, _, err := utils.Impersonate(executantID, gatewayClient, machineAuthAPIKey) + ctx, err := utils.GetServiceUserContext(serviceAccountID, gatewayClient, serviceAccountSecret) if err != nil { return err } - listStorageSpacesResponse, err := gatewayClient.ListStorageSpaces(executantCtx, &apiProvider.ListStorageSpacesRequest{ + listStorageSpacesResponse, err := gatewayClient.ListStorageSpaces(ctx, &apiProvider.ListStorageSpacesRequest{ Filters: []*apiProvider.ListStorageSpacesRequest_Filter{ { Type: apiProvider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE, @@ -43,52 +41,15 @@ func PurgeTrashBin(executantID *apiUser.UserId, deleteBefore time.Time, spaceTyp } for _, storageSpace := range listStorageSpacesResponse.StorageSpaces { - var ( - err error - impersonationID *apiUser.UserId - storageSpaceReference = &apiProvider.Reference{ - ResourceId: storageSpace.GetRoot(), - } - ) - - switch SpaceType(storageSpace.GetSpaceType()) { - case Personal: - impersonationID = storageSpace.GetOwner().GetId() - case Project: - var permissionsMap map[string]*apiProvider.ResourcePermissions - err := utils.ReadJSONFromOpaque(storageSpace.GetOpaque(), "grants", &permissionsMap) - if err != nil { - break - } - - for id, permissions := range permissionsMap { - if !permissions.Delete { - continue - } - - impersonationID = &apiUser.UserId{ - OpaqueId: id, - } - break - } - default: + if typ := storageSpace.GetSpaceType(); typ != "personal" && typ != "project" { + // ignore spaces that are neither personal nor project continue } - - if err != nil { - return err + storageSpaceReference := &apiProvider.Reference{ + ResourceId: storageSpace.GetRoot(), } - if impersonationID == nil { - return fmt.Errorf("can't impersonate space user for space: %s", storageSpace.GetId().GetOpaqueId()) - } - - impersonatedCtx, _, err := utils.Impersonate(impersonationID, gatewayClient, machineAuthAPIKey) - if err != nil { - return err - } - - listRecycleResponse, err := gatewayClient.ListRecycle(impersonatedCtx, &apiProvider.ListRecycleRequest{Ref: storageSpaceReference}) + listRecycleResponse, err := gatewayClient.ListRecycle(ctx, &apiProvider.ListRecycleRequest{Ref: storageSpaceReference}) if err != nil { return err } @@ -99,7 +60,7 @@ func PurgeTrashBin(executantID *apiUser.UserId, deleteBefore time.Time, spaceTyp continue } - purgeRecycleResponse, err := gatewayClient.PurgeRecycle(impersonatedCtx, &apiProvider.PurgeRecycleRequest{ + purgeRecycleResponse, err := gatewayClient.PurgeRecycle(ctx, &apiProvider.PurgeRecycleRequest{ Ref: storageSpaceReference, Key: recycleItem.Key, }) diff --git a/services/storage-users/pkg/task/trash_bin_test.go b/services/storage-users/pkg/task/trash_bin_test.go index 1066565c9..9800f954f 100644 --- a/services/storage-users/pkg/task/trash_bin_test.go +++ b/services/storage-users/pkg/task/trash_bin_test.go @@ -115,7 +115,7 @@ var _ = Describe("trash", func() { gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(nil, genericError) - err := task.PurgeTrashBin(user.Id, now, task.Project, gatewaySelector, "") + err := task.PurgeTrashBin("service-user-id", now, task.Project, gatewaySelector, "") Expect(err).To(HaveOccurred()) }) It("throws an error if space listing fails", func() { @@ -123,45 +123,9 @@ var _ = Describe("trash", func() { gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(authenticateResponse, nil) gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(nil, genericError) - err := task.PurgeTrashBin(user.Id, now, task.Project, gatewaySelector, "") + err := task.PurgeTrashBin("service-user-id", now, task.Project, gatewaySelector, "") Expect(err).To(HaveOccurred()) }) - It("throws an error if a personal space user can't be impersonated", func() { - listStorageSpacesResponse.StorageSpaces = []*apiProvider.StorageSpace{personalSpace} - gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) - gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(authenticateResponse, nil) - gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listStorageSpacesResponse, nil) - - err := task.PurgeTrashBin(user.Id, now, task.Project, gatewaySelector, "") - Expect(err).To(MatchError(errors.New("can't impersonate space user for space: personal"))) - }) - It("throws an error if a project space user can't be impersonated", func() { - listStorageSpacesResponse.StorageSpaces = []*apiProvider.StorageSpace{projectSpace} - gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) - gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(authenticateResponse, nil) - gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listStorageSpacesResponse, nil) - - err := task.PurgeTrashBin(user.Id, now, task.Project, gatewaySelector, "") - Expect(err).To(MatchError(errors.New("can't impersonate space user for space: project"))) - }) - It("throws an error if a project space has no user with delete permissions", func() { - listStorageSpacesResponse.StorageSpaces = []*apiProvider.StorageSpace{projectSpace} - projectSpace.Opaque.Map = map[string]*apiTypes.OpaqueEntry{ - "grants": { - Value: MustMarshal(map[string]*apiProvider.ResourcePermissions{ - "admin": { - Delete: false, - }, - }), - }, - } - gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil) - gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(authenticateResponse, nil) - gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listStorageSpacesResponse, nil) - - err := task.PurgeTrashBin(user.Id, now, task.Project, gatewaySelector, "") - Expect(err).To(MatchError(errors.New("can't impersonate space user for space: project"))) - }) It("only deletes items older than the specified period", func() { var ( recycleItems = map[string][]*apiProvider.RecycleItem{ @@ -231,7 +195,7 @@ var _ = Describe("trash", func() { }, nil, ) - err := task.PurgeTrashBin(user.Id, now, task.Project, gatewaySelector, "") + err := task.PurgeTrashBin("service-user-id", now, task.Project, gatewaySelector, "") Expect(err).To(BeNil()) Expect(recycleItems["personal"]).To(HaveLen(2)) Expect(recycleItems["project"]).To(HaveLen(2)) From adcfddb7b245dbf9e4abdc7e08ac40bfe2167789 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Fri, 11 Aug 2023 14:13:23 +0200 Subject: [PATCH 07/11] adjust tests Signed-off-by: jkoberg --- tests/acceptance/features/apiSpaces/listSpaces.feature | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/acceptance/features/apiSpaces/listSpaces.feature b/tests/acceptance/features/apiSpaces/listSpaces.feature index 87d0d4595..1390176d5 100644 --- a/tests/acceptance/features/apiSpaces/listSpaces.feature +++ b/tests/acceptance/features/apiSpaces/listSpaces.feature @@ -425,7 +425,5 @@ Feature: List and create spaces And the json responded should not contain a space with name "Project Venus" Examples: | role | - | Admin | - | Space Admin | | User | | User Light | From 90ce1a7ad0fc2350331a4b105f6b5d9d36243fb8 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Thu, 1 Jun 2023 13:46:49 +0200 Subject: [PATCH 08/11] add auth-service Signed-off-by: jkoberg --- .drone.star | 1 + Makefile | 1 + ocis-pkg/config/config.go | 2 + ocis-pkg/config/defaultconfig.go | 2 + ocis/pkg/command/services.go | 6 ++ ocis/pkg/runtime/service/service.go | 6 ++ services/auth-basic/README.md | 8 ++ services/auth-bearer/README.md | 10 +- services/auth-machine/README.md | 17 ++++ services/auth-service/Makefile | 37 +++++++ services/auth-service/README.md | 19 ++++ .../auth-service/cmd/auth-service/main.go | 14 +++ services/auth-service/pkg/command/health.go | 54 ++++++++++ services/auth-service/pkg/command/root.go | 34 +++++++ services/auth-service/pkg/command/server.go | 99 +++++++++++++++++++ services/auth-service/pkg/command/version.go | 50 ++++++++++ services/auth-service/pkg/config/config.go | 63 ++++++++++++ .../pkg/config/defaults/defaultconfig.go | 87 ++++++++++++++++ .../auth-service/pkg/config/parser/parse.go | 42 ++++++++ services/auth-service/pkg/config/reva.go | 6 ++ services/auth-service/pkg/logging/logging.go | 17 ++++ .../auth-service/pkg/revaconfig/config.go | 53 ++++++++++ .../auth-service/pkg/server/debug/option.go | 50 ++++++++++ .../auth-service/pkg/server/debug/server.go | 63 ++++++++++++ services/auth-service/pkg/tracing/tracing.go | 25 +++++ services/gateway/pkg/config/config.go | 1 + .../pkg/config/defaults/defaultconfig.go | 1 + services/gateway/pkg/revaconfig/config.go | 7 +- 28 files changed, 771 insertions(+), 4 deletions(-) create mode 100644 services/auth-machine/README.md create mode 100644 services/auth-service/Makefile create mode 100644 services/auth-service/README.md create mode 100644 services/auth-service/cmd/auth-service/main.go create mode 100644 services/auth-service/pkg/command/health.go create mode 100644 services/auth-service/pkg/command/root.go create mode 100644 services/auth-service/pkg/command/server.go create mode 100644 services/auth-service/pkg/command/version.go create mode 100644 services/auth-service/pkg/config/config.go create mode 100644 services/auth-service/pkg/config/defaults/defaultconfig.go create mode 100644 services/auth-service/pkg/config/parser/parse.go create mode 100644 services/auth-service/pkg/config/reva.go create mode 100644 services/auth-service/pkg/logging/logging.go create mode 100644 services/auth-service/pkg/revaconfig/config.go create mode 100644 services/auth-service/pkg/server/debug/option.go create mode 100644 services/auth-service/pkg/server/debug/server.go create mode 100644 services/auth-service/pkg/tracing/tracing.go diff --git a/.drone.star b/.drone.star index 0d14257a4..63fdd6852 100644 --- a/.drone.star +++ b/.drone.star @@ -61,6 +61,7 @@ config = { "services/auth-basic", "services/auth-bearer", "services/auth-machine", + "services/auth-service", "services/eventhistory", "services/frontend", "services/gateway", diff --git a/Makefile b/Makefile index 8e648ffc2..f45ce7f17 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,7 @@ OCIS_MODULES = \ services/auth-basic \ services/auth-bearer \ services/auth-machine \ + services/auth-service \ services/eventhistory \ services/frontend \ services/gateway \ diff --git a/ocis-pkg/config/config.go b/ocis-pkg/config/config.go index bf16fd671..77fec4f6f 100644 --- a/ocis-pkg/config/config.go +++ b/ocis-pkg/config/config.go @@ -9,6 +9,7 @@ import ( authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config" authbearer "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config" authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config" + authservice "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" eventhistory "github.com/owncloud/ocis/v2/services/eventhistory/pkg/config" frontend "github.com/owncloud/ocis/v2/services/frontend/pkg/config" gateway "github.com/owncloud/ocis/v2/services/gateway/pkg/config" @@ -83,6 +84,7 @@ type Config struct { AuthBasic *authbasic.Config `yaml:"auth_basic"` AuthBearer *authbearer.Config `yaml:"auth_bearer"` AuthMachine *authmachine.Config `yaml:"auth_machine"` + AuthService *authservice.Config `yaml:"auth_service"` EventHistory *eventhistory.Config `yaml:"eventhistory"` Frontend *frontend.Config `yaml:"frontend"` Gateway *gateway.Config `yaml:"gateway"` diff --git a/ocis-pkg/config/defaultconfig.go b/ocis-pkg/config/defaultconfig.go index bfa03aee6..8639821bc 100644 --- a/ocis-pkg/config/defaultconfig.go +++ b/ocis-pkg/config/defaultconfig.go @@ -8,6 +8,7 @@ import ( authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config/defaults" authbearer "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config/defaults" authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config/defaults" + authservice "github.com/owncloud/ocis/v2/services/auth-service/pkg/config/defaults" eventhistory "github.com/owncloud/ocis/v2/services/eventhistory/pkg/config/defaults" frontend "github.com/owncloud/ocis/v2/services/frontend/pkg/config/defaults" gateway "github.com/owncloud/ocis/v2/services/gateway/pkg/config/defaults" @@ -55,6 +56,7 @@ func DefaultConfig() *Config { AuthBasic: authbasic.DefaultConfig(), AuthBearer: authbearer.DefaultConfig(), AuthMachine: authmachine.DefaultConfig(), + AuthService: authservice.DefaultConfig(), EventHistory: eventhistory.DefaultConfig(), Frontend: frontend.DefaultConfig(), Gateway: gateway.DefaultConfig(), diff --git a/ocis/pkg/command/services.go b/ocis/pkg/command/services.go index df143b623..da634e2df 100644 --- a/ocis/pkg/command/services.go +++ b/ocis/pkg/command/services.go @@ -15,6 +15,7 @@ import ( authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/command" authbearer "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/command" authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/command" + authservice "github.com/owncloud/ocis/v2/services/auth-service/pkg/command" eventhistory "github.com/owncloud/ocis/v2/services/eventhistory/pkg/command" frontend "github.com/owncloud/ocis/v2/services/frontend/pkg/command" gateway "github.com/owncloud/ocis/v2/services/gateway/pkg/command" @@ -83,6 +84,11 @@ var svccmds = []register.Command{ cfg.AuthMachine.Commons = cfg.Commons }) }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.AuthService.Service.Name, authservice.GetCommands(cfg.AuthService), func(c *config.Config) { + cfg.AuthService.Commons = cfg.Commons + }) + }, func(cfg *config.Config) *cli.Command { return ServiceCommand(cfg, cfg.EventHistory.Service.Name, eventhistory.GetCommands(cfg.EventHistory), func(c *config.Config) { cfg.EventHistory.Commons = cfg.Commons diff --git a/ocis/pkg/runtime/service/service.go b/ocis/pkg/runtime/service/service.go index 027ef7e55..1f43db921 100644 --- a/ocis/pkg/runtime/service/service.go +++ b/ocis/pkg/runtime/service/service.go @@ -26,6 +26,7 @@ import ( audit "github.com/owncloud/ocis/v2/services/audit/pkg/command" authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/command" authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/command" + authservice "github.com/owncloud/ocis/v2/services/auth-service/pkg/command" eventhistory "github.com/owncloud/ocis/v2/services/eventhistory/pkg/command" frontend "github.com/owncloud/ocis/v2/services/frontend/pkg/command" gateway "github.com/owncloud/ocis/v2/services/gateway/pkg/command" @@ -135,6 +136,11 @@ func NewService(options ...Option) (*Service, error) { cfg.AuthMachine.Commons = cfg.Commons return authmachine.Execute(cfg.AuthMachine) }) + reg(opts.Config.AuthService.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.AuthService.Context = ctx + cfg.AuthService.Commons = cfg.Commons + return authservice.Execute(cfg.AuthService) + }) reg(opts.Config.EventHistory.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.EventHistory.Context = ctx cfg.EventHistory.Commons = cfg.Commons diff --git a/services/auth-basic/README.md b/services/auth-basic/README.md index c1b1db30b..c5afe1063 100644 --- a/services/auth-basic/README.md +++ b/services/auth-basic/README.md @@ -6,6 +6,14 @@ The `auth-basic` service is responsible for validating authentication of incomin To enable `auth-basic`, you first must set `PROXY_ENABLE_BASIC_AUTH` to `true`. +## The `auth` Service Family + +ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist: + - `auth-basic` handles basic authentication + - `auth-bearer` handles oidc authentication + - `auth-machine` handles interservice authentication when a user is impersonated + - `auth-service` handles interservice authentication when using service accounts + ## Auth Managers Since the `auth-basic` service does not do any validation itself, it needs to be configured with an authentication manager. One can use the `AUTH_BASIC_AUTH_MANAGER` environment variable to configure this. Currently only one auth manager is supported: `"ldap"` diff --git a/services/auth-bearer/README.md b/services/auth-bearer/README.md index 6ada9310e..024f6caa1 100644 --- a/services/auth-bearer/README.md +++ b/services/auth-bearer/README.md @@ -2,7 +2,15 @@ The oCIS Auth Bearer service communicates with the configured OpenID Connect identity provider to authenticate requests. OpenID Connect is the default authentication mechanism for all clients: web, desktop and mobile. Basic auth is only used for testing and has to be explicity enabled. -## Built in OpenID Connect identity provider +## The `auth` Service Family + +ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist: + - `auth-basic` handles basic authentication + - `auth-bearer` handles oidc authentication + - `auth-machine` handles interservice authentication when a user is impersonated + - `auth-service` handles interservice authentication when using service accounts + +## Built in OpenID Connect Identity Provider A default oCIS deployment will start a [built in OpenID Connect identity provider](https://github.com/owncloud/ocis/tree/master/services/idp) but can be configured to use an external one as well. diff --git a/services/auth-machine/README.md b/services/auth-machine/README.md new file mode 100644 index 000000000..b06664054 --- /dev/null +++ b/services/auth-machine/README.md @@ -0,0 +1,17 @@ +# Auth-Machine + +The oCIS Auth Machine is used for interservice communication when using user impersonation. + +ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist: + - `auth-basic` handles basic authentication + - `auth-bearer` handles oidc authentication + - `auth-machine` handles interservice authentication when a user is impersonated + - `auth-service` handles interservice authentication when using service accounts + +## User Impersonation + +When one ocis service is trying to talk to other ocis services, it needs to authenticate itself. To do so, it will impersonate a user using the `auth-machine` service. It will then act on behalf of this user. Any action will show up as action of this specific user, which gets visible when e.g. logged in the audit log. + +## Deprecation + +With the upcoming `auth-service` service, the `auth-machine` service will be used less frequently and is probably a candidate for deprecation. diff --git a/services/auth-service/Makefile b/services/auth-service/Makefile new file mode 100644 index 000000000..bab3eec58 --- /dev/null +++ b/services/auth-service/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := auth-service + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell command -v go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/services/auth-service/README.md b/services/auth-service/README.md new file mode 100644 index 000000000..b34057d14 --- /dev/null +++ b/services/auth-service/README.md @@ -0,0 +1,19 @@ +# Auth-Service + +The ocis Auth Service is used to authenticate service accounts. Compared to normal accounts, service accounts are ocis internal only and not available as ordinary users like via LDAP. + +## The `auth` Service Family + +ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist: + - `auth-basic` handles basic authentication + - `auth-bearer` handles oidc authentication + - `auth-machine` handles interservice authentication when a user is impersonated + - `auth-service` handles interservice authentication when using service accounts + +## Service Accounts + +Service accounts are user accounts that are only used for inter service communication. The users have no personal space, do not show up in user lists and cannot login via the UI. Service accounts can be configured in the settings service. Only the `admin` service user is available for now. Additionally to the actions it can do via its role, all service users can stat all files on all spaces. + +## Configuring Service Accounts + +By using the envvars `OCIS_SERVICE_ACCOUNT_ID` and `OCIS_SERVICE_ACCOUNT_SECRET`, one can configure the ID and the secret of the service user. The secret can be rotated regulary to increase security. For activating a new secret, all services where the envvars are used need to be restarted. The secret is always and only stored in memory and never written into any persistant store. Though you can use any string for the service account, it is recommmended to use a UUIDv4 string. diff --git a/services/auth-service/cmd/auth-service/main.go b/services/auth-service/cmd/auth-service/main.go new file mode 100644 index 000000000..bcc7a625e --- /dev/null +++ b/services/auth-service/cmd/auth-service/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/auth-service/pkg/command" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/auth-service/pkg/command/health.go b/services/auth-service/pkg/command/health.go new file mode 100644 index 000000000..7470b581d --- /dev/null +++ b/services/auth-service/pkg/command/health.go @@ -0,0 +1,54 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + return configlog.ReturnError(parser.ParseConfig(cfg)) + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/services/auth-service/pkg/command/root.go b/services/auth-service/pkg/command/root.go new file mode 100644 index 000000000..2fd57dd7a --- /dev/null +++ b/services/auth-service/pkg/command/root.go @@ -0,0 +1,34 @@ +package command + +import ( + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-auth-service command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "auth-service", + Usage: "Provide service authentication for oCIS", + Commands: GetCommands(cfg), + }) + + return app.Run(os.Args) +} diff --git a/services/auth-service/pkg/command/server.go b/services/auth-service/pkg/command/server.go new file mode 100644 index 000000000..21bdeee3b --- /dev/null +++ b/services/auth-service/pkg/command/server.go @@ -0,0 +1,99 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/sync" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/logging" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/tracing" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + return configlog.ReturnFatal(parser.ParseConfig(cfg)) + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.AuthMachineConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(err error) { + logger.Error(). + Err(err). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString()) + if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc service") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the service. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/services/auth-service/pkg/command/version.go b/services/auth-service/pkg/command/version.go new file mode 100644 index 000000000..8bd660262 --- /dev/null +++ b/services/auth-service/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running service instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.GetString()) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/services/auth-service/pkg/config/config.go b/services/auth-service/pkg/config/config.go new file mode 100644 index 000000000..031449700 --- /dev/null +++ b/services/auth-service/pkg/config/config.go @@ -0,0 +1,63 @@ +package config + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/shared" +) + +type Config struct { + Commons *shared.Commons `yaml:"-"` // don't use this directly as configuration for a service + Service Service `yaml:"-"` + Tracing *Tracing `yaml:"tracing"` + Log *Log `yaml:"log"` + Debug Debug `yaml:"debug"` + + GRPC GRPCConfig `yaml:"grpc"` + + TokenManager *TokenManager `yaml:"token_manager"` + Reva *shared.Reva `yaml:"reva"` + + // TODO: when using multiple service accounts we need to find a way to configure them + ServiceAccount ServiceAccount `yaml:"service_account"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` +} +type Tracing struct { + Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;AUTH_SERVICE_TRACING_ENABLED" desc:"Activates tracing."` + Type string `yaml:"type" env:"OCIS_TRACING_TYPE;AUTH_SERVICE_TRACING_TYPE" desc:"The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now."` + Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;AUTH_SERVICE_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent."` + Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;AUTH_SERVICE_TRACING_COLLECTOR" desc:"The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset."` +} + +type Log struct { + Level string `yaml:"level" env:"OCIS_LOG_LEVEL;AUTH_SERVICE_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'."` + Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;AUTH_SERVICE_LOG_PRETTY" desc:"Activates pretty log output."` + Color bool `yaml:"color" env:"OCIS_LOG_COLOR;AUTH_SERVICE_LOG_COLOR" desc:"Activates colorized log output."` + File string `yaml:"file" env:"OCIS_LOG_FILE;AUTH_SERVICE_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set."` +} + +type Service struct { + Name string `yaml:"-"` +} + +type Debug struct { + Addr string `yaml:"addr" env:"AUTH_SERVICE_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."` + Token string `yaml:"token" env:"AUTH_SERVICE_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint."` + Pprof bool `yaml:"pprof" env:"AUTH_SERVICE_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling."` + Zpages bool `yaml:"zpages" env:"AUTH_SERVICE_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing in-memory traces."` +} + +type GRPCConfig struct { + Addr string `yaml:"addr" env:"AUTH_SERVICE_GRPC_ADDR" desc:"The bind address of the GRPC service."` + TLS *shared.GRPCServiceTLS `yaml:"tls"` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"AUTH_SERVICE_GRPC_PROTOCOL" desc:"The transport protocol of the GRPC service."` +} + +// ServiceAccount is the configuration for the used service account +type ServiceAccount struct { + ServiceAccountID string `yaml:"service_account_id" env:"OCIS_SERVICE_ACCOUNT_ID;AUTH_SERVICE_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;AUTH_SERVICE_SERVICE_ACCOUNT_SECRET" desc:"The service account secret."` +} diff --git a/services/auth-service/pkg/config/defaults/defaultconfig.go b/services/auth-service/pkg/config/defaults/defaultconfig.go new file mode 100644 index 000000000..923a886bd --- /dev/null +++ b/services/auth-service/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,87 @@ +package defaults + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/ocis-pkg/structs" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" +) + +// FullDefaultConfig returns a fully initialized default configuration +func FullDefaultConfig() *config.Config { + cfg := DefaultConfig() + EnsureDefaults(cfg) + Sanitize(cfg) + return cfg +} + +// DefaultConfig returns a basic default configuration +func DefaultConfig() *config.Config { + return &config.Config{ + Debug: config.Debug{ + Addr: "127.0.0.1:9169", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9199", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + Service: config.Service{ + Name: "auth-service", + }, + Reva: shared.DefaultRevaConfig(), + ServiceAccount: config.ServiceAccount{ + ServiceAccountID: "service-user-id", + ServiceAccountSecret: "secret-string", + }, + } +} + +// EnsureDefaults adds default values to the configuration if they are not set yet +func EnsureDefaults(cfg *config.Config) { + // provide with defaults for shared logging, since we need a valid destination address for "envdecode". + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ + Level: cfg.Commons.Log.Level, + Pretty: cfg.Commons.Log.Pretty, + Color: cfg.Commons.Log.Color, + File: cfg.Commons.Log.File, + } + } else if cfg.Log == nil { + cfg.Log = &config.Log{} + } + // provide with defaults for shared tracing, since we need a valid destination address for "envdecode". + if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { + cfg.Tracing = &config.Tracing{ + Enabled: cfg.Commons.Tracing.Enabled, + Type: cfg.Commons.Tracing.Type, + Endpoint: cfg.Commons.Tracing.Endpoint, + Collector: cfg.Commons.Tracing.Collector, + } + } else if cfg.Tracing == nil { + cfg.Tracing = &config.Tracing{} + } + + if cfg.Reva == nil && cfg.Commons != nil { + cfg.Reva = structs.CopyOrZeroValue(cfg.Commons.Reva) + } + + 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.GRPC.TLS == nil && cfg.Commons != nil { + cfg.GRPC.TLS = structs.CopyOrZeroValue(cfg.Commons.GRPCServiceTLS) + } +} + +// Sanitize sanitized the configuration +func Sanitize(cfg *config.Config) { + // nothing to sanitize here atm +} diff --git a/services/auth-service/pkg/config/parser/parse.go b/services/auth-service/pkg/config/parser/parse.go new file mode 100644 index 000000000..2bb6b6630 --- /dev/null +++ b/services/auth-service/pkg/config/parser/parse.go @@ -0,0 +1,42 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + return nil +} diff --git a/services/auth-service/pkg/config/reva.go b/services/auth-service/pkg/config/reva.go new file mode 100644 index 000000000..14cb00d08 --- /dev/null +++ b/services/auth-service/pkg/config/reva.go @@ -0,0 +1,6 @@ +package config + +// TokenManager is the config for using the reva token manager +type TokenManager struct { + JWTSecret string `yaml:"jwt_secret" env:"OCIS_JWT_SECRET;AUTH_MACHINE_JWT_SECRET" desc:"The secret to mint and validate jwt tokens."` +} diff --git a/services/auth-service/pkg/logging/logging.go b/services/auth-service/pkg/logging/logging.go new file mode 100644 index 000000000..79b966fd2 --- /dev/null +++ b/services/auth-service/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/services/auth-service/pkg/revaconfig/config.go b/services/auth-service/pkg/revaconfig/config.go new file mode 100644 index 000000000..4baaff794 --- /dev/null +++ b/services/auth-service/pkg/revaconfig/config.go @@ -0,0 +1,53 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" +) + +// AuthMachineConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func AuthMachineConfigFromStruct(cfg *config.Config) map[string]interface{} { + return map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_exporter": cfg.Tracing.Type, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "grpc_client_options": cfg.Reva.GetGRPCClientConfig(), + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + "tls_settings": map[string]interface{}{ + "enabled": cfg.GRPC.TLS.Enabled, + "certificate": cfg.GRPC.TLS.Cert, + "key": cfg.GRPC.TLS.Key, + }, + "services": map[string]interface{}{ + "authprovider": map[string]interface{}{ + "auth_manager": "serviceaccounts", + "auth_managers": map[string]interface{}{ + "serviceaccounts": map[string]interface{}{ + "service_accounts": []map[string]interface{}{ + { + "id": cfg.ServiceAccount.ServiceAccountID, + "secret": cfg.ServiceAccount.ServiceAccountSecret, + }, + }, + }, + }, + }, + }, + "interceptors": map[string]interface{}{ + "prometheus": map[string]interface{}{ + "namespace": "ocis", + "subsystem": "auth_service", + }, + }, + }, + } +} diff --git a/services/auth-service/pkg/server/debug/option.go b/services/auth-service/pkg/server/debug/option.go new file mode 100644 index 000000000..b11a774e6 --- /dev/null +++ b/services/auth-service/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/services/auth-service/pkg/server/debug/server.go b/services/auth-service/pkg/server/debug/server.go new file mode 100644 index 000000000..faedc717b --- /dev/null +++ b/services/auth-service/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.GetString()), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/services/auth-service/pkg/tracing/tracing.go b/services/auth-service/pkg/tracing/tracing.go new file mode 100644 index 000000000..357ca4ebb --- /dev/null +++ b/services/auth-service/pkg/tracing/tracing.go @@ -0,0 +1,25 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" + "go.opentelemetry.io/otel/trace" + + pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + var err error + if cfg.Tracing.Enabled { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { + return err + } + } + + return nil +} diff --git a/services/gateway/pkg/config/config.go b/services/gateway/pkg/config/config.go index 791064a67..c19f88785 100644 --- a/services/gateway/pkg/config/config.go +++ b/services/gateway/pkg/config/config.go @@ -38,6 +38,7 @@ type Config struct { AuthBasicEndpoint string `yaml:"-"` AuthBearerEndpoint string `yaml:"-"` AuthMachineEndpoint string `yaml:"-"` + AuthServiceEndpoint string `yaml:"-"` StoragePublicLinkEndpoint string `yaml:"-"` StorageUsersEndpoint string `yaml:"-"` StorageSharesEndpoint string `yaml:"-"` diff --git a/services/gateway/pkg/config/defaults/defaultconfig.go b/services/gateway/pkg/config/defaults/defaultconfig.go index 3e0fd026e..f66332a67 100644 --- a/services/gateway/pkg/config/defaults/defaultconfig.go +++ b/services/gateway/pkg/config/defaults/defaultconfig.go @@ -55,6 +55,7 @@ func DefaultConfig() *config.Config { AppRegistryEndpoint: "com.owncloud.api.app-registry", AuthBasicEndpoint: "com.owncloud.api.auth-basic", AuthMachineEndpoint: "com.owncloud.api.auth-machine", + AuthServiceEndpoint: "com.owncloud.api.auth-service", GroupsEndpoint: "com.owncloud.api.groups", PermissionsEndpoint: "com.owncloud.api.settings", SharingEndpoint: "com.owncloud.api.sharing", diff --git a/services/gateway/pkg/revaconfig/config.go b/services/gateway/pkg/revaconfig/config.go index d9c5df0d9..97ef2b44d 100644 --- a/services/gateway/pkg/revaconfig/config.go +++ b/services/gateway/pkg/revaconfig/config.go @@ -80,9 +80,10 @@ func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]i "drivers": map[string]interface{}{ "static": map[string]interface{}{ "rules": map[string]interface{}{ - "basic": cfg.AuthBasicEndpoint, - "machine": cfg.AuthMachineEndpoint, - "publicshares": cfg.StoragePublicLinkEndpoint, + "basic": cfg.AuthBasicEndpoint, + "machine": cfg.AuthMachineEndpoint, + "publicshares": cfg.StoragePublicLinkEndpoint, + "serviceaccounts": cfg.AuthServiceEndpoint, }, }, }, From bd716156fc9a7f400f047d361167814ed42b9186 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Tue, 29 Aug 2023 15:37:02 +0200 Subject: [PATCH 09/11] let ocis init set the serviceaccounts Signed-off-by: jkoberg --- ocis/pkg/init/init.go | 62 ++++++++++++++++--- .../pkg/config/defaults/defaultconfig.go | 4 -- .../pkg/config/defaults/defaultconfig.go | 4 -- .../pkg/config/defaults/defaultconfig.go | 4 -- .../pkg/config/defaults/defaultconfig.go | 4 -- .../pkg/config/defaults/defaultconfig.go | 4 -- .../pkg/config/defaults/defaultconfig.go | 4 -- 7 files changed, 53 insertions(+), 33 deletions(-) diff --git a/ocis/pkg/init/init.go b/ocis/pkg/init/init.go index 560af6314..01aef0cea 100644 --- a/ocis/pkg/init/init.go +++ b/ocis/pkg/init/init.go @@ -55,10 +55,11 @@ type GraphApplication struct { } type GraphService struct { - Application GraphApplication - Events Events - Spaces InsecureService - Identity LdapBasedService + Application GraphApplication + Events Events + Spaces InsecureService + Identity LdapBasedService + ServiceAccount ServiceAccount `yaml:"service_account"` } type ServiceUserPasswordsSettings struct { @@ -101,7 +102,8 @@ type ThumbnailService struct { } type Search struct { - Events Events + Events Events + ServiceAccount ServiceAccount `yaml:"service_account"` } type Audit struct { @@ -113,8 +115,9 @@ type Sharing struct { } type StorageUsers struct { - Events Events - MountID string `yaml:"mount_id"` + Events Events + MountID string `yaml:"mount_id"` + ServiceAccount ServiceAccount `yaml:"service_account"` } type Gateway struct { @@ -126,7 +129,16 @@ type StorageRegistry struct { } type Notifications struct { - Notifications struct{ Events Events } // The notifications config has a field called notifications + Notifications struct{ Events Events } // The notifications config has a field called notifications + ServiceAccount ServiceAccount `yaml:"service_account"` +} + +type Userlog struct { + ServiceAccount ServiceAccount `yaml:"service_account"` +} + +type AuthService struct { + ServiceAccount ServiceAccount `yaml:"service_account"` } type Nats struct { @@ -136,6 +148,12 @@ type Nats struct { } } +// ServiceAccount is the configuration for the used service account +type ServiceAccount struct { + ServiceAccountID string `yaml:"service_account_id"` + ServiceAccountSecret string `yaml:"service_account_secret"` +} + // TODO: use the oCIS config struct instead of this custom struct // We can't use it right now, because it would need "omitempty" on // all elements, in order to produce a slim config file with `ocis init`. @@ -173,6 +191,8 @@ type OcisConfig struct { Notifications Notifications Nats Nats Gateway Gateway + Userlog Userlog + AuthService AuthService `yaml:"auth_service"` } func checkConfigPath(configPath string) error { @@ -225,6 +245,7 @@ func CreateConfig(insecure, forceOverwrite bool, configPath, adminPassword strin adminUserID := uuid.Must(uuid.NewV4()).String() graphApplicationID := uuid.Must(uuid.NewV4()).String() storageUsersMountID := uuid.Must(uuid.NewV4()).String() + serviceAccountID := uuid.Must(uuid.NewV4()).String() idmServicePassword, err := generators.GenerateRandomPassword(passwordLength) if err != nil { @@ -266,6 +287,15 @@ func CreateConfig(insecure, forceOverwrite bool, configPath, adminPassword strin if err != nil { return fmt.Errorf("could not generate random password for thumbnailsTransferSecret: %s", err) } + serviceAccountSecret, err := generators.GenerateRandomPassword(passwordLength) + if err != nil { + return fmt.Errorf("could not generate random password for thumbnailsTransferSecret: %s", err) + } + + serviceAccount := ServiceAccount{ + ServiceAccountID: serviceAccountID, + ServiceAccountSecret: serviceAccountSecret, + } cfg := OcisConfig{ TokenManager: TokenManager{ @@ -319,6 +349,7 @@ func CreateConfig(insecure, forceOverwrite bool, configPath, adminPassword strin BindPassword: idmServicePassword, }, }, + ServiceAccount: serviceAccount, }, Thumbnails: ThumbnailService{ Thumbnail: ThumbnailSettings{ @@ -331,7 +362,20 @@ func CreateConfig(insecure, forceOverwrite bool, configPath, adminPassword strin }, }, StorageUsers: StorageUsers{ - MountID: storageUsersMountID, + MountID: storageUsersMountID, + ServiceAccount: serviceAccount, + }, + Userlog: Userlog{ + ServiceAccount: serviceAccount, + }, + AuthService: AuthService{ + ServiceAccount: serviceAccount, + }, + Search: Search{ + ServiceAccount: serviceAccount, + }, + Notifications: Notifications{ + ServiceAccount: serviceAccount, }, } diff --git a/services/auth-service/pkg/config/defaults/defaultconfig.go b/services/auth-service/pkg/config/defaults/defaultconfig.go index 923a886bd..9f05e4fd2 100644 --- a/services/auth-service/pkg/config/defaults/defaultconfig.go +++ b/services/auth-service/pkg/config/defaults/defaultconfig.go @@ -32,10 +32,6 @@ func DefaultConfig() *config.Config { Name: "auth-service", }, Reva: shared.DefaultRevaConfig(), - ServiceAccount: config.ServiceAccount{ - ServiceAccountID: "service-user-id", - ServiceAccountSecret: "secret-string", - }, } } diff --git a/services/graph/pkg/config/defaults/defaultconfig.go b/services/graph/pkg/config/defaults/defaultconfig.go index cde17260c..004a031b5 100644 --- a/services/graph/pkg/config/defaults/defaultconfig.go +++ b/services/graph/pkg/config/defaults/defaultconfig.go @@ -105,10 +105,6 @@ func DefaultConfig() *config.Config { Cluster: "ocis-cluster", EnableTLS: false, }, - ServiceAccount: config.ServiceAccount{ - ServiceAccountID: "service-user-id", - ServiceAccountSecret: "secret-string", - }, } } diff --git a/services/notifications/pkg/config/defaults/defaultconfig.go b/services/notifications/pkg/config/defaults/defaultconfig.go index b7deaa018..ab8e2eca7 100644 --- a/services/notifications/pkg/config/defaults/defaultconfig.go +++ b/services/notifications/pkg/config/defaults/defaultconfig.go @@ -44,10 +44,6 @@ func DefaultConfig() *config.Config { }, RevaGateway: shared.DefaultRevaConfig().Address, }, - ServiceAccount: config.ServiceAccount{ - ServiceAccountID: "service-user-id", - ServiceAccountSecret: "secret-string", - }, } } diff --git a/services/search/pkg/config/defaults/defaultconfig.go b/services/search/pkg/config/defaults/defaultconfig.go index 04c8d0a24..c8009d30b 100644 --- a/services/search/pkg/config/defaults/defaultconfig.go +++ b/services/search/pkg/config/defaults/defaultconfig.go @@ -54,10 +54,6 @@ func DefaultConfig() *config.Config { EnableTLS: false, }, ContentExtractionSizeLimit: 20 * 1024 * 1024, // Limit content extraction to <20MB files by default - ServiceAccount: config.ServiceAccount{ - ServiceAccountID: "service-user-id", - ServiceAccountSecret: "secret-string", - }, } } diff --git a/services/storage-users/pkg/config/defaults/defaultconfig.go b/services/storage-users/pkg/config/defaults/defaultconfig.go index 5f4009bbc..9cb71e4f2 100644 --- a/services/storage-users/pkg/config/defaults/defaultconfig.go +++ b/services/storage-users/pkg/config/defaults/defaultconfig.go @@ -108,10 +108,6 @@ func DefaultConfig() *config.Config { PersonalDeleteBefore: 30 * 24 * time.Hour, }, }, - ServiceAccount: config.ServiceAccount{ - ServiceAccountID: "service-user-id", - ServiceAccountSecret: "secret-string", - }, } } diff --git a/services/userlog/pkg/config/defaults/defaultconfig.go b/services/userlog/pkg/config/defaults/defaultconfig.go index 294923d1d..1dfd6318c 100644 --- a/services/userlog/pkg/config/defaults/defaultconfig.go +++ b/services/userlog/pkg/config/defaults/defaultconfig.go @@ -52,10 +52,6 @@ func DefaultConfig() *config.Config { AllowCredentials: true, }, }, - ServiceAccount: config.ServiceAccount{ - ServiceAccountID: "service-user-id", - ServiceAccountSecret: "secret-string", - }, } } From a42d56a83c2e8f5dd4babc43631a7f7ec2ff5c16 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Thu, 1 Jun 2023 13:58:33 +0200 Subject: [PATCH 10/11] bump reva Signed-off-by: jkoberg --- go.mod | 2 +- go.sum | 4 +- ocis/pkg/command/auth-service.go | 30 +++++++ .../reva/v2/pkg/auth/manager/loader/loader.go | 1 + .../v2/pkg/auth/manager/registry/registry.go | 4 +- .../serviceaccounts/serviceaccounts.go | 90 +++++++++++++++++++ .../v2/pkg/storage/registry/spaces/spaces.go | 2 +- .../utils/decomposedfs/node/permissions.go | 20 +++++ .../cs3org/reva/v2/pkg/utils/grpc.go | 27 ++---- vendor/modules.txt | 3 +- 10 files changed, 158 insertions(+), 25 deletions(-) create mode 100644 ocis/pkg/command/auth-service.go create mode 100644 vendor/github.com/cs3org/reva/v2/pkg/auth/manager/serviceaccounts/serviceaccounts.go diff --git a/go.mod b/go.mod index 285f87300..b5615d4a7 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/coreos/go-oidc v2.2.1+incompatible github.com/coreos/go-oidc/v3 v3.6.0 github.com/cs3org/go-cs3apis v0.0.0-20230516150832-730ac860c71d - github.com/cs3org/reva/v2 v2.16.1-0.20230828111521-594d4e103741 + github.com/cs3org/reva/v2 v2.16.1-0.20230829124655-8ba013d7a129 github.com/disintegration/imaging v1.6.2 github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e github.com/egirna/icap-client v0.1.1 diff --git a/go.sum b/go.sum index e797d7913..ba9abd5d8 100644 --- a/go.sum +++ b/go.sum @@ -858,8 +858,8 @@ github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= github.com/crewjam/saml v0.4.13 h1:TYHggH/hwP7eArqiXSJUvtOPNzQDyQ7vwmwEqlFWhMc= github.com/crewjam/saml v0.4.13/go.mod h1:igEejV+fihTIlHXYP8zOec3V5A8y3lws5bQBFsTm4gA= -github.com/cs3org/reva/v2 v2.16.1-0.20230828111521-594d4e103741 h1:y3Tw/ZVGPSDRiCslFUESomgSUOa3SAguOJKpiSk9pls= -github.com/cs3org/reva/v2 v2.16.1-0.20230828111521-594d4e103741/go.mod h1:RvhuweTFqzezjUFU0SIdTXakrEx9vJlMvQ7znPXSP1g= +github.com/cs3org/reva/v2 v2.16.1-0.20230829124655-8ba013d7a129 h1:259bY0/RA/xOxN+7SnRryP5MXbj/GmXgNRqv4LYb8Co= +github.com/cs3org/reva/v2 v2.16.1-0.20230829124655-8ba013d7a129/go.mod h1:RvhuweTFqzezjUFU0SIdTXakrEx9vJlMvQ7znPXSP1g= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/ocis/pkg/command/auth-service.go b/ocis/pkg/command/auth-service.go new file mode 100644 index 000000000..e0796e9b6 --- /dev/null +++ b/ocis/pkg/command/auth-service.go @@ -0,0 +1,30 @@ +package command + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" + "github.com/owncloud/ocis/v2/ocis-pkg/config/parser" + "github.com/owncloud/ocis/v2/ocis/pkg/command/helper" + "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/command" + "github.com/urfave/cli/v2" +) + +// AuthServiceCommand is the entrypoint for the AuthService command. +func AuthServiceCommand(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: cfg.AuthService.Service.Name, + Usage: helper.SubcommandDescription(cfg.AuthService.Service.Name), + Category: "services", + Before: func(c *cli.Context) error { + configlog.Error(parser.ParseConfig(cfg, true)) + cfg.AuthService.Commons = cfg.Commons + return nil + }, + Subcommands: command.GetCommands(cfg.AuthService), + } +} + +func init() { + register.AddCommand(AuthServiceCommand) +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/auth/manager/loader/loader.go b/vendor/github.com/cs3org/reva/v2/pkg/auth/manager/loader/loader.go index 694cd98c2..9fcba0554 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/auth/manager/loader/loader.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/auth/manager/loader/loader.go @@ -30,5 +30,6 @@ import ( _ "github.com/cs3org/reva/v2/pkg/auth/manager/oidc" _ "github.com/cs3org/reva/v2/pkg/auth/manager/owncloudsql" _ "github.com/cs3org/reva/v2/pkg/auth/manager/publicshares" + _ "github.com/cs3org/reva/v2/pkg/auth/manager/serviceaccounts" // Add your own here ) diff --git a/vendor/github.com/cs3org/reva/v2/pkg/auth/manager/registry/registry.go b/vendor/github.com/cs3org/reva/v2/pkg/auth/manager/registry/registry.go index aea682f79..8d92a13e1 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/auth/manager/registry/registry.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/auth/manager/registry/registry.go @@ -18,7 +18,9 @@ package registry -import "github.com/cs3org/reva/v2/pkg/auth" +import ( + "github.com/cs3org/reva/v2/pkg/auth" +) // NewFunc is the function that auth implementations // should register to at init time. diff --git a/vendor/github.com/cs3org/reva/v2/pkg/auth/manager/serviceaccounts/serviceaccounts.go b/vendor/github.com/cs3org/reva/v2/pkg/auth/manager/serviceaccounts/serviceaccounts.go new file mode 100644 index 000000000..dad1dcf72 --- /dev/null +++ b/vendor/github.com/cs3org/reva/v2/pkg/auth/manager/serviceaccounts/serviceaccounts.go @@ -0,0 +1,90 @@ +package serviceaccounts + +import ( + "context" + + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + + "github.com/cs3org/reva/v2/pkg/auth" + "github.com/cs3org/reva/v2/pkg/auth/manager/registry" + "github.com/cs3org/reva/v2/pkg/auth/scope" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" +) + +type conf struct { + ServiceUsers []serviceuser `mapstructure:"service_accounts"` +} + +type serviceuser struct { + ID string `mapstructure:"id"` + Secret string `mapstructure:"secret"` +} + +type manager struct { + authenticate func(userID, secret string) error +} + +func init() { + registry.Register("serviceaccounts", New) +} + +// Configure parses the map conf +func (m *manager) Configure(config map[string]interface{}) error { + c := &conf{} + if err := mapstructure.Decode(config, c); err != nil { + return errors.Wrap(err, "error decoding conf") + } + // only inmem authenticator for now + a := &inmemAuthenticator{make(map[string]string)} + for _, s := range c.ServiceUsers { + a.m[s.ID] = s.Secret + } + m.authenticate = a.Authenticate + return nil +} + +// New creates a new manager for the 'service' authentication +func New(conf map[string]interface{}) (auth.Manager, error) { + m := &manager{} + err := m.Configure(conf) + if err != nil { + return nil, err + } + + return m, nil +} + +// Authenticate authenticates the service account +func (m *manager) Authenticate(ctx context.Context, userID string, secret string) (*userpb.User, map[string]*authpb.Scope, error) { + if err := m.authenticate(userID, secret); err != nil { + return nil, nil, err + } + scope, err := scope.AddOwnerScope(nil) + if err != nil { + return nil, nil, err + } + return &userpb.User{ + // TODO: more details for service users? + Id: &userpb.UserId{ + OpaqueId: userID, + Type: userpb.UserType_USER_TYPE_SERVICE, + Idp: "none", + }, + }, scope, nil +} + +type inmemAuthenticator struct { + m map[string]string +} + +func (a *inmemAuthenticator) Authenticate(userID string, secret string) error { + if secret == "" || a.m[userID] == "" { + return errors.New("unknown user") + } + if a.m[userID] == secret { + return nil + } + return errors.New("secrets do not match") +} diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/registry/spaces/spaces.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/registry/spaces/spaces.go index 17f3cea74..aa8385f66 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/registry/spaces/spaces.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/registry/spaces/spaces.go @@ -481,7 +481,7 @@ func (r *registry) findProvidersForResource(ctx context.Context, id string, find }, }) } - spaces, err := r.findStorageSpaceOnProvider(ctx, address, filters, false) + spaces, err := r.findStorageSpaceOnProvider(ctx, address, filters, unrestricted) if err != nil { appctx.GetLogger(ctx).Debug().Err(err).Interface("provider", provider).Msg("findStorageSpaceOnProvider by id failed, continuing") continue diff --git a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/permissions.go b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/permissions.go index 1e5017241..84814a164 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/permissions.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node/permissions.go @@ -22,6 +22,7 @@ import ( "context" "strings" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/appctx" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" @@ -84,6 +85,21 @@ func OwnerPermissions() provider.ResourcePermissions { } } +// ServiceAccountPermissions defines the permissions for nodes when requested by a service account +func ServiceAccountPermissions() provider.ResourcePermissions { + // TODO: Different permissions for different service accounts + return provider.ResourcePermissions{ + Stat: true, + ListContainer: true, + GetPath: true, // for search index + InitiateFileUpload: true, // for personal data export + InitiateFileDownload: true, // for full-text-search + RemoveGrant: true, // for share expiry + ListRecycle: true, // for purge-trash-bin command + PurgeRecycle: true, // for purge-trash-bin command + } +} + // Permissions implements permission checks type Permissions struct { lu PathLookup @@ -113,6 +129,10 @@ func (p *Permissions) assemblePermissions(ctx context.Context, n *Node, failOnTr return NoPermissions(), nil } + if u.GetId().GetType() == userpb.UserType_USER_TYPE_SERVICE { + return ServiceAccountPermissions(), nil + } + // are we reading a revision? if strings.Contains(n.ID, RevisionIDDelimiter) { // verify revision key format diff --git a/vendor/github.com/cs3org/reva/v2/pkg/utils/grpc.go b/vendor/github.com/cs3org/reva/v2/pkg/utils/grpc.go index 3945d32e8..90547443f 100644 --- a/vendor/github.com/cs3org/reva/v2/pkg/utils/grpc.go +++ b/vendor/github.com/cs3org/reva/v2/pkg/utils/grpc.go @@ -11,19 +11,8 @@ import ( "google.golang.org/grpc/metadata" ) -// Impersonate returns an authenticated reva context and the user it represents -func Impersonate(userID *user.UserId, gwc gateway.GatewayAPIClient, machineAuthAPIKey string) (context.Context, *user.User, error) { - usr, err := GetUser(userID, gwc, machineAuthAPIKey) - if err != nil { - return nil, nil, err - } - - ctx, err := ImpersonateUser(usr, gwc, machineAuthAPIKey) - return ctx, usr, err -} - // GetUser gets the specified user -func GetUser(userID *user.UserId, gwc gateway.GatewayAPIClient, machineAuthAPIKey string) (*user.User, error) { +func GetUser(userID *user.UserId, gwc gateway.GatewayAPIClient) (*user.User, error) { getUserResponse, err := gwc.GetUser(context.Background(), &user.GetUserRequest{UserId: userID}) if err != nil { return nil, err @@ -35,19 +24,19 @@ func GetUser(userID *user.UserId, gwc gateway.GatewayAPIClient, machineAuthAPIKe return getUserResponse.GetUser(), nil } -// ImpersonateUser impersonates the given user -func ImpersonateUser(usr *user.User, gwc gateway.GatewayAPIClient, machineAuthAPIKey string) (context.Context, error) { - ctx := revactx.ContextSetUser(context.Background(), usr) +// GetServiceUserContext returns an authenticated context of the given service user +func GetServiceUserContext(serviceUserID string, gwc gateway.GatewayAPIClient, serviceUserSecret string) (context.Context, error) { + ctx := context.Background() authRes, err := gwc.Authenticate(ctx, &gateway.AuthenticateRequest{ - Type: "machine", - ClientId: "userid:" + usr.GetId().GetOpaqueId(), - ClientSecret: machineAuthAPIKey, + Type: "serviceaccounts", + ClientId: serviceUserID, + ClientSecret: serviceUserSecret, }) if err != nil { return nil, err } if authRes.GetStatus().GetCode() != rpc.Code_CODE_OK { - return nil, fmt.Errorf("error impersonating user: %s", authRes.Status.Message) + return nil, fmt.Errorf("error authenticating service user: %s", authRes.Status.Message) } return metadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, authRes.Token), nil diff --git a/vendor/modules.txt b/vendor/modules.txt index 6d00fbc3b..d332df59f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -354,7 +354,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1 github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1 github.com/cs3org/go-cs3apis/cs3/tx/v1beta1 github.com/cs3org/go-cs3apis/cs3/types/v1beta1 -# github.com/cs3org/reva/v2 v2.16.1-0.20230828111521-594d4e103741 +# github.com/cs3org/reva/v2 v2.16.1-0.20230829124655-8ba013d7a129 ## explicit; go 1.20 github.com/cs3org/reva/v2/cmd/revad/internal/grace github.com/cs3org/reva/v2/cmd/revad/runtime @@ -473,6 +473,7 @@ github.com/cs3org/reva/v2/pkg/auth/manager/owncloudsql github.com/cs3org/reva/v2/pkg/auth/manager/owncloudsql/accounts github.com/cs3org/reva/v2/pkg/auth/manager/publicshares github.com/cs3org/reva/v2/pkg/auth/manager/registry +github.com/cs3org/reva/v2/pkg/auth/manager/serviceaccounts github.com/cs3org/reva/v2/pkg/auth/registry/loader github.com/cs3org/reva/v2/pkg/auth/registry/registry github.com/cs3org/reva/v2/pkg/auth/registry/static From 034e028f9db55c73df14aee35acf0198257115a0 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Thu, 31 Aug 2023 09:59:24 +0200 Subject: [PATCH 11/11] changelog Signed-off-by: jkoberg --- changelog/unreleased/bump-reva.md | 1 + changelog/unreleased/service-accounts.md | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 changelog/unreleased/service-accounts.md diff --git a/changelog/unreleased/bump-reva.md b/changelog/unreleased/bump-reva.md index 36011d7ff..6f39bacc3 100644 --- a/changelog/unreleased/bump-reva.md +++ b/changelog/unreleased/bump-reva.md @@ -3,3 +3,4 @@ Enhancement: Bump Reva bumps reva version https://github.com/owncloud/ocis/pull/7138 +https://github.com/owncloud/ocis/pull/6427 diff --git a/changelog/unreleased/service-accounts.md b/changelog/unreleased/service-accounts.md new file mode 100644 index 000000000..9042c9cf5 --- /dev/null +++ b/changelog/unreleased/service-accounts.md @@ -0,0 +1,5 @@ +Enhancement: Introduce service accounts + +Introduces service accounts to avoid impersonating users in async processes + +https://github.com/owncloud/ocis/pull/6427