ezshare/actions/serve.go

367 lines
10 KiB
Go

package actions
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/fs"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"strings"
"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
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
}