diff --git a/extensions/ocdav/pkg/command/ocdav.go b/extensions/ocdav/pkg/command/ocdav.go new file mode 100644 index 000000000..e29cfad59 --- /dev/null +++ b/extensions/ocdav/pkg/command/ocdav.go @@ -0,0 +1,172 @@ +package command + +import ( + "context" + "flag" + "fmt" + "strings" + + "github.com/cs3org/reva/v2/pkg/micro/ocdav" + "github.com/oklog/run" + "github.com/owncloud/ocis/extensions/ocdav/pkg/config" + "github.com/owncloud/ocis/extensions/storage/pkg/server/debug" + ociscfg "github.com/owncloud/ocis/ocis-pkg/config" + "github.com/owncloud/ocis/ocis-pkg/conversions" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/sync" + "github.com/owncloud/ocis/ocis-pkg/tracing" + "github.com/thejerf/suture/v4" + "github.com/urfave/cli/v2" +) + +// OCDav is the entrypoint for the ocdav command. +// TODO move ocdav cmd to a separate service +func OCDav(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "ocdav", + Usage: "start ocdav service", + Before: func(c *cli.Context) error { + if err := loadUserAgent(c, cfg); err != nil { + return err + } + // return ParseConfig(c, cfg, "ocdav") + return nil + }, + Action: func(c *cli.Context) error { + logCfg := cfg.Logging + logger := log.NewLogger( + log.Level(logCfg.Level), + log.File(logCfg.File), + log.Pretty(logCfg.Pretty), + log.Color(logCfg.Color), + ) + tracing.Configure(cfg.Tracing.Enabled, cfg.Tracing.Type, logger) + + gr := run.Group{} + ctx, cancel := context.WithCancel(context.Background()) + //metrics = metrics.New() + + defer cancel() + + gr.Add(func() error { + s, err := ocdav.Service( + ocdav.Context(ctx), + ocdav.Logger(logger.Logger), + ocdav.Address(cfg.HTTP.Addr), + ocdav.FilesNamespace(cfg.FilesNamespace), + ocdav.WebdavNamespace(cfg.WebdavNamespace), + ocdav.SharesNamespace(cfg.SharesNamespace), + ocdav.Timeout(cfg.Timeout), + ocdav.Insecure(cfg.Insecure), + ocdav.PublicURL(cfg.PublicURL), + ocdav.Prefix(cfg.HTTP.Prefix), + ocdav.GatewaySvc(cfg.GatewayEndpoint), + ocdav.JWTSecret(cfg.JWTSecret), + // ocdav.FavoriteManager() // FIXME needs a proper persistence implementation + // ocdav.LockSystem(), // will default to the CS3 lock system + // ocdav.TLSConfig() // tls config for the http server + ) + if err != nil { + return err + } + + return s.Run() + }, func(err error) { + logger.Info().Err(err).Str("server", c.Command.Name).Msg("Shutting down server") + cancel() + }) + + { + server, err := debug.Server( + debug.Name(c.Command.Name+"-debug"), + debug.Addr(cfg.Debug.Addr), + debug.Logger(logger), + debug.Context(ctx), + debug.Pprof(cfg.Debug.Pprof), + debug.Zpages(cfg.Debug.Zpages), + debug.Token(cfg.Debug.Token), + ) + + if err != nil { + logger.Info(). + Err(err). + Str("server", "debug"). + Msg("Failed to initialize server") + + return err + } + + gr.Add(server.ListenAndServe, func(_ error) { + cancel() + }) + } + + if !cfg.Supervised { + sync.Trap(&gr, cancel) + } + + return gr.Run() + }, + } +} + +// OCDavSutureService allows for the ocdav command to be embedded and supervised by a suture supervisor tree. +type OCDavSutureService struct { + cfg *config.Config +} + +// NewOCDav creates a new ocdav.OCDavSutureService +func NewOCDav(cfg *ociscfg.Config) suture.Service { + cfg.OCDav.Commons = cfg.Commons + return OCDavSutureService{ + cfg: cfg.OCDav, + } +} + +func (s OCDavSutureService) Serve(ctx context.Context) error { + // s.cfg.Reva.Frontend.Context = ctx + cmd := OCDav(s.cfg) + f := &flag.FlagSet{} + cmdFlags := cmd.Flags + for k := range cmdFlags { + if err := cmdFlags[k].Apply(f); err != nil { + return err + } + } + cliCtx := cli.NewContext(nil, f, nil) + if cmd.Before != nil { + if err := cmd.Before(cliCtx); err != nil { + return err + } + } + if err := cmd.Action(cliCtx); err != nil { + return err + } + + return nil +} + +// loadUserAgent reads the user-agent-whitelist-lock-in, since it is a string flag, and attempts to construct a map of +// "user-agent":"challenge" locks in for Reva. +// Modifies cfg. Spaces don't need to be trimmed as urfavecli takes care of it. User agents with spaces are valid. i.e: +// Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:83.0) Gecko/20100101 Firefox/83.0 +// This function works by relying in our format of specifying [user-agent:challenge] and the fact that the user agent +// might contain ":" (colon), so the original string is reversed, split in two parts, by the time it is split we +// have the indexes reversed and the tuple is in the format of [challenge:user-agent], then the same process is applied +// in reverse for each individual part +func loadUserAgent(c *cli.Context, cfg *config.Config) error { + cfg.Middleware.Auth.CredentialsByUserAgent = make(map[string]string) + locks := c.StringSlice("user-agent-whitelist-lock-in") + + for _, v := range locks { + vv := conversions.Reverse(v) + parts := strings.SplitN(vv, ":", 2) + if len(parts) != 2 { + return fmt.Errorf("unexpected config value for user-agent lock-in: %v, expected format is user-agent:challenge", v) + } + + cfg.Middleware.Auth.CredentialsByUserAgent[conversions.Reverse(parts[1])] = conversions.Reverse(parts[0]) + } + + return nil +} diff --git a/extensions/ocdav/pkg/config/config.go b/extensions/ocdav/pkg/config/config.go new file mode 100644 index 000000000..4b141a48c --- /dev/null +++ b/extensions/ocdav/pkg/config/config.go @@ -0,0 +1,71 @@ +package config + +import "github.com/owncloud/ocis/ocis-pkg/shared" + +type Config struct { + *shared.Commons `yaml:"-"` + Service Service `yaml:"-"` + Tracing *Tracing `yaml:"tracing"` + Logging *Logging `yaml:"log"` + Debug Debug `yaml:"debug"` + Supervised bool + + HTTP HTTPConfig `yaml:"http"` + + // JWTSecret used to verify reva access token + JWTSecret string `yaml:"jwt_secret"` + GatewayEndpoint string + SkipUserGroupsInToken bool + + WebdavNamespace string `yaml:"webdav_namespace"` + FilesNamespace string `yaml:"files_namespace"` + SharesNamespace string `yaml:"shares_namespace"` + // PublicURL used to redirect /s/{token} URLs to + PublicURL string `yaml:"public_url"` + + // Insecure certificates allowed when making requests to the gateway + Insecure bool `yaml:"insecure"` + // Timeout in seconds when making requests to the gateway + Timeout int64 `yaml:"timeout"` + Middleware Middleware +} +type Tracing struct { + Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;OCDAV_TRACING_ENABLED" desc:"Activates tracing."` + Type string `yaml:"type" env:"OCIS_TRACING_TYPE;OCDAV_TRACING_TYPE"` + Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;OCDAV_TRACING_ENDPOINT" desc:"The endpoint to the tracing collector."` + Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;OCDAV_TRACING_COLLECTOR"` +} + +type Logging struct { + Level string `yaml:"level" env:"OCIS_LOG_LEVEL;OCDAV_LOG_LEVEL" desc:"The log level."` + Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;OCDAV_LOG_PRETTY" desc:"Activates pretty log output."` + Color bool `yaml:"color" env:"OCIS_LOG_COLOR;OCDAV_LOG_COLOR" desc:"Activates colorized log output."` + File string `yaml:"file" env:"OCIS_LOG_FILE;OCDAV_LOG_FILE" desc:"The target log file."` +} + +type Service struct { + Name string `yaml:"-"` +} + +type Debug struct { + Addr string `yaml:"addr" env:"OCDAV_DEBUG_ADDR"` + Token string `yaml:"token" env:"OCDAV_DEBUG_TOKEN"` + Pprof bool `yaml:"pprof" env:"OCDAV_DEBUG_PPROF"` + Zpages bool `yaml:"zpages" env:"OCDAV_DEBUG_ZPAGES"` +} + +type HTTPConfig struct { + Addr string `yaml:"addr" env:"OCDAV_HTTP_ADDR" desc:"The address of the http service."` + Protocol string `yaml:"protocol" env:"OCDAV_HTTP_PROTOCOL" desc:"The transport protocol of the http service."` + Prefix string `yaml:"prefix"` +} + +// Middleware configures reva middlewares. +type Middleware struct { + Auth Auth `yaml:"auth"` +} + +// Auth configures reva http auth middleware. +type Auth struct { + CredentialsByUserAgent map[string]string `yaml:"credentials_by_user_agenr"` +} diff --git a/extensions/ocdav/pkg/config/defaults/defaultconfig.go b/extensions/ocdav/pkg/config/defaults/defaultconfig.go new file mode 100644 index 000000000..b2cc23123 --- /dev/null +++ b/extensions/ocdav/pkg/config/defaults/defaultconfig.go @@ -0,0 +1,70 @@ +package defaults + +import ( + "github.com/owncloud/ocis/extensions/ocdav/pkg/config" +) + +func FullDefaultConfig() *config.Config { + cfg := DefaultConfig() + + EnsureDefaults(cfg) + + return cfg +} + +func DefaultConfig() *config.Config { + return &config.Config{ + Debug: config.Debug{ + Addr: "127.0.0.1:9163", + Token: "", + Pprof: false, + Zpages: false, + }, + HTTP: config.HTTPConfig{ + Addr: "127.0.0.1:0", // :0 to pick any free local port + Protocol: "tcp", + Prefix: "", + }, + Service: config.Service{ + Name: "ocdav", + }, + GatewayEndpoint: "127.0.0.1:9142", + JWTSecret: "Pive-Fumkiu4", + WebdavNamespace: "/users/{{.Id.OpaqueId}}", + FilesNamespace: "/users/{{.Id.OpaqueId}}", + SharesNamespace: "/Shares", + PublicURL: "https://localhost:9200", + Insecure: false, + Timeout: 84300, + Middleware: config.Middleware{ + Auth: config.Auth{ + CredentialsByUserAgent: map[string]string{}, + }, + }, + } +} + +func EnsureDefaults(cfg *config.Config) { + // provide with defaults for shared logging, since we need a valid destination address for BindEnv. + if cfg.Logging == nil && cfg.Commons != nil && cfg.Commons.Log != nil { + cfg.Logging = &config.Logging{ + Level: cfg.Commons.Log.Level, + Pretty: cfg.Commons.Log.Pretty, + Color: cfg.Commons.Log.Color, + File: cfg.Commons.Log.File, + } + } else if cfg.Logging == nil { + cfg.Logging = &config.Logging{} + } + // provide with defaults for shared tracing, since we need a valid destination address for BindEnv. + 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{} + } +} diff --git a/extensions/storage/pkg/command/ocdav.go b/extensions/storage/pkg/command/ocdav.go deleted file mode 100644 index fd6afee77..000000000 --- a/extensions/storage/pkg/command/ocdav.go +++ /dev/null @@ -1,134 +0,0 @@ -package command - -import ( - "context" - "flag" - - "github.com/cs3org/reva/v2/pkg/micro/ocdav" - "github.com/oklog/run" - "github.com/owncloud/ocis/extensions/storage/pkg/config" - "github.com/owncloud/ocis/extensions/storage/pkg/server/debug" - "github.com/owncloud/ocis/extensions/storage/pkg/tracing" - ociscfg "github.com/owncloud/ocis/ocis-pkg/config" - "github.com/owncloud/ocis/ocis-pkg/sync" - "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" -) - -// OCDav is the entrypoint for the ocdav command. -// TODO move ocdav cmd to a separate service -func OCDav(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "ocdav", - Usage: "start ocdav service", - Before: func(c *cli.Context) error { - if err := loadUserAgent(c, cfg); err != nil { - return err - } - return ParseConfig(c, cfg, "ocdav") - }, - Action: func(c *cli.Context) error { - logger := NewLogger(cfg) - - tracing.Configure(cfg, logger) - - gr := run.Group{} - ctx, cancel := context.WithCancel(context.Background()) - //metrics = metrics.New() - - defer cancel() - - gr.Add(func() error { - s, err := ocdav.Service( - ocdav.Context(ctx), - ocdav.Logger(logger.Logger), - ocdav.Address(cfg.OCDav.Addr), - ocdav.FilesNamespace(cfg.OCDav.FilesNamespace), - ocdav.WebdavNamespace(cfg.OCDav.WebdavNamespace), - ocdav.SharesNamespace(cfg.OCDav.SharesNamespace), - ocdav.Timeout(cfg.OCDav.Timeout), - ocdav.Insecure(cfg.OCDav.Insecure), - ocdav.PublicURL(cfg.OCDav.PublicURL), - ocdav.Prefix(cfg.OCDav.Prefix), - ocdav.GatewaySvc(cfg.OCDav.GatewaySVC), - ocdav.JWTSecret(cfg.OCDav.JWTSecret), - // ocdav.FavoriteManager() // FIXME needs a proper persistence implementation - // ocdav.LockSystem(), // will default to the CS3 lock system - // ocdav.TLSConfig() // tls config for the http server - ) - if err != nil { - return err - } - - return s.Run() - }, func(err error) { - logger.Info().Err(err).Str("server", c.Command.Name).Msg("Shutting down server") - cancel() - }) - - { - server, err := debug.Server( - debug.Name(c.Command.Name+"-debug"), - debug.Addr(cfg.OCDav.DebugAddr), - 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(server.ListenAndServe, func(_ error) { - cancel() - }) - } - - if !cfg.Reva.Frontend.Supervised { - sync.Trap(&gr, cancel) - } - - return gr.Run() - }, - } -} - -// OCDavSutureService allows for the ocdav command to be embedded and supervised by a suture supervisor tree. -type OCDavSutureService struct { - cfg *config.Config -} - -// NewOCDav creates a new ocdav.OCDavSutureService -func NewOCDav(cfg *ociscfg.Config) suture.Service { - cfg.Storage.Commons = cfg.Commons - return OCDavSutureService{ - cfg: cfg.Storage, - } -} - -func (s OCDavSutureService) Serve(ctx context.Context) error { - s.cfg.Reva.Frontend.Context = ctx - f := &flag.FlagSet{} - cmdFlags := OCDav(s.cfg).Flags - for k := range cmdFlags { - if err := cmdFlags[k].Apply(f); err != nil { - return err - } - } - cliCtx := cli.NewContext(nil, f, nil) - if OCDav(s.cfg).Before != nil { - if err := OCDav(s.cfg).Before(cliCtx); err != nil { - return err - } - } - if err := OCDav(s.cfg).Action(cliCtx); err != nil { - return err - } - - return nil -} diff --git a/ocis-pkg/config/config.go b/ocis-pkg/config/config.go index 3ab695097..20893f2dd 100644 --- a/ocis-pkg/config/config.go +++ b/ocis-pkg/config/config.go @@ -17,6 +17,7 @@ import ( idp "github.com/owncloud/ocis/extensions/idp/pkg/config" nats "github.com/owncloud/ocis/extensions/nats/pkg/config" notifications "github.com/owncloud/ocis/extensions/notifications/pkg/config" + ocdav "github.com/owncloud/ocis/extensions/ocdav/pkg/config" ocs "github.com/owncloud/ocis/extensions/ocs/pkg/config" proxy "github.com/owncloud/ocis/extensions/proxy/pkg/config" settings "github.com/owncloud/ocis/extensions/settings/pkg/config" @@ -95,6 +96,7 @@ type Config struct { StoragePublicLink *storagepublic.Config `yaml:"storage_public"` StorageUsers *storageusers.Config `yaml:"storage_users"` StorageShares *storageshares.Config `yaml:"storage_shares"` + OCDav *ocdav.Config `yaml:"ocdav"` Store *store.Config `yaml:"store"` Thumbnails *thumbnails.Config `yaml:"thumbnails"` WebDAV *webdav.Config `yaml:"webdav"` diff --git a/ocis-pkg/config/defaultconfig.go b/ocis-pkg/config/defaultconfig.go index 907264a25..8213ec9a1 100644 --- a/ocis-pkg/config/defaultconfig.go +++ b/ocis-pkg/config/defaultconfig.go @@ -15,6 +15,7 @@ import ( idp "github.com/owncloud/ocis/extensions/idp/pkg/config/defaults" nats "github.com/owncloud/ocis/extensions/nats/pkg/config/defaults" notifications "github.com/owncloud/ocis/extensions/notifications/pkg/config/defaults" + ocdav "github.com/owncloud/ocis/extensions/ocdav/pkg/config/defaults" ocs "github.com/owncloud/ocis/extensions/ocs/pkg/config/defaults" proxy "github.com/owncloud/ocis/extensions/proxy/pkg/config/defaults" settings "github.com/owncloud/ocis/extensions/settings/pkg/config/defaults" @@ -68,5 +69,6 @@ func DefaultConfig() *Config { StorageUsers: storageusers.FullDefaultConfig(), StorageShares: storageshares.FullDefaultConfig(), AppProvider: appprovider.FullDefaultConfig(), + OCDav: ocdav.FullDefaultConfig(), } } diff --git a/ocis/pkg/command/ocdav.go b/ocis/pkg/command/ocdav.go index c458ee285..c13e6ee07 100644 --- a/ocis/pkg/command/ocdav.go +++ b/ocis/pkg/command/ocdav.go @@ -1,7 +1,7 @@ package command import ( - "github.com/owncloud/ocis/extensions/storage/pkg/command" + "github.com/owncloud/ocis/extensions/ocdav/pkg/command" "github.com/owncloud/ocis/ocis-pkg/config" "github.com/owncloud/ocis/ocis/pkg/register" "github.com/urfave/cli/v2" @@ -13,11 +13,11 @@ func OCDavCommand(cfg *config.Config) *cli.Command { Name: "ocdav", Usage: "start ocdav", Category: "extensions", - Before: func(ctx *cli.Context) error { - return ParseStorageCommon(ctx, cfg) - }, + // Before: func(ctx *cli.Context) error { + // return ParseStorageCommon(ctx, cfg) + // }, Action: func(c *cli.Context) error { - origCmd := command.OCDav(cfg.Storage) + origCmd := command.OCDav(cfg.OCDav) return handleOriginalAction(c, origCmd) }, } diff --git a/ocis/pkg/runtime/service/service.go b/ocis/pkg/runtime/service/service.go index d5b7c8e1a..b37a8a144 100644 --- a/ocis/pkg/runtime/service/service.go +++ b/ocis/pkg/runtime/service/service.go @@ -32,6 +32,7 @@ import ( idp "github.com/owncloud/ocis/extensions/idp/pkg/command" nats "github.com/owncloud/ocis/extensions/nats/pkg/command" notifications "github.com/owncloud/ocis/extensions/notifications/pkg/command" + ocdav "github.com/owncloud/ocis/extensions/ocdav/pkg/command" ocs "github.com/owncloud/ocis/extensions/ocs/pkg/command" proxy "github.com/owncloud/ocis/extensions/proxy/pkg/command" settings "github.com/owncloud/ocis/extensions/settings/pkg/command" @@ -118,7 +119,7 @@ func NewService(options ...Option) (*Service, error) { s.ServicesRegistry["web"] = web.NewSutureService s.ServicesRegistry["webdav"] = webdav.NewSutureService s.ServicesRegistry["storage-frontend"] = storage.NewFrontend - s.ServicesRegistry["ocdav"] = storage.NewOCDav + s.ServicesRegistry["ocdav"] = ocdav.NewOCDav s.ServicesRegistry["storage-gateway"] = storage.NewGateway s.ServicesRegistry["storage-userprovider"] = user.NewUserProvider s.ServicesRegistry["storage-groupprovider"] = group.NewGroupProvider