diff --git a/.drone.star b/.drone.star index 0d14257a46..63fdd68523 100644 --- a/.drone.star +++ b/.drone.star @@ -61,6 +61,7 @@ config = { "services/auth-basic", "services/auth-bearer", "services/auth-machine", + "services/auth-service", "services/eventhistory", "services/frontend", "services/gateway", diff --git a/Makefile b/Makefile index 8e648ffc23..f45ce7f175 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,7 @@ OCIS_MODULES = \ services/auth-basic \ services/auth-bearer \ services/auth-machine \ + services/auth-service \ services/eventhistory \ services/frontend \ services/gateway \ diff --git a/ocis-pkg/config/config.go b/ocis-pkg/config/config.go index bf16fd6717..77fec4f6fa 100644 --- a/ocis-pkg/config/config.go +++ b/ocis-pkg/config/config.go @@ -9,6 +9,7 @@ import ( authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config" authbearer "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config" authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config" + authservice "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" eventhistory "github.com/owncloud/ocis/v2/services/eventhistory/pkg/config" frontend "github.com/owncloud/ocis/v2/services/frontend/pkg/config" gateway "github.com/owncloud/ocis/v2/services/gateway/pkg/config" @@ -83,6 +84,7 @@ type Config struct { AuthBasic *authbasic.Config `yaml:"auth_basic"` AuthBearer *authbearer.Config `yaml:"auth_bearer"` AuthMachine *authmachine.Config `yaml:"auth_machine"` + AuthService *authservice.Config `yaml:"auth_service"` EventHistory *eventhistory.Config `yaml:"eventhistory"` Frontend *frontend.Config `yaml:"frontend"` Gateway *gateway.Config `yaml:"gateway"` diff --git a/ocis-pkg/config/defaultconfig.go b/ocis-pkg/config/defaultconfig.go index bfa03aee63..8639821bc7 100644 --- a/ocis-pkg/config/defaultconfig.go +++ b/ocis-pkg/config/defaultconfig.go @@ -8,6 +8,7 @@ import ( authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config/defaults" authbearer "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config/defaults" authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config/defaults" + authservice "github.com/owncloud/ocis/v2/services/auth-service/pkg/config/defaults" eventhistory "github.com/owncloud/ocis/v2/services/eventhistory/pkg/config/defaults" frontend "github.com/owncloud/ocis/v2/services/frontend/pkg/config/defaults" gateway "github.com/owncloud/ocis/v2/services/gateway/pkg/config/defaults" @@ -55,6 +56,7 @@ func DefaultConfig() *Config { AuthBasic: authbasic.DefaultConfig(), AuthBearer: authbearer.DefaultConfig(), AuthMachine: authmachine.DefaultConfig(), + AuthService: authservice.DefaultConfig(), EventHistory: eventhistory.DefaultConfig(), Frontend: frontend.DefaultConfig(), Gateway: gateway.DefaultConfig(), diff --git a/ocis/pkg/command/services.go b/ocis/pkg/command/services.go index df143b623e..da634e2dfa 100644 --- a/ocis/pkg/command/services.go +++ b/ocis/pkg/command/services.go @@ -15,6 +15,7 @@ import ( authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/command" authbearer "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/command" authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/command" + authservice "github.com/owncloud/ocis/v2/services/auth-service/pkg/command" eventhistory "github.com/owncloud/ocis/v2/services/eventhistory/pkg/command" frontend "github.com/owncloud/ocis/v2/services/frontend/pkg/command" gateway "github.com/owncloud/ocis/v2/services/gateway/pkg/command" @@ -83,6 +84,11 @@ var svccmds = []register.Command{ cfg.AuthMachine.Commons = cfg.Commons }) }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.AuthService.Service.Name, authservice.GetCommands(cfg.AuthService), func(c *config.Config) { + cfg.AuthService.Commons = cfg.Commons + }) + }, func(cfg *config.Config) *cli.Command { return ServiceCommand(cfg, cfg.EventHistory.Service.Name, eventhistory.GetCommands(cfg.EventHistory), func(c *config.Config) { cfg.EventHistory.Commons = cfg.Commons diff --git a/ocis/pkg/runtime/service/service.go b/ocis/pkg/runtime/service/service.go index 027ef7e551..1f43db921e 100644 --- a/ocis/pkg/runtime/service/service.go +++ b/ocis/pkg/runtime/service/service.go @@ -26,6 +26,7 @@ import ( audit "github.com/owncloud/ocis/v2/services/audit/pkg/command" authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/command" authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/command" + authservice "github.com/owncloud/ocis/v2/services/auth-service/pkg/command" eventhistory "github.com/owncloud/ocis/v2/services/eventhistory/pkg/command" frontend "github.com/owncloud/ocis/v2/services/frontend/pkg/command" gateway "github.com/owncloud/ocis/v2/services/gateway/pkg/command" @@ -135,6 +136,11 @@ func NewService(options ...Option) (*Service, error) { cfg.AuthMachine.Commons = cfg.Commons return authmachine.Execute(cfg.AuthMachine) }) + reg(opts.Config.AuthService.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.AuthService.Context = ctx + cfg.AuthService.Commons = cfg.Commons + return authservice.Execute(cfg.AuthService) + }) reg(opts.Config.EventHistory.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.EventHistory.Context = ctx cfg.EventHistory.Commons = cfg.Commons diff --git a/services/auth-basic/README.md b/services/auth-basic/README.md index c1b1db30bc..c5afe10633 100644 --- a/services/auth-basic/README.md +++ b/services/auth-basic/README.md @@ -6,6 +6,14 @@ The `auth-basic` service is responsible for validating authentication of incomin To enable `auth-basic`, you first must set `PROXY_ENABLE_BASIC_AUTH` to `true`. +## The `auth` Service Family + +ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist: + - `auth-basic` handles basic authentication + - `auth-bearer` handles oidc authentication + - `auth-machine` handles interservice authentication when a user is impersonated + - `auth-service` handles interservice authentication when using service accounts + ## Auth Managers Since the `auth-basic` service does not do any validation itself, it needs to be configured with an authentication manager. One can use the `AUTH_BASIC_AUTH_MANAGER` environment variable to configure this. Currently only one auth manager is supported: `"ldap"` diff --git a/services/auth-bearer/README.md b/services/auth-bearer/README.md index 6ada9310e3..024f6caa13 100644 --- a/services/auth-bearer/README.md +++ b/services/auth-bearer/README.md @@ -2,7 +2,15 @@ The oCIS Auth Bearer service communicates with the configured OpenID Connect identity provider to authenticate requests. OpenID Connect is the default authentication mechanism for all clients: web, desktop and mobile. Basic auth is only used for testing and has to be explicity enabled. -## Built in OpenID Connect identity provider +## The `auth` Service Family + +ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist: + - `auth-basic` handles basic authentication + - `auth-bearer` handles oidc authentication + - `auth-machine` handles interservice authentication when a user is impersonated + - `auth-service` handles interservice authentication when using service accounts + +## Built in OpenID Connect Identity Provider A default oCIS deployment will start a [built in OpenID Connect identity provider](https://github.com/owncloud/ocis/tree/master/services/idp) but can be configured to use an external one as well. diff --git a/services/auth-machine/README.md b/services/auth-machine/README.md new file mode 100644 index 0000000000..b06664054d --- /dev/null +++ b/services/auth-machine/README.md @@ -0,0 +1,17 @@ +# Auth-Machine + +The oCIS Auth Machine is used for interservice communication when using user impersonation. + +ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist: + - `auth-basic` handles basic authentication + - `auth-bearer` handles oidc authentication + - `auth-machine` handles interservice authentication when a user is impersonated + - `auth-service` handles interservice authentication when using service accounts + +## User Impersonation + +When one ocis service is trying to talk to other ocis services, it needs to authenticate itself. To do so, it will impersonate a user using the `auth-machine` service. It will then act on behalf of this user. Any action will show up as action of this specific user, which gets visible when e.g. logged in the audit log. + +## Deprecation + +With the upcoming `auth-service` service, the `auth-machine` service will be used less frequently and is probably a candidate for deprecation. diff --git a/services/auth-service/Makefile b/services/auth-service/Makefile new file mode 100644 index 0000000000..bab3eec589 --- /dev/null +++ b/services/auth-service/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := auth-service + +include ../../.make/recursion.mk + +############ tooling ############ +ifneq (, $(shell command -v go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI +include ../../.bingo/Variables.mk +endif + +############ go tooling ############ +include ../../.make/go.mk + +############ release ############ +include ../../.make/release.mk + +############ docs generate ############ +include ../../.make/docs.mk + +.PHONY: docs-generate +docs-generate: config-docs-generate + +############ generate ############ +include ../../.make/generate.mk + +.PHONY: ci-go-generate +ci-go-generate: # CI runs ci-node-generate automatically before this target + +.PHONY: ci-node-generate +ci-node-generate: + +############ licenses ############ +.PHONY: ci-node-check-licenses +ci-node-check-licenses: + +.PHONY: ci-node-save-licenses +ci-node-save-licenses: diff --git a/services/auth-service/README.md b/services/auth-service/README.md new file mode 100644 index 0000000000..b34057d14c --- /dev/null +++ b/services/auth-service/README.md @@ -0,0 +1,19 @@ +# Auth-Service + +The ocis Auth Service is used to authenticate service accounts. Compared to normal accounts, service accounts are ocis internal only and not available as ordinary users like via LDAP. + +## The `auth` Service Family + +ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist: + - `auth-basic` handles basic authentication + - `auth-bearer` handles oidc authentication + - `auth-machine` handles interservice authentication when a user is impersonated + - `auth-service` handles interservice authentication when using service accounts + +## Service Accounts + +Service accounts are user accounts that are only used for inter service communication. The users have no personal space, do not show up in user lists and cannot login via the UI. Service accounts can be configured in the settings service. Only the `admin` service user is available for now. Additionally to the actions it can do via its role, all service users can stat all files on all spaces. + +## Configuring Service Accounts + +By using the envvars `OCIS_SERVICE_ACCOUNT_ID` and `OCIS_SERVICE_ACCOUNT_SECRET`, one can configure the ID and the secret of the service user. The secret can be rotated regulary to increase security. For activating a new secret, all services where the envvars are used need to be restarted. The secret is always and only stored in memory and never written into any persistant store. Though you can use any string for the service account, it is recommmended to use a UUIDv4 string. diff --git a/services/auth-service/cmd/auth-service/main.go b/services/auth-service/cmd/auth-service/main.go new file mode 100644 index 0000000000..bcc7a625ec --- /dev/null +++ b/services/auth-service/cmd/auth-service/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/auth-service/pkg/command" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/auth-service/pkg/command/health.go b/services/auth-service/pkg/command/health.go new file mode 100644 index 0000000000..7470b581db --- /dev/null +++ b/services/auth-service/pkg/command/health.go @@ -0,0 +1,54 @@ +package command + +import ( + "fmt" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/logging" + "github.com/urfave/cli/v2" +) + +// Health is the entrypoint for the health command. +func Health(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "health", + Usage: "check health status", + Category: "info", + Before: func(c *cli.Context) error { + return configlog.ReturnError(parser.ParseConfig(cfg)) + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + + resp, err := http.Get( + fmt.Sprintf( + "http://%s/healthz", + cfg.Debug.Addr, + ), + ) + + if err != nil { + logger.Fatal(). + Err(err). + Msg("Failed to request health check") + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + logger.Fatal(). + Int("code", resp.StatusCode). + Msg("Health seems to be in bad state") + } + + logger.Debug(). + Int("code", resp.StatusCode). + Msg("Health got a good state") + + return nil + }, + } +} diff --git a/services/auth-service/pkg/command/root.go b/services/auth-service/pkg/command/root.go new file mode 100644 index 0000000000..2fd57dd7ad --- /dev/null +++ b/services/auth-service/pkg/command/root.go @@ -0,0 +1,34 @@ +package command + +import ( + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/clihelper" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" + "github.com/urfave/cli/v2" +) + +// GetCommands provides all commands for this service +func GetCommands(cfg *config.Config) cli.Commands { + return []*cli.Command{ + // start this service + Server(cfg), + + // interaction with this service + + // infos about this service + Health(cfg), + Version(cfg), + } +} + +// Execute is the entry point for the ocis-auth-service command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "auth-service", + Usage: "Provide service authentication for oCIS", + Commands: GetCommands(cfg), + }) + + return app.Run(os.Args) +} diff --git a/services/auth-service/pkg/command/server.go b/services/auth-service/pkg/command/server.go new file mode 100644 index 0000000000..21bdeee3b4 --- /dev/null +++ b/services/auth-service/pkg/command/server.go @@ -0,0 +1,99 @@ +package command + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/cs3org/reva/v2/cmd/revad/runtime" + "github.com/gofrs/uuid" + "github.com/oklog/run" + "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/sync" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/logging" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/server/debug" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/tracing" + "github.com/urfave/cli/v2" +) + +// Server is the entry point for the server command. +func Server(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "server", + Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + Category: "server", + Before: func(c *cli.Context) error { + return configlog.ReturnFatal(parser.ParseConfig(cfg)) + }, + Action: func(c *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + err := tracing.Configure(cfg, logger) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + + rcfg := revaconfig.AuthMachineConfigFromStruct(cfg) + + gr.Add(func() error { + runtime.RunWithOptions(rcfg, pidFile, runtime.WithLogger(&logger.Logger)) + return nil + }, func(err error) { + logger.Error(). + Err(err). + Str("server", cfg.Service.Name). + Msg("Shutting down server") + + cancel() + }) + + debugServer, err := debug.Server( + debug.Logger(logger), + debug.Context(ctx), + debug.Config(cfg), + ) + + if err != nil { + logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server") + return err + } + + gr.Add(debugServer.ListenAndServe, func(_ error) { + cancel() + }) + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString()) + if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil { + logger.Fatal().Err(err).Msg("failed to register the grpc service") + } + + return gr.Run() + }, + } +} + +// defineContext sets the context for the service. If there is a context configured it will create a new child from it, +// if not, it will create a root context that can be cancelled. +func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) { + return func() (context.Context, context.CancelFunc) { + if cfg.Context == nil { + return context.WithCancel(context.Background()) + } + return context.WithCancel(cfg.Context) + }() +} diff --git a/services/auth-service/pkg/command/version.go b/services/auth-service/pkg/command/version.go new file mode 100644 index 0000000000..8bd660262d --- /dev/null +++ b/services/auth-service/pkg/command/version.go @@ -0,0 +1,50 @@ +package command + +import ( + "fmt" + "os" + + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + + tw "github.com/olekukonko/tablewriter" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" + "github.com/urfave/cli/v2" +) + +// Version prints the service versions of all running instances. +func Version(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "version", + Usage: "print the version of this binary and the running service instances", + Category: "info", + Action: func(c *cli.Context) error { + fmt.Println("Version: " + version.GetString()) + fmt.Printf("Compiled: %s\n", version.Compiled()) + fmt.Println("") + + reg := registry.GetRegistry() + services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name) + if err != nil { + fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err)) + return err + } + + if len(services) == 0 { + fmt.Println("No running " + cfg.Service.Name + " service found.") + return nil + } + + table := tw.NewWriter(os.Stdout) + table.SetHeader([]string{"Version", "Address", "Id"}) + table.SetAutoFormatHeaders(false) + for _, s := range services { + for _, n := range s.Nodes { + table.Append([]string{s.Version, n.Address, n.Id}) + } + } + table.Render() + return nil + }, + } +} diff --git a/services/auth-service/pkg/config/config.go b/services/auth-service/pkg/config/config.go new file mode 100644 index 0000000000..0314497006 --- /dev/null +++ b/services/auth-service/pkg/config/config.go @@ -0,0 +1,63 @@ +package config + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/shared" +) + +type Config struct { + Commons *shared.Commons `yaml:"-"` // don't use this directly as configuration for a service + Service Service `yaml:"-"` + Tracing *Tracing `yaml:"tracing"` + Log *Log `yaml:"log"` + Debug Debug `yaml:"debug"` + + GRPC GRPCConfig `yaml:"grpc"` + + TokenManager *TokenManager `yaml:"token_manager"` + Reva *shared.Reva `yaml:"reva"` + + // TODO: when using multiple service accounts we need to find a way to configure them + ServiceAccount ServiceAccount `yaml:"service_account"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` +} +type Tracing struct { + Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;AUTH_SERVICE_TRACING_ENABLED" desc:"Activates tracing."` + Type string `yaml:"type" env:"OCIS_TRACING_TYPE;AUTH_SERVICE_TRACING_TYPE" desc:"The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now."` + Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;AUTH_SERVICE_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent."` + Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;AUTH_SERVICE_TRACING_COLLECTOR" desc:"The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset."` +} + +type Log struct { + Level string `yaml:"level" env:"OCIS_LOG_LEVEL;AUTH_SERVICE_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'."` + Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;AUTH_SERVICE_LOG_PRETTY" desc:"Activates pretty log output."` + Color bool `yaml:"color" env:"OCIS_LOG_COLOR;AUTH_SERVICE_LOG_COLOR" desc:"Activates colorized log output."` + File string `yaml:"file" env:"OCIS_LOG_FILE;AUTH_SERVICE_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set."` +} + +type Service struct { + Name string `yaml:"-"` +} + +type Debug struct { + Addr string `yaml:"addr" env:"AUTH_SERVICE_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed."` + Token string `yaml:"token" env:"AUTH_SERVICE_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint."` + Pprof bool `yaml:"pprof" env:"AUTH_SERVICE_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling."` + Zpages bool `yaml:"zpages" env:"AUTH_SERVICE_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing in-memory traces."` +} + +type GRPCConfig struct { + Addr string `yaml:"addr" env:"AUTH_SERVICE_GRPC_ADDR" desc:"The bind address of the GRPC service."` + TLS *shared.GRPCServiceTLS `yaml:"tls"` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"AUTH_SERVICE_GRPC_PROTOCOL" desc:"The transport protocol of the GRPC service."` +} + +// ServiceAccount is the configuration for the used service account +type ServiceAccount struct { + ServiceAccountID string `yaml:"service_account_id" env:"OCIS_SERVICE_ACCOUNT_ID;AUTH_SERVICE_SERVICE_ACCOUNT_ID" desc:"The ID of the service account the service should use. See the 'auth-service' service description for more details."` + ServiceAccountSecret string `yaml:"service_account_secret" env:"OCIS_SERVICE_ACCOUNT_SECRET;AUTH_SERVICE_SERVICE_ACCOUNT_SECRET" desc:"The service account secret."` +} diff --git a/services/auth-service/pkg/config/defaults/defaultconfig.go b/services/auth-service/pkg/config/defaults/defaultconfig.go new file mode 100644 index 0000000000..923a886bd4 --- /dev/null +++ b/services/auth-service/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,87 @@ +package defaults + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/ocis-pkg/structs" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" +) + +// FullDefaultConfig returns a fully initialized default configuration +func FullDefaultConfig() *config.Config { + cfg := DefaultConfig() + EnsureDefaults(cfg) + Sanitize(cfg) + return cfg +} + +// DefaultConfig returns a basic default configuration +func DefaultConfig() *config.Config { + return &config.Config{ + Debug: config.Debug{ + Addr: "127.0.0.1:9169", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9199", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + Service: config.Service{ + Name: "auth-service", + }, + Reva: shared.DefaultRevaConfig(), + ServiceAccount: config.ServiceAccount{ + ServiceAccountID: "service-user-id", + ServiceAccountSecret: "secret-string", + }, + } +} + +// EnsureDefaults adds default values to the configuration if they are not set yet +func EnsureDefaults(cfg *config.Config) { + // provide with defaults for shared logging, since we need a valid destination address for "envdecode". + if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Log = &config.Log{ + Level: cfg.Commons.Log.Level, + Pretty: cfg.Commons.Log.Pretty, + Color: cfg.Commons.Log.Color, + File: cfg.Commons.Log.File, + } + } else if cfg.Log == nil { + cfg.Log = &config.Log{} + } + // provide with defaults for shared tracing, since we need a valid destination address for "envdecode". + if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil { + cfg.Tracing = &config.Tracing{ + Enabled: cfg.Commons.Tracing.Enabled, + Type: cfg.Commons.Tracing.Type, + Endpoint: cfg.Commons.Tracing.Endpoint, + Collector: cfg.Commons.Tracing.Collector, + } + } else if cfg.Tracing == nil { + cfg.Tracing = &config.Tracing{} + } + + if cfg.Reva == nil && cfg.Commons != nil { + cfg.Reva = structs.CopyOrZeroValue(cfg.Commons.Reva) + } + + if cfg.TokenManager == nil && cfg.Commons != nil && cfg.Commons.TokenManager != nil { + cfg.TokenManager = &config.TokenManager{ + JWTSecret: cfg.Commons.TokenManager.JWTSecret, + } + } else if cfg.TokenManager == nil { + cfg.TokenManager = &config.TokenManager{} + } + + if cfg.GRPC.TLS == nil && cfg.Commons != nil { + cfg.GRPC.TLS = structs.CopyOrZeroValue(cfg.Commons.GRPCServiceTLS) + } +} + +// Sanitize sanitized the configuration +func Sanitize(cfg *config.Config) { + // nothing to sanitize here atm +} diff --git a/services/auth-service/pkg/config/parser/parse.go b/services/auth-service/pkg/config/parser/parse.go new file mode 100644 index 0000000000..2bb6b66305 --- /dev/null +++ b/services/auth-service/pkg/config/parser/parse.go @@ -0,0 +1,42 @@ +package parser + +import ( + "errors" + + ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config/defaults" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode" +) + +// ParseConfig loads configuration from known paths. +func ParseConfig(cfg *config.Config) error { + _, err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg) + if err != nil { + return err + } + + defaults.EnsureDefaults(cfg) + + // load all env variables relevant to the config in the current context. + if err := envdecode.Decode(cfg); err != nil { + // no environment variable set for this config is an expected "error" + if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) { + return err + } + } + + defaults.Sanitize(cfg) + + return Validate(cfg) +} + +func Validate(cfg *config.Config) error { + if cfg.TokenManager.JWTSecret == "" { + return shared.MissingJWTTokenError(cfg.Service.Name) + } + + return nil +} diff --git a/services/auth-service/pkg/config/reva.go b/services/auth-service/pkg/config/reva.go new file mode 100644 index 0000000000..14cb00d089 --- /dev/null +++ b/services/auth-service/pkg/config/reva.go @@ -0,0 +1,6 @@ +package config + +// TokenManager is the config for using the reva token manager +type TokenManager struct { + JWTSecret string `yaml:"jwt_secret" env:"OCIS_JWT_SECRET;AUTH_MACHINE_JWT_SECRET" desc:"The secret to mint and validate jwt tokens."` +} diff --git a/services/auth-service/pkg/logging/logging.go b/services/auth-service/pkg/logging/logging.go new file mode 100644 index 0000000000..79b966fd22 --- /dev/null +++ b/services/auth-service/pkg/logging/logging.go @@ -0,0 +1,17 @@ +package logging + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" +) + +// LoggerFromConfig initializes a service-specific logger instance. +func Configure(name string, cfg *config.Log) log.Logger { + return log.NewLogger( + log.Name(name), + log.Level(cfg.Level), + log.Pretty(cfg.Pretty), + log.Color(cfg.Color), + log.File(cfg.File), + ) +} diff --git a/services/auth-service/pkg/revaconfig/config.go b/services/auth-service/pkg/revaconfig/config.go new file mode 100644 index 0000000000..4baaff7949 --- /dev/null +++ b/services/auth-service/pkg/revaconfig/config.go @@ -0,0 +1,53 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" +) + +// AuthMachineConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func AuthMachineConfigFromStruct(cfg *config.Config) map[string]interface{} { + return map[string]interface{}{ + "core": map[string]interface{}{ + "tracing_enabled": cfg.Tracing.Enabled, + "tracing_exporter": cfg.Tracing.Type, + "tracing_endpoint": cfg.Tracing.Endpoint, + "tracing_collector": cfg.Tracing.Collector, + "tracing_service_name": cfg.Service.Name, + }, + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "grpc_client_options": cfg.Reva.GetGRPCClientConfig(), + }, + "grpc": map[string]interface{}{ + "network": cfg.GRPC.Protocol, + "address": cfg.GRPC.Addr, + "tls_settings": map[string]interface{}{ + "enabled": cfg.GRPC.TLS.Enabled, + "certificate": cfg.GRPC.TLS.Cert, + "key": cfg.GRPC.TLS.Key, + }, + "services": map[string]interface{}{ + "authprovider": map[string]interface{}{ + "auth_manager": "serviceaccounts", + "auth_managers": map[string]interface{}{ + "serviceaccounts": map[string]interface{}{ + "service_accounts": []map[string]interface{}{ + { + "id": cfg.ServiceAccount.ServiceAccountID, + "secret": cfg.ServiceAccount.ServiceAccountSecret, + }, + }, + }, + }, + }, + }, + "interceptors": map[string]interface{}{ + "prometheus": map[string]interface{}{ + "namespace": "ocis", + "subsystem": "auth_service", + }, + }, + }, + } +} diff --git a/services/auth-service/pkg/server/debug/option.go b/services/auth-service/pkg/server/debug/option.go new file mode 100644 index 0000000000..b11a774e6d --- /dev/null +++ b/services/auth-service/pkg/server/debug/option.go @@ -0,0 +1,50 @@ +package debug + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Logger log.Logger + Context context.Context + Config *config.Config +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} diff --git a/services/auth-service/pkg/server/debug/server.go b/services/auth-service/pkg/server/debug/server.go new file mode 100644 index 0000000000..faedc717bb --- /dev/null +++ b/services/auth-service/pkg/server/debug/server.go @@ -0,0 +1,63 @@ +package debug + +import ( + "io" + "net/http" + + "github.com/owncloud/ocis/v2/ocis-pkg/service/debug" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" +) + +// Server initializes the debug service and server. +func Server(opts ...Option) (*http.Server, error) { + options := newOptions(opts...) + + return debug.NewService( + debug.Logger(options.Logger), + debug.Name(options.Config.Service.Name), + debug.Version(version.GetString()), + debug.Address(options.Config.Debug.Addr), + debug.Token(options.Config.Debug.Token), + debug.Pprof(options.Config.Debug.Pprof), + debug.Zpages(options.Config.Debug.Zpages), + debug.Health(health(options.Config)), + debug.Ready(ready(options.Config)), + //debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins), + //debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods), + //debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders), + //debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials), + ), nil +} + +// health implements the health check. +func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} + +// ready implements the ready check. +func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + + // TODO: check if services are up and running + + _, err := io.WriteString(w, http.StatusText(http.StatusOK)) + // io.WriteString should not fail but if it does we want to know. + if err != nil { + panic(err) + } + } +} diff --git a/services/auth-service/pkg/tracing/tracing.go b/services/auth-service/pkg/tracing/tracing.go new file mode 100644 index 0000000000..357ca4ebb6 --- /dev/null +++ b/services/auth-service/pkg/tracing/tracing.go @@ -0,0 +1,25 @@ +package tracing + +import ( + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/auth-service/pkg/config" + "go.opentelemetry.io/otel/trace" + + pkgtrace "github.com/owncloud/ocis/v2/ocis-pkg/tracing" +) + +var ( + // TraceProvider is the global trace provider for the service. + TraceProvider = trace.NewNoopTracerProvider() +) + +func Configure(cfg *config.Config, logger log.Logger) error { + var err error + if cfg.Tracing.Enabled { + if TraceProvider, err = pkgtrace.GetTraceProvider(cfg.Tracing.Endpoint, cfg.Tracing.Collector, cfg.Service.Name, cfg.Tracing.Type); err != nil { + return err + } + } + + return nil +} diff --git a/services/gateway/pkg/config/config.go b/services/gateway/pkg/config/config.go index 791064a67e..c19f88785d 100644 --- a/services/gateway/pkg/config/config.go +++ b/services/gateway/pkg/config/config.go @@ -38,6 +38,7 @@ type Config struct { AuthBasicEndpoint string `yaml:"-"` AuthBearerEndpoint string `yaml:"-"` AuthMachineEndpoint string `yaml:"-"` + AuthServiceEndpoint string `yaml:"-"` StoragePublicLinkEndpoint string `yaml:"-"` StorageUsersEndpoint string `yaml:"-"` StorageSharesEndpoint string `yaml:"-"` diff --git a/services/gateway/pkg/config/defaults/defaultconfig.go b/services/gateway/pkg/config/defaults/defaultconfig.go index 3e0fd026e5..f66332a679 100644 --- a/services/gateway/pkg/config/defaults/defaultconfig.go +++ b/services/gateway/pkg/config/defaults/defaultconfig.go @@ -55,6 +55,7 @@ func DefaultConfig() *config.Config { AppRegistryEndpoint: "com.owncloud.api.app-registry", AuthBasicEndpoint: "com.owncloud.api.auth-basic", AuthMachineEndpoint: "com.owncloud.api.auth-machine", + AuthServiceEndpoint: "com.owncloud.api.auth-service", GroupsEndpoint: "com.owncloud.api.groups", PermissionsEndpoint: "com.owncloud.api.settings", SharingEndpoint: "com.owncloud.api.sharing", diff --git a/services/gateway/pkg/revaconfig/config.go b/services/gateway/pkg/revaconfig/config.go index d9c5df0d9e..97ef2b44df 100644 --- a/services/gateway/pkg/revaconfig/config.go +++ b/services/gateway/pkg/revaconfig/config.go @@ -80,9 +80,10 @@ func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]i "drivers": map[string]interface{}{ "static": map[string]interface{}{ "rules": map[string]interface{}{ - "basic": cfg.AuthBasicEndpoint, - "machine": cfg.AuthMachineEndpoint, - "publicshares": cfg.StoragePublicLinkEndpoint, + "basic": cfg.AuthBasicEndpoint, + "machine": cfg.AuthMachineEndpoint, + "publicshares": cfg.StoragePublicLinkEndpoint, + "serviceaccounts": cfg.AuthServiceEndpoint, }, }, },