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
listen:
http: 0.0.0.0:9000
https: 0.0.0.0:9443
ldap: 0.0.0.0:3389
ldaps: 0.0.0.0:6636
radius: 0.0.0.0:1812
metrics: 0.0.0.0:9300
http:
- "[::]:9000"
https:
- "[::]:9443"
ldap:
- "[::]:3389"
ldaps:
- "[::]:6636"
radius:
- "[::]:1812"
metrics:
- "[::]:9300"
debug: 0.0.0.0:9900
debug_py: 0.0.0.0:9901
trusted_proxy_cidrs:

View File

@@ -224,7 +224,10 @@ class WorkerHealthcheckMiddleware(Middleware):
thread: HTTPServerThread | None
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:
port = int(port)
@@ -323,7 +326,10 @@ class MetricsMiddleware(BaseMetricsMiddleware):
return []
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:
port = int(port)

View File

@@ -1,7 +1,9 @@
package main
import (
"context"
"fmt"
"net"
"net/http"
"os"
"path"
@@ -12,7 +14,8 @@ import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"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")
@@ -44,9 +47,15 @@ func init() {
func checkServer() int {
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)
if err != nil {
log.WithError(err).Warning("failed to send healthcheck request")

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ import (
"crypto/fips140"
"fmt"
"math/rand"
"net"
"net/http"
"net/url"
"os"
@@ -54,19 +55,44 @@ type APIController struct {
// NewAPIController initialise new API Controller instance from URL and API token
func NewAPIController(akURL url.URL, token string) *APIController {
rsp := sentry.StartSpan(context.Background(), "authentik.outposts.init")
log := log.WithField("logger", "authentik.outpost.ak-api-controller")
originalAkURL := akURL
var client http.Client
if akURL.Scheme == "unix" {
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(
constants.UserAgentOutpost(),
web.NewTracingTransport(
rsp.Context(),
GetTLSTransport(),
),
),
}
}
apiConfig := api.NewConfiguration()
apiConfig.Host = akURL.Host
apiConfig.Scheme = akURL.Scheme
apiConfig.HTTPClient = &http.Client{
Transport: web.NewUserAgentTransport(
constants.UserAgentOutpost(),
web.NewTracingTransport(
rsp.Context(),
GetTLSTransport(),
),
),
}
apiConfig.HTTPClient = &client
apiConfig.Servers = api.ServerConfigurations{
{
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
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
// The service account this token belongs to should only have access to a single outpost
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("offset", ac.reloadOffset.String()).Debug("HA Reload offset")
err = ac.initEvent(akURL, outpost.Pk)
err = ac.initEvent(originalAkURL, outpost.Pk)
if err != nil {
go ac.recentEvents()
}

View File

@@ -5,6 +5,7 @@ import (
"crypto/tls"
"fmt"
"maps"
"net"
"net/http"
"net/url"
"strconv"
@@ -45,9 +46,19 @@ func (ac *APIController) initEvent(akURL url.URL, outpostUUID string) error {
dialer := websocket.Dialer{
Proxy: http.ProxyFromEnvironment,
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,
},
}
}
wsu := ac.getWebsocketURL(akURL, outpostUUID, query).String()

View File

@@ -1,13 +1,16 @@
package healthcheck
import (
"fmt"
"context"
"net"
"net/http"
"os"
"path"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"goauthentik.io/internal/config"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/utils/web"
)
@@ -21,9 +24,15 @@ var Command = &cobra.Command{
func check() int {
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)
if err != nil {
log.WithError(err).Warning("failed to send healthcheck request")

View File

@@ -1,12 +1,22 @@
package ak
import (
"net/http"
"os"
"path"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus"
"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 (
OutpostInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
MetricsSocketName = "authentik-metrics.sock"
OutpostInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "authentik_outpost_info",
Help: "Outpost info",
}, []string{"outpost_name", "outpost_type", "uuid", "version", "build"})
@@ -19,3 +29,43 @@ var (
Help: "Connection status",
}, []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/crypto"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/ldap/metrics"
"goauthentik.io/internal/utils"
"beryju.io/ldap"
@@ -63,9 +62,7 @@ func (ls *LDAPServer) Type() string {
return "ldap"
}
func (ls *LDAPServer) StartLDAPServer() error {
listen := config.Get().Listen.LDAP
func (ls *LDAPServer) StartLDAPServer(listen string) error {
ln, err := net.Listen("tcp", listen)
if err != nil {
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 {
listenLdap := config.Get().Listen.LDAP
listenLdaps := config.Get().Listen.LDAPS
listenMetrics := config.Get().Listen.Metrics
metricsRouter := ak.MetricsRouter()
wg := sync.WaitGroup{}
wg.Add(3)
wg.Add(len(listenLdap) + len(listenLdaps) + 1 + len(listenMetrics))
for _, listen := range listenLdap {
go func() {
defer wg.Done()
err := ls.StartLDAPServer(listen)
if err != nil {
panic(err)
}
}()
}
for _, listen := range listenLdaps {
go func() {
defer wg.Done()
err := ls.StartLDAPTLSServer(listen)
if err != nil {
panic(err)
}
}()
}
go func() {
defer wg.Done()
metrics.RunServer()
}()
go func() {
defer wg.Done()
err := ls.StartLDAPServer()
if err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
err := ls.StartLDAPTLSServer()
if err != nil {
panic(err)
}
ak.RunMetricsUnix(metricsRouter)
}()
for _, listen := range listenMetrics {
go func() {
defer wg.Done()
ak.RunMetricsServer(listen, metricsRouter)
}()
}
wg.Wait()
return nil
}

View File

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

View File

@@ -1,16 +1,8 @@
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"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
@@ -23,19 +15,3 @@ var (
Help: "Total number of rejected requests",
}, []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
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/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
@@ -23,19 +15,3 @@ var (
Help: "Proxy upstream response latencies in seconds",
}, []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/outpost/ak"
"goauthentik.io/internal/outpost/proxyv2/application"
"goauthentik.io/internal/outpost/proxyv2/metrics"
"goauthentik.io/internal/utils"
sentryutils "goauthentik.io/internal/utils/sentry"
"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
func (ps *ProxyServer) ServeHTTP() {
listenAddress := config.Get().Listen.HTTP
listener, err := net.Listen("tcp", listenAddress)
func (ps *ProxyServer) ServeHTTP(listen string) {
listener, err := net.Listen("tcp", listen)
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
}
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.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
func (ps *ProxyServer) ServeHTTPS() {
listenAddress := config.Get().Listen.HTTPS
func (ps *ProxyServer) ServeHTTPS(listen string) {
tlsConfig := utils.GetTLSConfig()
tlsConfig.GetCertificate = ps.getCertificates
ln, err := net.Listen("tcp", listenAddress)
ln, err := net.Listen("tcp", listen)
if err != nil {
ps.log.WithError(err).Warning("Failed to listen (TLS)")
return
@@ -167,26 +164,40 @@ func (ps *ProxyServer) ServeHTTPS() {
}()
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.log.WithField("listen", listenAddress).Info("Stopping HTTPS server")
ps.log.WithField("listen", listen).Info("Stopping HTTPS server")
}
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.Add(3)
wg.Add(len(listenHttp) + len(listenHttps) + 1 + len(listenMetrics))
for _, listen := range listenHttp {
go func() {
defer wg.Done()
ps.ServeHTTP(listen)
}()
}
for _, listen := range listenHttps {
go func() {
defer wg.Done()
ps.ServeHTTPS(listen)
}()
}
go func() {
defer wg.Done()
ps.ServeHTTP()
}()
go func() {
defer wg.Done()
ps.ServeHTTPS()
}()
go func() {
defer wg.Done()
metrics.RunServer()
ak.RunMetricsUnix(metricsRouter)
}()
for _, listen := range listenMetrics {
go func() {
defer wg.Done()
ak.RunMetricsServer(listen, metricsRouter)
}()
}
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"
"github.com/wwt/guac"
"goauthentik.io/internal/config"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/rac/connection"
"goauthentik.io/internal/outpost/rac/metrics"
)
type RACServer struct {
@@ -92,12 +92,10 @@ func (rs *RACServer) wsHandler(ctx context.Context, msg ak.Event) error {
}
func (rs *RACServer) Start() error {
listenMetrics := config.Get().Listen.Metrics
metricsRouter := ak.MetricsRouter()
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
metrics.RunServer()
}()
wg.Add(1 + 1 + len(listenMetrics))
go func() {
defer wg.Done()
err := rs.startGuac()
@@ -105,6 +103,16 @@ func (rs *RACServer) Start() error {
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()
return nil
}

View File

@@ -1,16 +1,8 @@
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"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
@@ -23,19 +15,3 @@ var (
Help: "Total number of rejected requests",
}, []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"
"goauthentik.io/internal/config"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/radius/metrics"
"golang.org/x/sync/errgroup"
"layeh.com/radius"
)
@@ -30,7 +30,7 @@ type ProviderInstance struct {
}
type RadiusServer struct {
s radius.PacketServer
s []*radius.PacketServer
log *log.Entry
ac *ak.APIController
cryptoStore *ak.CryptoStore
@@ -45,10 +45,13 @@ func NewServer(ac *ak.APIController) ak.Outpost {
providers: map[int32]*ProviderInstance{},
cryptoStore: ak.NewCryptoStore(ac.Client.CryptoAPI),
}
rs.s = radius.PacketServer{
Handler: rs,
SecretSource: rs,
Addr: config.Get().Listen.Radius,
listenRadius := config.Get().Listen.Radius
for _, listen := range listenRadius {
rs.s = append(rs.s, &radius.PacketServer{
Handler: rs,
SecretSource: rs,
Addr: listen,
})
}
return rs
}
@@ -95,29 +98,44 @@ func (rs *RadiusServer) RADIUSSecret(ctx context.Context, remoteAddr net.Addr) (
}
func (rs *RadiusServer) Start() error {
listenMetrics := config.Get().Listen.Metrics
metricsRouter := ak.MetricsRouter()
wg := sync.WaitGroup{}
wg.Add(2)
wg.Add(len(rs.s) + 1 + len(listenMetrics))
for _, s := range rs.s {
go func() {
defer wg.Done()
rs.log.WithField("listen", s.Addr).Info("Starting radius server")
err := s.ListenAndServe()
if err != nil {
panic(err)
}
}()
}
go func() {
defer wg.Done()
metrics.RunServer()
}()
go func() {
defer wg.Done()
rs.log.WithField("listen", rs.s.Addr).Info("Starting radius server")
err := rs.s.ListenAndServe()
if err != nil {
panic(err)
}
ak.RunMetricsUnix(metricsRouter)
}()
for _, listen := range listenMetrics {
go func() {
defer wg.Done()
ak.RunMetricsServer(listen, metricsRouter)
}()
}
wg.Wait()
return nil
}
func (rs *RadiusServer) Stop() error {
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()
return err
return errs.Wait()
}
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",
}, []string{"dest"})
func (ws *WebServer) runMetricsServer() {
func (ws *WebServer) runMetricsServer(listen string) {
l := log.WithField("logger", "authentik.router.metrics")
m := mux.NewRouter()
@@ -49,10 +49,10 @@ func (ws *WebServer) runMetricsServer() {
return
}
})
l.WithField("listen", config.Get().Listen.Metrics).Info("Starting Metrics server")
err := http.ListenAndServe(config.Get().Listen.Metrics, m)
l.WithField("listen", listen).Info("Starting Metrics server")
err := http.ListenAndServe(listen, m)
if err != nil {
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/constants"
"goauthentik.io/internal/gounicorn"
"goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/proxyv2"
"goauthentik.io/internal/utils"
"goauthentik.io/internal/utils/unix"
"goauthentik.io/internal/utils/web"
"goauthentik.io/internal/web/brand_tls"
)
const (
SocketName = "authentik.sock"
IPCKeyFile = "authentik-core-ipc.key"
MetricsKeyFile = "authentik-core-metrics.key"
UnixSocketName = "authentik-core.sock"
CoreSocketName = "authentik-core.sock"
)
type WebServer struct {
@@ -64,7 +65,7 @@ func NewWebServer() *WebServer {
loggingHandler.Use(web.NewLoggingHandler(l, nil))
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
// 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() {
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 {
panic(err)
}
@@ -150,7 +152,11 @@ func (ws *WebServer) Start() {
apiConfig.HTTPClient = &http.Client{
Transport: web.NewUserAgentTransport(
constants.UserAgentIPC(),
ak.GetTLSTransport(),
&http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", socketPath)
},
},
),
}
apiConfig.Servers = api.ServerConfigurations{
@@ -171,10 +177,18 @@ func (ws *WebServer) Start() {
go tw.Start()
})
go ws.runMetricsServer()
for _, listen := range config.Get().Listen.Metrics {
go ws.runMetricsServer(listen)
}
go ws.attemptStartBackend()
go ws.listenPlain()
go ws.listenTLS()
_ = os.Remove(socketPath)
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() {
@@ -225,23 +239,41 @@ func (ws *WebServer) Shutdown() {
ws.stop <- struct{}{}
}
func (ws *WebServer) listenPlain() {
ln, err := net.Listen("tcp", config.Get().Listen.HTTP)
func (ws *WebServer) listenUnix(listen string) {
ln, err := unix.Listen(listen)
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
}
proxyListener := &proxyproto.Listener{Listener: ln, ConnPolicy: utils.GetProxyConnectionPolicy()}
defer func() {
err := proxyListener.Close()
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.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) {

View File

@@ -6,7 +6,6 @@ import (
"github.com/pires/go-proxyproto"
"goauthentik.io/internal/config"
"goauthentik.io/internal/crypto"
"goauthentik.io/internal/utils"
"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
func (ws *WebServer) listenTLS() {
func (ws *WebServer) listenTLS(listen string) {
tlsConfig := utils.GetTLSConfig()
tlsConfig.GetConfigForClient = ws.GetCertificate()
ln, err := net.Listen("tcp", config.Get().Listen.HTTPS)
ln, err := net.Listen("tcp", listen)
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
}
proxyListener := &proxyproto.Listener{
@@ -71,7 +70,7 @@ func (ws *WebServer) listenTLS() {
}()
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.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`
Listening address:port for HTTP.
List of comma-separated `address:port` values for HTTP.
Applies to the Server, the Worker, and Proxy outposts.
Defaults to `0.0.0.0:9000`.
Defaults to `[::]:9000`.
##### `AUTHENTIK_LISTEN__HTTPS`
Listening address:port for HTTPS.
List of comma-separated `address:port` values for HTTPS.
Applies to the Server and Proxy outposts.
Defaults to `0.0.0.0:9443`.
Defaults to `[::]:9443`.
##### `AUTHENTIK_LISTEN__LDAP`
Listening address:port for LDAP.
List of comma-separated `address:port` values for LDAP.
Applies to LDAP outposts.
Defaults to `0.0.0.0:3389`.
Defaults to `[::]:3389`.
##### `AUTHENTIK_LISTEN__LDAPS`
Listening address:port for LDAPS.
List of comma-separated `address:port` values for LDAPS.
Applies to LDAP outposts.
Defaults to `0.0.0.0:6636`.
Defaults to `[::]:6636`.
##### `AUTHENTIK_LISTEN__METRICS`
Listening address:port for Prometheus metrics.
List of comma-separated `address:port` values for Prometheus metrics.
Applies to all.
Defaults to `0.0.0.0:9300`.
Defaults to `[::]:9300`.
##### `AUTHENTIK_LISTEN__DEBUG`

View File

@@ -6,7 +6,11 @@ draft: true
## 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