2023-05-10 09:24:05 +02:00
|
|
|
package hscontrol
|
2020-06-21 12:32:08 +02:00
|
|
|
|
|
|
|
import (
|
2021-10-26 22:42:56 +02:00
|
|
|
"context"
|
|
|
|
"crypto/tls"
|
2021-04-24 04:54:15 +02:00
|
|
|
"errors"
|
2020-06-21 12:32:08 +02:00
|
|
|
"fmt"
|
2021-10-29 18:45:06 +02:00
|
|
|
"io"
|
2021-10-26 22:42:56 +02:00
|
|
|
"net"
|
2021-04-24 04:54:15 +02:00
|
|
|
"net/http"
|
2024-07-18 07:38:25 +02:00
|
|
|
_ "net/http/pprof" // nolint
|
2021-02-21 23:54:15 +01:00
|
|
|
"os"
|
2021-11-02 22:46:15 +01:00
|
|
|
"os/signal"
|
2024-02-17 13:36:19 +01:00
|
|
|
"path/filepath"
|
2023-09-11 13:04:58 +02:00
|
|
|
"runtime"
|
2021-04-23 22:54:35 +02:00
|
|
|
"strings"
|
2021-02-23 21:07:52 +01:00
|
|
|
"sync"
|
2021-11-02 22:46:15 +01:00
|
|
|
"syscall"
|
2021-05-23 02:15:29 +02:00
|
|
|
"time"
|
2020-06-21 12:32:08 +02:00
|
|
|
|
2021-10-18 21:27:52 +02:00
|
|
|
"github.com/coreos/go-oidc/v3/oidc"
|
2024-05-24 10:15:34 +02:00
|
|
|
"github.com/davecgh/go-spew/spew"
|
2022-06-18 18:41:42 +02:00
|
|
|
"github.com/gorilla/mux"
|
2022-09-04 11:34:23 +02:00
|
|
|
grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
2023-09-11 13:04:58 +02:00
|
|
|
grpcRuntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
2024-07-22 08:56:00 +02:00
|
|
|
"github.com/juanfont/headscale"
|
|
|
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
|
|
|
"github.com/juanfont/headscale/hscontrol/db"
|
|
|
|
"github.com/juanfont/headscale/hscontrol/derp"
|
|
|
|
derpServer "github.com/juanfont/headscale/hscontrol/derp/server"
|
|
|
|
"github.com/juanfont/headscale/hscontrol/mapper"
|
|
|
|
"github.com/juanfont/headscale/hscontrol/notifier"
|
|
|
|
"github.com/juanfont/headscale/hscontrol/policy"
|
|
|
|
"github.com/juanfont/headscale/hscontrol/types"
|
|
|
|
"github.com/juanfont/headscale/hscontrol/util"
|
2021-11-13 09:39:04 +01:00
|
|
|
"github.com/patrickmn/go-cache"
|
|
|
|
zerolog "github.com/philip-bui/grpc-zerolog"
|
2024-02-08 17:28:19 +01:00
|
|
|
"github.com/pkg/profile"
|
2022-07-15 20:03:46 +02:00
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
2021-11-08 23:06:25 +01:00
|
|
|
zl "github.com/rs/zerolog"
|
2021-10-26 22:42:56 +02:00
|
|
|
"github.com/rs/zerolog/log"
|
2021-10-03 20:26:38 +02:00
|
|
|
"golang.org/x/crypto/acme"
|
2021-04-24 04:54:15 +02:00
|
|
|
"golang.org/x/crypto/acme/autocert"
|
2021-11-13 09:39:04 +01:00
|
|
|
"golang.org/x/oauth2"
|
2021-10-26 22:42:56 +02:00
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"google.golang.org/grpc"
|
2021-10-29 18:45:06 +02:00
|
|
|
"google.golang.org/grpc/codes"
|
2022-02-12 18:05:30 +01:00
|
|
|
"google.golang.org/grpc/credentials"
|
2022-02-12 20:48:05 +01:00
|
|
|
"google.golang.org/grpc/credentials/insecure"
|
2021-10-29 18:45:06 +02:00
|
|
|
"google.golang.org/grpc/metadata"
|
|
|
|
"google.golang.org/grpc/peer"
|
|
|
|
"google.golang.org/grpc/reflection"
|
|
|
|
"google.golang.org/grpc/status"
|
2024-02-08 17:28:19 +01:00
|
|
|
"gorm.io/gorm"
|
2023-12-20 21:47:48 +01:00
|
|
|
"tailscale.com/envknob"
|
2021-02-20 23:57:06 +01:00
|
|
|
"tailscale.com/tailcfg"
|
2021-10-02 12:13:05 +02:00
|
|
|
"tailscale.com/types/dnstype"
|
2021-11-27 00:28:06 +01:00
|
|
|
"tailscale.com/types/key"
|
2024-04-17 07:03:06 +02:00
|
|
|
"tailscale.com/util/dnsname"
|
2020-06-21 12:32:08 +02:00
|
|
|
)
|
|
|
|
|
2023-05-11 09:09:18 +02:00
|
|
|
var (
|
|
|
|
errSTUNAddressNotSet = errors.New("STUN address not set")
|
|
|
|
errUnsupportedLetsEncryptChallengeType = errors.New(
|
2022-03-16 19:46:59 +01:00
|
|
|
"unknown value for Lets Encrypt challenge type",
|
|
|
|
)
|
2023-12-20 21:47:48 +01:00
|
|
|
errEmptyInitialDERPMap = errors.New(
|
2024-02-08 17:28:19 +01:00
|
|
|
"initial DERPMap is empty, Headscale requires at least one entry",
|
2023-12-20 21:47:48 +01:00
|
|
|
)
|
2022-03-16 19:46:59 +01:00
|
|
|
)
|
|
|
|
|
2021-10-29 18:45:06 +02:00
|
|
|
const (
|
2023-06-06 10:41:30 +02:00
|
|
|
AuthPrefix = "Bearer "
|
2024-05-02 17:57:53 +02:00
|
|
|
updateInterval = 5 * time.Second
|
2023-06-06 10:41:30 +02:00
|
|
|
privateKeyFileMode = 0o600
|
2024-02-17 13:36:19 +01:00
|
|
|
headscaleDirPerm = 0o700
|
2021-11-15 20:18:14 +01:00
|
|
|
|
2022-02-28 23:42:30 +01:00
|
|
|
registerCacheExpiration = time.Minute * 15
|
|
|
|
registerCacheCleanup = time.Minute * 20
|
2021-10-29 18:45:06 +02:00
|
|
|
)
|
|
|
|
|
2021-10-26 22:42:56 +02:00
|
|
|
// Headscale represents the base app of the service.
|
2020-06-21 12:32:08 +02:00
|
|
|
type Headscale struct {
|
2023-06-06 10:23:39 +02:00
|
|
|
cfg *types.Config
|
2023-05-21 18:37:59 +02:00
|
|
|
db *db.HSDatabase
|
2024-02-18 19:31:29 +01:00
|
|
|
ipAlloc *db.IPAllocator
|
2022-08-13 11:14:38 +02:00
|
|
|
noisePrivateKey *key.MachinePrivate
|
2024-07-18 10:01:59 +02:00
|
|
|
ephemeralGC *db.EphemeralGarbageCollector
|
2021-02-23 21:07:52 +01:00
|
|
|
|
2022-03-05 16:22:02 +01:00
|
|
|
DERPMap *tailcfg.DERPMap
|
2023-06-06 11:09:48 +02:00
|
|
|
DERPServer *derpServer.DERPServer
|
2021-10-22 18:55:14 +02:00
|
|
|
|
2023-05-21 18:37:59 +02:00
|
|
|
ACLPolicy *policy.ACLPolicy
|
2021-07-03 17:31:32 +02:00
|
|
|
|
2024-02-23 10:59:24 +01:00
|
|
|
mapper *mapper.Mapper
|
2023-06-21 11:29:52 +02:00
|
|
|
nodeNotifier *notifier.Notifier
|
2021-10-08 11:43:52 +02:00
|
|
|
|
2022-02-28 23:42:30 +01:00
|
|
|
oidcProvider *oidc.Provider
|
|
|
|
oauth2Config *oauth2.Config
|
2022-02-24 14:18:18 +01:00
|
|
|
|
2022-02-28 09:06:39 +01:00
|
|
|
registrationCache *cache.Cache
|
|
|
|
|
2022-07-11 20:33:24 +02:00
|
|
|
pollNetMapStreamWG sync.WaitGroup
|
2020-06-21 12:32:08 +02:00
|
|
|
}
|
|
|
|
|
2023-12-20 21:47:48 +01:00
|
|
|
var (
|
2024-05-24 10:15:34 +02:00
|
|
|
profilingEnabled = envknob.Bool("HEADSCALE_DEBUG_PROFILING_ENABLED")
|
|
|
|
profilingPath = envknob.String("HEADSCALE_DEBUG_PROFILING_PATH")
|
2023-12-20 21:47:48 +01:00
|
|
|
tailsqlEnabled = envknob.Bool("HEADSCALE_DEBUG_TAILSQL_ENABLED")
|
|
|
|
tailsqlStateDir = envknob.String("HEADSCALE_DEBUG_TAILSQL_STATE_DIR")
|
|
|
|
tailsqlTSKey = envknob.String("TS_AUTHKEY")
|
2024-05-24 10:15:34 +02:00
|
|
|
dumpConfig = envknob.Bool("HEADSCALE_DEBUG_DUMP_CONFIG")
|
2023-12-20 21:47:48 +01:00
|
|
|
)
|
|
|
|
|
2023-06-06 10:23:39 +02:00
|
|
|
func NewHeadscale(cfg *types.Config) (*Headscale, error) {
|
2024-02-18 19:31:29 +01:00
|
|
|
var err error
|
2023-12-20 21:47:48 +01:00
|
|
|
if profilingEnabled {
|
2023-09-11 13:04:58 +02:00
|
|
|
runtime.SetBlockProfileRate(1)
|
|
|
|
}
|
|
|
|
|
2022-08-13 11:14:38 +02:00
|
|
|
noisePrivateKey, err := readOrCreatePrivateKey(cfg.NoisePrivateKeyPath)
|
|
|
|
if err != nil {
|
2022-11-09 23:23:30 +01:00
|
|
|
return nil, fmt.Errorf("failed to read or create Noise protocol private key: %w", err)
|
2022-08-13 11:14:38 +02:00
|
|
|
}
|
|
|
|
|
2022-02-28 09:06:39 +01:00
|
|
|
registrationCache := cache.New(
|
2022-02-28 23:42:30 +01:00
|
|
|
registerCacheExpiration,
|
|
|
|
registerCacheCleanup,
|
2022-02-28 09:06:39 +01:00
|
|
|
)
|
|
|
|
|
2021-11-14 20:32:03 +01:00
|
|
|
app := Headscale{
|
2022-07-11 20:33:24 +02:00
|
|
|
cfg: cfg,
|
2022-08-13 11:14:38 +02:00
|
|
|
noisePrivateKey: noisePrivateKey,
|
2022-07-11 20:33:24 +02:00
|
|
|
registrationCache: registrationCache,
|
|
|
|
pollNetMapStreamWG: sync.WaitGroup{},
|
2024-04-27 10:47:39 +02:00
|
|
|
nodeNotifier: notifier.NewNotifier(cfg),
|
2020-06-21 12:32:08 +02:00
|
|
|
}
|
2021-07-04 13:24:05 +02:00
|
|
|
|
2024-02-18 19:31:29 +01:00
|
|
|
app.db, err = db.NewHeadscaleDatabase(
|
2024-02-09 07:27:00 +01:00
|
|
|
cfg.Database,
|
2023-05-11 09:09:18 +02:00
|
|
|
cfg.BaseDomain)
|
2020-06-21 12:32:08 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-07-04 21:40:46 +02:00
|
|
|
|
2024-04-17 07:03:06 +02:00
|
|
|
app.ipAlloc, err = db.NewIPAllocator(app.db, cfg.PrefixV4, cfg.PrefixV6, cfg.IPAllocation)
|
2024-02-18 19:31:29 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-05-11 09:09:18 +02:00
|
|
|
|
2024-07-18 10:01:59 +02:00
|
|
|
app.ephemeralGC = db.NewEphemeralGarbageCollector(func(ni types.NodeID) {
|
|
|
|
if err := app.db.DeleteEphemeralNode(ni); err != nil {
|
|
|
|
log.Err(err).Uint64("node.id", ni.Uint64()).Msgf("failed to delete ephemeral node")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2021-10-18 21:27:52 +02:00
|
|
|
if cfg.OIDC.Issuer != "" {
|
2021-11-14 20:32:03 +01:00
|
|
|
err = app.initOIDC()
|
2022-09-27 11:51:00 +02: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 11:43:52 +02:00
|
|
|
}
|
2021-10-18 21:27:52 +02:00
|
|
|
}
|
2021-10-16 16:31:37 +02:00
|
|
|
|
2021-11-14 20:32:03 +01:00
|
|
|
if app.cfg.DNSConfig != nil && app.cfg.DNSConfig.Proxied { // if MagicDNS
|
2024-02-18 19:31:29 +01:00
|
|
|
// TODO(kradalby): revisit why this takes a list.
|
2024-04-17 07:03:06 +02:00
|
|
|
|
|
|
|
var magicDNSDomains []dnsname.FQDN
|
|
|
|
if cfg.PrefixV4 != nil {
|
|
|
|
magicDNSDomains = append(magicDNSDomains, util.GenerateIPv4DNSRootDomain(*cfg.PrefixV4)...)
|
|
|
|
}
|
|
|
|
if cfg.PrefixV6 != nil {
|
|
|
|
magicDNSDomains = append(magicDNSDomains, util.GenerateIPv6DNSRootDomain(*cfg.PrefixV6)...)
|
|
|
|
}
|
|
|
|
|
2021-10-20 09:35:56 +02:00
|
|
|
// we might have routes already from Split DNS
|
2021-11-14 20:32:03 +01:00
|
|
|
if app.cfg.DNSConfig.Routes == nil {
|
2022-06-11 17:33:48 +02:00
|
|
|
app.cfg.DNSConfig.Routes = make(map[string][]*dnstype.Resolver)
|
2021-10-19 20:51:43 +02:00
|
|
|
}
|
2021-10-10 12:43:41 +02:00
|
|
|
for _, d := range magicDNSDomains {
|
2021-11-14 20:32:03 +01:00
|
|
|
app.cfg.DNSConfig.Routes[d.WithoutTrailingDot()] = nil
|
2021-10-02 12:13:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-05 16:22:02 +01:00
|
|
|
if cfg.DERP.ServerEnabled {
|
2023-11-23 08:31:33 +01:00
|
|
|
derpServerKey, err := readOrCreatePrivateKey(cfg.DERP.ServerPrivateKeyPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to read or create DERP server private key: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if derpServerKey.Equal(*noisePrivateKey) {
|
2023-12-09 18:09:24 +01:00
|
|
|
return nil, fmt.Errorf(
|
|
|
|
"DERP server private key and noise private key are the same: %w",
|
|
|
|
err,
|
|
|
|
)
|
2023-11-23 08:31:33 +01:00
|
|
|
}
|
|
|
|
|
2023-06-21 11:29:52 +02:00
|
|
|
embeddedDERPServer, err := derpServer.NewDERPServer(
|
|
|
|
cfg.ServerURL,
|
2023-11-23 08:31:33 +01:00
|
|
|
key.NodePrivate(*derpServerKey),
|
2023-06-21 11:29:52 +02:00
|
|
|
&cfg.DERP,
|
|
|
|
)
|
2022-03-04 00:01:31 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-03-05 16:22:02 +01:00
|
|
|
app.DERPServer = embeddedDERPServer
|
2022-03-04 00:01:31 +01:00
|
|
|
}
|
|
|
|
|
2021-11-14 20:32:03 +01:00
|
|
|
return &app, nil
|
2020-06-21 12:32:08 +02:00
|
|
|
}
|
|
|
|
|
2021-10-26 22:42:56 +02:00
|
|
|
// Redirect to our TLS url.
|
2021-04-24 04:54:15 +02:00
|
|
|
func (h *Headscale) redirect(w http.ResponseWriter, req *http.Request) {
|
|
|
|
target := h.cfg.ServerURL + req.URL.RequestURI()
|
|
|
|
http.Redirect(w, req, target, http.StatusFound)
|
|
|
|
}
|
|
|
|
|
2024-05-02 17:57:53 +02:00
|
|
|
// expireExpiredNodes expires nodes that have an explicit expiry set
|
2022-12-15 02:02:39 +01:00
|
|
|
// after that expiry time has passed.
|
2024-05-02 17:57:53 +02:00
|
|
|
func (h *Headscale) expireExpiredNodes(ctx context.Context, every time.Duration) {
|
|
|
|
ticker := time.NewTicker(every)
|
2023-06-21 11:29:52 +02:00
|
|
|
|
|
|
|
lastCheck := time.Unix(0, 0)
|
2024-02-08 17:28:19 +01:00
|
|
|
var update types.StateUpdate
|
|
|
|
var changed bool
|
2023-06-21 11:29:52 +02:00
|
|
|
|
2024-05-02 17:57:53 +02:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
ticker.Stop()
|
|
|
|
return
|
|
|
|
case <-ticker.C:
|
|
|
|
if err := h.db.Write(func(tx *gorm.DB) error {
|
|
|
|
lastCheck, update, changed = db.ExpireExpiredNodes(tx, lastCheck)
|
2024-02-08 17:28:19 +01:00
|
|
|
|
2024-05-02 17:57:53 +02:00
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
log.Error().Err(err).Msg("database error while expiring nodes")
|
|
|
|
continue
|
|
|
|
}
|
2024-02-08 17:28:19 +01:00
|
|
|
|
2024-05-02 17:57:53 +02:00
|
|
|
if changed {
|
|
|
|
log.Trace().Interface("nodes", update.ChangePatches).Msgf("expiring nodes")
|
2024-02-23 10:59:24 +01:00
|
|
|
|
2024-05-02 17:57:53 +02:00
|
|
|
ctx := types.NotifyCtx(context.Background(), "expire-expired", "na")
|
|
|
|
h.nodeNotifier.NotifyAll(ctx, update)
|
|
|
|
}
|
2024-02-08 17:28:19 +01:00
|
|
|
}
|
2022-12-15 02:02:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-06 10:41:30 +02:00
|
|
|
// scheduledDERPMapUpdateWorker refreshes the DERPMap stored on the global object
|
2023-06-06 11:09:48 +02:00
|
|
|
// at a set interval.
|
2023-06-06 10:41:30 +02:00
|
|
|
func (h *Headscale) scheduledDERPMapUpdateWorker(cancelChan <-chan struct{}) {
|
|
|
|
log.Info().
|
|
|
|
Dur("frequency", h.cfg.DERP.UpdateFrequency).
|
|
|
|
Msg("Setting up a DERPMap update worker")
|
|
|
|
ticker := time.NewTicker(h.cfg.DERP.UpdateFrequency)
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-cancelChan:
|
|
|
|
return
|
|
|
|
|
|
|
|
case <-ticker.C:
|
|
|
|
log.Info().Msg("Fetching DERPMap updates")
|
|
|
|
h.DERPMap = derp.GetDERPMap(h.cfg.DERP)
|
2024-01-16 16:04:03 +01:00
|
|
|
if h.cfg.DERP.ServerEnabled && h.cfg.DERP.AutomaticallyAddEmbeddedDerpRegion {
|
2023-06-06 11:09:48 +02:00
|
|
|
region, _ := h.DERPServer.GenerateRegion()
|
|
|
|
h.DERPMap.Regions[region.RegionID] = ®ion
|
2023-06-06 10:41:30 +02:00
|
|
|
}
|
|
|
|
|
2024-02-23 10:59:24 +01:00
|
|
|
ctx := types.NotifyCtx(context.Background(), "derpmap-update", "na")
|
|
|
|
h.nodeNotifier.NotifyAll(ctx, types.StateUpdate{
|
2023-06-29 12:20:22 +02:00
|
|
|
Type: types.StateDERPUpdated,
|
2023-12-09 18:09:24 +01:00
|
|
|
DERPMap: h.DERPMap,
|
2024-02-23 10:59:24 +01:00
|
|
|
})
|
2022-11-25 16:11:22 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-29 18:45:06 +02:00
|
|
|
func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context,
|
|
|
|
req interface{},
|
|
|
|
info *grpc.UnaryServerInfo,
|
2022-04-30 23:48:28 +02:00
|
|
|
handler grpc.UnaryHandler,
|
|
|
|
) (interface{}, error) {
|
2021-10-29 18:45:06 +02:00
|
|
|
// 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
|
2024-05-19 23:49:27 +02:00
|
|
|
// It is also needed for grpc-gateway to be able to connect to
|
2021-10-29 18:45:06 +02:00
|
|
|
// the server
|
2021-11-14 20:32:03 +01:00
|
|
|
client, _ := peer.FromContext(ctx)
|
2021-10-29 18:45:06 +02:00
|
|
|
|
2021-11-13 09:36:45 +01:00
|
|
|
log.Trace().
|
|
|
|
Caller().
|
2021-11-14 20:32:03 +01:00
|
|
|
Str("client_address", client.Addr.String()).
|
2021-11-13 09:36:45 +01:00
|
|
|
Msg("Client is trying to authenticate")
|
2021-10-29 18:45:06 +02:00
|
|
|
|
2021-11-14 20:32:03 +01:00
|
|
|
meta, ok := metadata.FromIncomingContext(ctx)
|
2021-10-29 18:45:06 +02:00
|
|
|
if !ok {
|
2021-11-13 09:36:45 +01:00
|
|
|
return ctx, status.Errorf(
|
|
|
|
codes.InvalidArgument,
|
|
|
|
"Retrieving metadata is failed",
|
|
|
|
)
|
2021-10-29 18:45:06 +02:00
|
|
|
}
|
|
|
|
|
2021-11-14 20:32:03 +01:00
|
|
|
authHeader, ok := meta["authorization"]
|
2021-10-29 18:45:06 +02:00
|
|
|
if !ok {
|
2021-11-13 09:36:45 +01:00
|
|
|
return ctx, status.Errorf(
|
|
|
|
codes.Unauthenticated,
|
|
|
|
"Authorization token is not supplied",
|
|
|
|
)
|
2021-10-29 18:45:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
token := authHeader[0]
|
|
|
|
|
2021-11-15 18:24:24 +01:00
|
|
|
if !strings.HasPrefix(token, AuthPrefix) {
|
2021-11-13 09:36:45 +01:00
|
|
|
return ctx, status.Error(
|
|
|
|
codes.Unauthenticated,
|
|
|
|
`missing "Bearer " prefix in "Authorization" header`,
|
|
|
|
)
|
2021-10-29 18:45:06 +02:00
|
|
|
}
|
|
|
|
|
2023-05-11 09:09:18 +02:00
|
|
|
valid, err := h.db.ValidateAPIKey(strings.TrimPrefix(token, AuthPrefix))
|
2022-01-25 23:11:15 +01:00
|
|
|
if err != nil {
|
|
|
|
return ctx, status.Error(codes.Internal, "failed to validate token")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !valid {
|
|
|
|
log.Info().
|
|
|
|
Str("client_address", client.Addr.String()).
|
|
|
|
Msg("invalid token")
|
2021-10-29 18:45:06 +02:00
|
|
|
|
2022-01-25 23:11:15 +01:00
|
|
|
return ctx, status.Error(codes.Unauthenticated, "invalid token")
|
|
|
|
}
|
2021-10-29 18:45:06 +02:00
|
|
|
|
2022-01-25 23:11:15 +01:00
|
|
|
return handler(ctx, req)
|
2021-10-29 18:45:06 +02:00
|
|
|
}
|
|
|
|
|
2022-06-18 18:41:42 +02:00
|
|
|
func (h *Headscale) httpAuthenticationMiddleware(next http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(
|
2022-06-26 11:55:37 +02:00
|
|
|
writer http.ResponseWriter,
|
|
|
|
req *http.Request,
|
2022-06-18 18:41:42 +02:00
|
|
|
) {
|
|
|
|
log.Trace().
|
|
|
|
Caller().
|
2022-06-26 11:55:37 +02:00
|
|
|
Str("client_address", req.RemoteAddr).
|
2022-06-18 18:41:42 +02:00
|
|
|
Msg("HTTP authentication invoked")
|
2021-10-29 18:45:06 +02:00
|
|
|
|
2022-06-26 11:55:37 +02:00
|
|
|
authHeader := req.Header.Get("authorization")
|
2021-10-29 18:45:06 +02:00
|
|
|
|
2022-06-18 18:41:42 +02:00
|
|
|
if !strings.HasPrefix(authHeader, AuthPrefix) {
|
|
|
|
log.Error().
|
|
|
|
Caller().
|
2022-06-26 11:55:37 +02:00
|
|
|
Str("client_address", req.RemoteAddr).
|
2022-06-18 18:41:42 +02:00
|
|
|
Msg(`missing "Bearer " prefix in "Authorization" header`)
|
2022-06-26 11:55:37 +02:00
|
|
|
writer.WriteHeader(http.StatusUnauthorized)
|
2022-06-26 12:21:35 +02:00
|
|
|
_, err := writer.Write([]byte("Unauthorized"))
|
|
|
|
if err != nil {
|
|
|
|
log.Error().
|
|
|
|
Caller().
|
|
|
|
Err(err).
|
|
|
|
Msg("Failed to write response")
|
|
|
|
}
|
2021-10-29 18:45:06 +02:00
|
|
|
|
2022-06-18 18:41:42 +02:00
|
|
|
return
|
|
|
|
}
|
2021-10-29 18:45:06 +02:00
|
|
|
|
2023-05-11 09:09:18 +02:00
|
|
|
valid, err := h.db.ValidateAPIKey(strings.TrimPrefix(authHeader, AuthPrefix))
|
2022-06-18 18:41:42 +02:00
|
|
|
if err != nil {
|
|
|
|
log.Error().
|
|
|
|
Caller().
|
|
|
|
Err(err).
|
2022-06-26 11:55:37 +02:00
|
|
|
Str("client_address", req.RemoteAddr).
|
2022-06-18 18:41:42 +02:00
|
|
|
Msg("failed to validate token")
|
2022-01-25 23:11:15 +01:00
|
|
|
|
2022-06-26 11:55:37 +02:00
|
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
2022-06-26 12:21:35 +02:00
|
|
|
_, err := writer.Write([]byte("Unauthorized"))
|
|
|
|
if err != nil {
|
|
|
|
log.Error().
|
|
|
|
Caller().
|
|
|
|
Err(err).
|
|
|
|
Msg("Failed to write response")
|
|
|
|
}
|
2022-01-25 23:11:15 +01:00
|
|
|
|
2022-06-18 18:41:42 +02:00
|
|
|
return
|
|
|
|
}
|
2022-01-25 23:11:15 +01:00
|
|
|
|
2022-06-18 18:41:42 +02:00
|
|
|
if !valid {
|
|
|
|
log.Info().
|
2022-06-26 11:55:37 +02:00
|
|
|
Str("client_address", req.RemoteAddr).
|
2022-06-18 18:41:42 +02:00
|
|
|
Msg("invalid token")
|
2022-01-25 23:11:15 +01:00
|
|
|
|
2022-06-26 11:55:37 +02:00
|
|
|
writer.WriteHeader(http.StatusUnauthorized)
|
2022-06-26 12:21:35 +02:00
|
|
|
_, err := writer.Write([]byte("Unauthorized"))
|
|
|
|
if err != nil {
|
|
|
|
log.Error().
|
|
|
|
Caller().
|
|
|
|
Err(err).
|
|
|
|
Msg("Failed to write response")
|
|
|
|
}
|
2021-10-29 18:45:06 +02:00
|
|
|
|
2022-06-18 18:41:42 +02:00
|
|
|
return
|
|
|
|
}
|
2021-10-29 18:45:06 +02:00
|
|
|
|
2022-06-26 11:55:37 +02:00
|
|
|
next.ServeHTTP(writer, req)
|
2022-06-18 18:41:42 +02:00
|
|
|
})
|
2021-10-29 18:45:06 +02:00
|
|
|
}
|
|
|
|
|
2021-11-07 10:55:32 +01: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 16:46:09 +01:00
|
|
|
|
2021-11-07 10:55:32 +01:00
|
|
|
return os.Remove(h.cfg.UnixSocket)
|
|
|
|
}
|
|
|
|
|
2023-09-11 13:04:58 +02:00
|
|
|
func (h *Headscale) createRouter(grpcMux *grpcRuntime.ServeMux) *mux.Router {
|
2022-06-18 18:41:42 +02:00
|
|
|
router := mux.NewRouter()
|
2024-04-21 18:28:17 +02:00
|
|
|
router.Use(prometheusMiddleware)
|
2022-02-12 14:25:27 +01:00
|
|
|
|
2022-08-13 20:55:37 +02:00
|
|
|
router.HandleFunc(ts2021UpgradePath, h.NoiseUpgradeHandler).Methods(http.MethodPost)
|
|
|
|
|
2022-07-06 13:39:10 +02:00
|
|
|
router.HandleFunc("/health", h.HealthHandler).Methods(http.MethodGet)
|
2022-06-18 18:41:42 +02:00
|
|
|
router.HandleFunc("/key", h.KeyHandler).Methods(http.MethodGet)
|
2023-11-19 22:37:04 +01:00
|
|
|
router.HandleFunc("/register/{mkey}", h.RegisterWebAPI).Methods(http.MethodGet)
|
2022-11-04 11:26:33 +01:00
|
|
|
|
2023-11-19 22:37:04 +01:00
|
|
|
router.HandleFunc("/oidc/register/{mkey}", h.RegisterOIDC).Methods(http.MethodGet)
|
2022-06-18 18:41:42 +02:00
|
|
|
router.HandleFunc("/oidc/callback", h.OIDCCallback).Methods(http.MethodGet)
|
|
|
|
router.HandleFunc("/apple", h.AppleConfigMessage).Methods(http.MethodGet)
|
2022-09-26 11:34:04 +02:00
|
|
|
router.HandleFunc("/apple/{platform}", h.ApplePlatformConfig).
|
|
|
|
Methods(http.MethodGet)
|
2022-06-18 18:41:42 +02:00
|
|
|
router.HandleFunc("/windows", h.WindowsConfigMessage).Methods(http.MethodGet)
|
2023-05-10 10:19:16 +02:00
|
|
|
|
|
|
|
// TODO(kristoffer): move swagger into a package
|
|
|
|
router.HandleFunc("/swagger", headscale.SwaggerUI).Methods(http.MethodGet)
|
|
|
|
router.HandleFunc("/swagger/v1/openapiv2.json", headscale.SwaggerAPIv1).
|
2022-09-26 11:34:04 +02:00
|
|
|
Methods(http.MethodGet)
|
2022-02-12 14:25:27 +01:00
|
|
|
|
2022-03-05 16:22:02 +01:00
|
|
|
if h.cfg.DERP.ServerEnabled {
|
2023-06-06 11:09:48 +02:00
|
|
|
router.HandleFunc("/derp", h.DERPServer.DERPHandler)
|
|
|
|
router.HandleFunc("/derp/probe", derpServer.DERPProbeHandler)
|
|
|
|
router.HandleFunc("/bootstrap-dns", derpServer.DERPBootstrapDNSHandler(h.DERPMap))
|
2022-03-04 00:01:31 +01:00
|
|
|
}
|
|
|
|
|
2022-07-21 23:57:07 +02:00
|
|
|
apiRouter := router.PathPrefix("/api").Subrouter()
|
|
|
|
apiRouter.Use(h.httpAuthenticationMiddleware)
|
|
|
|
apiRouter.PathPrefix("/v1/").HandlerFunc(grpcMux.ServeHTTP)
|
2022-02-12 14:25:27 +01:00
|
|
|
|
2023-03-03 17:14:30 +01:00
|
|
|
router.PathPrefix("/").HandlerFunc(notFoundHandler)
|
2022-02-12 14:25:27 +01:00
|
|
|
|
|
|
|
return router
|
|
|
|
}
|
|
|
|
|
2024-04-10 15:35:09 +02:00
|
|
|
// Serve launches the HTTP and gRPC server service Headscale and the API.
|
2020-06-21 12:32:08 +02:00
|
|
|
func (h *Headscale) Serve() error {
|
2024-05-24 10:15:34 +02:00
|
|
|
if profilingEnabled {
|
|
|
|
if profilingPath != "" {
|
|
|
|
err := os.MkdirAll(profilingPath, os.ModePerm)
|
2024-02-08 17:28:19 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("failed to create profiling directory")
|
|
|
|
}
|
|
|
|
|
2024-05-24 10:15:34 +02:00
|
|
|
defer profile.Start(profile.ProfilePath(profilingPath)).Stop()
|
2024-02-08 17:28:19 +01:00
|
|
|
} else {
|
|
|
|
defer profile.Start().Stop()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-26 22:42:56 +02:00
|
|
|
var err error
|
|
|
|
|
2024-07-18 07:38:25 +02:00
|
|
|
if err = h.loadACLPolicy(); err != nil {
|
|
|
|
return fmt.Errorf("failed to load ACL policy: %w", err)
|
|
|
|
}
|
|
|
|
|
2024-05-24 10:15:34 +02:00
|
|
|
if dumpConfig {
|
|
|
|
spew.Dump(h.cfg)
|
|
|
|
}
|
|
|
|
|
2022-03-05 20:04:31 +01:00
|
|
|
// Fetch an initial DERP Map before we start serving
|
2023-06-06 10:41:30 +02:00
|
|
|
h.DERPMap = derp.GetDERPMap(h.cfg.DERP)
|
2024-04-21 18:28:17 +02:00
|
|
|
h.mapper = mapper.NewMapper(h.db, h.cfg, h.DERPMap, h.nodeNotifier)
|
2022-03-05 20:04:31 +01:00
|
|
|
|
2022-03-05 16:22:02 +01:00
|
|
|
if h.cfg.DERP.ServerEnabled {
|
2022-03-18 13:10:35 +01:00
|
|
|
// When embedded DERP is enabled we always need a STUN server
|
2022-03-16 18:45:34 +01:00
|
|
|
if h.cfg.DERP.STUNAddr == "" {
|
2022-03-15 13:22:25 +01:00
|
|
|
return errSTUNAddressNotSet
|
|
|
|
}
|
|
|
|
|
2023-06-06 11:09:48 +02:00
|
|
|
region, err := h.DERPServer.GenerateRegion()
|
|
|
|
if err != nil {
|
2024-04-10 15:35:09 +02:00
|
|
|
return fmt.Errorf("generating DERP region for embedded server: %w", err)
|
2023-06-06 11:09:48 +02:00
|
|
|
}
|
|
|
|
|
2024-01-16 16:04:03 +01:00
|
|
|
if h.cfg.DERP.AutomaticallyAddEmbeddedDerpRegion {
|
|
|
|
h.DERPMap.Regions[region.RegionID] = ®ion
|
|
|
|
}
|
2023-06-06 11:09:48 +02:00
|
|
|
|
|
|
|
go h.DERPServer.ServeSTUN()
|
2022-03-05 20:04:31 +01:00
|
|
|
}
|
2021-10-26 22:42:56 +02:00
|
|
|
|
2022-03-05 20:04:31 +01:00
|
|
|
if h.cfg.DERP.AutoUpdate {
|
|
|
|
derpMapCancelChannel := make(chan struct{})
|
|
|
|
defer func() { derpMapCancelChannel <- struct{}{} }()
|
|
|
|
go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel)
|
2022-02-12 14:25:27 +01:00
|
|
|
}
|
|
|
|
|
2023-12-09 18:09:24 +01:00
|
|
|
if len(h.DERPMap.Regions) == 0 {
|
|
|
|
return errEmptyInitialDERPMap
|
|
|
|
}
|
|
|
|
|
2024-07-18 10:01:59 +02:00
|
|
|
// Start ephemeral node garbage collector and schedule all nodes
|
|
|
|
// that are already in the database and ephemeral. If they are still
|
|
|
|
// around between restarts, they will reconnect and the GC will
|
|
|
|
// be cancelled.
|
|
|
|
go h.ephemeralGC.Start()
|
|
|
|
ephmNodes, err := h.db.ListEphemeralNodes()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to list ephemeral nodes: %w", err)
|
|
|
|
}
|
|
|
|
for _, node := range ephmNodes {
|
|
|
|
h.ephemeralGC.Schedule(node.ID, h.cfg.EphemeralNodeInactivityTimeout)
|
|
|
|
}
|
2024-05-02 17:57:53 +02:00
|
|
|
|
|
|
|
expireNodeCtx, expireNodeCancel := context.WithCancel(context.Background())
|
|
|
|
defer expireNodeCancel()
|
|
|
|
go h.expireExpiredNodes(expireNodeCtx, updateInterval)
|
2022-02-12 14:25:27 +01:00
|
|
|
|
|
|
|
if zl.GlobalLevel() == zl.TraceLevel {
|
|
|
|
zerolog.RespLog = true
|
|
|
|
} else {
|
|
|
|
zerolog.RespLog = false
|
|
|
|
}
|
|
|
|
|
2022-02-12 18:05:30 +01:00
|
|
|
// Prepare group for running listeners
|
|
|
|
errorGroup := new(errgroup.Group)
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
defer cancel()
|
|
|
|
|
2022-02-12 14:25:27 +01:00
|
|
|
//
|
|
|
|
//
|
|
|
|
// Set up LOCAL listeners
|
|
|
|
//
|
2021-10-26 22:42:56 +02:00
|
|
|
|
2021-11-07 10:55:32 +01:00
|
|
|
err = h.ensureUnixSocketIsAbsent()
|
|
|
|
if err != nil {
|
2021-12-07 08:46:55 +01:00
|
|
|
return fmt.Errorf("unable to remove old socket file: %w", err)
|
2021-11-07 10:55:32 +01:00
|
|
|
}
|
|
|
|
|
2024-02-17 13:36:19 +01:00
|
|
|
socketDir := filepath.Dir(h.cfg.UnixSocket)
|
|
|
|
err = util.EnsureDir(socketDir)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("setting up unix socket: %w", err)
|
|
|
|
}
|
|
|
|
|
2021-10-30 16:08:16 +02:00
|
|
|
socketListener, err := net.Listen("unix", h.cfg.UnixSocket)
|
|
|
|
if err != nil {
|
2021-12-07 11:44:00 +01:00
|
|
|
return fmt.Errorf("failed to set up gRPC socket: %w", err)
|
2021-10-30 16:08:16 +02:00
|
|
|
}
|
|
|
|
|
2022-01-28 19:58:22 +01:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2023-09-11 13:04:58 +02:00
|
|
|
grpcGatewayMux := grpcRuntime.NewServeMux()
|
2021-10-26 22:42:56 +02:00
|
|
|
|
2021-10-30 16:08:16 +02:00
|
|
|
// Make the grpc-gateway connect to grpc over socket
|
|
|
|
grpcGatewayConn, err := grpc.Dial(
|
|
|
|
h.cfg.UnixSocket,
|
|
|
|
[]grpc.DialOption{
|
2022-02-12 20:48:05 +01:00
|
|
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
2023-05-11 09:09:18 +02:00
|
|
|
grpc.WithContextDialer(util.GrpcSocketDialer),
|
2021-10-30 16:08:16 +02:00
|
|
|
}...,
|
|
|
|
)
|
2021-10-29 18:45:06 +02:00
|
|
|
if err != nil {
|
2024-04-10 15:35:09 +02:00
|
|
|
return fmt.Errorf("setting up gRPC gateway via socket: %w", err)
|
2021-10-29 18:45:06 +02:00
|
|
|
}
|
2021-10-26 22:42:56 +02:00
|
|
|
|
2021-10-29 18:45:06 +02:00
|
|
|
// Connect to the gRPC server over localhost to skip
|
|
|
|
// the authentication.
|
2021-11-04 23:18:55 +01:00
|
|
|
err = v1.RegisterHeadscaleServiceHandler(ctx, grpcGatewayMux, grpcGatewayConn)
|
2021-10-26 22:42:56 +02:00
|
|
|
if err != nil {
|
2024-04-10 15:35:09 +02:00
|
|
|
return fmt.Errorf("registering Headscale API service to gRPC: %w", err)
|
2021-10-26 22:42:56 +02:00
|
|
|
}
|
|
|
|
|
2021-10-31 20:52:34 +01:00
|
|
|
// Start the local gRPC server without TLS and without authentication
|
2023-12-10 15:22:59 +01:00
|
|
|
grpcSocket := grpc.NewServer(
|
|
|
|
// Uncomment to debug grpc communication.
|
|
|
|
// zerolog.UnaryInterceptor(),
|
|
|
|
)
|
2021-10-31 20:52:34 +01:00
|
|
|
|
2021-11-04 23:18:55 +01:00
|
|
|
v1.RegisterHeadscaleServiceServer(grpcSocket, newHeadscaleV1APIServer(h))
|
2021-10-31 20:52:34 +01:00
|
|
|
reflection.Register(grpcSocket)
|
2021-10-29 18:45:06 +02:00
|
|
|
|
2022-02-12 18:05:30 +01:00
|
|
|
errorGroup.Go(func() error { return grpcSocket.Serve(socketListener) })
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// Set up REMOTE listeners
|
|
|
|
//
|
|
|
|
|
|
|
|
tlsConfig, err := h.getTLSSettings()
|
|
|
|
if err != nil {
|
2024-04-10 15:35:09 +02:00
|
|
|
return fmt.Errorf("configuring TLS settings: %w", err)
|
2022-02-12 18:05:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// gRPC setup
|
|
|
|
//
|
|
|
|
|
2022-02-12 20:48:05 +01:00
|
|
|
// 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-06-30 23:35:22 +02:00
|
|
|
var grpcServer *grpc.Server
|
|
|
|
var grpcListener net.Listener
|
2022-02-13 10:08:46 +01:00
|
|
|
if tlsConfig != nil || h.cfg.GRPCAllowInsecure {
|
2022-02-12 18:05:30 +01:00
|
|
|
log.Info().Msgf("Enabling remote gRPC at %s", h.cfg.GRPCAddr)
|
|
|
|
|
|
|
|
grpcOptions := []grpc.ServerOption{
|
|
|
|
grpc.UnaryInterceptor(
|
2022-09-04 11:34:23 +02:00
|
|
|
grpcMiddleware.ChainUnaryServer(
|
2022-02-12 18:05:30 +01:00
|
|
|
h.grpcAuthenticationInterceptor,
|
2023-12-10 15:22:59 +01:00
|
|
|
// Uncomment to debug grpc communication.
|
|
|
|
// zerolog.NewUnaryServerInterceptor(),
|
2022-02-12 18:05:30 +01:00
|
|
|
),
|
|
|
|
),
|
2022-02-13 10:08:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if tlsConfig != nil {
|
|
|
|
grpcOptions = append(grpcOptions,
|
|
|
|
grpc.Creds(credentials.NewTLS(tlsConfig)),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
log.Warn().Msg("gRPC is running without security")
|
2022-02-12 18:05:30 +01:00
|
|
|
}
|
|
|
|
|
2022-06-30 23:35:22 +02:00
|
|
|
grpcServer = grpc.NewServer(grpcOptions...)
|
2022-02-12 18:05:30 +01:00
|
|
|
|
|
|
|
v1.RegisterHeadscaleServiceServer(grpcServer, newHeadscaleV1APIServer(h))
|
|
|
|
reflection.Register(grpcServer)
|
|
|
|
|
2022-06-30 23:35:22 +02:00
|
|
|
grpcListener, err = net.Listen("tcp", h.cfg.GRPCAddr)
|
2022-02-12 18:05:30 +01: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-12 20:30:25 +01:00
|
|
|
|
|
|
|
log.Info().
|
|
|
|
Msgf("listening and serving gRPC on: %s", h.cfg.GRPCAddr)
|
2022-02-12 17:15:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// HTTP setup
|
|
|
|
//
|
2022-08-13 20:55:37 +02:00
|
|
|
// This is the regular router that we expose
|
2024-04-21 22:08:59 +02:00
|
|
|
// over our main Addr
|
2022-02-12 17:15:26 +01:00
|
|
|
router := h.createRouter(grpcGatewayMux)
|
|
|
|
|
|
|
|
httpServer := &http.Server{
|
|
|
|
Addr: h.cfg.Addr,
|
|
|
|
Handler: router,
|
2024-04-10 15:35:09 +02:00
|
|
|
ReadTimeout: types.HTTPTimeout,
|
|
|
|
|
2024-07-22 08:56:00 +02:00
|
|
|
// Long polling should not have any timeout, this is overridden
|
2024-04-10 15:35:09 +02:00
|
|
|
// further down the chain
|
|
|
|
WriteTimeout: types.HTTPTimeout,
|
2022-02-12 17:15:26 +01:00
|
|
|
}
|
|
|
|
|
2022-02-12 17:33:18 +01:00
|
|
|
var httpListener net.Listener
|
2022-02-12 17:15:26 +01:00
|
|
|
if tlsConfig != nil {
|
|
|
|
httpServer.TLSConfig = tlsConfig
|
2022-02-12 17:33:18 +01: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-12 17:15:26 +01:00
|
|
|
}
|
|
|
|
|
2022-02-12 17:33:18 +01:00
|
|
|
errorGroup.Go(func() error { return httpServer.Serve(httpListener) })
|
2022-02-12 14:25:27 +01:00
|
|
|
|
2021-11-13 09:36:45 +01:00
|
|
|
log.Info().
|
2022-02-12 20:30:25 +01:00
|
|
|
Msgf("listening and serving HTTP on: %s", h.cfg.Addr)
|
2021-10-26 22:42:56 +02:00
|
|
|
|
2024-04-10 15:35:09 +02:00
|
|
|
debugMux := http.NewServeMux()
|
2024-04-21 22:08:59 +02:00
|
|
|
debugMux.Handle("/debug/pprof/", http.DefaultServeMux)
|
2024-04-10 15:35:09 +02:00
|
|
|
debugMux.HandleFunc("/debug/notifier", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write([]byte(h.nodeNotifier.String()))
|
|
|
|
})
|
|
|
|
debugMux.Handle("/metrics", promhttp.Handler())
|
|
|
|
|
|
|
|
debugHTTPServer := &http.Server{
|
2022-02-28 14:40:02 +01:00
|
|
|
Addr: h.cfg.MetricsAddr,
|
2024-04-10 15:35:09 +02:00
|
|
|
Handler: debugMux,
|
|
|
|
ReadTimeout: types.HTTPTimeout,
|
2022-02-28 14:40:02 +01:00
|
|
|
WriteTimeout: 0,
|
|
|
|
}
|
2022-02-21 16:50:15 +01:00
|
|
|
|
2024-04-10 15:35:09 +02:00
|
|
|
debugHTTPListener, err := net.Listen("tcp", h.cfg.MetricsAddr)
|
2022-02-28 14:40:02 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to bind to TCP address: %w", err)
|
|
|
|
}
|
2022-02-21 16:50:15 +01:00
|
|
|
|
2024-04-10 15:35:09 +02:00
|
|
|
errorGroup.Go(func() error { return debugHTTPServer.Serve(debugHTTPListener) })
|
2022-02-21 16:50:15 +01:00
|
|
|
|
2022-02-28 14:40:02 +01:00
|
|
|
log.Info().
|
2024-04-10 15:35:09 +02:00
|
|
|
Msgf("listening and serving debug and metrics on: %s", h.cfg.MetricsAddr)
|
2022-02-21 16:50:15 +01:00
|
|
|
|
2023-12-20 21:47:48 +01:00
|
|
|
var tailsqlContext context.Context
|
|
|
|
if tailsqlEnabled {
|
2024-02-09 07:27:00 +01:00
|
|
|
if h.cfg.Database.Type != types.DatabaseSqlite {
|
|
|
|
log.Fatal().
|
|
|
|
Str("type", h.cfg.Database.Type).
|
|
|
|
Msgf("tailsql only support %q", types.DatabaseSqlite)
|
2023-12-20 21:47:48 +01:00
|
|
|
}
|
|
|
|
if tailsqlTSKey == "" {
|
|
|
|
log.Fatal().Msg("tailsql requires TS_AUTHKEY to be set")
|
|
|
|
}
|
|
|
|
tailsqlContext = context.Background()
|
2024-02-09 07:27:00 +01:00
|
|
|
go runTailSQLService(ctx, util.TSLogfWrapper(), tailsqlStateDir, h.cfg.Database.Sqlite.Path)
|
2023-12-20 21:47:48 +01:00
|
|
|
}
|
|
|
|
|
2022-05-31 10:57:20 +02:00
|
|
|
// Handle common process-killing signals so we can gracefully shut down:
|
|
|
|
sigc := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(sigc,
|
|
|
|
syscall.SIGHUP,
|
|
|
|
syscall.SIGINT,
|
|
|
|
syscall.SIGTERM,
|
|
|
|
syscall.SIGQUIT,
|
|
|
|
syscall.SIGHUP)
|
2022-07-11 20:33:24 +02:00
|
|
|
sigFunc := func(c chan os.Signal) {
|
2022-05-31 10:57:20 +02:00
|
|
|
// Wait for a SIGINT or SIGKILL:
|
2022-05-31 13:02:23 +02:00
|
|
|
for {
|
|
|
|
sig := <-c
|
|
|
|
switch sig {
|
|
|
|
case syscall.SIGHUP:
|
|
|
|
log.Info().
|
|
|
|
Str("signal", sig.String()).
|
|
|
|
Msg("Received SIGHUP, reloading ACL and Config")
|
|
|
|
|
2022-06-30 23:35:22 +02:00
|
|
|
// TODO(kradalby): Reload config on SIGHUP
|
2024-07-18 07:38:25 +02:00
|
|
|
if err := h.loadACLPolicy(); err != nil {
|
|
|
|
log.Error().Err(err).Msg("failed to reload ACL policy")
|
|
|
|
}
|
2022-05-31 13:02:23 +02:00
|
|
|
|
2024-07-18 07:38:25 +02:00
|
|
|
if h.ACLPolicy != nil {
|
2022-05-31 13:02:23 +02:00
|
|
|
log.Info().
|
2022-06-11 13:54:44 +02:00
|
|
|
Msg("ACL policy successfully reloaded, notifying nodes of change")
|
|
|
|
|
2024-02-08 17:28:19 +01:00
|
|
|
ctx := types.NotifyCtx(context.Background(), "acl-sighup", "na")
|
|
|
|
h.nodeNotifier.NotifyAll(ctx, types.StateUpdate{
|
2023-06-29 12:20:22 +02:00
|
|
|
Type: types.StateFullUpdate,
|
|
|
|
})
|
2022-05-31 13:02:23 +02:00
|
|
|
}
|
|
|
|
default:
|
2024-09-09 14:10:22 +02:00
|
|
|
info := func(msg string) { log.Info().Msg(msg) }
|
2022-05-31 13:02:23 +02:00
|
|
|
log.Info().
|
|
|
|
Str("signal", sig.String()).
|
|
|
|
Msg("Received signal to stop, shutting down gracefully")
|
|
|
|
|
2024-05-02 17:57:53 +02:00
|
|
|
expireNodeCancel()
|
2024-07-18 10:01:59 +02:00
|
|
|
h.ephemeralGC.Close()
|
2024-05-02 17:57:53 +02:00
|
|
|
|
2022-05-31 13:02:23 +02:00
|
|
|
// Gracefully shut down servers
|
2022-08-04 10:47:00 +02:00
|
|
|
ctx, cancel := context.WithTimeout(
|
|
|
|
context.Background(),
|
2023-06-06 10:41:30 +02:00
|
|
|
types.HTTPShutdownTimeout,
|
2022-08-04 10:47:00 +02:00
|
|
|
)
|
2024-09-09 14:10:22 +02:00
|
|
|
info("shutting down debug http server")
|
2024-04-10 15:35:09 +02:00
|
|
|
if err := debugHTTPServer.Shutdown(ctx); err != nil {
|
2024-09-09 14:10:22 +02:00
|
|
|
log.Error().Err(err).Msg("failed to shutdown prometheus http")
|
2022-06-17 10:58:22 +02:00
|
|
|
}
|
2024-09-09 14:10:22 +02:00
|
|
|
info("shutting down main http server")
|
2022-06-17 10:58:22 +02:00
|
|
|
if err := httpServer.Shutdown(ctx); err != nil {
|
2024-09-09 14:10:22 +02:00
|
|
|
log.Error().Err(err).Msg("failed to shutdown http")
|
2022-06-17 10:58:22 +02:00
|
|
|
}
|
2024-05-02 13:39:19 +02:00
|
|
|
|
2024-09-09 14:10:22 +02:00
|
|
|
info("closing node notifier")
|
|
|
|
h.nodeNotifier.Close()
|
|
|
|
|
|
|
|
info("waiting for netmap stream to close")
|
|
|
|
h.pollNetMapStreamWG.Wait()
|
|
|
|
|
|
|
|
info("shutting down grpc server (socket)")
|
2022-05-31 13:02:23 +02:00
|
|
|
grpcSocket.GracefulStop()
|
|
|
|
|
2022-06-30 23:35:22 +02:00
|
|
|
if grpcServer != nil {
|
2024-09-09 14:10:22 +02:00
|
|
|
info("shutting down grpc server (external)")
|
2022-06-30 23:35:22 +02:00
|
|
|
grpcServer.GracefulStop()
|
|
|
|
grpcListener.Close()
|
|
|
|
}
|
|
|
|
|
2023-12-20 21:47:48 +01:00
|
|
|
if tailsqlContext != nil {
|
2024-09-09 14:10:22 +02:00
|
|
|
info("shutting down tailsql")
|
2023-12-20 21:47:48 +01:00
|
|
|
tailsqlContext.Done()
|
|
|
|
}
|
|
|
|
|
2022-05-31 13:02:23 +02:00
|
|
|
// Close network listeners
|
2024-09-09 14:10:22 +02:00
|
|
|
info("closing network listeners")
|
2024-04-10 15:35:09 +02:00
|
|
|
debugHTTPListener.Close()
|
2022-05-31 13:02:23 +02:00
|
|
|
httpListener.Close()
|
|
|
|
grpcGatewayConn.Close()
|
|
|
|
|
|
|
|
// Stop listening (and unlink the socket if unix type):
|
2024-09-09 14:10:22 +02:00
|
|
|
info("closing socket listener")
|
2022-05-31 13:02:23 +02:00
|
|
|
socketListener.Close()
|
|
|
|
|
2022-06-17 10:58:22 +02:00
|
|
|
// Close db connections
|
2024-09-09 14:10:22 +02:00
|
|
|
info("closing database connection")
|
2023-05-21 18:37:59 +02:00
|
|
|
err = h.db.Close()
|
2022-06-17 10:58:22 +02:00
|
|
|
if err != nil {
|
2024-09-09 14:10:22 +02:00
|
|
|
log.Error().Err(err).Msg("failed to close db")
|
2022-06-17 10:58:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
log.Info().
|
|
|
|
Msg("Headscale stopped")
|
|
|
|
|
2022-05-31 13:02:23 +02:00
|
|
|
// And we're done:
|
2022-06-17 10:58:22 +02:00
|
|
|
cancel()
|
2023-07-07 13:29:53 +02:00
|
|
|
|
|
|
|
return
|
2022-05-31 13:02:23 +02:00
|
|
|
}
|
2022-05-31 10:57:20 +02:00
|
|
|
}
|
2022-06-30 23:35:22 +02:00
|
|
|
}
|
2022-07-11 20:33:24 +02:00
|
|
|
errorGroup.Go(func() error {
|
|
|
|
sigFunc(sigc)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
2022-05-31 10:57:20 +02:00
|
|
|
|
2021-11-14 20:32:03 +01:00
|
|
|
return errorGroup.Wait()
|
2021-10-26 22:42:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (h *Headscale) getTLSSettings() (*tls.Config, error) {
|
2021-11-14 17:51:34 +01:00
|
|
|
var err error
|
2022-06-03 10:14:14 +02:00
|
|
|
if h.cfg.TLS.LetsEncrypt.Hostname != "" {
|
2021-04-24 04:54:15 +02:00
|
|
|
if !strings.HasPrefix(h.cfg.ServerURL, "https://") {
|
2021-11-13 09:36:45 +01:00
|
|
|
log.Warn().
|
|
|
|
Msg("Listening with TLS but ServerURL does not start with https://")
|
2021-04-24 04:54:15 +02:00
|
|
|
}
|
|
|
|
|
2021-11-14 20:32:03 +01:00
|
|
|
certManager := autocert.Manager{
|
2021-04-24 04:54:15 +02:00
|
|
|
Prompt: autocert.AcceptTOS,
|
2022-06-03 10:14:14 +02:00
|
|
|
HostPolicy: autocert.HostWhitelist(h.cfg.TLS.LetsEncrypt.Hostname),
|
|
|
|
Cache: autocert.DirCache(h.cfg.TLS.LetsEncrypt.CacheDir),
|
2021-10-03 20:26:38 +02:00
|
|
|
Client: &acme.Client{
|
|
|
|
DirectoryURL: h.cfg.ACMEURL,
|
|
|
|
},
|
|
|
|
Email: h.cfg.ACMEEmail,
|
2021-04-24 04:54:15 +02:00
|
|
|
}
|
2021-10-02 16:29:27 +02:00
|
|
|
|
2022-06-03 10:14:14 +02:00
|
|
|
switch h.cfg.TLS.LetsEncrypt.ChallengeType {
|
2023-06-06 11:12:36 +02:00
|
|
|
case types.TLSALPN01ChallengeType:
|
2021-04-24 04:54:15 +02:00
|
|
|
// 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
|
2021-07-24 15:01:20 +02:00
|
|
|
// must be reachable on port 443.
|
2021-11-14 20:32:03 +01:00
|
|
|
return certManager.TLSConfig(), nil
|
2021-11-14 18:44:37 +01:00
|
|
|
|
2023-06-06 11:12:36 +02:00
|
|
|
case types.HTTP01ChallengeType:
|
2021-04-24 04:54:15 +02:00
|
|
|
// 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.
|
2022-09-04 11:47:05 +02:00
|
|
|
|
|
|
|
server := &http.Server{
|
|
|
|
Addr: h.cfg.TLS.LetsEncrypt.Listen,
|
|
|
|
Handler: certManager.HTTPHandler(http.HandlerFunc(h.redirect)),
|
2024-04-10 15:35:09 +02:00
|
|
|
ReadTimeout: types.HTTPTimeout,
|
2022-09-04 11:47:05 +02:00
|
|
|
}
|
|
|
|
|
2021-04-24 04:54:15 +02:00
|
|
|
go func() {
|
2022-09-26 11:33:48 +02:00
|
|
|
err := server.ListenAndServe()
|
2021-08-05 19:11:26 +02:00
|
|
|
log.Fatal().
|
2022-01-25 23:11:15 +01:00
|
|
|
Caller().
|
2022-09-04 11:47:05 +02:00
|
|
|
Err(err).
|
2021-08-05 19:11:26 +02:00
|
|
|
Msg("failed to set up a HTTP server")
|
2021-04-24 04:54:15 +02:00
|
|
|
}()
|
2021-10-26 22:42:56 +02:00
|
|
|
|
2021-11-14 20:32:03 +01:00
|
|
|
return certManager.TLSConfig(), nil
|
2021-11-14 18:44:37 +01:00
|
|
|
|
|
|
|
default:
|
2021-11-15 20:18:14 +01:00
|
|
|
return nil, errUnsupportedLetsEncryptChallengeType
|
2021-04-24 04:54:15 +02:00
|
|
|
}
|
2022-06-03 10:14:14 +02:00
|
|
|
} else if h.cfg.TLS.CertPath == "" {
|
2021-04-23 22:54:35 +02:00
|
|
|
if !strings.HasPrefix(h.cfg.ServerURL, "http://") {
|
2021-08-05 19:11:26 +02:00
|
|
|
log.Warn().Msg("Listening without TLS but ServerURL does not start with http://")
|
2021-04-23 22:54:35 +02:00
|
|
|
}
|
2021-10-26 22:42:56 +02:00
|
|
|
|
2021-11-14 17:51:34 +01:00
|
|
|
return nil, err
|
2021-04-23 22:54:35 +02:00
|
|
|
} else {
|
|
|
|
if !strings.HasPrefix(h.cfg.ServerURL, "https://") {
|
2021-08-05 19:11:26 +02:00
|
|
|
log.Warn().Msg("Listening with TLS but ServerURL does not start with https://")
|
2021-04-23 22:54:35 +02:00
|
|
|
}
|
2022-01-29 18:59:31 +01:00
|
|
|
|
2021-11-15 19:31:52 +01:00
|
|
|
tlsConfig := &tls.Config{
|
|
|
|
NextProtos: []string{"http/1.1"},
|
|
|
|
Certificates: make([]tls.Certificate, 1),
|
|
|
|
MinVersion: tls.VersionTLS12,
|
|
|
|
}
|
2022-01-29 18:59:31 +01:00
|
|
|
|
2022-06-03 10:14:14 +02:00
|
|
|
tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(h.cfg.TLS.CertPath, h.cfg.TLS.KeyPath)
|
2021-10-26 22:42:56 +02:00
|
|
|
|
|
|
|
return tlsConfig, err
|
2021-04-23 22:54:35 +02:00
|
|
|
}
|
2020-06-21 12:32:08 +02:00
|
|
|
}
|
2021-08-19 00:21:11 +02:00
|
|
|
|
2023-03-03 17:14:30 +01:00
|
|
|
func notFoundHandler(
|
2022-06-26 11:55:37 +02:00
|
|
|
writer http.ResponseWriter,
|
|
|
|
req *http.Request,
|
2022-06-18 18:41:42 +02:00
|
|
|
) {
|
2022-06-26 11:55:37 +02:00
|
|
|
body, _ := io.ReadAll(req.Body)
|
2021-10-29 18:45:06 +02:00
|
|
|
|
|
|
|
log.Trace().
|
2022-06-26 11:55:37 +02:00
|
|
|
Interface("header", req.Header).
|
|
|
|
Interface("proto", req.Proto).
|
|
|
|
Interface("url", req.URL).
|
2021-11-14 20:32:03 +01:00
|
|
|
Bytes("body", body).
|
2021-10-29 18:45:06 +02:00
|
|
|
Msg("Request did not match")
|
2023-03-03 17:14:30 +01:00
|
|
|
writer.WriteHeader(http.StatusNotFound)
|
2021-10-29 18:45:06 +02:00
|
|
|
}
|
2021-11-28 10:17:18 +01:00
|
|
|
|
|
|
|
func readOrCreatePrivateKey(path string) (*key.MachinePrivate, error) {
|
2024-02-17 13:36:19 +01:00
|
|
|
dir := filepath.Dir(path)
|
|
|
|
err := util.EnsureDir(dir)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("ensuring private key directory: %w", err)
|
|
|
|
}
|
|
|
|
|
2021-11-28 10:17:18 +01:00
|
|
|
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(
|
2023-12-20 21:47:48 +01:00
|
|
|
"failed to save private key to disk at path %q: %w",
|
|
|
|
path,
|
2021-11-28 10:17:18 +01:00
|
|
|
err,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &machineKey, nil
|
|
|
|
} else if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to read private key file: %w", err)
|
|
|
|
}
|
|
|
|
|
2022-01-28 18:23:01 +01:00
|
|
|
trimmedPrivateKey := strings.TrimSpace(string(privateKey))
|
2021-11-28 10:17:18 +01:00
|
|
|
|
|
|
|
var machineKey key.MachinePrivate
|
2023-11-16 17:55:29 +01:00
|
|
|
if err = machineKey.UnmarshalText([]byte(trimmedPrivateKey)); err != nil {
|
2021-11-28 10:17:18 +01:00
|
|
|
return nil, fmt.Errorf("failed to parse private key: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &machineKey, nil
|
|
|
|
}
|
2024-07-18 07:38:25 +02:00
|
|
|
|
|
|
|
func (h *Headscale) loadACLPolicy() error {
|
|
|
|
var (
|
|
|
|
pol *policy.ACLPolicy
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
switch h.cfg.Policy.Mode {
|
|
|
|
case types.PolicyModeFile:
|
|
|
|
path := h.cfg.Policy.Path
|
|
|
|
|
|
|
|
// It is fine to start headscale without a policy file.
|
|
|
|
if len(path) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
absPath := util.AbsolutePathFromConfigPath(path)
|
|
|
|
pol, err = policy.LoadACLPolicyFromPath(absPath)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to load ACL policy from file: %w", err)
|
|
|
|
}
|
2024-08-30 16:58:29 +02:00
|
|
|
|
|
|
|
// Validate and reject configuration that would error when applied
|
|
|
|
// when creating a map response. This requires nodes, so there is still
|
|
|
|
// a scenario where they might be allowed if the server has no nodes
|
|
|
|
// yet, but it should help for the general case and for hot reloading
|
|
|
|
// configurations.
|
|
|
|
// Note that this check is only done for file-based policies in this function
|
|
|
|
// as the database-based policies are checked in the gRPC API where it is not
|
|
|
|
// allowed to be written to the database.
|
|
|
|
nodes, err := h.db.ListNodes()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("loading nodes from database to validate policy: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = pol.CompileFilterRules(nodes)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("verifying policy rules: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(nodes) > 0 {
|
|
|
|
_, err = pol.CompileSSHPolicy(nodes[0], nodes)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("verifying SSH rules: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-18 07:38:25 +02:00
|
|
|
case types.PolicyModeDB:
|
|
|
|
p, err := h.db.GetPolicy()
|
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, types.ErrPolicyNotFound) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Errorf("failed to get policy from database: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
pol, err = policy.LoadACLPolicyFromBytes([]byte(p.Data))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to parse policy: %w", err)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
log.Fatal().
|
|
|
|
Str("mode", string(h.cfg.Policy.Mode)).
|
|
|
|
Msg("Unknown ACL policy mode")
|
|
|
|
}
|
|
|
|
|
|
|
|
h.ACLPolicy = pol
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|