2024-05-17 20:24:06 +02:00
package main
import (
2024-07-01 11:50:18 +02:00
"crypto/tls"
2024-07-24 16:26:26 +02:00
"errors"
2024-07-01 11:50:18 +02:00
"fmt"
2024-07-24 16:26:26 +02:00
"net/http"
2024-05-17 20:24:06 +02:00
"os"
2024-05-27 09:42:27 +02:00
"os/signal"
"syscall"
2024-07-08 17:01:11 +02:00
"time"
2024-05-23 13:24:02 +02:00
2024-05-17 20:24:06 +02:00
log "github.com/sirupsen/logrus"
2024-06-19 17:40:16 +02:00
"github.com/spf13/cobra"
2024-05-17 20:24:06 +02:00
2024-07-01 11:50:18 +02:00
"github.com/netbirdio/netbird/encryption"
2024-07-08 17:01:11 +02:00
auth "github.com/netbirdio/netbird/relay/auth/hmac"
2024-05-17 20:24:06 +02:00
"github.com/netbirdio/netbird/relay/server"
2024-07-24 16:26:26 +02:00
"github.com/netbirdio/netbird/signal/metrics"
2024-05-27 09:42:27 +02:00
"github.com/netbirdio/netbird/util"
2024-05-17 20:24:06 +02:00
)
2024-07-24 16:26:26 +02:00
const (
metricsPort = 9090
)
2024-07-17 17:10:33 +02:00
type Config struct {
ListenAddress string
2024-07-02 11:57:17 +02:00
// in HA every peer connect to a common domain, the instance domain has been distributed during the p2p connection
2024-07-03 15:03:57 +02:00
// it is a domain:port or ip:port
2024-07-17 17:10:33 +02:00
ExposedAddress string
LetsencryptDataDir string
LetsencryptDomains [ ] string
TlsCertFile string
TlsKeyFile string
AuthSecret string
}
func ( c Config ) Validate ( ) error {
if c . ExposedAddress == "" {
return fmt . Errorf ( "exposed address is required" )
}
if c . AuthSecret == "" {
return fmt . Errorf ( "auth secret is required" )
}
return nil
}
func ( c Config ) HasCertConfig ( ) bool {
return c . TlsCertFile != "" && c . TlsKeyFile != ""
}
func ( c Config ) HasLetsEncrypt ( ) bool {
return c . LetsencryptDataDir != "" && c . LetsencryptDomains != nil && len ( c . LetsencryptDomains ) > 0
}
var (
cobraConfig * Config
cfgFile string
rootCmd = & cobra . Command {
2024-06-19 17:40:16 +02:00
Use : "relay" ,
Short : "Relay service" ,
Long : "Relay service for Netbird agents" ,
2024-07-24 16:26:26 +02:00
RunE : execute ,
2024-06-19 17:40:16 +02:00
}
)
2024-05-17 20:24:06 +02:00
func init ( ) {
2024-06-19 17:40:16 +02:00
_ = util . InitLog ( "trace" , "console" )
2024-07-17 17:10:33 +02:00
cobraConfig = & Config { }
rootCmd . PersistentFlags ( ) . StringVarP ( & cfgFile , "config-file" , "f" , "/etc/netbird/relay.json" , "Relay server config file location" )
rootCmd . PersistentFlags ( ) . StringVarP ( & cobraConfig . ListenAddress , "listen-address" , "l" , ":443" , "listen address" )
rootCmd . PersistentFlags ( ) . StringVarP ( & cobraConfig . ExposedAddress , "exposed-address" , "e" , "" , "instance domain address (or ip) and port, it will be distributes between peers" )
rootCmd . PersistentFlags ( ) . StringVarP ( & cobraConfig . LetsencryptDataDir , "letsencrypt-data-dir" , "d" , "" , "a directory to store Let's Encrypt data. Required if Let's Encrypt is enabled." )
rootCmd . PersistentFlags ( ) . StringArrayVarP ( & cobraConfig . LetsencryptDomains , "letsencrypt-domains" , "a" , nil , "list of domains to issue Let's Encrypt certificate for. Enables TLS using Let's Encrypt. Will fetch and renew certificate, and run the server with TLS" )
rootCmd . PersistentFlags ( ) . StringVarP ( & cobraConfig . TlsCertFile , "tls-cert-file" , "c" , "" , "" )
rootCmd . PersistentFlags ( ) . StringVarP ( & cobraConfig . TlsKeyFile , "tls-key-file" , "k" , "" , "" )
rootCmd . PersistentFlags ( ) . StringVarP ( & cobraConfig . AuthSecret , "auth-secret" , "s" , "" , "log level" )
2024-05-17 20:24:06 +02:00
}
2024-05-27 09:42:27 +02:00
func waitForExitSignal ( ) {
osSigs := make ( chan os . Signal , 1 )
signal . Notify ( osSigs , syscall . SIGINT , syscall . SIGTERM )
2024-06-25 15:13:08 +02:00
<- osSigs
2024-05-27 09:42:27 +02:00
}
2024-05-17 20:24:06 +02:00
2024-07-17 17:10:33 +02:00
func loadConfig ( configFile string ) ( * Config , error ) {
log . Infof ( "loading config from: %s" , configFile )
loadedConfig := & Config { }
_ , err := util . ReadJson ( configFile , loadedConfig )
if err != nil {
return nil , err
}
if cobraConfig . ListenAddress != "" {
loadedConfig . ListenAddress = cobraConfig . ListenAddress
}
if cobraConfig . ExposedAddress != "" {
loadedConfig . ExposedAddress = cobraConfig . ExposedAddress
}
if cobraConfig . LetsencryptDataDir != "" {
loadedConfig . LetsencryptDataDir = cobraConfig . LetsencryptDataDir
}
if len ( cobraConfig . LetsencryptDomains ) > 0 {
loadedConfig . LetsencryptDomains = cobraConfig . LetsencryptDomains
}
if cobraConfig . TlsCertFile != "" {
loadedConfig . TlsCertFile = cobraConfig . TlsCertFile
}
if cobraConfig . TlsKeyFile != "" {
loadedConfig . TlsKeyFile = cobraConfig . TlsKeyFile
}
if cobraConfig . AuthSecret != "" {
loadedConfig . AuthSecret = cobraConfig . AuthSecret
}
return loadedConfig , err
}
2024-07-24 16:26:26 +02:00
func execute ( cmd * cobra . Command , args [ ] string ) error {
2024-07-17 17:10:33 +02:00
cfg , err := loadConfig ( cfgFile )
if err != nil {
2024-07-24 16:26:26 +02:00
return fmt . Errorf ( "failed to load config: %s" , err )
2024-07-03 15:03:57 +02:00
}
2024-07-17 17:10:33 +02:00
err = cfg . Validate ( )
if err != nil {
log . Errorf ( "invalid config: %s" , err )
2024-07-05 16:12:30 +02:00
os . Exit ( 1 )
}
2024-07-24 16:26:26 +02:00
metricsServer , err := metrics . NewServer ( metricsPort , "" )
if err != nil {
return fmt . Errorf ( "setup metrics: %v" , err )
}
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 )
}
} ( )
2024-07-02 11:57:17 +02:00
srvListenerCfg := server . ListenerConfig {
2024-07-17 17:10:33 +02:00
Address : cfg . ListenAddress ,
2024-07-01 11:50:18 +02:00
}
2024-07-17 17:10:33 +02:00
if cfg . HasLetsEncrypt ( ) {
tlsCfg , err := setupTLSCertManager ( cfg . LetsencryptDataDir , cfg . LetsencryptDomains ... )
2024-07-01 11:50:18 +02:00
if err != nil {
2024-07-24 16:26:26 +02:00
return fmt . Errorf ( "%s" , err )
2024-07-01 11:50:18 +02:00
}
2024-07-03 15:03:57 +02:00
srvListenerCfg . TLSConfig = tlsCfg
2024-07-17 17:10:33 +02:00
} else if cfg . HasCertConfig ( ) {
tlsCfg , err := encryption . LoadTLSConfig ( cfg . TlsCertFile , cfg . TlsKeyFile )
2024-07-03 15:03:57 +02:00
if err != nil {
2024-07-24 16:26:26 +02:00
return fmt . Errorf ( "%s" , err )
2024-07-03 15:03:57 +02:00
}
srvListenerCfg . TLSConfig = tlsCfg
2024-07-01 11:50:18 +02:00
}
2024-07-02 11:57:17 +02:00
tlsSupport := srvListenerCfg . TLSConfig != nil
2024-07-08 17:01:11 +02:00
2024-07-17 17:10:33 +02:00
authenticator := auth . NewTimedHMACValidator ( cfg . AuthSecret , 24 * time . Hour )
2024-07-24 16:26:26 +02:00
srv , err := server . NewServer ( metricsServer . Meter , cfg . ExposedAddress , tlsSupport , authenticator )
if err != nil {
return fmt . Errorf ( "failed to create relay server: %v" , err )
}
2024-07-03 15:03:57 +02:00
log . Infof ( "server will be available on: %s" , srv . InstanceURL ( ) )
2024-07-17 17:10:33 +02:00
err = srv . Listen ( srvListenerCfg )
2024-05-17 20:24:06 +02:00
if err != nil {
2024-07-24 16:26:26 +02:00
return fmt . Errorf ( "failed to bind server: %s" , err )
2024-05-17 20:24:06 +02:00
}
2024-05-26 22:14:33 +02:00
2024-05-27 09:42:27 +02:00
waitForExitSignal ( )
err = srv . Close ( )
if err != nil {
2024-07-24 16:26:26 +02:00
return fmt . Errorf ( "failed to close server: %s" , err )
2024-05-27 09:42:27 +02:00
}
2024-07-24 16:26:26 +02:00
return nil
2024-05-17 20:24:06 +02:00
}
2024-06-19 17:40:16 +02:00
2024-07-17 17:10:33 +02:00
func setupTLSCertManager ( letsencryptDataDir string , letsencryptDomains ... string ) ( * tls . Config , error ) {
2024-07-02 11:57:17 +02:00
certManager , err := encryption . CreateCertManager ( letsencryptDataDir , letsencryptDomains ... )
2024-07-01 11:50:18 +02:00
if err != nil {
return nil , fmt . Errorf ( "failed creating LetsEncrypt cert manager: %v" , err )
}
return certManager . TLSConfig ( ) , nil
}
2024-06-19 17:40:16 +02:00
func main ( ) {
err := rootCmd . Execute ( )
if err != nil {
os . Exit ( 1 )
}
}