223 lines
5.4 KiB
Go
223 lines
5.4 KiB
Go
package actions
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"time"
|
|
|
|
"gitea.benny.dog/torjus/ezshare/certs"
|
|
"gitea.benny.dog/torjus/ezshare/pb"
|
|
"gitea.benny.dog/torjus/ezshare/server"
|
|
"gitea.benny.dog/torjus/ezshare/server/interceptors"
|
|
"gitea.benny.dog/torjus/ezshare/store"
|
|
"github.com/google/uuid"
|
|
"github.com/urfave/cli/v2"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials"
|
|
)
|
|
|
|
func ActionServe(c *cli.Context) error {
|
|
cfg, err := getConfig(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Read certificates
|
|
srvCertBytes, err := cfg.Server.GRPC.Certs.GetCertBytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
srvKeyBytes, err := cfg.Server.GRPC.Certs.GetKeyBytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
caCertBytes, err := cfg.Server.GRPC.CACerts.GetCertBytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
caKeyBytes, err := cfg.Server.GRPC.CACerts.GetKeyBytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Setup file store
|
|
s, fileCloseFunc, err := cfg.Server.FileStoreConfig.GetStore()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to initialize file store: %w", err)
|
|
}
|
|
defer fileCloseFunc()
|
|
|
|
// Setup user store
|
|
userStore, userCloseFunc, err := cfg.Server.UserStoreConfig.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); 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)
|
|
if err != nil {
|
|
return fmt.Errorf("error initializing certificate service: %w", err)
|
|
}
|
|
|
|
// Setup shutdown-handling
|
|
rootCtx, rootCancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
|
defer rootCancel()
|
|
|
|
// Used to initiate grpc shutdown
|
|
grpcCtx, grpcCancel := context.WithCancel(rootCtx)
|
|
defer grpcCancel()
|
|
|
|
// Cancelled once grpc is successfully shut down
|
|
grpcShutdownCtx, grpcShutdownCancel := context.WithCancel(context.Background())
|
|
defer grpcShutdownCancel()
|
|
|
|
// Start grpc server
|
|
go func() {
|
|
grpcAddr := cfg.Server.GRPC.ListenAddr
|
|
if c.IsSet("grpc-addr") {
|
|
grpcAddr = c.String("grpc-addr")
|
|
}
|
|
|
|
// Setup file-service
|
|
grpcFileServer := server.NewGRPCFileServiceServer(s)
|
|
grpcFileServer.Hostname = cfg.Server.Hostname
|
|
if c.IsSet("hostname") {
|
|
grpcFileServer.Hostname = c.String("hostname")
|
|
}
|
|
|
|
// Setup user-service
|
|
grpcUserServer := server.NewGRPCUserServiceServer(userStore, certSvc)
|
|
|
|
lis, err := net.Listen("tcp", grpcAddr)
|
|
if err != nil {
|
|
log.Printf("Unable to setup grpc listener: %s\n", err)
|
|
rootCancel()
|
|
}
|
|
srvCert, err := tls.X509KeyPair(srvCertBytes, srvKeyBytes)
|
|
if err != nil {
|
|
log.Printf("Unable load server certs: %s\n", err)
|
|
rootCancel()
|
|
}
|
|
certPool := x509.NewCertPool()
|
|
if !certPool.AppendCertsFromPEM(caCertBytes) {
|
|
log.Println("Unable to load CA cert")
|
|
rootCancel()
|
|
}
|
|
tlsConfig := &tls.Config{
|
|
Certificates: []tls.Certificate{srvCert},
|
|
ClientAuth: tls.RequireAnyClientCert,
|
|
ClientCAs: certPool,
|
|
}
|
|
creds := credentials.NewTLS(tlsConfig)
|
|
|
|
grpcServer := grpc.NewServer(
|
|
grpc.Creds(creds),
|
|
grpc.ChainUnaryInterceptor(interceptors.NewAuthInterceptor(userStore)),
|
|
)
|
|
pb.RegisterFileServiceServer(grpcServer, grpcFileServer)
|
|
pb.RegisterUserServiceServer(grpcServer, grpcUserServer)
|
|
|
|
// wait for cancel
|
|
go func() {
|
|
<-grpcCtx.Done()
|
|
grpcServer.GracefulStop()
|
|
}()
|
|
|
|
log.Printf("Starting grpc server")
|
|
if err = grpcServer.Serve(lis); err != nil {
|
|
log.Printf("GRPC Shutdown with error: %s\n", err)
|
|
rootCancel()
|
|
}
|
|
log.Println("GRPC Shutdown")
|
|
grpcShutdownCancel()
|
|
}()
|
|
|
|
httpCtx, httpCancel := context.WithCancel(rootCtx)
|
|
defer httpCancel()
|
|
|
|
httpShutdownCtx, httpShutdownCancel := context.WithCancel(context.Background())
|
|
defer httpShutdownCancel()
|
|
// Start http server
|
|
go func() {
|
|
httpAddr := ":8088"
|
|
if c.IsSet("http-addr") {
|
|
httpAddr = c.String("http-addr")
|
|
}
|
|
httpServer := server.NewHTTPSever(s, srvCertBytes, cfg.Server.GRPCEndpoint)
|
|
httpServer.Addr = httpAddr
|
|
|
|
// wait for cancel
|
|
go func() {
|
|
<-httpCtx.Done()
|
|
timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
httpServer.Shutdown(timeoutCtx)
|
|
}()
|
|
|
|
log.Printf("Starting http server")
|
|
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Printf("HTTP Server shutdown with error: %s\n", err)
|
|
rootCancel()
|
|
}
|
|
log.Println("HTTP Shutdown")
|
|
httpShutdownCancel()
|
|
}()
|
|
|
|
<-grpcShutdownCtx.Done()
|
|
<-httpShutdownCtx.Done()
|
|
return nil
|
|
}
|
|
|
|
func initializeUsers(us store.UserStore) error {
|
|
// TODO: Logging
|
|
userIDs, err := us.ListUsers()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(userIDs) != 0 {
|
|
return nil
|
|
}
|
|
|
|
// no users, create initial admin-user
|
|
log.Printf("No users in store. Creating admin-user.")
|
|
password := uuid.Must(uuid.NewRandom()).String()
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
admin := &pb.User{
|
|
Id: uuid.Must(uuid.NewRandom()).String(),
|
|
HashedPassword: hashedPassword,
|
|
Username: "admin",
|
|
UserRole: pb.User_ADMIN,
|
|
Active: true,
|
|
}
|
|
|
|
if err := us.StoreUser(admin); err != nil {
|
|
return err
|
|
}
|
|
log.Printf("user created with id %s:%s", admin.Username, password)
|
|
|
|
return nil
|
|
}
|