root: allow listening on multiple IPs (#20930)

This commit is contained in:
Marc 'risson' Schmitt
2026-03-19 15:46:47 +00:00
committed by GitHub
parent 545b1e8f19
commit 4dfdf9afa3
24 changed files with 385 additions and 244 deletions

View File

@@ -32,12 +32,18 @@ postgresql:
# host: replica1.example.com # host: replica1.example.com
listen: listen:
http: 0.0.0.0:9000 http:
https: 0.0.0.0:9443 - "[::]:9000"
ldap: 0.0.0.0:3389 https:
ldaps: 0.0.0.0:6636 - "[::]:9443"
radius: 0.0.0.0:1812 ldap:
metrics: 0.0.0.0:9300 - "[::]:3389"
ldaps:
- "[::]:6636"
radius:
- "[::]:1812"
metrics:
- "[::]:9300"
debug: 0.0.0.0:9900 debug: 0.0.0.0:9900
debug_py: 0.0.0.0:9901 debug_py: 0.0.0.0:9901
trusted_proxy_cidrs: trusted_proxy_cidrs:

View File

@@ -224,7 +224,10 @@ class WorkerHealthcheckMiddleware(Middleware):
thread: HTTPServerThread | None thread: HTTPServerThread | None
def __init__(self): def __init__(self):
host, _, port = CONFIG.get("listen.http").rpartition(":") listen = CONFIG.get("listen.http", ["[::]:9000"])
if isinstance(listen, str):
listen = listen.split(",")
host, _, port = listen[0].rpartition(":")
try: try:
port = int(port) port = int(port)
@@ -323,7 +326,10 @@ class MetricsMiddleware(BaseMetricsMiddleware):
return [] return []
def after_worker_boot(self, broker: Broker, worker: Worker): def after_worker_boot(self, broker: Broker, worker: Worker):
addr, _, port = CONFIG.get("listen.metrics").rpartition(":") listen = CONFIG.get("listen.metrics", ["[::]:9300"])
if isinstance(listen, str):
listen = listen.split(",")
addr, _, port = listen[0].rpartition(":")
try: try:
port = int(port) port = int(port)

View File

@@ -1,7 +1,9 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"net"
"net/http" "net/http"
"os" "os"
"path" "path"
@@ -12,7 +14,8 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"goauthentik.io/internal/config" "goauthentik.io/internal/config"
"goauthentik.io/internal/utils/web" utils "goauthentik.io/internal/utils/web"
"goauthentik.io/internal/web"
) )
var workerPidFile = path.Join(os.TempDir(), "authentik-worker.pid") var workerPidFile = path.Join(os.TempDir(), "authentik-worker.pid")
@@ -44,9 +47,15 @@ func init() {
func checkServer() int { func checkServer() int {
h := &http.Client{ h := &http.Client{
Transport: web.NewUserAgentTransport("goauthentik.io/healthcheck", http.DefaultTransport), Transport: utils.NewUserAgentTransport("goauthentik.io/healthcheck",
&http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", path.Join(os.TempDir(), web.SocketName))
},
},
),
} }
url := fmt.Sprintf("http://%s%s-/health/live/", config.Get().Listen.HTTP, config.Get().Web.Path) url := fmt.Sprintf("http://localhost%s-/health/live/", config.Get().Web.Path)
res, err := h.Head(url) res, err := h.Head(url)
if err != nil { if err != nil {
log.WithError(err).Warning("failed to send healthcheck request") log.WithError(err).Warning("failed to send healthcheck request")

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"os"
"time" "time"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
@@ -51,9 +52,10 @@ var rootCmd = &cobra.Command{
ex := common.Init() ex := common.Init()
defer common.Defer() defer common.Defer()
u, err := url.Parse(fmt.Sprintf("http://%s%s", config.Get().Listen.HTTP, config.Get().Web.Path)) u := url.URL{
if err != nil { Scheme: "unix",
panic(err) Host: fmt.Sprintf("%s/%s", os.TempDir(), web.SocketName),
Path: config.Get().Web.Path,
} }
ws := web.NewWebServer() ws := web.NewWebServer()
@@ -70,13 +72,13 @@ var rootCmd = &cobra.Command{
}, },
} }
func attemptProxyStart(ws *web.WebServer, u *url.URL) { func attemptProxyStart(ws *web.WebServer, u url.URL) {
maxTries := 100 maxTries := 100
attempt := 0 attempt := 0
l := log.WithField("logger", "authentik.server") l := log.WithField("logger", "authentik.server")
for { for {
l.Debug("attempting to init outpost") l.Debug("attempting to init outpost")
ac := ak.NewAPIController(*u, config.Get().SecretKey) ac := ak.NewAPIController(u, config.Get().SecretKey)
if ac == nil { if ac == nil {
attempt += 1 attempt += 1
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)

View File

@@ -50,12 +50,12 @@ type PostgreSQLConfig struct {
} }
type ListenConfig struct { type ListenConfig struct {
HTTP string `yaml:"http" env:"HTTP, overwrite"` HTTP []string `yaml:"http" env:"HTTP, overwrite"`
HTTPS string `yaml:"https" env:"HTTPS, overwrite"` HTTPS []string `yaml:"https" env:"HTTPS, overwrite"`
LDAP string `yaml:"ldap" env:"LDAP, overwrite"` LDAP []string `yaml:"ldap" env:"LDAP, overwrite"`
LDAPS string `yaml:"ldaps" env:"LDAPS, overwrite"` LDAPS []string `yaml:"ldaps" env:"LDAPS, overwrite"`
Radius string `yaml:"radius" env:"RADIUS, overwrite"` Radius []string `yaml:"radius" env:"RADIUS, overwrite"`
Metrics string `yaml:"metrics" env:"METRICS, overwrite"` Metrics []string `yaml:"metrics" env:"METRICS, overwrite"`
Debug string `yaml:"debug" env:"DEBUG, overwrite"` Debug string `yaml:"debug" env:"DEBUG, overwrite"`
TrustedProxyCIDRs []string `yaml:"trusted_proxy_cidrs" env:"TRUSTED_PROXY_CIDRS, overwrite"` TrustedProxyCIDRs []string `yaml:"trusted_proxy_cidrs" env:"TRUSTED_PROXY_CIDRS, overwrite"`
} }

View File

@@ -5,6 +5,7 @@ import (
"crypto/fips140" "crypto/fips140"
"fmt" "fmt"
"math/rand" "math/rand"
"net"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@@ -54,11 +55,30 @@ type APIController struct {
// NewAPIController initialise new API Controller instance from URL and API token // NewAPIController initialise new API Controller instance from URL and API token
func NewAPIController(akURL url.URL, token string) *APIController { func NewAPIController(akURL url.URL, token string) *APIController {
rsp := sentry.StartSpan(context.Background(), "authentik.outposts.init") rsp := sentry.StartSpan(context.Background(), "authentik.outposts.init")
log := log.WithField("logger", "authentik.outpost.ak-api-controller")
apiConfig := api.NewConfiguration() originalAkURL := akURL
apiConfig.Host = akURL.Host var client http.Client
apiConfig.Scheme = akURL.Scheme if akURL.Scheme == "unix" {
apiConfig.HTTPClient = &http.Client{ log.WithField("host", akURL.Host).WithField("path", akURL.Path).Debug("using unix socket")
socketPath := akURL.Host
client = http.Client{
Transport: web.NewUserAgentTransport(
constants.UserAgentOutpost(),
web.NewTracingTransport(
rsp.Context(),
&http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", socketPath)
},
},
),
),
}
akURL.Scheme = "http"
akURL.Host = "localhost"
} else {
client = http.Client{
Transport: web.NewUserAgentTransport( Transport: web.NewUserAgentTransport(
constants.UserAgentOutpost(), constants.UserAgentOutpost(),
web.NewTracingTransport( web.NewTracingTransport(
@@ -67,6 +87,12 @@ func NewAPIController(akURL url.URL, token string) *APIController {
), ),
), ),
} }
}
apiConfig := api.NewConfiguration()
apiConfig.Host = akURL.Host
apiConfig.Scheme = akURL.Scheme
apiConfig.HTTPClient = &client
apiConfig.Servers = api.ServerConfigurations{ apiConfig.Servers = api.ServerConfigurations{
{ {
URL: fmt.Sprintf("%sapi/v3", akURL.Path), URL: fmt.Sprintf("%sapi/v3", akURL.Path),
@@ -77,8 +103,6 @@ func NewAPIController(akURL url.URL, token string) *APIController {
// create the API client, with the transport // create the API client, with the transport
apiClient := api.NewAPIClient(apiConfig) apiClient := api.NewAPIClient(apiConfig)
log := log.WithField("logger", "authentik.outpost.ak-api-controller")
// Because we don't know the outpost UUID, we simply do a list and pick the first // Because we don't know the outpost UUID, we simply do a list and pick the first
// The service account this token belongs to should only have access to a single outpost // The service account this token belongs to should only have access to a single outpost
outposts, _ := retry.DoWithData[*api.PaginatedOutpostList]( outposts, _ := retry.DoWithData[*api.PaginatedOutpostList](
@@ -124,7 +148,7 @@ func NewAPIController(akURL url.URL, token string) *APIController {
} }
ac.logger.WithField("embedded", ac.IsEmbedded()).Info("Outpost mode") ac.logger.WithField("embedded", ac.IsEmbedded()).Info("Outpost mode")
ac.logger.WithField("offset", ac.reloadOffset.String()).Debug("HA Reload offset") ac.logger.WithField("offset", ac.reloadOffset.String()).Debug("HA Reload offset")
err = ac.initEvent(akURL, outpost.Pk) err = ac.initEvent(originalAkURL, outpost.Pk)
if err != nil { if err != nil {
go ac.recentEvents() go ac.recentEvents()
} }

View File

@@ -5,6 +5,7 @@ import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"maps" "maps"
"net"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
@@ -45,9 +46,19 @@ func (ac *APIController) initEvent(akURL url.URL, outpostUUID string) error {
dialer := websocket.Dialer{ dialer := websocket.Dialer{
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
HandshakeTimeout: 10 * time.Second, HandshakeTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{ }
if akURL.Scheme == "unix" {
ac.logger.WithField("host", akURL.Host).WithField("path", akURL.Path).Debug("websocket is using unix connection")
socketPath := akURL.Host
dialer.NetDialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
return (&net.Dialer{}).DialContext(ctx, "unix", socketPath)
}
akURL.Scheme = "http"
akURL.Host = "localhost"
} else {
dialer.TLSClientConfig = &tls.Config{
InsecureSkipVerify: config.Get().AuthentikInsecure, InsecureSkipVerify: config.Get().AuthentikInsecure,
}, }
} }
wsu := ac.getWebsocketURL(akURL, outpostUUID, query).String() wsu := ac.getWebsocketURL(akURL, outpostUUID, query).String()

View File

@@ -1,13 +1,16 @@
package healthcheck package healthcheck
import ( import (
"fmt" "context"
"net"
"net/http" "net/http"
"os" "os"
"path"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"goauthentik.io/internal/config" "goauthentik.io/internal/config"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/utils/web" "goauthentik.io/internal/utils/web"
) )
@@ -21,9 +24,15 @@ var Command = &cobra.Command{
func check() int { func check() int {
h := &http.Client{ h := &http.Client{
Transport: web.NewUserAgentTransport("goauthentik.io/healthcheck", http.DefaultTransport), Transport: web.NewUserAgentTransport("goauthentik.io/healthcheck",
&http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", path.Join(os.TempDir(), ak.MetricsSocketName))
},
},
),
} }
url := fmt.Sprintf("http://%s/outpost.goauthentik.io/ping", config.Get().Listen.Metrics) url := "http://localhost/outpost.goauthentik.io/ping"
res, err := h.Head(url) res, err := h.Head(url)
if err != nil { if err != nil {
log.WithError(err).Warning("failed to send healthcheck request") log.WithError(err).Warning("failed to send healthcheck request")

View File

@@ -1,11 +1,21 @@
package ak package ak
import ( import (
"net/http"
"os"
"path"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/utils/sentry"
"goauthentik.io/internal/utils/unix"
) )
var ( var (
MetricsSocketName = "authentik-metrics.sock"
OutpostInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{ OutpostInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "authentik_outpost_info", Name: "authentik_outpost_info",
Help: "Outpost info", Help: "Outpost info",
@@ -19,3 +29,43 @@ var (
Help: "Connection status", Help: "Connection status",
}, []string{"outpost_name", "outpost_type", "uuid"}) }, []string{"outpost_name", "outpost_type", "uuid"})
) )
func MetricsRouter() *mux.Router {
m := mux.NewRouter()
m.Use(sentry.SentryNoSampleMiddleware)
m.HandleFunc("/outpost.goauthentik.io/ping", func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(204)
})
m.Path("/metrics").Handler(promhttp.Handler())
return m
}
func RunMetricsServer(listen string, router *mux.Router) {
l := log.WithField("logger", "authentik.outpost.metrics").WithField("listen", listen)
l.Info("Starting Metrics server")
err := http.ListenAndServe(listen, router)
if err != nil {
l.WithError(err).Warning("Failed to start metrics listener")
}
}
func RunMetricsUnix(router *mux.Router) {
socketPath := path.Join(os.TempDir(), MetricsSocketName)
l := log.WithField("logger", "authentik.outpost.metrics").WithField("listen", socketPath)
ln, err := unix.Listen(socketPath)
if err != nil {
l.WithError(err).Warning("failed to listen")
return
}
defer func() {
err := ln.Close()
if err != nil {
l.WithError(err).Warning("failed to close listener")
}
}()
l.WithField("listen", socketPath).Info("Starting Metrics server")
err = http.Serve(ln, router)
if err != nil {
l.WithError(err).Warning("Failed to start metrics listener")
}
}

View File

@@ -11,7 +11,6 @@ import (
"goauthentik.io/internal/config" "goauthentik.io/internal/config"
"goauthentik.io/internal/crypto" "goauthentik.io/internal/crypto"
"goauthentik.io/internal/outpost/ak" "goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/ldap/metrics"
"goauthentik.io/internal/utils" "goauthentik.io/internal/utils"
"beryju.io/ldap" "beryju.io/ldap"
@@ -63,9 +62,7 @@ func (ls *LDAPServer) Type() string {
return "ldap" return "ldap"
} }
func (ls *LDAPServer) StartLDAPServer() error { func (ls *LDAPServer) StartLDAPServer(listen string) error {
listen := config.Get().Listen.LDAP
ln, err := net.Listen("tcp", listen) ln, err := net.Listen("tcp", listen)
if err != nil { if err != nil {
ls.log.WithField("listen", listen).WithError(err).Warning("Failed to listen (SSL)") ls.log.WithField("listen", listen).WithError(err).Warning("Failed to listen (SSL)")
@@ -89,26 +86,40 @@ func (ls *LDAPServer) StartLDAPServer() error {
} }
func (ls *LDAPServer) Start() error { func (ls *LDAPServer) Start() error {
listenLdap := config.Get().Listen.LDAP
listenLdaps := config.Get().Listen.LDAPS
listenMetrics := config.Get().Listen.Metrics
metricsRouter := ak.MetricsRouter()
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(3) wg.Add(len(listenLdap) + len(listenLdaps) + 1 + len(listenMetrics))
for _, listen := range listenLdap {
go func() { go func() {
defer wg.Done() defer wg.Done()
metrics.RunServer() err := ls.StartLDAPServer(listen)
}()
go func() {
defer wg.Done()
err := ls.StartLDAPServer()
if err != nil { if err != nil {
panic(err) panic(err)
} }
}() }()
}
for _, listen := range listenLdaps {
go func() { go func() {
defer wg.Done() defer wg.Done()
err := ls.StartLDAPTLSServer() err := ls.StartLDAPTLSServer(listen)
if err != nil { if err != nil {
panic(err) panic(err)
} }
}() }()
}
go func() {
defer wg.Done()
ak.RunMetricsUnix(metricsRouter)
}()
for _, listen := range listenMetrics {
go func() {
defer wg.Done()
ak.RunMetricsServer(listen, metricsRouter)
}()
}
wg.Wait() wg.Wait()
return nil return nil
} }

View File

@@ -5,7 +5,6 @@ import (
"net" "net"
"github.com/pires/go-proxyproto" "github.com/pires/go-proxyproto"
"goauthentik.io/internal/config"
"goauthentik.io/internal/utils" "goauthentik.io/internal/utils"
) )
@@ -37,8 +36,7 @@ func (ls *LDAPServer) getCertificates(info *tls.ClientHelloInfo) (*tls.Certifica
return ls.defaultCert, nil return ls.defaultCert, nil
} }
func (ls *LDAPServer) StartLDAPTLSServer() error { func (ls *LDAPServer) StartLDAPTLSServer(listen string) error {
listen := config.Get().Listen.LDAPS
tlsConfig := utils.GetTLSConfig() tlsConfig := utils.GetTLSConfig()
tlsConfig.GetCertificate = ls.getCertificates tlsConfig.GetCertificate = ls.getCertificates

View File

@@ -1,16 +1,8 @@
package metrics package metrics
import ( import (
"net/http"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/config"
"goauthentik.io/internal/utils/sentry"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
) )
var ( var (
@@ -23,19 +15,3 @@ var (
Help: "Total number of rejected requests", Help: "Total number of rejected requests",
}, []string{"outpost_name", "type", "reason", "app"}) }, []string{"outpost_name", "type", "reason", "app"})
) )
func RunServer() {
m := mux.NewRouter()
l := log.WithField("logger", "authentik.outpost.metrics")
m.Use(sentry.SentryNoSampleMiddleware)
m.HandleFunc("/outpost.goauthentik.io/ping", func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(204)
})
m.Path("/metrics").Handler(promhttp.Handler())
listen := config.Get().Listen.Metrics
l.WithField("listen", listen).Info("Starting Metrics server")
err := http.ListenAndServe(listen, m)
if err != nil {
l.WithError(err).Warning("Failed to start metrics listener")
}
}

View File

@@ -1,16 +1,8 @@
package metrics package metrics
import ( import (
"net/http"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/config"
"goauthentik.io/internal/utils/sentry"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
) )
var ( var (
@@ -23,19 +15,3 @@ var (
Help: "Proxy upstream response latencies in seconds", Help: "Proxy upstream response latencies in seconds",
}, []string{"outpost_name", "method", "scheme", "host", "upstream_host"}) }, []string{"outpost_name", "method", "scheme", "host", "upstream_host"})
) )
func RunServer() {
m := mux.NewRouter()
l := log.WithField("logger", "authentik.outpost.metrics")
m.Use(sentry.SentryNoSampleMiddleware)
m.HandleFunc("/outpost.goauthentik.io/ping", func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(204)
})
m.Path("/metrics").Handler(promhttp.Handler())
listen := config.Get().Listen.Metrics
l.WithField("listen", listen).Info("Starting Metrics server")
err := http.ListenAndServe(listen, m)
if err != nil {
l.WithError(err).Warning("Failed to start metrics listener")
}
}

View File

@@ -18,7 +18,6 @@ import (
"goauthentik.io/internal/crypto" "goauthentik.io/internal/crypto"
"goauthentik.io/internal/outpost/ak" "goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/proxyv2/application" "goauthentik.io/internal/outpost/proxyv2/application"
"goauthentik.io/internal/outpost/proxyv2/metrics"
"goauthentik.io/internal/utils" "goauthentik.io/internal/utils"
sentryutils "goauthentik.io/internal/utils/sentry" sentryutils "goauthentik.io/internal/utils/sentry"
"goauthentik.io/internal/utils/web" "goauthentik.io/internal/utils/web"
@@ -127,11 +126,10 @@ func (ps *ProxyServer) getCertificates(info *tls.ClientHelloInfo) (*tls.Certific
} }
// ServeHTTP constructs a net.Listener and starts handling HTTP requests // ServeHTTP constructs a net.Listener and starts handling HTTP requests
func (ps *ProxyServer) ServeHTTP() { func (ps *ProxyServer) ServeHTTP(listen string) {
listenAddress := config.Get().Listen.HTTP listener, err := net.Listen("tcp", listen)
listener, err := net.Listen("tcp", listenAddress)
if err != nil { if err != nil {
ps.log.WithField("listen", listenAddress).WithError(err).Warning("Failed to listen") ps.log.WithField("listen", listen).WithError(err).Warning("Failed to listen")
return return
} }
proxyListener := &proxyproto.Listener{Listener: listener, ConnPolicy: utils.GetProxyConnectionPolicy()} proxyListener := &proxyproto.Listener{Listener: listener, ConnPolicy: utils.GetProxyConnectionPolicy()}
@@ -142,18 +140,17 @@ func (ps *ProxyServer) ServeHTTP() {
} }
}() }()
ps.log.WithField("listen", listenAddress).Info("Starting HTTP server") ps.log.WithField("listen", listen).Info("Starting HTTP server")
ps.serve(proxyListener) ps.serve(proxyListener)
ps.log.WithField("listen", listenAddress).Info("Stopping HTTP server") ps.log.WithField("listen", listen).Info("Stopping HTTP server")
} }
// ServeHTTPS constructs a net.Listener and starts handling HTTPS requests // ServeHTTPS constructs a net.Listener and starts handling HTTPS requests
func (ps *ProxyServer) ServeHTTPS() { func (ps *ProxyServer) ServeHTTPS(listen string) {
listenAddress := config.Get().Listen.HTTPS
tlsConfig := utils.GetTLSConfig() tlsConfig := utils.GetTLSConfig()
tlsConfig.GetCertificate = ps.getCertificates tlsConfig.GetCertificate = ps.getCertificates
ln, err := net.Listen("tcp", listenAddress) ln, err := net.Listen("tcp", listen)
if err != nil { if err != nil {
ps.log.WithError(err).Warning("Failed to listen (TLS)") ps.log.WithError(err).Warning("Failed to listen (TLS)")
return return
@@ -167,26 +164,40 @@ func (ps *ProxyServer) ServeHTTPS() {
}() }()
tlsListener := tls.NewListener(proxyListener, tlsConfig) tlsListener := tls.NewListener(proxyListener, tlsConfig)
ps.log.WithField("listen", listenAddress).Info("Starting HTTPS server") ps.log.WithField("listen", listen).Info("Starting HTTPS server")
ps.serve(tlsListener) ps.serve(tlsListener)
ps.log.WithField("listen", listenAddress).Info("Stopping HTTPS server") ps.log.WithField("listen", listen).Info("Stopping HTTPS server")
} }
func (ps *ProxyServer) Start() error { func (ps *ProxyServer) Start() error {
listenHttp := config.Get().Listen.HTTP
listenHttps := config.Get().Listen.HTTPS
listenMetrics := config.Get().Listen.Metrics
metricsRouter := ak.MetricsRouter()
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(3) wg.Add(len(listenHttp) + len(listenHttps) + 1 + len(listenMetrics))
for _, listen := range listenHttp {
go func() { go func() {
defer wg.Done() defer wg.Done()
ps.ServeHTTP() ps.ServeHTTP(listen)
}() }()
}
for _, listen := range listenHttps {
go func() { go func() {
defer wg.Done() defer wg.Done()
ps.ServeHTTPS() ps.ServeHTTPS(listen)
}() }()
}
go func() { go func() {
defer wg.Done() defer wg.Done()
metrics.RunServer() ak.RunMetricsUnix(metricsRouter)
}() }()
for _, listen := range listenMetrics {
go func() {
defer wg.Done()
ak.RunMetricsServer(listen, metricsRouter)
}()
}
return nil return nil
} }

View File

@@ -1,28 +0,0 @@
package metrics
import (
"net/http"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/config"
"goauthentik.io/internal/utils/sentry"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func RunServer() {
m := mux.NewRouter()
l := log.WithField("logger", "authentik.outpost.metrics")
m.Use(sentry.SentryNoSampleMiddleware)
m.HandleFunc("/outpost.goauthentik.io/ping", func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(204)
})
m.Path("/metrics").Handler(promhttp.Handler())
listen := config.Get().Listen.Metrics
l.WithField("listen", listen).Info("Starting Metrics server")
err := http.ListenAndServe(listen, m)
if err != nil {
l.WithError(err).Warning("Failed to start metrics listener")
}
}

View File

@@ -9,9 +9,9 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/wwt/guac" "github.com/wwt/guac"
"goauthentik.io/internal/config"
"goauthentik.io/internal/outpost/ak" "goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/rac/connection" "goauthentik.io/internal/outpost/rac/connection"
"goauthentik.io/internal/outpost/rac/metrics"
) )
type RACServer struct { type RACServer struct {
@@ -92,12 +92,10 @@ func (rs *RACServer) wsHandler(ctx context.Context, msg ak.Event) error {
} }
func (rs *RACServer) Start() error { func (rs *RACServer) Start() error {
listenMetrics := config.Get().Listen.Metrics
metricsRouter := ak.MetricsRouter()
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(2) wg.Add(1 + 1 + len(listenMetrics))
go func() {
defer wg.Done()
metrics.RunServer()
}()
go func() { go func() {
defer wg.Done() defer wg.Done()
err := rs.startGuac() err := rs.startGuac()
@@ -105,6 +103,16 @@ func (rs *RACServer) Start() error {
panic(err) panic(err)
} }
}() }()
go func() {
defer wg.Done()
ak.RunMetricsUnix(metricsRouter)
}()
for _, listen := range listenMetrics {
go func() {
defer wg.Done()
ak.RunMetricsServer(listen, metricsRouter)
}()
}
wg.Wait() wg.Wait()
return nil return nil
} }

View File

@@ -1,16 +1,8 @@
package metrics package metrics
import ( import (
"net/http"
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/config"
"goauthentik.io/internal/utils/sentry"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
) )
var ( var (
@@ -23,19 +15,3 @@ var (
Help: "Total number of rejected requests", Help: "Total number of rejected requests",
}, []string{"outpost_name", "reason", "app"}) }, []string{"outpost_name", "reason", "app"})
) )
func RunServer() {
m := mux.NewRouter()
l := log.WithField("logger", "authentik.outpost.metrics")
m.Use(sentry.SentryNoSampleMiddleware)
m.HandleFunc("/outpost.goauthentik.io/ping", func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(204)
})
m.Path("/metrics").Handler(promhttp.Handler())
listen := config.Get().Listen.Metrics
l.WithField("listen", listen).Info("Starting Metrics server")
err := http.ListenAndServe(listen, m)
if err != nil {
l.WithError(err).Warning("Failed to start metrics listener")
}
}

View File

@@ -10,7 +10,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/internal/config" "goauthentik.io/internal/config"
"goauthentik.io/internal/outpost/ak" "goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/radius/metrics" "golang.org/x/sync/errgroup"
"layeh.com/radius" "layeh.com/radius"
) )
@@ -30,7 +30,7 @@ type ProviderInstance struct {
} }
type RadiusServer struct { type RadiusServer struct {
s radius.PacketServer s []*radius.PacketServer
log *log.Entry log *log.Entry
ac *ak.APIController ac *ak.APIController
cryptoStore *ak.CryptoStore cryptoStore *ak.CryptoStore
@@ -45,10 +45,13 @@ func NewServer(ac *ak.APIController) ak.Outpost {
providers: map[int32]*ProviderInstance{}, providers: map[int32]*ProviderInstance{},
cryptoStore: ak.NewCryptoStore(ac.Client.CryptoAPI), cryptoStore: ak.NewCryptoStore(ac.Client.CryptoAPI),
} }
rs.s = radius.PacketServer{ listenRadius := config.Get().Listen.Radius
for _, listen := range listenRadius {
rs.s = append(rs.s, &radius.PacketServer{
Handler: rs, Handler: rs,
SecretSource: rs, SecretSource: rs,
Addr: config.Get().Listen.Radius, Addr: listen,
})
} }
return rs return rs
} }
@@ -95,29 +98,44 @@ func (rs *RadiusServer) RADIUSSecret(ctx context.Context, remoteAddr net.Addr) (
} }
func (rs *RadiusServer) Start() error { func (rs *RadiusServer) Start() error {
listenMetrics := config.Get().Listen.Metrics
metricsRouter := ak.MetricsRouter()
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(2) wg.Add(len(rs.s) + 1 + len(listenMetrics))
for _, s := range rs.s {
go func() { go func() {
defer wg.Done() defer wg.Done()
metrics.RunServer() rs.log.WithField("listen", s.Addr).Info("Starting radius server")
}() err := s.ListenAndServe()
go func() {
defer wg.Done()
rs.log.WithField("listen", rs.s.Addr).Info("Starting radius server")
err := rs.s.ListenAndServe()
if err != nil { if err != nil {
panic(err) panic(err)
} }
}() }()
}
go func() {
defer wg.Done()
ak.RunMetricsUnix(metricsRouter)
}()
for _, listen := range listenMetrics {
go func() {
defer wg.Done()
ak.RunMetricsServer(listen, metricsRouter)
}()
}
wg.Wait() wg.Wait()
return nil return nil
} }
func (rs *RadiusServer) Stop() error { func (rs *RadiusServer) Stop() error {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
err := rs.s.Shutdown(ctx) errs := new(errgroup.Group)
for _, s := range rs.s {
errs.Go(func() error {
return s.Shutdown(ctx)
})
}
cancel() cancel()
return err return errs.Wait()
} }
func (rs *RadiusServer) TimerFlowCacheExpiry(context.Context) {} func (rs *RadiusServer) TimerFlowCacheExpiry(context.Context) {}

