Serve Management gRPC and HTTP on a single 80/443 port (#400)

This PR is a part of an effort to use standard ports (443 or 80) that are usually allowed by default in most of the environments.

Right now Management Service runs the Let'sEncrypt manager on port 443, HTTP API server on port 33071,
and a gRPC server on port 33073. There are three separate listeners.
This PR combines these listeners into one.
With this change, the HTTP and gRPC server runs on either 443 with TLS or 80 without TLS
by default (no --port specified).
Let's Encrypt manager always runs on port 443 if enabled.
The backward compatibility server runs on port 33073 (with TLS or without).
HTTP port 33071 is obsolete and not used anymore.

Newly installed agents will connect to port 443 by default instead of port 33073 if not specified otherwise.
This commit is contained in:
Misha Bragin 2022-07-29 20:37:09 +02:00 committed by GitHub
parent 67ddaade58
commit 966661fe91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 274 additions and 263 deletions

View File

@ -22,7 +22,7 @@ func ManagementURLDefault() *url.URL {
}
func init() {
managementURL, err := parseURL("Management URL", "https://api.wiretrustee.com:33073")
managementURL, err := parseURL("Management URL", "https://api.wiretrustee.com:443")
if err != nil {
panic(err)
}

2
go.mod
View File

@ -39,6 +39,7 @@ require (
github.com/rs/xid v1.3.0
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/stretchr/testify v1.7.1
golang.org/x/net v0.0.0-20220513224357-95641704303c
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467
)
@ -96,7 +97,6 @@ require (
github.com/yuin/goldmark v1.4.1 // indirect
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/net v0.0.0-20220513224357-95641704303c // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 // indirect
golang.org/x/tools v0.1.10 // indirect

View File

@ -1,21 +1,25 @@
package cmd
import (
"context"
"crypto/tls"
"errors"
"flag"
"fmt"
httpapi "github.com/netbirdio/netbird/management/server/http"
"golang.org/x/crypto/acme/autocert"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"io"
"io/fs"
"io/ioutil"
"net"
"net/http"
"os"
"path"
"strings"
"time"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/http"
"github.com/netbirdio/netbird/management/server/idp"
"github.com/netbirdio/netbird/util"
@ -28,11 +32,16 @@ import (
"google.golang.org/grpc/keepalive"
)
// ManagementLegacyPort is the port that was used before by the Management gRPC server.
// It is used for backward compatibility now.
const ManagementLegacyPort = 33073
var (
mgmtPort int
mgmtLetsencryptDomain string
certFile string
certKey string
config *server.Config
kaep = keepalive.EnforcementPolicy{
MinTime: 15 * time.Second,
@ -46,47 +55,57 @@ 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",
Run: func(cmd *cobra.Command, args []string) {
Short: "start NetBird Management Server",
PreRunE: func(cmd *cobra.Command, args []string) error {
// detect whether user specified a port
userPort := cmd.Flag("port").Changed
var err error
config, err = loadMgmtConfig(mgmtConfig)
if err != nil {
return fmt.Errorf("failed reading provided config file: %s: %v", mgmtConfig, err)
}
tlsEnabled := false
if mgmtLetsencryptDomain != "" || (config.HttpConfig.CertFile != "" && config.HttpConfig.CertKey != "") {
tlsEnabled = true
}
if !userPort {
// different defaults for port when tls enabled/disabled
if tlsEnabled {
mgmtPort = 443
} else {
mgmtPort = 80
}
}
return nil
},
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)
return fmt.Errorf("failed initializing log %v", err)
}
err = handleRebrand(cmd)
if err != nil {
log.Fatalf("failed to migrate files %v", err)
}
config, err := loadMgmtConfig(mgmtConfig)
if err != nil {
log.Fatalf("failed reading provided config file: %s: %v", mgmtConfig, err)
return fmt.Errorf("failed to migrate files %v", err)
}
if _, err = os.Stat(config.Datadir); os.IsNotExist(err) {
err = os.MkdirAll(config.Datadir, os.ModeDir)
if err != nil {
log.Fatalf("failed creating datadir: %s: %v", config.Datadir, err)
return fmt.Errorf("failed creating datadir: %s: %v", config.Datadir, err)
}
}
store, err := server.NewStore(config.Datadir)
if err != nil {
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
return fmt.Errorf("failed creating Store: %s: %v", config.Datadir, err)
}
peersUpdateManager := server.NewPeersUpdateManager()
@ -94,85 +113,181 @@ var (
if config.IdpManagerConfig != nil {
idpManager, err = idp.NewManager(*config.IdpManagerConfig)
if err != nil {
log.Fatalln("failed retrieving a new idp manager with err: ", err)
return fmt.Errorf("failed retrieving a new idp manager with err: %v", err)
}
}
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager)
if err != nil {
log.Fatalln("failed build default manager: ", err)
return fmt.Errorf("failed to build default manager: %v", err)
}
var opts []grpc.ServerOption
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
var httpServer *http.Server
gRPCOpts := []grpc.ServerOption{grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp)}
var certManager *autocert.Manager
var tlsConfig *tls.Config
tlsEnabled := false
if config.HttpConfig.LetsEncryptDomain != "" {
// automatically generate a new certificate with Let's Encrypt
certManager, err := encryption.CreateCertManager(config.Datadir, config.HttpConfig.LetsEncryptDomain)
certManager, err = encryption.CreateCertManager(config.Datadir, config.HttpConfig.LetsEncryptDomain)
if err != nil {
log.Fatalf("failed creating Let's Encrypt cert manager: %v", err)
return fmt.Errorf("failed creating LetsEncrypt cert manager: %v", err)
}
transportCredentials := credentials.NewTLS(certManager.TLSConfig())
opts = append(opts, grpc.Creds(transportCredentials))
httpServer = http.NewHttpsServer(config.HttpConfig, certManager, accountManager)
gRPCOpts = append(gRPCOpts, grpc.Creds(transportCredentials))
tlsEnabled = true
} else if config.HttpConfig.CertFile != "" && config.HttpConfig.CertKey != "" {
// use provided certificate
tlsConfig, err := loadTLSConfig(config.HttpConfig.CertFile, config.HttpConfig.CertKey)
tlsConfig, err = loadTLSConfig(config.HttpConfig.CertFile, config.HttpConfig.CertKey)
if err != nil {
log.Fatal("cannot load TLS credentials: ", err)
log.Errorf("cannot load TLS credentials: %v", err)
return err
}
transportCredentials := credentials.NewTLS(tlsConfig)
opts = append(opts, grpc.Creds(transportCredentials))
httpServer = http.NewHttpsServerWithTLSConfig(config.HttpConfig, tlsConfig, accountManager)
} else {
// start server without SSL
httpServer = http.NewHttpServer(config.HttpConfig, accountManager)
gRPCOpts = append(gRPCOpts, grpc.Creds(transportCredentials))
tlsEnabled = true
}
opts = append(opts, grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
grpcServer := grpc.NewServer(opts...)
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
server, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager)
httpAPIHandler, err := httpapi.APIHandler(accountManager,
config.HttpConfig.AuthIssuer, config.HttpConfig.AuthAudience, config.HttpConfig.AuthKeysLocation)
if err != nil {
log.Fatalf("failed creating new server: %v", err)
return fmt.Errorf("failed creating HTTP API handler: %v", err)
}
mgmtProto.RegisterManagementServiceServer(grpcServer, server)
log.Printf("started server: localhost:%v", mgmtPort)
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", mgmtPort))
gRPCAPIHandler := grpc.NewServer(gRPCOpts...)
srv, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager)
if err != nil {
log.Fatalf("failed to listen: %v", err)
return fmt.Errorf("failed creating gRPC API handler: %v", err)
}
mgmtProto.RegisterManagementServiceServer(gRPCAPIHandler, srv)
go func() {
if err = grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve gRpc server: %v", err)
}
}()
go func() {
err = httpServer.Start()
var compatListener net.Listener
if mgmtPort != ManagementLegacyPort {
// The Management gRPC server was running on port 33073 previously. Old agents that are already connected to it
// are using port 33073. For compatibility purposes we keep running a 2nd gRPC server on port 33073.
compatListener, err = serveGRPC(gRPCAPIHandler, ManagementLegacyPort)
if err != nil {
log.Fatalf("failed to serve http server: %v", err)
return err
}
}()
log.Infof("running gRPC backward compatibility server: %s", compatListener.Addr().String())
}
rootHandler := handlerFunc(gRPCAPIHandler, httpAPIHandler)
var listener net.Listener
if certManager != nil {
// a call to certManager.Listener() always creates a new listener so we do it once
cml := certManager.Listener()
if mgmtPort == 443 {
// CertManager, HTTP and gRPC API all on the same port
rootHandler = certManager.HTTPHandler(rootHandler)
listener = cml
} else {
listener, err = tls.Listen("tcp", fmt.Sprintf(":%d", mgmtPort), certManager.TLSConfig())
if err != nil {
return fmt.Errorf("failed creating TLS listener on port %d: %v", mgmtPort, err)
}
log.Infof("running HTTP server (LetsEncrypt challenge handler): %s", cml.Addr().String())
serveHTTP(cml, certManager.HTTPHandler(nil))
}
} else if tlsConfig != nil {
listener, err = tls.Listen("tcp", fmt.Sprintf(":%d", mgmtPort), tlsConfig)
if err != nil {
return fmt.Errorf("failed creating TLS listener on port %d: %v", mgmtPort, err)
}
} else {
listener, err = net.Listen("tcp", fmt.Sprintf(":%d", mgmtPort))
if err != nil {
return fmt.Errorf("failed creating TCP listener on port %d: %v", mgmtPort, err)
}
}
log.Infof("running HTTP server and gRPC server on the same port: %s", listener.Addr().String())
serveGRPCWithHTTP(listener, rootHandler, tlsEnabled)
SetupCloseHandler()
<-stopCh
log.Println("Receive signal to stop running Management server")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err = httpServer.Stop(ctx)
if err != nil {
log.Fatalf("failed stopping the http server %v", err)
}
grpcServer.Stop()
<-stopCh
_ = listener.Close()
if certManager != nil {
_ = certManager.Listener().Close()
}
gRPCAPIHandler.Stop()
log.Infof("stopped Management Service")
return nil
},
}
)
func notifyStop(msg string) {
select {
case stopCh <- 1:
log.Error(msg)
default:
// stop has been already called, nothing to report
}
}
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
}
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 serveGRPCWithHTTP(listener net.Listener, handler http.Handler, tlsEnabled bool) {
go func() {
var err error
if tlsEnabled {
err = http.Serve(listener, handler)
} else {
// the following magic is needed to support HTTP2 without TLS
// and still share a single port between gRPC and HTTP APIs
h1s := &http.Server{
Handler: h2c.NewHandler(handler, &http2.Server{}),
}
err = h1s.Serve(listener)
}
if err != nil {
select {
case stopCh <- 1:
log.Errorf("failed to serve HTTP and gRPC server: %v", err)
default:
// stop has been already called, nothing to report
}
}
}()
}
func handlerFunc(gRPCHandler *grpc.Server, httpHandler http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
grpcHeader := strings.HasPrefix(request.Header.Get("Content-Type"), "application/grpc") ||
strings.HasPrefix(request.Header.Get("Content-Type"), "application/grpc+proto")
fmt.Println(grpcHeader)
if request.ProtoMajor == 2 && grpcHeader {
gRPCHandler.ServeHTTP(writer, request)
} else {
httpHandler.ServeHTTP(writer, request)
}
})
}
func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
config := &server.Config{}
_, err := util.ReadJson(mgmtConfigPath, config)

