2021-05-01 12:45:37 +02:00
package cmd
import (
2024-06-13 01:20:46 +02:00
"context"
2024-07-16 20:44:21 +02:00
"crypto/tls"
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
"net"
"net/http"
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
2024-07-16 20:44:21 +02:00
signalCertFile string
signalCertKey string
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 {
2024-07-18 12:15:14 +02:00
Use : "run" ,
Short : "start NetBird Signal Server daemon" ,
SilenceUsage : true ,
2022-07-25 19:55:38 +02:00
PreRun : func ( cmd * cobra . Command , args [ ] string ) {
2024-07-18 12:15:14 +02:00
err := util . InitLog ( logLevel , logFile )
if err != nil {
log . Fatalf ( "failed initializing log %v" , err )
}
2024-07-16 20:44:21 +02:00
flag . Parse ( )
2022-07-25 19:55:38 +02:00
// detect whether user specified a port
userPort := cmd . Flag ( "port" ) . Changed
2024-07-16 20:44:21 +02:00
tlsEnabled := false
if signalLetsencryptDomain != "" || ( signalCertFile != "" && signalCertKey != "" ) {
2022-07-25 19:55:38 +02:00
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
2024-07-18 12:15:14 +02:00
opts , certManager , err := getTLSConfigurations ( )
2021-09-07 09:53:18 +02:00
if err != nil {
2024-07-18 12:15:14 +02:00
return err
2021-07-21 20:23:11 +02:00
}
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
2024-07-16 20:44:21 +02:00
grpcRootHandler := grpcHandlerFunc ( grpcServer )
if certManager != nil {
2024-07-18 12:15:14 +02:00
startServerWithCertManager ( certManager , grpcRootHandler )
2021-05-01 12:45:37 +02:00
}
2024-07-18 12:15:14 +02:00
var compatListener net . Listener
var grpcListener net . Listener
var httpListener net . Listener
2024-07-16 20:44:21 +02:00
// If certManager is configured and signalPort == 443, then the gRPC server has already been started
if certManager == nil || signalPort != 443 {
2022-07-25 19:55:38 +02:00
grpcListener , err = serveGRPC ( grpcServer , signalPort )
if err != nil {
return err
}
log . Infof ( "running gRPC server: %s" , grpcListener . Addr ( ) . String ( ) )
}
2024-07-16 20:44:21 +02:00
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 ( ) )
}
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
} ,
}
)
2024-07-18 12:15:14 +02:00
func getTLSConfigurations ( ) ( [ ] grpc . ServerOption , * autocert . Manager , error ) {
var (
err error
certManager * autocert . Manager
tlsConfig * tls . Config
)
if signalLetsencryptDomain == "" && signalCertFile == "" && signalCertKey == "" {
log . Infof ( "running without TLS" )
return nil , nil , nil
}
if signalLetsencryptDomain != "" {
certManager , err = encryption . CreateCertManager ( signalSSLDir , signalLetsencryptDomain )
if err != nil {
return nil , certManager , err
}
tlsConfig = certManager . TLSConfig ( )
log . Infof ( "setting up TLS with LetsEncrypt." )
} else {
if signalCertFile == "" || signalCertKey == "" {
log . Errorf ( "both cert-file and cert-key must be provided when not using LetsEncrypt" )
return nil , certManager , errors . New ( "both cert-file and cert-key must be provided when not using LetsEncrypt" )
}
tlsConfig , err = loadTLSConfig ( signalCertFile , signalCertKey )
if err != nil {
log . Errorf ( "cannot load TLS credentials: %v" , err )
return nil , certManager , err
}
log . Infof ( "setting up TLS with custom certificates." )
}
transportCredentials := credentials . NewTLS ( tlsConfig )
return [ ] grpc . ServerOption { grpc . Creds ( transportCredentials ) } , certManager , err
}
func startServerWithCertManager ( certManager * autocert . Manager , grpcRootHandler http . Handler ) {
// a call to certManager.Listener() always creates a new listener so we do it once
httpListener := certManager . Listener ( )
if signalPort == 443 {
// running gRPC and HTTP cert manager on the same port
serveHTTP ( httpListener , certManager . HTTPHandler ( grpcRootHandler ) )
log . Infof ( "running HTTP server (LetsEncrypt challenge handler) and gRPC server on the same port: %s" , httpListener . Addr ( ) . String ( ) )
} else {
// Start the HTTP cert manager server separately
serveHTTP ( httpListener , certManager . HTTPHandler ( nil ) )
log . Infof ( "running HTTP server (LetsEncrypt challenge handler): %s" , httpListener . Addr ( ) . String ( ) )
}
}
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
}
2024-07-16 20:44:21 +02:00
func loadTLSConfig ( certFile string , certKey string ) ( * tls . Config , error ) {
// Load server's certificate and private key
serverCert , err := tls . LoadX509KeyPair ( certFile , certKey )
if err != nil {
return nil , err
}
// NewDefaultAppMetrics the credentials and return it
config := & tls . Config {
Certificates : [ ] tls . Certificate { serverCert } ,
ClientAuth : tls . NoClientCert ,
NextProtos : [ ] string {
"h2" , "http/1.1" , // enable HTTP/2
} ,
}
return config , nil
}
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" )
2024-07-16 20:44:21 +02:00
runCmd . Flags ( ) . StringVar ( & signalCertFile , "cert-file" , "" , "Location of your SSL certificate. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect" )
runCmd . Flags ( ) . StringVar ( & signalCertKey , "cert-key" , "" , "Location of your SSL certificate private key. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect" )
2021-05-01 12:45:37 +02:00
}