View File

@@ -0,0 +1,43 @@
package unix
import (
"net"
)
type Listener struct {
*net.UnixListener
}
type Conn struct {
net.Conn
}
func Listen(path string) (*Listener, error) {
addr, err := net.ResolveUnixAddr("unix", path)
if err != nil {
return nil, err
}
ln, err := net.ListenUnix("unix", addr)
if err != nil {
return nil, err
}
return &Listener{
ln,
}, nil
}
func (l *Listener) Accept() (net.Conn, error) {
c, err := l.UnixListener.Accept()
if err != nil {
return nil, err
}
return &Conn{c}, nil
}
func (c *Conn) LocalAddr() net.Addr {
return &net.TCPAddr{IP: net.IPv6loopback, Port: 0}
}
func (c *Conn) RemoteAddr() net.Addr {
return &net.TCPAddr{IP: net.IPv6loopback, Port: 0}
}

View File

@@ -19,7 +19,7 @@ var Requests = promauto.NewHistogramVec(prometheus.HistogramOpts{
Help: "API request latencies in seconds", Help: "API request latencies in seconds",
}, []string{"dest"}) }, []string{"dest"})
func (ws *WebServer) runMetricsServer() { func (ws *WebServer) runMetricsServer(listen string) {
l := log.WithField("logger", "authentik.router.metrics") l := log.WithField("logger", "authentik.router.metrics")
m := mux.NewRouter() m := mux.NewRouter()
@@ -49,10 +49,10 @@ func (ws *WebServer) runMetricsServer() {
return return
} }
}) })
l.WithField("listen", config.Get().Listen.Metrics).Info("Starting Metrics server") l.WithField("listen", listen).Info("Starting Metrics server")
err := http.ListenAndServe(config.Get().Listen.Metrics, m) err := http.ListenAndServe(listen, m)
if err != nil { if err != nil {
l.WithError(err).Warning("Failed to start metrics server") l.WithError(err).Warning("Failed to start metrics server")
} }
l.WithField("listen", config.Get().Listen.Metrics).Info("Stopping Metrics server") l.WithField("listen", listen).Info("Stopping Metrics server")
} }

