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 3ba384b9..6accaf9c 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 a4856642..4faf9053 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 6accaf9c..73017574 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 4faf9053..1cbfcf62 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 557cdf01..f8818ce8 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 73017574..a375165d 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 1cbfcf62..1f9092e8 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 3301669d..3d8fe88c 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 a375165d..26d7b953 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 f8818ce8..19cf16a6 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 26d7b953..8d2a2b17 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 bff13933..a53a8802 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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 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 62d284b6..b3725eea 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 a53a8802..94b6ef00 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 2e112483..2e4fb4bd 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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 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 8df56ddd..26ec9568 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 3df59482..53c703a6 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 dbcc8bba..9316302a 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 70bda12d..2aaf580b 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 d8371444..7dc322cd 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 9316302a..e3dce6b7 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 7dc322cd..c319359a 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. |