headscale/hscontrol/app.go

959 lines
25 KiB
Go
Raw Normal View History

package hscontrol
2020-06-21 18:32:08 +08:00
import (
"context"
"crypto/tls"
"errors"
2020-06-21 18:32:08 +08:00
"fmt"
"io"
"net"
"net/http"
2021-02-22 06:54:15 +08:00
"os"
2021-11-03 05:46:15 +08:00
"os/signal"
"sort"
2022-11-04 01:11:22 +08:00
"strconv"
"strings"
"sync"
2021-11-03 05:46:15 +08:00
"syscall"
"time"
2020-06-21 18:32:08 +08:00
2021-10-19 03:27:52 +08:00
"github.com/coreos/go-oidc/v3/oidc"
2022-06-19 00:41:42 +08:00
"github.com/gorilla/mux"
2022-09-04 17:34:23 +08:00
grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/juanfont/headscale"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/db"
"github.com/juanfont/headscale/hscontrol/policy"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
2021-11-13 16:39:04 +08:00
"github.com/patrickmn/go-cache"
zerolog "github.com/philip-bui/grpc-zerolog"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/puzpuzpuz/xsync/v2"
zl "github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
2021-11-13 16:39:04 +08:00
"golang.org/x/oauth2"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
2022-02-13 01:05:30 +08:00
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/status"
2021-02-21 06:57:06 +08:00
"tailscale.com/tailcfg"
"tailscale.com/types/dnstype"
"tailscale.com/types/key"
2020-06-21 18:32:08 +08:00
)
var (
errSTUNAddressNotSet = errors.New("STUN address not set")
errUnsupportedDatabase = errors.New("unsupported DB")
errUnsupportedLetsEncryptChallengeType = errors.New(
2022-03-17 02:46:59 +08:00
"unknown value for Lets Encrypt challenge type",
)
)
const (
AuthPrefix = "Bearer "
updateInterval = 5000
HTTPReadTimeout = 30 * time.Second
HTTPShutdownTimeout = 3 * time.Second
privateKeyFileMode = 0o600
2021-11-16 03:18:14 +08:00
registerCacheExpiration = time.Minute * 15
registerCacheCleanup = time.Minute * 20
2022-01-30 23:46:57 +08:00
DisabledClientAuth = "disabled"
RelaxedClientAuth = "relaxed"
EnforcedClientAuth = "enforced"
)
// Headscale represents the base app of the service.
2020-06-21 18:32:08 +08:00
type Headscale struct {
cfg *Config
db *db.HSDatabase
dbString string
dbType string
dbDebug bool
privateKey2019 *key.MachinePrivate
noisePrivateKey *key.MachinePrivate
DERPMap *tailcfg.DERPMap
DERPServer *DERPServer
2021-10-23 00:55:14 +08:00
ACLPolicy *policy.ACLPolicy
2021-07-03 23:31:32 +08:00
lastStateChange *xsync.MapOf[string, time.Time]
2021-10-08 17:43:52 +08:00
oidcProvider *oidc.Provider
oauth2Config *oauth2.Config
registrationCache *cache.Cache
2022-07-12 02:33:24 +08:00
shutdownChan chan struct{}
pollNetMapStreamWG sync.WaitGroup
stateUpdateChan chan struct{}
cancelStateUpdateChan chan struct{}
2020-06-21 18:32:08 +08:00
}
2022-06-05 23:47:26 +08:00
func NewHeadscale(cfg *Config) (*Headscale, error) {
privateKey, err := readOrCreatePrivateKey(cfg.PrivateKeyPath)
if err != nil {
return nil, fmt.Errorf("failed to read or create private key: %w", err)
}
2022-08-20 06:23:33 +08:00
// TS2021 requires to have a different key from the legacy protocol.
noisePrivateKey, err := readOrCreatePrivateKey(cfg.NoisePrivateKeyPath)
if err != nil {
return nil, fmt.Errorf("failed to read or create Noise protocol private key: %w", err)
}
if privateKey.Equal(*noisePrivateKey) {
return nil, fmt.Errorf("private key and noise private key are the same: %w", err)
}
var dbString string
switch cfg.DBtype {
case db.Postgres:
2021-11-13 16:36:45 +08:00
dbString = fmt.Sprintf(
2022-08-16 15:56:36 +08:00
"host=%s dbname=%s user=%s",
2021-11-13 16:36:45 +08:00
cfg.DBhost,
cfg.DBname,
cfg.DBuser,
)
2022-11-24 20:02:34 +08:00
if sslEnabled, err := strconv.ParseBool(cfg.DBssl); err == nil {
if !sslEnabled {
2022-11-04 01:11:22 +08:00
dbString += " sslmode=disable"
}
} else {
dbString += fmt.Sprintf(" sslmode=%s", cfg.DBssl)
2022-08-18 02:32:20 +08:00
}
2022-08-16 15:56:36 +08:00
if cfg.DBport != 0 {
dbString += fmt.Sprintf(" port=%d", cfg.DBport)
}
if cfg.DBpass != "" {
dbString += fmt.Sprintf(" password=%s", cfg.DBpass)
}
case db.Sqlite:
dbString = cfg.DBpath
default:
2021-11-16 03:18:14 +08:00
return nil, errUnsupportedDatabase
}
registrationCache := cache.New(
registerCacheExpiration,
registerCacheCleanup,
)
app := Headscale{
2022-07-12 02:33:24 +08:00
cfg: cfg,
dbType: cfg.DBtype,
dbString: dbString,
privateKey2019: privateKey,
noisePrivateKey: noisePrivateKey,
2022-07-12 02:33:24 +08:00
registrationCache: registrationCache,
pollNetMapStreamWG: sync.WaitGroup{},
lastStateChange: xsync.NewMapOf[time.Time](),
stateUpdateChan: make(chan struct{}),
cancelStateUpdateChan: make(chan struct{}),
2020-06-21 18:32:08 +08:00
}
2021-07-04 19:24:05 +08:00
go app.watchStateChannel()
database, err := db.NewHeadscaleDatabase(
cfg.DBtype,
dbString,
cfg.OIDC.StripEmaildomain,
app.dbDebug,
app.stateUpdateChan,
cfg.IPPrefixes,
cfg.BaseDomain)
2020-06-21 18:32:08 +08:00
if err != nil {
return nil, err
}
2021-07-05 03:40:46 +08:00
app.db = database
2021-10-19 03:27:52 +08:00
if cfg.OIDC.Issuer != "" {
err = app.initOIDC()
2022-09-27 17:51:00 +08:00
if err != nil {
if cfg.OIDC.OnlyStartIfOIDCIsAvailable {
return nil, err
} else {
log.Warn().Err(err).Msg("failed to set up OIDC provider, falling back to CLI based authentication")
}
2021-10-08 17:43:52 +08:00
}
2021-10-19 03:27:52 +08:00
}
2021-10-16 22:31:37 +08:00
if app.cfg.DNSConfig != nil && app.cfg.DNSConfig.Proxied { // if MagicDNS
2022-01-16 21:16:59 +08:00
magicDNSDomains := generateMagicDNSRootDomains(app.cfg.IPPrefixes)
// we might have routes already from Split DNS
if app.cfg.DNSConfig.Routes == nil {
2022-06-11 23:33:48 +08:00
app.cfg.DNSConfig.Routes = make(map[string][]*dnstype.Resolver)
}
2021-10-10 18:43:41 +08:00
for _, d := range magicDNSDomains {
app.cfg.DNSConfig.Routes[d.WithoutTrailingDot()] = nil
}
}
if cfg.DERP.ServerEnabled {
embeddedDERPServer, err := app.NewDERPServer()
if err != nil {
return nil, err
}
app.DERPServer = embeddedDERPServer
}
return &app, nil
2020-06-21 18:32:08 +08:00
}
// Redirect to our TLS url.
func (h *Headscale) redirect(w http.ResponseWriter, req *http.Request) {
target := h.cfg.ServerURL + req.URL.RequestURI()
http.Redirect(w, req, target, http.StatusFound)
}
2021-08-13 03:45:40 +08:00
// expireEphemeralNodes deletes ephemeral machine records that have not been
// seen for longer than h.cfg.EphemeralNodeInactivityTimeout.
2021-08-13 03:45:40 +08:00
func (h *Headscale) expireEphemeralNodes(milliSeconds int64) {
ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
for range ticker.C {
h.db.ExpireEphemeralMachines(h.cfg.EphemeralNodeInactivityTimeout)
}
}
2022-12-15 09:02:39 +08:00
// expireExpiredMachines expires machines that have an explicit expiry set
// after that expiry time has passed.
func (h *Headscale) expireExpiredMachines(milliSeconds int64) {
ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
for range ticker.C {
h.db.ExpireExpiredMachines(h.getLastStateChange())
2022-12-15 09:02:39 +08:00
}
}
func (h *Headscale) failoverSubnetRoutes(milliSeconds int64) {
ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
for range ticker.C {
err := h.db.HandlePrimarySubnetFailover()
if err != nil {
log.Error().Err(err).Msg("failed to handle primary subnet failover")
}
}
}
func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
2022-05-01 05:48:28 +08:00
handler grpc.UnaryHandler,
) (interface{}, error) {
// Check if the request is coming from the on-server client.
// This is not secure, but it is to maintain maintainability
// with the "legacy" database-based client
// It is also neede for grpc-gateway to be able to connect to
// the server
client, _ := peer.FromContext(ctx)
2021-11-13 16:36:45 +08:00
log.Trace().
Caller().
Str("client_address", client.Addr.String()).
2021-11-13 16:36:45 +08:00
Msg("Client is trying to authenticate")
meta, ok := metadata.FromIncomingContext(ctx)
if !ok {
2021-11-13 16:36:45 +08:00
log.Error().
Caller().
Str("client_address", client.Addr.String()).
2021-11-13 16:36:45 +08:00
Msg("Retrieving metadata is failed")
2021-11-14 23:46:09 +08:00
2021-11-13 16:36:45 +08:00
return ctx, status.Errorf(
codes.InvalidArgument,
"Retrieving metadata is failed",
)
}
authHeader, ok := meta["authorization"]
if !ok {
2021-11-13 16:36:45 +08:00
log.Error().
Caller().
Str("client_address", client.Addr.String()).
2021-11-13 16:36:45 +08:00
Msg("Authorization token is not supplied")
2021-11-14 23:46:09 +08:00
2021-11-13 16:36:45 +08:00
return ctx, status.Errorf(
codes.Unauthenticated,
"Authorization token is not supplied",
)
}
token := authHeader[0]
if !strings.HasPrefix(token, AuthPrefix) {
log.Error().
Caller().
Str("client_address", client.Addr.String()).
Msg(`missing "Bearer " prefix in "Authorization" header`)
2021-11-14 23:46:09 +08:00
2021-11-13 16:36:45 +08:00
return ctx, status.Error(
codes.Unauthenticated,
`missing "Bearer " prefix in "Authorization" header`,
)
}
valid, err := h.db.ValidateAPIKey(strings.TrimPrefix(token, AuthPrefix))
if err != nil {
log.Error().
Caller().
Err(err).
Str("client_address", client.Addr.String()).
Msg("failed to validate token")
return ctx, status.Error(codes.Internal, "failed to validate token")
}
if !valid {
log.Info().
Str("client_address", client.Addr.String()).
Msg("invalid token")
return ctx, status.Error(codes.Unauthenticated, "invalid token")
}
return handler(ctx, req)
}
2022-06-19 00:41:42 +08:00
func (h *Headscale) httpAuthenticationMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(
2022-06-26 17:55:37 +08:00
writer http.ResponseWriter,
req *http.Request,
2022-06-19 00:41:42 +08:00
) {
log.Trace().
Caller().
2022-06-26 17:55:37 +08:00
Str("client_address", req.RemoteAddr).
2022-06-19 00:41:42 +08:00
Msg("HTTP authentication invoked")
2022-06-26 17:55:37 +08:00
authHeader := req.Header.Get("authorization")
2022-06-19 00:41:42 +08:00
if !strings.HasPrefix(authHeader, AuthPrefix) {
log.Error().
Caller().
2022-06-26 17:55:37 +08:00
Str("client_address", req.RemoteAddr).
2022-06-19 00:41:42 +08:00
Msg(`missing "Bearer " prefix in "Authorization" header`)
2022-06-26 17:55:37 +08:00
writer.WriteHeader(http.StatusUnauthorized)
2022-06-26 18:21:35 +08:00
_, err := writer.Write([]byte("Unauthorized"))
if err != nil {
log.Error().
Caller().
Err(err).
Msg("Failed to write response")
}
2022-06-19 00:41:42 +08:00
return
}
valid, err := h.db.ValidateAPIKey(strings.TrimPrefix(authHeader, AuthPrefix))
2022-06-19 00:41:42 +08:00
if err != nil {
log.Error().
Caller().
Err(err).
2022-06-26 17:55:37 +08:00
Str("client_address", req.RemoteAddr).
2022-06-19 00:41:42 +08:00
Msg("failed to validate token")
2022-06-26 17:55:37 +08:00
writer.WriteHeader(http.StatusInternalServerError)
2022-06-26 18:21:35 +08:00
_, err := writer.Write([]byte("Unauthorized"))
if err != nil {
log.Error().
Caller().
Err(err).
Msg("Failed to write response")
}
2022-06-19 00:41:42 +08:00
return
}
2022-06-19 00:41:42 +08:00
if !valid {
log.Info().
2022-06-26 17:55:37 +08:00
Str("client_address", req.RemoteAddr).
2022-06-19 00:41:42 +08:00
Msg("invalid token")
2022-06-26 17:55:37 +08:00
writer.WriteHeader(http.StatusUnauthorized)
2022-06-26 18:21:35 +08:00
_, err := writer.Write([]byte("Unauthorized"))
if err != nil {
log.Error().
Caller().
Err(err).
Msg("Failed to write response")
}
2022-06-19 00:41:42 +08:00
return
}
2022-06-26 17:55:37 +08:00
next.ServeHTTP(writer, req)
2022-06-19 00:41:42 +08:00
})
}
// ensureUnixSocketIsAbsent will check if the given path for headscales unix socket is clear
// and will remove it if it is not.
func (h *Headscale) ensureUnixSocketIsAbsent() error {
// File does not exist, all fine
if _, err := os.Stat(h.cfg.UnixSocket); errors.Is(err, os.ErrNotExist) {
return nil
}
2021-11-14 23:46:09 +08:00
return os.Remove(h.cfg.UnixSocket)
}
2022-06-19 00:41:42 +08:00
func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *mux.Router {
router := mux.NewRouter()
2022-02-12 21:25:27 +08:00
router.HandleFunc(ts2021UpgradePath, h.NoiseUpgradeHandler).Methods(http.MethodPost)
2022-07-06 19:39:10 +08:00
router.HandleFunc("/health", h.HealthHandler).Methods(http.MethodGet)
2022-06-19 00:41:42 +08:00
router.HandleFunc("/key", h.KeyHandler).Methods(http.MethodGet)
router.HandleFunc("/register/{nkey}", h.RegisterWebAPI).Methods(http.MethodGet)
2022-11-04 18:26:33 +08:00
h.addLegacyHandlers(router)
2022-08-11 18:15:16 +08:00
router.HandleFunc("/oidc/register/{nkey}", h.RegisterOIDC).Methods(http.MethodGet)
2022-06-19 00:41:42 +08:00
router.HandleFunc("/oidc/callback", h.OIDCCallback).Methods(http.MethodGet)
router.HandleFunc("/apple", h.AppleConfigMessage).Methods(http.MethodGet)
router.HandleFunc("/apple/{platform}", h.ApplePlatformConfig).
Methods(http.MethodGet)
2022-06-19 00:41:42 +08:00
router.HandleFunc("/windows", h.WindowsConfigMessage).Methods(http.MethodGet)
router.HandleFunc("/windows/tailscale.reg", h.WindowsRegConfig).
Methods(http.MethodGet)
// TODO(kristoffer): move swagger into a package
router.HandleFunc("/swagger", headscale.SwaggerUI).Methods(http.MethodGet)
router.HandleFunc("/swagger/v1/openapiv2.json", headscale.SwaggerAPIv1).
Methods(http.MethodGet)
2022-02-12 21:25:27 +08:00
if h.cfg.DERP.ServerEnabled {
2022-06-19 00:41:42 +08:00
router.HandleFunc("/derp", h.DERPHandler)
router.HandleFunc("/derp/probe", h.DERPProbeHandler)
router.HandleFunc("/bootstrap-dns", h.DERPBootstrapDNSHandler)
}
2022-07-22 05:57:07 +08:00
apiRouter := router.PathPrefix("/api").Subrouter()
apiRouter.Use(h.httpAuthenticationMiddleware)
apiRouter.PathPrefix("/v1/").HandlerFunc(grpcMux.ServeHTTP)
2022-02-12 21:25:27 +08:00
2023-03-04 00:14:30 +08:00
router.PathPrefix("/").HandlerFunc(notFoundHandler)
2022-02-12 21:25:27 +08:00
return router
}
// Serve launches a GIN server with the Headscale API.
2020-06-21 18:32:08 +08:00
func (h *Headscale) Serve() error {
var err error
// Fetch an initial DERP Map before we start serving
h.DERPMap = GetDERPMap(h.cfg.DERP)
if h.cfg.DERP.ServerEnabled {
2022-03-18 20:10:35 +08:00
// When embedded DERP is enabled we always need a STUN server
if h.cfg.DERP.STUNAddr == "" {
return errSTUNAddressNotSet
}
h.DERPMap.Regions[h.DERPServer.region.RegionID] = &h.DERPServer.region
2022-03-18 20:10:35 +08:00
go h.ServeSTUN()
}
if h.cfg.DERP.AutoUpdate {
derpMapCancelChannel := make(chan struct{})
defer func() { derpMapCancelChannel <- struct{}{} }()
go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel)
2022-02-12 21:25:27 +08:00
}
// TODO(kradalby): These should have cancel channels and be cleaned
// up on shutdown.
2022-02-12 21:25:27 +08:00
go h.expireEphemeralNodes(updateInterval)
2022-12-15 09:02:39 +08:00
go h.expireExpiredMachines(updateInterval)
2022-02-12 21:25:27 +08:00
go h.failoverSubnetRoutes(updateInterval)
2022-02-12 21:25:27 +08:00
if zl.GlobalLevel() == zl.TraceLevel {
zerolog.RespLog = true
} else {
zerolog.RespLog = false
}
2022-02-13 01:05:30 +08:00
// Prepare group for running listeners
errorGroup := new(errgroup.Group)
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
2022-02-12 21:25:27 +08:00
//
//
// Set up LOCAL listeners
//
err = h.ensureUnixSocketIsAbsent()
if err != nil {
2021-12-07 15:46:55 +08:00
return fmt.Errorf("unable to remove old socket file: %w", err)
}
socketListener, err := net.Listen("unix", h.cfg.UnixSocket)
if err != nil {
return fmt.Errorf("failed to set up gRPC socket: %w", err)
}
// Change socket permissions
if err := os.Chmod(h.cfg.UnixSocket, h.cfg.UnixSocketPermission); err != nil {
return fmt.Errorf("failed change permission of gRPC socket: %w", err)
}
grpcGatewayMux := runtime.NewServeMux()
// Make the grpc-gateway connect to grpc over socket
grpcGatewayConn, err := grpc.Dial(
h.cfg.UnixSocket,
[]grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(util.GrpcSocketDialer),
}...,
)
if err != nil {
return err
}
// Connect to the gRPC server over localhost to skip
// the authentication.
err = v1.RegisterHeadscaleServiceHandler(ctx, grpcGatewayMux, grpcGatewayConn)
if err != nil {
return err
}
// Start the local gRPC server without TLS and without authentication
grpcSocket := grpc.NewServer(zerolog.UnaryInterceptor())
v1.RegisterHeadscaleServiceServer(grpcSocket, newHeadscaleV1APIServer(h))
reflection.Register(grpcSocket)
2022-02-13 01:05:30 +08:00
errorGroup.Go(func() error { return grpcSocket.Serve(socketListener) })
//
//
// Set up REMOTE listeners
//
tlsConfig, err := h.getTLSSettings()
if err != nil {
log.Error().Err(err).Msg("Failed to set up TLS configuration")
return err
}
//
//
// gRPC setup
//
// We are sadly not able to run gRPC and HTTPS (2.0) on the same
// port because the connection mux does not support matching them
// since they are so similar. There is multiple issues open and we
// can revisit this if changes:
// https://github.com/soheilhy/cmux/issues/68
// https://github.com/soheilhy/cmux/issues/91
2022-07-01 05:35:22 +08:00
var grpcServer *grpc.Server
var grpcListener net.Listener
2022-02-13 17:08:46 +08:00
if tlsConfig != nil || h.cfg.GRPCAllowInsecure {
2022-02-13 01:05:30 +08:00
log.Info().Msgf("Enabling remote gRPC at %s", h.cfg.GRPCAddr)
grpcOptions := []grpc.ServerOption{
grpc.UnaryInterceptor(
2022-09-04 17:34:23 +08:00
grpcMiddleware.ChainUnaryServer(
2022-02-13 01:05:30 +08:00
h.grpcAuthenticationInterceptor,
zerolog.NewUnaryServerInterceptor(),
),
),
2022-02-13 17:08:46 +08:00
}
if tlsConfig != nil {
grpcOptions = append(grpcOptions,
grpc.Creds(credentials.NewTLS(tlsConfig)),
)
} else {
log.Warn().Msg("gRPC is running without security")
2022-02-13 01:05:30 +08:00
}
2022-07-01 05:35:22 +08:00
grpcServer = grpc.NewServer(grpcOptions...)
2022-02-13 01:05:30 +08:00
v1.RegisterHeadscaleServiceServer(grpcServer, newHeadscaleV1APIServer(h))
reflection.Register(grpcServer)
2022-07-01 05:35:22 +08:00
grpcListener, err = net.Listen("tcp", h.cfg.GRPCAddr)
2022-02-13 01:05:30 +08:00
if err != nil {
return fmt.Errorf("failed to bind to TCP address: %w", err)
}
errorGroup.Go(func() error { return grpcServer.Serve(grpcListener) })
2022-02-13 03:30:25 +08:00
log.Info().
Msgf("listening and serving gRPC on: %s", h.cfg.GRPCAddr)
2022-02-13 00:15:26 +08:00
}
//
//
// HTTP setup
//
// This is the regular router that we expose
// over our main Addr. It also serves the legacy Tailcale API
2022-02-13 00:15:26 +08:00
router := h.createRouter(grpcGatewayMux)
httpServer := &http.Server{
Addr: h.cfg.Addr,
Handler: router,
ReadTimeout: HTTPReadTimeout,
// Go does not handle timeouts in HTTP very well, and there is
// no good way to handle streaming timeouts, therefore we need to
// keep this at unlimited and be careful to clean up connections
// https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/#aboutstreaming
WriteTimeout: 0,
}
2022-02-13 00:33:18 +08:00
var httpListener net.Listener
2022-02-13 00:15:26 +08:00
if tlsConfig != nil {
httpServer.TLSConfig = tlsConfig
2022-02-13 00:33:18 +08:00
httpListener, err = tls.Listen("tcp", h.cfg.Addr, tlsConfig)
} else {
httpListener, err = net.Listen("tcp", h.cfg.Addr)
}
if err != nil {
return fmt.Errorf("failed to bind to TCP address: %w", err)
2022-02-13 00:15:26 +08:00
}
2022-02-13 00:33:18 +08:00
errorGroup.Go(func() error { return httpServer.Serve(httpListener) })
2022-02-12 21:25:27 +08:00
2021-11-13 16:36:45 +08:00
log.Info().
2022-02-13 03:30:25 +08:00
Msgf("listening and serving HTTP on: %s", h.cfg.Addr)
promMux := http.NewServeMux()
promMux.Handle("/metrics", promhttp.Handler())
promHTTPServer := &http.Server{
Addr: h.cfg.MetricsAddr,
Handler: promMux,
ReadTimeout: HTTPReadTimeout,
WriteTimeout: 0,
}
var promHTTPListener net.Listener
promHTTPListener, err = net.Listen("tcp", h.cfg.MetricsAddr)
if err != nil {
return fmt.Errorf("failed to bind to TCP address: %w", err)
}
errorGroup.Go(func() error { return promHTTPServer.Serve(promHTTPListener) })
log.Info().
Msgf("listening and serving metrics on: %s", h.cfg.MetricsAddr)
// Handle common process-killing signals so we can gracefully shut down:
h.shutdownChan = make(chan struct{})
sigc := make(chan os.Signal, 1)
signal.Notify(sigc,
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT,
syscall.SIGHUP)
2022-07-12 02:33:24 +08:00
sigFunc := func(c chan os.Signal) {
// Wait for a SIGINT or SIGKILL:
for {
sig := <-c
switch sig {
case syscall.SIGHUP:
log.Info().
Str("signal", sig.String()).
Msg("Received SIGHUP, reloading ACL and Config")
2022-07-01 05:35:22 +08:00
// TODO(kradalby): Reload config on SIGHUP
if h.cfg.ACL.PolicyPath != "" {
aclPath := util.AbsolutePathFromConfigPath(h.cfg.ACL.PolicyPath)
pol, err := policy.LoadACLPolicyFromPath(aclPath)
if err != nil {
log.Error().Err(err).Msg("Failed to reload ACL policy")
}
h.ACLPolicy = pol
log.Info().
Str("path", aclPath).
Msg("ACL policy successfully reloaded, notifying nodes of change")
h.setLastStateChangeToNow()
}
default:
log.Info().
Str("signal", sig.String()).
Msg("Received signal to stop, shutting down gracefully")
2022-07-01 05:35:22 +08:00
close(h.shutdownChan)
2022-07-12 02:33:24 +08:00
h.pollNetMapStreamWG.Wait()
// Gracefully shut down servers
2022-08-04 16:47:00 +08:00
ctx, cancel := context.WithTimeout(
context.Background(),
HTTPShutdownTimeout,
)
if err := promHTTPServer.Shutdown(ctx); err != nil {
log.Error().Err(err).Msg("Failed to shutdown prometheus http")
}
if err := httpServer.Shutdown(ctx); err != nil {
log.Error().Err(err).Msg("Failed to shutdown http")
}
grpcSocket.GracefulStop()
2022-07-01 05:35:22 +08:00
if grpcServer != nil {
grpcServer.GracefulStop()
grpcListener.Close()
}
// Close network listeners
promHTTPListener.Close()
httpListener.Close()
grpcGatewayConn.Close()
// Stop listening (and unlink the socket if unix type):
socketListener.Close()
<-h.cancelStateUpdateChan
close(h.stateUpdateChan)
close(h.cancelStateUpdateChan)
// Close db connections
err = h.db.Close()
if err != nil {
log.Error().Err(err).Msg("Failed to close db")
}
log.Info().
Msg("Headscale stopped")
// And we're done:
cancel()
}
}
2022-07-01 05:35:22 +08:00
}
2022-07-12 02:33:24 +08:00
errorGroup.Go(func() error {
sigFunc(sigc)
return nil
})
return errorGroup.Wait()
}
func (h *Headscale) getTLSSettings() (*tls.Config, error) {
2021-11-15 00:51:34 +08:00
var err error
2022-06-03 16:14:14 +08:00
if h.cfg.TLS.LetsEncrypt.Hostname != "" {
if !strings.HasPrefix(h.cfg.ServerURL, "https://") {
2021-11-13 16:36:45 +08:00
log.Warn().
Msg("Listening with TLS but ServerURL does not start with https://")
}
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
2022-06-03 16:14:14 +08:00
HostPolicy: autocert.HostWhitelist(h.cfg.TLS.LetsEncrypt.Hostname),
Cache: autocert.DirCache(h.cfg.TLS.LetsEncrypt.CacheDir),
Client: &acme.Client{
DirectoryURL: h.cfg.ACMEURL,
},
Email: h.cfg.ACMEEmail,
}
2022-06-03 16:14:14 +08:00
switch h.cfg.TLS.LetsEncrypt.ChallengeType {
2022-07-12 02:33:24 +08:00
case tlsALPN01ChallengeType:
// Configuration via autocert with TLS-ALPN-01 (https://tools.ietf.org/html/rfc8737)
// The RFC requires that the validation is done on port 443; in other words, headscale
// must be reachable on port 443.
return certManager.TLSConfig(), nil
2021-11-15 01:44:37 +08:00
2022-07-12 02:33:24 +08:00
case http01ChallengeType:
// Configuration via autocert with HTTP-01. This requires listening on
// port 80 for the certificate validation in addition to the headscale
// service, which can be configured to run on any other port.
server := &http.Server{
Addr: h.cfg.TLS.LetsEncrypt.Listen,
Handler: certManager.HTTPHandler(http.HandlerFunc(h.redirect)),
ReadTimeout: HTTPReadTimeout,
}
go func() {
err := server.ListenAndServe()
2021-08-06 01:11:26 +08:00
log.Fatal().
Caller().
Err(err).
2021-08-06 01:11:26 +08:00
Msg("failed to set up a HTTP server")
}()
return certManager.TLSConfig(), nil
2021-11-15 01:44:37 +08:00
default:
2021-11-16 03:18:14 +08:00
return nil, errUnsupportedLetsEncryptChallengeType
}
2022-06-03 16:14:14 +08:00
} else if h.cfg.TLS.CertPath == "" {
if !strings.HasPrefix(h.cfg.ServerURL, "http://") {
2021-08-06 01:11:26 +08:00
log.Warn().Msg("Listening without TLS but ServerURL does not start with http://")
}
2021-11-15 00:51:34 +08:00
return nil, err
} else {
if !strings.HasPrefix(h.cfg.ServerURL, "https://") {
2021-08-06 01:11:26 +08:00
log.Warn().Msg("Listening with TLS but ServerURL does not start with https://")
}
2021-11-16 02:31:52 +08:00
tlsConfig := &tls.Config{
NextProtos: []string{"http/1.1"},
Certificates: make([]tls.Certificate, 1),
MinVersion: tls.VersionTLS12,
}
2022-06-03 16:14:14 +08:00
tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(h.cfg.TLS.CertPath, h.cfg.TLS.KeyPath)
return tlsConfig, err
}
2020-06-21 18:32:08 +08:00
}
// TODO(kradalby): baby steps, make this more robust.
func (h *Headscale) watchStateChannel() {
for {
select {
case <-h.stateUpdateChan:
h.setLastStateChangeToNow()
case <-h.cancelStateUpdateChan:
return
}
}
}
func (h *Headscale) setLastStateChangeToNow() {
var err error
now := time.Now().UTC()
users, err := h.db.ListUsers()
if err != nil {
log.Error().
Caller().
Err(err).
Msg("failed to fetch all users, failing to update last changed state.")
}
for _, user := range users {
lastStateUpdate.WithLabelValues(user.Name, "headscale").Set(float64(now.Unix()))
if h.lastStateChange == nil {
h.lastStateChange = xsync.NewMapOf[time.Time]()
}
h.lastStateChange.Store(user.Name, now)
}
}
func (h *Headscale) getLastStateChange(users ...types.User) time.Time {
times := []time.Time{}
// getLastStateChange takes a list of users as a "filter", if no users
// are past, then use the entier list of users and look for the last update
if len(users) > 0 {
for _, user := range users {
if lastChange, ok := h.lastStateChange.Load(user.Name); ok {
times = append(times, lastChange)
}
}
} else {
h.lastStateChange.Range(func(key string, value time.Time) bool {
times = append(times, value)
return true
})
2021-08-20 01:19:26 +08:00
}
sort.Slice(times, func(i, j int) bool {
return times[i].After(times[j])
})
log.Trace().Msgf("Latest times %#v", times)
if len(times) == 0 {
return time.Now().UTC()
} else {
return times[0]
}
}
2023-03-04 00:14:30 +08:00
func notFoundHandler(
2022-06-26 17:55:37 +08:00
writer http.ResponseWriter,
req *http.Request,
2022-06-19 00:41:42 +08:00
) {
2022-06-26 17:55:37 +08:00
body, _ := io.ReadAll(req.Body)
log.Trace().
2022-06-26 17:55:37 +08:00
Interface("header", req.Header).
Interface("proto", req.Proto).
Interface("url", req.URL).
Bytes("body", body).
Msg("Request did not match")
2023-03-04 00:14:30 +08:00
writer.WriteHeader(http.StatusNotFound)
}
func readOrCreatePrivateKey(path string) (*key.MachinePrivate, error) {
privateKey, err := os.ReadFile(path)
if errors.Is(err, os.ErrNotExist) {
log.Info().Str("path", path).Msg("No private key file at path, creating...")
machineKey := key.NewMachine()
machineKeyStr, err := machineKey.MarshalText()
if err != nil {
return nil, fmt.Errorf(
"failed to convert private key to string for saving: %w",
err,
)
}
err = os.WriteFile(path, machineKeyStr, privateKeyFileMode)
if err != nil {
return nil, fmt.Errorf(
"failed to save private key to disk: %w",
err,
)
}
return &machineKey, nil
} else if err != nil {
return nil, fmt.Errorf("failed to read private key file: %w", err)
}
trimmedPrivateKey := strings.TrimSpace(string(privateKey))
privateKeyEnsurePrefix := util.PrivateKeyEnsurePrefix(trimmedPrivateKey)
var machineKey key.MachinePrivate
if err = machineKey.UnmarshalText([]byte(privateKeyEnsurePrefix)); err != nil {
log.Info().
Str("path", path).
Msg("This might be due to a legacy (headscale pre-0.12) private key. " +
"If the key is in WireGuard format, delete the key and restart headscale. " +
"A new key will automatically be generated. All Tailscale clients will have to be restarted")
return nil, fmt.Errorf("failed to parse private key: %w", err)
}
return &machineKey, nil
}