package server import ( "crypto/tls" "crypto/x509" "fmt" "net" "os" "github.com/sirupsen/logrus" "github.com/valyala/fasthttp" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/logging" "github.com/authelia/authelia/v4/internal/middlewares" ) // CreateDefaultServer Create Authelia's internal webserver with the given configuration and providers. func CreateDefaultServer(config *schema.Configuration, providers middlewares.Providers) (server *fasthttp.Server, listener net.Listener, paths []string, isTLS bool, err error) { if err = providers.Templates.LoadTemplatedAssets(assets); err != nil { return nil, nil, nil, false, fmt.Errorf("failed to load templated assets: %w", err) } server = &fasthttp.Server{ ErrorHandler: handleError(), Handler: handleRouter(config, providers), NoDefaultServerHeader: true, ReadBufferSize: config.Server.Buffers.Read, WriteBufferSize: config.Server.Buffers.Write, ReadTimeout: config.Server.Timeouts.Read, WriteTimeout: config.Server.Timeouts.Write, IdleTimeout: config.Server.Timeouts.Idle, Logger: logging.LoggerPrintf(logrus.DebugLevel), } var ( connectionScheme = schemeHTTP ) if listener, err = config.Server.Address.Listener(); err != nil { return nil, nil, nil, false, fmt.Errorf("error occurred while attempting to initialize main server listener for address '%s': %w", config.Server.Address.String(), err) } if config.Server.TLS.Certificate != "" && config.Server.TLS.Key != "" { isTLS, connectionScheme = true, schemeHTTPS tlsConfig, err := loadTLSCertificates(config) if err != nil { return nil, nil, nil, false, fmt.Errorf("HTTP server: %w", err) } // Apply configuration listener = tls.NewListener(listener, tlsConfig.Clone()) } if err = writeHealthCheckEnv(config.Server.DisableHealthcheck, connectionScheme, config.Server.Address.Hostname(), config.Server.Address.Path(), config.Server.Address.Port()); err != nil { return nil, nil, nil, false, fmt.Errorf("unable to configure healthcheck: %w", err) } paths = []string{"/"} if config.Server.Address.Path() != "/" { paths = append(paths, config.Server.Address.Path()) } return server, listener, paths, isTLS, nil } // CreateMetricsServer creates a metrics server. func CreateMetricsServer(config *schema.Configuration, providers middlewares.Providers) (server *fasthttp.Server, listener net.Listener, paths []string, tls bool, err error) { if providers.Metrics == nil { return } server = &fasthttp.Server{ ErrorHandler: handleError(), NoDefaultServerHeader: true, Handler: handleMetrics(config.Telemetry.Metrics.Address.Path()), ReadBufferSize: config.Telemetry.Metrics.Buffers.Read, WriteBufferSize: config.Telemetry.Metrics.Buffers.Write, ReadTimeout: config.Telemetry.Metrics.Timeouts.Read, WriteTimeout: config.Telemetry.Metrics.Timeouts.Write, IdleTimeout: config.Telemetry.Metrics.Timeouts.Idle, Logger: logging.LoggerPrintf(logrus.DebugLevel), } if listener, err = config.Telemetry.Metrics.Address.Listener(); err != nil { return nil, nil, nil, false, fmt.Errorf("error occurred while attempting to initialize metrics telemetry server listener for address '%s': %w", config.Telemetry.Metrics.Address.String(), err) } return server, listener, []string{config.Telemetry.Metrics.Address.Path()}, false, nil } // CreateGRPCServer creates a server for handling gRPC authentication requests from an envoy proxy. func CreateGRPCServer(config *schema.Configuration, providers middlewares.Providers) (server *grpc.Server, listener net.Listener, tls bool, err error) { if config.Server.GRPC.Address == nil { return nil, nil, false, nil } // Initialize gPRC server lis, err := config.Server.GRPC.Address.Listener() if err != nil { return nil, nil, false, fmt.Errorf("error occurred while attempting to initialize grcp server listener for address '%s': %w", config.Server.GRPC.Address, err) } opts := []grpc.ServerOption{grpc.MaxConcurrentStreams(10)} if config.Server.TLS.Certificate != "" && config.Server.TLS.Key != "" && !config.Server.GRPC.DisableTLS { tlsConfig, err := loadTLSCertificates(config) if err != nil { return nil, nil, false, fmt.Errorf("gRPC server: %w", err) } opts = append(opts, grpc.Creds(credentials.NewTLS(tlsConfig))) tls = true } s := grpc.NewServer(opts...) handleGRCP(s, config, providers) return s, lis, tls, nil } // loadTLSCertificates loads the server and client certificates from the files and returns a // tls.Config object for the server func loadTLSCertificates(config *schema.Configuration) (*tls.Config, error) { // Load the server certificates serverCert, err := tls.LoadX509KeyPair(config.Server.TLS.Certificate, config.Server.TLS.Key) if err != nil { return nil, fmt.Errorf("unable to load server certificate '%s' or key '%s': %w", config.Server.TLS.Certificate, config.Server.TLS.Key, err) } // Create the tls configuration tlsConfig := &tls.Config{ Certificates: []tls.Certificate{serverCert}, ClientAuth: tls.NoClientCert, } // Load client certificates if len(config.Server.TLS.ClientCertificates) > 0 { caCertPool := x509.NewCertPool() var cert []byte for _, path := range config.Server.TLS.ClientCertificates { if cert, err = os.ReadFile(path); err != nil { return nil, fmt.Errorf("unable to load tls client certificate '%s': %w", path, err) } caCertPool.AppendCertsFromPEM(cert) } // ClientCAs should never be nil, otherwise the system cert pool is used for client authentication, // but we don't want everybody on the Internet to be able to authenticate. tlsConfig.ClientCAs = caCertPool tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert } return tlsConfig, nil }