2021-07-17 14:38:59 +02:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
2022-10-16 13:33:46 +02:00
|
|
|
"context"
|
2021-09-25 19:22:49 +02:00
|
|
|
"crypto/tls"
|
2022-08-23 15:46:12 +02:00
|
|
|
"encoding/json"
|
2022-05-22 18:53:47 +02:00
|
|
|
"errors"
|
2021-07-17 14:38:59 +02:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
2022-05-13 14:11:21 +02:00
|
|
|
"io"
|
2022-05-22 18:53:47 +02:00
|
|
|
"io/fs"
|
2022-05-13 14:11:21 +02:00
|
|
|
"net"
|
2022-07-29 20:37:09 +02:00
|
|
|
"net/http"
|
2022-08-23 15:46:12 +02:00
|
|
|
"net/url"
|
2022-05-13 14:11:21 +02:00
|
|
|
"os"
|
|
|
|
"path"
|
2022-07-29 20:37:09 +02:00
|
|
|
"strings"
|
2022-05-13 14:11:21 +02:00
|
|
|
"time"
|
|
|
|
|
2023-02-03 21:47:20 +01:00
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/miekg/dns"
|
2023-03-30 10:54:09 +02:00
|
|
|
"golang.org/x/crypto/acme/autocert"
|
|
|
|
"golang.org/x/net/http2"
|
|
|
|
"golang.org/x/net/http2/h2c"
|
|
|
|
|
2023-02-03 21:47:20 +01:00
|
|
|
"github.com/netbirdio/netbird/management/server/activity/sqlite"
|
|
|
|
httpapi "github.com/netbirdio/netbird/management/server/http"
|
2023-03-30 10:54:09 +02:00
|
|
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
2023-02-03 21:47:20 +01:00
|
|
|
"github.com/netbirdio/netbird/management/server/metrics"
|
|
|
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
|
|
|
|
2022-03-26 12:08:54 +01:00
|
|
|
"github.com/netbirdio/netbird/management/server"
|
|
|
|
"github.com/netbirdio/netbird/management/server/idp"
|
|
|
|
"github.com/netbirdio/netbird/util"
|
2021-07-24 16:14:29 +02:00
|
|
|
|
2021-07-17 14:38:59 +02:00
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"google.golang.org/grpc"
|
2021-07-17 14:51:16 +02:00
|
|
|
"google.golang.org/grpc/credentials"
|
|
|
|
"google.golang.org/grpc/keepalive"
|
2023-03-30 10:54:09 +02:00
|
|
|
|
|
|
|
"github.com/netbirdio/netbird/encryption"
|
|
|
|
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
2021-07-17 14:38:59 +02:00
|
|
|
)
|
|
|
|
|
2022-07-29 20:37:09 +02:00
|
|
|
// ManagementLegacyPort is the port that was used before by the Management gRPC server.
|
|
|
|
// It is used for backward compatibility now.
|
|
|
|
const ManagementLegacyPort = 33073
|
|
|
|
|
2021-07-17 14:38:59 +02:00
|
|
|
var (
|
2022-10-19 17:43:28 +02:00
|
|
|
mgmtPort int
|
2022-10-21 16:24:13 +02:00
|
|
|
mgmtMetricsPort int
|
2022-10-19 17:43:28 +02:00
|
|
|
mgmtLetsencryptDomain string
|
|
|
|
mgmtSingleAccModeDomain string
|
|
|
|
certFile string
|
|
|
|
certKey string
|
|
|
|
config *server.Config
|
2021-07-17 14:51:16 +02:00
|
|
|
|
|
|
|
kaep = keepalive.EnforcementPolicy{
|
2021-07-22 10:28:00 +02:00
|
|
|
MinTime: 15 * time.Second,
|
2021-07-17 14:51:16 +02:00
|
|
|
PermitWithoutStream: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
kasp = keepalive.ServerParameters{
|
|
|
|
MaxConnectionIdle: 15 * time.Second,
|
|
|
|
MaxConnectionAgeGrace: 5 * time.Second,
|
|
|
|
Time: 5 * time.Second,
|
|
|
|
Timeout: 2 * time.Second,
|
|
|
|
}
|
2021-07-17 14:38:59 +02:00
|
|
|
|
|
|
|
mgmtCmd = &cobra.Command{
|
|
|
|
Use: "management",
|
2022-07-29 20:37:09 +02:00
|
|
|
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)
|
2021-09-07 09:53:18 +02:00
|
|
|
if err != nil {
|
2022-07-29 20:37:09 +02:00
|
|
|
return fmt.Errorf("failed reading provided config file: %s: %v", mgmtConfig, err)
|
2021-09-07 09:53:18 +02:00
|
|
|
}
|
2023-04-15 02:44:42 +02:00
|
|
|
config.HttpConfig.IdpSignKeyRefreshEnabled = idpSignKeyRefreshEnabled
|
2021-07-17 14:38:59 +02:00
|
|
|
|
2022-07-29 20:37:09 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-07 15:38:21 +01:00
|
|
|
_, valid := dns.IsDomainName(dnsDomain)
|
|
|
|
if !valid || len(dnsDomain) > 192 {
|
|
|
|
return fmt.Errorf("failed parsing the provided dns-domain. Valid status: %t, Lenght: %d", valid, len(dnsDomain))
|
|
|
|
}
|
|
|
|
|
2022-07-29 20:37:09 +02:00
|
|
|
return nil
|
|
|
|
},
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
flag.Parse()
|
|
|
|
err := util.InitLog(logLevel, logFile)
|
2022-05-26 12:55:39 +02:00
|
|
|
if err != nil {
|
2022-07-29 20:37:09 +02:00
|
|
|
return fmt.Errorf("failed initializing log %v", err)
|
2022-05-13 14:11:21 +02:00
|
|
|
}
|
|
|
|
|
2022-07-29 20:37:09 +02:00
|
|
|
err = handleRebrand(cmd)
|
2021-07-30 17:46:38 +02:00
|
|
|
if err != nil {
|
2022-07-29 20:37:09 +02:00
|
|
|
return fmt.Errorf("failed to migrate files %v", err)
|
2021-07-30 17:46:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if _, err = os.Stat(config.Datadir); os.IsNotExist(err) {
|
|
|
|
err = os.MkdirAll(config.Datadir, os.ModeDir)
|
2021-07-17 17:26:51 +02:00
|
|
|
if err != nil {
|
2022-07-29 20:37:09 +02:00
|
|
|
return fmt.Errorf("failed creating datadir: %s: %v", config.Datadir, err)
|
2021-07-17 17:26:51 +02:00
|
|
|
}
|
2021-07-17 14:38:59 +02:00
|
|
|
}
|
2022-10-22 13:29:39 +02:00
|
|
|
appMetrics, err := telemetry.NewDefaultAppMetrics(cmd.Context())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = appMetrics.Expose(mgmtMetricsPort, "/metrics")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-05-19 11:42:25 +02:00
|
|
|
store, err := server.NewFileStore(config.Datadir, appMetrics)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed creating Store: %s: %v", config.Datadir, err)
|
|
|
|
}
|
|
|
|
peersUpdateManager := server.NewPeersUpdateManager()
|
2022-10-22 13:29:39 +02:00
|
|
|
|
2022-02-17 19:18:46 +01:00
|
|
|
var idpManager idp.Manager
|
|
|
|
if config.IdpManagerConfig != nil {
|
2022-10-22 13:29:39 +02:00
|
|
|
idpManager, err = idp.NewManager(*config.IdpManagerConfig, appMetrics)
|
2022-02-17 19:18:46 +01:00
|
|
|
if err != nil {
|
2022-07-29 20:37:09 +02:00
|
|
|
return fmt.Errorf("failed retrieving a new idp manager with err: %v", err)
|
2022-02-17 19:18:46 +01:00
|
|
|
}
|
2022-01-24 11:21:30 +01:00
|
|
|
}
|
2022-02-17 19:18:46 +01:00
|
|
|
|
2022-10-19 17:43:28 +02:00
|
|
|
if disableSingleAccMode {
|
|
|
|
mgmtSingleAccModeDomain = ""
|
|
|
|
}
|
2023-01-02 15:11:32 +01:00
|
|
|
eventStore, err := sqlite.NewSQLiteStore(config.Datadir)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain,
|
|
|
|
dnsDomain, eventStore)
|
2022-05-21 15:21:39 +02:00
|
|
|
if err != nil {
|
2022-07-29 20:37:09 +02:00
|
|
|
return fmt.Errorf("failed to build default manager: %v", err)
|
2022-05-21 15:21:39 +02:00
|
|
|
}
|
2021-08-12 12:49:10 +02:00
|
|
|
|
2022-07-29 20:37:09 +02:00
|
|
|
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
2021-07-17 14:51:16 +02:00
|
|
|
|
2022-07-29 20:37:09 +02:00
|
|
|
gRPCOpts := []grpc.ServerOption{grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp)}
|
|
|
|
var certManager *autocert.Manager
|
|
|
|
var tlsConfig *tls.Config
|
|
|
|
tlsEnabled := false
|
2021-08-07 13:35:52 +02:00
|
|
|
if config.HttpConfig.LetsEncryptDomain != "" {
|
2022-07-29 20:37:09 +02:00
|
|
|
certManager, err = encryption.CreateCertManager(config.Datadir, config.HttpConfig.LetsEncryptDomain)
|
2022-07-25 19:55:38 +02:00
|
|
|
if err != nil {
|
2022-07-29 20:37:09 +02:00
|
|
|
return fmt.Errorf("failed creating LetsEncrypt cert manager: %v", err)
|
2022-07-25 19:55:38 +02:00
|
|
|
}
|
2021-08-07 12:26:07 +02:00
|
|
|
transportCredentials := credentials.NewTLS(certManager.TLSConfig())
|
2022-07-29 20:37:09 +02:00
|
|
|
gRPCOpts = append(gRPCOpts, grpc.Creds(transportCredentials))
|
|
|
|
tlsEnabled = true
|
2021-09-25 19:22:49 +02:00
|
|
|
} else if config.HttpConfig.CertFile != "" && config.HttpConfig.CertKey != "" {
|
2022-07-29 20:37:09 +02:00
|
|
|
tlsConfig, err = loadTLSConfig(config.HttpConfig.CertFile, config.HttpConfig.CertKey)
|
2021-09-25 19:22:49 +02:00
|
|
|
if err != nil {
|
2022-07-29 20:37:09 +02:00
|
|
|
log.Errorf("cannot load TLS credentials: %v", err)
|
|
|
|
return err
|
2021-09-25 19:22:49 +02:00
|
|
|
}
|
|
|
|
transportCredentials := credentials.NewTLS(tlsConfig)
|
2022-07-29 20:37:09 +02:00
|
|
|
gRPCOpts = append(gRPCOpts, grpc.Creds(transportCredentials))
|
|
|
|
tlsEnabled = true
|
2021-07-17 14:38:59 +02:00
|
|
|
}
|
2022-10-21 16:24:13 +02:00
|
|
|
|
2023-03-30 10:54:09 +02:00
|
|
|
jwtValidator, err := jwtclaims.NewJWTValidator(
|
|
|
|
config.HttpConfig.AuthIssuer,
|
2023-04-04 16:40:56 +02:00
|
|
|
config.GetAuthAudiences(),
|
2023-03-30 10:54:09 +02:00
|
|
|
config.HttpConfig.AuthKeysLocation,
|
2023-04-15 02:44:42 +02:00
|
|
|
config.HttpConfig.IdpSignKeyRefreshEnabled,
|
2023-03-30 10:54:09 +02:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed creating JWT validator: %v", err)
|
|
|
|
}
|
|
|
|
|
2023-02-03 21:47:20 +01:00
|
|
|
httpAPIAuthCfg := httpapi.AuthCfg{
|
|
|
|
Issuer: config.HttpConfig.AuthIssuer,
|
|
|
|
Audience: config.HttpConfig.AuthAudience,
|
|
|
|
UserIDClaim: config.HttpConfig.AuthUserIDClaim,
|
|
|
|
KeysLocation: config.HttpConfig.AuthKeysLocation,
|
|
|
|
}
|
2023-03-30 10:54:09 +02:00
|
|
|
httpAPIHandler, err := httpapi.APIHandler(accountManager, *jwtValidator, appMetrics, httpAPIAuthCfg)
|
2021-07-17 14:38:59 +02:00
|
|
|
if err != nil {
|
2022-07-29 20:37:09 +02:00
|
|
|
return fmt.Errorf("failed creating HTTP API handler: %v", err)
|
2021-07-17 14:38:59 +02:00
|
|
|
}
|
2021-07-17 14:51:16 +02:00
|
|
|
|
2022-07-29 20:37:09 +02:00
|
|
|
gRPCAPIHandler := grpc.NewServer(gRPCOpts...)
|
2022-10-22 15:06:54 +02:00
|
|
|
srv, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager, appMetrics)
|
2021-07-17 14:51:16 +02:00
|
|
|
if err != nil {
|
2022-07-29 20:37:09 +02:00
|
|
|
return fmt.Errorf("failed creating gRPC API handler: %v", err)
|
2021-07-17 14:51:16 +02:00
|
|
|
}
|
2022-07-29 20:37:09 +02:00
|
|
|
mgmtProto.RegisterManagementServiceServer(gRPCAPIHandler, srv)
|
2021-07-17 14:51:16 +02:00
|
|
|
|
2022-10-16 13:33:46 +02:00
|
|
|
installationID, err := getInstallationID(store)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("cannot load TLS credentials: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !disableMetrics {
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
2023-07-22 19:30:59 +02:00
|
|
|
idpManager := "disabled"
|
|
|
|
if config.IdpManagerConfig != nil && config.IdpManagerConfig.ManagerType != "" {
|
|
|
|
idpManager = config.IdpManagerConfig.ManagerType
|
|
|
|
}
|
|
|
|
metricsWorker := metrics.NewWorker(ctx, installationID, store, peersUpdateManager, idpManager)
|
2022-10-16 13:33:46 +02:00
|
|
|
go metricsWorker.Run()
|
|
|
|
}
|
|
|
|
|
2022-07-29 20:37:09 +02:00
|
|
|
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 {
|
|
|
|
return err
|
2021-07-17 14:38:59 +02:00
|
|
|
}
|
2022-07-29 20:37:09 +02:00
|
|
|
log.Infof("running gRPC backward compatibility server: %s", compatListener.Addr().String())
|
|
|
|
}
|
2021-07-17 14:38:59 +02:00
|
|
|
|
2022-07-29 20:37:09 +02:00
|
|
|
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))
|
2021-08-07 12:26:07 +02:00
|
|
|
if err != nil {
|
2022-07-29 20:37:09 +02:00
|
|
|
return fmt.Errorf("failed creating TCP listener on port %d: %v", mgmtPort, err)
|
2021-08-07 12:26:07 +02:00
|
|
|
}
|
2022-07-29 20:37:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
log.Infof("running HTTP server and gRPC server on the same port: %s", listener.Addr().String())
|
|
|
|
serveGRPCWithHTTP(listener, rootHandler, tlsEnabled)
|
2021-08-07 12:26:07 +02:00
|
|
|
|
2021-07-17 14:38:59 +02:00
|
|
|
SetupCloseHandler()
|
2022-07-29 20:37:09 +02:00
|
|
|
|
2021-07-17 14:38:59 +02:00
|
|
|
<-stopCh
|
2022-10-22 11:50:21 +02:00
|
|
|
_ = appMetrics.Close()
|
2022-07-29 20:37:09 +02:00
|
|
|
_ = listener.Close()
|
|
|
|
if certManager != nil {
|
|
|
|
_ = certManager.Listener().Close()
|
2021-08-07 12:26:07 +02:00
|
|
|
}
|
2022-07-29 20:37:09 +02:00
|
|
|
gRPCAPIHandler.Stop()
|
2022-11-08 10:46:12 +01:00
|
|
|
_ = store.Close()
|
2023-01-02 15:11:32 +01:00
|
|
|
_ = eventStore.Close()
|
2022-07-29 20:37:09 +02:00
|
|
|
log.Infof("stopped Management Service")
|
2021-08-07 12:26:07 +02:00
|
|
|
|
2022-07-29 20:37:09 +02:00
|
|
|
return nil
|
2021-07-17 14:38:59 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2022-07-29 20:37:09 +02:00
|
|
|
func notifyStop(msg string) {
|
|
|
|
select {
|
|
|
|
case stopCh <- 1:
|
|
|
|
log.Error(msg)
|
|
|
|
default:
|
|
|
|
// stop has been already called, nothing to report
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-16 13:33:46 +02:00
|
|
|
func getInstallationID(store server.Store) (string, error) {
|
|
|
|
installationID := store.GetInstallationID()
|
|
|
|
if installationID != "" {
|
|
|
|
return installationID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
installationID = strings.ToUpper(uuid.New().String())
|
|
|
|
err := store.SaveInstallationID(installationID)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return installationID, nil
|
|
|
|
}
|
|
|
|
|
2022-07-29 20:37:09 +02:00
|
|
|
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")
|
|
|
|
if request.ProtoMajor == 2 && grpcHeader {
|
|
|
|
gRPCHandler.ServeHTTP(writer, request)
|
|
|
|
} else {
|
|
|
|
httpHandler.ServeHTTP(writer, request)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-05-13 14:11:21 +02:00
|
|
|
func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
|
2021-07-30 17:46:38 +02:00
|
|
|
config := &server.Config{}
|
2022-05-13 14:11:21 +02:00
|
|
|
_, err := util.ReadJson(mgmtConfigPath, config)
|
2021-07-30 17:46:38 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if mgmtLetsencryptDomain != "" {
|
2021-08-07 13:35:52 +02:00
|
|
|
config.HttpConfig.LetsEncryptDomain = mgmtLetsencryptDomain
|
2021-07-30 17:46:38 +02:00
|
|
|
}
|
|
|
|
if mgmtDataDir != "" {
|
|
|
|
config.Datadir = mgmtDataDir
|
|
|
|
}
|
|
|
|
|
2021-09-25 19:22:49 +02:00
|
|
|
if certKey != "" && certFile != "" {
|
|
|
|
config.HttpConfig.CertFile = certFile
|
|
|
|
config.HttpConfig.CertKey = certKey
|
|
|
|
}
|
|
|
|
|
2022-08-23 15:46:12 +02:00
|
|
|
oidcEndpoint := config.HttpConfig.OIDCConfigEndpoint
|
|
|
|
if oidcEndpoint != "" {
|
|
|
|
// if OIDCConfigEndpoint is specified, we can load DeviceAuthEndpoint and TokenEndpoint automatically
|
|
|
|
log.Infof("loading OIDC configuration from the provided IDP configuration endpoint %s", oidcEndpoint)
|
|
|
|
oidcConfig, err := fetchOIDCConfig(oidcEndpoint)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
log.Infof("loaded OIDC configuration from the provided IDP configuration endpoint: %s", oidcEndpoint)
|
|
|
|
|
|
|
|
log.Infof("overriding HttpConfig.AuthIssuer with a new value %s, previously configured value: %s",
|
|
|
|
oidcConfig.Issuer, config.HttpConfig.AuthIssuer)
|
|
|
|
config.HttpConfig.AuthIssuer = oidcConfig.Issuer
|
|
|
|
|
|
|
|
log.Infof("overriding HttpConfig.AuthKeysLocation (JWT certs) with a new value %s, previously configured value: %s",
|
|
|
|
oidcConfig.JwksURI, config.HttpConfig.AuthKeysLocation)
|
|
|
|
config.HttpConfig.AuthKeysLocation = oidcConfig.JwksURI
|
|
|
|
|
2022-08-24 14:37:18 +02:00
|
|
|
if !(config.DeviceAuthorizationFlow == nil || strings.ToLower(config.DeviceAuthorizationFlow.Provider) == string(server.NONE)) {
|
2022-08-23 15:46:12 +02:00
|
|
|
log.Infof("overriding DeviceAuthorizationFlow.TokenEndpoint with a new value: %s, previously configured value: %s",
|
|
|
|
oidcConfig.TokenEndpoint, config.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint)
|
|
|
|
config.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint = oidcConfig.TokenEndpoint
|
|
|
|
log.Infof("overriding DeviceAuthorizationFlow.DeviceAuthEndpoint with a new value: %s, previously configured value: %s",
|
|
|
|
oidcConfig.DeviceAuthEndpoint, config.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint)
|
|
|
|
config.DeviceAuthorizationFlow.ProviderConfig.DeviceAuthEndpoint = oidcConfig.DeviceAuthEndpoint
|
|
|
|
|
|
|
|
u, err := url.Parse(oidcEndpoint)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
log.Infof("overriding DeviceAuthorizationFlow.ProviderConfig.Domain with a new value: %s, previously configured value: %s",
|
|
|
|
u.Host, config.DeviceAuthorizationFlow.ProviderConfig.Domain)
|
|
|
|
config.DeviceAuthorizationFlow.ProviderConfig.Domain = u.Host
|
2023-04-05 17:46:34 +02:00
|
|
|
|
|
|
|
if config.DeviceAuthorizationFlow.ProviderConfig.Scope == "" {
|
|
|
|
config.DeviceAuthorizationFlow.ProviderConfig.Scope = server.DefaultDeviceAuthFlowScope
|
|
|
|
}
|
2022-08-23 15:46:12 +02:00
|
|
|
}
|
2023-07-27 11:31:07 +02:00
|
|
|
|
|
|
|
if config.PKCEAuthorizationFlow != nil {
|
|
|
|
log.Infof("overriding PKCEAuthorizationFlow.TokenEndpoint with a new value: %s, previously configured value: %s",
|
|
|
|
oidcConfig.TokenEndpoint, config.PKCEAuthorizationFlow.ProviderConfig.TokenEndpoint)
|
|
|
|
config.DeviceAuthorizationFlow.ProviderConfig.TokenEndpoint = oidcConfig.TokenEndpoint
|
|
|
|
log.Infof("overriding PKCEAuthorizationFlow.AuthorizationEndpoint with a new value: %s, previously configured value: %s",
|
|
|
|
oidcConfig.AuthorizationEndpoint, config.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint)
|
|
|
|
config.PKCEAuthorizationFlow.ProviderConfig.AuthorizationEndpoint = oidcConfig.AuthorizationEndpoint
|
|
|
|
}
|
2022-08-23 15:46:12 +02:00
|
|
|
}
|
|
|
|
|
2021-07-30 17:46:38 +02:00
|
|
|
return config, err
|
|
|
|
}
|
|
|
|
|
2022-08-23 15:46:12 +02:00
|
|
|
// OIDCConfigResponse used for parsing OIDC config response
|
|
|
|
type OIDCConfigResponse struct {
|
2023-07-27 11:31:07 +02:00
|
|
|
Issuer string `json:"issuer"`
|
|
|
|
TokenEndpoint string `json:"token_endpoint"`
|
|
|
|
DeviceAuthEndpoint string `json:"device_authorization_endpoint"`
|
|
|
|
JwksURI string `json:"jwks_uri"`
|
|
|
|
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
2022-08-23 15:46:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// fetchOIDCConfig fetches OIDC configuration from the IDP
|
|
|
|
func fetchOIDCConfig(oidcEndpoint string) (OIDCConfigResponse, error) {
|
|
|
|
res, err := http.Get(oidcEndpoint)
|
|
|
|
if err != nil {
|
2023-05-11 15:14:00 +02:00
|
|
|
return OIDCConfigResponse{}, fmt.Errorf("failed fetching OIDC configuration from endpoint %s %v", oidcEndpoint, err)
|
2022-08-23 15:46:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
err := res.Body.Close()
|
|
|
|
if err != nil {
|
|
|
|
log.Debugf("failed closing response body %v", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
body, err := io.ReadAll(res.Body)
|
|
|
|
if err != nil {
|
|
|
|
return OIDCConfigResponse{}, fmt.Errorf("failed reading OIDC configuration response body: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if res.StatusCode != 200 {
|
|
|
|
return OIDCConfigResponse{}, fmt.Errorf("OIDC configuration request returned status %d with response: %s",
|
|
|
|
res.StatusCode, string(body))
|
|
|
|
}
|
|
|
|
|
|
|
|
config := OIDCConfigResponse{}
|
|
|
|
err = json.Unmarshal(body, &config)
|
|
|
|
if err != nil {
|
|
|
|
return OIDCConfigResponse{}, fmt.Errorf("failed unmarshaling OIDC configuration response: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return config, nil
|
|
|
|
}
|
|
|
|
|
2021-09-25 19:22:49 +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
|
|
|
|
}
|
|
|
|
|
2022-10-22 11:50:21 +02:00
|
|
|
// NewDefaultAppMetrics the credentials and return it
|
2021-09-25 19:22:49 +02:00
|
|
|
config := &tls.Config{
|
|
|
|
Certificates: []tls.Certificate{serverCert},
|
|
|
|
ClientAuth: tls.NoClientCert,
|
2022-08-15 19:36:00 +02:00
|
|
|
NextProtos: []string{
|
|
|
|
"h2", "http/1.1", // enable HTTP/2
|
|
|
|
},
|
2021-09-25 19:22:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return config, nil
|
|
|
|
}
|
|
|
|
|
2022-05-26 12:55:39 +02:00
|
|
|
func handleRebrand(cmd *cobra.Command) error {
|
|
|
|
var err error
|
|
|
|
if logFile == defaultLogFile {
|
|
|
|
if migrateToNetbird(oldDefaultLogFile, defaultLogFile) {
|
|
|
|
cmd.Printf("will copy Log dir %s and its content to %s\n", oldDefaultLogDir, defaultLogDir)
|
|
|
|
err = cpDir(oldDefaultLogDir, defaultLogDir)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if mgmtConfig == defaultMgmtConfig {
|
|
|
|
if migrateToNetbird(oldDefaultMgmtConfig, defaultMgmtConfig) {
|
|
|
|
cmd.Printf("will copy Config dir %s and its content to %s\n", oldDefaultMgmtConfigDir, defaultMgmtConfigDir)
|
|
|
|
err = cpDir(oldDefaultMgmtConfigDir, defaultMgmtConfigDir)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if mgmtDataDir == defaultMgmtDataDir {
|
|
|
|
if migrateToNetbird(oldDefaultMgmtDataDir, defaultMgmtDataDir) {
|
|
|
|
cmd.Printf("will copy Config dir %s and its content to %s\n", oldDefaultMgmtDataDir, defaultMgmtDataDir)
|
|
|
|
err = cpDir(oldDefaultMgmtDataDir, defaultMgmtDataDir)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-05-13 14:11:21 +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-08-18 18:22:15 +02:00
|
|
|
var fds []os.DirEntry
|
2022-05-13 14:11:21 +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-08-18 18:22:15 +02:00
|
|
|
if fds, err = os.ReadDir(src); err != nil {
|
2022-05-13 14:11:21 +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 14:11:21 +02:00
|
|
|
|
2022-05-22 18:53:47 +02:00
|
|
|
if errors.Is(errOld, fs.ErrNotExist) || errNew == nil {
|
2022-05-13 14:11:21 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|