Skip to content

Commit

Permalink
Add a 'tls_letsencrypt_listen' config option
Browse files Browse the repository at this point in the history
Currently the default (and non-configurable) Let's Encrypt listener will
bind to all IPs. This isn't ideal if we want to run headscale on a specific
IP only.

This also allows for one to set the listener to something other than
port 80. This is useful for OSs like OpenBSD which only allow root to
bind the lower port ranges (and don't have `setcap`) as we can now run
`headscale` as a non-privileged user while still using the baked in ACME
magic. Obviously this configuration would also require a reverse proxy
or firewall rule to redirect traffic. I attempted to outline that in the
README change.
  • Loading branch information
qbit committed Jul 23, 2021
1 parent 1af9c11 commit 69d77f6
Show file tree
Hide file tree
Showing 6 changed files with 9 additions and 1 deletion.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,14 @@ Headscale can be configured to expose its web service via TLS. To configure the
```
"tls_letsencrypt_hostname": "",
"tls_letsencrypt_listen": ":http",
"tls_letsencrypt_cache_dir": ".cache",
"tls_letsencrypt_challenge_type": "HTTP-01",
```
To get a certificate automatically via [Let's Encrypt](https://letsencrypt.org/), set `tls_letsencrypt_hostname` to the desired certificate hostname. This name must resolve to the IP address(es) Headscale is reachable on (i.e., it must correspond to the `server_url` configuration parameter). The certificate and Let's Encrypt account credentials will be stored in the directory configured in `tls_letsencrypt_cache_dir`. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. The certificate will automatically be renewed as needed. The default challenge type HTTP-01 requires that Headscale listens on port 80 for the Let's Encrypt automated validation, in addition to whatever port is configured in `listen_addr`. Alternatively, `tls_letsencrypt_challenge_type` can be set to `TLS-ALPN-01`. In this configuration, Headscale must be reachable via port 443, but port 80 is not required.

If you need to change the ip/port used for the Let's Encrypt process, set `tls_letsencrypt_listen` to the appropriate value. This can be handy if you are running `headscale` as non-root (or can't run `setcap`). Keep in mind, however, Let's Encrypt will _only_ connect to port 80, so if you change `tls_letsencrypt_listen` you will also need to configure something else to send the traffic to the port you specify!
### Policy ACLs
Expand Down
3 changes: 2 additions & 1 deletion app.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Config struct {
DBuser string
DBpass string

TLSLetsEncryptListen string
TLSLetsEncryptHostname string
TLSLetsEncryptCacheDir string
TLSLetsEncryptChallengeType string
Expand Down Expand Up @@ -171,7 +172,7 @@ func (h *Headscale) Serve() error {
// port 80 for the certificate validation in addition to the headscale
// service, which can be configured to run on any other port.
go func() {
log.Fatal(http.ListenAndServe(":http", m.HTTPHandler(http.HandlerFunc(h.redirect))))
log.Fatal(http.ListenAndServe(h.cfg.TLSLetsEncryptListen, m.HTTPHandler(http.HandlerFunc(h.redirect))))
}()
err = s.ListenAndServeTLS("", "")
} else {
Expand Down
1 change: 1 addition & 0 deletions cmd/headscale/cli/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func getHeadscaleApp() (*headscale.Headscale, error) {
DBpass: viper.GetString("db_pass"),

TLSLetsEncryptHostname: viper.GetString("tls_letsencrypt_hostname"),
TLSLetsEncryptListen: viper.GetString("tls_letsencrypt_listen"),
TLSLetsEncryptCacheDir: absPath(viper.GetString("tls_letsencrypt_cache_dir")),
TLSLetsEncryptChallengeType: viper.GetString("tls_letsencrypt_challenge_type"),

Expand Down
2 changes: 2 additions & 0 deletions cmd/headscale/headscale_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func (*Suite) TestPostgresConfigLoading(c *check.C) {
c.Assert(viper.GetString("db_type"), check.Equals, "postgres")
c.Assert(viper.GetString("db_port"), check.Equals, "5432")
c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http")
c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01")
}

Expand Down Expand Up @@ -89,6 +90,7 @@ func (*Suite) TestSqliteConfigLoading(c *check.C) {
c.Assert(viper.GetString("db_type"), check.Equals, "sqlite3")
c.Assert(viper.GetString("db_path"), check.Equals, "db.sqlite")
c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http")
c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01")
}

Expand Down
1 change: 1 addition & 0 deletions config.json.postgres.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"db_user": "foo",
"db_pass": "bar",
"tls_letsencrypt_hostname": "",
"tls_letsencrypt_listen": ":http",
"tls_letsencrypt_cache_dir": ".cache",
"tls_letsencrypt_challenge_type": "HTTP-01",
"tls_cert_path": "",
Expand Down
1 change: 1 addition & 0 deletions config.json.sqlite.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"db_type": "sqlite3",
"db_path": "db.sqlite",
"tls_letsencrypt_hostname": "",
"tls_letsencrypt_listen": ":http",
"tls_letsencrypt_cache_dir": ".cache",
"tls_letsencrypt_challenge_type": "HTTP-01",
"tls_cert_path": "",
Expand Down

0 comments on commit 69d77f6

Please sign in to comment.