improvement: change http(s) listener to web listener addr and a public web addr

This commit is contained in:
Johan Siebens
2024-02-23 10:35:39 +01:00
parent 94d9168eab
commit d72ea03d9d
8 changed files with 130 additions and 131 deletions
+2 -3
View File
@@ -52,10 +52,9 @@ func configureCommand() *cobra.Command {
command.RunE = func(command *cobra.Command, args []string) error { command.RunE = func(command *cobra.Command, args []string) error {
c := &config.Config{} c := &config.Config{}
c.HttpListenAddr = "0.0.0.0:80" c.WebListenAddr = "0.0.0.0:443"
c.HttpsListenAddr = "0.0.0.0:443"
c.MetricsListenAddr = "127.0.0.1:9090" c.MetricsListenAddr = "127.0.0.1:9090"
c.ServerUrl = fmt.Sprintf("https://%s", domain) c.WebPublicAddr = fmt.Sprintf("%s:443", domain)
c.Keys = config.Keys{ c.Keys = config.Keys{
ControlKey: key.NewServerKey().String(), ControlKey: key.NewServerKey().String(),
+23 -9
View File
@@ -11,9 +11,9 @@ import (
"github.com/jsiebens/ionscale/internal/util" "github.com/jsiebens/ionscale/internal/util"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"strings"
tkey "tailscale.com/types/key" tkey "tailscale.com/types/key"
"time" "time"
) )
@@ -95,15 +95,13 @@ func LoadConfig(path string) (*Config, error) {
dnsProviderConfigured = true dnsProviderConfigured = true
} }
return cfg, nil return cfg.Validate()
} }
func defaultConfig() *Config { func defaultConfig() *Config {
return &Config{ return &Config{
HttpListenAddr: ":8080", WebListenAddr: ":8080",
HttpsListenAddr: ":8443",
MetricsListenAddr: ":9091", MetricsListenAddr: ":9091",
ServerUrl: "https://localhost:8843",
Database: Database{ Database: Database{
Type: "sqlite", Type: "sqlite",
Url: "./ionscale.db?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_pragma=foreign_keys(ON)", Url: "./ionscale.db?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_pragma=foreign_keys(ON)",
@@ -135,10 +133,9 @@ type ServerKeys struct {
} }
type Config struct { type Config struct {
HttpListenAddr string `yaml:"http_listen_addr,omitempty" env:"HTTP_LISTEN_ADDR"` WebListenAddr string `yaml:"web_listen_addr,omitempty" env:"WEB_LISTEN_ADDR"`
HttpsListenAddr string `yaml:"https_listen_addr,omitempty" env:"HTTPS_LISTEN_ADDR"`
MetricsListenAddr string `yaml:"metrics_listen_addr,omitempty" env:"METRICS_LISTEN_ADDR"` MetricsListenAddr string `yaml:"metrics_listen_addr,omitempty" env:"METRICS_LISTEN_ADDR"`
ServerUrl string `yaml:"server_url,omitempty" env:"SERVER_URL"` WebPublicAddr string `yaml:"web_public_addr,omitempty" env:"WEB_PUBLIC_ADDR"`
Tls Tls `yaml:"tls,omitempty" envPrefix:"TLS_"` Tls Tls `yaml:"tls,omitempty" envPrefix:"TLS_"`
PollNet PollNet `yaml:"poll_net,omitempty" envPrefix:"POLL_NET_"` PollNet PollNet `yaml:"poll_net,omitempty" envPrefix:"POLL_NET_"`
Keys Keys `yaml:"keys,omitempty" envPrefix:"KEYS_"` Keys Keys `yaml:"keys,omitempty" envPrefix:"KEYS_"`
@@ -146,6 +143,8 @@ type Config struct {
Auth Auth `yaml:"auth,omitempty" envPrefix:"AUTH_"` Auth Auth `yaml:"auth,omitempty" envPrefix:"AUTH_"`
DNS DNS `yaml:"dns,omitempty"` DNS DNS `yaml:"dns,omitempty"`
Logging Logging `yaml:"logging,omitempty" envPrefix:"LOGGING_"` Logging Logging `yaml:"logging,omitempty" envPrefix:"LOGGING_"`
WebPublicUrl *url.URL `yaml:"-"`
} }
type Tls struct { type Tls struct {
@@ -212,9 +211,24 @@ type SystemAdminPolicy struct {
Filters []string `yaml:"filters,omitempty"` Filters []string `yaml:"filters,omitempty"`
} }
func (c *Config) Validate() (*Config, error) {
publicWebUrl, err := publicAddrToUrl(c.WebPublicAddr)
if err != nil {
return nil, err
}
c.WebPublicUrl = publicWebUrl
return c, nil
}
func (c *Config) CreateUrl(format string, a ...interface{}) string { func (c *Config) CreateUrl(format string, a ...interface{}) string {
path := fmt.Sprintf(format, a...) path := fmt.Sprintf(format, a...)
return strings.TrimSuffix(c.ServerUrl, "/") + "/" + strings.TrimPrefix(path, "/") u := url.URL{
Scheme: c.WebPublicUrl.Scheme,
Host: c.WebPublicUrl.Host,
Path: path,
}
return u.String()
} }
func (c *Config) ReadServerKeys(defaultKeys *domain.ControlKeys) (*ServerKeys, error) { func (c *Config) ReadServerKeys(defaultKeys *domain.ControlKeys) (*ServerKeys, error) {
+28
View File
@@ -1,6 +1,9 @@
package config package config
import ( import (
"fmt"
"net"
"net/url"
"os" "os"
"strings" "strings"
) )
@@ -20,3 +23,28 @@ func GetString(key, defaultValue string) string {
} }
return defaultValue return defaultValue
} }
func publicAddrToUrl(addr string) (*url.URL, error) {
scheme := "https"
if strings.HasPrefix(addr, "http://") {
scheme = "http"
addr = strings.TrimPrefix(addr, "http://")
}
if strings.HasPrefix(addr, "https://") {
scheme = "https"
addr = strings.TrimPrefix(addr, "https://")
}
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, fmt.Errorf("invalid public addr")
}
if (port == "443" && scheme == "https") || (port == "80" && scheme == "http") || port == "" {
return &url.URL{Scheme: scheme, Host: host}, nil
}
return &url.URL{Scheme: scheme, Host: fmt.Sprintf("%s:%s", host, port)}, nil
}
+36
View File
@@ -0,0 +1,36 @@
package config
import (
"fmt"
"github.com/stretchr/testify/require"
"net/url"
"testing"
)
func TestPublicAddrToUrl(t *testing.T) {
mustParseUrl := func(s string) *url.URL {
parse, err := url.Parse(s)
require.NoError(t, err)
return parse
}
parameters := []struct {
input string
expected *url.URL
err error
}{
{"localtest.me", nil, fmt.Errorf("invalid public addr")},
{"localtest.me:443", mustParseUrl("https://localtest.me"), nil},
{"localtest.me:80", mustParseUrl("https://localtest.me:80"), nil},
{"localtest.me:8080", mustParseUrl("https://localtest.me:8080"), nil},
{"http://localtest.me:8080", mustParseUrl("http://localtest.me:8080"), nil},
}
for _, p := range parameters {
t.Run(fmt.Sprintf("Testing [%v]", p.input), func(t *testing.T) {
url, err := publicAddrToUrl(p.input)
require.Equal(t, p.expected, url)
require.Equal(t, p.err, err)
})
}
}
-38
View File
@@ -1,12 +1,9 @@
package handlers package handlers
import ( import (
"github.com/caddyserver/certmagic"
"github.com/jsiebens/ionscale/internal/config" "github.com/jsiebens/ionscale/internal/config"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware" "github.com/labstack/echo/v4/middleware"
"net"
"net/http"
) )
func httpsRedirectSkipper(c config.Tls) func(ctx echo.Context) bool { func httpsRedirectSkipper(c config.Tls) func(ctx echo.Context) bool {
@@ -23,38 +20,3 @@ func HttpsRedirect(c config.Tls) echo.MiddlewareFunc {
Skipper: httpsRedirectSkipper(c), Skipper: httpsRedirectSkipper(c),
}) })
} }
func HttpRedirectHandler(tls config.Tls) echo.HandlerFunc {
if tls.Disable {
return IndexHandler(http.StatusNotFound)
}
if tls.AcmeEnabled {
cfg := certmagic.NewDefault()
if len(cfg.Issuers) > 0 {
if am, ok := cfg.Issuers[0].(*certmagic.ACMEIssuer); ok {
handler := am.HTTPChallengeHandler(http.HandlerFunc(httpRedirectHandler))
return echo.WrapHandler(handler)
}
}
}
return echo.WrapHandler(http.HandlerFunc(httpRedirectHandler))
}
func httpRedirectHandler(w http.ResponseWriter, r *http.Request) {
toURL := "https://"
requestHost := hostOnly(r.Host)
toURL += requestHost
toURL += r.URL.RequestURI()
w.Header().Set("Connection", "close")
http.Redirect(w, r, toURL, http.StatusMovedPermanently)
}
func hostOnly(hostport string) string {
host, _, err := net.SplitHostPort(hostport)
if err != nil {
return hostport
}
return host
}
+2 -2
View File
@@ -17,14 +17,14 @@ import (
func NewIDTokenHandlers(machineKey key.MachinePublic, config *config.Config, repository domain.Repository) *IDTokenHandlers { func NewIDTokenHandlers(machineKey key.MachinePublic, config *config.Config, repository domain.Repository) *IDTokenHandlers {
return &IDTokenHandlers{ return &IDTokenHandlers{
machineKey: machineKey, machineKey: machineKey,
issuer: config.ServerUrl, issuer: config.WebPublicUrl.String(),
repository: repository, repository: repository,
} }
} }
func NewOIDCConfigHandlers(config *config.Config, repository domain.Repository) *OIDCConfigHandlers { func NewOIDCConfigHandlers(config *config.Config, repository domain.Repository) *OIDCConfigHandlers {
return &OIDCConfigHandlers{ return &OIDCConfigHandlers{
issuer: config.ServerUrl, issuer: config.WebPublicUrl.String(),
jwksUri: config.CreateUrl("/.well-known/jwks"), jwksUri: config.CreateUrl("/.well-known/jwks"),
repository: repository, repository: repository,
} }
+37 -77
View File
@@ -27,7 +27,6 @@ import (
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"net" "net"
"net/http" "net/http"
"net/url"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
@@ -74,11 +73,6 @@ func Start(ctx context.Context, c *config.Config) error {
core.StartWorker(repository, sessionManager) core.StartWorker(repository, sessionManager)
serverUrl, err := url.Parse(c.ServerUrl)
if err != nil {
return logError(err)
}
// prepare CertMagic // prepare CertMagic
if c.Tls.AcmeEnabled { if c.Tls.AcmeEnabled {
storage, err := certmagicsql.NewStorage(ctx, db, certmagicsql.Options{}) storage, err := certmagicsql.NewStorage(ctx, db, certmagicsql.Options{})
@@ -95,12 +89,9 @@ func Start(ctx context.Context, c *config.Config) error {
certmagic.Default.Storage = storage certmagic.Default.Storage = storage
cfg := certmagic.NewDefault() cfg := certmagic.NewDefault()
if err := cfg.ManageAsync(ctx, []string{serverUrl.Host}); err != nil { if err := cfg.ManageAsync(ctx, []string{c.WebPublicUrl.Hostname()}); err != nil {
return logError(err) return logError(err)
} }
c.HttpListenAddr = fmt.Sprintf(":%d", certmagic.HTTPPort)
c.HttpsListenAddr = fmt.Sprintf(":%d", certmagic.HTTPSPort)
} }
authProvider, systemIAMPolicy, err := setupAuthProvider(c.Auth) authProvider, systemIAMPolicy, err := setupAuthProvider(c.Auth)
@@ -115,10 +106,6 @@ func Start(ctx context.Context, c *config.Config) error {
promMiddleware := echoprometheus.NewMiddleware("http") promMiddleware := echoprometheus.NewMiddleware("http")
metricsHandler := echo.New()
metricsHandler.GET("/metrics", echoprometheus.NewHandler())
pprof.Register(metricsHandler)
createPeerHandler := func(machinePublicKey key.MachinePublic) http.Handler { createPeerHandler := func(machinePublicKey key.MachinePublic) http.Handler {
registrationHandlers := handlers.NewRegistrationHandlers(machinePublicKey, c, sessionManager, repository) registrationHandlers := handlers.NewRegistrationHandlers(machinePublicKey, c, sessionManager, repository)
pollNetMapHandler := handlers.NewPollNetMapHandler(machinePublicKey, sessionManager, repository) pollNetMapHandler := handlers.NewPollNetMapHandler(machinePublicKey, sessionManager, repository)
@@ -155,39 +142,33 @@ func Start(ctx context.Context, c *config.Config) error {
rpcService := service.NewService(c, authProvider, dnsProvider, repository, sessionManager) rpcService := service.NewService(c, authProvider, dnsProvider, repository, sessionManager)
rpcPath, rpcHandler := NewRpcHandler(serverKey.SystemAdminKey, repository, rpcService) rpcPath, rpcHandler := NewRpcHandler(serverKey.SystemAdminKey, repository, rpcService)
nonTlsAppHandler := echo.New() metricsMux := echo.New()
nonTlsAppHandler.Use(promMiddleware, EchoLogger(httpLogger), EchoErrorHandler(), EchoRecover()) metricsMux.GET("/metrics", echoprometheus.NewHandler())
nonTlsAppHandler.POST("/ts2021", noiseHandlers.Upgrade) pprof.Register(metricsMux)
nonTlsAppHandler.Any("/*", handlers.HttpRedirectHandler(c.Tls))
tlsAppHandler := echo.New() webMux := echo.New()
tlsAppHandler.Renderer = &templates.Renderer{} webMux.Renderer = &templates.Renderer{}
tlsAppHandler.Pre(handlers.HttpsRedirect(c.Tls)) webMux.Pre(handlers.HttpsRedirect(c.Tls))
tlsAppHandler.Use(promMiddleware, EchoLogger(httpLogger), EchoErrorHandler(), EchoRecover()) webMux.Use(promMiddleware, EchoLogger(httpLogger), EchoErrorHandler(), EchoRecover())
tlsAppHandler.Any("/*", handlers.IndexHandler(http.StatusNotFound)) webMux.Any("/*", handlers.IndexHandler(http.StatusNotFound))
tlsAppHandler.Any("/", handlers.IndexHandler(http.StatusOK)) webMux.Any("/", handlers.IndexHandler(http.StatusOK))
tlsAppHandler.POST(rpcPath+"*", echo.WrapHandler(rpcHandler)) webMux.POST(rpcPath+"*", echo.WrapHandler(rpcHandler))
tlsAppHandler.GET("/version", handlers.Version) webMux.GET("/version", handlers.Version)
tlsAppHandler.GET("/key", handlers.KeyHandler(serverKey)) webMux.GET("/key", handlers.KeyHandler(serverKey))
tlsAppHandler.POST("/ts2021", noiseHandlers.Upgrade) webMux.POST("/ts2021", noiseHandlers.Upgrade)
tlsAppHandler.GET("/.well-known/jwks", oidcConfigHandlers.Jwks) webMux.GET("/.well-known/jwks", oidcConfigHandlers.Jwks)
tlsAppHandler.GET("/.well-known/openid-configuration", oidcConfigHandlers.OpenIDConfig) webMux.GET("/.well-known/openid-configuration", oidcConfigHandlers.OpenIDConfig)
csrf := middleware.CSRFWithConfig(middleware.CSRFConfig{TokenLookup: "form:_csrf"}) csrf := middleware.CSRFWithConfig(middleware.CSRFConfig{TokenLookup: "form:_csrf"})
tlsAppHandler.GET("/a/:flow/:key", authenticationHandlers.StartAuth, csrf) webMux.GET("/a/:flow/:key", authenticationHandlers.StartAuth, csrf)
tlsAppHandler.POST("/a/:flow/:key", authenticationHandlers.ProcessAuth, csrf) webMux.POST("/a/:flow/:key", authenticationHandlers.ProcessAuth, csrf)
tlsAppHandler.GET("/a/callback", authenticationHandlers.Callback, csrf) webMux.GET("/a/callback", authenticationHandlers.Callback, csrf)
tlsAppHandler.POST("/a/callback", authenticationHandlers.EndAuth, csrf) webMux.POST("/a/callback", authenticationHandlers.EndAuth, csrf)
tlsAppHandler.GET("/a/success", authenticationHandlers.Success, csrf) webMux.GET("/a/success", authenticationHandlers.Success, csrf)
tlsAppHandler.GET("/a/error", authenticationHandlers.Error, csrf) webMux.GET("/a/error", authenticationHandlers.Error, csrf)
tlsL, err := tlsListener(c) webL, err := webListener(c)
if err != nil {
return logError(err)
}
nonTlsL, err := nonTlsListener(c)
if err != nil { if err != nil {
return logError(err) return logError(err)
} }
@@ -202,9 +183,8 @@ func Start(ctx context.Context, c *config.Config) error {
return logError(err) return logError(err)
} }
httpAppServer := &http.Server{ErrorLog: errorLog, Handler: nonTlsAppHandler} webServer := &http.Server{ErrorLog: errorLog, Handler: h2c.NewHandler(webMux, &http2.Server{})}
httpsAppServer := &http.Server{ErrorLog: errorLog, Handler: h2c.NewHandler(tlsAppHandler, &http2.Server{})} metricsServer := &http.Server{ErrorLog: errorLog, Handler: metricsMux}
metricsServer := &http.Server{ErrorLog: errorLog, Handler: metricsHandler}
g, gCtx := errgroup.WithContext(ctx) g, gCtx := errgroup.WithContext(ctx)
@@ -212,23 +192,21 @@ func Start(ctx context.Context, c *config.Config) error {
<-gCtx.Done() <-gCtx.Done()
logger.Sugar().Infow("Shutting down ionscale server") logger.Sugar().Infow("Shutting down ionscale server")
shutdownHttpServer(metricsServer) shutdownHttpServer(metricsServer)
shutdownHttpServer(httpAppServer) shutdownHttpServer(webServer)
shutdownHttpServer(httpsAppServer)
}() }()
g.Go(func() error { return serveHttp(webServer, webL) })
g.Go(func() error { return serveHttp(metricsServer, metricsL) }) g.Go(func() error { return serveHttp(metricsServer, metricsL) })
g.Go(func() error { return serveHttp(httpAppServer, nonTlsOrNoListener(tlsL, nonTlsL)) })
g.Go(func() error { return serveHttp(httpsAppServer, tlsOrNonTlsListener(tlsL, nonTlsL)) })
if c.Tls.AcmeEnabled { if c.Tls.AcmeEnabled {
logger.Sugar().Infow("TLS is enabled with ACME", "domain", serverUrl.Host) logger.Sugar().Infow("TLS is enabled with ACME", "domain", c.WebPublicUrl.Hostname())
logger.Sugar().Infow("Server is running", "http_addr", c.HttpListenAddr, "https_addr", c.HttpsListenAddr, "metrics_addr", c.MetricsListenAddr) logger.Sugar().Infow("Server is running", "addr", c.WebListenAddr, "metrics_addr", c.MetricsListenAddr, "url", c.WebPublicUrl)
} else if !c.Tls.Disable { } else if !c.Tls.Disable {
logger.Sugar().Infow("TLS is enabled", "cert", c.Tls.CertFile) logger.Sugar().Infow("TLS is enabled", "cert", c.Tls.CertFile)
logger.Sugar().Infow("Server is running", "http_addr", c.HttpListenAddr, "https_addr", c.HttpsListenAddr, "metrics_addr", c.MetricsListenAddr) logger.Sugar().Infow("Server is running", "addr", c.WebListenAddr, "metrics_addr", c.MetricsListenAddr, "url", c.WebPublicUrl)
} else { } else {
logger.Sugar().Warnw("TLS is disabled") logger.Sugar().Warnw("TLS is disabled")
logger.Sugar().Infow("Server is running", "http_addr", c.HttpListenAddr, "metrics_addr", c.MetricsListenAddr) logger.Sugar().Infow("Server is running", "addr", c.WebListenAddr, "metrics_addr", c.MetricsListenAddr, "url", c.WebPublicUrl)
} }
return g.Wait() return g.Wait()
@@ -267,20 +245,16 @@ func setupAuthProvider(config config.Auth) (auth.Provider, *domain.IAMPolicy, er
}, nil }, nil
} }
func metricsListener(config *config.Config) (net.Listener, error) { func webListener(config *config.Config) (net.Listener, error) {
return net.Listen("tcp", config.MetricsListenAddr)
}
func tlsListener(config *config.Config) (net.Listener, error) {
if config.Tls.Disable { if config.Tls.Disable {
return nil, nil return net.Listen("tcp", config.WebListenAddr)
} }
if config.Tls.AcmeEnabled { if config.Tls.AcmeEnabled {
cfg := certmagic.NewDefault() cfg := certmagic.NewDefault()
tlsConfig := cfg.TLSConfig() tlsConfig := cfg.TLSConfig()
tlsConfig.NextProtos = append([]string{"h2", "http/1.1"}, tlsConfig.NextProtos...) tlsConfig.NextProtos = append([]string{"h2", "http/1.1"}, tlsConfig.NextProtos...)
return tls.Listen("tcp", config.HttpsListenAddr, tlsConfig) return tls.Listen("tcp", config.WebListenAddr, tlsConfig)
} }
certPEMBlock, err := os.ReadFile(config.Tls.CertFile) certPEMBlock, err := os.ReadFile(config.Tls.CertFile)
@@ -299,25 +273,11 @@ func tlsListener(config *config.Config) (net.Listener, error) {
tlsConfig := &tls.Config{Certificates: []tls.Certificate{cer}} tlsConfig := &tls.Config{Certificates: []tls.Certificate{cer}}
return tls.Listen("tcp", config.HttpsListenAddr, tlsConfig) return tls.Listen("tcp", config.WebListenAddr, tlsConfig)
} }
func nonTlsListener(config *config.Config) (net.Listener, error) { func metricsListener(config *config.Config) (net.Listener, error) {
return net.Listen("tcp", config.HttpListenAddr) return net.Listen("tcp", config.MetricsListenAddr)
}
func tlsOrNonTlsListener(tlsL net.Listener, nonTlsL net.Listener) net.Listener {
if tlsL != nil {
return tlsL
}
return nonTlsL
}
func nonTlsOrNoListener(tlsL net.Listener, nonTlsL net.Listener) net.Listener {
if tlsL != nil {
return nonTlsL
}
return nil
} }
func setupLogging(config config.Logging) (*zap.Logger, error) { func setupLogging(config config.Logging) (*zap.Logger, error) {
+2 -2
View File
@@ -1,5 +1,5 @@
http_listen_addr: ":80" web_listen_addr: ":80"
server_url: "http://ionscale" web_public_addr: "http://ionscale:80"
tls: tls:
disable: true disable: true