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