diff --git a/.github/actions/headscale-up/action.yml b/.github/actions/headscale-up/action.yml index 42541b2d5..585e808c7 100644 --- a/.github/actions/headscale-up/action.yml +++ b/.github/actions/headscale-up/action.yml @@ -83,8 +83,9 @@ runs: nameservers: global: - 1.1.1.1 - tls_cert_path: /tmp/hs/tls.crt - tls_key_path: /tmp/hs/tls.key + tls: + cert_path: /tmp/hs/tls.crt + key_path: /tmp/hs/tls.key policy: mode: file path: /tmp/hs/policy.hujson diff --git a/cmd/headscale/headscale_test.go b/cmd/headscale/headscale_test.go index 45adf1a17..ce47cbe6e 100644 --- a/cmd/headscale/headscale_test.go +++ b/cmd/headscale/headscale_test.go @@ -38,9 +38,9 @@ func TestConfigFileLoading(t *testing.T) { assert.Equal(t, "127.0.0.1:9090", viper.GetString("metrics_listen_addr")) assert.Equal(t, "sqlite", viper.GetString("database.type")) assert.Equal(t, "/var/lib/headscale/db.sqlite", viper.GetString("database.sqlite.path")) - assert.Empty(t, viper.GetString("tls_letsencrypt_hostname")) - assert.Equal(t, ":http", viper.GetString("tls_letsencrypt_listen")) - assert.Equal(t, "HTTP-01", viper.GetString("tls_letsencrypt_challenge_type")) + assert.Empty(t, viper.GetString("tls.acme.hostname")) + assert.Equal(t, ":http", viper.GetString("tls.acme.http_listen")) + assert.Equal(t, "HTTP-01", viper.GetString("tls.acme.challenge_type")) mode, err := util.ParseFileMode(viper.GetString("unix_socket_permission")) require.NoError(t, err) assert.Equal(t, fs.FileMode(0o770), mode) @@ -70,9 +70,9 @@ func TestConfigLoading(t *testing.T) { assert.Equal(t, "127.0.0.1:9090", viper.GetString("metrics_listen_addr")) assert.Equal(t, "sqlite", viper.GetString("database.type")) assert.Equal(t, "/var/lib/headscale/db.sqlite", viper.GetString("database.sqlite.path")) - assert.Empty(t, viper.GetString("tls_letsencrypt_hostname")) - assert.Equal(t, ":http", viper.GetString("tls_letsencrypt_listen")) - assert.Equal(t, "HTTP-01", viper.GetString("tls_letsencrypt_challenge_type")) + assert.Empty(t, viper.GetString("tls.acme.hostname")) + assert.Equal(t, ":http", viper.GetString("tls.acme.http_listen")) + assert.Equal(t, "HTTP-01", viper.GetString("tls.acme.challenge_type")) mode, err := util.ParseFileMode(viper.GetString("unix_socket_permission")) require.NoError(t, err) assert.Equal(t, fs.FileMode(0o770), mode) diff --git a/config-example.yaml b/config-example.yaml index d955350df..bf9c8d4f8 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -232,35 +232,35 @@ database: ### TLS configuration # See: https://headscale.net/stable/ref/tls/ +tls: + ## Use already defined certificates: + cert_path: "" + key_path: "" -## Let's Encrypt / ACME -# Headscale supports automatically requesting and setting up -# TLS for a domain with Let's Encrypt. + ## Let's Encrypt / ACME + # Headscale supports automatically requesting and setting up + # TLS for a domain with Let's Encrypt. + acme: + # Domain name to request a TLS certificate for: + hostname: "" -# URL to ACME directory -acme_url: https://acme-v02.api.letsencrypt.org/directory + # Type of ACME challenge to use, currently supported types: + # HTTP-01 or TLS-ALPN-01 + challenge_type: HTTP-01 -# Email to register with ACME provider -acme_email: "" + # When HTTP-01 challenge is chosen, letsencrypt must set up a + # verification endpoint, and it will be listening on: + # :http = port 80 + http_listen: ":http" -# Domain name to request a TLS certificate for: -tls_letsencrypt_hostname: "" + # URL to ACME directory + url: https://acme-v02.api.letsencrypt.org/directory -# Path to store certificates and metadata needed by letsencrypt -tls_letsencrypt_cache_dir: /var/lib/headscale/cache + # Email to register with ACME provider + email: "" -# Type of ACME challenge to use, currently supported types: -# HTTP-01 or TLS-ALPN-01 -tls_letsencrypt_challenge_type: HTTP-01 - -# When HTTP-01 challenge is chosen, letsencrypt must set up a -# verification endpoint, and it will be listening on: -# :http = port 80 -tls_letsencrypt_listen: ":http" - -## Use already defined certificates: -tls_cert_path: "" -tls_key_path: "" + # Path to store certificates and metadata needed by letsencrypt + cache_dir: /var/lib/headscale/cache log: # Valid log levels: panic, fatal, error, warn, info, debug, trace diff --git a/hscontrol/app.go b/hscontrol/app.go index 2b431b7e2..45be6b543 100644 --- a/hscontrol/app.go +++ b/hscontrol/app.go @@ -1022,30 +1022,30 @@ type tlsBundle struct { } func (h *Headscale) getTLSSettings(ctx context.Context) (*tlsBundle, error) { - tlsEnabled := h.cfg.TLS.LetsEncrypt.Hostname != "" || h.cfg.TLS.CertPath != "" + tlsEnabled := h.cfg.TLS.ACME.Hostname != "" || h.cfg.TLS.CertPath != "" if tlsEnabled && !strings.HasPrefix(h.cfg.ServerURL, "https://") { log.Warn().Msg("listening with TLS but ServerURL does not start with https://") } else if !tlsEnabled && !strings.HasPrefix(h.cfg.ServerURL, "http://") { log.Warn().Msg("listening without TLS but ServerURL does not start with http://") } - if h.cfg.TLS.LetsEncrypt.Hostname != "" { + if h.cfg.TLS.ACME.Hostname != "" { certManager := autocert.Manager{ Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist(h.cfg.TLS.LetsEncrypt.Hostname), - Cache: autocert.DirCache(h.cfg.TLS.LetsEncrypt.CacheDir), + HostPolicy: autocert.HostWhitelist(h.cfg.TLS.ACME.Hostname), + Cache: autocert.DirCache(h.cfg.TLS.ACME.CacheDir), Client: &acme.Client{ - DirectoryURL: h.cfg.ACMEURL, + DirectoryURL: h.cfg.TLS.ACME.URL, HTTPClient: &http.Client{ Transport: &acmeLogger{ rt: http.DefaultTransport, }, }, }, - Email: h.cfg.ACMEEmail, + Email: h.cfg.TLS.ACME.Email, } - switch h.cfg.TLS.LetsEncrypt.ChallengeType { + switch h.cfg.TLS.ACME.ChallengeType { case types.TLSALPN01ChallengeType: // Configuration via autocert with TLS-ALPN-01 (https://tools.ietf.org/html/rfc8737) // The RFC requires that the validation is done on port 443; in other words, headscale @@ -1057,7 +1057,7 @@ func (h *Headscale) getTLSSettings(ctx context.Context) (*tlsBundle, error) { // port 80 for the certificate validation in addition to the headscale // service, which can be configured to run on any other port. server := &http.Server{ - Addr: h.cfg.TLS.LetsEncrypt.Listen, + Addr: h.cfg.TLS.ACME.HTTPListen, Handler: certManager.HTTPHandler(http.HandlerFunc(h.redirect)), ReadTimeout: types.HTTPTimeout, } @@ -1066,7 +1066,7 @@ func (h *Headscale) getTLSSettings(ctx context.Context) (*tlsBundle, error) { if err != nil { return nil, &types.ListenerBindError{ Listener: "ACME HTTP-01 challenge", - YAMLKey: "tls_letsencrypt_listen", + YAMLKey: "tls.acme.http_listen", Addr: server.Addr, Err: err, } @@ -1089,7 +1089,7 @@ func (h *Headscale) getTLSSettings(ctx context.Context) (*tlsBundle, error) { cert, err := tls.LoadX509KeyPair(h.cfg.TLS.CertPath, h.cfg.TLS.KeyPath) if err != nil { - return nil, fmt.Errorf("loading TLS keypair (tls_cert_path=%q, tls_key_path=%q): %w", + return nil, fmt.Errorf("loading TLS keypair (tls.cert_path=%q, tls.key_path=%q): %w", h.cfg.TLS.CertPath, h.cfg.TLS.KeyPath, err) } diff --git a/hscontrol/types/config.go b/hscontrol/types/config.go index 74ce4a307..fbcdd4484 100644 --- a/hscontrol/types/config.go +++ b/hscontrol/types/config.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io/fs" + "maps" "net" "net/netip" "net/url" @@ -140,9 +141,6 @@ type Config struct { TLS TLSConfig - ACMEURL string - ACMEEmail string - // DNSConfig is the headscale representation of the DNS configuration. // It is kept in the config update for some settings that are // not directly converted into a [tailcfg.DNSConfig]. @@ -250,14 +248,60 @@ type TLSConfig struct { CertPath string KeyPath string - LetsEncrypt LetsEncryptConfig + ACME ACMEConfig } -type LetsEncryptConfig struct { - Listen string +// ACMEConfig gets headscale's own certificate from an ACME CA. +type ACMEConfig struct { Hostname string - CacheDir string ChallengeType string + // HTTPListen serves HTTP-01 challenges. + HTTPListen string + URL string + Email string + CacheDir string +} + +// legacyTLSKeys maps each tls key to the flat key it replaced. The flat +// keys keep working, so existing configs and NixOS modules still run. +var legacyTLSKeys = map[string]string{ + "tls.cert_path": "tls_cert_path", + "tls.key_path": "tls_key_path", + "tls.acme.hostname": "tls_letsencrypt_hostname", + "tls.acme.challenge_type": "tls_letsencrypt_challenge_type", + "tls.acme.http_listen": "tls_letsencrypt_listen", + "tls.acme.cache_dir": "tls_letsencrypt_cache_dir", + "tls.acme.url": "acme_url", + "tls.acme.email": "acme_email", +} + +var tlsDefaults = map[string]string{ + "tls.acme.challenge_type": HTTP01ChallengeType, + "tls.acme.http_listen": ":http", + "tls.acme.cache_dir": "/var/www/.cache", +} + +// warnLegacyTLSKeys points at the tls keys for flat keys in the config +// file that turn TLS on. NixOS writes the flat keys even with TLS off, and +// the HEADSCALE_TLS_* variables name both the flat and the tls keys. +func warnLegacyTLSKeys(depr *deprecator) { + legacyTLSOn := func(key string) bool { return viper.InConfig(key) && viper.GetString(key) != "" } + if !legacyTLSOn("tls_letsencrypt_hostname") && !legacyTLSOn("tls_cert_path") { + return + } + + for _, key := range slices.Sorted(maps.Keys(legacyTLSKeys)) { + if viper.InConfig(legacyTLSKeys[key]) { + depr.warnNoAlias(key, legacyTLSKeys[key]) + } + } +} + +// tlsKey reads a tls setting, falling back to the flat key it replaced and +// then to its default. The defaults are not viper defaults: those would +// shadow the flat key. +func tlsKey(key string) string { + return cmp.Or(viper.GetString(key), viper.GetString(legacyTLSKeys[key]), tlsDefaults[key]) } type PKCEConfig struct { @@ -474,10 +518,6 @@ func LoadConfig(path string, isFile bool) error { viper.SetDefault("policy.mode", "file") - viper.SetDefault("tls_letsencrypt_cache_dir", "/var/www/.cache") - viper.SetDefault("tls_letsencrypt_challenge_type", HTTP01ChallengeType) - viper.SetDefault("tls_letsencrypt_listen", ":http") - viper.SetDefault("log.level", "info") viper.SetDefault("log.format", TextLogFormat) @@ -675,14 +715,16 @@ func validateServerConfigInto(v *configValidator) { }) } - if (viper.GetString("tls_letsencrypt_hostname") != "") && - ((viper.GetString("tls_cert_path") != "") || (viper.GetString("tls_key_path") != "")) { + warnLegacyTLSKeys(&depr) + + if tlsKey("tls.acme.hostname") != "" && + (tlsKey("tls.cert_path") != "" || tlsKey("tls.key_path") != "") { v.Add(&ConfigError{ - Reason: "tls_letsencrypt_hostname and tls_cert_path/tls_key_path are mutually exclusive", - Current: []KV{{"tls_letsencrypt_hostname", viper.GetString("tls_letsencrypt_hostname")}}, + Reason: "tls.acme.hostname and tls.cert_path/tls.key_path are mutually exclusive", + Current: []KV{{"tls.acme.hostname", tlsKey("tls.acme.hostname")}}, ConflictsWith: []KV{ - {"tls_cert_path", viper.GetString("tls_cert_path")}, - {"tls_key_path", viper.GetString("tls_key_path")}, + {"tls.cert_path", tlsKey("tls.cert_path")}, + {"tls.key_path", tlsKey("tls.key_path")}, }, Hint: "choose one TLS strategy and unset the other (Let's Encrypt OR a static keypair)", See: "https://headscale.net/stable/ref/tls/", @@ -697,20 +739,20 @@ func validateServerConfigInto(v *configValidator) { }) } - if (viper.GetString("tls_letsencrypt_hostname") != "") && - (viper.GetString("tls_letsencrypt_challenge_type") == TLSALPN01ChallengeType) && - (!strings.HasSuffix(viper.GetString("listen_addr"), ":443")) { + if tlsKey("tls.acme.hostname") != "" && + tlsKey("tls.acme.challenge_type") == TLSALPN01ChallengeType && + !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") + Msg("Warning: when using tls.acme.hostname with TLS-ALPN-01 as challenge type, headscale must be reachable on port 443, i.e. listen_addr should probably end in :443") } validateListenerCollisions(v) - if ct := viper.GetString("tls_letsencrypt_challenge_type"); ct != HTTP01ChallengeType && ct != TLSALPN01ChallengeType { + if ct := tlsKey("tls.acme.challenge_type"); ct != HTTP01ChallengeType && ct != TLSALPN01ChallengeType { v.Add(&ConfigError{ - Reason: "tls_letsencrypt_challenge_type has an unsupported value", - Current: []KV{{"tls_letsencrypt_challenge_type", ct}}, + Reason: "tls.acme.challenge_type has an unsupported value", + Current: []KV{{"tls.acme.challenge_type", ct}}, Allowed: []string{HTTP01ChallengeType, TLSALPN01ChallengeType}, Hint: "pick one of the allowed values; HTTP-01 is the default", }) @@ -989,20 +1031,16 @@ func dnsChallengeConfig() (DNSChallengeConfig, error) { func tlsConfig() TLSConfig { return TLSConfig{ - LetsEncrypt: LetsEncryptConfig{ - Hostname: viper.GetString("tls_letsencrypt_hostname"), - Listen: viper.GetString("tls_letsencrypt_listen"), - CacheDir: util.AbsolutePathFromConfigPath( - viper.GetString("tls_letsencrypt_cache_dir"), - ), - ChallengeType: viper.GetString("tls_letsencrypt_challenge_type"), + CertPath: util.AbsolutePathFromConfigPath(tlsKey("tls.cert_path")), + KeyPath: util.AbsolutePathFromConfigPath(tlsKey("tls.key_path")), + ACME: ACMEConfig{ + Hostname: tlsKey("tls.acme.hostname"), + ChallengeType: tlsKey("tls.acme.challenge_type"), + HTTPListen: tlsKey("tls.acme.http_listen"), + URL: tlsKey("tls.acme.url"), + Email: tlsKey("tls.acme.email"), + CacheDir: util.AbsolutePathFromConfigPath(tlsKey("tls.acme.cache_dir")), }, - CertPath: util.AbsolutePathFromConfigPath( - viper.GetString("tls_cert_path"), - ), - KeyPath: util.AbsolutePathFromConfigPath( - viper.GetString("tls_key_path"), - ), } } @@ -1608,9 +1646,6 @@ func LoadServerConfig() (*Config, error) { TailcfgDNSConfig: dnsToTailcfgDNS(dnsConfig), DNSChallenge: dnsChallenge, - ACMEEmail: viper.GetString("acme_email"), - ACMEURL: viper.GetString("acme_url"), - UnixSocket: viper.GetString("unix_socket"), UnixSocketPermission: socketPerm, diff --git a/hscontrol/types/config_test.go b/hscontrol/types/config_test.go index e70e1142e..59c594599 100644 --- a/hscontrol/types/config_test.go +++ b/hscontrol/types/config_test.go @@ -367,9 +367,11 @@ func TestTLSConfigValidation(t *testing.T) { var err error configYaml := []byte(`--- -tls_letsencrypt_hostname: example.com -tls_letsencrypt_challenge_type: "" -tls_cert_path: abc.pem +tls: + cert_path: abc.pem + acme: + hostname: example.com + challenge_type: bogus noise: private_key_path: noise_private.key`) @@ -390,12 +392,12 @@ noise: assert.Contains( t, err.Error(), - "Fatal config error: tls_letsencrypt_hostname and tls_cert_path/tls_key_path are mutually exclusive", + "Fatal config error: tls.acme.hostname and tls.cert_path/tls.key_path are mutually exclusive", ) assert.Contains( t, err.Error(), - "Fatal config error: tls_letsencrypt_challenge_type has an unsupported value", + "Fatal config error: tls.acme.challenge_type has an unsupported value", ) assert.Contains( t, @@ -408,8 +410,10 @@ noise: noise: private_key_path: noise_private.key server_url: http://127.0.0.1:8080 -tls_letsencrypt_hostname: example.com -tls_letsencrypt_challenge_type: TLS-ALPN-01 +tls: + acme: + hostname: example.com + challenge_type: TLS-ALPN-01 `) err = os.WriteFile(configFilePath, configYaml, 0o600) @@ -523,22 +527,37 @@ oidc:` + tt.oidcBlock + "\n") } } -func TestTLSLetsEncryptListenAddrCollision(t *testing.T) { +func TestTLSACMEListenAddrCollision(t *testing.T) { + const ( + tlsKeys = `tls: + acme: + hostname: %q + challenge_type: %q + http_listen: %q +` + legacyKeys = `tls_letsencrypt_hostname: %q +tls_letsencrypt_challenge_type: %q +tls_letsencrypt_listen: %q +` + ) + tests := []struct { name string listenAddr string - leListen string + acmeListen string hostname string challengeType string + keys string wantErr string // empty = expect no error }{ - {"collision-numeric", ":80", ":80", "example.com", "HTTP-01", "would bind the same TCP socket"}, - {"collision-named-vs-numeric", "0.0.0.0:80", ":http", "example.com", "HTTP-01", "would bind the same TCP socket"}, - {"collision-https-named", ":443", ":https", "example.com", "HTTP-01", "would bind the same TCP socket"}, - {"canonical", "0.0.0.0:443", ":http", "example.com", "HTTP-01", ""}, - {"no-hostname-skipped", "0.0.0.0:80", ":http", "", "HTTP-01", ""}, - {"tls-alpn-01-skipped", "0.0.0.0:80", ":http", "example.com", "TLS-ALPN-01", ""}, - {"different-numeric", ":8080", ":8081", "example.com", "HTTP-01", ""}, + {"collision-numeric", ":80", ":80", "example.com", "HTTP-01", tlsKeys, "would bind the same TCP socket"}, + {"collision-named-vs-numeric", "0.0.0.0:80", ":http", "example.com", "HTTP-01", tlsKeys, "would bind the same TCP socket"}, + {"collision-https-named", ":443", ":https", "example.com", "HTTP-01", tlsKeys, "would bind the same TCP socket"}, + {"collision-legacy-keys", ":80", ":80", "example.com", "HTTP-01", legacyKeys, "would bind the same TCP socket"}, + {"canonical", "0.0.0.0:443", ":http", "example.com", "HTTP-01", tlsKeys, ""}, + {"no-hostname-skipped", "0.0.0.0:80", ":http", "", "HTTP-01", tlsKeys, ""}, + {"tls-alpn-01-skipped", "0.0.0.0:80", ":http", "example.com", "TLS-ALPN-01", tlsKeys, ""}, + {"different-numeric", ":8080", ":8081", "example.com", "HTTP-01", tlsKeys, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -548,17 +567,14 @@ func TestTLSLetsEncryptListenAddrCollision(t *testing.T) { cfg := fmt.Sprintf(`--- server_url: https://example.com listen_addr: %q -tls_letsencrypt_hostname: %q -tls_letsencrypt_challenge_type: %q -tls_letsencrypt_listen: %q -noise: +`+tt.keys+`noise: private_key_path: noise_private.key database: type: sqlite3 dns: magic_dns: false override_local_dns: false -`, tt.listenAddr, tt.hostname, tt.challengeType, tt.leListen) +`, tt.listenAddr, tt.hostname, tt.challengeType, tt.acmeListen) require.NoError(t, os.WriteFile( filepath.Join(tmpDir, "config.yaml"), []byte(cfg), 0o600)) require.NoError(t, LoadConfig(tmpDir, false)) @@ -1291,3 +1307,124 @@ dns: }) } } + +// TestTLSLegacyKeys pins that the flat TLS keys keep working under the tls +// block, with the tls keys winning, and that the NixOS module's defaults +// for the flat keys leave TLS off. +func TestTLSLegacyKeys(t *testing.T) { + base := `--- +server_url: https://headscale.example.com +noise: + private_key_path: noise_private.key +prefixes: + v4: 100.64.0.0/10 +database: + type: sqlite +dns: + magic_dns: false + override_local_dns: false +` + + tests := []struct { + name string + yaml string + env map[string]string + want TLSConfig + }{ + { + name: "defaults", + yaml: base, + want: TLSConfig{ACME: ACMEConfig{ + ChallengeType: HTTP01ChallengeType, + HTTPListen: ":http", + CacheDir: "/var/www/.cache", + }}, + }, + { + name: "legacy keys", + yaml: base + `tls_letsencrypt_hostname: headscale.example.com +tls_letsencrypt_challenge_type: TLS-ALPN-01 +tls_letsencrypt_listen: ":8080" +tls_letsencrypt_cache_dir: /var/lib/headscale/cache +acme_url: https://acme.example.com/directory +acme_email: admin@example.com +`, + want: TLSConfig{ACME: ACMEConfig{ + Hostname: "headscale.example.com", + ChallengeType: TLSALPN01ChallengeType, + HTTPListen: ":8080", + URL: "https://acme.example.com/directory", + Email: "admin@example.com", + CacheDir: "/var/lib/headscale/cache", + }}, + }, + { + name: "tls keys win over legacy keys", + yaml: base + `tls_cert_path: /old/cert.pem +tls_key_path: /old/key.pem +tls: + cert_path: /new/cert.pem + key_path: /new/key.pem +`, + want: TLSConfig{ + CertPath: "/new/cert.pem", + KeyPath: "/new/key.pem", + ACME: ACMEConfig{ + ChallengeType: HTTP01ChallengeType, + HTTPListen: ":http", + CacheDir: "/var/www/.cache", + }, + }, + }, + { + // What the NixOS module writes when TLS is off. + name: "nixos defaults", + yaml: base + `tls_letsencrypt_hostname: "" +tls_letsencrypt_challenge_type: HTTP-01 +tls_letsencrypt_listen: ":http" +tls_letsencrypt_cache_dir: /var/lib/headscale/.cache +`, + want: TLSConfig{ACME: ACMEConfig{ + ChallengeType: HTTP01ChallengeType, + HTTPListen: ":http", + CacheDir: "/var/lib/headscale/.cache", + }}, + }, + { + // Both keys read HEADSCALE_TLS_CERT_PATH. + name: "env", + yaml: base, + env: map[string]string{ + "HEADSCALE_TLS_CERT_PATH": "/env/cert.pem", + "HEADSCALE_TLS_KEY_PATH": "/env/key.pem", + }, + want: TLSConfig{ + CertPath: "/env/cert.pem", + KeyPath: "/env/key.pem", + ACME: ACMEConfig{ + ChallengeType: HTTP01ChallengeType, + HTTPListen: ":http", + CacheDir: "/var/www/.cache", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + viper.Reset() + + for k, v := range tt.env { + t.Setenv(k, v) + } + + tmpDir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "config.yaml"), []byte(tt.yaml), 0o600)) + require.NoError(t, LoadConfig(tmpDir, false)) + + cfg, err := LoadServerConfig() + require.NoError(t, err) + assert.Equal(t, tt.want, cfg.TLS) + }) + } +} diff --git a/hscontrol/types/listener.go b/hscontrol/types/listener.go index d6c8853b6..dfca1883a 100644 --- a/hscontrol/types/listener.go +++ b/hscontrol/types/listener.go @@ -109,18 +109,18 @@ func validateListenerCollisions(v *configValidator) { listenAddr := viper.GetString("listen_addr") grpcAddr := viper.GetString("grpc_listen_addr") metricsAddr := viper.GetString("metrics_listen_addr") - acmeAddr := viper.GetString("tls_letsencrypt_listen") + acmeAddr := tlsKey("tls.acme.http_listen") listeners := []spec{ {key: "listen_addr", addr: listenAddr, active: listenAddr != ""}, {key: "grpc_listen_addr", addr: grpcAddr, active: grpcAddr != ""}, {key: "metrics_listen_addr", addr: metricsAddr, active: metricsAddr != ""}, { - key: "tls_letsencrypt_listen", + key: "tls.acme.http_listen", addr: acmeAddr, active: acmeAddr != "" && - viper.GetString("tls_letsencrypt_hostname") != "" && - viper.GetString("tls_letsencrypt_challenge_type") == HTTP01ChallengeType, + tlsKey("tls.acme.hostname") != "" && + tlsKey("tls.acme.challenge_type") == HTTP01ChallengeType, }, }