2023-06-06 10:23:39 +02:00
package types
2022-06-03 09:05:41 +02:00
import (
2022-06-03 09:26:36 +02:00
"errors"
"fmt"
2022-06-03 09:05:41 +02:00
"io/fs"
2022-09-04 11:32:29 +02:00
"net/netip"
2022-06-03 09:05:41 +02:00
"net/url"
2023-01-10 13:46:42 +02:00
"os"
2022-06-03 09:26:36 +02:00
"strings"
2022-06-03 09:05:41 +02:00
"time"
2022-06-03 09:26:36 +02:00
"github.com/coreos/go-oidc/v3/oidc"
2024-07-22 08:56:00 +02:00
"github.com/juanfont/headscale/hscontrol/util"
2023-01-31 12:40:38 +01:00
"github.com/prometheus/common/model"
2022-06-03 10:37:45 +02:00
"github.com/rs/zerolog"
2022-06-03 09:26:36 +02:00
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
2022-09-02 00:04:04 +02:00
"go4.org/netipx"
2023-05-06 09:30:15 +00:00
"tailscale.com/net/tsaddr"
2022-06-03 09:05:41 +02:00
"tailscale.com/tailcfg"
2022-06-03 09:26:36 +02:00
"tailscale.com/types/dnstype"
2024-08-19 11:41:05 +02:00
"tailscale.com/util/set"
2022-06-03 09:05:41 +02:00
)
2022-07-11 20:33:24 +02:00
const (
2023-01-31 12:40:38 +01:00
defaultOIDCExpiryTime = 180 * 24 * time . Hour // 180 Days
maxDuration time . Duration = 1 << 63 - 1
2022-07-11 20:33:24 +02:00
)
2023-01-31 12:40:38 +01:00
var errOidcMutuallyExclusive = errors . New (
"oidc_client_secret and oidc_client_secret_path are mutually exclusive" ,
)
2023-01-10 13:46:42 +02:00
2024-04-17 07:03:06 +02:00
type IPAllocationStrategy string
const (
IPAllocationStrategySequential IPAllocationStrategy = "sequential"
IPAllocationStrategyRandom IPAllocationStrategy = "random"
)
2024-07-18 11:08:25 +05:30
type PolicyMode string
const (
PolicyModeDB = "database"
PolicyModeFile = "file"
)
2022-06-03 09:05:41 +02:00
// Config contains the initial Headscale configuration.
type Config struct {
ServerURL string
Addr string
MetricsAddr string
GRPCAddr string
GRPCAllowInsecure bool
EphemeralNodeInactivityTimeout time . Duration
2024-02-18 19:31:29 +01:00
PrefixV4 * netip . Prefix
PrefixV6 * netip . Prefix
2024-04-17 07:03:06 +02:00
IPAllocation IPAllocationStrategy
2022-08-13 11:14:38 +02:00
NoisePrivateKeyPath string
2022-06-03 09:05:41 +02:00
BaseDomain string
2022-09-11 21:37:23 +02:00
Log LogConfig
2022-06-03 10:37:45 +02:00
DisableUpdateCheck bool
2022-06-03 09:05:41 +02:00
2024-02-09 07:27:00 +01:00
Database DatabaseConfig
2022-06-03 09:05:41 +02:00
2024-02-09 07:27:00 +01:00
DERP DERPConfig
2022-06-03 09:05:41 +02:00
2022-06-03 10:14:14 +02:00
TLS TLSConfig
2022-06-03 09:05:41 +02:00
ACMEURL string
ACMEEmail string
2024-06-26 13:44:40 +02:00
DNSConfig * tailcfg . DNSConfig
DNSUserNameInMagicDNS bool
2022-06-03 09:05:41 +02:00
UnixSocket string
UnixSocketPermission fs . FileMode
OIDC OIDCConfig
2022-06-09 21:20:11 +02:00
LogTail LogTailConfig
RandomizeClientPort bool
2022-06-03 09:05:41 +02:00
CLI CLIConfig
2024-07-18 11:08:25 +05:30
Policy PolicyConfig
2024-02-23 10:59:24 +01:00
Tuning Tuning
2022-06-03 09:05:41 +02:00
}
2024-08-19 11:41:05 +02:00
type DNSConfig struct {
MagicDNS bool ` mapstructure:"magic_dns" `
BaseDomain string ` mapstructure:"base_domain" `
Nameservers Nameservers
SearchDomains [ ] string ` mapstructure:"search_domains" `
ExtraRecords [ ] tailcfg . DNSRecord ` mapstructure:"extra_records" `
UserNameInMagicDNS bool ` mapstructure:"use_username_in_magic_dns" `
}
type Nameservers struct {
Global [ ] string
Split map [ string ] [ ] string
}
2024-02-09 07:27:00 +01:00
type SqliteConfig struct {
2024-06-23 22:06:59 +02:00
Path string
WriteAheadLog bool
2024-02-09 07:27:00 +01:00
}
type PostgresConfig struct {
2024-02-09 22:04:28 +05:30
Host string
Port int
Name string
User string
Pass string
Ssl string
MaxOpenConnections int
MaxIdleConnections int
ConnMaxIdleTimeSecs int
2024-02-09 07:27:00 +01:00
}
2024-08-19 18:47:52 +09:00
type GormConfig struct {
Debug bool
SlowThreshold time . Duration
SkipErrRecordNotFound bool
ParameterizedQueries bool
PrepareStmt bool
}
2024-02-09 07:27:00 +01:00
type DatabaseConfig struct {
// Type sets the database type, either "sqlite3" or "postgres"
Type string
Debug bool
2024-08-19 18:47:52 +09:00
// Type sets the gorm configuration
Gorm GormConfig
2024-02-09 07:27:00 +01:00
Sqlite SqliteConfig
Postgres PostgresConfig
}
2022-06-03 10:14:14 +02:00
type TLSConfig struct {
2022-11-19 10:33:15 +00:00
CertPath string
KeyPath string
2022-06-03 10:14:14 +02:00
LetsEncrypt LetsEncryptConfig
}
type LetsEncryptConfig struct {
Listen string
Hostname string
CacheDir string
ChallengeType string
}
2022-06-03 09:05:41 +02:00
type OIDCConfig struct {
2022-09-26 09:57:28 +02:00
OnlyStartIfOIDCIsAvailable bool
Issuer string
ClientID string
ClientSecret string
Scope [ ] string
ExtraParams map [ string ] string
AllowedDomains [ ] string
AllowedUsers [ ] string
2022-12-07 00:08:01 +00:00
AllowedGroups [ ] string
2022-09-26 09:57:28 +02:00
StripEmaildomain bool
2023-01-31 12:40:38 +01:00
Expiry time . Duration
UseExpiryFromToken bool
2022-06-03 09:05:41 +02:00
}
type DERPConfig struct {
2024-01-16 16:04:03 +01:00
ServerEnabled bool
AutomaticallyAddEmbeddedDerpRegion bool
ServerRegionID int
ServerRegionCode string
ServerRegionName string
ServerPrivateKeyPath string
STUNAddr string
URLs [ ] url . URL
Paths [ ] string
AutoUpdate bool
UpdateFrequency time . Duration
IPv4 string
IPv6 string
2022-06-03 09:05:41 +02:00
}
type LogTailConfig struct {
Enabled bool
}
type CLIConfig struct {
Address string
APIKey string
Timeout time . Duration
Insecure bool
}
2024-07-18 11:08:25 +05:30
type PolicyConfig struct {
Path string
Mode PolicyMode
2022-06-03 09:05:41 +02:00
}
2022-06-03 09:26:36 +02:00
2022-09-11 21:37:23 +02:00
type LogConfig struct {
Format string
Level zerolog . Level
}
2024-02-23 10:59:24 +01:00
type Tuning struct {
2024-05-24 09:15:34 +01:00
NotifierSendTimeout time . Duration
2024-02-23 10:59:24 +01:00
BatchChangeDelay time . Duration
NodeMapSessionBufferedChanSize int
}
2024-09-07 09:23:58 +02:00
// LoadConfig prepares and loads the Headscale configuration into Viper.
// This means it sets the default values, reads the configuration file and
// environment variables, and handles deprecated configuration options.
// It has to be called before LoadServerConfig and LoadCLIConfig.
// The configuration is not validated and the caller should check for errors
// using a validation function.
2022-06-07 22:24:35 +08:00
func LoadConfig ( path string , isFile bool ) error {
if isFile {
viper . SetConfigFile ( path )
2022-06-03 09:26:36 +02:00
} else {
2022-06-07 22:24:35 +08:00
viper . SetConfigName ( "config" )
if path == "" {
viper . AddConfigPath ( "/etc/headscale/" )
viper . AddConfigPath ( "$HOME/.headscale" )
viper . AddConfigPath ( "." )
} else {
// For testing
viper . AddConfigPath ( path )
}
2022-06-03 09:26:36 +02:00
}
2024-08-19 11:41:05 +02:00
envPrefix := "headscale"
viper . SetEnvPrefix ( envPrefix )
2022-06-03 09:26:36 +02:00
viper . SetEnvKeyReplacer ( strings . NewReplacer ( "." , "_" ) )
viper . AutomaticEnv ( )
2024-07-18 11:08:25 +05:30
viper . SetDefault ( "policy.mode" , "file" )
2022-06-03 09:26:36 +02:00
viper . SetDefault ( "tls_letsencrypt_cache_dir" , "/var/www/.cache" )
2023-06-06 11:12:36 +02:00
viper . SetDefault ( "tls_letsencrypt_challenge_type" , HTTP01ChallengeType )
2022-06-03 09:26:36 +02:00
2022-09-11 21:37:23 +02:00
viper . SetDefault ( "log.level" , "info" )
viper . SetDefault ( "log.format" , TextLogFormat )
2022-06-03 09:26:36 +02:00
2024-08-19 11:41:05 +02:00
viper . SetDefault ( "dns.magic_dns" , true )
viper . SetDefault ( "dns.base_domain" , "" )
viper . SetDefault ( "dns.nameservers.global" , [ ] string { } )
viper . SetDefault ( "dns.nameservers.split" , map [ string ] string { } )
viper . SetDefault ( "dns.search_domains" , [ ] string { } )
viper . SetDefault ( "dns.extra_records" , [ ] tailcfg . DNSRecord { } )
2022-06-03 09:26:36 +02:00
viper . SetDefault ( "derp.server.enabled" , false )
viper . SetDefault ( "derp.server.stun.enabled" , true )
2024-01-16 16:04:03 +01:00
viper . SetDefault ( "derp.server.automatically_add_embedded_derp_region" , true )
2022-06-03 09:26:36 +02:00
2023-05-10 14:32:15 +00:00
viper . SetDefault ( "unix_socket" , "/var/run/headscale/headscale.sock" )
2022-06-03 09:26:36 +02:00
viper . SetDefault ( "unix_socket_permission" , "0o770" )
viper . SetDefault ( "grpc_listen_addr" , ":50443" )
viper . SetDefault ( "grpc_allow_insecure" , false )
viper . SetDefault ( "cli.timeout" , "5s" )
viper . SetDefault ( "cli.insecure" , false )
2024-02-09 07:27:00 +01:00
viper . SetDefault ( "database.postgres.ssl" , false )
2024-02-09 22:04:28 +05:30
viper . SetDefault ( "database.postgres.max_open_conns" , 10 )
viper . SetDefault ( "database.postgres.max_idle_conns" , 10 )
viper . SetDefault ( "database.postgres.conn_max_idle_time_secs" , 3600 )
2022-12-07 09:37:45 +01:00
2024-06-23 22:06:59 +02:00
viper . SetDefault ( "database.sqlite.write_ahead_log" , true )
2022-06-03 09:26:36 +02:00
viper . SetDefault ( "oidc.scope" , [ ] string { oidc . ScopeOpenID , "profile" , "email" } )
viper . SetDefault ( "oidc.strip_email_domain" , true )
2022-09-26 09:57:28 +02:00
viper . SetDefault ( "oidc.only_start_if_oidc_is_available" , true )
2023-01-31 12:40:38 +01:00
viper . SetDefault ( "oidc.expiry" , "180d" )
viper . SetDefault ( "oidc.use_expiry_from_token" , false )
2022-06-03 09:26:36 +02:00
viper . SetDefault ( "logtail.enabled" , false )
2022-06-09 21:20:11 +02:00
viper . SetDefault ( "randomize_client_port" , false )
2022-06-03 09:26:36 +02:00
2022-06-12 13:12:53 +00:00
viper . SetDefault ( "ephemeral_node_inactivity_timeout" , "120s" )
2024-05-24 09:15:34 +01:00
viper . SetDefault ( "tuning.notifier_send_timeout" , "800ms" )
2024-02-23 10:59:24 +01:00
viper . SetDefault ( "tuning.batch_change_delay" , "800ms" )
viper . SetDefault ( "tuning.node_mapsession_buffered_chan_size" , 30 )
2024-04-17 11:09:22 +02:00
viper . SetDefault ( "prefixes.allocation" , string ( IPAllocationStrategySequential ) )
2024-04-17 07:03:06 +02:00
2022-06-03 09:26:36 +02:00
if err := viper . ReadInConfig ( ) ; err != nil {
return fmt . Errorf ( "fatal error reading config file: %w" , err )
}
2024-09-07 09:23:58 +02:00
return nil
}
func validateServerConfig ( ) error {
2024-08-19 11:41:05 +02:00
depr := deprecator {
warns : make ( set . Set [ string ] ) ,
fatals : make ( set . Set [ string ] ) ,
}
2024-07-18 11:08:25 +05:30
// Register aliases for backward compatibility
// Has to be called _after_ viper.ReadInConfig()
// https://github.com/spf13/viper/issues/560
// Alias the old ACL Policy path with the new configuration option.
2024-08-19 13:03:01 +02:00
depr . fatalIfNewKeyIsNotUsed ( "policy.path" , "acl_policy_path" )
2024-08-19 11:41:05 +02:00
// Move dns_config -> dns
depr . warn ( "dns_config.override_local_dns" )
depr . fatalIfNewKeyIsNotUsed ( "dns.magic_dns" , "dns_config.magic_dns" )
depr . fatalIfNewKeyIsNotUsed ( "dns.base_domain" , "dns_config.base_domain" )
depr . fatalIfNewKeyIsNotUsed ( "dns.nameservers.global" , "dns_config.nameservers" )
depr . fatalIfNewKeyIsNotUsed ( "dns.nameservers.split" , "dns_config.restricted_nameservers" )
depr . fatalIfNewKeyIsNotUsed ( "dns.search_domains" , "dns_config.domains" )
depr . fatalIfNewKeyIsNotUsed ( "dns.extra_records" , "dns_config.extra_records" )
depr . warn ( "dns_config.use_username_in_magic_dns" )
depr . warn ( "dns.use_username_in_magic_dns" )
depr . Log ( )
2024-07-18 11:08:25 +05:30
2022-06-03 09:26:36 +02:00
// Collect any validation errors and return them all at once
var errorText string
if ( viper . GetString ( "tls_letsencrypt_hostname" ) != "" ) &&
( ( viper . GetString ( "tls_cert_path" ) != "" ) || ( viper . GetString ( "tls_key_path" ) != "" ) ) {
errorText += "Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both\n"
}
2022-08-21 10:42:23 +02:00
if ! viper . IsSet ( "noise" ) || viper . GetString ( "noise.private_key_path" ) == "" {
errorText += "Fatal config error: headscale now requires a new `noise.private_key_path` field in the config file for the Tailscale v2 protocol\n"
2022-08-14 12:35:14 +02:00
}
2022-06-03 09:26:36 +02:00
if ( viper . GetString ( "tls_letsencrypt_hostname" ) != "" ) &&
2023-06-06 11:12:36 +02:00
( viper . GetString ( "tls_letsencrypt_challenge_type" ) == TLSALPN01ChallengeType ) &&
2022-06-03 09:26:36 +02:00
( ! strings . HasSuffix ( viper . GetString ( "listen_addr" ) , ":443" ) ) {
// this is only a warning because there could be something sitting in front of headscale that redirects the traffic (e.g. an iptables rule)
log . Warn ( ) .
Msg ( "Warning: when using tls_letsencrypt_hostname with TLS-ALPN-01 as challenge type, headscale must be reachable on port 443, i.e. listen_addr should probably end in :443" )
}
2023-06-06 11:12:36 +02:00
if ( viper . GetString ( "tls_letsencrypt_challenge_type" ) != HTTP01ChallengeType ) &&
( viper . GetString ( "tls_letsencrypt_challenge_type" ) != TLSALPN01ChallengeType ) {
2022-06-03 09:26:36 +02:00
errorText += "Fatal config error: the only supported values for tls_letsencrypt_challenge_type are HTTP-01 and TLS-ALPN-01\n"
}
if ! strings . HasPrefix ( viper . GetString ( "server_url" ) , "http://" ) &&
! strings . HasPrefix ( viper . GetString ( "server_url" ) , "https://" ) {
errorText += "Fatal config error: server_url must start with https:// or http://\n"
}
2022-06-12 13:12:43 +00:00
// Minimum inactivity time out is keepalive timeout (60s) plus a few seconds
// to avoid races
minInactivityTimeout , _ := time . ParseDuration ( "65s" )
if viper . GetDuration ( "ephemeral_node_inactivity_timeout" ) <= minInactivityTimeout {
errorText += fmt . Sprintf (
"Fatal config error: ephemeral_node_inactivity_timeout (%s) is set too low, must be more than %s" ,
viper . GetString ( "ephemeral_node_inactivity_timeout" ) ,
minInactivityTimeout ,
)
}
2022-06-03 09:26:36 +02:00
if errorText != "" {
2024-02-09 22:04:28 +05:30
// nolint
2022-06-03 09:26:36 +02:00
return errors . New ( strings . TrimSuffix ( errorText , "\n" ) )
}
2024-09-07 09:23:58 +02:00
return nil
2022-06-03 09:26:36 +02:00
}
2024-09-07 09:23:58 +02:00
func tlsConfig ( ) TLSConfig {
2022-06-03 10:14:14 +02:00
return TLSConfig {
LetsEncrypt : LetsEncryptConfig {
Hostname : viper . GetString ( "tls_letsencrypt_hostname" ) ,
Listen : viper . GetString ( "tls_letsencrypt_listen" ) ,
2023-05-11 09:09:18 +02:00
CacheDir : util . AbsolutePathFromConfigPath (
2022-06-03 10:14:14 +02:00
viper . GetString ( "tls_letsencrypt_cache_dir" ) ,
) ,
ChallengeType : viper . GetString ( "tls_letsencrypt_challenge_type" ) ,
} ,
2023-05-11 09:09:18 +02:00
CertPath : util . AbsolutePathFromConfigPath (
2022-06-03 10:14:14 +02:00
viper . GetString ( "tls_cert_path" ) ,
) ,
2023-05-11 09:09:18 +02:00
KeyPath : util . AbsolutePathFromConfigPath (
2022-06-03 10:14:14 +02:00
viper . GetString ( "tls_key_path" ) ,
) ,
}
}
2024-09-07 09:23:58 +02:00
func derpConfig ( ) DERPConfig {
2022-06-03 09:26:36 +02:00
serverEnabled := viper . GetBool ( "derp.server.enabled" )
serverRegionID := viper . GetInt ( "derp.server.region_id" )
serverRegionCode := viper . GetString ( "derp.server.region_code" )
serverRegionName := viper . GetString ( "derp.server.region_name" )
stunAddr := viper . GetString ( "derp.server.stun_listen_addr" )
2024-01-16 16:04:03 +01:00
privateKeyPath := util . AbsolutePathFromConfigPath (
viper . GetString ( "derp.server.private_key_path" ) ,
)
ipv4 := viper . GetString ( "derp.server.ipv4" )
ipv6 := viper . GetString ( "derp.server.ipv6" )
automaticallyAddEmbeddedDerpRegion := viper . GetBool (
"derp.server.automatically_add_embedded_derp_region" ,
)
2022-06-03 09:26:36 +02:00
if serverEnabled && stunAddr == "" {
log . Fatal ( ) .
Msg ( "derp.server.stun_listen_addr must be set if derp.server.enabled is true" )
}
urlStrs := viper . GetStringSlice ( "derp.urls" )
urls := make ( [ ] url . URL , len ( urlStrs ) )
for index , urlStr := range urlStrs {
urlAddr , err := url . Parse ( urlStr )
if err != nil {
log . Error ( ) .
Str ( "url" , urlStr ) .
Err ( err ) .
Msg ( "Failed to parse url, ignoring..." )
}
urls [ index ] = * urlAddr
}
paths := viper . GetStringSlice ( "derp.paths" )
2024-01-16 16:04:03 +01:00
if serverEnabled && ! automaticallyAddEmbeddedDerpRegion && len ( paths ) == 0 {
log . Fatal ( ) .
Msg ( "Disabling derp.server.automatically_add_embedded_derp_region requires to configure the derp server in derp.paths" )
}
2022-06-03 09:26:36 +02:00
autoUpdate := viper . GetBool ( "derp.auto_update_enabled" )
updateFrequency := viper . GetDuration ( "derp.update_frequency" )
return DERPConfig {
2024-01-16 16:04:03 +01:00
ServerEnabled : serverEnabled ,
ServerRegionID : serverRegionID ,
ServerRegionCode : serverRegionCode ,
ServerRegionName : serverRegionName ,
ServerPrivateKeyPath : privateKeyPath ,
STUNAddr : stunAddr ,
URLs : urls ,
Paths : paths ,
AutoUpdate : autoUpdate ,
UpdateFrequency : updateFrequency ,
IPv4 : ipv4 ,
IPv6 : ipv6 ,
AutomaticallyAddEmbeddedDerpRegion : automaticallyAddEmbeddedDerpRegion ,
2022-06-03 09:26:36 +02:00
}
}
2024-09-07 09:23:58 +02:00
func logtailConfig ( ) LogTailConfig {
2022-06-03 09:26:36 +02:00
enabled := viper . GetBool ( "logtail.enabled" )
return LogTailConfig {
Enabled : enabled ,
}
}
2024-09-07 09:23:58 +02:00
func policyConfig ( ) PolicyConfig {
2024-07-18 11:08:25 +05:30
policyPath := viper . GetString ( "policy.path" )
policyMode := viper . GetString ( "policy.mode" )
2022-06-03 09:26:36 +02:00
2024-07-18 11:08:25 +05:30
return PolicyConfig {
Path : policyPath ,
Mode : PolicyMode ( policyMode ) ,
2022-06-03 09:26:36 +02:00
}
}
2024-09-07 09:23:58 +02:00
func logConfig ( ) LogConfig {
2022-09-11 21:37:23 +02:00
logLevelStr := viper . GetString ( "log.level" )
logLevel , err := zerolog . ParseLevel ( logLevelStr )
if err != nil {
logLevel = zerolog . DebugLevel
}
logFormatOpt := viper . GetString ( "log.format" )
var logFormat string
switch logFormatOpt {
2024-09-07 09:23:58 +02:00
case JSONLogFormat :
2022-09-11 21:37:23 +02:00
logFormat = JSONLogFormat
2024-09-07 09:23:58 +02:00
case TextLogFormat :
2022-09-11 21:37:23 +02:00
logFormat = TextLogFormat
case "" :
logFormat = TextLogFormat
default :
log . Error ( ) .
Str ( "func" , "GetLogConfig" ) .
Msgf ( "Could not parse log format: %s. Valid choices are 'json' or 'text'" , logFormatOpt )
}
return LogConfig {
Format : logFormat ,
Level : logLevel ,
}
}
2024-09-07 09:23:58 +02:00
func databaseConfig ( ) DatabaseConfig {
2024-02-09 07:27:00 +01:00
debug := viper . GetBool ( "database.debug" )
type_ := viper . GetString ( "database.type" )
2024-08-19 18:47:52 +09:00
skipErrRecordNotFound := viper . GetBool ( "database.gorm.skip_err_record_not_found" )
slowThreshold := viper . GetDuration ( "database.gorm.slow_threshold" ) * time . Millisecond
parameterizedQueries := viper . GetBool ( "database.gorm.parameterized_queries" )
prepareStmt := viper . GetBool ( "database.gorm.prepare_stmt" )
2024-02-09 07:27:00 +01:00
switch type_ {
case DatabaseSqlite , DatabasePostgres :
break
case "sqlite" :
type_ = "sqlite3"
default :
2024-02-09 22:04:28 +05:30
log . Fatal ( ) .
Msgf ( "invalid database type %q, must be sqlite, sqlite3 or postgres" , type_ )
2024-02-09 07:27:00 +01:00
}
return DatabaseConfig {
Type : type_ ,
Debug : debug ,
2024-08-19 18:47:52 +09:00
Gorm : GormConfig {
Debug : debug ,
SkipErrRecordNotFound : skipErrRecordNotFound ,
SlowThreshold : slowThreshold ,
ParameterizedQueries : parameterizedQueries ,
PrepareStmt : prepareStmt ,
} ,
2024-02-09 07:27:00 +01:00
Sqlite : SqliteConfig {
2024-02-09 22:04:28 +05:30
Path : util . AbsolutePathFromConfigPath (
viper . GetString ( "database.sqlite.path" ) ,
) ,
2024-06-23 22:06:59 +02:00
WriteAheadLog : viper . GetBool ( "database.sqlite.write_ahead_log" ) ,
2024-02-09 07:27:00 +01:00
} ,
Postgres : PostgresConfig {
2024-02-09 22:04:28 +05:30
Host : viper . GetString ( "database.postgres.host" ) ,
Port : viper . GetInt ( "database.postgres.port" ) ,
Name : viper . GetString ( "database.postgres.name" ) ,
User : viper . GetString ( "database.postgres.user" ) ,
Pass : viper . GetString ( "database.postgres.pass" ) ,
Ssl : viper . GetString ( "database.postgres.ssl" ) ,
MaxOpenConnections : viper . GetInt ( "database.postgres.max_open_conns" ) ,
MaxIdleConnections : viper . GetInt ( "database.postgres.max_idle_conns" ) ,
ConnMaxIdleTimeSecs : viper . GetInt (
"database.postgres.conn_max_idle_time_secs" ,
) ,
2024-02-09 07:27:00 +01:00
} ,
}
}
2024-09-07 09:23:58 +02:00
func dns ( ) ( DNSConfig , error ) {
2024-08-19 11:41:05 +02:00
var dns DNSConfig
2022-06-03 09:26:36 +02:00
2024-08-19 11:41:05 +02:00
// TODO: Use this instead of manually getting settings when
// UnmarshalKey is compatible with Environment Variables.
// err := viper.UnmarshalKey("dns", &dns)
// if err != nil {
// return DNSConfig{}, fmt.Errorf("unmarshaling dns config: %w", err)
// }
2022-10-31 16:26:18 +01:00
2024-08-19 11:41:05 +02:00
dns . MagicDNS = viper . GetBool ( "dns.magic_dns" )
dns . BaseDomain = viper . GetString ( "dns.base_domain" )
dns . Nameservers . Global = viper . GetStringSlice ( "dns.nameservers.global" )
dns . Nameservers . Split = viper . GetStringMapStringSlice ( "dns.nameservers.split" )
dns . SearchDomains = viper . GetStringSlice ( "dns.search_domains" )
2022-06-03 09:26:36 +02:00
2024-08-19 11:41:05 +02:00
if viper . IsSet ( "dns.extra_records" ) {
var extraRecords [ ] tailcfg . DNSRecord
2022-11-07 21:10:06 +01:00
2024-08-19 11:41:05 +02:00
err := viper . UnmarshalKey ( "dns.extra_records" , & extraRecords )
if err != nil {
return DNSConfig { } , fmt . Errorf ( "unmarshaling dns extra records: %w" , err )
}
2022-11-07 21:10:06 +01:00
2024-08-19 11:41:05 +02:00
dns . ExtraRecords = extraRecords
}
2022-06-03 09:26:36 +02:00
2024-08-19 11:41:05 +02:00
dns . UserNameInMagicDNS = viper . GetBool ( "dns.use_username_in_magic_dns" )
2022-06-03 09:26:36 +02:00
2024-08-19 11:41:05 +02:00
return dns , nil
}
2022-06-03 09:26:36 +02:00
2024-09-07 09:23:58 +02:00
// globalResolvers returns the global DNS resolvers
2024-08-19 11:41:05 +02:00
// defined in the config file.
// If a nameserver is a valid IP, it will be used as a regular resolver.
// If a nameserver is a valid URL, it will be used as a DoH resolver.
// If a nameserver is neither a valid URL nor a valid IP, it will be ignored.
2024-09-07 09:23:58 +02:00
func ( d * DNSConfig ) globalResolvers ( ) [ ] * dnstype . Resolver {
2024-08-19 11:41:05 +02:00
var resolvers [ ] * dnstype . Resolver
for _ , nsStr := range d . Nameservers . Global {
warn := ""
if _ , err := netip . ParseAddr ( nsStr ) ; err == nil {
resolvers = append ( resolvers , & dnstype . Resolver {
Addr : nsStr ,
} )
continue
} else {
warn = fmt . Sprintf ( "Invalid global nameserver %q. Parsing error: %s ignoring" , nsStr , err )
}
2022-10-31 16:26:18 +01:00
2024-08-19 11:41:05 +02:00
if _ , err := url . Parse ( nsStr ) ; err == nil {
resolvers = append ( resolvers , & dnstype . Resolver {
Addr : nsStr ,
} )
2024-08-23 17:17:37 +02:00
continue
2024-08-19 11:41:05 +02:00
} else {
warn = fmt . Sprintf ( "Invalid global nameserver %q. Parsing error: %s ignoring" , nsStr , err )
2022-06-03 09:26:36 +02:00
}
2024-08-19 11:41:05 +02:00
if warn != "" {
log . Warn ( ) . Msg ( warn )
2022-06-03 09:26:36 +02:00
}
2024-08-19 11:41:05 +02:00
}
return resolvers
}
2022-06-03 09:26:36 +02:00
2024-09-07 09:23:58 +02:00
// splitResolvers returns a map of domain to DNS resolvers.
2024-08-19 11:41:05 +02:00
// If a nameserver is a valid IP, it will be used as a regular resolver.
// If a nameserver is a valid URL, it will be used as a DoH resolver.
// If a nameserver is neither a valid URL nor a valid IP, it will be ignored.
2024-09-07 09:23:58 +02:00
func ( d * DNSConfig ) splitResolvers ( ) map [ string ] [ ] * dnstype . Resolver {
2024-08-19 11:41:05 +02:00
routes := make ( map [ string ] [ ] * dnstype . Resolver )
for domain , nameservers := range d . Nameservers . Split {
var resolvers [ ] * dnstype . Resolver
for _ , nsStr := range nameservers {
warn := ""
if _ , err := netip . ParseAddr ( nsStr ) ; err == nil {
resolvers = append ( resolvers , & dnstype . Resolver {
Addr : nsStr ,
} )
2022-12-02 02:03:26 +01:00
2024-08-19 11:41:05 +02:00
continue
} else {
warn = fmt . Sprintf ( "Invalid split dns nameserver %q. Parsing error: %s ignoring" , nsStr , err )
2022-12-02 02:03:26 +01:00
}
2024-08-19 11:41:05 +02:00
if _ , err := url . Parse ( nsStr ) ; err == nil {
resolvers = append ( resolvers , & dnstype . Resolver {
Addr : nsStr ,
} )
2024-08-23 17:17:37 +02:00
continue
2024-08-19 11:41:05 +02:00
} else {
warn = fmt . Sprintf ( "Invalid split dns nameserver %q. Parsing error: %s ignoring" , nsStr , err )
}
2022-12-02 02:03:26 +01:00
2024-08-19 11:41:05 +02:00
if warn != "" {
log . Warn ( ) . Msg ( warn )
}
2022-10-31 14:59:50 +00:00
}
2024-08-19 11:41:05 +02:00
routes [ domain ] = resolvers
}
2022-10-31 14:59:50 +00:00
2024-08-19 11:41:05 +02:00
return routes
}
2022-06-03 09:26:36 +02:00
2024-09-07 09:23:58 +02:00
func dnsToTailcfgDNS ( dns DNSConfig ) * tailcfg . DNSConfig {
2024-08-19 11:41:05 +02:00
cfg := tailcfg . DNSConfig { }
2024-06-26 13:44:40 +02:00
2024-08-19 11:41:05 +02:00
if dns . BaseDomain == "" && dns . MagicDNS {
log . Fatal ( ) . Msg ( "dns.base_domain must be set when using MagicDNS (dns.magic_dns)" )
}
2023-08-31 18:37:18 +02:00
2024-08-19 11:41:05 +02:00
cfg . Proxied = dns . MagicDNS
cfg . ExtraRecords = dns . ExtraRecords
2024-09-07 09:23:58 +02:00
cfg . Resolvers = dns . globalResolvers ( )
2024-08-19 11:41:05 +02:00
2024-09-07 09:23:58 +02:00
routes := dns . splitResolvers ( )
2024-08-19 11:41:05 +02:00
cfg . Routes = routes
if dns . BaseDomain != "" {
cfg . Domains = [ ] string { dns . BaseDomain }
2022-06-03 09:26:36 +02:00
}
2024-08-19 11:41:05 +02:00
cfg . Domains = append ( cfg . Domains , dns . SearchDomains ... )
2022-06-03 09:26:36 +02:00
2024-08-19 11:41:05 +02:00
return & cfg
2022-06-03 09:26:36 +02:00
}
2024-09-07 09:23:58 +02:00
func prefixV4 ( ) ( * netip . Prefix , error ) {
2024-02-18 19:31:29 +01:00
prefixV4Str := viper . GetString ( "prefixes.v4" )
2024-04-17 07:03:06 +02:00
if prefixV4Str == "" {
return nil , nil
2024-02-18 19:31:29 +01:00
}
2024-04-17 07:03:06 +02:00
prefixV4 , err := netip . ParsePrefix ( prefixV4Str )
2024-02-18 19:31:29 +01:00
if err != nil {
2024-04-17 07:03:06 +02:00
return nil , fmt . Errorf ( "parsing IPv4 prefix from config: %w" , err )
2024-02-18 19:31:29 +01:00
}
builder := netipx . IPSetBuilder { }
builder . AddPrefix ( tsaddr . CGNATRange ( ) )
ipSet , _ := builder . IPSet ( )
if ! ipSet . ContainsPrefix ( prefixV4 ) {
log . Warn ( ) .
Msgf ( "Prefix %s is not in the %s range. This is an unsupported configuration." ,
prefixV4Str , tsaddr . CGNATRange ( ) )
}
2024-04-17 07:03:06 +02:00
return & prefixV4 , nil
}
2024-09-07 09:23:58 +02:00
func prefixV6 ( ) ( * netip . Prefix , error ) {
2024-04-17 07:03:06 +02:00
prefixV6Str := viper . GetString ( "prefixes.v6" )
if prefixV6Str == "" {
return nil , nil
}
prefixV6 , err := netip . ParsePrefix ( prefixV6Str )
if err != nil {
return nil , fmt . Errorf ( "parsing IPv6 prefix from config: %w" , err )
}
builder := netipx . IPSetBuilder { }
builder . AddPrefix ( tsaddr . TailscaleULARange ( ) )
ipSet , _ := builder . IPSet ( )
2024-02-18 19:31:29 +01:00
if ! ipSet . ContainsPrefix ( prefixV6 ) {
log . Warn ( ) .
Msgf ( "Prefix %s is not in the %s range. This is an unsupported configuration." ,
prefixV6Str , tsaddr . TailscaleULARange ( ) )
}
2024-04-17 07:03:06 +02:00
return & prefixV6 , nil
2024-02-18 19:31:29 +01:00
}
2024-09-07 09:23:58 +02:00
// LoadCLIConfig returns the needed configuration for the CLI client
// of Headscale to connect to a Headscale server.
func LoadCLIConfig ( ) ( * Config , error ) {
2024-09-11 10:43:22 +02:00
logConfig := logConfig ( )
zerolog . SetGlobalLevel ( logConfig . Level )
2024-09-07 09:23:58 +02:00
return & Config {
DisableUpdateCheck : viper . GetBool ( "disable_check_updates" ) ,
UnixSocket : viper . GetString ( "unix_socket" ) ,
CLI : CLIConfig {
Address : viper . GetString ( "cli.address" ) ,
APIKey : viper . GetString ( "cli.api_key" ) ,
Timeout : viper . GetDuration ( "cli.timeout" ) ,
Insecure : viper . GetBool ( "cli.insecure" ) ,
} ,
2024-09-11 10:43:22 +02:00
Log : logConfig ,
2024-09-07 09:23:58 +02:00
} , nil
}
// LoadServerConfig returns the full Headscale configuration to
// host a Headscale server. This is called as part of `headscale serve`.
func LoadServerConfig ( ) ( * Config , error ) {
if err := validateServerConfig ( ) ; err != nil {
return nil , err
2022-11-18 18:02:34 +01:00
}
2024-09-07 09:23:58 +02:00
logConfig := logConfig ( )
2024-05-16 02:40:30 +02:00
zerolog . SetGlobalLevel ( logConfig . Level )
2024-09-07 09:23:58 +02:00
prefix4 , err := prefixV4 ( )
2024-04-17 07:03:06 +02:00
if err != nil {
return nil , err
}
2024-09-07 09:23:58 +02:00
prefix6 , err := prefixV6 ( )
2024-02-18 19:31:29 +01:00
if err != nil {
return nil , err
}
2024-04-30 11:11:29 +02:00
if prefix4 == nil && prefix6 == nil {
return nil , fmt . Errorf ( "no IPv4 or IPv6 prefix configured, minimum one prefix is required" )
}
2024-04-17 07:03:06 +02:00
allocStr := viper . GetString ( "prefixes.allocation" )
var alloc IPAllocationStrategy
switch allocStr {
case string ( IPAllocationStrategySequential ) :
alloc = IPAllocationStrategySequential
case string ( IPAllocationStrategyRandom ) :
alloc = IPAllocationStrategyRandom
default :
2024-04-30 11:11:29 +02:00
return nil , fmt . Errorf ( "config error, prefixes.allocation is set to %s, which is not a valid strategy, allowed options: %s, %s" , allocStr , IPAllocationStrategySequential , IPAllocationStrategyRandom )
2024-04-17 07:03:06 +02:00
}
2024-09-07 09:23:58 +02:00
dnsConfig , err := dns ( )
2024-08-19 11:41:05 +02:00
if err != nil {
return nil , err
}
2024-09-07 09:23:58 +02:00
derpConfig := derpConfig ( )
logTailConfig := logtailConfig ( )
2022-06-09 21:20:11 +02:00
randomizeClientPort := viper . GetBool ( "randomize_client_port" )
2022-06-03 09:26:36 +02:00
2023-01-10 13:46:42 +02:00
oidcClientSecret := viper . GetString ( "oidc.client_secret" )
oidcClientSecretPath := viper . GetString ( "oidc.client_secret_path" )
if oidcClientSecretPath != "" && oidcClientSecret != "" {
return nil , errOidcMutuallyExclusive
}
if oidcClientSecretPath != "" {
secretBytes , err := os . ReadFile ( os . ExpandEnv ( oidcClientSecretPath ) )
if err != nil {
return nil , err
}
2024-01-25 09:53:34 +01:00
oidcClientSecret = strings . TrimSpace ( string ( secretBytes ) )
2023-01-10 13:46:42 +02:00
}
2024-08-19 11:41:05 +02:00
serverURL := viper . GetString ( "server_url" )
// BaseDomain cannot be the same as the server URL.
// This is because Tailscale takes over the domain in BaseDomain,
// causing the headscale server and DERP to be unreachable.
// For Tailscale upstream, the following is true:
// - DERP run on their own domains
// - Control plane runs on login.tailscale.com/controlplane.tailscale.com
// - MagicDNS (BaseDomain) for users is on a *.ts.net domain per tailnet (e.g. tail-scale.ts.net)
//
// TODO(kradalby): remove dnsConfig.UserNameInMagicDNS check when removed.
if ! dnsConfig . UserNameInMagicDNS && dnsConfig . BaseDomain != "" && strings . Contains ( serverURL , dnsConfig . BaseDomain ) {
return nil , errors . New ( "server_url cannot contain the base_domain, this will cause the headscale server and embedded DERP to become unreachable from the Tailscale node." )
}
2022-06-05 17:47:12 +02:00
return & Config {
2024-08-19 11:41:05 +02:00
ServerURL : serverURL ,
2022-06-03 10:37:45 +02:00
Addr : viper . GetString ( "listen_addr" ) ,
MetricsAddr : viper . GetString ( "metrics_listen_addr" ) ,
GRPCAddr : viper . GetString ( "grpc_listen_addr" ) ,
GRPCAllowInsecure : viper . GetBool ( "grpc_allow_insecure" ) ,
2024-09-07 09:23:58 +02:00
DisableUpdateCheck : false ,
2022-06-03 09:26:36 +02:00
2024-04-17 07:03:06 +02:00
PrefixV4 : prefix4 ,
PrefixV6 : prefix6 ,
IPAllocation : IPAllocationStrategy ( alloc ) ,
2024-02-18 19:31:29 +01:00
2023-05-11 09:09:18 +02:00
NoisePrivateKeyPath : util . AbsolutePathFromConfigPath (
2022-08-21 10:42:23 +02:00
viper . GetString ( "noise.private_key_path" ) ,
2022-08-13 11:14:38 +02:00
) ,
2024-08-19 11:41:05 +02:00
BaseDomain : dnsConfig . BaseDomain ,
2022-06-03 09:26:36 +02:00
DERP : derpConfig ,
EphemeralNodeInactivityTimeout : viper . GetDuration (
"ephemeral_node_inactivity_timeout" ,
) ,
2024-09-07 09:23:58 +02:00
Database : databaseConfig ( ) ,
2022-06-03 09:26:36 +02:00
2024-09-07 09:23:58 +02:00
TLS : tlsConfig ( ) ,
2022-06-03 09:26:36 +02:00
2024-09-07 09:23:58 +02:00
DNSConfig : dnsToTailcfgDNS ( dnsConfig ) ,
2024-08-19 11:41:05 +02:00
DNSUserNameInMagicDNS : dnsConfig . UserNameInMagicDNS ,
2022-06-03 09:26:36 +02:00
ACMEEmail : viper . GetString ( "acme_email" ) ,
ACMEURL : viper . GetString ( "acme_url" ) ,
UnixSocket : viper . GetString ( "unix_socket" ) ,
2023-05-11 09:09:18 +02:00
UnixSocketPermission : util . GetFileMode ( "unix_socket_permission" ) ,
2022-06-03 09:26:36 +02:00
OIDC : OIDCConfig {
2022-09-26 09:57:28 +02:00
OnlyStartIfOIDCIsAvailable : viper . GetBool (
"oidc.only_start_if_oidc_is_available" ,
) ,
2022-06-03 09:26:36 +02:00
Issuer : viper . GetString ( "oidc.issuer" ) ,
ClientID : viper . GetString ( "oidc.client_id" ) ,
2023-01-10 13:46:42 +02:00
ClientSecret : oidcClientSecret ,
2022-06-03 09:26:36 +02:00
Scope : viper . GetStringSlice ( "oidc.scope" ) ,
ExtraParams : viper . GetStringMapString ( "oidc.extra_params" ) ,
AllowedDomains : viper . GetStringSlice ( "oidc.allowed_domains" ) ,
AllowedUsers : viper . GetStringSlice ( "oidc.allowed_users" ) ,
2022-12-07 00:08:01 +00:00
AllowedGroups : viper . GetStringSlice ( "oidc.allowed_groups" ) ,
2022-06-03 09:26:36 +02:00
StripEmaildomain : viper . GetBool ( "oidc.strip_email_domain" ) ,
2023-01-31 12:40:38 +01:00
Expiry : func ( ) time . Duration {
// if set to 0, we assume no expiry
if value := viper . GetString ( "oidc.expiry" ) ; value == "0" {
return maxDuration
} else {
expiry , err := model . ParseDuration ( value )
if err != nil {
log . Warn ( ) . Msg ( "failed to parse oidc.expiry, defaulting back to 180 days" )
return defaultOIDCExpiryTime
}
return time . Duration ( expiry )
}
} ( ) ,
UseExpiryFromToken : viper . GetBool ( "oidc.use_expiry_from_token" ) ,
2022-06-03 09:26:36 +02:00
} ,
2024-05-16 02:40:30 +02:00
LogTail : logTailConfig ,
2022-06-09 21:20:11 +02:00
RandomizeClientPort : randomizeClientPort ,
2022-06-03 09:26:36 +02:00
2024-09-07 09:23:58 +02:00
Policy : policyConfig ( ) ,
2022-09-11 21:37:23 +02:00
2022-11-18 18:48:34 +01:00
CLI : CLIConfig {
Address : viper . GetString ( "cli.address" ) ,
APIKey : viper . GetString ( "cli.api_key" ) ,
Timeout : viper . GetDuration ( "cli.timeout" ) ,
Insecure : viper . GetBool ( "cli.insecure" ) ,
} ,
2024-05-16 02:40:30 +02:00
Log : logConfig ,
2024-02-23 10:59:24 +01:00
// TODO(kradalby): Document these settings when more stable
Tuning : Tuning {
2024-05-24 09:15:34 +01:00
NotifierSendTimeout : viper . GetDuration ( "tuning.notifier_send_timeout" ) ,
2024-02-23 10:59:24 +01:00
BatchChangeDelay : viper . GetDuration ( "tuning.batch_change_delay" ) ,
NodeMapSessionBufferedChanSize : viper . GetInt ( "tuning.node_mapsession_buffered_chan_size" ) ,
} ,
2022-06-05 17:47:12 +02:00
} , nil
2022-06-03 09:26:36 +02:00
}
2022-11-18 18:02:34 +01:00
2024-08-19 11:41:05 +02:00
type deprecator struct {
warns set . Set [ string ]
fatals set . Set [ string ]
}
// warnWithAlias will register an alias between the newKey and the oldKey,
2024-07-18 11:08:25 +05:30
// and log a deprecation warning if the oldKey is set.
2024-08-19 11:41:05 +02:00
func ( d * deprecator ) warnWithAlias ( newKey , oldKey string ) {
2024-07-18 11:08:25 +05:30
// NOTE: RegisterAlias is called with NEW KEY -> OLD KEY
viper . RegisterAlias ( newKey , oldKey )
if viper . IsSet ( oldKey ) {
2024-08-19 11:41:05 +02:00
d . warns . Add ( fmt . Sprintf ( "The %q configuration key is deprecated. Please use %q instead. %q will be removed in the future." , oldKey , newKey , oldKey ) )
2024-07-18 11:08:25 +05:30
}
}
2024-08-19 11:41:05 +02:00
// fatal deprecates and adds an entry to the fatal list of options if the oldKey is set.
func ( d * deprecator ) fatal ( newKey , oldKey string ) {
2024-07-18 11:08:25 +05:30
if viper . IsSet ( oldKey ) {
2024-08-19 11:41:05 +02:00
d . fatals . Add ( fmt . Sprintf ( "The %q configuration key is deprecated. Please use %q instead. %q has been removed." , oldKey , newKey , oldKey ) )
}
}
// fatalIfNewKeyIsNotUsed deprecates and adds an entry to the fatal list of options if the oldKey is set and the new key is _not_ set.
// If the new key is set, a warning is emitted instead.
func ( d * deprecator ) fatalIfNewKeyIsNotUsed ( newKey , oldKey string ) {
if viper . IsSet ( oldKey ) && ! viper . IsSet ( newKey ) {
d . fatals . Add ( fmt . Sprintf ( "The %q configuration key is deprecated. Please use %q instead. %q has been removed." , oldKey , newKey , oldKey ) )
} else if viper . IsSet ( oldKey ) {
d . warns . Add ( fmt . Sprintf ( "The %q configuration key is deprecated. Please use %q instead. %q has been removed." , oldKey , newKey , oldKey ) )
}
}
// warn deprecates and adds an option to log a warning if the oldKey is set.
func ( d * deprecator ) warnNoAlias ( newKey , oldKey string ) {
if viper . IsSet ( oldKey ) {
d . warns . Add ( fmt . Sprintf ( "The %q configuration key is deprecated. Please use %q instead. %q has been removed." , oldKey , newKey , oldKey ) )
}
}
// warn deprecates and adds an entry to the warn list of options if the oldKey is set.
func ( d * deprecator ) warn ( oldKey string ) {
if viper . IsSet ( oldKey ) {
d . warns . Add ( fmt . Sprintf ( "The %q configuration key is deprecated and has been removed. Please see the changelog for more details." , oldKey ) )
}
}
func ( d * deprecator ) String ( ) string {
var b strings . Builder
for _ , w := range d . warns . Slice ( ) {
fmt . Fprintf ( & b , "WARN: %s\n" , w )
}
for _ , f := range d . fatals . Slice ( ) {
fmt . Fprintf ( & b , "FATAL: %s\n" , f )
}
return b . String ( )
}
func ( d * deprecator ) Log ( ) {
if len ( d . fatals ) > 0 {
log . Fatal ( ) . Msg ( "\n" + d . String ( ) )
} else if len ( d . warns ) > 0 {
log . Warn ( ) . Msg ( "\n" + d . String ( ) )
2024-07-18 11:08:25 +05:30
}
}