Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable mtls #297

Merged
merged 21 commits into from
Feb 24, 2022
Merged

Configurable mtls #297

merged 21 commits into from
Feb 24, 2022

Conversation

ImpostorKeanu
Copy link
Contributor

@ImpostorKeanu ImpostorKeanu commented Jan 29, 2022

  • read the [CONTRIBUTING guidelines](README.md#user-content-contributing
  • raised a GitHub issue or discussed it on the projects chat beforehand
  • added unit tests
  • [] added integration tests
  • updated documentation if needed
  • [] updated CHANGELOG.md

The Problem

Currently, mTLS is set to require any certificate from clients when TLS is enabled. Verification of the certificate is not performed by Headscale, and as far as I can tell, there isn't a way to configure the Tailscale client to accept TLS certificate for authentication.

Implemented Solution

This merge makes mTLS configurable by adding a configuration parameter to config.yaml. It's disabled via the disabled value by default and can be configured to require any certificate (relaxed) or require and verify (enforced) a client certificate.

Related Issues

Copy link
Collaborator

@kradalby kradalby left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test to verify that the values work as intended and a failure case to verify failure?

And add the entry to config-example.yaml with some docs (and linking to the tls doc)

@@ -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")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This default is different than the current setting, how come?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please correct me if I'm wrong, but I think Tailscale doesn't support client certificates right now.

The current default, RequireAnyClientCert, prevents connections over TLS unless any cert is provided by the client.

If I'm not overlooking something, then this means that Tailscale can't interact with Headscale when it's configured to use TLS.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm not overlooking something, then this means that Tailscale can't interact with Headscale when it's configured to use TLS.

Hmm, I am not sure what this means? I am running my headscale with TLS?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How curious! All of my browsers throw a bad ssl error unless a client certificate is supplied, then it works as expected.

I'm using the config parameters to point at cert values for letsencrypt files I already have on hand.

I believe issue #254 reflects the same outcome.

Do we have a misconfiguration somewhere or something?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, happy to make it configurable so users with this problem can at least test them, however, there are many successful users (me included) and I think think there is a reason to change the default for everyone.

My current setup has tls set in nginx, but a week ago, I used the letsencrypt built in to headscale.

So yep, let's get it in, but, we should not change the default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed! Sorry for the confusion there.

Should have things buttoned up tonight/early tomorrow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alrighty, the latest commit preserves the original default.

app.go Show resolved Hide resolved
docs/tls.md Outdated
the following values to the `tls_client_auth_mode` setting in the configuration
file.

| Value | Behavior |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file needs to be ran through prettier

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has now been ran through prettier. My bad on the oversight.

app.go Outdated
@@ -644,12 +645,34 @@ 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://")
}

var clientAuthMode tls.ClientAuthType
if h.cfg.TLSClientAuthMode == "disabled" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a switch statement, and the alternatives should be Constants.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. I'm new to Go, so thank you for this critique. Much less typing in a switch statement.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the latest commit, I moved this logic to a new function to make testing a bit simpler and implemented a switch statement.

Copy link
Collaborator

@kradalby kradalby left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking like its get there, I think this might be a way to simplify the flow a bit and remove some redundant checks.

app.go Outdated
TLSKeyPath string
TLSCertPath string
TLSKeyPath string
TLSClientAuthMode string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I would argue that you can just use the tls.ClientAuthType type here.

app.go Outdated
Comment on lines 658 to 661
clientAuthMode, err := h.GetClientAuthMode()
if err != nil {
return nil, err
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Then remove this ( or cut it for later )

app.go Outdated
tlsConfig := &tls.Config{
ClientAuth: tls.RequireAnyClientCert,
ClientAuth: clientAuthMode,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Then do this:
Suggested change
ClientAuth: clientAuthMode,
ClientAuth: h.cfg.TLSClientAuthMode,

Comment on lines 90 to 93
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."
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Then instead here call the h.GetClientAuthMode function and check the error instead of having that if.

TLSKeyPath: absPath(viper.GetString("tls_key_path")),
TLSCertPath: absPath(viper.GetString("tls_cert_path")),
TLSKeyPath: absPath(viper.GetString("tls_key_path")),
TLSClientAuthMode: viper.GetString("tls_client_auth_mode"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Then just set it here if there was no error in 4.:
Suggested change
TLSClientAuthMode: viper.GetString("tls_client_auth_mode"),
TLSClientAuthMode: clientAuthMode,

# - disabled: client authentication disabled
# - relaxed: client certificate is required but not verified
# - enforced: client certificate is required and verified
tls_client_auth_mode: disabled
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be relaxed to reflect the default.

Suggested change
tls_client_auth_mode: disabled
tls_client_auth_mode: relaxed

docs/tls.md Outdated
### 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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate wording? by by applying one of

@kradalby
Copy link
Collaborator

@Arch4ngel Looks good, last few things is just make the linters happy, and add a line to the changelog under TBD.

Good job :)

@ImpostorKeanu
Copy link
Contributor Author

@Arch4ngel Looks good, last few things is just make the linters happy, and add a line to the changelog under TBD.

Good job :)

Thanks for your patience on this! Work consumed me for a few weeks.

@kradalby
Copy link
Collaborator

Need another run of prettier and then I'll approve, good work, sorry for all the nits ;)

@kradalby kradalby merged commit 5596a0a into juanfont:main Feb 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants