Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
@@ -0,0 +1,54 @@
package command
import (
"fmt"
"net/http"
"github.com/qsfera/server/pkg/config/configlog"
"github.com/qsfera/server/pkg/log"
"github.com/qsfera/server/services/gateway/pkg/config"
"github.com/qsfera/server/services/gateway/pkg/config/parser"
"github.com/spf13/cobra"
)
// Health is the entrypoint for the health command.
func Health(cfg *config.Config) *cobra.Command {
return &cobra.Command{
Use: "health",
Short: "check health status",
PreRunE: func(cmd *cobra.Command, args []string) error {
return configlog.ReturnError(parser.ParseConfig(cfg))
},
RunE: func(cmd *cobra.Command, args []string) error {
logger := log.Configure(cfg.Service.Name, cfg.Commons, cfg.LogLevel)
resp, err := http.Get(
fmt.Sprintf(
"http://%s/healthz",
cfg.Debug.Addr,
),
)
if err != nil {
logger.Fatal().
Err(err).
Msg("Failed to request health check")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
logger.Fatal().
Int("code", resp.StatusCode).
Msg("Health seems to be in bad state")
}
logger.Debug().
Int("code", resp.StatusCode).
Msg("Health got a good state")
return nil
},
}
}
@@ -0,0 +1,36 @@
package command
import (
"os"
"github.com/qsfera/server/pkg/clihelper"
"github.com/qsfera/server/services/gateway/pkg/config"
"github.com/spf13/cobra"
)
// GetCommands provides all commands for this service
func GetCommands(cfg *config.Config) []*cobra.Command {
return []*cobra.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 qsfera-gateway command.
func Execute(cfg *config.Config) error {
app := clihelper.DefaultApp(&cobra.Command{
Use: "gateway",
Short: "Provide a CS3api gateway for КуСфера",
})
app.AddCommand(GetCommands(cfg)...)
app.SetArgs(os.Args[1:])
return app.ExecuteContext(cfg.Context)
}
@@ -0,0 +1,96 @@
package command
import (
"context"
"fmt"
"os/signal"
"github.com/qsfera/server/pkg/config/configlog"
"github.com/qsfera/server/pkg/log"
"github.com/qsfera/server/pkg/registry"
"github.com/qsfera/server/pkg/runner"
"github.com/qsfera/server/pkg/tracing"
"github.com/qsfera/server/pkg/version"
"github.com/qsfera/server/services/gateway/pkg/config"
"github.com/qsfera/server/services/gateway/pkg/config/parser"
"github.com/qsfera/server/services/gateway/pkg/revaconfig"
"github.com/qsfera/server/services/gateway/pkg/server/debug"
"github.com/opencloud-eu/reva/v2/cmd/revad/runtime"
"github.com/spf13/cobra"
)
// Server is the entry point for the server command.
func Server(cfg *config.Config) *cobra.Command {
return &cobra.Command{
Use: "server",
Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name),
PreRunE: func(cmd *cobra.Command, args []string) error {
return configlog.ReturnFatal(parser.ParseConfig(cfg))
},
RunE: func(cmd *cobra.Command, args []string) error {
logger := log.Configure(cfg.Service.Name, cfg.Commons, cfg.LogLevel)
traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name)
if err != nil {
return err
}
var cancel context.CancelFunc
if cfg.Context == nil {
cfg.Context, cancel = signal.NotifyContext(context.Background(), runner.StopSignals...)
defer cancel()
}
ctx := cfg.Context
gr := runner.NewGroup()
{
// run the appropriate reva servers based on the config
rCfg := revaconfig.GatewayConfigFromStruct(cfg, logger)
if rServer := runtime.NewDrivenHTTPServerWithOptions(rCfg,
runtime.WithLogger(&logger.Logger),
runtime.WithRegistry(registry.GetRegistry()),
runtime.WithTraceProvider(traceProvider),
); rServer != nil {
gr.Add(runner.NewRevaServiceRunner(cfg.Service.Name+".rhttp", rServer))
}
if rServer := runtime.NewDrivenGRPCServerWithOptions(rCfg,
runtime.WithLogger(&logger.Logger),
runtime.WithRegistry(registry.GetRegistry()),
runtime.WithTraceProvider(traceProvider),
); rServer != nil {
gr.Add(runner.NewRevaServiceRunner(cfg.Service.Name+".rgrpc", rServer))
}
}
{
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(runner.NewGolangHttpServerRunner(cfg.Service.Name+".debug", debugServer))
}
grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, cfg.GRPC.Protocol, cfg.GRPC.Addr, version.GetString())
if err := registry.RegisterService(ctx, logger, grpcSvc, cfg.Debug.Addr); err != nil {
logger.Fatal().Err(err).Msg("failed to register the grpc service")
}
grResults := gr.Run(ctx)
// return the first non-nil error found in the results
for _, grResult := range grResults {
if grResult.RunnerError != nil {
return grResult.RunnerError
}
}
return nil
},
}
}
@@ -0,0 +1,49 @@
package command
import (
"fmt"
"os"
"github.com/qsfera/server/pkg/registry"
"github.com/qsfera/server/pkg/version"
"github.com/qsfera/server/services/gateway/pkg/config"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"github.com/spf13/cobra"
)
// Version prints the service versions of all running instances.
func Version(cfg *config.Config) *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "print the version of this binary and the running service instances",
RunE: func(cmd *cobra.Command, args []string) 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 := tablewriter.NewTable(os.Stdout, tablewriter.WithHeaderAutoFormat(tw.Off))
table.Header([]string{"Version", "Address", "Id"})
for _, s := range services {
for _, n := range s.Nodes {
table.Append([]string{s.Version, n.Address, n.Id})
}
}
table.Render()
return nil
},
}
}
@@ -0,0 +1,94 @@
package config
import (
"context"
"time"
"github.com/qsfera/server/pkg/shared"
)
type Config struct {
Commons *shared.Commons `yaml:"-"` // don't use this directly as configuration for a service
Service Service `yaml:"-"`
LogLevel string `yaml:"loglevel" env:"OC_LOG_LEVEL;GATEWAY_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"1.0.0"`
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:"GATEWAY_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the loading of user's group memberships from the reva access token." introductionVersion:"1.0.0"`
CommitShareToStorageGrant bool `yaml:"commit_share_to_storage_grant" env:"GATEWAY_COMMIT_SHARE_TO_STORAGE_GRANT" desc:"Commit shares to storage grants. This grants access to shared resources for the share receiver directly on the storage." introductionVersion:"1.0.0"`
ShareFolder string `yaml:"share_folder_name" env:"GATEWAY_SHARE_FOLDER_NAME" desc:"Name of the share folder in users' home space." introductionVersion:"1.0.0"`
DisableHomeCreationOnLogin bool `yaml:"disable_home_creation_on_login" env:"GATEWAY_DISABLE_HOME_CREATION_ON_LOGIN" desc:"Disable creation of the home space on login." introductionVersion:"1.0.0"`
TransferSecret string `yaml:"transfer_secret" env:"OC_TRANSFER_SECRET" desc:"The storage transfer secret." introductionVersion:"1.0.0"`
TransferExpires int `yaml:"transfer_expires" env:"GATEWAY_TRANSFER_EXPIRES" desc:"Expiry for the gateway tokens." introductionVersion:"1.0.0"`
Cache Cache `yaml:"cache"`
FrontendPublicURL string `yaml:"frontend_public_url" env:"OC_URL;GATEWAY_FRONTEND_PUBLIC_URL" desc:"The public facing URL of the КуСфера frontend." introductionVersion:"1.0.0"`
UsersEndpoint string `yaml:"users_endpoint" env:"GATEWAY_USERS_ENDPOINT" desc:"The endpoint of the users service. Can take a service name or a gRPC URI with the dns, kubernetes or unix protocol." introductionVersion:"1.0.0"`
GroupsEndpoint string `yaml:"groups_endpoint" env:"GATEWAY_GROUPS_ENDPOINT" desc:"The endpoint of the groups service. Can take a service name or a gRPC URI with the dns, kubernetes or unix protocol." introductionVersion:"1.0.0"`
PermissionsEndpoint string `yaml:"permissions_endpoint" env:"GATEWAY_PERMISSIONS_ENDPOINT" desc:"The endpoint of the permissions service. Can take a service name or a gRPC URI with the dns, kubernetes or unix protocol." introductionVersion:"1.0.0"`
SharingEndpoint string `yaml:"sharing_endpoint" env:"GATEWAY_SHARING_ENDPOINT" desc:"The endpoint of the shares service. Can take a service name or a gRPC URI with the dns, kubernetes or unix protocol." introductionVersion:"1.0.0"`
AuthAppEndpoint string `yaml:"auth_app_endpoint" env:"GATEWAY_AUTH_APP_ENDPOINT" desc:"The endpoint of the auth-app service. Can take a service name or a gRPC URI with the dns, kubernetes or unix protocol." introductionVersion:"1.0.0"`
AuthBasicEndpoint string `yaml:"auth_basic_endpoint" env:"GATEWAY_AUTH_BASIC_ENDPOINT" desc:"The endpoint of the auth-basic service. Can take a service name or a gRPC URI with the dns, kubernetes or unix protocol." introductionVersion:"1.0.0"`
AuthBearerEndpoint string `yaml:"auth_bearer_endpoint" env:"GATEWAY_AUTH_BEARER_ENDPOINT" desc:"The endpoint of the auth-bearer service. Can take a service name or a gRPC URI with the dns, kubernetes or unix protocol." introductionVersion:"1.0.0"`
AuthMachineEndpoint string `yaml:"auth_machine_endpoint" env:"GATEWAY_AUTH_MACHINE_ENDPOINT" desc:"The endpoint of the auth-machine service. Can take a service name or a gRPC URI with the dns, kubernetes or unix protocol." introductionVersion:"1.0.0"`
AuthServiceEndpoint string `yaml:"auth_service_endpoint" env:"GATEWAY_AUTH_SERVICE_ENDPOINT" desc:"The endpoint of the auth-service service. Can take a service name or a gRPC URI with the dns, kubernetes or unix protocol." introductionVersion:"1.0.0"`
StoragePublicLinkEndpoint string `yaml:"storage_public_link_endpoint" env:"GATEWAY_STORAGE_PUBLIC_LINK_ENDPOINT" desc:"The endpoint of the storage-publiclink service. Can take a service name or a gRPC URI with the dns, kubernetes or unix protocol." introductionVersion:"1.0.0"`
StorageUsersEndpoint string `yaml:"storage_users_endpoint" env:"GATEWAY_STORAGE_USERS_ENDPOINT" desc:"The endpoint of the storage-users service. Can take a service name or a gRPC URI with the dns, kubernetes or unix protocol." introductionVersion:"1.0.0"`
StorageSharesEndpoint string `yaml:"storage_shares_endpoint" env:"GATEWAY_STORAGE_SHARES_ENDPOINT" desc:"The endpoint of the storage-shares service. Can take a service name or a gRPC URI with the dns, kubernetes or unix protocol." introductionVersion:"1.0.0"`
AppRegistryEndpoint string `yaml:"app_registry_endpoint" env:"GATEWAY_APP_REGISTRY_ENDPOINT" desc:"The endpoint of the app-registry service. Can take a service name or a gRPC URI with the dns, kubernetes or unix protocol." introductionVersion:"1.0.0"`
OCMEndpoint string `yaml:"ocm_endpoint" env:"GATEWAY_OCM_ENDPOINT" desc:"The endpoint of the ocm service. Can take a service name or a gRPC URI with the dns, kubernetes or unix protocol." introductionVersion:"1.0.0"`
StorageRegistry StorageRegistry `yaml:"storage_registry"` // TODO: should we even support switching this?
Context context.Context `yaml:"-"`
}
type Service struct {
Name string `yaml:"-"`
}
type Debug struct {
Addr string `yaml:"addr" env:"GATEWAY_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed." introductionVersion:"1.0.0"`
Token string `yaml:"token" env:"GATEWAY_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint." introductionVersion:"1.0.0"`
Pprof bool `yaml:"pprof" env:"GATEWAY_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling." introductionVersion:"1.0.0"`
Zpages bool `yaml:"zpages" env:"GATEWAY_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing in-memory traces." introductionVersion:"1.0.0"`
}
type GRPCConfig struct {
Addr string `yaml:"addr" env:"OC_GATEWAY_GRPC_ADDR;GATEWAY_GRPC_ADDR" desc:"The bind address of the GRPC service." introductionVersion:"1.0.0"`
TLS *shared.GRPCServiceTLS `yaml:"tls"`
Namespace string `yaml:"-"`
Protocol string `yaml:"protocol" env:"OC_GRPC_PROTOCOL;GATEWAY_GRPC_PROTOCOL" desc:"The transport protocol of the GRPC service." introductionVersion:"1.0.0"`
}
type StorageRegistry struct {
Driver string `yaml:"driver" env:"GATEWAY_STORAGE_REGISTRY_DRIVER" desc:"The driver name of the storage registry to use." introductionVersion:"1.0.0"`
Rules []string `yaml:"rules" env:"GATEWAY_STORAGE_REGISTRY_RULES" desc:"The rules for the storage registry. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
JSON string `yaml:"json" env:"GATEWAY_STORAGE_REGISTRY_CONFIG_JSON" desc:"Additional configuration for the storage registry in json format." introductionVersion:"1.0.0"`
StorageUsersMountID string `yaml:"storage_users_mount_id" env:"GATEWAY_STORAGE_USERS_MOUNT_ID" desc:"Mount ID of this storage. Admins can set the ID for the storage in this config option manually which is then used to reference the storage. Any reasonable long string is possible, preferably this would be an UUIDv4 format." introductionVersion:"1.0.0"`
}
// Cache holds cache config
type Cache struct {
ProviderCacheStore string `yaml:"provider_cache_store" env:"OC_CACHE_STORE;GATEWAY_PROVIDER_CACHE_STORE" desc:"The type of the cache store. Supported values are: 'memory', 'redis-sentinel', 'nats-js-kv', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
ProviderCacheNodes []string `yaml:"provider_cache_nodes" env:"OC_CACHE_STORE_NODES;GATEWAY_PROVIDER_CACHE_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
ProviderCacheDatabase string `yaml:"provider_cache_database" env:"OC_CACHE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
ProviderCacheTTL time.Duration `yaml:"provider_cache_ttl" env:"OC_CACHE_TTL;GATEWAY_PROVIDER_CACHE_TTL" desc:"Default time to live for user info in the cache. Only applied when access tokens has no expiration. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
ProviderCacheDisablePersistence bool `yaml:"provider_cache_disable_persistence" env:"OC_CACHE_DISABLE_PERSISTENCE;GATEWAY_PROVIDER_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the provider cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false." introductionVersion:"1.0.0"`
ProviderCacheAuthUsername string `yaml:"provider_cache_auth_username" env:"OC_CACHE_AUTH_USERNAME;GATEWAY_PROVIDER_CACHE_AUTH_USERNAME" desc:"The username to use for authentication. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
ProviderCacheAuthPassword string `yaml:"provider_cache_auth_password" env:"OC_CACHE_AUTH_PASSWORD;GATEWAY_PROVIDER_CACHE_AUTH_PASSWORD" desc:"The password to use for authentication. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
CreateHomeCacheStore string `yaml:"create_home_cache_store" env:"OC_CACHE_STORE;GATEWAY_CREATE_HOME_CACHE_STORE" desc:"The type of the cache store. Supported values are: 'memory', 'redis-sentinel', 'nats-js-kv', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
CreateHomeCacheNodes []string `yaml:"create_home_cache_nodes" env:"OC_CACHE_STORE_NODES;GATEWAY_CREATE_HOME_CACHE_STORE_NODES" desc:"A list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
CreateHomeCacheDatabase string `yaml:"create_home_cache_database" env:"OC_CACHE_DATABASE" desc:"The database name the configured store should use." introductionVersion:"1.0.0"`
CreateHomeCacheTTL time.Duration `yaml:"create_home_cache_ttl" env:"OC_CACHE_TTL;GATEWAY_CREATE_HOME_CACHE_TTL" desc:"Default time to live for user info in the cache. Only applied when access tokens has no expiration. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"`
CreateHomeCacheDisablePersistence bool `yaml:"create_home_cache_disable_persistence" env:"OC_CACHE_DISABLE_PERSISTENCE;GATEWAY_CREATE_HOME_CACHE_DISABLE_PERSISTENCE" desc:"Disables persistence of the create home cache. Only applies when store type 'nats-js-kv' is configured. Defaults to false." introductionVersion:"1.0.0"`
CreateHomeCacheAuthUsername string `yaml:"create_home_cache_auth_username" env:"OC_CACHE_AUTH_USERNAME;GATEWAY_CREATE_HOME_CACHE_AUTH_USERNAME" desc:"The username to use for authentication. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
CreateHomeCacheAuthPassword string `yaml:"create_home_cache_auth_password" env:"OC_CACHE_AUTH_PASSWORD;GATEWAY_CREATE_HOME_CACHE_AUTH_PASSWORD" desc:"The password to use for authentication. Only applies when store type 'nats-js-kv' is configured." introductionVersion:"1.0.0"`
}
@@ -0,0 +1,105 @@
package defaults
import (
"time"
"github.com/qsfera/server/pkg/shared"
"github.com/qsfera/server/pkg/structs"
"github.com/qsfera/server/services/gateway/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:9143",
Token: "",
Pprof: false,
Zpages: false,
},
GRPC: config.GRPCConfig{
Addr: "127.0.0.1:9142",
Namespace: "qsfera.api",
Protocol: "tcp",
},
Service: config.Service{
Name: "gateway",
},
Reva: shared.DefaultRevaConfig(),
CommitShareToStorageGrant: true,
ShareFolder: "Shares",
DisableHomeCreationOnLogin: true,
TransferExpires: 24 * 60 * 60,
Cache: config.Cache{
ProviderCacheStore: "noop",
ProviderCacheNodes: []string{"127.0.0.1:9233"},
ProviderCacheDatabase: "cache-providers",
ProviderCacheTTL: 300 * time.Second,
CreateHomeCacheStore: "memory",
CreateHomeCacheNodes: []string{"127.0.0.1:9233"},
CreateHomeCacheDatabase: "cache-createhome",
CreateHomeCacheTTL: 300 * time.Second,
},
FrontendPublicURL: "https://localhost:9200",
AppRegistryEndpoint: "qsfera.api.app-registry",
AuthAppEndpoint: "qsfera.api.auth-app",
AuthBasicEndpoint: "qsfera.api.auth-basic",
AuthMachineEndpoint: "qsfera.api.auth-machine",
AuthServiceEndpoint: "qsfera.api.auth-service",
GroupsEndpoint: "qsfera.api.groups",
PermissionsEndpoint: "qsfera.api.settings",
SharingEndpoint: "qsfera.api.sharing",
StoragePublicLinkEndpoint: "qsfera.api.storage-publiclink",
StorageSharesEndpoint: "qsfera.api.storage-shares",
StorageUsersEndpoint: "qsfera.api.storage-users",
UsersEndpoint: "qsfera.api.users",
OCMEndpoint: "qsfera.api.ocm",
StorageRegistry: config.StorageRegistry{
Driver: "spaces",
JSON: "",
},
}
}
// EnsureDefaults adds default values to the configuration if they are not set yet
func EnsureDefaults(cfg *config.Config) {
if cfg.LogLevel == "" {
cfg.LogLevel = "error"
}
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.TransferSecret == "" && cfg.Commons != nil && cfg.Commons.TransferSecret != "" {
cfg.TransferSecret = cfg.Commons.TransferSecret
}
if cfg.GRPC.TLS == nil && cfg.Commons != nil {
cfg.GRPC.TLS = structs.CopyOrZeroValue(cfg.Commons.GRPCServiceTLS)
}
}
// Sanitize sanitized the configuration
func Sanitize(cfg *config.Config) {
// nothing to sanitize here atm
}
@@ -0,0 +1,55 @@
package parser
import (
"errors"
"fmt"
occfg "github.com/qsfera/server/pkg/config"
defaults2 "github.com/qsfera/server/pkg/config/defaults"
"github.com/qsfera/server/pkg/config/envdecode"
"github.com/qsfera/server/pkg/shared"
"github.com/qsfera/server/services/gateway/pkg/config"
"github.com/qsfera/server/services/gateway/pkg/config/defaults"
)
// ParseConfig loads configuration from known paths.
func ParseConfig(cfg *config.Config) error {
err := occfg.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)
}
if cfg.TransferSecret == "" {
return shared.MissingRevaTransferSecretError(cfg.Service.Name)
}
if cfg.StorageRegistry.StorageUsersMountID == "" {
return fmt.Errorf("The storage users mount ID has not been configured for %s. "+
"Make sure your %s config contains the proper values "+
"(e.g. by running qsfera init or setting it manually in "+
"the config/corresponding environment variable).",
"gateway", defaults2.BaseConfigPath())
}
return nil
}
@@ -0,0 +1,6 @@
package config
// TokenManager is the config for using the reva token manager
type TokenManager struct {
JWTSecret string `yaml:"jwt_secret" env:"OC_JWT_SECRET;GATEWAY_JWT_SECRET" desc:"The secret to mint and validate jwt tokens." introductionVersion:"1.0.0"`
}
@@ -0,0 +1,206 @@
package revaconfig
import (
"encoding/json"
"os"
"strings"
pkgconfig "github.com/qsfera/server/pkg/config"
"github.com/qsfera/server/pkg/log"
"github.com/qsfera/server/services/gateway/pkg/config"
"github.com/opencloud-eu/reva/v2/pkg/utils"
)
// GatewayConfigFromStruct will adapt a КуСфера config struct into a reva mapstructure to start a reva service.
func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]any {
localEndpoint := pkgconfig.LocalEndpoint(cfg.GRPC.Protocol, cfg.GRPC.Addr)
rcfg := map[string]any{
"shared": map[string]any{
"jwt_secret": cfg.TokenManager.JWTSecret,
"gatewaysvc": cfg.Reva.Address,
"skip_user_groups_in_token": cfg.SkipUserGroupsInToken,
"grpc_client_options": cfg.Reva.GetGRPCClientConfig(),
"multi_tenant_enabled": cfg.Commons.MultiTenantEnabled,
},
"grpc": map[string]any{
"network": cfg.GRPC.Protocol,
"address": cfg.GRPC.Addr,
"tls_settings": map[string]any{
"enabled": cfg.GRPC.TLS.Enabled,
"certificate": cfg.GRPC.TLS.Cert,
"key": cfg.GRPC.TLS.Key,
},
// TODO build services dynamically
"services": map[string]any{
"gateway": map[string]any{
"applicationauthsvc": cfg.AuthAppEndpoint,
// registries are located on the gateway
"authregistrysvc": localEndpoint,
"storageregistrysvc": localEndpoint,
"appregistrysvc": cfg.AppRegistryEndpoint,
// user metadata is located on the users services
"preferencessvc": cfg.UsersEndpoint,
"userprovidersvc": cfg.UsersEndpoint,
"groupprovidersvc": cfg.GroupsEndpoint,
"permissionssvc": cfg.PermissionsEndpoint,
// sharing is located on the sharing service
"usershareprovidersvc": cfg.SharingEndpoint,
"publicshareprovidersvc": cfg.SharingEndpoint,
"ocmshareprovidersvc": cfg.OCMEndpoint,
"ocminvitemanagersvc": cfg.OCMEndpoint,
"ocmproviderauthorizersvc": cfg.OCMEndpoint,
"ocmcoresvc": cfg.OCMEndpoint,
"use_common_space_root_share_logic": true,
"commit_share_to_storage_grant": cfg.CommitShareToStorageGrant,
"share_folder": cfg.ShareFolder, // ShareFolder is the location where to create shares in the recipient's storage provider.
// other
"disable_home_creation_on_login": cfg.DisableHomeCreationOnLogin,
"datagateway": strings.TrimRight(cfg.FrontendPublicURL, "/") + "/data",
"transfer_shared_secret": cfg.TransferSecret,
"transfer_expires": cfg.TransferExpires,
// cache and TTLs
"provider_cache_config": map[string]any{
"cache_store": cfg.Cache.ProviderCacheStore,
"cache_nodes": cfg.Cache.ProviderCacheNodes,
"cache_database": cfg.Cache.ProviderCacheDatabase,
"cache_table": "provider",
"cache_ttl": cfg.Cache.ProviderCacheTTL,
"disable_persistence": cfg.Cache.ProviderCacheDisablePersistence,
"cache_auth_username": cfg.Cache.ProviderCacheAuthUsername,
"cache_auth_password": cfg.Cache.ProviderCacheAuthPassword,
},
"create_personal_space_cache_config": map[string]any{
"cache_store": cfg.Cache.CreateHomeCacheStore,
"cache_nodes": cfg.Cache.CreateHomeCacheNodes,
"cache_database": cfg.Cache.CreateHomeCacheDatabase,
"cache_table": "create_personal_space",
"cache_ttl": cfg.Cache.CreateHomeCacheTTL,
"cache_disable_persistence": cfg.Cache.CreateHomeCacheDisablePersistence,
"cache_auth_username": cfg.Cache.CreateHomeCacheAuthUsername,
"cache_auth_password": cfg.Cache.CreateHomeCacheAuthPassword,
},
},
"authregistry": map[string]any{
"driver": "static",
"drivers": map[string]any{
"static": map[string]any{
"rules": map[string]any{
"appauth": cfg.AuthAppEndpoint,
"basic": cfg.AuthBasicEndpoint,
"machine": cfg.AuthMachineEndpoint,
"publicshares": cfg.StoragePublicLinkEndpoint,
"serviceaccounts": cfg.AuthServiceEndpoint,
"ocmshares": cfg.OCMEndpoint,
},
},
},
},
"storageregistry": map[string]any{
"driver": cfg.StorageRegistry.Driver,
"drivers": map[string]any{
"spaces": map[string]any{
"providers": spacesProviders(cfg, logger),
},
},
},
},
"interceptors": map[string]any{
"prometheus": map[string]any{
"namespace": "qsfera",
"subsystem": "gateway",
},
},
},
}
return rcfg
}
func spacesProviders(cfg *config.Config, logger log.Logger) map[string]map[string]any {
// if a list of rules is given it overrides the generated rules from below
if len(cfg.StorageRegistry.Rules) > 0 {
rules := map[string]map[string]any{}
for i := range cfg.StorageRegistry.Rules {
parts := strings.SplitN(cfg.StorageRegistry.Rules[i], "=", 2)
rules[parts[0]] = map[string]any{"address": parts[1]}
}
return rules
}
// check if the rules have to be read from a json file
if cfg.StorageRegistry.JSON != "" {
data, err := os.ReadFile(cfg.StorageRegistry.JSON)
if err != nil {
logger.Error().Err(err).Msg("Failed to read storage registry rules from JSON file: " + cfg.StorageRegistry.JSON)
return nil
}
var rules map[string]map[string]any
if err = json.Unmarshal(data, &rules); err != nil {
logger.Error().Err(err).Msg("Failed to unmarshal storage registry rules")
return nil
}
return rules
}
// generate rules based on default config
return map[string]map[string]any{
cfg.StorageUsersEndpoint: {
"providerid": cfg.StorageRegistry.StorageUsersMountID,
"spaces": map[string]any{
"personal": map[string]any{
"mount_point": "/users",
"path_template": "/users/{{.Space.Owner.Id.OpaqueId}}",
},
"project": map[string]any{
"mount_point": "/projects",
"path_template": "/projects/{{.Space.Name}}",
},
},
},
cfg.StorageSharesEndpoint: {
"providerid": utils.ShareStorageProviderID,
"spaces": map[string]any{
"virtual": map[string]any{
// The root of the share jail is mounted here
"mount_point": "/users/{{.CurrentUser.Id.OpaqueId}}/Shares",
},
"grant": map[string]any{
// Grants are relative to a space root that the gateway will determine with a stat
"mount_point": ".",
},
"mountpoint": map[string]any{
// The jail needs to be filled with mount points
// .Space.Name is a path relative to the mount point
"mount_point": "/users/{{.CurrentUser.Id.OpaqueId}}/Shares",
"path_template": "/users/{{.CurrentUser.Id.OpaqueId}}/Shares/{{.Space.Name}}",
},
},
},
// public link storage returns the mount id of the actual storage
cfg.StoragePublicLinkEndpoint: {
"providerid": utils.PublicStorageProviderID,
"spaces": map[string]any{
"grant": map[string]any{
"mount_point": ".",
},
"mountpoint": map[string]any{
"mount_point": "/public",
"path_template": "/public/{{.Space.Root.OpaqueId}}",
},
},
},
cfg.OCMEndpoint: {
"providerid": utils.OCMStorageProviderID,
"spaces": map[string]any{
"grant": map[string]any{
"mount_point": ".",
},
"mountpoint": map[string]any{
"mount_point": "/ocm",
"path_template": "/ocm/{{.Space.Root.OpaqueId}}",
},
},
},
// medatada storage not part of the global namespace
}
}
@@ -0,0 +1,50 @@
package debug
import (
"context"
"github.com/qsfera/server/pkg/log"
"github.com/qsfera/server/services/gateway/pkg/config"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Context context.Context
Config *config.Config
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
@@ -0,0 +1,40 @@
package debug
import (
"context"
"net/http"
"github.com/qsfera/server/pkg/checks"
"github.com/qsfera/server/pkg/handlers"
"github.com/qsfera/server/pkg/service/debug"
"github.com/qsfera/server/pkg/version"
)
// Server initializes the debug service and server.
func Server(opts ...Option) (*http.Server, error) {
options := newOptions(opts...)
readyHandlerConfiguration := handlers.NewCheckHandlerConfiguration().
WithLogger(options.Logger).
WithCheck("nats reachability", func(ctx context.Context) error {
if options.Config.Cache.ProviderCacheStore == "nats-js-kv" && len(options.Config.Cache.ProviderCacheNodes) > 0 {
return checks.NewNatsCheck(options.Config.Cache.ProviderCacheNodes[0])(ctx)
}
return nil
})
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.Ready(handlers.NewCheckHandler(readyHandlerConfiguration)),
//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
}