mirror of
https://github.com/jsiebens/ionscale.git
synced 2026-03-31 15:07:49 +01:00
improvement: change http(s) listener to web listener addr and a public web addr
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user