View File

@ -60,7 +60,7 @@ func init() {
oldDefaultMgmtConfig = oldDefaultMgmtConfigDir + "/management.json"
oldDefaultLogFile = oldDefaultLogDir + "/management.log"
mgmtCmd.Flags().IntVar(&mgmtPort, "port", 33073, "server port to listen on")
mgmtCmd.Flags().IntVar(&mgmtPort, "port", 80, "server port to listen on (defaults to 443 if TLS is enabled, 80 otherwise")
mgmtCmd.Flags().StringVar(&mgmtDataDir, "datadir", defaultMgmtDataDir, "server data directory location")
mgmtCmd.Flags().StringVar(&mgmtConfig, "config", defaultMgmtConfig, "Netbird config file location. Config params specified via command line (e.g. datadir) have a precedence over configuration from this file")
mgmtCmd.Flags().StringVar(&mgmtLetsencryptDomain, "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")

View File

@ -49,7 +49,6 @@ type HttpServerConfig struct {
CertFile string
//CertKey is the location of the certificate private key
CertKey string
Address string
// AuthAudience identifies the recipients that the JWT is intended for (aud in JWT)
AuthAudience string
// AuthIssuer identifies principal that issued the JWT.

View File

@ -18,8 +18,8 @@ import (
"google.golang.org/grpc/status"
)
// Server an instance of a Management server
type Server struct {
// GRPCServer an instance of a Management gRPC API server
type GRPCServer struct {
accountManager AccountManager
wgKey wgtypes.Key
proto.UnimplementedManagementServiceServer
@ -30,7 +30,7 @@ type Server struct {
}
// NewServer creates a new Management server
func NewServer(config *Config, accountManager AccountManager, peersUpdateManager *PeersUpdateManager, turnCredentialsManager TURNCredentialsManager) (*Server, error) {
func NewServer(config *Config, accountManager AccountManager, peersUpdateManager *PeersUpdateManager, turnCredentialsManager TURNCredentialsManager) (*GRPCServer, error) {
key, err := wgtypes.GeneratePrivateKey()
if err != nil {
return nil, err
@ -50,7 +50,7 @@ func NewServer(config *Config, accountManager AccountManager, peersUpdateManager
log.Debug("unable to use http config to create new jwt middleware")
}
return &Server{
return &GRPCServer{
wgKey: key,
// peerKey -> event channel
peersUpdateManager: peersUpdateManager,
@ -61,7 +61,7 @@ func NewServer(config *Config, accountManager AccountManager, peersUpdateManager
}, nil
}
func (s *Server) GetServerKey(ctx context.Context, req *proto.Empty) (*proto.ServerKeyResponse, error) {
func (s *GRPCServer) GetServerKey(ctx context.Context, req *proto.Empty) (*proto.ServerKeyResponse, error) {
// todo introduce something more meaningful with the key expiration/rotation
now := time.Now().Add(24 * time.Hour)
secs := int64(now.Second())
@ -76,7 +76,7 @@ func (s *Server) GetServerKey(ctx context.Context, req *proto.Empty) (*proto.Ser
// Sync validates the existence of a connecting peer, sends an initial state (all available for the connecting peers) and
// notifies the connected peer of any updates (e.g. new peers under the same account)
func (s *Server) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_SyncServer) error {
func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_SyncServer) error {
log.Debugf("Sync request from peer %s", req.WgPubKey)
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
@ -150,7 +150,7 @@ func (s *Server) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_S
}
}
func (s *Server) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Peer, error) {
func (s *GRPCServer) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Peer, error) {
var (
reqSetupKey string
userId string
@ -245,7 +245,7 @@ func (s *Server) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Pe
// In case it is, the login is successful
// In case it isn't, the endpoint checks whether setup key is provided within the request and tries to register a peer.
// In case of the successful registration login is also successful
func (s *Server) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
log.Debugf("Login request from peer %s", req.WgPubKey)
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
@ -429,12 +429,12 @@ func toSyncResponse(config *Config, peer *Peer, peers []*Peer, turnCredentials *
}
// IsHealthy indicates whether the service is healthy
func (s *Server) IsHealthy(ctx context.Context, req *proto.Empty) (*proto.Empty, error) {
func (s *GRPCServer) IsHealthy(ctx context.Context, req *proto.Empty) (*proto.Empty, error) {
return &proto.Empty{}, nil
}
// sendInitialSync sends initial proto.SyncResponse to the peer requesting synchronization
func (s *Server) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.ManagementService_SyncServer) error {
func (s *GRPCServer) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.ManagementService_SyncServer) error {
networkMap, err := s.accountManager.GetNetworkMap(peer.Key)
if err != nil {
log.Warnf("error getting a list of peers for a peer %s", peer.Key)
@ -472,7 +472,7 @@ func (s *Server) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.Mana
// GetDeviceAuthorizationFlow returns a device authorization flow information
// This is used for initiating an Oauth 2 device authorization grant flow
// which will be used by our clients to Login
func (s *Server) GetDeviceAuthorizationFlow(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
func (s *GRPCServer) GetDeviceAuthorizationFlow(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
peerKey, err := wgtypes.ParseKey(req.GetWgPubKey())
if err != nil {
errMSG := fmt.Sprintf("error while parsing peer's Wireguard public key %s on GetDeviceAuthorizationFlow request.", req.WgPubKey)

View File

@ -1,4 +1,4 @@
package handler
package http
import (
"encoding/json"

View File

@ -1,4 +1,4 @@
package handler
package http
import (
"bytes"

View File

@ -0,0 +1,64 @@
package http
import (
"github.com/gorilla/mux"
s "github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/http/middleware"
"github.com/rs/cors"
"net/http"
)
// APIHandler creates the Management service HTTP API handler registering all the available endpoints.
func APIHandler(accountManager s.AccountManager, authIssuer string, authAudience string, authKeysLocation string) (http.Handler, error) {
jwtMiddleware, err := middleware.NewJwtMiddleware(
authIssuer,
authAudience,
authKeysLocation,
)
if err != nil {
return nil, err
}
corsMiddleware := cors.AllowAll()
acMiddleware := middleware.NewAccessControll(
authAudience,
accountManager.IsUserAdmin)
apiHandler := mux.NewRouter()
apiHandler.Use(jwtMiddleware.Handler, corsMiddleware.Handler, acMiddleware.Handler)
groupsHandler := NewGroups(accountManager, authAudience)
rulesHandler := NewRules(accountManager, authAudience)
peersHandler := NewPeers(accountManager, authAudience)
keysHandler := NewSetupKeysHandler(accountManager, authAudience)
userHandler := NewUserHandler(accountManager, authAudience)
apiHandler.HandleFunc("/api/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
apiHandler.HandleFunc("/api/peers/{id}", peersHandler.HandlePeer).
Methods("GET", "PUT", "DELETE", "OPTIONS")
apiHandler.HandleFunc("/api/users", userHandler.GetUsers).Methods("GET", "OPTIONS")
apiHandler.HandleFunc("/api/setup-keys", keysHandler.GetKeys).Methods("GET", "POST", "OPTIONS")
apiHandler.HandleFunc("/api/setup-keys/{id}", keysHandler.HandleKey).Methods("GET", "PUT", "OPTIONS")
apiHandler.HandleFunc("/api/setup-keys", keysHandler.GetKeys).Methods("POST", "OPTIONS")
apiHandler.HandleFunc("/api/setup-keys/{id}", keysHandler.HandleKey).
Methods("GET", "PUT", "DELETE", "OPTIONS")
apiHandler.HandleFunc("/api/rules", rulesHandler.GetAllRulesHandler).Methods("GET", "OPTIONS")
apiHandler.HandleFunc("/api/rules", rulesHandler.CreateRuleHandler).Methods("POST", "OPTIONS")
apiHandler.HandleFunc("/api/rules/{id}", rulesHandler.UpdateRuleHandler).Methods("PUT", "OPTIONS")
apiHandler.HandleFunc("/api/rules/{id}", rulesHandler.PatchRuleHandler).Methods("PATCH", "OPTIONS")
apiHandler.HandleFunc("/api/rules/{id}", rulesHandler.GetRuleHandler).Methods("GET", "OPTIONS")
apiHandler.HandleFunc("/api/rules/{id}", rulesHandler.DeleteRuleHandler).Methods("DELETE", "OPTIONS")
apiHandler.HandleFunc("/api/groups", groupsHandler.GetAllGroupsHandler).Methods("GET", "OPTIONS")
apiHandler.HandleFunc("/api/groups", groupsHandler.CreateGroupHandler).Methods("POST", "OPTIONS")
apiHandler.HandleFunc("/api/groups/{id}", groupsHandler.UpdateGroupHandler).Methods("PUT", "OPTIONS")
apiHandler.HandleFunc("/api/groups/{id}", groupsHandler.PatchGroupHandler).Methods("PATCH", "OPTIONS")
apiHandler.HandleFunc("/api/groups/{id}", groupsHandler.GetGroupHandler).Methods("GET", "OPTIONS")
apiHandler.HandleFunc("/api/groups/{id}", groupsHandler.DeleteGroupHandler).Methods("DELETE", "OPTIONS")
return apiHandler, nil
}

View File

@ -1,4 +1,4 @@
package handler
package http
import (
"encoding/json"

View File

@ -1,4 +1,4 @@
package handler
package http
import (
"encoding/json"

View File

@ -1,4 +1,4 @@
package handler
package http
import (
"encoding/json"

View File

@ -1,4 +1,4 @@
package handler
package http
import (
"bytes"

View File

@ -1,167 +0,0 @@
package http
import (
"context"
"crypto/tls"
"net/http"
"time"
"github.com/gorilla/mux"
s "github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/http/handler"
"github.com/netbirdio/netbird/management/server/http/middleware"
"github.com/rs/cors"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/acme/autocert"
)
type Server struct {
server *http.Server
config *s.HttpServerConfig
certManager *autocert.Manager
tlsConfig *tls.Config
accountManager s.AccountManager
}
// NewHttpsServer creates a new HTTPs server (with HTTPS support) and a certManager that is responsible for generating and renewing Let's Encrypt certificate
// The listening address will be :443 no matter what was specified in s.HttpServerConfig.Address
func NewHttpsServer(
config *s.HttpServerConfig,
certManager *autocert.Manager,
accountManager s.AccountManager,
) *Server {
server := &http.Server{
Addr: config.Address,
WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
}
return &Server{
server: server,
config: config,
certManager: certManager,
accountManager: accountManager,
}
}
// NewHttpsServerWithTLSConfig creates a new HTTPs server with a provided tls.Config.
// Usually used when you already have a certificate
func NewHttpsServerWithTLSConfig(
config *s.HttpServerConfig,
tlsConfig *tls.Config,
accountManager s.AccountManager,
) *Server {
server := &http.Server{
Addr: config.Address,
WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
}
return &Server{
server: server,
config: config,
tlsConfig: tlsConfig,
accountManager: accountManager,
}
}
// NewHttpServer creates a new HTTP server (without HTTPS)
func NewHttpServer(config *s.HttpServerConfig, accountManager s.AccountManager) *Server {
return NewHttpsServer(config, nil, accountManager)
}
// Stop stops the http server
func (s *Server) Stop(ctx context.Context) error {
err := s.server.Shutdown(ctx)
if err != nil {
return err
}
return nil
}
// Start defines http handlers and starts the http server. Blocks until server is shutdown.
func (s *Server) Start() error {
jwtMiddleware, err := middleware.NewJwtMiddleware(
s.config.AuthIssuer,
s.config.AuthAudience,
s.config.AuthKeysLocation,
)
if err != nil {
return err
}
corsMiddleware := cors.AllowAll()
acMiddleware := middleware.NewAccessControll(
s.config.AuthAudience,
s.accountManager.IsUserAdmin)
r := mux.NewRouter()
r.Use(jwtMiddleware.Handler, corsMiddleware.Handler, acMiddleware.Handler)
groupsHandler := handler.NewGroups(s.accountManager, s.config.AuthAudience)
rulesHandler := handler.NewRules(s.accountManager, s.config.AuthAudience)
peersHandler := handler.NewPeers(s.accountManager, s.config.AuthAudience)
keysHandler := handler.NewSetupKeysHandler(s.accountManager, s.config.AuthAudience)
userHandler := handler.NewUserHandler(s.accountManager, s.config.AuthAudience)
r.HandleFunc("/api/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
r.HandleFunc("/api/peers/{id}", peersHandler.HandlePeer).
Methods("GET", "PUT", "DELETE", "OPTIONS")
r.HandleFunc("/api/users", userHandler.GetUsers).Methods("GET", "OPTIONS")
r.HandleFunc("/api/setup-keys", keysHandler.GetKeys).Methods("GET", "POST", "OPTIONS")
r.HandleFunc("/api/setup-keys/{id}", keysHandler.HandleKey).Methods("GET", "PUT", "OPTIONS")
r.HandleFunc("/api/setup-keys", keysHandler.GetKeys).Methods("POST", "OPTIONS")
r.HandleFunc("/api/setup-keys/{id}", keysHandler.HandleKey).
Methods("GET", "PUT", "DELETE", "OPTIONS")
r.HandleFunc("/api/rules", rulesHandler.GetAllRulesHandler).Methods("GET", "OPTIONS")
r.HandleFunc("/api/rules", rulesHandler.CreateRuleHandler).Methods("POST", "OPTIONS")
r.HandleFunc("/api/rules/{id}", rulesHandler.UpdateRuleHandler).Methods("PUT", "OPTIONS")
r.HandleFunc("/api/rules/{id}", rulesHandler.PatchRuleHandler).Methods("PATCH", "OPTIONS")
r.HandleFunc("/api/rules/{id}", rulesHandler.GetRuleHandler).Methods("GET", "OPTIONS")
r.HandleFunc("/api/rules/{id}", rulesHandler.DeleteRuleHandler).Methods("DELETE", "OPTIONS")
r.HandleFunc("/api/groups", groupsHandler.GetAllGroupsHandler).Methods("GET", "OPTIONS")
r.HandleFunc("/api/groups", groupsHandler.CreateGroupHandler).Methods("POST", "OPTIONS")
r.HandleFunc("/api/groups/{id}", groupsHandler.UpdateGroupHandler).Methods("PUT", "OPTIONS")
r.HandleFunc("/api/groups/{id}", groupsHandler.PatchGroupHandler).Methods("PATCH", "OPTIONS")
r.HandleFunc("/api/groups/{id}", groupsHandler.GetGroupHandler).Methods("GET", "OPTIONS")
r.HandleFunc("/api/groups/{id}", groupsHandler.DeleteGroupHandler).Methods("DELETE", "OPTIONS")
http.Handle("/", r)
if s.certManager != nil {
// if HTTPS is enabled we reuse the listener from the cert manager
listener := s.certManager.Listener()
log.Infof(
"HTTPs server listening on %s with Let's Encrypt autocert configured",
listener.Addr(),
)
if err = http.Serve(listener, s.certManager.HTTPHandler(r)); err != nil {
log.Errorf("failed to serve https server: %v", err)
return err
}
} else if s.tlsConfig != nil {
listener, err := tls.Listen("tcp", s.config.Address, s.tlsConfig)
if err != nil {
log.Errorf("failed to serve https server: %v", err)
return err
}
log.Infof("HTTPs server listening on %s", listener.Addr())
if err = http.Serve(listener, r); err != nil {
log.Errorf("failed to serve https server: %v", err)
return err
}
} else {
log.Infof("HTTP server listening on %s", s.server.Addr)
if err = s.server.ListenAndServe(); err != nil {
log.Errorf("failed to serve http server: %v", err)
return err
}
}
return nil
}

View File

@ -1,4 +1,4 @@
package handler
package http
import (
"encoding/json"

View File

@ -1,4 +1,4 @@
package handler
package http
import (
"github.com/netbirdio/netbird/management/server/http/api"

View File

@ -1,4 +1,4 @@
package handler
package http
import (
"encoding/json"

View File

@ -1,4 +1,4 @@
package handler
package http
import (
"encoding/json"

View File

@ -359,7 +359,7 @@ func TestServer_GetDeviceAuthorizationFlow(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
mgmtServer := &Server{
mgmtServer := &GRPCServer{
wgKey: testingServerKey,
config: &Config{
DeviceAuthorizationFlow: testCase.inputFlow,