From 9e619fc020963bc0c1c7664d163d93a1b5dc4d0a Mon Sep 17 00:00:00 2001 From: Justin Angel Date: Sat, 29 Jan 2022 12:59:31 -0500 Subject: [PATCH 01/12] Making client authentication mode configurable --- app.go | 24 +++++++++++++++++++++--- cmd/headscale/cli/utils.go | 8 ++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index 3ba384b..6accaf9 100644 --- a/app.go +++ b/app.go @@ -87,8 +87,9 @@ type Config struct { TLSLetsEncryptCacheDir string TLSLetsEncryptChallengeType string - TLSCertPath string - TLSKeyPath string + TLSCertPath string + TLSKeyPath string + TLSClientAuthMode string ACMEURL string ACMEEmail string @@ -644,12 +645,29 @@ func (h *Headscale) getTLSSettings() (*tls.Config, error) { if !strings.HasPrefix(h.cfg.ServerURL, "https://") { log.Warn().Msg("Listening with TLS but ServerURL does not start with https://") } + + // Leaving flexibility here to support other authentication modes + // if desired. + var client_auth_mode tls.ClientAuthType + msg := "Client authentication (mTLS) " + if(h.cfg.TLSClientAuthMode == "disabled"){ + log.Warn().Msg(msg + "is disabled") + client_auth_mode = tls.NoClientCert + }else if (h.cfg.TLSClientAuthMode == "relaxed"){ + log.Warn().Msg(msg + "is relaxed. Client certs will be required but will not be verified.") + client_auth_mode = tls.RequireAnyClientCert + }else{ + log.Warn().Msg(msg + "is enforced. Disable or relax in the configuration file.") + client_auth_mode = tls.RequireAndVerifyClientCert + } + tlsConfig := &tls.Config{ - ClientAuth: tls.RequireAnyClientCert, + ClientAuth: client_auth_mode, NextProtos: []string{"http/1.1"}, Certificates: make([]tls.Certificate, 1), MinVersion: tls.VersionTLS12, } + tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(h.cfg.TLSCertPath, h.cfg.TLSKeyPath) return tlsConfig, err diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index a485664..4faf905 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -40,6 +40,7 @@ func LoadConfig(path string) error { viper.SetDefault("tls_letsencrypt_cache_dir", "/var/www/.cache") viper.SetDefault("tls_letsencrypt_challenge_type", "HTTP-01") + viper.SetDefault("tls_client_auth_mode", "disabled") viper.SetDefault("ip_prefix", "100.64.0.0/10") @@ -80,6 +81,12 @@ func LoadConfig(path string) error { !strings.HasPrefix(viper.GetString("server_url"), "https://") { errorText += "Fatal config error: server_url must start with https:// or http://\n" } + + auth_mode := viper.GetString("tls_client_auth_mode") + if (auth_mode != "disabled" && auth_mode != "enforced"){ + errorText += "Invalid tls_client_auth_mode supplied. Accepted values: disabled, enforced." + } + if errorText != "" { //nolint return errors.New(strings.TrimSuffix(errorText, "\n")) @@ -251,6 +258,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"), DNSConfig: dnsConfig, From 5935b13b6780180d717cd66a13657c043c11905b Mon Sep 17 00:00:00 2001 From: Justin Angel Date: Sat, 29 Jan 2022 13:35:08 -0500 Subject: [PATCH 02/12] refining --- app.go | 19 ++++++++++++------- cmd/headscale/cli/utils.go | 4 ++-- docs/tls.md | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/app.go b/app.go index 6accaf9..7301757 100644 --- a/app.go +++ b/app.go @@ -646,21 +646,26 @@ func (h *Headscale) getTLSSettings() (*tls.Config, error) { log.Warn().Msg("Listening with TLS but ServerURL does not start with https://") } - // Leaving flexibility here to support other authentication modes - // if desired. var client_auth_mode tls.ClientAuthType - msg := "Client authentication (mTLS) " if(h.cfg.TLSClientAuthMode == "disabled"){ - log.Warn().Msg(msg + "is disabled") + // Client cert is _not_ required. client_auth_mode = tls.NoClientCert }else if (h.cfg.TLSClientAuthMode == "relaxed"){ - log.Warn().Msg(msg + "is relaxed. Client certs will be required but will not be verified.") + // Client cert required, but not verified. client_auth_mode = tls.RequireAnyClientCert - }else{ - log.Warn().Msg(msg + "is enforced. Disable or relax in the configuration file.") + }else if (h.cfg.TLSClientAuthMode == "enforced"){ + // Client cert is required and verified. client_auth_mode = tls.RequireAndVerifyClientCert + }else{ + return nil, errors.New( + "Invalid tls_client_auth_mode provided: " + + h.cfg.TLSClientAuthMode) } + 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: client_auth_mode, NextProtos: []string{"http/1.1"}, diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 4faf905..1cbfcf6 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -83,8 +83,8 @@ func LoadConfig(path string) error { } auth_mode := viper.GetString("tls_client_auth_mode") - if (auth_mode != "disabled" && auth_mode != "enforced"){ - errorText += "Invalid tls_client_auth_mode supplied. Accepted values: disabled, enforced." + if (auth_mode != "disabled" && auth_mode != "relaxed" && auth_mode != "enforced"){ + errorText += "Invalid tls_client_auth_mode supplied. Accepted values: disabled, relaxed, enforced." } if errorText != "" { diff --git a/docs/tls.md b/docs/tls.md index 557cdf0..f8818ce 100644 --- a/docs/tls.md +++ b/docs/tls.md @@ -29,3 +29,22 @@ headscale can also be configured to expose its web service via TLS. To configure tls_cert_path: "" 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 +the following values to the `tls_client_auth_mode` setting in the configuration +file. + +| Value | Behavior | +| ----- | -------- | +| `disabled` | Disable mTLS (default). | +| `relaxed` | A client certificate is required, but it is not verified. | +| `enforced` | Requires clients to supply a certificate that is verified. | + + +```yaml +tls_client_auth_mode: "" +``` + From c98a559b4df7222953d5fba6cd781c1cc391efa0 Mon Sep 17 00:00:00 2001 From: Justin Angel Date: Sat, 29 Jan 2022 14:15:33 -0500 Subject: [PATCH 03/12] linting/formatting --- app.go | 44 +++++++++++++++++++------------------- cmd/headscale/cli/utils.go | 16 +++++++------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/app.go b/app.go index 7301757..a375165 100644 --- a/app.go +++ b/app.go @@ -87,9 +87,9 @@ type Config struct { TLSLetsEncryptCacheDir string TLSLetsEncryptChallengeType string - TLSCertPath string - TLSKeyPath string - TLSClientAuthMode string + TLSCertPath string + TLSKeyPath string + TLSClientAuthMode string ACMEURL string ACMEEmail string @@ -646,28 +646,28 @@ func (h *Headscale) getTLSSettings() (*tls.Config, error) { log.Warn().Msg("Listening with TLS but ServerURL does not start with https://") } - var client_auth_mode tls.ClientAuthType - if(h.cfg.TLSClientAuthMode == "disabled"){ - // Client cert is _not_ required. - client_auth_mode = tls.NoClientCert - }else if (h.cfg.TLSClientAuthMode == "relaxed"){ - // Client cert required, but not verified. - client_auth_mode = tls.RequireAnyClientCert - }else if (h.cfg.TLSClientAuthMode == "enforced"){ - // Client cert is required and verified. - client_auth_mode = tls.RequireAndVerifyClientCert - }else{ - return nil, errors.New( - "Invalid tls_client_auth_mode provided: " + - h.cfg.TLSClientAuthMode) - } + var clientAuthMode tls.ClientAuthType + if h.cfg.TLSClientAuthMode == "disabled" { + // Client cert is _not_ required. + clientAuthMode = tls.NoClientCert + } else if h.cfg.TLSClientAuthMode == "relaxed" { + // Client cert required, but not verified. + clientAuthMode = tls.RequireAnyClientCert + } else if h.cfg.TLSClientAuthMode == "enforced" { + // Client cert is required and verified. + clientAuthMode = tls.RequireAndVerifyClientCert + } else { + return nil, errors.New( + "Invalid tls_clientAuthMode provided: " + + h.cfg.TLSClientAuthMode) + } - log.Info().Msg(fmt.Sprintf( - "Client authentication (mTLS) is \"%s\". See the docs to learn about configuring this setting.", - h.cfg.TLSClientAuthMode)) + 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: client_auth_mode, + ClientAuth: clientAuthMode, NextProtos: []string{"http/1.1"}, Certificates: make([]tls.Certificate, 1), MinVersion: tls.VersionTLS12, diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 1cbfcf6..1f9092e 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -40,7 +40,7 @@ func LoadConfig(path string) error { viper.SetDefault("tls_letsencrypt_cache_dir", "/var/www/.cache") viper.SetDefault("tls_letsencrypt_challenge_type", "HTTP-01") - viper.SetDefault("tls_client_auth_mode", "disabled") + viper.SetDefault("tls_client_auth_mode", "disabled") viper.SetDefault("ip_prefix", "100.64.0.0/10") @@ -82,10 +82,10 @@ func LoadConfig(path string) error { errorText += "Fatal config error: server_url must start with https:// or http://\n" } - auth_mode := viper.GetString("tls_client_auth_mode") - if (auth_mode != "disabled" && auth_mode != "relaxed" && auth_mode != "enforced"){ - errorText += "Invalid tls_client_auth_mode supplied. Accepted values: disabled, relaxed, enforced." - } + 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." + } if errorText != "" { //nolint @@ -256,9 +256,9 @@ func getHeadscaleConfig() headscale.Config { ), TLSLetsEncryptChallengeType: viper.GetString("tls_letsencrypt_challenge_type"), - TLSCertPath: absPath(viper.GetString("tls_cert_path")), - TLSKeyPath: absPath(viper.GetString("tls_key_path")), - TLSClientAuthMode: viper.GetString("tls_client_auth_mode"), + TLSCertPath: absPath(viper.GetString("tls_cert_path")), + TLSKeyPath: absPath(viper.GetString("tls_key_path")), + TLSClientAuthMode: viper.GetString("tls_client_auth_mode"), DNSConfig: dnsConfig, From d44b2a7c014b98178743421b5ebc0b7b65100cb3 Mon Sep 17 00:00:00 2001 From: Justin Angel Date: Sun, 30 Jan 2022 07:26:28 -0500 Subject: [PATCH 04/12] adding default for tls_client_auth_mode --- config-example.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/config-example.yaml b/config-example.yaml index 3301669..3d8fe88 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -85,6 +85,13 @@ acme_email: "" # Domain name to request a TLS certificate for: tls_letsencrypt_hostname: "" +# Client (Tailscale/Browser) authentication mode (mTLS) +# Acceptable values: +# - disabled: client authentication disabled +# - relaxed: client certificate is required but not verified +# - enforced: client certificate is required and verified +tls_client_auth_mode: disabled + # Path to store certificates and metadata needed by # letsencrypt tls_letsencrypt_cache_dir: /var/lib/headscale/cache From 310e7b15c7d436205ff598f131118f45b3fa0be8 Mon Sep 17 00:00:00 2001 From: Justin Angel Date: Sun, 30 Jan 2022 10:46:57 -0500 Subject: [PATCH 05/12] making alternatives constants --- app.go | 22 +++++++++++++--------- docs/tls.md | 10 ++++------ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/app.go b/app.go index a375165..26d7b95 100644 --- a/app.go +++ b/app.go @@ -61,6 +61,10 @@ const ( errUnsupportedLetsEncryptChallengeType = Error( "unknown value for Lets Encrypt challenge type", ) + + DisabledClientAuth = "disabled" + RelaxedClientAuth = "relaxed" + EnforcedClientAuth = "enforced" ) // Config contains the initial Headscale configuration. @@ -647,19 +651,19 @@ func (h *Headscale) getTLSSettings() (*tls.Config, error) { } var clientAuthMode tls.ClientAuthType - if h.cfg.TLSClientAuthMode == "disabled" { + switch h.cfg.TLSClientAuthMode { + case DisabledClientAuth: // Client cert is _not_ required. clientAuthMode = tls.NoClientCert - } else if h.cfg.TLSClientAuthMode == "relaxed" { - // Client cert required, but not verified. + case RelaxedClientAuth: + // Client cert required, but _not verified_. clientAuthMode = tls.RequireAnyClientCert - } else if h.cfg.TLSClientAuthMode == "enforced" { - // Client cert is required and verified. + case EnforcedClientAuth: + // Client cert is _required and verified_. clientAuthMode = tls.RequireAndVerifyClientCert - } else { - return nil, errors.New( - "Invalid tls_clientAuthMode provided: " + - h.cfg.TLSClientAuthMode) + default: + return nil, Error("Invalid tls_client_auth_mode provided: " + + h.cfg.TLSClientAuthMode) } log.Info().Msg(fmt.Sprintf( diff --git a/docs/tls.md b/docs/tls.md index f8818ce..19cf16a 100644 --- a/docs/tls.md +++ b/docs/tls.md @@ -37,14 +37,12 @@ using TLS certificates. The capability can be configured by by applying one of the following values to the `tls_client_auth_mode` setting in the configuration file. -| Value | Behavior | -| ----- | -------- | -| `disabled` | Disable mTLS (default). | -| `relaxed` | A client certificate is required, but it is not verified. | +| Value | Behavior | +| ---------- | ---------------------------------------------------------- | +| `disabled` | Disable mTLS (default). | +| `relaxed` | A client certificate is required, but it is not verified. | | `enforced` | Requires clients to supply a certificate that is verified. | - ```yaml tls_client_auth_mode: "" ``` - From 0c3fd16113c2d8209cb6575bcb30a3b072482e99 Mon Sep 17 00:00:00 2001 From: Justin Angel Date: Mon, 31 Jan 2022 07:18:50 -0500 Subject: [PATCH 06/12] refining and adding tests --- app.go | 41 ++++++++++++++++++++++++++--------------- app_test.go | 22 ++++++++++++++++++++++ 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/app.go b/app.go index 26d7b95..8d2a2b1 100644 --- a/app.go +++ b/app.go @@ -650,21 +650,11 @@ func (h *Headscale) getTLSSettings() (*tls.Config, error) { log.Warn().Msg("Listening with TLS but ServerURL does not start with https://") } - var clientAuthMode tls.ClientAuthType - switch h.cfg.TLSClientAuthMode { - case DisabledClientAuth: - // Client cert is _not_ required. - clientAuthMode = tls.NoClientCert - case RelaxedClientAuth: - // Client cert required, but _not verified_. - clientAuthMode = tls.RequireAnyClientCert - case EnforcedClientAuth: - // Client cert is _required and verified_. - clientAuthMode = tls.RequireAndVerifyClientCert - default: - return nil, Error("Invalid tls_client_auth_mode provided: " + - h.cfg.TLSClientAuthMode) - } + 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.", @@ -683,6 +673,27 @@ 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/app_test.go b/app_test.go index bff1393..a53a880 100644 --- a/app_test.go +++ b/app_test.go @@ -63,3 +63,25 @@ func (s *Suite) ResetDB(c *check.C) { } app.db = db } + +// Enusre an error is returned when an invalid auth mode +// is supplied. +func (s *Suite) TestInvalidClientAuthMode(c *check.C){ + app.cfg.TLSClientAuthMode = "invalid" + _, err := app.GetClientAuthMode() + c.Assert(err, check.NotNil) +} + +// Ensure that all client auth modes return a nil error +func (s *Suite) TestAuthModes(c *check.C){ + + var modes = []string{"disabled", "relaxed", "enforced"} + + for _, v := range modes { + app.cfg.TLSClientAuthMode = v + _, err := app.GetClientAuthMode() + c.Assert(err, check.IsNil) + } + +} + From 9de5c7f8b8a6836197f555b8ed05fd7a0df41012 Mon Sep 17 00:00:00 2001 From: Justin Angel Date: Mon, 31 Jan 2022 07:22:17 -0500 Subject: [PATCH 07/12] updating default --- cmd/headscale/cli/.utils.go.swp | Bin 0 -> 24576 bytes cmd/headscale/cli/utils.go | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 cmd/headscale/cli/.utils.go.swp diff --git a/cmd/headscale/cli/.utils.go.swp b/cmd/headscale/cli/.utils.go.swp new file mode 100644 index 0000000000000000000000000000000000000000..fbd933abe05eba73d62b6cfcf550855fe57a20f6 GIT binary patch literal 24576 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 z Date: Mon, 31 Jan 2022 10:27:43 -0500 Subject: [PATCH 08/12] linting again --- app.go | 39 ++++++++++++++++++--------------------- app_test.go | 27 ++++++++++++--------------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/app.go b/app.go index 62d284b..b3725ee 100644 --- a/app.go +++ b/app.go @@ -657,11 +657,10 @@ 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 - } + 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.", @@ -683,22 +682,20 @@ 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) - } - + 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) { diff --git a/app_test.go b/app_test.go index a53a880..94b6ef0 100644 --- a/app_test.go +++ b/app_test.go @@ -66,22 +66,19 @@ func (s *Suite) ResetDB(c *check.C) { // Enusre an error is returned when an invalid auth mode // is supplied. -func (s *Suite) TestInvalidClientAuthMode(c *check.C){ - app.cfg.TLSClientAuthMode = "invalid" - _, err := app.GetClientAuthMode() - c.Assert(err, check.NotNil) +func (s *Suite) TestInvalidClientAuthMode(c *check.C) { + app.cfg.TLSClientAuthMode = "invalid" + _, err := app.GetClientAuthMode() + c.Assert(err, check.NotNil) } -// Ensure that all client auth modes return a nil error -func (s *Suite) TestAuthModes(c *check.C){ - - var modes = []string{"disabled", "relaxed", "enforced"} - - for _, v := range modes { - app.cfg.TLSClientAuthMode = v - _, err := app.GetClientAuthMode() - c.Assert(err, check.IsNil) - } +// Ensure that all client auth modes return a nil error. +func (s *Suite) TestAuthModes(c *check.C) { + modes := []string{"disabled", "relaxed", "enforced"} + for _, v := range modes { + app.cfg.TLSClientAuthMode = v + _, err := app.GetClientAuthMode() + c.Assert(err, check.IsNil) + } } - From 385dd9cc34cabb0c7c1a2dcdf92c4bd055db1f52 Mon Sep 17 00:00:00 2001 From: Justin Angel Date: Sun, 20 Feb 2022 09:06:14 -0500 Subject: [PATCH 09/12] refactoring --- app.go | 49 +++++++++++++++----------------- cmd/headscale/cli/.utils.go.swp | Bin 24576 -> 0 bytes cmd/headscale/cli/utils.go | 17 ++++++++--- config-example.yaml | 2 +- docs/tls.md | 2 +- 5 files changed, 38 insertions(+), 32 deletions(-) delete mode 100644 cmd/headscale/cli/.utils.go.swp 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 fbd933abe05eba73d62b6cfcf550855fe57a20f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24576 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 z Date: Mon, 21 Feb 2022 10:09:23 -0500 Subject: [PATCH 10/12] Linting and updating tests --- app.go | 2 +- app_test.go | 10 ++++------ cmd/headscale/cli/utils.go | 17 +++++++++-------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/app.go b/app.go index 8df56dd..26ec956 100644 --- a/app.go +++ b/app.go @@ -171,7 +171,7 @@ func LookupTLSClientAuthMode(mode string) (tls.ClientAuthType, bool) { // Client cert is _required and verified_. return tls.RequireAndVerifyClientCert, true default: - // Return the default when an unknown value is supplied. + // Return the default when an unknown value is supplied. return tls.RequireAnyClientCert, false } } diff --git a/app_test.go b/app_test.go index 3df5948..53c703a 100644 --- a/app_test.go +++ b/app_test.go @@ -69,9 +69,8 @@ func (s *Suite) ResetDB(c *check.C) { // Enusre an error is returned when an invalid auth mode // is supplied. func (s *Suite) TestInvalidClientAuthMode(c *check.C) { - app.cfg.TLSClientAuthMode = "invalid" - _, err := app.GetClientAuthMode() - c.Assert(err, check.NotNil) + _, isValid := LookupTLSClientAuthMode("invalid") + c.Assert(isValid, check.Equals, false) } // Ensure that all client auth modes return a nil error. @@ -79,8 +78,7 @@ func (s *Suite) TestAuthModes(c *check.C) { modes := []string{"disabled", "relaxed", "enforced"} for _, v := range modes { - app.cfg.TLSClientAuthMode = v - _, err := app.GetClientAuthMode() - c.Assert(err, check.IsNil) + _, isValid := LookupTLSClientAuthMode(v) + c.Assert(isValid, check.Equals, true) } } diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index dbcc8bb..9316302 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -34,7 +34,6 @@ const ( ) func LoadConfig(path string) error { - viper.SetConfigName("config") if path == "" { viper.AddConfigPath("/etc/headscale/") @@ -98,12 +97,12 @@ func LoadConfig(path string) error { _, 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) + 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 != "" { @@ -295,7 +294,9 @@ func getHeadscaleConfig() headscale.Config { Msgf("'ip_prefixes' not configured, falling back to default: %v", prefixes) } - tlsClientAuthMode, _ := headscale.LookupTLSClientAuthMode(viper.GetString("tls_client_auth_mode")) + tlsClientAuthMode, _ := headscale.LookupTLSClientAuthMode( + viper.GetString("tls_client_auth_mode"), + ) return headscale.Config{ ServerURL: viper.GetString("server_url"), From b5a59d4e7acaef7e74b8015324cf77f7c59d150f Mon Sep 17 00:00:00 2001 From: Justin Angel Date: Mon, 21 Feb 2022 10:20:11 -0500 Subject: [PATCH 11/12] updating changelog and docs --- CHANGELOG.md | 2 ++ docs/tls.md | 15 ++++++--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70bda12..2aaf580 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ **TBD (TBD):** +- Add support for configurable mTLS [docs](docs/tls.md#configuring-mutual-tls-authentication-mtls) + **0.13.0 (2022-02-18):** **Features**: diff --git a/docs/tls.md b/docs/tls.md index d837144..7dc322c 100644 --- a/docs/tls.md +++ b/docs/tls.md @@ -32,16 +32,13 @@ 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 applying one of -the following values to the `tls_client_auth_mode` setting in the configuration -file. +mTLS is a method by which an HTTPS server authenticates clients, e.g. Tailscale, using TLS certificates. This can be configured by applying one of the following values to the `tls_client_auth_mode` setting in the configuration file. -| Value | Behavior | -| ---------- | ---------------------------------------------------------- | -| `disabled` | Disable mTLS (default). | -| `relaxed` | A client certificate is required, but it is not verified. | -| `enforced` | Requires clients to supply a certificate that is verified. | +| Value | Behavior | +| ------------------- | -----------------------------------------------------------| +| `disabled` | Disable mTLS. | +| `relaxed` (default) | A client certificate is required, but it is not verified. | +| `enforced` | Requires clients to supply a certificate that is verified. | ```yaml tls_client_auth_mode: "" From 8c339076555c6872d2379500ebcaeabcdd1c8184 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Thu, 24 Feb 2022 11:10:40 +0000 Subject: [PATCH 12/12] Sort lint --- cmd/headscale/cli/utils.go | 4 +++- docs/tls.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 9316302..e3dce6b 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -94,7 +94,9 @@ func LoadConfig(path string) error { errorText += "Fatal config error: server_url must start with https:// or http://\n" } - _, authModeValid := headscale.LookupTLSClientAuthMode(viper.GetString("tls_client_auth_mode")) + _, authModeValid := headscale.LookupTLSClientAuthMode( + viper.GetString("tls_client_auth_mode"), + ) if !authModeValid { errorText += fmt.Sprintf( diff --git a/docs/tls.md b/docs/tls.md index 7dc322c..c319359 100644 --- a/docs/tls.md +++ b/docs/tls.md @@ -35,7 +35,7 @@ tls_key_path: "" mTLS is a method by which an HTTPS server authenticates clients, e.g. Tailscale, using TLS certificates. This can be configured by applying one of the following values to the `tls_client_auth_mode` setting in the configuration file. | Value | Behavior | -| ------------------- | -----------------------------------------------------------| +| ------------------- | ---------------------------------------------------------- | | `disabled` | Disable mTLS. | | `relaxed` (default) | A client certificate is required, but it is not verified. | | `enforced` | Requires clients to supply a certificate that is verified. |