View File

@@ -21,17 +21,18 @@ import (
"goauthentik.io/internal/config" "goauthentik.io/internal/config"
"goauthentik.io/internal/constants" "goauthentik.io/internal/constants"
"goauthentik.io/internal/gounicorn" "goauthentik.io/internal/gounicorn"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/proxyv2" "goauthentik.io/internal/outpost/proxyv2"
"goauthentik.io/internal/utils" "goauthentik.io/internal/utils"
"goauthentik.io/internal/utils/unix"
"goauthentik.io/internal/utils/web" "goauthentik.io/internal/utils/web"
"goauthentik.io/internal/web/brand_tls" "goauthentik.io/internal/web/brand_tls"
) )
const ( const (
SocketName = "authentik.sock"
IPCKeyFile = "authentik-core-ipc.key" IPCKeyFile = "authentik-core-ipc.key"
MetricsKeyFile = "authentik-core-metrics.key" MetricsKeyFile = "authentik-core-metrics.key"
UnixSocketName = "authentik-core.sock" CoreSocketName = "authentik-core.sock"
) )
type WebServer struct { type WebServer struct {
@@ -64,7 +65,7 @@ func NewWebServer() *WebServer {
loggingHandler.Use(web.NewLoggingHandler(l, nil)) loggingHandler.Use(web.NewLoggingHandler(l, nil))
tmp := os.TempDir() tmp := os.TempDir()
socketPath := path.Join(tmp, UnixSocketName) socketPath := path.Join(tmp, CoreSocketName)
// create http client to talk to backend, normal client if we're in debug more // create http client to talk to backend, normal client if we're in debug more
// and a client that connects to our socket when in non debug mode // and a client that connects to our socket when in non debug mode
@@ -140,7 +141,8 @@ func (ws *WebServer) prepareKeys() {
func (ws *WebServer) Start() { func (ws *WebServer) Start() {
ws.prepareKeys() ws.prepareKeys()
u, err := url.Parse(fmt.Sprintf("http://%s%s", config.Get().Listen.HTTP, config.Get().Web.Path)) socketPath := path.Join(os.TempDir(), SocketName)
u, err := url.Parse(fmt.Sprintf("http://localhost%s", config.Get().Web.Path))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -150,7 +152,11 @@ func (ws *WebServer) Start() {
apiConfig.HTTPClient = &http.Client{ apiConfig.HTTPClient = &http.Client{
Transport: web.NewUserAgentTransport( Transport: web.NewUserAgentTransport(
constants.UserAgentIPC(), constants.UserAgentIPC(),
ak.GetTLSTransport(), &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", socketPath)
},
},
), ),
} }
apiConfig.Servers = api.ServerConfigurations{ apiConfig.Servers = api.ServerConfigurations{
@@ -171,10 +177,18 @@ func (ws *WebServer) Start() {
go tw.Start() go tw.Start()
}) })
go ws.runMetricsServer() for _, listen := range config.Get().Listen.Metrics {
go ws.runMetricsServer(listen)
}
go ws.attemptStartBackend() go ws.attemptStartBackend()
go ws.listenPlain() _ = os.Remove(socketPath)
go ws.listenTLS() go ws.listenUnix(socketPath)
for _, listen := range config.Get().Listen.HTTP {
go ws.listenPlain(listen)
}
for _, listen := range config.Get().Listen.HTTPS {
go ws.listenTLS(listen)
}
} }
func (ws *WebServer) attemptStartBackend() { func (ws *WebServer) attemptStartBackend() {
@@ -225,23 +239,41 @@ func (ws *WebServer) Shutdown() {
ws.stop <- struct{}{} ws.stop <- struct{}{}
} }
func (ws *WebServer) listenPlain() { func (ws *WebServer) listenUnix(listen string) {
ln, err := net.Listen("tcp", config.Get().Listen.HTTP) ln, err := unix.Listen(listen)
if err != nil { if err != nil {
ws.log.WithError(err).Warning("failed to listen") ws.log.WithField("listen", listen).WithError(err).Warning("failed to listen")
return
}
defer func() {
err := ln.Close()
if err != nil {
ws.log.WithField("listen", listen).WithError(err).Warning("failed to close listener")
}
}()
ws.log.WithField("listen", listen).Info("Starting HTTP server")
ws.serve(ln)
ws.log.WithField("listen", listen).Info("Stopping HTTP server")
}
func (ws *WebServer) listenPlain(listen string) {
ln, err := net.Listen("tcp", listen)
if err != nil {
ws.log.WithField("listen", listen).WithError(err).Warning("failed to listen")
return return
} }
proxyListener := &proxyproto.Listener{Listener: ln, ConnPolicy: utils.GetProxyConnectionPolicy()} proxyListener := &proxyproto.Listener{Listener: ln, ConnPolicy: utils.GetProxyConnectionPolicy()}
defer func() { defer func() {
err := proxyListener.Close() err := proxyListener.Close()
if err != nil { if err != nil {
ws.log.WithError(err).Warning("failed to close proxy listener") ws.log.WithField("listen", listen).WithError(err).Warning("failed to close proxy listener")
} }
}() }()
ws.log.WithField("listen", config.Get().Listen.HTTP).Info("Starting HTTP server") ws.log.WithField("listen", listen).Info("Starting HTTP server")
ws.serve(proxyListener) ws.serve(proxyListener)
ws.log.WithField("listen", config.Get().Listen.HTTP).Info("Stopping HTTP server") ws.log.WithField("listen", listen).Info("Stopping HTTP server")
} }
func (ws *WebServer) serve(listener net.Listener) { func (ws *WebServer) serve(listener net.Listener) {

View File

@@ -6,7 +6,6 @@ import (
"github.com/pires/go-proxyproto" "github.com/pires/go-proxyproto"
"goauthentik.io/internal/config"
"goauthentik.io/internal/crypto" "goauthentik.io/internal/crypto"
"goauthentik.io/internal/utils" "goauthentik.io/internal/utils"
"goauthentik.io/internal/utils/web" "goauthentik.io/internal/utils/web"
@@ -48,13 +47,13 @@ func (ws *WebServer) GetCertificate() func(ch *tls.ClientHelloInfo) (*tls.Config
} }
// ServeHTTPS constructs a net.Listener and starts handling HTTPS requests // ServeHTTPS constructs a net.Listener and starts handling HTTPS requests
func (ws *WebServer) listenTLS() { func (ws *WebServer) listenTLS(listen string) {
tlsConfig := utils.GetTLSConfig() tlsConfig := utils.GetTLSConfig()
tlsConfig.GetConfigForClient = ws.GetCertificate() tlsConfig.GetConfigForClient = ws.GetCertificate()
ln, err := net.Listen("tcp", config.Get().Listen.HTTPS) ln, err := net.Listen("tcp", listen)
if err != nil { if err != nil {
ws.log.WithError(err).Warning("failed to listen (TLS)") ws.log.WithField("listen", listen).WithError(err).Warning("failed to listen (TLS)")
return return
} }
proxyListener := &proxyproto.Listener{ proxyListener := &proxyproto.Listener{
@@ -71,7 +70,7 @@ func (ws *WebServer) listenTLS() {
}() }()
tlsListener := tls.NewListener(proxyListener, tlsConfig) tlsListener := tls.NewListener(proxyListener, tlsConfig)
ws.log.WithField("listen", config.Get().Listen.HTTPS).Info("Starting HTTPS server") ws.log.WithField("listen", listen).Info("Starting HTTPS server")
ws.serve(tlsListener) ws.serve(tlsListener)
ws.log.WithField("listen", config.Get().Listen.HTTPS).Info("Stopping HTTPS server") ws.log.WithField("listen", listen).Info("Stopping HTTPS server")
} }

View File

@@ -237,43 +237,43 @@ Defaults to `seconds=60`.
##### `AUTHENTIK_LISTEN__HTTP` ##### `AUTHENTIK_LISTEN__HTTP`
Listening address:port for HTTP. List of comma-separated `address:port` values for HTTP.
Applies to the Server, the Worker, and Proxy outposts. Applies to the Server, the Worker, and Proxy outposts.
Defaults to `0.0.0.0:9000`. Defaults to `[::]:9000`.
##### `AUTHENTIK_LISTEN__HTTPS` ##### `AUTHENTIK_LISTEN__HTTPS`
Listening address:port for HTTPS. List of comma-separated `address:port` values for HTTPS.
Applies to the Server and Proxy outposts. Applies to the Server and Proxy outposts.
Defaults to `0.0.0.0:9443`. Defaults to `[::]:9443`.
##### `AUTHENTIK_LISTEN__LDAP` ##### `AUTHENTIK_LISTEN__LDAP`
Listening address:port for LDAP. List of comma-separated `address:port` values for LDAP.
Applies to LDAP outposts. Applies to LDAP outposts.
Defaults to `0.0.0.0:3389`. Defaults to `[::]:3389`.
##### `AUTHENTIK_LISTEN__LDAPS` ##### `AUTHENTIK_LISTEN__LDAPS`
Listening address:port for LDAPS. List of comma-separated `address:port` values for LDAPS.
Applies to LDAP outposts. Applies to LDAP outposts.
Defaults to `0.0.0.0:6636`. Defaults to `[::]:6636`.
##### `AUTHENTIK_LISTEN__METRICS` ##### `AUTHENTIK_LISTEN__METRICS`
Listening address:port for Prometheus metrics. List of comma-separated `address:port` values for Prometheus metrics.
Applies to all. Applies to all.
Defaults to `0.0.0.0:9300`. Defaults to `[::]:9300`.
##### `AUTHENTIK_LISTEN__DEBUG` ##### `AUTHENTIK_LISTEN__DEBUG`

View File

@@ -6,7 +6,11 @@ draft: true
## Highlights ## Highlights
<!-- ## Breaking changes --> ## Breaking changes
### Listening on multiple IPs
For advanced use cases, authentik now supports setting listening settings to a comma-separated list of IPs. With this change, the default IP we listen on changed from `0.0.0.0` to `[::]` to better match ecosystem standards. Some IPv4-only environments might need to adapt those settings.
## New features and improvements ## New features and improvements