2021-05-01 12:45:37 +02:00
package cmd
import (
2024-06-13 01:20:46 +02:00
"context"
2022-05-22 18:53:47 +02:00
"errors"
2021-05-01 12:45:37 +02:00
"flag"
"fmt"
2022-05-13 21:51:41 +02:00
"io"
2022-05-22 18:53:47 +02:00
"io/fs"
2022-05-13 21:51:41 +02:00
"net"
"net/http"
"os"
"path"
2022-07-25 19:55:38 +02:00
"strings"
2022-05-13 21:51:41 +02:00
"time"
2024-06-13 01:20:46 +02:00
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
2024-02-29 16:05:47 +01:00
"golang.org/x/crypto/acme/autocert"
2024-06-13 01:20:46 +02:00
"github.com/netbirdio/netbird/signal/metrics"
2022-03-26 12:08:54 +01:00
"github.com/netbirdio/netbird/encryption"
"github.com/netbirdio/netbird/signal/proto"
"github.com/netbirdio/netbird/signal/server"
"github.com/netbirdio/netbird/util"
2024-02-29 16:05:47 +01:00
"github.com/netbirdio/netbird/version"
2021-05-01 12:45:37 +02:00
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"google.golang.org/grpc"
2021-07-22 15:23:24 +02:00
"google.golang.org/grpc/credentials"
2021-07-21 20:23:11 +02:00
"google.golang.org/grpc/keepalive"
2021-05-01 12:45:37 +02:00
)
2024-06-13 01:20:46 +02:00
const (
metricsPort = 9090
)
2021-05-01 12:45:37 +02:00
var (
2021-07-21 20:23:11 +02:00
signalPort int
signalLetsencryptDomain string
2021-08-13 08:46:30 +02:00
signalSSLDir string
2022-05-13 21:51:41 +02:00
defaultSignalSSLDir string
2022-07-25 19:55:38 +02:00
tlsEnabled bool
2021-07-21 20:23:11 +02:00
signalKaep = grpc . KeepaliveEnforcementPolicy ( keepalive . EnforcementPolicy {
MinTime : 5 * time . Second ,
PermitWithoutStream : true ,
} )
signalKasp = grpc . KeepaliveParams ( keepalive . ServerParameters {
MaxConnectionIdle : 15 * time . Second ,
MaxConnectionAgeGrace : 5 * time . Second ,
Time : 5 * time . Second ,
Timeout : 2 * time . Second ,
} )
2021-05-01 12:45:37 +02:00
2021-08-13 08:46:30 +02:00
runCmd = & cobra . Command {
Use : "run" ,
2022-07-25 19:55:38 +02:00
Short : "start NetBird Signal Server daemon" ,
PreRun : func ( cmd * cobra . Command , args [ ] string ) {
// detect whether user specified a port
userPort := cmd . Flag ( "port" ) . Changed
if signalLetsencryptDomain != "" {
tlsEnabled = true
}
if ! userPort {
// different defaults for signalPort
if tlsEnabled {
signalPort = 443
} else {
signalPort = 80
}
}
} ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2021-05-01 12:45:37 +02:00
flag . Parse ( )
2022-07-25 19:55:38 +02:00
2021-09-07 09:53:18 +02:00
err := util . InitLog ( logLevel , logFile )
if err != nil {
log . Fatalf ( "failed initializing log %v" , err )
}
2021-05-01 12:45:37 +02:00
2022-05-13 21:51:41 +02:00
if signalSSLDir == "" {
oldPath := "/var/lib/wiretrustee"
if migrateToNetbird ( oldPath , defaultSignalSSLDir ) {
if err := cpDir ( oldPath , defaultSignalSSLDir ) ; err != nil {
log . Fatal ( err )
}
}
}
2021-07-21 20:23:11 +02:00
var opts [ ] grpc . ServerOption
2022-07-25 19:55:38 +02:00
var certManager * autocert . Manager
if tlsEnabled {
// Let's encrypt enabled -> generate certificate automatically
certManager , err = encryption . CreateCertManager ( signalSSLDir , signalLetsencryptDomain )
if err != nil {
return err
2021-08-13 08:46:30 +02:00
}
2021-08-07 12:26:07 +02:00
transportCredentials := credentials . NewTLS ( certManager . TLSConfig ( ) )
2021-07-21 20:23:11 +02:00
opts = append ( opts , grpc . Creds ( transportCredentials ) )
}
2024-06-13 01:20:46 +02:00
metricsServer := metrics . NewServer ( metricsPort , "" )
if err != nil {
return fmt . Errorf ( "setup metrics: %v" , err )
}
opts = append ( opts , signalKaep , signalKasp , grpc . StatsHandler ( otelgrpc . NewServerHandler ( ) ) )
2021-07-21 20:23:11 +02:00
grpcServer := grpc . NewServer ( opts ... )
2024-06-13 01:20:46 +02:00
go func ( ) {
log . Infof ( "running metrics server: %s%s" , metricsServer . Addr , metricsServer . Endpoint )
if err := metricsServer . ListenAndServe ( ) ; ! errors . Is ( err , http . ErrServerClosed ) {
log . Fatalf ( "Failed to start metrics server: %v" , err )
}
} ( )
srv , err := server . NewServer ( metricsServer . Meter )
if err != nil {
return fmt . Errorf ( "creating signal server: %v" , err )
}
proto . RegisterSignalExchangeServer ( grpcServer , srv )
2021-07-21 20:23:11 +02:00
2022-07-25 19:55:38 +02:00
var compatListener net . Listener
if signalPort != 10000 {
// The Signal gRPC server was running on port 10000 previously. Old agents that are already connected to Signal
// are using port 10000. For compatibility purposes we keep running a 2nd gRPC server on port 10000.
compatListener , err = serveGRPC ( grpcServer , 10000 )
if err != nil {
return err
}
log . Infof ( "running gRPC backward compatibility server: %s" , compatListener . Addr ( ) . String ( ) )
2021-05-01 12:45:37 +02:00
}
2022-07-25 19:55:38 +02:00
var grpcListener net . Listener
var httpListener net . Listener
if tlsEnabled {
httpListener = certManager . Listener ( )
if signalPort == 443 {
// running gRPC and HTTP cert manager on the same port
serveHTTP ( httpListener , certManager . HTTPHandler ( grpcHandlerFunc ( grpcServer ) ) )
log . Infof ( "running HTTP server (LetsEncrypt challenge handler) and gRPC server on the same port: %s" , httpListener . Addr ( ) . String ( ) )
} else {
serveHTTP ( httpListener , certManager . HTTPHandler ( nil ) )
log . Infof ( "running HTTP server (LetsEncrypt challenge handler): %s" , httpListener . Addr ( ) . String ( ) )
}
2021-05-01 12:45:37 +02:00
}
2022-07-25 19:55:38 +02:00
if signalPort != 443 || ! tlsEnabled {
grpcListener , err = serveGRPC ( grpcServer , signalPort )
if err != nil {
return err
}
log . Infof ( "running gRPC server: %s" , grpcListener . Addr ( ) . String ( ) )
}
2024-02-29 16:05:47 +01:00
log . Infof ( "signal server version %s" , version . NetbirdVersion ( ) )
2022-07-25 19:55:38 +02:00
log . Infof ( "started Signal Service" )
2021-05-01 12:45:37 +02:00
SetupCloseHandler ( )
2022-07-25 19:55:38 +02:00
2021-07-21 20:23:11 +02:00
<- stopCh
2022-07-25 19:55:38 +02:00
if grpcListener != nil {
_ = grpcListener . Close ( )
log . Infof ( "stopped gRPC server" )
}
if httpListener != nil {
_ = httpListener . Close ( )
log . Infof ( "stopped HTTP server" )
}
if compatListener != nil {
_ = compatListener . Close ( )
log . Infof ( "stopped gRPC backward compatibility server" )
}
2024-06-13 01:20:46 +02:00
ctx , cancel := context . WithTimeout ( cmd . Context ( ) , 5 * time . Second )
defer cancel ( )
if err := metricsServer . Shutdown ( ctx ) ; err != nil {
log . Errorf ( "Failed to stop metrics server: %v" , err )
}
log . Infof ( "stopped metrics server" )
2022-07-25 19:55:38 +02:00
log . Infof ( "stopped Signal Service" )
return nil
2021-05-01 12:45:37 +02:00
} ,
}
)
2022-07-25 19:55:38 +02:00
func grpcHandlerFunc ( grpcServer * grpc . Server ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
grpcHeader := strings . HasPrefix ( r . Header . Get ( "Content-Type" ) , "application/grpc" ) ||
strings . HasPrefix ( r . Header . Get ( "Content-Type" ) , "application/grpc+proto" )
if r . ProtoMajor == 2 && grpcHeader {
grpcServer . ServeHTTP ( w , r )
}
} )
}
func notifyStop ( msg string ) {
select {
case stopCh <- 1 :
log . Error ( msg )
default :
// stop has been already called, nothing to report
}
}
func serveHTTP ( httpListener net . Listener , handler http . Handler ) {
go func ( ) {
err := http . Serve ( httpListener , handler )
if err != nil {
notifyStop ( fmt . Sprintf ( "failed running HTTP server %v" , err ) )
}
} ( )
}
func serveGRPC ( grpcServer * grpc . Server , port int ) ( net . Listener , error ) {
listener , err := net . Listen ( "tcp" , fmt . Sprintf ( ":%d" , port ) )
if err != nil {
return nil , err
}
go func ( ) {
err := grpcServer . Serve ( listener )
if err != nil {
notifyStop ( fmt . Sprintf ( "failed running gRPC server on port %d: %v" , port , err ) )
}
} ( )
return listener , nil
}
2022-05-13 21:51:41 +02:00
func cpFile ( src , dst string ) error {
var err error
var srcfd * os . File
var dstfd * os . File
var srcinfo os . FileInfo
if srcfd , err = os . Open ( src ) ; err != nil {
return err
}
defer srcfd . Close ( )
if dstfd , err = os . Create ( dst ) ; err != nil {
return err
}
defer dstfd . Close ( )
if _ , err = io . Copy ( dstfd , srcfd ) ; err != nil {
return err
}
if srcinfo , err = os . Stat ( src ) ; err != nil {
return err
}
return os . Chmod ( dst , srcinfo . Mode ( ) )
}
func copySymLink ( source , dest string ) error {
link , err := os . Readlink ( source )
if err != nil {
return err
}
return os . Symlink ( link , dest )
}
func cpDir ( src string , dst string ) error {
var err error
2022-12-04 13:22:21 +01:00
var fds [ ] os . DirEntry
2022-05-13 21:51:41 +02:00
var srcinfo os . FileInfo
if srcinfo , err = os . Stat ( src ) ; err != nil {
return err
}
if err = os . MkdirAll ( dst , srcinfo . Mode ( ) ) ; err != nil {
return err
}
2022-12-04 13:22:21 +01:00
if fds , err = os . ReadDir ( src ) ; err != nil {
2022-05-13 21:51:41 +02:00
return err
}
for _ , fd := range fds {
srcfp := path . Join ( src , fd . Name ( ) )
dstfp := path . Join ( dst , fd . Name ( ) )
fileInfo , err := os . Stat ( srcfp )
if err != nil {
log . Fatalf ( "Couldn't get fileInfo; %v" , err )
}
switch fileInfo . Mode ( ) & os . ModeType {
case os . ModeSymlink :
if err = copySymLink ( srcfp , dstfp ) ; err != nil {
log . Fatalf ( "Failed to copy from %s to %s; %v" , srcfp , dstfp , err )
}
case os . ModeDir :
if err = cpDir ( srcfp , dstfp ) ; err != nil {
log . Fatalf ( "Failed to copy from %s to %s; %v" , srcfp , dstfp , err )
}
default :
if err = cpFile ( srcfp , dstfp ) ; err != nil {
log . Fatalf ( "Failed to copy from %s to %s; %v" , srcfp , dstfp , err )
}
}
}
return nil
}
func migrateToNetbird ( oldPath , newPath string ) bool {
2022-05-22 18:53:47 +02:00
_ , errOld := os . Stat ( oldPath )
_ , errNew := os . Stat ( newPath )
2022-05-13 21:51:41 +02:00
2022-05-22 18:53:47 +02:00
if errors . Is ( errOld , fs . ErrNotExist ) || errNew == nil {
2022-05-13 21:51:41 +02:00
return false
}
return true
}
2021-05-01 12:45:37 +02:00
func init ( ) {
2022-07-25 19:55:38 +02:00
runCmd . PersistentFlags ( ) . IntVar ( & signalPort , "port" , 80 , "Server port to listen on (defaults to 443 if TLS is enabled, 80 otherwise" )
2022-05-13 21:51:41 +02:00
runCmd . Flags ( ) . StringVar ( & signalSSLDir , "ssl-dir" , defaultSignalSSLDir , "server ssl directory location. *Required only for Let's Encrypt certificates." )
2021-08-13 08:46:30 +02:00
runCmd . Flags ( ) . StringVar ( & signalLetsencryptDomain , "letsencrypt-domain" , "" , "a domain to issue Let's Encrypt certificate for. Enables TLS using Let's Encrypt. Will fetch and renew certificate, and run the server with TLS" )
2021-05-01 12:45:37 +02:00
}