Merge pull request #6427 from kobergj/ServiceAccounts
[full-ci] Service Accounts
This commit is contained in:
@@ -61,6 +61,7 @@ config = {
|
||||
"services/auth-basic",
|
||||
"services/auth-bearer",
|
||||
"services/auth-machine",
|
||||
"services/auth-service",
|
||||
"services/eventhistory",
|
||||
"services/frontend",
|
||||
"services/gateway",
|
||||
|
||||
@@ -26,6 +26,7 @@ OCIS_MODULES = \
|
||||
services/auth-basic \
|
||||
services/auth-bearer \
|
||||
services/auth-machine \
|
||||
services/auth-service \
|
||||
services/eventhistory \
|
||||
services/frontend \
|
||||
services/gateway \
|
||||
|
||||
@@ -3,3 +3,4 @@ Enhancement: Bump Reva
|
||||
bumps reva version
|
||||
|
||||
https://github.com/owncloud/ocis/pull/7138
|
||||
https://github.com/owncloud/ocis/pull/6427
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
+53
-9
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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:
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}()
|
||||
}
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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."`
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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."`
|
||||
}
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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:"-"`
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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."`
|
||||
}
|
||||
|
||||
@@ -159,10 +159,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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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."`
|
||||
}
|
||||
|
||||
@@ -73,9 +73,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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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."`
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ func DefaultConfig() *config.Config {
|
||||
EnableTLS: false,
|
||||
},
|
||||
ContentExtractionSizeLimit: 20 * 1024 * 1024, // Limit content extraction to <20MB files by default
|
||||
MachineAuthAPIKey: "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,10 +90,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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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, ""
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:"-"`
|
||||
}
|
||||
|
||||
|
||||
@@ -64,8 +64,9 @@ func DefaultConfig() *config.Config {
|
||||
TTL: time.Minute * 10,
|
||||
},
|
||||
},
|
||||
BundlesPath: "",
|
||||
Bundles: nil,
|
||||
BundlesPath: "",
|
||||
Bundles: nil,
|
||||
ServiceAccountIDAdmin: "service-user-id",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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."`
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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."`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 |
|
||||
|
||||
+1
@@ -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
|
||||
)
|
||||
|
||||
+3
-1
@@ -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.
|
||||
|
||||
Generated
Vendored
+90
@@ -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")
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
|
||||
Generated
Vendored
+20
@@ -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
|
||||
|
||||
+8
-19
@@ -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
|
||||
|
||||
Vendored
+2
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user