From 43403edfb8a6e30b94b439142ac7f48de9945792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Mon, 6 May 2024 12:48:49 +0200 Subject: [PATCH 01/12] feat: reva app auth --- services/proxy/pkg/command/server.go | 16 ++++++++ services/proxy/pkg/middleware/app_auth.go | 45 +++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 services/proxy/pkg/middleware/app_auth.go diff --git a/services/proxy/pkg/command/server.go b/services/proxy/pkg/command/server.go index dd26ce29ec..8793651038 100644 --- a/services/proxy/pkg/command/server.go +++ b/services/proxy/pkg/command/server.go @@ -194,6 +194,7 @@ func Server(cfg *config.Config) *cli.Command { { middlewares := loadMiddlewares(logger, cfg, userInfoCache, signingKeyStore, traceProvider, *m, userProvider, gatewaySelector) + server, err := proxyHTTP.Server( proxyHTTP.Handler(lh.Handler()), proxyHTTP.Logger(logger), @@ -246,10 +247,14 @@ func Server(cfg *config.Config) *cli.Command { } } +<<<<<<< HEAD func loadMiddlewares(logger log.Logger, cfg *config.Config, userInfoCache, signingKeyStore microstore.Store, traceProvider trace.TracerProvider, metrics metrics.Metrics, userProvider backend.UserBackend, gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) alice.Chain { +======= +func loadMiddlewares(logger log.Logger, cfg *config.Config, userInfoCache, signingKeyStore microstore.Store, traceProvider trace.TracerProvider, metrics metrics.Metrics) alice.Chain { +>>>>>>> a9df2a66b1 (feat: reva app auth) rolesClient := settingssvc.NewRoleService("com.owncloud.api.settings", cfg.GrpcClient) policiesProviderClient := policiessvc.NewPoliciesProviderService("com.owncloud.api.policies", cfg.GrpcClient) @@ -293,6 +298,17 @@ func loadMiddlewares(logger log.Logger, cfg *config.Config, }) } +<<<<<<< HEAD +======= + authenticators = append(authenticators, middleware.AppAuthAuthenticator{ + Logger: logger, + RevaGatewaySelector: gatewaySelector, + }) + authenticators = append(authenticators, middleware.PublicShareAuthenticator{ + Logger: logger, + RevaGatewaySelector: gatewaySelector, + }) +>>>>>>> a9df2a66b1 (feat: reva app auth) authenticators = append(authenticators, middleware.NewOIDCAuthenticator( middleware.Logger(logger), middleware.UserInfoCache(userInfoCache), diff --git a/services/proxy/pkg/middleware/app_auth.go b/services/proxy/pkg/middleware/app_auth.go new file mode 100644 index 0000000000..f03791b299 --- /dev/null +++ b/services/proxy/pkg/middleware/app_auth.go @@ -0,0 +1,45 @@ +package middleware + +import ( + "net/http" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/owncloud/ocis/v2/ocis-pkg/log" +) + +// AppAuthAuthenticator defines the app auth authenticator +type AppAuthAuthenticator struct { + Logger log.Logger + RevaGatewaySelector *pool.Selector[gateway.GatewayAPIClient] +} + +// Authenticate implements the authenticator interface to authenticate requests via app auth. +func (m AppAuthAuthenticator) Authenticate(r *http.Request) (*http.Request, bool) { + if isPublicPath(r.URL.Path) { + // The authentication of public path requests is handled by another authenticator. + // Since we can't guarantee the order of execution of the authenticators, we better + // implement an early return here for paths we can't authenticate in this authenticator. + return nil, false + } + + username, password, ok := r.BasicAuth() + if !ok { + return nil, false + } + next, err := m.RevaGatewaySelector.Next() + if err != nil { + return nil, false + } + authenticateResponse, err := next.Authenticate(r.Context(), &gateway.AuthenticateRequest{ + Type: "appauth", + ClientId: username, + ClientSecret: password, + }) + if err != nil { + return nil, false + } + r.Header.Add(_headerRevaAccessToken, authenticateResponse.GetToken()) + + return r, true +} From 1eb66eb18c057b451f03b0c47d77e5e2505e671a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Mon, 6 May 2024 16:04:27 +0200 Subject: [PATCH 02/12] feat: add REVA appauth as oCIS service --- docs/services/general-info/port-ranges.md | 2 +- ocis-pkg/config/config.go | 2 + ocis-pkg/config/defaultconfig.go | 2 + ocis/pkg/command/services.go | 12 +- ocis/pkg/runtime/service/service.go | 6 + services/auth-app/Makefile | 37 +++++++ services/auth-app/README.md | 3 + services/auth-app/cmd/auth-app/main.go | 14 +++ services/auth-app/pkg/command/health.go | 54 +++++++++ services/auth-app/pkg/command/root.go | 34 ++++++ services/auth-app/pkg/command/server.go | 104 ++++++++++++++++++ services/auth-app/pkg/command/version.go | 50 +++++++++ services/auth-app/pkg/config/config.go | 55 +++++++++ .../pkg/config/defaults/defaultconfig.go | 83 ++++++++++++++ services/auth-app/pkg/config/parser/parse.go | 42 +++++++ services/auth-app/pkg/config/reva.go | 6 + services/auth-app/pkg/config/tracing.go | 21 ++++ services/auth-app/pkg/logging/logging.go | 17 +++ services/auth-app/pkg/revaconfig/config.go | 43 ++++++++ services/auth-app/pkg/server/debug/option.go | 50 +++++++++ services/auth-app/pkg/server/debug/server.go | 63 +++++++++++ 21 files changed, 696 insertions(+), 4 deletions(-) create mode 100644 services/auth-app/Makefile create mode 100644 services/auth-app/README.md create mode 100644 services/auth-app/cmd/auth-app/main.go create mode 100644 services/auth-app/pkg/command/health.go create mode 100644 services/auth-app/pkg/command/root.go create mode 100644 services/auth-app/pkg/command/server.go create mode 100644 services/auth-app/pkg/command/version.go create mode 100644 services/auth-app/pkg/config/config.go create mode 100644 services/auth-app/pkg/config/defaults/defaultconfig.go create mode 100644 services/auth-app/pkg/config/parser/parse.go create mode 100644 services/auth-app/pkg/config/reva.go create mode 100644 services/auth-app/pkg/config/tracing.go create mode 100644 services/auth-app/pkg/logging/logging.go create mode 100644 services/auth-app/pkg/revaconfig/config.go create mode 100644 services/auth-app/pkg/server/debug/option.go create mode 100644 services/auth-app/pkg/server/debug/server.go diff --git a/docs/services/general-info/port-ranges.md b/docs/services/general-info/port-ranges.md index d36dd9a626..ae027bbee8 100644 --- a/docs/services/general-info/port-ranges.md +++ b/docs/services/general-info/port-ranges.md @@ -60,7 +60,7 @@ We also suggest using the last port in your extensions' range as a debug/metrics | 9230-9234 | [nats]({{< ref "../nats/_index.md" >}}) | | 9235-9239 | [idm]({{< ref "../idm/_index.md" >}}) | | 9240-9244 | [app-registry]({{< ref "../app-registry/_index.md" >}}) | -| 9245-9249 | FREE | +| 9245-9249 | [auth-app]({{< ref "../auth-app/_index.md" >}}) | | 9250-9254 | [ocis server (runtime)](https://github.com/owncloud/ocis/tree/master/ocis/pkg/runtime) | | 9255-9259 | [postprocessing]({{< ref "../postprocessing/_index.md" >}}) | | 9260-9264 | [clientlog]({{< ref "../clientlog/_index.md" >}}) | diff --git a/ocis-pkg/config/config.go b/ocis-pkg/config/config.go index 245e8ceb01..79926807fb 100644 --- a/ocis-pkg/config/config.go +++ b/ocis-pkg/config/config.go @@ -7,6 +7,7 @@ import ( appProvider "github.com/owncloud/ocis/v2/services/app-provider/pkg/config" appRegistry "github.com/owncloud/ocis/v2/services/app-registry/pkg/config" audit "github.com/owncloud/ocis/v2/services/audit/pkg/config" + authapp "github.com/owncloud/ocis/v2/services/auth-app/pkg/config" 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" @@ -86,6 +87,7 @@ type Config struct { AppProvider *appProvider.Config `yaml:"app_provider"` AppRegistry *appRegistry.Config `yaml:"app_registry"` Audit *audit.Config `yaml:"audit"` + AuthApp *authapp.Config `yaml:"auth_app"` AuthBasic *authbasic.Config `yaml:"auth_basic"` AuthBearer *authbearer.Config `yaml:"auth_bearer"` AuthMachine *authmachine.Config `yaml:"auth_machine"` diff --git a/ocis-pkg/config/defaultconfig.go b/ocis-pkg/config/defaultconfig.go index 7e284f006f..bd15668a76 100644 --- a/ocis-pkg/config/defaultconfig.go +++ b/ocis-pkg/config/defaultconfig.go @@ -6,6 +6,7 @@ import ( appProvider "github.com/owncloud/ocis/v2/services/app-provider/pkg/config/defaults" appRegistry "github.com/owncloud/ocis/v2/services/app-registry/pkg/config/defaults" audit "github.com/owncloud/ocis/v2/services/audit/pkg/config/defaults" + authapp "github.com/owncloud/ocis/v2/services/auth-app/pkg/config/defaults" 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" @@ -58,6 +59,7 @@ func DefaultConfig() *Config { AppProvider: appProvider.DefaultConfig(), AppRegistry: appRegistry.DefaultConfig(), Audit: audit.DefaultConfig(), + AuthApp: authapp.DefaultConfig(), AuthBasic: authbasic.DefaultConfig(), AuthBearer: authbearer.DefaultConfig(), AuthMachine: authmachine.DefaultConfig(), diff --git a/ocis/pkg/command/services.go b/ocis/pkg/command/services.go index a247e3728a..0920dd4e3b 100644 --- a/ocis/pkg/command/services.go +++ b/ocis/pkg/command/services.go @@ -13,6 +13,7 @@ import ( appprovider "github.com/owncloud/ocis/v2/services/app-provider/pkg/command" appregistry "github.com/owncloud/ocis/v2/services/app-registry/pkg/command" audit "github.com/owncloud/ocis/v2/services/audit/pkg/command" + authapp "github.com/owncloud/ocis/v2/services/auth-app/pkg/command" 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" @@ -78,6 +79,11 @@ var svccmds = []register.Command{ cfg.Audit.Commons = cfg.Commons }) }, + func(cfg *config.Config) *cli.Command { + return ServiceCommand(cfg, cfg.AuthApp.Service.Name, authapp.GetCommands(cfg.AuthApp), func(_ *config.Config) { + cfg.AuthApp.Commons = cfg.Commons + }) + }, func(cfg *config.Config) *cli.Command { return ServiceCommand(cfg, cfg.AuthBasic.Service.Name, authbasic.GetCommands(cfg.AuthBasic), func(c *config.Config) { cfg.AuthBasic.Commons = cfg.Commons @@ -266,10 +272,10 @@ var svccmds = []register.Command{ } // ServiceCommand is the entry point for the all service commands. -func ServiceCommand(cfg *config.Config, servicename string, subcommands []*cli.Command, f func(*config.Config)) *cli.Command { +func ServiceCommand(cfg *config.Config, serviceName string, subcommands []*cli.Command, f func(*config.Config)) *cli.Command { return &cli.Command{ - Name: servicename, - Usage: helper.SubcommandDescription(servicename), + Name: serviceName, + Usage: helper.SubcommandDescription(serviceName), Category: "services", Before: func(c *cli.Context) error { configlog.Error(parser.ParseConfig(cfg, true)) diff --git a/ocis/pkg/runtime/service/service.go b/ocis/pkg/runtime/service/service.go index 7c8ec280f5..f41e2099e7 100644 --- a/ocis/pkg/runtime/service/service.go +++ b/ocis/pkg/runtime/service/service.go @@ -3,6 +3,7 @@ package service import ( "context" "fmt" + authapp "github.com/owncloud/ocis/v2/services/auth-app/pkg/command" "net" "net/http" "net/rpc" @@ -160,6 +161,11 @@ func NewService(options ...Option) (*Service, error) { cfg.AppRegistry.Commons = cfg.Commons return appRegistry.Execute(cfg.AppRegistry) }) + reg(3, opts.Config.AuthApp.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.AuthApp.Context = ctx + cfg.AuthApp.Commons = cfg.Commons + return authapp.Execute(cfg.AuthApp) + }) reg(3, opts.Config.AuthBasic.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.AuthBasic.Context = ctx cfg.AuthBasic.Commons = cfg.Commons diff --git a/services/auth-app/Makefile b/services/auth-app/Makefile new file mode 100644 index 0000000000..3898eb98c8 --- /dev/null +++ b/services/auth-app/Makefile @@ -0,0 +1,37 @@ +SHELL := bash +NAME := auth-app + +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-app/README.md b/services/auth-app/README.md new file mode 100644 index 0000000000..139ee92254 --- /dev/null +++ b/services/auth-app/README.md @@ -0,0 +1,3 @@ +# Auth-App + +TBD diff --git a/services/auth-app/cmd/auth-app/main.go b/services/auth-app/cmd/auth-app/main.go new file mode 100644 index 0000000000..185d6eb9fd --- /dev/null +++ b/services/auth-app/cmd/auth-app/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "os" + + "github.com/owncloud/ocis/v2/services/auth-app/pkg/command" + "github.com/owncloud/ocis/v2/services/auth-app/pkg/config/defaults" +) + +func main() { + if err := command.Execute(defaults.DefaultConfig()); err != nil { + os.Exit(1) + } +} diff --git a/services/auth-app/pkg/command/health.go b/services/auth-app/pkg/command/health.go new file mode 100644 index 0000000000..fad375e207 --- /dev/null +++ b/services/auth-app/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-app/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-app/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/auth-app/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(_ *cli.Context) error { + return configlog.ReturnError(parser.ParseConfig(cfg)) + }, + Action: func(_ *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-app/pkg/command/root.go b/services/auth-app/pkg/command/root.go new file mode 100644 index 0000000000..9563118240 --- /dev/null +++ b/services/auth-app/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-app/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-app command. +func Execute(cfg *config.Config) error { + app := clihelper.DefaultApp(&cli.App{ + Name: "auth-app", + Usage: "Provide app authentication for oCIS", + Commands: GetCommands(cfg), + }) + + return app.Run(os.Args) +} diff --git a/services/auth-app/pkg/command/server.go b/services/auth-app/pkg/command/server.go new file mode 100644 index 0000000000..afb3b54dc0 --- /dev/null +++ b/services/auth-app/pkg/command/server.go @@ -0,0 +1,104 @@ +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/tracing" + "github.com/owncloud/ocis/v2/ocis-pkg/version" + "github.com/owncloud/ocis/v2/services/auth-app/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-app/pkg/config/parser" + "github.com/owncloud/ocis/v2/services/auth-app/pkg/logging" + "github.com/owncloud/ocis/v2/services/auth-app/pkg/revaconfig" + "github.com/owncloud/ocis/v2/services/auth-app/pkg/server/debug" + "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(_ *cli.Context) error { + return configlog.ReturnFatal(parser.ParseConfig(cfg)) + }, + Action: func(_ *cli.Context) error { + logger := logging.Configure(cfg.Service.Name, cfg.Log) + traceProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name) + if err != nil { + return err + } + gr := run.Group{} + ctx, cancel := defineContext(cfg) + + defer cancel() + + gr.Add(func() error { + pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid") + rCfg := revaconfig.AuthAppConfigFromStruct(cfg) + reg := registry.GetRegistry() + + runtime.RunWithOptions(rCfg, pidFile, + runtime.WithLogger(&logger.Logger), + runtime.WithRegistry(reg), + runtime.WithTraceProvider(traceProvider), + ) + + return nil + }, func(err error) { + logger.Error(). + Str("server", cfg.Service.Name). + Err(err). + Msg("Shutting down server") + + cancel() + os.Exit(1) + }) + + 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-app/pkg/command/version.go b/services/auth-app/pkg/command/version.go new file mode 100644 index 0000000000..b7c644c552 --- /dev/null +++ b/services/auth-app/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-app/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-app/pkg/config/config.go b/services/auth-app/pkg/config/config.go new file mode 100644 index 0000000000..d3feceb464 --- /dev/null +++ b/services/auth-app/pkg/config/config.go @@ -0,0 +1,55 @@ +package config + +import ( + "context" + + "github.com/owncloud/ocis/v2/ocis-pkg/shared" +) + +// Config defines the root config structure +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"` + + SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"AUTH_APP_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the encoding of the user's group memberships in the reva access token. This reduces the token size, especially when users are members of a large number of groups." introductionVersion:"pre5.0"` + + Supervised bool `yaml:"-"` + Context context.Context `yaml:"-"` +} + +// Log defines the loging configuration +type Log struct { + Level string `yaml:"level" env:"OCIS_LOG_LEVEL;AUTH_APP_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"pre5.0"` + Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;AUTH_APP_LOG_PRETTY" desc:"Activates pretty log output." introductionVersion:"pre5.0"` + Color bool `yaml:"color" env:"OCIS_LOG_COLOR;AUTH_APP_LOG_COLOR" desc:"Activates colorized log output." introductionVersion:"pre5.0"` + File string `yaml:"file" env:"OCIS_LOG_FILE;AUTH_APP_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set." introductionVersion:"pre5.0"` +} + +// Service defines the service configuration +type Service struct { + Name string `yaml:"-"` +} + +// Debug defines the debug configuration +type Debug struct { + Addr string `yaml:"addr" env:"AUTH_APP_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed." introductionVersion:"pre5.0"` + Token string `yaml:"token" env:"AUTH_APP_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint." introductionVersion:"pre5.0"` + Pprof bool `yaml:"pprof" env:"AUTH_APP_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling." introductionVersion:"pre5.0"` + Zpages bool `yaml:"zpages" env:"AUTH_APP_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing traces in-memory." introductionVersion:"pre5.0"` +} + +// GRPCConfig defines the GRPC configuration +type GRPCConfig struct { + Addr string `yaml:"addr" env:"AUTH_APP_GRPC_ADDR" desc:"The bind address of the GRPC service." introductionVersion:"pre5.0"` + TLS *shared.GRPCServiceTLS `yaml:"tls"` + Namespace string `yaml:"-"` + Protocol string `yaml:"protocol" env:"AUTH_APP_GRPC_PROTOCOL" desc:"The transport protocol of the GRPC service." introductionVersion:"pre5.0"` +} diff --git a/services/auth-app/pkg/config/defaults/defaultconfig.go b/services/auth-app/pkg/config/defaults/defaultconfig.go new file mode 100644 index 0000000000..efc4028f3b --- /dev/null +++ b/services/auth-app/pkg/config/defaults/defaultconfig.go @@ -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-app/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:9197", + Token: "", + Pprof: false, + Zpages: false, + }, + GRPC: config.GRPCConfig{ + Addr: "127.0.0.1:9195", + Namespace: "com.owncloud.api", + Protocol: "tcp", + }, + Service: config.Service{ + Name: "auth-app", + }, + 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(_ *config.Config) { + // nothing to sanitize here atm +} diff --git a/services/auth-app/pkg/config/parser/parse.go b/services/auth-app/pkg/config/parser/parse.go new file mode 100644 index 0000000000..d9c71d9275 --- /dev/null +++ b/services/auth-app/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-app/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-app/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-app/pkg/config/reva.go b/services/auth-app/pkg/config/reva.go new file mode 100644 index 0000000000..92aa140854 --- /dev/null +++ b/services/auth-app/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_APP_JWT_SECRET" desc:"The secret to mint and validate jwt tokens." introductionVersion:"pre5.0"` +} diff --git a/services/auth-app/pkg/config/tracing.go b/services/auth-app/pkg/config/tracing.go new file mode 100644 index 0000000000..b33f263419 --- /dev/null +++ b/services/auth-app/pkg/config/tracing.go @@ -0,0 +1,21 @@ +package config + +import "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + +// Tracing defines the tracing configuration. +type Tracing struct { + Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;AUTH_APP_TRACING_ENABLED" desc:"Activates tracing." introductionVersion:"pre5.0"` + Type string `yaml:"type" env:"OCIS_TRACING_TYPE;AUTH_APP_TRACING_TYPE" desc:"The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now." introductionVersion:"pre5.0"` + Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;AUTH_APP_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent." introductionVersion:"pre5.0"` + Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;AUTH_APP_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." introductionVersion:"pre5.0"` +} + +// Convert Tracing to the tracing package's Config struct. +func (t Tracing) Convert() tracing.Config { + return tracing.Config{ + Enabled: t.Enabled, + Type: t.Type, + Endpoint: t.Endpoint, + Collector: t.Collector, + } +} diff --git a/services/auth-app/pkg/logging/logging.go b/services/auth-app/pkg/logging/logging.go new file mode 100644 index 0000000000..b01a179327 --- /dev/null +++ b/services/auth-app/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-app/pkg/config" +) + +// Configure 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-app/pkg/revaconfig/config.go b/services/auth-app/pkg/revaconfig/config.go new file mode 100644 index 0000000000..1a015fcd16 --- /dev/null +++ b/services/auth-app/pkg/revaconfig/config.go @@ -0,0 +1,43 @@ +package revaconfig + +import ( + "github.com/owncloud/ocis/v2/services/auth-app/pkg/config" +) + +// AuthAppConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. +func AuthAppConfigFromStruct(cfg *config.Config) map[string]interface{} { + rcfg := map[string]interface{}{ + "shared": map[string]interface{}{ + "jwt_secret": cfg.TokenManager.JWTSecret, + "gatewaysvc": cfg.Reva.Address, + "skip_user_groups_in_token": cfg.SkipUserGroupsInToken, + "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": "appauth", + "auth_managers": map[string]interface{}{ + "appauth": map[string]interface{}{ + "gateway_addr": cfg.Reva.Address, + }, + }, + }, + }, + "interceptors": map[string]interface{}{ + "prometheus": map[string]interface{}{ + "namespace": "ocis", + "subsystem": "auth_app", + }, + }, + }, + } + return rcfg +} diff --git a/services/auth-app/pkg/server/debug/option.go b/services/auth-app/pkg/server/debug/option.go new file mode 100644 index 0000000000..81f5152678 --- /dev/null +++ b/services/auth-app/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-app/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-app/pkg/server/debug/server.go b/services/auth-app/pkg/server/debug/server.go new file mode 100644 index 0000000000..b87b7e52f1 --- /dev/null +++ b/services/auth-app/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-app/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(_ *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, _ *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(_ *config.Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, _ *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) + } + } +} From 4fa7ea0b203743b93c87372385301f8594f87766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 7 May 2024 08:27:26 +0200 Subject: [PATCH 03/12] feat: add cli command to generate app token for user --- services/auth-app/pkg/command/create.go | 109 ++++++++++++++++++ services/auth-app/pkg/command/root.go | 1 + services/auth-app/pkg/revaconfig/config.go | 13 +++ services/gateway/pkg/config/config.go | 1 + .../pkg/config/defaults/defaultconfig.go | 1 + services/gateway/pkg/revaconfig/config.go | 2 + services/proxy/pkg/command/server.go | 2 +- services/proxy/pkg/middleware/app_auth.go | 10 +- 8 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 services/auth-app/pkg/command/create.go diff --git a/services/auth-app/pkg/command/create.go b/services/auth-app/pkg/command/create.go new file mode 100644 index 0000000000..04d892d6c5 --- /dev/null +++ b/services/auth-app/pkg/command/create.go @@ -0,0 +1,109 @@ +package command + +import ( + "context" + "fmt" + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + "github.com/cs3org/reva/v2/pkg/auth/scope" + + applicationsv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" + gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "github.com/owncloud/ocis/v2/ocis-pkg/config/configlog" + "github.com/owncloud/ocis/v2/ocis-pkg/registry" + "github.com/owncloud/ocis/v2/ocis-pkg/tracing" + "github.com/owncloud/ocis/v2/services/auth-app/pkg/config" + "github.com/owncloud/ocis/v2/services/auth-app/pkg/config/parser" + "github.com/urfave/cli/v2" + "google.golang.org/grpc/metadata" + "time" +) + +// Create is the entrypoint for the app auth create command +func Create(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "create", + Usage: "create an app auth token for a user", + Category: "maintenance", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "user-name", + Value: "", + Usage: "the user name", + Required: true, + }, + &cli.IntFlag{ + Name: "expiration", + Value: 72, + Usage: "expiration of the app password in hours", + Required: false, + }, + }, + Before: func(_ *cli.Context) error { + return configlog.ReturnError(parser.ParseConfig(cfg)) + }, + Action: func(c *cli.Context) error { + traceProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name) + if err != nil { + return err + } + + gatewaySelector, err := pool.GatewaySelector( + cfg.Reva.Address, + append( + cfg.Reva.GetRevaOptions(), + pool.WithRegistry(registry.GetRegistry()), + pool.WithTracerProvider(traceProvider), + )...) + if err != nil { + return err + } + + next, err := gatewaySelector.Next() + if err != nil { + return err + } + + userID := c.String("user-name") + ctx := context.Background() + authRes, err := next.Authenticate(ctx, &gatewayv1beta1.AuthenticateRequest{ + Type: "machine", + ClientId: "username:" + userID, + ClientSecret: cfg.Commons.MachineAuthAPIKey, + }) + if err != nil { + return err + } + granteeCtx := ctxpkg.ContextSetUser(context.Background(), &userpb.User{Id: authRes.GetUser().GetId()}) + granteeCtx = metadata.AppendToOutgoingContext(granteeCtx, ctxpkg.TokenHeader, authRes.GetToken()) + + exp := c.Int("expiration") + expiryDuration := time.Duration(exp) * time.Hour + scopes, err := scope.AddOwnerScope(map[string]*authpb.Scope{}) + if err != nil { + return err + } + + appPassword, err := next.GenerateAppPassword(granteeCtx, &applicationsv1beta1.GenerateAppPasswordRequest{ + TokenScope: scopes, + Label: "Generated via CLI", + Expiration: &typesv1beta1.Timestamp{ + Seconds: uint64(time.Now().Add(expiryDuration).Unix()), + }, + }) + if err != nil { + return err + } + + fmt.Printf("App password created for %s", authRes.GetUser().GetUsername()) + fmt.Println() + fmt.Printf(" password: %s", appPassword.GetAppPassword().GetPassword()) + fmt.Println() + + return nil + }, + } +} diff --git a/services/auth-app/pkg/command/root.go b/services/auth-app/pkg/command/root.go index 9563118240..f1ba83b36e 100644 --- a/services/auth-app/pkg/command/root.go +++ b/services/auth-app/pkg/command/root.go @@ -15,6 +15,7 @@ func GetCommands(cfg *config.Config) cli.Commands { Server(cfg), // interaction with this service + Create(cfg), // infos about this service Health(cfg), diff --git a/services/auth-app/pkg/revaconfig/config.go b/services/auth-app/pkg/revaconfig/config.go index 1a015fcd16..da75275314 100644 --- a/services/auth-app/pkg/revaconfig/config.go +++ b/services/auth-app/pkg/revaconfig/config.go @@ -1,11 +1,16 @@ package revaconfig import ( + "path/filepath" + + "github.com/owncloud/ocis/v2/ocis-pkg/config/defaults" "github.com/owncloud/ocis/v2/services/auth-app/pkg/config" ) // AuthAppConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service. func AuthAppConfigFromStruct(cfg *config.Config) map[string]interface{} { + appAuthJSON := filepath.Join(defaults.BaseDataPath(), "appauth.json") + rcfg := map[string]interface{}{ "shared": map[string]interface{}{ "jwt_secret": cfg.TokenManager.JWTSecret, @@ -30,6 +35,14 @@ func AuthAppConfigFromStruct(cfg *config.Config) map[string]interface{} { }, }, }, + "applicationauth": map[string]interface{}{ + "driver": "json", + "drivers": map[string]interface{}{ + "json": map[string]interface{}{ + "file": appAuthJSON, + }, + }, + }, }, "interceptors": map[string]interface{}{ "prometheus": map[string]interface{}{ diff --git a/services/gateway/pkg/config/config.go b/services/gateway/pkg/config/config.go index 73b932de74..1d683de4b1 100644 --- a/services/gateway/pkg/config/config.go +++ b/services/gateway/pkg/config/config.go @@ -35,6 +35,7 @@ type Config struct { GroupsEndpoint string `yaml:"-"` PermissionsEndpoint string `yaml:"-"` SharingEndpoint string `yaml:"-"` + AuthAppEndpoint string `yaml:"-"` AuthBasicEndpoint string `yaml:"-"` AuthBearerEndpoint string `yaml:"-"` AuthMachineEndpoint string `yaml:"-"` diff --git a/services/gateway/pkg/config/defaults/defaultconfig.go b/services/gateway/pkg/config/defaults/defaultconfig.go index b8bea1a4bc..5d003bb379 100644 --- a/services/gateway/pkg/config/defaults/defaultconfig.go +++ b/services/gateway/pkg/config/defaults/defaultconfig.go @@ -52,6 +52,7 @@ func DefaultConfig() *config.Config { FrontendPublicURL: "https://localhost:9200", AppRegistryEndpoint: "com.owncloud.api.app-registry", + AuthAppEndpoint: "com.owncloud.api.auth-app", AuthBasicEndpoint: "com.owncloud.api.auth-basic", AuthMachineEndpoint: "com.owncloud.api.auth-machine", AuthServiceEndpoint: "com.owncloud.api.auth-service", diff --git a/services/gateway/pkg/revaconfig/config.go b/services/gateway/pkg/revaconfig/config.go index f4b5e841ef..035ae05bf6 100644 --- a/services/gateway/pkg/revaconfig/config.go +++ b/services/gateway/pkg/revaconfig/config.go @@ -37,6 +37,7 @@ func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]i // TODO build services dynamically "services": map[string]interface{}{ "gateway": map[string]interface{}{ + "applicationauthsvc": cfg.AuthAppEndpoint, // registries are located on the gateway "authregistrysvc": cfg.Reva.Address, "storageregistrysvc": cfg.Reva.Address, @@ -89,6 +90,7 @@ func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]i "drivers": map[string]interface{}{ "static": map[string]interface{}{ "rules": map[string]interface{}{ + "appauth": cfg.AuthAppEndpoint, "basic": cfg.AuthBasicEndpoint, "machine": cfg.AuthMachineEndpoint, "publicshares": cfg.StoragePublicLinkEndpoint, diff --git a/services/proxy/pkg/command/server.go b/services/proxy/pkg/command/server.go index 8793651038..9bf9245acd 100644 --- a/services/proxy/pkg/command/server.go +++ b/services/proxy/pkg/command/server.go @@ -364,7 +364,7 @@ func loadMiddlewares(logger log.Logger, cfg *config.Config, userInfoCache, signi middleware.CredentialsByUserAgent(cfg.AuthMiddleware.CredentialsByUserAgent), middleware.Logger(logger), middleware.OIDCIss(cfg.OIDC.Issuer), - middleware.EnableBasicAuth(cfg.EnableBasicAuth), + middleware.EnableBasicAuth(true), middleware.TraceProvider(traceProvider), ), middleware.AccountResolver( diff --git a/services/proxy/pkg/middleware/app_auth.go b/services/proxy/pkg/middleware/app_auth.go index f03791b299..815e9a4575 100644 --- a/services/proxy/pkg/middleware/app_auth.go +++ b/services/proxy/pkg/middleware/app_auth.go @@ -4,6 +4,8 @@ import ( "net/http" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + revactx "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/owncloud/ocis/v2/ocis-pkg/log" ) @@ -31,6 +33,7 @@ func (m AppAuthAuthenticator) Authenticate(r *http.Request) (*http.Request, bool if err != nil { return nil, false } + authenticateResponse, err := next.Authenticate(r.Context(), &gateway.AuthenticateRequest{ Type: "appauth", ClientId: username, @@ -39,7 +42,12 @@ func (m AppAuthAuthenticator) Authenticate(r *http.Request) (*http.Request, bool if err != nil { return nil, false } - r.Header.Add(_headerRevaAccessToken, authenticateResponse.GetToken()) + if authenticateResponse.GetStatus().GetCode() != cs3rpc.Code_CODE_OK { + // TODO: log??? + return nil, false + } + + r.Header.Set(revactx.TokenHeader, authenticateResponse.GetToken()) return r, true } From 7005cbc0fcb99a84cf22ca90768bd3cb43cd5628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 7 May 2024 17:01:50 +0200 Subject: [PATCH 04/12] feat: add unit test for app auth middleware --- .../proxy/pkg/middleware/app_auth_test.go | 68 +++++++++++++++++++ .../pkg/middleware/public_share_auth_test.go | 1 + 2 files changed, 69 insertions(+) create mode 100644 services/proxy/pkg/middleware/app_auth_test.go diff --git a/services/proxy/pkg/middleware/app_auth_test.go b/services/proxy/pkg/middleware/app_auth_test.go new file mode 100644 index 0000000000..f2e4d8d6aa --- /dev/null +++ b/services/proxy/pkg/middleware/app_auth_test.go @@ -0,0 +1,68 @@ +package middleware + +import ( + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" + "google.golang.org/grpc" + "net/http" + "net/http/httptest" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/owncloud/ocis/v2/ocis-pkg/log" +) + +var _ = Describe("Authenticating requests", Label("AppAuthAuthenticator"), func() { + var authenticator Authenticator + BeforeEach(func() { + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") + authenticator = AppAuthAuthenticator{ + Logger: log.NewLogger(), + RevaGatewaySelector: pool.GetSelector[gateway.GatewayAPIClient]( + "GatewaySelector", + "com.owncloud.api.gateway", + func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + return mockGatewayClient{ + AuthenticateFunc: func(authType, clientID, clientSecret string) (string, rpcv1beta1.Code) { + if authType != "appauth" { + return "", rpcv1beta1.Code_CODE_NOT_FOUND + } + + if clientID == "test-user" && clientSecret == "AppPassword" { + return "reva-token", rpcv1beta1.Code_CODE_OK + } + + return "", rpcv1beta1.Code_CODE_NOT_FOUND + }, + } + }, + ), + } + }) + + When("the request contains correct data", func() { + It("should successfully authenticate", func() { + req := httptest.NewRequest(http.MethodGet, "http://example.com/example/path", http.NoBody) + req.SetBasicAuth("test-user", "AppPassword") + + req2, valid := authenticator.Authenticate(req) + + Expect(valid).To(Equal(true)) + Expect(req2).ToNot(BeNil()) + Expect(req2.Header.Get("x-access-token")).To(Equal("reva-token")) + }) + }) + + When("the request contains incorrect data", func() { + It("should not successfully authenticate", func() { + req := httptest.NewRequest(http.MethodGet, "http://example.com/example/path", http.NoBody) + req.SetBasicAuth("test-user", "WrongAppPassword") + + req2, valid := authenticator.Authenticate(req) + + Expect(valid).To(Equal(false)) + Expect(req2).To(BeNil()) + }) + }) +}) diff --git a/services/proxy/pkg/middleware/public_share_auth_test.go b/services/proxy/pkg/middleware/public_share_auth_test.go index 4c07ffe061..9f748c4ed1 100644 --- a/services/proxy/pkg/middleware/public_share_auth_test.go +++ b/services/proxy/pkg/middleware/public_share_auth_test.go @@ -18,6 +18,7 @@ import ( var _ = Describe("Authenticating requests", Label("PublicShareAuthenticator"), func() { var authenticator Authenticator BeforeEach(func() { + pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway") authenticator = PublicShareAuthenticator{ Logger: log.NewLogger(), RevaGatewaySelector: pool.GetSelector[gateway.GatewayAPIClient]( From 3b3d30159d8b3649b0c9b3937e5a097bf1663144 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Fri, 12 Jul 2024 09:12:58 +0200 Subject: [PATCH 05/12] fix(proxy): fix build after rebase Signed-off-by: jkoberg --- services/auth-app/pkg/config/defaults/defaultconfig.go | 4 ++-- services/proxy/pkg/command/server.go | 7 ------- services/proxy/pkg/middleware/app_auth.go | 2 +- services/proxy/pkg/middleware/app_auth_test.go | 7 ++++--- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/services/auth-app/pkg/config/defaults/defaultconfig.go b/services/auth-app/pkg/config/defaults/defaultconfig.go index efc4028f3b..636ab93804 100644 --- a/services/auth-app/pkg/config/defaults/defaultconfig.go +++ b/services/auth-app/pkg/config/defaults/defaultconfig.go @@ -18,13 +18,13 @@ func FullDefaultConfig() *config.Config { func DefaultConfig() *config.Config { return &config.Config{ Debug: config.Debug{ - Addr: "127.0.0.1:9197", + Addr: "127.0.0.1:9245", Token: "", Pprof: false, Zpages: false, }, GRPC: config.GRPCConfig{ - Addr: "127.0.0.1:9195", + Addr: "127.0.0.1:9246", Namespace: "com.owncloud.api", Protocol: "tcp", }, diff --git a/services/proxy/pkg/command/server.go b/services/proxy/pkg/command/server.go index 9bf9245acd..ebc3881049 100644 --- a/services/proxy/pkg/command/server.go +++ b/services/proxy/pkg/command/server.go @@ -247,14 +247,10 @@ func Server(cfg *config.Config) *cli.Command { } } -<<<<<<< HEAD func loadMiddlewares(logger log.Logger, cfg *config.Config, userInfoCache, signingKeyStore microstore.Store, traceProvider trace.TracerProvider, metrics metrics.Metrics, userProvider backend.UserBackend, gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) alice.Chain { -======= -func loadMiddlewares(logger log.Logger, cfg *config.Config, userInfoCache, signingKeyStore microstore.Store, traceProvider trace.TracerProvider, metrics metrics.Metrics) alice.Chain { ->>>>>>> a9df2a66b1 (feat: reva app auth) rolesClient := settingssvc.NewRoleService("com.owncloud.api.settings", cfg.GrpcClient) policiesProviderClient := policiessvc.NewPoliciesProviderService("com.owncloud.api.policies", cfg.GrpcClient) @@ -298,8 +294,6 @@ func loadMiddlewares(logger log.Logger, cfg *config.Config, userInfoCache, signi }) } -<<<<<<< HEAD -======= authenticators = append(authenticators, middleware.AppAuthAuthenticator{ Logger: logger, RevaGatewaySelector: gatewaySelector, @@ -308,7 +302,6 @@ func loadMiddlewares(logger log.Logger, cfg *config.Config, userInfoCache, signi Logger: logger, RevaGatewaySelector: gatewaySelector, }) ->>>>>>> a9df2a66b1 (feat: reva app auth) authenticators = append(authenticators, middleware.NewOIDCAuthenticator( middleware.Logger(logger), middleware.UserInfoCache(userInfoCache), diff --git a/services/proxy/pkg/middleware/app_auth.go b/services/proxy/pkg/middleware/app_auth.go index 815e9a4575..b828af4b48 100644 --- a/services/proxy/pkg/middleware/app_auth.go +++ b/services/proxy/pkg/middleware/app_auth.go @@ -13,7 +13,7 @@ import ( // AppAuthAuthenticator defines the app auth authenticator type AppAuthAuthenticator struct { Logger log.Logger - RevaGatewaySelector *pool.Selector[gateway.GatewayAPIClient] + RevaGatewaySelector pool.Selectable[gateway.GatewayAPIClient] } // Authenticate implements the authenticator interface to authenticate requests via app auth. diff --git a/services/proxy/pkg/middleware/app_auth_test.go b/services/proxy/pkg/middleware/app_auth_test.go index f2e4d8d6aa..79d857019f 100644 --- a/services/proxy/pkg/middleware/app_auth_test.go +++ b/services/proxy/pkg/middleware/app_auth_test.go @@ -1,12 +1,13 @@ package middleware import ( + "net/http" + "net/http/httptest" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "google.golang.org/grpc" - "net/http" - "net/http/httptest" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -22,7 +23,7 @@ var _ = Describe("Authenticating requests", Label("AppAuthAuthenticator"), func( RevaGatewaySelector: pool.GetSelector[gateway.GatewayAPIClient]( "GatewaySelector", "com.owncloud.api.gateway", - func(cc *grpc.ClientConn) gateway.GatewayAPIClient { + func(cc grpc.ClientConnInterface) gateway.GatewayAPIClient { return mockGatewayClient{ AuthenticateFunc: func(authType, clientID, clientSecret string) (string, rpcv1beta1.Code) { if authType != "appauth" { From 11103a422075e4bfdad1bb3f63d46d1a5fb002e4 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Mon, 15 Jul 2024 10:51:04 +0200 Subject: [PATCH 06/12] feat(auth-app): make service optional plus docu Signed-off-by: jkoberg --- ocis/pkg/runtime/service/service.go | 13 +++++++------ services/auth-app/README.md | 29 +++++++++++++++++++++++++++- services/auth-basic/README.md | 1 + services/auth-bearer/README.md | 1 + services/auth-machine/README.md | 1 + services/auth-service/README.md | 1 + services/proxy/pkg/command/server.go | 10 ++++++---- services/proxy/pkg/config/config.go | 1 + 8 files changed, 46 insertions(+), 11 deletions(-) diff --git a/ocis/pkg/runtime/service/service.go b/ocis/pkg/runtime/service/service.go index f41e2099e7..d42604b7e6 100644 --- a/ocis/pkg/runtime/service/service.go +++ b/ocis/pkg/runtime/service/service.go @@ -3,7 +3,6 @@ package service import ( "context" "fmt" - authapp "github.com/owncloud/ocis/v2/services/auth-app/pkg/command" "net" "net/http" "net/rpc" @@ -14,6 +13,8 @@ import ( "syscall" "time" + authapp "github.com/owncloud/ocis/v2/services/auth-app/pkg/command" + "github.com/cenkalti/backoff" "github.com/cs3org/reva/v2/pkg/events/stream" "github.com/cs3org/reva/v2/pkg/logger" @@ -161,11 +162,6 @@ func NewService(options ...Option) (*Service, error) { cfg.AppRegistry.Commons = cfg.Commons return appRegistry.Execute(cfg.AppRegistry) }) - reg(3, opts.Config.AuthApp.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { - cfg.AuthApp.Context = ctx - cfg.AuthApp.Commons = cfg.Commons - return authapp.Execute(cfg.AuthApp) - }) reg(3, opts.Config.AuthBasic.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.AuthBasic.Context = ctx cfg.AuthBasic.Commons = cfg.Commons @@ -330,6 +326,11 @@ func NewService(options ...Option) (*Service, error) { cfg.Audit.Commons = cfg.Commons return audit.Execute(cfg.Audit) }) + areg(opts.Config.AuthApp.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { + cfg.AuthApp.Context = ctx + cfg.AuthApp.Commons = cfg.Commons + return authapp.Execute(cfg.AuthApp) + }) areg(opts.Config.Policies.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error { cfg.Policies.Context = ctx cfg.Policies.Commons = cfg.Commons diff --git a/services/auth-app/README.md b/services/auth-app/README.md index 139ee92254..58ac12f179 100644 --- a/services/auth-app/README.md +++ b/services/auth-app/README.md @@ -1,3 +1,30 @@ # Auth-App -TBD +The auth-app service provides authentication for 3rd party apps. + +## 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-app` handles authentication of external 3rd party apps + +## Optional Service + +This service is an optional service that will not run with default settings. To start use it, two envvars need to be set: +```bash +OCIS_ADD_RUN_SERVICES=auth-app # to start the service. Alternatively you can start the service explicitly via the command line. +PROXY_ENABLE_APP_AUTH=true # to allow app authentication. This envvar goes to the proxy service in case of a distributed environment. +``` + +## App Tokens + +App Tokens are used to authenticate 3rd party apps. To be able to use an app token, one must first create a token via cli. + +```bash +ocis auth-app create --user-name={user-name} --expiration={token-expiration} +``` + +Once generated, these tokens can be used to authenticate requests to the oCIS services. They can be passed in any request as `Basic Auth` header. diff --git a/services/auth-basic/README.md b/services/auth-basic/README.md index c5afe10633..b293d338aa 100644 --- a/services/auth-basic/README.md +++ b/services/auth-basic/README.md @@ -13,6 +13,7 @@ ocis uses serveral authentication services for different use cases. All services - `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-app` handles authentication of external 3rd party apps ## Auth Managers diff --git a/services/auth-bearer/README.md b/services/auth-bearer/README.md index 024f6caa13..63eef5fa03 100644 --- a/services/auth-bearer/README.md +++ b/services/auth-bearer/README.md @@ -9,6 +9,7 @@ ocis uses serveral authentication services for different use cases. All services - `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-app` handles authentication of external 3rd party apps ## Built in OpenID Connect Identity Provider diff --git a/services/auth-machine/README.md b/services/auth-machine/README.md index b06664054d..bafdfcff56 100644 --- a/services/auth-machine/README.md +++ b/services/auth-machine/README.md @@ -7,6 +7,7 @@ ocis uses serveral authentication services for different use cases. All services - `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-app` handles authentication of external 3rd party apps ## User Impersonation diff --git a/services/auth-service/README.md b/services/auth-service/README.md index b34057d14c..e2338ab6ed 100644 --- a/services/auth-service/README.md +++ b/services/auth-service/README.md @@ -9,6 +9,7 @@ ocis uses serveral authentication services for different use cases. All services - `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-app` handles authentication of external 3rd party apps ## Service Accounts diff --git a/services/proxy/pkg/command/server.go b/services/proxy/pkg/command/server.go index ebc3881049..d83b613727 100644 --- a/services/proxy/pkg/command/server.go +++ b/services/proxy/pkg/command/server.go @@ -294,10 +294,12 @@ func loadMiddlewares(logger log.Logger, cfg *config.Config, }) } - authenticators = append(authenticators, middleware.AppAuthAuthenticator{ - Logger: logger, - RevaGatewaySelector: gatewaySelector, - }) + if cfg.AuthMiddleware.AllowAppAuth { + authenticators = append(authenticators, middleware.AppAuthAuthenticator{ + Logger: logger, + RevaGatewaySelector: gatewaySelector, + }) + } authenticators = append(authenticators, middleware.PublicShareAuthenticator{ Logger: logger, RevaGatewaySelector: gatewaySelector, diff --git a/services/proxy/pkg/config/config.go b/services/proxy/pkg/config/config.go index 9c067c45d7..611cd4ac6a 100644 --- a/services/proxy/pkg/config/config.go +++ b/services/proxy/pkg/config/config.go @@ -91,6 +91,7 @@ var ( // AuthMiddleware configures the proxy http auth middleware. type AuthMiddleware struct { CredentialsByUserAgent map[string]string `yaml:"credentials_by_user_agent"` + AllowAppAuth bool `yaml:"allow_app_auth" env:"PROXY_ENABLE_APP_AUTH" desc:"Allow app authentication. This can be used to authenticate 3rd party applications. Note that auth-app service must be running for this feature to work." introductionVersion:"%NEXT%"` } // PoliciesMiddleware configures the proxy's policies middleware. From a58a0f83f6c33adb265211f9641080cae4e34f07 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Mon, 15 Jul 2024 11:59:35 +0200 Subject: [PATCH 07/12] feat(auth-app): polish create cli Signed-off-by: jkoberg --- Makefile | 1 + services/auth-app/pkg/command/create.go | 51 ++++++++++++------- services/auth-app/pkg/config/config.go | 2 + .../pkg/config/defaults/defaultconfig.go | 4 ++ services/proxy/pkg/config/config.go | 2 +- 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index 156cb56a13..23467a7444 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ OCIS_MODULES = \ services/app-provider \ services/app-registry \ services/audit \ + services/auth-app \ services/auth-basic \ services/auth-bearer \ services/auth-machine \ diff --git a/services/auth-app/pkg/command/create.go b/services/auth-app/pkg/command/create.go index 04d892d6c5..5e2b897f9e 100644 --- a/services/auth-app/pkg/command/create.go +++ b/services/auth-app/pkg/command/create.go @@ -3,12 +3,16 @@ package command import ( "context" "fmt" + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" "github.com/cs3org/reva/v2/pkg/auth/scope" + "time" + applicationsv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" @@ -19,7 +23,6 @@ import ( "github.com/owncloud/ocis/v2/services/auth-app/pkg/config/parser" "github.com/urfave/cli/v2" "google.golang.org/grpc/metadata" - "time" ) // Create is the entrypoint for the app auth create command @@ -30,16 +33,14 @@ func Create(cfg *config.Config) *cli.Command { Category: "maintenance", Flags: []cli.Flag{ &cli.StringFlag{ - Name: "user-name", - Value: "", - Usage: "the user name", - Required: true, + Name: "user-name", + Value: "", + Usage: "user to create the app-token for", }, - &cli.IntFlag{ - Name: "expiration", - Value: 72, - Usage: "expiration of the app password in hours", - Required: false, + &cli.StringFlag{ + Name: "expiration", + Value: "72h", + Usage: "expiration of the app password, e.g. 72h, 1h, 1m, 1s. Default is 72h.", }, }, Before: func(_ *cli.Context) error { @@ -67,40 +68,54 @@ func Create(cfg *config.Config) *cli.Command { return err } - userID := c.String("user-name") + userName := c.String("user-name") + if userName == "" { + fmt.Printf("Username to create app token for: ") + if _, err := fmt.Scanln(&userName); err != nil { + return err + } + } + ctx := context.Background() authRes, err := next.Authenticate(ctx, &gatewayv1beta1.AuthenticateRequest{ Type: "machine", - ClientId: "username:" + userID, - ClientSecret: cfg.Commons.MachineAuthAPIKey, + ClientId: "username:" + userName, + ClientSecret: cfg.MachineAuthAPIKey, }) if err != nil { return err } + if authRes.GetStatus().GetCode() != rpc.Code_CODE_OK { + return fmt.Errorf("error authenticating user: %s", authRes.GetStatus().GetMessage()) + } + granteeCtx := ctxpkg.ContextSetUser(context.Background(), &userpb.User{Id: authRes.GetUser().GetId()}) granteeCtx = metadata.AppendToOutgoingContext(granteeCtx, ctxpkg.TokenHeader, authRes.GetToken()) - exp := c.Int("expiration") - expiryDuration := time.Duration(exp) * time.Hour scopes, err := scope.AddOwnerScope(map[string]*authpb.Scope{}) if err != nil { return err } + expiry, err := time.ParseDuration(c.String("expiration")) + if err != nil { + return err + } + appPassword, err := next.GenerateAppPassword(granteeCtx, &applicationsv1beta1.GenerateAppPasswordRequest{ TokenScope: scopes, Label: "Generated via CLI", Expiration: &typesv1beta1.Timestamp{ - Seconds: uint64(time.Now().Add(expiryDuration).Unix()), + Seconds: uint64(time.Now().Add(expiry).Unix()), }, }) if err != nil { return err } - fmt.Printf("App password created for %s", authRes.GetUser().GetUsername()) + fmt.Printf("App token created for %s", authRes.GetUser().GetUsername()) fmt.Println() - fmt.Printf(" password: %s", appPassword.GetAppPassword().GetPassword()) + fmt.Printf(" token: %s", appPassword.GetAppPassword().GetPassword()) fmt.Println() return nil diff --git a/services/auth-app/pkg/config/config.go b/services/auth-app/pkg/config/config.go index d3feceb464..4c094edc97 100644 --- a/services/auth-app/pkg/config/config.go +++ b/services/auth-app/pkg/config/config.go @@ -21,6 +21,8 @@ type Config struct { SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"AUTH_APP_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the encoding of the user's group memberships in the reva access token. This reduces the token size, especially when users are members of a large number of groups." introductionVersion:"pre5.0"` + MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;AUTH_APP_MACHINE_AUTH_API_KEY" desc:"The machine auth API key used to validate internal requests necessary to access resources from other services." introductionVersion:"%%NEXT%%"` + Supervised bool `yaml:"-"` Context context.Context `yaml:"-"` } diff --git a/services/auth-app/pkg/config/defaults/defaultconfig.go b/services/auth-app/pkg/config/defaults/defaultconfig.go index 636ab93804..19953bcef2 100644 --- a/services/auth-app/pkg/config/defaults/defaultconfig.go +++ b/services/auth-app/pkg/config/defaults/defaultconfig.go @@ -64,6 +64,10 @@ func EnsureDefaults(cfg *config.Config) { cfg.Reva = structs.CopyOrZeroValue(cfg.Commons.Reva) } + if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" { + cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey + } + if cfg.TokenManager == nil && cfg.Commons != nil && cfg.Commons.TokenManager != nil { cfg.TokenManager = &config.TokenManager{ JWTSecret: cfg.Commons.TokenManager.JWTSecret, diff --git a/services/proxy/pkg/config/config.go b/services/proxy/pkg/config/config.go index 611cd4ac6a..4f9c67a6e7 100644 --- a/services/proxy/pkg/config/config.go +++ b/services/proxy/pkg/config/config.go @@ -91,7 +91,7 @@ var ( // AuthMiddleware configures the proxy http auth middleware. type AuthMiddleware struct { CredentialsByUserAgent map[string]string `yaml:"credentials_by_user_agent"` - AllowAppAuth bool `yaml:"allow_app_auth" env:"PROXY_ENABLE_APP_AUTH" desc:"Allow app authentication. This can be used to authenticate 3rd party applications. Note that auth-app service must be running for this feature to work." introductionVersion:"%NEXT%"` + AllowAppAuth bool `yaml:"allow_app_auth" env:"PROXY_ENABLE_APP_AUTH" desc:"Allow app authentication. This can be used to authenticate 3rd party applications. Note that auth-app service must be running for this feature to work." introductionVersion:"%%NEXT%%"` } // PoliciesMiddleware configures the proxy's policies middleware. From eeb4749985a2727d36f390c866ff51a9b0bfd664 Mon Sep 17 00:00:00 2001 From: kobergj Date: Tue, 16 Jul 2024 16:31:44 +0200 Subject: [PATCH 08/12] feat(auth-app): improve documentation Co-authored-by: Martin --- services/auth-app/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/auth-app/README.md b/services/auth-app/README.md index 58ac12f179..2c71f28dd3 100644 --- a/services/auth-app/README.md +++ b/services/auth-app/README.md @@ -11,20 +11,20 @@ ocis uses serveral authentication services for different use cases. All services - `auth-service` handles interservice authentication when using service accounts - `auth-app` handles authentication of external 3rd party apps -## Optional Service +## Service Startup -This service is an optional service that will not run with default settings. To start use it, two envvars need to be set: +Because this service is not started automatically, a manual start needs to be initiated which can be done in several ways. To configure the service usage, an environment variable for the proxy service needs to be set to allow app authentication. ```bash -OCIS_ADD_RUN_SERVICES=auth-app # to start the service. Alternatively you can start the service explicitly via the command line. -PROXY_ENABLE_APP_AUTH=true # to allow app authentication. This envvar goes to the proxy service in case of a distributed environment. +OCIS_ADD_RUN_SERVICES=auth-app # deployment specific. Add the service to the manual startup list, use with binary deployments. Alternatively you can start the service explicitly via the command line. +PROXY_ENABLE_APP_AUTH=true # mandatory, allow app authentication. In case of a distributed environment, this envvar needs to be set in the the proxy service. ``` ## App Tokens -App Tokens are used to authenticate 3rd party apps. To be able to use an app token, one must first create a token via cli. +App Tokens are used to authenticate 3rd party access via https like when using curl (apps) to access an API endpoint. These apps need to authenticate themselve as no logged in user authenticates the request. To be able to use an app token, one must first create a token via the cli. Replace the `user-name` with an existing user. For the `token-expiration`, you can use any time abbreviation from the following list: `h, m, s`. Examples: `72h` or `1h` or `1m` or `1s.` Default is `72h`. ```bash ocis auth-app create --user-name={user-name} --expiration={token-expiration} ``` -Once generated, these tokens can be used to authenticate requests to the oCIS services. They can be passed in any request as `Basic Auth` header. +Once generated, these tokens can be used to authenticate requests to ocis. They are passed as part of the request as `Basic Auth` header. From 63c62d423c84d1fd7b8c8b267bd0e6d965a2407a Mon Sep 17 00:00:00 2001 From: jkoberg Date: Tue, 16 Jul 2024 16:34:37 +0200 Subject: [PATCH 09/12] fix(auth-app): use correct env introduction version Signed-off-by: jkoberg --- services/auth-app/pkg/config/config.go | 22 +++++++++++----------- services/auth-app/pkg/config/reva.go | 2 +- services/auth-app/pkg/config/tracing.go | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/services/auth-app/pkg/config/config.go b/services/auth-app/pkg/config/config.go index 4c094edc97..01d58b81eb 100644 --- a/services/auth-app/pkg/config/config.go +++ b/services/auth-app/pkg/config/config.go @@ -19,7 +19,7 @@ type Config struct { TokenManager *TokenManager `yaml:"token_manager"` Reva *shared.Reva `yaml:"reva"` - SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"AUTH_APP_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the encoding of the user's group memberships in the reva access token. This reduces the token size, especially when users are members of a large number of groups." introductionVersion:"pre5.0"` + SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"AUTH_APP_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the encoding of the user's group memberships in the access token. This reduces the token size, especially when users are members of a large number of groups." introductionVersion:"%%NEXT%%"` MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;AUTH_APP_MACHINE_AUTH_API_KEY" desc:"The machine auth API key used to validate internal requests necessary to access resources from other services." introductionVersion:"%%NEXT%%"` @@ -29,10 +29,10 @@ type Config struct { // Log defines the loging configuration type Log struct { - Level string `yaml:"level" env:"OCIS_LOG_LEVEL;AUTH_APP_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"pre5.0"` - Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;AUTH_APP_LOG_PRETTY" desc:"Activates pretty log output." introductionVersion:"pre5.0"` - Color bool `yaml:"color" env:"OCIS_LOG_COLOR;AUTH_APP_LOG_COLOR" desc:"Activates colorized log output." introductionVersion:"pre5.0"` - File string `yaml:"file" env:"OCIS_LOG_FILE;AUTH_APP_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set." introductionVersion:"pre5.0"` + Level string `yaml:"level" env:"OCIS_LOG_LEVEL;AUTH_APP_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"%%NEXT%%"` + Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;AUTH_APP_LOG_PRETTY" desc:"Activates pretty log output." introductionVersion:"%%NEXT%%"` + Color bool `yaml:"color" env:"OCIS_LOG_COLOR;AUTH_APP_LOG_COLOR" desc:"Activates colorized log output." introductionVersion:"%%NEXT%%"` + File string `yaml:"file" env:"OCIS_LOG_FILE;AUTH_APP_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set." introductionVersion:"%%NEXT%%"` } // Service defines the service configuration @@ -42,16 +42,16 @@ type Service struct { // Debug defines the debug configuration type Debug struct { - Addr string `yaml:"addr" env:"AUTH_APP_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed." introductionVersion:"pre5.0"` - Token string `yaml:"token" env:"AUTH_APP_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint." introductionVersion:"pre5.0"` - Pprof bool `yaml:"pprof" env:"AUTH_APP_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling." introductionVersion:"pre5.0"` - Zpages bool `yaml:"zpages" env:"AUTH_APP_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing traces in-memory." introductionVersion:"pre5.0"` + Addr string `yaml:"addr" env:"AUTH_APP_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed." introductionVersion:"%%NEXT%%"` + Token string `yaml:"token" env:"AUTH_APP_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint." introductionVersion:"%%NEXT%%"` + Pprof bool `yaml:"pprof" env:"AUTH_APP_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling." introductionVersion:"%%NEXT%%"` + Zpages bool `yaml:"zpages" env:"AUTH_APP_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing traces in-memory." introductionVersion:"%%NEXT%%"` } // GRPCConfig defines the GRPC configuration type GRPCConfig struct { - Addr string `yaml:"addr" env:"AUTH_APP_GRPC_ADDR" desc:"The bind address of the GRPC service." introductionVersion:"pre5.0"` + Addr string `yaml:"addr" env:"AUTH_APP_GRPC_ADDR" desc:"The bind address of the GRPC service." introductionVersion:"%%NEXT%%"` TLS *shared.GRPCServiceTLS `yaml:"tls"` Namespace string `yaml:"-"` - Protocol string `yaml:"protocol" env:"AUTH_APP_GRPC_PROTOCOL" desc:"The transport protocol of the GRPC service." introductionVersion:"pre5.0"` + Protocol string `yaml:"protocol" env:"AUTH_APP_GRPC_PROTOCOL" desc:"The transport protocol of the GRPC service." introductionVersion:"%%NEXT%%"` } diff --git a/services/auth-app/pkg/config/reva.go b/services/auth-app/pkg/config/reva.go index 92aa140854..45c0e16376 100644 --- a/services/auth-app/pkg/config/reva.go +++ b/services/auth-app/pkg/config/reva.go @@ -2,5 +2,5 @@ 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_APP_JWT_SECRET" desc:"The secret to mint and validate jwt tokens." introductionVersion:"pre5.0"` + JWTSecret string `yaml:"jwt_secret" env:"OCIS_JWT_SECRET;AUTH_APP_JWT_SECRET" desc:"The secret to mint and validate jwt tokens." introductionVersion:"%%NEXT%%"` } diff --git a/services/auth-app/pkg/config/tracing.go b/services/auth-app/pkg/config/tracing.go index b33f263419..3a39cae679 100644 --- a/services/auth-app/pkg/config/tracing.go +++ b/services/auth-app/pkg/config/tracing.go @@ -4,10 +4,10 @@ import "github.com/owncloud/ocis/v2/ocis-pkg/tracing" // Tracing defines the tracing configuration. type Tracing struct { - Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;AUTH_APP_TRACING_ENABLED" desc:"Activates tracing." introductionVersion:"pre5.0"` - Type string `yaml:"type" env:"OCIS_TRACING_TYPE;AUTH_APP_TRACING_TYPE" desc:"The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now." introductionVersion:"pre5.0"` - Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;AUTH_APP_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent." introductionVersion:"pre5.0"` - Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;AUTH_APP_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." introductionVersion:"pre5.0"` + Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;AUTH_APP_TRACING_ENABLED" desc:"Activates tracing." introductionVersion:"%%NEXT%%"` + Type string `yaml:"type" env:"OCIS_TRACING_TYPE;AUTH_APP_TRACING_TYPE" desc:"The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now." introductionVersion:"%%NEXT%%"` + Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;AUTH_APP_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent." introductionVersion:"%%NEXT%%"` + Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;AUTH_APP_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." introductionVersion:"%%NEXT%%"` } // Convert Tracing to the tracing package's Config struct. From 0b514ba71548d40333277dcf549c13b2d949387c Mon Sep 17 00:00:00 2001 From: kobergj Date: Wed, 17 Jul 2024 10:59:31 +0200 Subject: [PATCH 10/12] docu(auth-app): Improve documentation Co-authored-by: Martin --- services/auth-app/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/auth-app/README.md b/services/auth-app/README.md index 2c71f28dd3..28a75336f0 100644 --- a/services/auth-app/README.md +++ b/services/auth-app/README.md @@ -16,12 +16,12 @@ ocis uses serveral authentication services for different use cases. All services Because this service is not started automatically, a manual start needs to be initiated which can be done in several ways. To configure the service usage, an environment variable for the proxy service needs to be set to allow app authentication. ```bash OCIS_ADD_RUN_SERVICES=auth-app # deployment specific. Add the service to the manual startup list, use with binary deployments. Alternatively you can start the service explicitly via the command line. -PROXY_ENABLE_APP_AUTH=true # mandatory, allow app authentication. In case of a distributed environment, this envvar needs to be set in the the proxy service. +PROXY_ENABLE_APP_AUTH=true # mandatory, allow app authentication. In case of a distributed environment, this envvar needs to be set in the proxy service. ``` ## App Tokens -App Tokens are used to authenticate 3rd party access via https like when using curl (apps) to access an API endpoint. These apps need to authenticate themselve as no logged in user authenticates the request. To be able to use an app token, one must first create a token via the cli. Replace the `user-name` with an existing user. For the `token-expiration`, you can use any time abbreviation from the following list: `h, m, s`. Examples: `72h` or `1h` or `1m` or `1s.` Default is `72h`. +App Tokens are used to authenticate 3rd party access via https like when using curl (apps) to access an API endpoint. These apps need to authenticate themselves as no logged in user authenticates the request. To be able to use an app token, one must first create a token via the cli. Replace the `user-name` with an existing user. For the `token-expiration`, you can use any time abbreviation from the following list: `h, m, s`. Examples: `72h` or `1h` or `1m` or `1s.` Default is `72h`. ```bash ocis auth-app create --user-name={user-name} --expiration={token-expiration} From 83e6ba7d09b8bc6c76b8108851f98b489ad1c39d Mon Sep 17 00:00:00 2001 From: jkoberg Date: Wed, 17 Jul 2024 16:11:25 +0200 Subject: [PATCH 11/12] fix(proxy): fix pipeline Signed-off-by: jkoberg --- services/proxy/pkg/command/server.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/services/proxy/pkg/command/server.go b/services/proxy/pkg/command/server.go index d83b613727..beb83ae506 100644 --- a/services/proxy/pkg/command/server.go +++ b/services/proxy/pkg/command/server.go @@ -300,10 +300,6 @@ func loadMiddlewares(logger log.Logger, cfg *config.Config, RevaGatewaySelector: gatewaySelector, }) } - authenticators = append(authenticators, middleware.PublicShareAuthenticator{ - Logger: logger, - RevaGatewaySelector: gatewaySelector, - }) authenticators = append(authenticators, middleware.NewOIDCAuthenticator( middleware.Logger(logger), middleware.UserInfoCache(userInfoCache), @@ -359,7 +355,7 @@ func loadMiddlewares(logger log.Logger, cfg *config.Config, middleware.CredentialsByUserAgent(cfg.AuthMiddleware.CredentialsByUserAgent), middleware.Logger(logger), middleware.OIDCIss(cfg.OIDC.Issuer), - middleware.EnableBasicAuth(true), + middleware.EnableBasicAuth(cfg.EnableBasicAuth), middleware.TraceProvider(traceProvider), ), middleware.AccountResolver( From 91396f5aa2dfa303460a9b4d0cd455825aaa05bd Mon Sep 17 00:00:00 2001 From: jkoberg Date: Tue, 23 Jul 2024 08:59:52 +0200 Subject: [PATCH 12/12] feat(auth-app): add changelog Signed-off-by: jkoberg --- changelog/unreleased/auth-app-service.md | 5 +++++ services/auth-app/README.md | 2 +- services/auth-basic/README.md | 2 +- services/auth-bearer/README.md | 2 +- services/auth-machine/README.md | 2 +- services/auth-service/README.md | 2 +- 6 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 changelog/unreleased/auth-app-service.md diff --git a/changelog/unreleased/auth-app-service.md b/changelog/unreleased/auth-app-service.md new file mode 100644 index 0000000000..3d27f95eba --- /dev/null +++ b/changelog/unreleased/auth-app-service.md @@ -0,0 +1,5 @@ +Enhancement: Introduce auth-app service + +Introduce a new service, auth-app, that provides authentication and authorization services for applications. + +https://github.com/owncloud/ocis/pull/9079 diff --git a/services/auth-app/README.md b/services/auth-app/README.md index 28a75336f0..012490527e 100644 --- a/services/auth-app/README.md +++ b/services/auth-app/README.md @@ -5,11 +5,11 @@ The auth-app service provides authentication for 3rd party apps. ## 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-app` handles authentication of external 3rd party apps - `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-app` handles authentication of external 3rd party apps ## Service Startup diff --git a/services/auth-basic/README.md b/services/auth-basic/README.md index b293d338aa..5b90fdcb8c 100644 --- a/services/auth-basic/README.md +++ b/services/auth-basic/README.md @@ -9,11 +9,11 @@ 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-app` handles authentication of external 3rd party apps - `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-app` handles authentication of external 3rd party apps ## Auth Managers diff --git a/services/auth-bearer/README.md b/services/auth-bearer/README.md index 63eef5fa03..152359c885 100644 --- a/services/auth-bearer/README.md +++ b/services/auth-bearer/README.md @@ -5,11 +5,11 @@ The oCIS Auth Bearer service communicates with the configured OpenID Connect ide ## 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-app` handles authentication of external 3rd party apps - `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-app` handles authentication of external 3rd party apps ## Built in OpenID Connect Identity Provider diff --git a/services/auth-machine/README.md b/services/auth-machine/README.md index bafdfcff56..31222489e2 100644 --- a/services/auth-machine/README.md +++ b/services/auth-machine/README.md @@ -3,11 +3,11 @@ 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-app` handles authentication of external 3rd party apps - `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-app` handles authentication of external 3rd party apps ## User Impersonation diff --git a/services/auth-service/README.md b/services/auth-service/README.md index e2338ab6ed..b4b94633e1 100644 --- a/services/auth-service/README.md +++ b/services/auth-service/README.md @@ -5,11 +5,11 @@ The ocis Auth Service is used to authenticate service accounts. Compared to norm ## 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-app` handles authentication of external 3rd party apps - `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-app` handles authentication of external 3rd party apps ## Service Accounts