diff --git a/app.go b/app.go index 2e11248..2e4fb4b 100644 --- a/app.go +++ b/app.go @@ -94,7 +94,7 @@ type Config struct { TLSCertPath string TLSKeyPath string - TLSClientAuthMode string + TLSClientAuthMode tls.ClientAuthType ACMEURL string ACMEEmail string @@ -153,6 +153,27 @@ type Headscale struct { requestedExpiryCache *cache.Cache } +// Look up the TLS constant relative to user-supplied TLS client +// authentication mode. If an unknown mode is supplied, the default +// value, tls.RequireAnyClientCert, is returned. The returned boolean +// indicates if the supplied mode was valid. +func LookupTLSClientAuthMode(mode string) (tls.ClientAuthType, bool) { + switch mode { + case DisabledClientAuth: + // Client cert is _not_ required. + return tls.NoClientCert, true + case RelaxedClientAuth: + // Client cert required, but _not verified_. + return tls.RequireAnyClientCert, true + case EnforcedClientAuth: + // Client cert is _required and verified_. + return tls.RequireAndVerifyClientCert, true + default: + // Return the default when an unknown value is supplied. + return tls.RequireAnyClientCert, false + } +} + // NewHeadscale returns the Headscale app. func NewHeadscale(cfg Config) (*Headscale, error) { privKey, err := readOrCreatePrivateKey(cfg.PrivateKeyPath) @@ -655,17 +676,12 @@ func (h *Headscale) getTLSSettings() (*tls.Config, error) { log.Warn().Msg("Listening with TLS but ServerURL does not start with https://") } - clientAuthMode, err := h.GetClientAuthMode() - if err != nil { - return nil, err - } - log.Info().Msg(fmt.Sprintf( "Client authentication (mTLS) is \"%s\". See the docs to learn about configuring this setting.", h.cfg.TLSClientAuthMode)) tlsConfig := &tls.Config{ - ClientAuth: clientAuthMode, + ClientAuth: h.cfg.TLSClientAuthMode, NextProtos: []string{"http/1.1"}, Certificates: make([]tls.Certificate, 1), MinVersion: tls.VersionTLS12, @@ -677,25 +693,6 @@ func (h *Headscale) getTLSSettings() (*tls.Config, error) { } } -// Look up the TLS constant relative to user-supplied TLS client -// authentication mode. -func (h *Headscale) GetClientAuthMode() (tls.ClientAuthType, error) { - switch h.cfg.TLSClientAuthMode { - case DisabledClientAuth: - // Client cert is _not_ required. - return tls.NoClientCert, nil - case RelaxedClientAuth: - // Client cert required, but _not verified_. - return tls.RequireAnyClientCert, nil - case EnforcedClientAuth: - // Client cert is _required and verified_. - return tls.RequireAndVerifyClientCert, nil - default: - return tls.NoClientCert, Error("Invalid tls_client_auth_mode provided: " + - h.cfg.TLSClientAuthMode) - } -} - func (h *Headscale) setLastStateChangeToNow(namespace string) { now := time.Now().UTC() lastStateUpdate.WithLabelValues("", "headscale").Set(float64(now.Unix())) diff --git a/cmd/headscale/cli/.utils.go.swp b/cmd/headscale/cli/.utils.go.swp deleted file mode 100644 index fbd933a..0000000 Binary files a/cmd/headscale/cli/.utils.go.swp and /dev/null differ diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index b628298..1a66fb1 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -30,6 +30,7 @@ const ( ) func LoadConfig(path string) error { + viper.SetConfigName("config") if path == "" { viper.AddConfigPath("/etc/headscale/") @@ -87,9 +88,15 @@ func LoadConfig(path string) error { errorText += "Fatal config error: server_url must start with https:// or http://\n" } - clientAuthMode := viper.GetString("tls_client_auth_mode") - if clientAuthMode != "disabled" && clientAuthMode != "relaxed" && clientAuthMode != "enforced" { - errorText += "Invalid tls_client_auth_mode supplied. Accepted values: disabled, relaxed, enforced." + _, authModeValid := headscale.LookupTLSClientAuthMode(viper.GetString("tls_client_auth_mode")) + + if !authModeValid { + errorText += fmt.Sprintf( + "Invalid tls_client_auth_mode supplied: %s. Accepted values: %s, %s, %s.", + viper.GetString("tls_client_auth_mode"), + headscale.DisabledClientAuth, + headscale.RelaxedClientAuth, + headscale.EnforcedClientAuth) } if errorText != "" { @@ -280,6 +287,8 @@ func getHeadscaleConfig() headscale.Config { log.Warn().Msgf("'ip_prefixes' not configured, falling back to default: %v", prefixes) } + tlsClientAuthMode, _ := headscale.LookupTLSClientAuthMode(viper.GetString("tls_client_auth_mode")) + return headscale.Config{ ServerURL: viper.GetString("server_url"), Addr: viper.GetString("listen_addr"), @@ -310,7 +319,7 @@ func getHeadscaleConfig() headscale.Config { TLSCertPath: absPath(viper.GetString("tls_cert_path")), TLSKeyPath: absPath(viper.GetString("tls_key_path")), - TLSClientAuthMode: viper.GetString("tls_client_auth_mode"), + TLSClientAuthMode: tlsClientAuthMode, DNSConfig: dnsConfig, diff --git a/config-example.yaml b/config-example.yaml index 1300ab3..0fd7b03 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -97,7 +97,7 @@ tls_letsencrypt_hostname: "" # - disabled: client authentication disabled # - relaxed: client certificate is required but not verified # - enforced: client certificate is required and verified -tls_client_auth_mode: disabled +tls_client_auth_mode: relaxed # Path to store certificates and metadata needed by # letsencrypt diff --git a/docs/tls.md b/docs/tls.md index 19cf16a..d837144 100644 --- a/docs/tls.md +++ b/docs/tls.md @@ -33,7 +33,7 @@ tls_key_path: "" ### Configuring Mutual TLS Authentication (mTLS) mTLS is a method by which an HTTPS server authenticates clients, e.g. Tailscale, -using TLS certificates. The capability can be configured by by applying one of +using TLS certificates. The capability can be configured by applying one of the following values to the `tls_client_auth_mode` setting in the configuration file.