package actions import ( "context" "crypto/tls" "crypto/x509" "fmt" "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" "go.uber.org/zap" "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 } logger := cfg.Server.GetLogger() serverLogger := logger.Named("SERV") authLogger := logger.Named("AUTH") httpLogger := logger.Named("HTTP") certLogger := logger.Named("CERT") binsLogger := logger.Named("BINS") // 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, 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) 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() // 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 grpcFileServer.Logger = logger.Named("FILE") if c.IsSet("hostname") { grpcFileServer.Hostname = c.String("hostname") } // Setup cert-service certServiceServer := server.NewCertServiceServer(certSvc, certStore, userStore) certServiceServer.Logger = certLogger // Setup user-service grpcUserServer := server.NewGRPCUserServiceServer(userStore, certSvc) grpcUserServer.Logger = logger.Named("USER") binaryServer := server.NewBinaryServiceServer(binaryStore) binaryServer.Logger = binsLogger lis, err := net.Listen("tcp", grpcAddr) if err != nil { serverLogger.Errorw("Unable to setup GRPC listener.", "error", err) rootCancel() } srvCert, err := tls.X509KeyPair(srvCertBytes, srvKeyBytes) if err != nil { serverLogger.Errorw("Unable to load server certs.", "error", err) rootCancel() } certPool := x509.NewCertPool() if !certPool.AppendCertsFromPEM(caCertBytes) { serverLogger.Errorw("Unable to load CA certs.") rootCancel() } tlsConfig := &tls.Config{ Certificates: []tls.Certificate{srvCert}, ClientAuth: tls.RequireAnyClientCert, ClientCAs: certPool, } creds := credentials.NewTLS(tlsConfig) grpcServer := grpc.NewServer( grpc.MaxRecvMsgSize(100*1024*1024), grpc.MaxSendMsgSize(100*1024*1024), grpc.Creds(creds), grpc.ChainUnaryInterceptor(interceptors.NewAuthInterceptor(userStore, certSvc, authLogger)), ) pb.RegisterFileServiceServer(grpcServer, grpcFileServer) pb.RegisterUserServiceServer(grpcServer, grpcUserServer) pb.RegisterCertificateServiceServer(grpcServer, certServiceServer) pb.RegisterBinaryServiceServer(grpcServer, binaryServer) // wait for cancel go func() { <-grpcCtx.Done() grpcServer.GracefulStop() }() serverLogger.Info("Starting GRPC server.") if err = grpcServer.Serve(lis); err != nil { serverLogger.Warnw("GRPC shutdown with error", "error", err) rootCancel() } serverLogger.Info("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, binaryStore, srvCertBytes, cfg.Server.GRPCEndpoint) httpServer.Logger = httpLogger httpServer.Addr = httpAddr // wait for cancel go func() { <-httpCtx.Done() timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() httpServer.Shutdown(timeoutCtx) }() serverLogger.Info("Starting HTTP server.") if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { serverLogger.Warnw("HTTP server shutdown with error.", "error", err) rootCancel() } serverLogger.Info("HTTP server shutdown.") httpShutdownCancel() }() <-grpcShutdownCtx.Done() <-httpShutdownCtx.Done() return nil } func initializeUsers(us store.UserStore, logger *zap.SugaredLogger) error { // TODO: Logging userIDs, err := us.ListUsers() if err != nil { return err } if len(userIDs) != 0 { return nil } // no users, create initial 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 } logger.Infow("Created admin user.", "username", admin.Username, "password", password) return nil }