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 {
c := &config.Config{}
c.HttpListenAddr = "0.0.0.0:80"
c.HttpsListenAddr = "0.0.0.0:443"
c.WebListenAddr = "0.0.0.0:443"
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{
ControlKey: key.NewServerKey().String(),
+23 -9
View File
@@ -11,9 +11,9 @@ import (
"github.com/jsiebens/ionscale/internal/util"
"github.com/mitchellh/go-homedir"
"gopkg.in/yaml.v3"
"net/url"
"os"
"path/filepath"
"strings"
tkey "tailscale.com/types/key"
"time"
)
@@ -95,15 +95,13 @@ func LoadConfig(path string) (*Config, error) {
dnsProviderConfigured = true
}
return cfg, nil
return cfg.Validate()
}
func defaultConfig() *Config {
return &Config{
HttpListenAddr: ":8080",
HttpsListenAddr: ":8443",
WebListenAddr: ":8080",
MetricsListenAddr: ":9091",
ServerUrl: "https://localhost:8843",
Database: Database{
Type: "sqlite",
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 {
HttpListenAddr string `yaml:"http_listen_addr,omitempty" env:"HTTP_LISTEN_ADDR"`
HttpsListenAddr string `yaml:"https_listen_addr,omitempty" env:"HTTPS_LISTEN_ADDR"`
WebListenAddr string `yaml:"web_listen_addr,omitempty" env:"WEB_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_"`
PollNet PollNet `yaml:"poll_net,omitempty" envPrefix:"POLL_NET_"`
Keys Keys `yaml:"keys,omitempty" envPrefix:"KEYS_"`
@@ -146,6 +143,8 @@ type Config struct {
Auth Auth `yaml:"auth,omitempty" envPrefix:"AUTH_"`
DNS DNS `yaml:"dns,omitempty"`
Logging Logging `yaml:"logging,omitempty" envPrefix:"LOGGING_"`
WebPublicUrl *url.URL `yaml:"-"`
}
type Tls struct {
@@ -212,9 +211,24 @@ type SystemAdminPolicy struct {
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 {
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) {
+28
View File
@@ -1,6 +1,9 @@
package config
import (
"fmt"
"net"
"net/url"
"os"
"strings"
)
@@ -20,3 +23,28 @@ func GetString(key, defaultValue string) string {
}
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
import (
"github.com/caddyserver/certmagic"
"github.com/jsiebens/ionscale/internal/config"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"net"
"net/http"
)
func httpsRedirectSkipper(c config.Tls) func(ctx echo.Context) bool {
@@ -23,38 +20,3 @@ func HttpsRedirect(c config.Tls) echo.MiddlewareFunc {
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 {
return &IDTokenHandlers{
machineKey: machineKey,
issuer: config.ServerUrl,
issuer: config.WebPublicUrl.String(),
repository: repository,
}
}
func NewOIDCConfigHandlers(config *config.Config, repository domain.Repository) *OIDCConfigHandlers {
return &OIDCConfigHandlers{
issuer: config.ServerUrl,
issuer: config.WebPublicUrl.String(),
jwksUri: config.CreateUrl("/.well-known/jwks"),
repository: repository,
}
+37 -77
View File
@@ -27,7 +27,6 @@ import (
"golang.org/x/sync/errgroup"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"syscall"
@@ -74,11 +73,6 @@ func Start(ctx context.Context, c *config.Config) error {
core.StartWorker(repository, sessionManager)
serverUrl, err := url.Parse(c.ServerUrl)
if err != nil {
return logError(err)
}
// prepare CertMagic
if c.Tls.AcmeEnabled {
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
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)
}
c.HttpListenAddr = fmt.Sprintf(":%d", certmagic.HTTPPort)
c.HttpsListenAddr = fmt.Sprintf(":%d", certmagic.HTTPSPort)
}
authProvider, systemIAMPolicy, err := setupAuthProvider(c.Auth)
@@ -115,10 +106,6 @@ func Start(ctx context.Context, c *config.Config) error {
promMiddleware := echoprometheus.NewMiddleware("http")
metricsHandler := echo.New()
metricsHandler.GET("/metrics", echoprometheus.NewHandler())
pprof.Register(metricsHandler)
createPeerHandler := func(machinePublicKey key.MachinePublic) http.Handler {
registrationHandlers := handlers.NewRegistrationHandlers(machinePublicKey, c, 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)
rpcPath, rpcHandler := NewRpcHandler(serverKey.SystemAdminKey, repository, rpcService)
nonTlsAppHandler := echo.New()
nonTlsAppHandler.Use(promMiddleware, EchoLogger(httpLogger), EchoErrorHandler(), EchoRecover())
nonTlsAppHandler.POST("/ts2021", noiseHandlers.Upgrade)
nonTlsAppHandler.Any("/*", handlers.HttpRedirectHandler(c.Tls))
metricsMux := echo.New()
metricsMux.GET("/metrics", echoprometheus.NewHandler())
pprof.Register(metricsMux)
tlsAppHandler := echo.New()
tlsAppHandler.Renderer = &templates.Renderer{}
tlsAppHandler.Pre(handlers.HttpsRedirect(c.Tls))
tlsAppHandler.Use(promMiddleware, EchoLogger(httpLogger), EchoErrorHandler(), EchoRecover())
webMux := echo.New()
webMux.Renderer = &templates.Renderer{}
webMux.Pre(handlers.HttpsRedirect(c.Tls))
webMux.Use(promMiddleware, EchoLogger(httpLogger), EchoErrorHandler(), EchoRecover())
tlsAppHandler.Any("/*", handlers.IndexHandler(http.StatusNotFound))
tlsAppHandler.Any("/", handlers.IndexHandler(http.StatusOK))
tlsAppHandler.POST(rpcPath+"*", echo.WrapHandler(rpcHandler))
tlsAppHandler.GET("/version", handlers.Version)
tlsAppHandler.GET("/key", handlers.KeyHandler(serverKey))
tlsAppHandler.POST("/ts2021", noiseHandlers.Upgrade)
tlsAppHandler.GET("/.well-known/jwks", oidcConfigHandlers.Jwks)
tlsAppHandler.GET("/.well-known/openid-configuration", oidcConfigHandlers.OpenIDConfig)
webMux.Any("/*", handlers.IndexHandler(http.StatusNotFound))
webMux.Any("/", handlers.IndexHandler(http.StatusOK))
webMux.POST(rpcPath+"*", echo.WrapHandler(rpcHandler))
webMux.GET("/version", handlers.Version)
webMux.GET("/key", handlers.KeyHandler(serverKey))
webMux.POST("/ts2021", noiseHandlers.Upgrade)
webMux.GET("/.well-known/jwks", oidcConfigHandlers.Jwks)
webMux.GET("/.well-known/openid-configuration", oidcConfigHandlers.OpenIDConfig)
csrf := middleware.CSRFWithConfig(middleware.CSRFConfig{TokenLookup: "form:_csrf"})
tlsAppHandler.GET("/a/:flow/:key", authenticationHandlers.StartAuth, csrf)
tlsAppHandler.POST("/a/:flow/:key", authenticationHandlers.ProcessAuth, csrf)
tlsAppHandler.GET("/a/callback", authenticationHandlers.Callback, csrf)
tlsAppHandler.POST("/a/callback", authenticationHandlers.EndAuth, csrf)
tlsAppHandler.GET("/a/success", authenticationHandlers.Success, csrf)
tlsAppHandler.GET("/a/error", authenticationHandlers.Error, csrf)
webMux.GET("/a/:flow/:key", authenticationHandlers.StartAuth, csrf)
webMux.POST("/a/:flow/:key", authenticationHandlers.ProcessAuth, csrf)
webMux.GET("/a/callback", authenticationHandlers.Callback, csrf)
webMux.POST("/a/callback", authenticationHandlers.EndAuth, csrf)
webMux.GET("/a/success", authenticationHandlers.Success, csrf)
webMux.GET("/a/error", authenticationHandlers.Error, csrf)
tlsL, err := tlsListener(c)
if err != nil {
return logError(err)
}
nonTlsL, err := nonTlsListener(c)
webL, err := webListener(c)
if err != nil {
return logError(err)
}
@@ -202,9 +183,8 @@ func Start(ctx context.Context, c *config.Config) error {
return logError(err)
}
httpAppServer := &http.Server{ErrorLog: errorLog, Handler: nonTlsAppHandler}
httpsAppServer := &http.Server{ErrorLog: errorLog, Handler: h2c.NewHandler(tlsAppHandler, &http2.Server{})}
metricsServer := &http.Server{ErrorLog: errorLog, Handler: metricsHandler}
webServer := &http.Server{ErrorLog: errorLog, Handler: h2c.NewHandler(webMux, &http2.Server{})}
metricsServer := &http.Server{ErrorLog: errorLog, Handler: metricsMux}
g, gCtx := errgroup.WithContext(ctx)
@@ -212,23 +192,21 @@ func Start(ctx context.Context, c *config.Config) error {
<-gCtx.Done()
logger.Sugar().Infow("Shutting down ionscale server")
shutdownHttpServer(metricsServer)
shutdownHttpServer(httpAppServer)
shutdownHttpServer(httpsAppServer)
shutdownHttpServer(webServer)
}()
g.Go(func() error { return serveHttp(webServer, webL) })
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 {
logger.Sugar().Infow("TLS is enabled with ACME", "domain", serverUrl.Host)
logger.Sugar().Infow("Server is running", "http_addr", c.HttpListenAddr, "https_addr", c.HttpsListenAddr, "metrics_addr", c.MetricsListenAddr)
logger.Sugar().Infow("TLS is enabled with ACME", "domain", c.WebPublicUrl.Hostname())
logger.Sugar().Infow("Server is running", "addr", c.WebListenAddr, "metrics_addr", c.MetricsListenAddr, "url", c.WebPublicUrl)
} else if !c.Tls.Disable {
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 {
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()
@@ -267,20 +245,16 @@ func setupAuthProvider(config config.Auth) (auth.Provider, *domain.IAMPolicy, er
}, nil
}
func metricsListener(config *config.Config) (net.Listener, error) {
return net.Listen("tcp", config.MetricsListenAddr)
}
func tlsListener(config *config.Config) (net.Listener, error) {
func webListener(config *config.Config) (net.Listener, error) {
if config.Tls.Disable {
return nil, nil
return net.Listen("tcp", config.WebListenAddr)
}
if config.Tls.AcmeEnabled {
cfg := certmagic.NewDefault()
tlsConfig := cfg.TLSConfig()
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)
@@ -299,25 +273,11 @@ func tlsListener(config *config.Config) (net.Listener, error) {
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) {
return net.Listen("tcp", config.HttpListenAddr)
}
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 metricsListener(config *config.Config) (net.Listener, error) {
return net.Listen("tcp", config.MetricsListenAddr)
}
func setupLogging(config config.Logging) (*zap.Logger, error) {
+2 -2
View File
@@ -1,5 +1,5 @@
http_listen_addr: ":80"
server_url: "http://ionscale"
web_listen_addr: ":80"
web_public_addr: "http://ionscale:80"
tls:
disable: true