use service accounts for notifications

Signed-off-by: jkoberg <jkoberg@owncloud.com>
This commit is contained in:
jkoberg
2023-08-16 11:04:56 +02:00
parent 0cd5ad6415
commit ab10e5e152
8 changed files with 99 additions and 59 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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