package actions import ( "context" "crypto/tls" "crypto/x509" "errors" "fmt" "io/fs" "net" "net/http" "net/url" "os" "os/signal" "strings" "time" "git.t-juice.club/torjus/ezshare/certs" "git.t-juice.club/torjus/ezshare/pb" "git.t-juice.club/torjus/ezshare/server" "git.t-juice.club/torjus/ezshare/server/interceptors" "git.t-juice.club/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 certificates, err := getCerts(c, serverLogger) if err != nil { return cli.Exit(fmt.Sprintf("Error getting certificates: %s", err), 1) } // 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 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(dataStore, serverLogger); err != nil { return fmt.Errorf("error initializing store: %w", err) } // Setup cert-service certSvc, err := certs.NewCertService(dataStore, certificates.caCert, certificates.caCertKey) 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 grpcFileServer.Logger = logger.Named("FILE") if c.IsSet("hostname") { grpcFileServer.Hostname = c.String("hostname") } // Setup cert-service certServiceServer := server.NewCertServiceServer(certSvc, dataStore, dataStore) certServiceServer.Logger = certLogger // Setup user-service grpcUserServer := server.NewGRPCUserServiceServer(dataStore, certSvc) grpcUserServer.Logger = logger.Named("USER") binaryServer := server.NewBinaryServiceServer(dataStore) 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(certificates.serverCert, certificates.serverKey) if err != nil { serverLogger.Errorw("Unable to load server certs.", "error", err) rootCancel() } certPool := x509.NewCertPool() if !certPool.AppendCertsFromPEM(certificates.caCert) { 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(dataStore, 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, dataStore, certificates.serverCert, 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 } type certBytes struct { caCert []byte caCertKey []byte serverCert []byte serverKey []byte } func getCerts(c *cli.Context, logger *zap.SugaredLogger) (*certBytes, error) { cfg, err := getConfig(c) if err != nil { return nil, err } cb := &certBytes{} caCertBytes, caCertErr := cfg.Server.GRPC.CACerts.GetCertBytes() caKeyBytes, caKeyErr := cfg.Server.GRPC.CACerts.GetKeyBytes() if caCertErr != nil || caKeyErr != nil { if errors.Is(caCertErr, fs.ErrNotExist) && errors.Is(caKeyErr, fs.ErrNotExist) { // Neither cert or key found, generate logger.Warn("Certificates not found. Generating.") priv, pub, err := certs.GenCACert() if err != nil { return nil, err } cb.caCert = pub cb.caCertKey = priv // Since we remade ca certs, any existing server certs are useless parsedUrl, err := url.Parse(cfg.Server.Hostname) if err != nil { return nil, fmt.Errorf("unable to parse hostname: %w", err) } host := parsedUrl.Host if strings.Contains(host, ":") { host, _, err = net.SplitHostPort(host) if err != nil { return nil, fmt.Errorf("unable to parse hostname: %w", err) } } if err != nil { return nil, fmt.Errorf("unable to generate certs due to unknown hostname") } priv, pub, err = certs.GenCert(host, cb.caCert, cb.caCertKey, []string{host}) if err != nil { return nil, fmt.Errorf("error creating server cert: %s", err) } pub, err = certs.ToPEM(pub, "CERTIFICATE") if err != nil { return nil, fmt.Errorf("error encoding server cert: %s", err) } priv, err = certs.ToPEM(priv, "EC PRIVATE KEY") if err != nil { return nil, fmt.Errorf("error encoding server cert: %s", err) } cb.serverCert = pub cb.serverKey = priv cb.caCert, err = certs.ToPEM(cb.caCert, "CERTIFICATE") if err != nil { return nil, fmt.Errorf("error encoding server cert: %s", err) } cb.caCertKey, err = certs.ToPEM(cb.caCertKey, "EC PRIVATE KEY") if err != nil { return nil, fmt.Errorf("error encoding server cert: %s", err) } // Write them to files if cfg.Server.GRPC.CACerts.CertificatePath != "" { f, err := os.Create(cfg.Server.GRPC.CACerts.CertificatePath) if err != nil { return nil, fmt.Errorf("error writing certificate: %w", err) } defer f.Close() if _, err := f.Write(cb.caCert); err != nil { return nil, fmt.Errorf("error writing certificate: %w", err) } logger.Infow("Wrote CACert.", "path", cfg.Server.GRPC.CACerts.CertificatePath) } if cfg.Server.GRPC.CACerts.CertificateKeyPath != "" { f, err := os.Create(cfg.Server.GRPC.CACerts.CertificateKeyPath) if err != nil { return nil, fmt.Errorf("error writing certificate: %w", err) } defer f.Close() if _, err := f.Write(cb.caCertKey); err != nil { return nil, fmt.Errorf("error writing certificate: %w", err) } logger.Infow("Wrote CACert key.", "path", cfg.Server.GRPC.CACerts.CertificateKeyPath) } if cfg.Server.GRPC.Certs.CertificateKeyPath != "" { f, err := os.Create(cfg.Server.GRPC.Certs.CertificateKeyPath) if err != nil { return nil, fmt.Errorf("error writing certificate: %w", err) } defer f.Close() if _, err := f.Write(cb.serverKey); err != nil { return nil, fmt.Errorf("error writing certificate: %w", err) } logger.Infow("Wrote server cert key.", "path", cfg.Server.GRPC.Certs.CertificateKeyPath) } if cfg.Server.GRPC.Certs.CertificatePath != "" { f, err := os.Create(cfg.Server.GRPC.Certs.CertificatePath) if err != nil { return nil, fmt.Errorf("error writing certificate: %w", err) } defer f.Close() if _, err := f.Write(cb.serverCert); err != nil { return nil, fmt.Errorf("error writing certificate: %w", err) } logger.Infow("Wrote server cert key.", "path", cfg.Server.GRPC.Certs.CertificatePath) } return cb, nil } else { if caCertErr != nil { return nil, caCertErr } return nil, caKeyErr } } srvCertBytes, err := cfg.Server.GRPC.Certs.GetCertBytes() if err != nil { return nil, err } srvKeyBytes, err := cfg.Server.GRPC.Certs.GetKeyBytes() if err != nil { return nil, err } return &certBytes{ caCert: caCertBytes, caCertKey: caKeyBytes, serverCert: srvCertBytes, serverKey: srvKeyBytes}, nil }