Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Signal Service listen on a standard 443/80 port instead of 10000 #396

Merged
merged 4 commits into from
Jul 25, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Migrate Signal to port 80 or 443
  • Loading branch information
braginini committed Jul 24, 2022
commit 94c2023e6b5314acd82ce92d44f0cdecf7e10805
2 changes: 1 addition & 1 deletion encryption/letsencrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func CreateCertManager(datadir string, letsencryptDomain string) (*autocert.Mana
}
}

log.Infof("running with Let's encrypt with domain %s. Cert will be stored in %s", letsencryptDomain, certDir)
log.Infof("running with LetsEncrypt (%s). Cert will be stored in %s", letsencryptDomain, certDir)

certManager := &autocert.Manager{
Prompt: autocert.AcceptTOS,
Expand Down
11 changes: 11 additions & 0 deletions management/cmd/management.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ var (
Timeout: 2 * time.Second,
}

// TLS enabled:
// - HTTP 80 for LetsEncrypt
// - if --port not specified gRPC and HTTP servers on 443 (with multiplexing)
// - if --port=X specified then run gRPC and HTTP servers on X (with multiplexing)
// - if --port=80 forbid this (throw error, otherwise we need to overcomplicate the logic with multiplexing)
// TLS disabled:
// - if --port not specified gRPC and HTTP servers on 443 on 80 (with multiplexing)
// - if --port=X specified then run gRPC and HTTP servers on 443 on X (with multiplexing)
// Always run gRPC on port 33073 regardless of TLS to be backward compatible
// Remove HTTP port 33071 from the configuration.

mgmtCmd = &cobra.Command{
Use: "management",
Short: "start Netbird Management Server",
Expand Down
154 changes: 119 additions & 35 deletions signal/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"net/http"
"os"
"path"
"strings"
"time"

"github.com/netbirdio/netbird/encryption"
Expand All @@ -32,6 +31,7 @@ var (
signalLetsencryptDomain string
signalSSLDir string
defaultSignalSSLDir string
tlsEnabled bool

signalKaep = grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
MinTime: 5 * time.Second,
Expand All @@ -48,8 +48,25 @@ var (
runCmd = &cobra.Command{
Use: "run",
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 {
flag.Parse()

err := util.InitLog(logLevel, logFile)
if err != nil {
log.Fatalf("failed initializing log %v", err)
Expand All @@ -65,71 +82,138 @@ var (
}

var opts []grpc.ServerOption
var listener net.Listener
var httpListener net.Listener
var grpcListener net.Listener
var certManager *autocert.Manager
// Let's encrypt enabled -> generate certificate automatically
if signalLetsencryptDomain != "" {
var cMux cmux.CMux
cMux = nil
if tlsEnabled {
// Let's encrypt enabled -> generate certificate automatically
certManager, err = encryption.CreateCertManager(signalSSLDir, signalLetsencryptDomain)
if err != nil {
return err
}
transportCredentials := credentials.NewTLS(certManager.TLSConfig())
opts = append(opts, grpc.Creds(transportCredentials))

listener = certManager.Listener()
log.Infof("HTTP server listening on %s", listener.Addr())
if signalPort == 443 {
// the only case when we need multiplexing
cMux = cmux.New(certManager.Listener())
grpcListener = cMux.MatchWithWriters(
cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"),
cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc+proto"),
)
httpListener = cMux.Match(cmux.Any())
} else {
// separate ports for HTTP and gRPC, no multiplexing required
grpcListener, err = net.Listen("tcp", fmt.Sprintf(":%d", signalPort))
if err != nil {
return err
}
httpListener = certManager.Listener()
}
} else {
listener, err = net.Listen("tcp", fmt.Sprintf(":%d", signalPort))
grpcListener, err = net.Listen("tcp", fmt.Sprintf(":%d", signalPort))
if err != nil {
return err
}
httpListener = nil
}

cMux := cmux.New(listener)
grpcListener := cMux.MatchWithWriters(
cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"),
cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc+proto"),
)
httpListener := cMux.Match(cmux.HTTP1())

opts = append(opts, signalKaep, signalKasp)
grpcServer := grpc.NewServer(opts...)
proto.RegisterSignalExchangeServer(grpcServer, server.NewServer())

go grpcServer.Serve(grpcListener)
if certManager != nil {
go http.Serve(httpListener, certManager.HTTPHandler(nil))
var compatListener net.Listener
if signalPort != 10000 {
compatListener, err = serveCompatibilityGRPC(grpcServer)
if err != nil {
return err
}
}
serveGRPC(grpcServer, grpcListener)
if httpListener != nil {
serveHTTP(httpListener, certManager.HTTPHandler(nil))
}
if cMux != nil {
serveMux(cMux)
}

log.Infof("started Signal Service: %v", grpcListener.Addr())
log.Infof("started Signal Service")

SetupCloseHandler()

err = cMux.Serve()
if err != nil {
return err
}

<-stopCh
_ = listener.Close()
_ = grpcListener.Close()
if httpListener != nil {
_ = httpListener.Close()
}
if cMux != nil {
cMux.Close()
}
if compatListener != nil {
_ = compatListener.Close()
}
log.Infof("stopped Signal Service")

return nil
},
}
)

// grpcHandlerFunc returns a http.Handler that delegates to grpcServer on incoming gRPC
func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// TODO(tamird): point to merged gRPC code rather than a PR.
// This is a partial recreation of gRPC's internal checks https://github.com/grpc/grpc-go/pull/514/files#diff-95e9a25b738459a2d3030e1e6fa2a718R61
if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
grpcServer.ServeHTTP(w, r)
} else {
otherHandler.ServeHTTP(w, r)
func notifyStop(msg string) {
select {
case stopCh <- 1:
default:
}
log.Error(msg)
}

func serveMux(cMux cmux.CMux) {
log.Infof("running gRPC and HTTP server in a multiplex mode on port 443")
go func() {
err := cMux.Serve()
if err != nil {
notifyStop(fmt.Sprintf("failed running HTTP Mux server %v", err))
}
}()
}

func serveHTTP(httpListener net.Listener, handler http.Handler) {
log.Infof("running HTTP server: %s", httpListener.Addr().String())
go func() {
err := http.Serve(httpListener, handler)
if err != nil {
notifyStop(fmt.Sprintf("failed running HTTP server %v", err))
}
})
}()
}

// 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.
func serveCompatibilityGRPC(grpcServer *grpc.Server) (net.Listener, error) {
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", 10000))
if err != nil {
return nil, err
}
log.Infof("running gRPC backward compatibility server: %s", listener.Addr().String())
go func() {
err := grpcServer.Serve(listener)
if err != nil {
notifyStop(fmt.Sprintf("failed running compatibility gRPC server (port 10000) %v", err))
}
}()
return listener, nil
}

func serveGRPC(grpcServer *grpc.Server, grpcListener net.Listener) {
log.Infof("running gRPC server: %s", grpcListener.Addr().String())
go func() {
err := grpcServer.Serve(grpcListener)
if err != nil {
notifyStop(fmt.Sprintf("failed running gRPC server %v", err))
}
}()
}

func cpFile(src, dst string) error {
Expand Down Expand Up @@ -220,7 +304,7 @@ func migrateToNetbird(oldPath, newPath string) bool {
}

func init() {
runCmd.PersistentFlags().IntVar(&signalPort, "port", 80, "Server port to listen on (e.g. 80)")
runCmd.PersistentFlags().IntVar(&signalPort, "port", 80, "Server port to listen on (defaults to 443 if TLS is enabled, 80 otherwise")
runCmd.Flags().StringVar(&signalSSLDir, "ssl-dir", defaultSignalSSLDir, "server ssl directory location. *Required only for Let's Encrypt certificates.")
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")
}