Add validation to config

This commit is contained in:
Torjus Håkestad 2021-12-08 09:42:12 +01:00
parent cc9687b907
commit 195310282f
5 changed files with 313 additions and 21 deletions

View File

@ -62,30 +62,23 @@ func ActionServe(c *cli.Context) error {
defer fileCloseFunc() defer fileCloseFunc()
// Setup user store // Setup user store
userStore, userCloseFunc, err := cfg.Server.UserStoreConfig.GetStore() dataStore, userCloseFunc, err := cfg.Server.DataStoreConfig.GetStore()
if err != nil { if err != nil {
return fmt.Errorf("unable to initialize user store: %w", err) return fmt.Errorf("unable to initialize user store: %w", err)
} }
defer userCloseFunc() defer userCloseFunc()
// Create initial admin-user if neccessary // 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) return fmt.Errorf("error initializing store: %w", err)
} }
// Setup cert store
// TODO: User proper store
certStore := store.NewMemoryStore()
// Setup cert-service // Setup cert-service
certSvc, err := certs.NewCertService(certStore, caCertBytes, caKeyBytes) certSvc, err := certs.NewCertService(dataStore, caCertBytes, caKeyBytes)
if err != nil { if err != nil {
return fmt.Errorf("error initializing certificate service: %w", err) return fmt.Errorf("error initializing certificate service: %w", err)
} }
// Setup binary store
binaryStore := store.NewMemoryStore()
// Setup shutdown-handling // Setup shutdown-handling
rootCtx, rootCancel := signal.NotifyContext(context.Background(), os.Interrupt) rootCtx, rootCancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer rootCancel() defer rootCancel()
@ -114,14 +107,14 @@ func ActionServe(c *cli.Context) error {
} }
// Setup cert-service // Setup cert-service
certServiceServer := server.NewCertServiceServer(certSvc, certStore, userStore) certServiceServer := server.NewCertServiceServer(certSvc, dataStore, dataStore)
certServiceServer.Logger = certLogger certServiceServer.Logger = certLogger
// Setup user-service // Setup user-service
grpcUserServer := server.NewGRPCUserServiceServer(userStore, certSvc) grpcUserServer := server.NewGRPCUserServiceServer(dataStore, certSvc)
grpcUserServer.Logger = logger.Named("USER") grpcUserServer.Logger = logger.Named("USER")
binaryServer := server.NewBinaryServiceServer(binaryStore) binaryServer := server.NewBinaryServiceServer(dataStore)
binaryServer.Logger = binsLogger binaryServer.Logger = binsLogger
lis, err := net.Listen("tcp", grpcAddr) lis, err := net.Listen("tcp", grpcAddr)
@ -150,7 +143,7 @@ func ActionServe(c *cli.Context) error {
grpc.MaxRecvMsgSize(100*1024*1024), grpc.MaxRecvMsgSize(100*1024*1024),
grpc.MaxSendMsgSize(100*1024*1024), grpc.MaxSendMsgSize(100*1024*1024),
grpc.Creds(creds), grpc.Creds(creds),
grpc.ChainUnaryInterceptor(interceptors.NewAuthInterceptor(userStore, certSvc, authLogger)), grpc.ChainUnaryInterceptor(interceptors.NewAuthInterceptor(dataStore, certSvc, authLogger)),
) )
pb.RegisterFileServiceServer(grpcServer, grpcFileServer) pb.RegisterFileServiceServer(grpcServer, grpcFileServer)
pb.RegisterUserServiceServer(grpcServer, grpcUserServer) pb.RegisterUserServiceServer(grpcServer, grpcUserServer)
@ -183,7 +176,7 @@ func ActionServe(c *cli.Context) error {
if c.IsSet("http-addr") { if c.IsSet("http-addr") {
httpAddr = c.String("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.Logger = httpLogger
httpServer.Addr = httpAddr httpServer.Addr = httpAddr

View File

@ -35,7 +35,7 @@ type ServerConfig struct {
LogLevel string `toml:"LogLevel"` LogLevel string `toml:"LogLevel"`
Hostname string `toml:"Hostname"` Hostname string `toml:"Hostname"`
GRPCEndpoint string `toml:"GRPCEndpoint"` GRPCEndpoint string `toml:"GRPCEndpoint"`
UserStoreConfig *ServerUserStoreConfig `toml:"UserStore"` DataStoreConfig *ServerDataStoreConfig `toml:"DataStore"`
FileStoreConfig *ServerFileStoreConfig `toml:"FileStore"` FileStoreConfig *ServerFileStoreConfig `toml:"FileStore"`
GRPC *ServerGRPCConfig `toml:"GRPC"` GRPC *ServerGRPCConfig `toml:"GRPC"`
HTTP *ServerHTTPConfig `toml:"HTTP"` HTTP *ServerHTTPConfig `toml:"HTTP"`
@ -47,7 +47,7 @@ type ServerFileStoreConfig struct {
BoltStoreConfig *BoltStoreConfig `toml:"Bolt"` BoltStoreConfig *BoltStoreConfig `toml:"Bolt"`
} }
type ServerUserStoreConfig struct { type ServerDataStoreConfig struct {
Type string `toml:"Type"` Type string `toml:"Type"`
BoltStoreConfig *BoltStoreConfig `toml:"Bolt"` BoltStoreConfig *BoltStoreConfig `toml:"Bolt"`
} }
@ -88,6 +88,8 @@ func FromDefault() *Config {
HTTP: &ServerHTTPConfig{ HTTP: &ServerHTTPConfig{
ListenAddr: ":8089", ListenAddr: ":8089",
}, },
DataStoreConfig: &ServerDataStoreConfig{},
FileStoreConfig: &ServerFileStoreConfig{},
}, },
Client: &ClientConfig{ Client: &ClientConfig{
Certs: &CertificatePaths{}, Certs: &CertificatePaths{},
@ -142,6 +144,87 @@ func FromDefaultLocations() (*Config, error) {
return nil, fmt.Errorf("config not found") 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 { func (c *Config) Location() string {
return c.location return c.location
} }
@ -293,7 +376,7 @@ func (sc *ServerFileStoreConfig) GetStore() (store.FileStore, func() error, erro
return nil, nil, fmt.Errorf("invalid store config") 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 } nopCloseFunc := func() error { return nil }
if strings.EqualFold(sc.Type, "bolt") { if strings.EqualFold(sc.Type, "bolt") {
s, err := store.NewBoltStore(sc.BoltStoreConfig.Path) s, err := store.NewBoltStore(sc.BoltStoreConfig.Path)

209
config/config_test.go Normal file
View File

@ -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)
}
})
}
})
}

View File

@ -34,14 +34,15 @@ Path = "/data/files.db"
# Required if store-type is filesystem # Required if store-type is filesystem
Dir = "/data" Dir = "/data"
[Server.UserStore] [Server.DataStore]
# What store to use for users # What store to use for users, certs and binaries
# Must be one of: memory, bolt # Must be one of: memory, bolt
# Required # Required
Type = "bolt" Type = "bolt"
[Server.UserStore.Bolt] [Server.DataStore.Bolt]
# Path to bolt database-file # Path to bolt database-file
# Required if Server.Datastore is bolt
Path = "/data/users.db" Path = "/data/users.db"
# GRPC Configuration # GRPC Configuration

View File

@ -17,6 +17,12 @@ type FileStore interface {
ListFiles() ([]*pb.ListFilesResponse_ListFileInfo, error) ListFiles() ([]*pb.ListFilesResponse_ListFileInfo, error)
} }
type DataStore interface {
BinaryStore
CertificateStore
UserStore
}
type CertificateStore interface { type CertificateStore interface {
GetCertificate(serial string) (*x509.Certificate, error) GetCertificate(serial string) (*x509.Certificate, error)
StoreCertificate(cert *x509.Certificate) error StoreCertificate(cert *x509.Certificate) error