mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-07-09 11:40:15 +00:00
refactor: use tailscale api for user checking instead of tsnet
This commit is contained in:
@@ -278,27 +278,6 @@ func (app *BootstrapApp) Setup() error {
|
||||
|
||||
app.runtime.ConfiguredProviders = configuredProviders
|
||||
|
||||
// if tailscale is enabled and listening, replace the app url with the tailscale hostname
|
||||
if app.services.tailscaleService != nil && app.config.Experimental.Tailscale.Listen {
|
||||
tailscaleUrl := "https://" + app.services.tailscaleService.GetHostname()
|
||||
|
||||
// if the tailscale url is different from the app url, replace it
|
||||
if tailscaleUrl != app.runtime.AppURL {
|
||||
app.log.App.Info().Msg("Listening on tailscale, replacing app url with tailscale hostname")
|
||||
|
||||
app.runtime.AppURL = tailscaleUrl
|
||||
|
||||
// also update cookie domain
|
||||
cookieDomain, err := utils.GetCookieDomain(tailscaleUrl, app.config.Auth.SubdomainsEnabled)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get cookie domain: %w", err)
|
||||
}
|
||||
|
||||
app.runtime.CookieDomain = cookieDomain
|
||||
}
|
||||
}
|
||||
|
||||
// force an update of the redirect urls for all oauth providers, if they are empty
|
||||
services := app.services.oauthBrokerService.GetConfiguredServices()
|
||||
|
||||
|
||||
@@ -126,17 +126,9 @@ func (app *BootstrapApp) setupRouter() error {
|
||||
}
|
||||
|
||||
// Top down
|
||||
// 1. Tailscale (if tailscale.listen)
|
||||
// 2. Unix socket (if server.socketPath)
|
||||
// 3. HTTP - default
|
||||
// 1. Unix socket (if server.socketPath)
|
||||
// 2. HTTP - default
|
||||
func (app *BootstrapApp) getListenerFunc() (func(ctx context.Context) error, error) {
|
||||
if app.config.Experimental.Tailscale.Listen {
|
||||
if app.services.tailscaleService == nil {
|
||||
return nil, fmt.Errorf("experimental.tailscale.listen is enabled but tailscale service is not initialized")
|
||||
}
|
||||
return app.serveTailscale, nil
|
||||
}
|
||||
|
||||
if app.config.Server.SocketPath != "" {
|
||||
return app.serveUnix, nil
|
||||
}
|
||||
@@ -190,22 +182,6 @@ func (app *BootstrapApp) serveUnix(ctx context.Context) error {
|
||||
return app.serve(listener, server, ctx, "unix socket")
|
||||
}
|
||||
|
||||
func (app *BootstrapApp) serveTailscale(ctx context.Context) error {
|
||||
app.log.App.Info().Msgf("Starting Tailscale server on %s", fmt.Sprintf("https://%s", app.services.tailscaleService.GetHostname()))
|
||||
|
||||
listener, err := app.services.tailscaleService.CreateListener()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create tailscale listener: %w", err)
|
||||
}
|
||||
|
||||
server := &http.Server{
|
||||
Handler: app.router.Handler(),
|
||||
}
|
||||
|
||||
return app.serve(listener, server, ctx, "tailscale")
|
||||
}
|
||||
|
||||
func (app *BootstrapApp) serve(listener net.Listener, server *http.Server, ctx context.Context, name string) error {
|
||||
shutdown := func() {
|
||||
// we use a new context for the shutdown since the main one is cancelled
|
||||
|
||||
@@ -112,7 +112,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
|
||||
// Lastly check if we have a tailscale session to add
|
||||
if m.tailscale != nil {
|
||||
tailscaleContext, err := m.tailscaleWhois(c.Request.Context(), c.ClientIP())
|
||||
tailscaleContext, err := m.tailscaleWhois(c.ClientIP())
|
||||
|
||||
if err != nil {
|
||||
m.log.App.Error().Err(err).Msgf("Error performing tailscale whois for IP %s: %v", c.ClientIP(), err)
|
||||
@@ -167,7 +167,7 @@ func (m *ContextMiddleware) cookieAuth(ctx context.Context, uuid string, ip stri
|
||||
userContext.Local.Attributes.Email = utils.CompileUserEmail(user.Username, m.runtime.CookieDomain)
|
||||
}
|
||||
case model.ProviderTailscale:
|
||||
tailscaleContext, err := m.tailscaleWhois(ctx, ip)
|
||||
tailscaleContext, err := m.tailscaleWhois(ip)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error performing tailscale whois: %w", err)
|
||||
@@ -177,6 +177,10 @@ func (m *ContextMiddleware) cookieAuth(ctx context.Context, uuid string, ip stri
|
||||
return nil, nil, fmt.Errorf("tailscale whois returned no result for IP: %s", ip)
|
||||
}
|
||||
|
||||
if tailscaleContext.Email != userContext.Tailscale.Email {
|
||||
return nil, nil, fmt.Errorf("device owner mismatch, login again")
|
||||
}
|
||||
|
||||
userContext.Tailscale = tailscaleContext
|
||||
case model.ProviderLDAP:
|
||||
search, err := m.auth.SearchUser(userContext.LDAP.Username)
|
||||
@@ -308,12 +312,12 @@ func (m *ContextMiddleware) isIgnorePath(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *ContextMiddleware) tailscaleWhois(ctx context.Context, ip string) (*model.TailscaleContext, error) {
|
||||
func (m *ContextMiddleware) tailscaleWhois(ip string) (*model.TailscaleContext, error) {
|
||||
if m.tailscale == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
whois, err := m.tailscale.Whois(ctx, ip)
|
||||
whois, err := m.tailscale.Whois(ip)
|
||||
|
||||
if err != nil {
|
||||
m.log.App.Error().Err(err).Msgf("Error performing Tailscale whois for IP %s: %v", ip, err)
|
||||
@@ -324,13 +328,15 @@ func (m *ContextMiddleware) tailscaleWhois(ctx context.Context, ip string) (*mod
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
username := strings.Replace(whois.LoginName, "@", "_", 1)
|
||||
|
||||
uctx := model.TailscaleContext{
|
||||
BaseContext: model.BaseContext{
|
||||
Username: whois.NodeName,
|
||||
Username: username,
|
||||
Email: whois.LoginName,
|
||||
Name: whois.DisplayName,
|
||||
},
|
||||
UserID: whois.UserID,
|
||||
NodeName: whois.NodeName,
|
||||
}
|
||||
|
||||
return &uctx, nil
|
||||
|
||||
+12
-16
@@ -1,6 +1,9 @@
|
||||
package model
|
||||
|
||||
import "os"
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RuntimeEnv int
|
||||
|
||||
@@ -81,10 +84,8 @@ func NewDefaultConfiguration(runtimeEnv RuntimeEnv) *Config {
|
||||
PrivateKeyPath: "./tinyauth_oidc_key",
|
||||
PublicKeyPath: "./tinyauth_oidc_key.pub",
|
||||
},
|
||||
Experimental: ExperimentalConfig{
|
||||
Tailscale: TailscaleConfig{
|
||||
Dir: "./tailscale_state",
|
||||
},
|
||||
Tailscale: TailscaleConfig{
|
||||
CacheDuration: int(time.Duration(5 * time.Minute).Seconds()),
|
||||
},
|
||||
LabelProvider: "auto",
|
||||
}
|
||||
@@ -95,7 +96,6 @@ func NewDefaultConfiguration(runtimeEnv RuntimeEnv) *Config {
|
||||
cfg.Resources.Path = "/data/resources"
|
||||
cfg.OIDC.PrivateKeyPath = "/data/oidc/key.pem"
|
||||
cfg.OIDC.PublicKeyPath = "/data/oidc/key.pub"
|
||||
cfg.Experimental.Tailscale.Dir = "/data/tailscale"
|
||||
}
|
||||
|
||||
return cfg
|
||||
@@ -114,6 +114,7 @@ type Config struct {
|
||||
UI UIConfig `description:"UI customization." yaml:"ui,omitempty"`
|
||||
LDAP LDAPConfig `description:"LDAP configuration." yaml:"ldap,omitempty"`
|
||||
Experimental ExperimentalConfig `description:"Experimental features, use with caution." yaml:"experimental,omitempty"`
|
||||
Tailscale TailscaleConfig `description:"Tailscale configuration." yaml:"tailscale,omitempty"`
|
||||
LabelProvider string `description:"Label provider to use for ACLs (auto, docker, kubernetes or none to disable). auto detects the environment." yaml:"labelProvider,omitempty"`
|
||||
Log LogConfig `description:"Logging configuration." yaml:"log,omitempty"`
|
||||
ConfigFile string `description:"Path to config file." yaml:"-"`
|
||||
@@ -238,18 +239,13 @@ type LogStreamConfig struct {
|
||||
Level string `description:"Log level for this stream. Use global if empty." yaml:"level,omitempty"`
|
||||
}
|
||||
|
||||
type ExperimentalConfig struct {
|
||||
Tailscale TailscaleConfig `description:"Tailscale configuration." yaml:"tailscale"`
|
||||
}
|
||||
type ExperimentalConfig struct{}
|
||||
|
||||
type TailscaleConfig struct {
|
||||
Enabled bool `description:"Enable Tailscale integration." yaml:"enabled,omitempty"`
|
||||
Dir string `description:"Tailscale state directory." yaml:"dir,omitempty"`
|
||||
Hostname string `description:"Tailscale hostname." yaml:"hostname,omitempty"`
|
||||
AuthKey string `description:"Tailscale auth key." yaml:"authKey,omitempty"`
|
||||
Ephemeral bool `description:"Use ephemeral Tailscale node." yaml:"ephemeral,omitempty"`
|
||||
Funnel bool `description:"Enable Tailscale Funnel." yaml:"funnel,omitempty"`
|
||||
Listen bool `description:"Listen on the Tailscale address instead of standard address." yaml:"listen,omitempty"`
|
||||
Enabled bool `description:"Enable Tailscale integration." yaml:"enabled,omitempty"`
|
||||
APIToken string `description:"Tailscale API token." yaml:"apiToken,omitempty"`
|
||||
Tailnet string `description:"Tailnet name." yaml:"tailnet,omitempty"`
|
||||
CacheDuration int `description:"Cache duration for Tailscale device and user lists in seconds." yaml:"cacheDuration,omitempty"`
|
||||
}
|
||||
|
||||
// OAuth/OIDC config
|
||||
|
||||
@@ -59,7 +59,7 @@ type LDAPContext struct {
|
||||
|
||||
type TailscaleContext struct {
|
||||
BaseContext
|
||||
UserID string
|
||||
NodeName string
|
||||
}
|
||||
|
||||
func (c *UserContext) IsAuthenticated() bool {
|
||||
@@ -249,7 +249,7 @@ func (c *UserContext) OAuthName() string {
|
||||
|
||||
func (c *UserContext) TailscaleNodeName() string {
|
||||
if c.Tailscale != nil {
|
||||
return c.Tailscale.Username
|
||||
return c.Tailscale.NodeName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@@ -75,38 +72,3 @@ func githubExtractor(client *http.Client, _ string) (*model.Claims, error) {
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func simpleReq[T any](client *http.Client, url string, headers map[string]string) (*T, error) {
|
||||
var decodedRes T
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for key, value := range headers {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||
return nil, fmt.Errorf("request failed with status: %s", res.Status)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &decodedRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &decodedRes, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func simpleReq[T any](client *http.Client, url string, headers map[string]string) (*T, error) {
|
||||
var decodedRes T
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for key, value := range headers {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||
return nil, fmt.Errorf("request failed with status: %s", res.Status)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &decodedRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &decodedRes, nil
|
||||
}
|
||||
@@ -2,197 +2,232 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
"go.uber.org/dig"
|
||||
"tailscale.com/client/local"
|
||||
"tailscale.com/tsnet"
|
||||
)
|
||||
|
||||
const tailscaleAPIBaseURL = "https://api.tailscale.com/api/v2"
|
||||
|
||||
var (
|
||||
tailscaleAPIDeviceList = func(tailnet string) string {
|
||||
return tailscaleAPIBaseURL + "/tailnet/" + tailnet + "/devices"
|
||||
}
|
||||
tailscaleAPIUserList = func(tailnet string) string {
|
||||
return tailscaleAPIBaseURL + "/tailnet/" + tailnet + "/users"
|
||||
}
|
||||
)
|
||||
|
||||
type tailscaleDevice struct {
|
||||
Addresses []string `json:"addresses"`
|
||||
User string `json:"user"`
|
||||
Name string `json:"name"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
type tailscaleAPIDevices struct {
|
||||
Devices []tailscaleDevice `json:"devices"`
|
||||
}
|
||||
|
||||
type tailscaleUser struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
LoginName string `json:"loginName"`
|
||||
}
|
||||
|
||||
type tailscaleAPIUsers struct {
|
||||
Users []tailscaleUser `json:"users"`
|
||||
}
|
||||
|
||||
type TailscaleWhoisResponse struct {
|
||||
UserID string
|
||||
LoginName string
|
||||
DisplayName string
|
||||
LoginName string
|
||||
NodeName string
|
||||
}
|
||||
|
||||
type TailscaleService struct {
|
||||
log *logger.Logger
|
||||
config *model.Config
|
||||
ctx context.Context
|
||||
log *logger.Logger
|
||||
client *http.Client
|
||||
|
||||
srv *tsnet.Server
|
||||
lc *local.Client
|
||||
ln *net.Listener
|
||||
mu sync.Mutex
|
||||
caches struct {
|
||||
devices *CacheStore[tailscaleAPIDevices]
|
||||
users *CacheStore[tailscaleAPIUsers]
|
||||
}
|
||||
|
||||
urls struct {
|
||||
devices string
|
||||
users string
|
||||
}
|
||||
}
|
||||
|
||||
type TailscaleServiceInput struct {
|
||||
dig.In
|
||||
|
||||
Log *logger.Logger
|
||||
Config *model.Config
|
||||
Ctx context.Context
|
||||
Ding *ding.Ding
|
||||
Config *model.Config
|
||||
Log *logger.Logger
|
||||
}
|
||||
|
||||
func NewTailscaleService(i TailscaleServiceInput) (*TailscaleService, error) {
|
||||
if !i.Config.Experimental.Tailscale.Enabled {
|
||||
if !i.Config.Tailscale.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
srv := new(tsnet.Server)
|
||||
|
||||
// node options
|
||||
srv.Dir = i.Config.Experimental.Tailscale.Dir
|
||||
srv.Hostname = i.Config.Experimental.Tailscale.Hostname
|
||||
srv.AuthKey = i.Config.Experimental.Tailscale.AuthKey
|
||||
srv.Ephemeral = i.Config.Experimental.Tailscale.Ephemeral
|
||||
|
||||
// redirect logs to zerolog
|
||||
srv.Logf = i.Log.App.Printf
|
||||
srv.UserLogf = i.Log.App.Printf
|
||||
|
||||
err := srv.Start()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start tailscale server: %w", err)
|
||||
if i.Config.Tailscale.APIToken == "" {
|
||||
return nil, fmt.Errorf("tailscale api token not set")
|
||||
}
|
||||
|
||||
lc, err := srv.LocalClient()
|
||||
|
||||
if err != nil {
|
||||
_ = srv.Close()
|
||||
return nil, fmt.Errorf("failed to get tailscale local client: %w", err)
|
||||
if i.Config.Tailscale.Tailnet == "" {
|
||||
return nil, fmt.Errorf("tailscale tailnet not set")
|
||||
}
|
||||
|
||||
service := &TailscaleService{
|
||||
log: i.Log,
|
||||
s := &TailscaleService{
|
||||
config: i.Config,
|
||||
ctx: i.Ctx,
|
||||
srv: srv,
|
||||
lc: lc,
|
||||
log: i.Log,
|
||||
}
|
||||
|
||||
connectCtx, cancel := context.WithTimeout(i.Ctx, 2*time.Minute) // large enough timeout to allow for user to manually authenticate with link if needed
|
||||
defer cancel()
|
||||
devicesCache := NewCacheStore[tailscaleAPIDevices](0)
|
||||
usersCache := NewCacheStore[tailscaleAPIUsers](0)
|
||||
|
||||
err = service.waitForConn(connectCtx)
|
||||
s.caches.devices = devicesCache
|
||||
s.caches.users = usersCache
|
||||
|
||||
if err != nil {
|
||||
_ = srv.Close()
|
||||
return nil, fmt.Errorf("failed to connect to tailscale network: %w", err)
|
||||
}
|
||||
i.Ding.Go(func(ctx context.Context) {
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
i.Ding.Go(service.watchAndClose, ding.RingMajor)
|
||||
|
||||
if i.Config.Experimental.Tailscale.Funnel && !i.Config.Experimental.Tailscale.Listen {
|
||||
service.log.App.Warn().Msg("Tailscale Funnel is enabled but listen is disabled. Funnel will not work without listen enabled.")
|
||||
}
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (ts *TailscaleService) watchAndClose(ctx context.Context) {
|
||||
<-ctx.Done()
|
||||
ts.log.App.Debug().Msg("Shutting down Tailscale service")
|
||||
ts.mu.Lock()
|
||||
srv := ts.srv
|
||||
ln := ts.ln
|
||||
ts.ln = nil
|
||||
ts.srv = nil
|
||||
ts.mu.Unlock()
|
||||
if ln != nil {
|
||||
(*ln).Close()
|
||||
}
|
||||
if srv != nil {
|
||||
srv.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *TailscaleService) Whois(ctx context.Context, addr string) (*TailscaleWhoisResponse, error) {
|
||||
who, err := ts.lc.WhoIs(ctx, addr)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, local.ErrPeerNotFound) {
|
||||
return nil, nil
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
s.caches.devices.Sweep()
|
||||
s.caches.users.Sweep()
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get client whois: %w", err)
|
||||
}, ding.RingMinor)
|
||||
|
||||
s.urls.devices = tailscaleAPIDeviceList(i.Config.Tailscale.Tailnet)
|
||||
s.urls.users = tailscaleAPIUserList(i.Config.Tailscale.Tailnet)
|
||||
s.client = &http.Client{}
|
||||
|
||||
_, _, err := s.getDeviceList()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get device list: %w", err)
|
||||
}
|
||||
|
||||
if who.Node.IsTagged() {
|
||||
ts.log.App.Debug().Msgf("Skipping whois for tagged node %s", who.Node.Name)
|
||||
_, _, err = s.getUsersList()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get user list: %w", err)
|
||||
}
|
||||
|
||||
s.log.App.Info().Msg("Tailscale service initialized")
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *TailscaleService) buildAuthorizationHeader() string {
|
||||
return "Bearer " + s.config.Tailscale.APIToken
|
||||
}
|
||||
|
||||
func (s *TailscaleService) getDeviceList() (*tailscaleAPIDevices, bool, error) {
|
||||
cached, ok := s.caches.devices.Get("devices")
|
||||
|
||||
if ok {
|
||||
return &cached, true, nil
|
||||
}
|
||||
|
||||
devices, err := simpleReq[tailscaleAPIDevices](s.client, s.urls.devices, map[string]string{
|
||||
"Authorization": s.buildAuthorizationHeader(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("failed to get device list: %w", err)
|
||||
}
|
||||
|
||||
s.caches.devices.Set("devices", *devices, time.Duration(s.config.Tailscale.CacheDuration)*time.Second)
|
||||
|
||||
return devices, false, nil
|
||||
}
|
||||
|
||||
func (s *TailscaleService) getUsersList() (*tailscaleAPIUsers, bool, error) {
|
||||
cached, ok := s.caches.users.Get("users")
|
||||
|
||||
if ok {
|
||||
return &cached, true, nil
|
||||
}
|
||||
|
||||
users, err := simpleReq[tailscaleAPIUsers](s.client, s.urls.users, map[string]string{
|
||||
"Authorization": s.buildAuthorizationHeader(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("failed to get user list: %w", err)
|
||||
}
|
||||
|
||||
s.caches.users.Set("users", *users, time.Duration(s.config.Tailscale.CacheDuration)*time.Second)
|
||||
|
||||
return users, false, nil
|
||||
}
|
||||
|
||||
func (s *TailscaleService) Whois(addr string) (*TailscaleWhoisResponse, error) {
|
||||
var device *tailscaleDevice
|
||||
|
||||
devices, dCacheHit, err := s.getDeviceList()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get device list: %w", err)
|
||||
}
|
||||
|
||||
for _, d := range devices.Devices {
|
||||
if len(d.Tags) != 0 {
|
||||
continue
|
||||
}
|
||||
for _, a := range d.Addresses {
|
||||
if a == addr {
|
||||
device = &d
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if device == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
uid := strings.TrimPrefix(who.UserProfile.ID.String(), "userid:")
|
||||
s.log.App.Debug().Str("device", device.Name).Bool("cache_hit", dCacheHit).Msg("Tailscale device found")
|
||||
|
||||
res := TailscaleWhoisResponse{
|
||||
UserID: uid,
|
||||
LoginName: who.UserProfile.LoginName,
|
||||
DisplayName: who.UserProfile.DisplayName,
|
||||
NodeName: strings.TrimSuffix(who.Node.Name, "."),
|
||||
}
|
||||
var user *tailscaleUser
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (ts *TailscaleService) CreateListener() (net.Listener, error) {
|
||||
ts.mu.Lock()
|
||||
defer ts.mu.Unlock()
|
||||
|
||||
if ts.ln != nil {
|
||||
return *ts.ln, nil
|
||||
}
|
||||
|
||||
if ts.config.Experimental.Tailscale.Funnel {
|
||||
ln, err := ts.srv.ListenFunnel("tcp", ":443")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts.ln = &ln
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
ln, err := ts.srv.ListenTLS("tcp", ":443")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts.ln = &ln
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func (ts *TailscaleService) GetHostname() string {
|
||||
status, err := ts.lc.Status(ts.ctx)
|
||||
users, uCacheHit, err := s.getUsersList()
|
||||
|
||||
if err != nil {
|
||||
ts.log.App.Error().Err(err).Msg("Failed to get Tailscale status")
|
||||
return ""
|
||||
return nil, fmt.Errorf("failed to get user list: %w", err)
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(status.Self.DNSName, ".")
|
||||
}
|
||||
|
||||
func (ts *TailscaleService) waitForConn(ctx context.Context) error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("timed out waiting for tailscale connection")
|
||||
default:
|
||||
ip4, _ := ts.srv.TailscaleIPs()
|
||||
if !ip4.IsValid() {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
for _, u := range users.Users {
|
||||
if u.LoginName == device.User {
|
||||
user = &u
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
s.log.App.Debug().Str("user", user.LoginName).Bool("cache_hit", uCacheHit).Msg("Tailscale user found")
|
||||
|
||||
return &TailscaleWhoisResponse{
|
||||
LoginName: user.LoginName,
|
||||
DisplayName: user.DisplayName,
|
||||
NodeName: device.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user