diff --git a/actions/serve.go b/actions/serve.go index 6e1dc1a..f7c1ebb 100644 --- a/actions/serve.go +++ b/actions/serve.go @@ -62,30 +62,23 @@ func ActionServe(c *cli.Context) error { defer fileCloseFunc() // Setup user store - userStore, userCloseFunc, err := cfg.Server.UserStoreConfig.GetStore() + dataStore, userCloseFunc, err := cfg.Server.DataStoreConfig.GetStore() if err != nil { return fmt.Errorf("unable to initialize user store: %w", err) } defer userCloseFunc() // Create initial admin-user if neccessary - if err := initializeUsers(userStore, serverLogger); err != nil { + if err := initializeUsers(dataStore, serverLogger); err != nil { return fmt.Errorf("error initializing store: %w", err) } - // Setup cert store - // TODO: User proper store - certStore := store.NewMemoryStore() - // Setup cert-service - certSvc, err := certs.NewCertService(certStore, caCertBytes, caKeyBytes) + certSvc, err := certs.NewCertService(dataStore, caCertBytes, caKeyBytes) if err != nil { return fmt.Errorf("error initializing certificate service: %w", err) } - // Setup binary store - binaryStore := store.NewMemoryStore() - // Setup shutdown-handling rootCtx, rootCancel := signal.NotifyContext(context.Background(), os.Interrupt) defer rootCancel() @@ -114,14 +107,14 @@ func ActionServe(c *cli.Context) error { } // Setup cert-service - certServiceServer := server.NewCertServiceServer(certSvc, certStore, userStore) + certServiceServer := server.NewCertServiceServer(certSvc, dataStore, dataStore) certServiceServer.Logger = certLogger // Setup user-service - grpcUserServer := server.NewGRPCUserServiceServer(userStore, certSvc) + grpcUserServer := server.NewGRPCUserServiceServer(dataStore, certSvc) grpcUserServer.Logger = logger.Named("USER") - binaryServer := server.NewBinaryServiceServer(binaryStore) + binaryServer := server.NewBinaryServiceServer(dataStore) binaryServer.Logger = binsLogger lis, err := net.Listen("tcp", grpcAddr) @@ -150,7 +143,7 @@ func ActionServe(c *cli.Context) error { grpc.MaxRecvMsgSize(100*1024*1024), grpc.MaxSendMsgSize(100*1024*1024), grpc.Creds(creds), - grpc.ChainUnaryInterceptor(interceptors.NewAuthInterceptor(userStore, certSvc, authLogger)), + grpc.ChainUnaryInterceptor(interceptors.NewAuthInterceptor(dataStore, certSvc, authLogger)), ) pb.RegisterFileServiceServer(grpcServer, grpcFileServer) pb.RegisterUserServiceServer(grpcServer, grpcUserServer) @@ -183,7 +176,7 @@ func ActionServe(c *cli.Context) error { if c.IsSet("http-addr") { httpAddr = c.String("http-addr") } - httpServer := server.NewHTTPSever(s, binaryStore, srvCertBytes, cfg.Server.GRPCEndpoint) + httpServer := server.NewHTTPSever(s, dataStore, srvCertBytes, cfg.Server.GRPCEndpoint) httpServer.Logger = httpLogger httpServer.Addr = httpAddr diff --git a/config/config.go b/config/config.go index b822995..37cd6bb 100644 --- a/config/config.go +++ b/config/config.go @@ -35,7 +35,7 @@ type ServerConfig struct { LogLevel string `toml:"LogLevel"` Hostname string `toml:"Hostname"` GRPCEndpoint string `toml:"GRPCEndpoint"` - UserStoreConfig *ServerUserStoreConfig `toml:"UserStore"` + DataStoreConfig *ServerDataStoreConfig `toml:"DataStore"` FileStoreConfig *ServerFileStoreConfig `toml:"FileStore"` GRPC *ServerGRPCConfig `toml:"GRPC"` HTTP *ServerHTTPConfig `toml:"HTTP"` @@ -47,7 +47,7 @@ type ServerFileStoreConfig struct { BoltStoreConfig *BoltStoreConfig `toml:"Bolt"` } -type ServerUserStoreConfig struct { +type ServerDataStoreConfig struct { Type string `toml:"Type"` BoltStoreConfig *BoltStoreConfig `toml:"Bolt"` } @@ -88,6 +88,8 @@ func FromDefault() *Config { HTTP: &ServerHTTPConfig{ ListenAddr: ":8089", }, + DataStoreConfig: &ServerDataStoreConfig{}, + FileStoreConfig: &ServerFileStoreConfig{}, }, Client: &ClientConfig{ Certs: &CertificatePaths{}, @@ -142,6 +144,87 @@ func FromDefaultLocations() (*Config, error) { return nil, fmt.Errorf("config not found") } +func (c *Config) ValidForServer() error { + // Verify that grpc-endpoint is set + if c.Server.GRPCEndpoint == "" { + return fmt.Errorf("missing require config-value Server.GRPCEndpoint") + } + + // Verify loglevel + switch strings.ToUpper(c.Server.LogLevel) { + case "DEBUG", "INFO", "WARN", "ERROR", "FATAL": + break + default: + return fmt.Errorf("config-value Server.LogLevel is invalid") + } + + // Verify datastore config + switch strings.ToLower(c.Server.DataStoreConfig.Type) { + case "memory": + break + case "bolt": + if c.Server.DataStoreConfig.BoltStoreConfig == nil || c.Server.DataStoreConfig.BoltStoreConfig.Path == "" { + return fmt.Errorf("server datastore is bolt, missing required config value Server.DataStore.Bolt.Path") + } + default: + return fmt.Errorf("config-value Server.DataStore.Type is invalid") + } + + // Verify filestore config + switch strings.ToLower(c.Server.FileStoreConfig.Type) { + case "memory": + break + case "filesystem": + if c.Server.FileStoreConfig.FSStoreConfig == nil || c.Server.FileStoreConfig.FSStoreConfig.Dir == "" { + return fmt.Errorf("server datastore is bolt, missing required config value Server.FileStore.FSStore.Path") + } + case "bolt": + if c.Server.FileStoreConfig.BoltStoreConfig == nil || c.Server.FileStoreConfig.BoltStoreConfig.Path == "" { + return fmt.Errorf("server datastore is bolt, missing required config value Server.DataStore.Bolt.Path") + } + } + + // Verify grpc-config + if c.Server.GRPC.ListenAddr == "" { + return fmt.Errorf("missing required config-value Server.GRPC.ListenAddr") + } + if c.Server.GRPC.CACerts.CertificateKeyPath == "" { + // TODO: Maybe return custom error, so we can create certs if missing + return fmt.Errorf("missing require value Server.GRPC.CACerts.CertificateKeyPath") + } + if c.Server.GRPC.CACerts.CertificatePath == "" { + // TODO: Maybe return custom error, so we can create certs if missing + return fmt.Errorf("missing require value Server.GRPC.CACerts.CertificatePath") + } + if c.Server.GRPC.Certs.CertificatePath == "" { + // TODO: Maybe return custom error, so we can create certs if missing + return fmt.Errorf("missing require value Server.GRPC.Certs.CertificatePath") + } + if c.Server.GRPC.Certs.CertificateKeyPath == "" { + // TODO: Maybe return custom error, so we can create certs if missing + return fmt.Errorf("missing require value Server.GRPC.Certs.CertificateKeyPath") + } + + return nil +} +func (c *Config) ValidForClient() error { + if c.Client.Certs.CertificateKeyPath == "" { + return fmt.Errorf("missing required value Client.Certs.CertificateKeyPath") + } + if c.Client.Certs.CertificatePath == "" { + return fmt.Errorf("missing required value Client.Certs.CertificatePath") + } + if c.Client.DefaultServer == "" { + // TODO: Should probably have its own custom error + return fmt.Errorf("missing required value Client.DefaultServer") + } + if c.Client.ServerCertPath == "" { + // TODO: Should probably have its own custom error + return fmt.Errorf("missing required value Client.ServerCertPath") + } + return nil +} + func (c *Config) Location() string { return c.location } @@ -293,7 +376,7 @@ func (sc *ServerFileStoreConfig) GetStore() (store.FileStore, func() error, erro return nil, nil, fmt.Errorf("invalid store config") } -func (sc *ServerUserStoreConfig) GetStore() (store.UserStore, func() error, error) { +func (sc *ServerDataStoreConfig) GetStore() (store.DataStore, func() error, error) { nopCloseFunc := func() error { return nil } if strings.EqualFold(sc.Type, "bolt") { s, err := store.NewBoltStore(sc.BoltStoreConfig.Path) diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000..449c9f1 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,209 @@ +package config_test + +import ( + "strings" + "testing" + + "gitea.benny.dog/torjus/ezshare/config" +) + +var configStrExample = ` +######################## +# Server configuration # +######################## +[Server] +# Set server log-level +# Must be one of: DEBUG, INFO, WARN, ERROR +# Default: INFO +LogLevel = "INFO" + +# Server hostname +# Used for generating links +# Required +Hostname = "localhost" + +# Endpoint reachable by clients +# Fetched by clients for automatic setup +# Required +GRPCEndpoint = "localhost:50051" + +# File store configuration +[Server.FileStore] +# How server stores file +# Must be one of: filesystem, memory, bolt +# Required +Type = "bolt" + +[Server.FileStore.Bolt] +# Where the bolt-db is stored +# Required if store-type is bolt +Path = "/data/files.db" + +[Server.FileStore.Filesystem] +# Where files are stored +# Required if store-type is filesystem +Dir = "/data" + +[Server.DataStore] +# What store to use for users, certs and binaries +# Must be one of: memory, bolt +# Required +Type = "bolt" + +[Server.DataStore.Bolt] +# Path to bolt database-file +# Required if Server.Datastore is bolt +Path = "/data/users.db" + +# GRPC Configuration +[Server.GRPC] +# Address to listen to +# Default: :50051 +ListenAddr = ":50051" + +# GRPC Certificate Configuration +[Server.GRPC.CACerts] +# Path of PEM-encoded certificate file +CertificatePath = "/data/ca.pem" +# Path of PEM-encoded private key +# Must be of type ecdsa +CertificateKeyPath = "/data/ca.key" + +[Server.GRPC.Certs] +# Path of PEM-encoded certificate file +CertificatePath = "/data/server.pem" +# Path of PEM-encoded private key +# Must be of type ecdsa +CertificateKeyPath = "/data/server.key" + +[Server.HTTP] +# Address to listen to +# Default: :8089 +ListenAddr = ":8089" + + +######################## +# Client configuration # +######################## +[Client] +# Server used if not specified using command-line +DefaultServer = "localhost:50051" +# Path to PEM-encoder server-certificate +ServerCertPath = "/data/server.pem" + +[Client.Certs] +# Path of PEM-encoded certificate file +CertificatePath = "/data/client.pem" +# Path of PEM-encoded private key +# Must be of type ecdsa +CertificateKeyPath = "/data/client.key" +` + +var configStrValidClient = ` +[Client] +DefaultServer = "localhost:50051" +ServerCertPath = "/data/server.pem" +[Client.Certs] +CertificatePath = "/data/client.pem" +CertificateKeyPath = "/data/client.key" +` + +var configStrValidServerMinimal = ` +[Server] +LogLevel = "INFO" +Hostname = "localhost" +GRPCEndpoint = "localhost:50051" +[Server.FileStore] +Type = "memory" +[Server.DataStore] +Type = "memory" +[Server.GRPC] +ListenAddr = ":50051" +[Server.GRPC.CACerts] +CertificatePath = "/data/ca.pem" +CertificateKeyPath = "/data/ca.key" +[Server.GRPC.Certs] +CertificatePath = "/data/server.pem" +CertificateKeyPath = "/data/server.key" +[Server.HTTP] +ListenAddr = ":8089" +` + +var configStrInvalidServerMissingStoreConfig = ` +[Server] +LogLevel = "INFO" +Hostname = "localhost" +GRPCEndpoint = "localhost:50051" +[Server.FileStore] +Type = "bolt" +[Server.DataStore] +Type = "memory" +[Server.GRPC] +ListenAddr = ":50051" +[Server.GRPC.CACerts] +CertificatePath = "/data/ca.pem" +CertificateKeyPath = "/data/ca.key" +[Server.GRPC.Certs] +CertificatePath = "/data/server.pem" +CertificateKeyPath = "/data/server.key" +[Server.HTTP] +ListenAddr = ":8089" +` + +func TestConfig(t *testing.T) { + t.Run("TestValid", func(t *testing.T) { + + testCases := []struct { + Name string + ConfigString string + ValidForClient bool + ValidForServer bool + }{ + { + Name: "ExampleConfig", + ConfigString: configStrExample, + ValidForClient: true, + ValidForServer: true, + }, + { + Name: "ServerValidMinimal", + ConfigString: configStrValidServerMinimal, + ValidForServer: true, + }, + { + Name: "ClientValidMinimal", + ConfigString: configStrValidClient, + ValidForClient: true, + }, + { + Name: "ServerInvalidMissingStoreConfig", + ConfigString: configStrInvalidServerMissingStoreConfig, + }, + } + + for _, c := range testCases { + t.Run(c.Name, func(t *testing.T) { + sr := strings.NewReader(c.ConfigString) + cfg, err := config.FromReader(sr) + if err != nil { + t.Fatalf("Error reading config: %s", err) + } + clientErr := cfg.ValidForClient() + serverErr := cfg.ValidForServer() + + if c.ValidForClient && !(clientErr == nil) { + t.Errorf("Valid config ValidClientConfig returned wrong result: %s", clientErr) + } + if !c.ValidForClient && (clientErr == nil) { + t.Errorf("Invalid config ValidClientConfig returned wrong result: %s", clientErr) + } + if c.ValidForServer && !(serverErr == nil) { + t.Errorf("Valid config ValidServerConfig returned wrong result: %s", clientErr) + } + if !c.ValidForServer && (serverErr == nil) { + t.Errorf("Invalid config ValidServerConfig returned wrong result: %s", clientErr) + } + }) + } + }) +} diff --git a/ezshare.example.toml b/ezshare.example.toml index 584877d..d1b410e 100644 --- a/ezshare.example.toml +++ b/ezshare.example.toml @@ -34,14 +34,15 @@ Path = "/data/files.db" # Required if store-type is filesystem Dir = "/data" -[Server.UserStore] -# What store to use for users +[Server.DataStore] +# What store to use for users, certs and binaries # Must be one of: memory, bolt # Required Type = "bolt" -[Server.UserStore.Bolt] +[Server.DataStore.Bolt] # Path to bolt database-file +# Required if Server.Datastore is bolt Path = "/data/users.db" # GRPC Configuration diff --git a/store/store.go b/store/store.go index b4d5e48..f21eb3a 100644 --- a/store/store.go +++ b/store/store.go @@ -17,6 +17,12 @@ type FileStore interface { ListFiles() ([]*pb.ListFilesResponse_ListFileInfo, error) } +type DataStore interface { + BinaryStore + CertificateStore + UserStore +} + type CertificateStore interface { GetCertificate(serial string) (*x509.Certificate, error) StoreCertificate(cert *x509.Certificate) error