From 18b00b5d8d3ee98fa0f6539e78b5a980551f5922 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Tue, 19 Oct 2021 20:51:43 +0200 Subject: [PATCH 01/70] Add support for Split DNS (implements #179) --- app.go | 4 +++- cmd/headscale/cli/utils.go | 27 +++++++++++++++++++++++++++ docs/DNS.md | 36 +++++++++++++++++++++--------------- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/app.go b/app.go index 864ae16a..0576e7f2 100644 --- a/app.go +++ b/app.go @@ -113,7 +113,9 @@ func NewHeadscale(cfg Config) (*Headscale, error) { if err != nil { return nil, err } - h.cfg.DNSConfig.Routes = make(map[string][]dnstype.Resolver) + if h.cfg.DNSConfig.Routes == nil { // we might have routes already from Split DNS + h.cfg.DNSConfig.Routes = make(map[string][]dnstype.Resolver) + } for _, d := range magicDNSDomains { h.cfg.DNSConfig.Routes[d.WithoutTrailingDot()] = nil } diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 95555e94..7b7d84ad 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -104,6 +104,33 @@ func GetDNSConfig() (*tailcfg.DNSConfig, string) { dnsConfig.Nameservers = nameservers dnsConfig.Resolvers = resolvers } + + if viper.IsSet("dns_config.restricted_nameservers") { + if len(dnsConfig.Nameservers) > 0 { + dnsConfig.Routes = make(map[string][]dnstype.Resolver) + restrictedDNS := viper.GetStringMapStringSlice("dns_config.restricted_nameservers") + for domain, resNameservers := range restrictedDNS { + resResolvers := make([]dnstype.Resolver, len(resNameservers)) + for index, nameserverStr := range resNameservers { + nameserver, err := netaddr.ParseIP(nameserverStr) + if err != nil { + log.Error(). + Str("func", "getDNSConfig"). + Err(err). + Msgf("Could not parse restricted nameserver IP: %s", nameserverStr) + } + resResolvers[index] = dnstype.Resolver{ + Addr: nameserver.String(), + } + } + dnsConfig.Routes[domain] = resResolvers + } + } else { + log.Warn(). + Msg("Warning: dns_config.restricted_nameservers is set, but no nameservers are configured. Ignoring restricted_nameservers.") + } + } + if viper.IsSet("dns_config.domains") { dnsConfig.Domains = viper.GetStringSlice("dns_config.domains") } diff --git a/docs/DNS.md b/docs/DNS.md index 85bf9f44..948f3c79 100644 --- a/docs/DNS.md +++ b/docs/DNS.md @@ -11,23 +11,29 @@ Long story short, you can define the DNS servers you want to use in your tailnet ## Configuration reference -The setup is done via the `config.json` file, under the `dns_config` key. +The setup is done via the `config.yaml` file, under the `dns_config` key. -```json -{ - "server_url": "http://127.0.0.1:8001", - "listen_addr": "0.0.0.0:8001", - "private_key_path": "private.key", - //... - "dns_config": { - "nameservers": ["1.1.1.1", "8.8.8.8"], - "domains": [], - "magic_dns": true, - "base_domain": "example.com" - } -} +```yaml +server_url: http://127.0.0.1:8001 +listen_addr: 0.0.0.0:8001 +private_key_path: private.key +dns_config: + nameservers: + - 1.1.1.1 + - 8.8.8.8 + restricted_nameservers: + foo.bar.com: + - 1.1.1.1 + darp.headscale.net: + - 1.1.1.1 + - 8.8.8.8 + domains: [] + magic_dns: true + base_domain: example.com ``` + - `nameservers`: The list of DNS servers to use. - `domains`: Search domains to inject. - `magic_dns`: Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/). Only works if there is at least a nameserver defined. -- `base_domain`: Defines the base domain to create the hostnames for MagicDNS. `base_domain` must be a FQDNs, without the trailing dot. The FQDN of the hosts will be `hostname.namespace.base_domain` (e.g., _myhost.mynamespace.example.com_). \ No newline at end of file +- `base_domain`: Defines the base domain to create the hostnames for MagicDNS. `base_domain` must be a FQDNs, without the trailing dot. The FQDN of the hosts will be `hostname.namespace.base_domain` (e.g., _myhost.mynamespace.example.com_). +- `restricted_nameservers`: Also known as Split DNS (see https://tailscale.com/kb/1054/dns/), list of search domains and the DNS you want to use for them. \ No newline at end of file From 7bb354117be2b0838a45c23dc0e9e85c068ee63f Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Tue, 19 Oct 2021 20:59:33 +0200 Subject: [PATCH 02/70] Move README documentation to doc/ --- README.md | 215 +----------------------------------------- docs/Configuration.md | 97 +++++++++++++++++++ docs/Running.md | 126 +++++++++++++++++++++++++ 3 files changed, 225 insertions(+), 213 deletions(-) create mode 100644 docs/Configuration.md create mode 100644 docs/Running.md diff --git a/README.md b/README.md index b948a40f..c86955b2 100644 --- a/README.md +++ b/README.md @@ -47,219 +47,6 @@ Headscale implements this coordination server. Suggestions/PRs welcomed! -## Running it - -1. Download the Headscale binary https://github.com/juanfont/headscale/releases, and place it somewhere in your PATH or use the docker container - - ```shell - docker pull headscale/headscale:x.x.x - ``` - - - -2. (Optional, you can also use SQLite) Get yourself a PostgreSQL DB running - - ```shell - docker run --name headscale -e POSTGRES_DB=headscale -e \ - POSTGRES_USER=foo -e POSTGRES_PASSWORD=bar -p 5432:5432 -d postgres - ``` - -3. Set some stuff up (headscale Wireguard keys & the config.json file) - - ```shell - wg genkey > private.key - wg pubkey < private.key > public.key # not needed - - # Postgres - cp config.json.postgres.example config.json - # or - # SQLite - cp config.json.sqlite.example config.json - ``` - -4. Create a namespace (a namespace is a 'tailnet', a group of Tailscale nodes that can talk to each other) - - ```shell - headscale namespaces create myfirstnamespace - ``` - - or docker: - - the db.sqlite mount is only needed if you use sqlite - - ```shell - touch db.sqlite - docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8080:8080 headscale/headscale:x.x.x headscale namespaces create myfirstnamespace - ``` - - or if your server is already running in docker: - - ```shell - docker exec headscale create myfirstnamespace - ``` - -5. Run the server - - ```shell - headscale serve - ``` - - or docker: - - the db.sqlite mount is only needed if you use sqlite - - ```shell - docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8080:8080 headscale/headscale:x.x.x headscale serve - ``` - -6. If you used tailscale.com before in your nodes, make sure you clear the tailscald data folder - - ```shell - systemctl stop tailscaled - rm -fr /var/lib/tailscale - systemctl start tailscaled - ``` - -7. Add your first machine - - ```shell - tailscale up --login-server YOUR_HEADSCALE_URL - ``` - -8. Navigate to the URL you will get with `tailscale up`, where you'll find your machine key. - -9. In the server, register your machine to a namespace with the CLI - ```shell - headscale -n myfirstnamespace nodes register YOURMACHINEKEY - ``` - or docker: - ```shell - docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml headscale/headscale:x.x.x headscale -n myfirstnamespace nodes register YOURMACHINEKEY - ``` - or if your server is already running in docker: - ```shell - docker exec headscale -n myfirstnamespace nodes register YOURMACHINEKEY - ``` - -Alternatively, you can use Auth Keys to register your machines: - -1. Create an authkey - - ```shell - headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h - ``` - - or docker: - - ```shell - docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v$(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite headscale/headscale:x.x.x headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h - ``` - - or if your server is already running in docker: - - ```shell - docker exec headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h - ``` - -2. Use the authkey from your machine to register it - ```shell - tailscale up --login-server YOUR_HEADSCALE_URL --authkey YOURAUTHKEY - ``` - -If you create an authkey with the `--ephemeral` flag, that key will create ephemeral nodes. This implies that `--reusable` is true. - -Please bear in mind that all the commands from headscale support adding `-o json` or `-o json-line` to get a nicely JSON-formatted output. - -## Configuration reference - -Headscale's configuration file is named `config.json` or `config.yaml`. Headscale will look for it in `/etc/headscale`, `~/.headscale` and finally the directory from where the Headscale binary is executed. - -``` - "server_url": "http://192.168.1.12:8080", - "listen_addr": "0.0.0.0:8080", - "ip_prefix": "100.64.0.0/10" -``` - -`server_url` is the external URL via which Headscale is reachable. `listen_addr` is the IP address and port the Headscale program should listen on. `ip_prefix` is the IP prefix (range) in which IP addresses for nodes will be allocated (default 100.64.0.0/10, e.g., 192.168.4.0/24, 10.0.0.0/8) - -``` - "log_level": "debug" -``` - -`log_level` can be used to set the Log level for Headscale, it defaults to `debug`, and the available levels are: `trace`, `debug`, `info`, `warn` and `error`. - -``` - "private_key_path": "private.key", -``` - -`private_key_path` is the path to the Wireguard private key. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. - -``` - "derp_map_path": "derp.yaml", -``` - -`derp_map_path` is the path to the [DERP](https://pkg.go.dev/tailscale.com/derp) map file. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. - -``` - "ephemeral_node_inactivity_timeout": "30m", -``` - -`ephemeral_node_inactivity_timeout` is the timeout after which inactive ephemeral node records will be deleted from the database. The default is 30 minutes. This value must be higher than 65 seconds (the keepalive timeout for the HTTP long poll is 60 seconds, plus a few seconds to avoid race conditions). - -``` - "db_host": "localhost", - "db_port": 5432, - "db_name": "headscale", - "db_user": "foo", - "db_pass": "bar", -``` - -The fields starting with `db_` are used for the PostgreSQL connection information. - -### Running the service via TLS (optional) - -``` - "tls_cert_path": "" - "tls_key_path": "" -``` - -Headscale can be configured to expose its web service via TLS. To configure the certificate and key file manually, set the `tls_cert_path` and `tls_cert_path` configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. - -``` - "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. - -#### Challenge type HTTP-01 - -The default challenge type `HTTP-01` requires that Headscale is reachable on port 80 for the Let's Encrypt automated validation, in addition to whatever port is configured in `listen_addr`. By default, Headscale listens on port 80 on all local IPs for Let's Encrypt automated validation. - -If you need to change the ip and/or port used by Headscale for the Let's Encrypt validation process, set `tls_letsencrypt_listen` to the appropriate value. This can be handy if you are running Headscale as a non-root user (or can't run `setcap`). Keep in mind, however, that Let's Encrypt will _only_ connect to port 80 for the validation callback, so if you change `tls_letsencrypt_listen` you will also need to configure something else (e.g. a firewall rule) to forward the traffic from port 80 to the ip:port combination specified in `tls_letsencrypt_listen`. - -#### Challenge type TLS-ALPN-01 - -Alternatively, `tls_letsencrypt_challenge_type` can be set to `TLS-ALPN-01`. In this configuration, Headscale listens on the ip:port combination defined in `listen_addr`. Let's Encrypt will _only_ connect to port 443 for the validation callback, so if `listen_addr` is not set to port 443, something else (e.g. a firewall rule) will be required to forward the traffic from port 443 to the ip:port combination specified in `listen_addr`. - -### Policy ACLs - -Headscale implements the same policy ACLs as Tailscale.com, adapted to the self-hosted environment. - -For instance, instead of referring to users when defining groups you must -use namespaces (which are the equivalent to user/logins in Tailscale.com). - -Please check https://tailscale.com/kb/1018/acls/, and `./tests/acls/` in this repo for working examples. - -### Apple devices - -An endpoint with information on how to connect your Apple devices (currently macOS only) is available at `/apple` on your running instance. ## Disclaimer @@ -271,3 +58,5 @@ An endpoint with information on how to connect your Apple devices (currently mac - https://tailscale.com/blog/how-tailscale-works/ - https://tailscale.com/blog/tailscale-key-management/ - https://tailscale.com/blog/an-unlikely-database-migration/ + + diff --git a/docs/Configuration.md b/docs/Configuration.md new file mode 100644 index 00000000..56791a9a --- /dev/null +++ b/docs/Configuration.md @@ -0,0 +1,97 @@ +# Configuration reference + +Headscale's configuration file is named `config.json` or `config.yaml`. Headscale will look for it in `/etc/headscale`, `~/.headscale` and finally the directory from where the Headscale binary is executed. + +```yaml +server_url: http://192.168.1.12:8080 +listen_addr: 0.0.0.0:8080 +ip_prefix: 100.64.0.0/10 +``` + +`server_url` is the external URL via which Headscale is reachable. `listen_addr` is the IP address and port the Headscale program should listen on. `ip_prefix` is the IP prefix (range) in which IP addresses for nodes will be allocated (default 100.64.0.0/10, e.g., 192.168.4.0/24, 10.0.0.0/8) + +```yaml +log_level: debug +``` + +`log_level` can be used to set the Log level for Headscale, it defaults to `debug`, and the available levels are: `trace`, `debug`, `info`, `warn` and `error`. + +```yaml +private_key_path: private.key +``` + +`private_key_path` is the path to the Wireguard private key. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. + +```yaml +derp_map_path: derp.yaml +``` + +`derp_map_path` is the path to the [DERP](https://pkg.go.dev/tailscale.com/derp) map file. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. + +```yaml +ephemeral_node_inactivity_timeout": "30m" +``` + +`ephemeral_node_inactivity_timeout` is the timeout after which inactive ephemeral node records will be deleted from the database. The default is 30 minutes. This value must be higher than 65 seconds (the keepalive timeout for the HTTP long poll is 60 seconds, plus a few seconds to avoid race conditions). + +```yaml +db_host: localhost +db_port: 5432 +db_name: headscale +db_user: foo +db_pass: bar +``` + +The fields starting with `db_` are used for the PostgreSQL connection information. + +### Running the service via TLS (optional) + +```yaml +tls_cert_path: '' +tls_key_path: '' +``` + +Headscale can be configured to expose its web service via TLS. To configure the certificate and key file manually, set the `tls_cert_path` and `tls_cert_path` configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. + +```yaml +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. + +#### Challenge type HTTP-01 + +The default challenge type `HTTP-01` requires that Headscale is reachable on port 80 for the Let's Encrypt automated validation, in addition to whatever port is configured in `listen_addr`. By default, Headscale listens on port 80 on all local IPs for Let's Encrypt automated validation. + +If you need to change the ip and/or port used by Headscale for the Let's Encrypt validation process, set `tls_letsencrypt_listen` to the appropriate value. This can be handy if you are running Headscale as a non-root user (or can't run `setcap`). Keep in mind, however, that Let's Encrypt will _only_ connect to port 80 for the validation callback, so if you change `tls_letsencrypt_listen` you will also need to configure something else (e.g. a firewall rule) to forward the traffic from port 80 to the ip:port combination specified in `tls_letsencrypt_listen`. + +#### Challenge type TLS-ALPN-01 + +Alternatively, `tls_letsencrypt_challenge_type` can be set to `TLS-ALPN-01`. In this configuration, Headscale listens on the ip:port combination defined in `listen_addr`. Let's Encrypt will _only_ connect to port 443 for the validation callback, so if `listen_addr` is not set to port 443, something else (e.g. a firewall rule) will be required to forward the traffic from port 443 to the ip:port combination specified in `listen_addr`. + +### Policy ACLs + +Headscale implements the same policy ACLs as Tailscale.com, adapted to the self-hosted environment. + +For instance, instead of referring to users when defining groups you must +use namespaces (which are the equivalent to user/logins in Tailscale.com). + +Please check https://tailscale.com/kb/1018/acls/, and `./tests/acls/` in this repo for working examples. + +### Apple devices + +An endpoint with information on how to connect your Apple devices (currently macOS only) is available at `/apple` on your running instance. + +## Disclaimer + +1. We have nothing to do with Tailscale, or Tailscale Inc. +2. The purpose of writing this was to learn how Tailscale works. + +## More on Tailscale + +- https://tailscale.com/blog/how-tailscale-works/ +- https://tailscale.com/blog/tailscale-key-management/ +- https://tailscale.com/blog/an-unlikely-database-migration/ diff --git a/docs/Running.md b/docs/Running.md new file mode 100644 index 00000000..6f165edd --- /dev/null +++ b/docs/Running.md @@ -0,0 +1,126 @@ +# Running headscale + +1. Download the Headscale binary https://github.com/juanfont/headscale/releases, and place it somewhere in your PATH or use the docker container + + ```shell + docker pull headscale/headscale:x.x.x + ``` + + + +2. (Optional, you can also use SQLite) Get yourself a PostgreSQL DB running + + ```shell + docker run --name headscale -e POSTGRES_DB=headscale -e \ + POSTGRES_USER=foo -e POSTGRES_PASSWORD=bar -p 5432:5432 -d postgres + ``` + +3. Set some stuff up (headscale Wireguard keys & the config.json file) + + ```shell + wg genkey > private.key + wg pubkey < private.key > public.key # not needed + + # Postgres + cp config.json.postgres.example config.json + # or + # SQLite + cp config.json.sqlite.example config.json + ``` + +4. Create a namespace (a namespace is a 'tailnet', a group of Tailscale nodes that can talk to each other) + + ```shell + headscale namespaces create myfirstnamespace + ``` + + or docker: + + the db.sqlite mount is only needed if you use sqlite + + ```shell + touch db.sqlite + docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8080:8080 headscale/headscale:x.x.x headscale namespaces create myfirstnamespace + ``` + + or if your server is already running in docker: + + ```shell + docker exec headscale create myfirstnamespace + ``` + +5. Run the server + + ```shell + headscale serve + ``` + + or docker: + + the db.sqlite mount is only needed if you use sqlite + + ```shell + docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8080:8080 headscale/headscale:x.x.x headscale serve + ``` + +6. If you used tailscale.com before in your nodes, make sure you clear the tailscald data folder + + ```shell + systemctl stop tailscaled + rm -fr /var/lib/tailscale + systemctl start tailscaled + ``` + +7. Add your first machine + + ```shell + tailscale up --login-server YOUR_HEADSCALE_URL + ``` + +8. Navigate to the URL you will get with `tailscale up`, where you'll find your machine key. + +9. In the server, register your machine to a namespace with the CLI + ```shell + headscale -n myfirstnamespace nodes register YOURMACHINEKEY + ``` + or docker: + ```shell + docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml headscale/headscale:x.x.x headscale -n myfirstnamespace nodes register YOURMACHINEKEY + ``` + or if your server is already running in docker: + ```shell + docker exec headscale -n myfirstnamespace nodes register YOURMACHINEKEY + ``` + +Alternatively, you can use Auth Keys to register your machines: + +1. Create an authkey + + ```shell + headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h + ``` + + or docker: + + ```shell + docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v$(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite headscale/headscale:x.x.x headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h + ``` + + or if your server is already running in docker: + + ```shell + docker exec headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h + ``` + +2. Use the authkey from your machine to register it + ```shell + tailscale up --login-server YOUR_HEADSCALE_URL --authkey YOURAUTHKEY + ``` + +If you create an authkey with the `--ephemeral` flag, that key will create ephemeral nodes. This implies that `--reusable` is true. + +Please bear in mind that all the commands from headscale support adding `-o json` or `-o json-line` to get a nicely JSON-formatted output. \ No newline at end of file From 2236cc8bf7c3d8a69f27e96295f998fee746b0b8 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Tue, 19 Oct 2021 21:01:36 +0200 Subject: [PATCH 03/70] Improve wording here and there --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c86955b2..06d09d74 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Headscale implements this coordination server. - [x] Base functionality (nodes can communicate with each other) - [x] Node registration through the web flow - [x] Network changes are relayed to the nodes -- [x] Namespace support (~equivalent to multi-user in Tailscale.com) +- [x] Multiple namespaces support (~tailnets in Tailscale.com naming) - [x] Routing (advertise & accept, including exit nodes) - [x] Node registration via pre-auth keys (including reusable keys, and ephemeral node support) - [x] JSON-formatted output @@ -29,7 +29,7 @@ Headscale implements this coordination server. - [x] Taildrop (File Sharing) - [x] Support for alternative IP ranges in the tailnets (default Tailscale's 100.64.0.0/10) - [x] DNS (passing DNS servers to nodes) -- [x] Share nodes between ~~users~~ namespaces +- [x] Share nodes between namespaces - [x] MagicDNS (see `docs/`) ## Client OS support From 995dcfc6aebe520208cfd0f04679803ed17efa05 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Tue, 19 Oct 2021 21:02:45 +0200 Subject: [PATCH 04/70] Reference doc/ --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 06d09d74..83b162a5 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,11 @@ Headscale implements this coordination server. Suggestions/PRs welcomed! +## Running headscale + +Please have a look at the documentation under (`docs/`). + + ## Disclaimer 1. We have nothing to do with Tailscale, or Tailscale Inc. From 0318af5a332b32a6787cee08051ebd1920f2bdac Mon Sep 17 00:00:00 2001 From: Juan Font Date: Tue, 19 Oct 2021 23:22:56 +0200 Subject: [PATCH 05/70] Apply suggestions from code review Co-authored-by: Kristoffer Dalby --- README.md | 2 +- docs/Configuration.md | 8 ++++++-- docs/Running.md | 35 +++++++++++++++++++++++++++++------ 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 83b162a5..94c4b241 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Suggestions/PRs welcomed! ## Running headscale -Please have a look at the documentation under (`docs/`). +Please have a look at the documentation under [`docs/`](docs/). ## Disclaimer diff --git a/docs/Configuration.md b/docs/Configuration.md index 56791a9a..6074f2b4 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -1,9 +1,13 @@ # Configuration reference -Headscale's configuration file is named `config.json` or `config.yaml`. Headscale will look for it in `/etc/headscale`, `~/.headscale` and finally the directory from where the Headscale binary is executed. +Headscale will look for a configuration file named `config.yaml` (or `config.json`) in the following order: + +- `/etc/headscale` +- `~/.headscale` +- current working directory ```yaml -server_url: http://192.168.1.12:8080 +server_url: http://headscale.mydomain.net listen_addr: 0.0.0.0:8080 ip_prefix: 100.64.0.0/10 ``` diff --git a/docs/Running.md b/docs/Running.md index 6f165edd..888ddbfd 100644 --- a/docs/Running.md +++ b/docs/Running.md @@ -19,11 +19,10 @@ POSTGRES_USER=foo -e POSTGRES_PASSWORD=bar -p 5432:5432 -d postgres ``` -3. Set some stuff up (headscale Wireguard keys & the config.json file) +3. Create a WireGuard Private key and headscale configuration ```shell wg genkey > private.key - wg pubkey < private.key > public.key # not needed # Postgres cp config.json.postgres.example config.json @@ -44,7 +43,14 @@ ```shell touch db.sqlite - docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8080:8080 headscale/headscale:x.x.x headscale namespaces create myfirstnamespace + docker run \ + -v $(pwd)/private.key:/private.key \ + -v $(pwd)/config.json:/config.json \ + -v $(pwd)/derp.yaml:/derp.yaml \ + -v $(pwd)/db.sqlite:/db.sqlite \ + -p 127.0.0.1:8080:8080 \ + headscale/headscale:x.x.x \ + headscale namespaces create myfirstnamespace ``` or if your server is already running in docker: @@ -64,7 +70,13 @@ the db.sqlite mount is only needed if you use sqlite ```shell - docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite -p 127.0.0.1:8080:8080 headscale/headscale:x.x.x headscale serve + docker run \ + -v $(pwd)/private.key:/private.key \ + -v $(pwd)/config.json:/config.json \ + -v $(pwd)/derp.yaml:/derp.yaml \ + -v $(pwd)/db.sqlite:/db.sqlite \ + -p 127.0.0.1:8080:8080 \ + headscale/headscale:x.x.x headscale serve ``` 6. If you used tailscale.com before in your nodes, make sure you clear the tailscald data folder @@ -89,7 +101,12 @@ ``` or docker: ```shell - docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v $(pwd)/derp.yaml:/derp.yaml headscale/headscale:x.x.x headscale -n myfirstnamespace nodes register YOURMACHINEKEY + docker run \ + -v $(pwd)/private.key:/private.key \ + -v $(pwd)/config.json:/config.json \ + -v $(pwd)/derp.yaml:/derp.yaml \ + headscale/headscale:x.x.x \ + headscale -n myfirstnamespace nodes register YOURMACHINEKEY ``` or if your server is already running in docker: ```shell @@ -107,7 +124,13 @@ Alternatively, you can use Auth Keys to register your machines: or docker: ```shell - docker run -v $(pwd)/private.key:/private.key -v $(pwd)/config.json:/config.json -v$(pwd)/derp.yaml:/derp.yaml -v $(pwd)/db.sqlite:/db.sqlite headscale/headscale:x.x.x headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h + docker run \ + -v $(pwd)/private.key:/private.key \ + -v $(pwd)/config.json:/config.json \ + -v$(pwd)/derp.yaml:/derp.yaml \ + -v $(pwd)/db.sqlite:/db.sqlite \ + headscale/headscale:x.x.x \ + headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h ``` or if your server is already running in docker: From 06706aab9a408f8780aefee2c43f3d5d0814ff24 Mon Sep 17 00:00:00 2001 From: Juan Font Date: Tue, 19 Oct 2021 23:41:08 +0200 Subject: [PATCH 06/70] Update docs/Running.md Co-authored-by: Kristoffer Dalby --- docs/Running.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Running.md b/docs/Running.md index 888ddbfd..6445f678 100644 --- a/docs/Running.md +++ b/docs/Running.md @@ -31,7 +31,7 @@ cp config.json.sqlite.example config.json ``` -4. Create a namespace (a namespace is a 'tailnet', a group of Tailscale nodes that can talk to each other) +4. Create a namespace ```shell headscale namespaces create myfirstnamespace From 7b40e99aec28ef0a06457712f6f758a27a9c5665 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Tue, 19 Oct 2021 23:45:20 +0200 Subject: [PATCH 07/70] Added notes on SQLite --- docs/Configuration.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index 6074f2b4..a9286778 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -38,6 +38,7 @@ ephemeral_node_inactivity_timeout": "30m" `ephemeral_node_inactivity_timeout` is the timeout after which inactive ephemeral node records will be deleted from the database. The default is 30 minutes. This value must be higher than 65 seconds (the keepalive timeout for the HTTP long poll is 60 seconds, plus a few seconds to avoid race conditions). +PostgresSQL ```yaml db_host: localhost db_port: 5432 @@ -46,7 +47,13 @@ db_user: foo db_pass: bar ``` -The fields starting with `db_` are used for the PostgreSQL connection information. +SQLite +```yaml +db_type: sqlite3 +db_path: db.sqlite +``` + +The fields starting with `db_` are used for the DB connection information. ### Running the service via TLS (optional) From d1e8ac7ba5f1a8f748f87465bb66be61af48b5f4 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Wed, 20 Oct 2021 00:07:05 +0200 Subject: [PATCH 08/70] Moved TLS config to another file --- docs/Configuration.md | 37 ++++--------------------------------- docs/TLS.md | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 33 deletions(-) create mode 100644 docs/TLS.md diff --git a/docs/Configuration.md b/docs/Configuration.md index a9286778..a293d358 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -55,33 +55,15 @@ db_path: db.sqlite The fields starting with `db_` are used for the DB connection information. -### Running the service via TLS (optional) +### TLS configuration -```yaml -tls_cert_path: '' -tls_key_path: '' -``` +Please check [`TLS.md`](TLS.md). -Headscale can be configured to expose its web service via TLS. To configure the certificate and key file manually, set the `tls_cert_path` and `tls_cert_path` configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. -```yaml -tls_letsencrypt_hostname: '' -tls_letsencrypt_listen: ":http" -tls_letsencrypt_cache_dir: ".cache" -tls_letsencrypt_challenge_type: HTTP-01 -``` +### DNS configuration -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. +Please refer to [`DNS.md`](DNS.md). -#### Challenge type HTTP-01 - -The default challenge type `HTTP-01` requires that Headscale is reachable on port 80 for the Let's Encrypt automated validation, in addition to whatever port is configured in `listen_addr`. By default, Headscale listens on port 80 on all local IPs for Let's Encrypt automated validation. - -If you need to change the ip and/or port used by Headscale for the Let's Encrypt validation process, set `tls_letsencrypt_listen` to the appropriate value. This can be handy if you are running Headscale as a non-root user (or can't run `setcap`). Keep in mind, however, that Let's Encrypt will _only_ connect to port 80 for the validation callback, so if you change `tls_letsencrypt_listen` you will also need to configure something else (e.g. a firewall rule) to forward the traffic from port 80 to the ip:port combination specified in `tls_letsencrypt_listen`. - -#### Challenge type TLS-ALPN-01 - -Alternatively, `tls_letsencrypt_challenge_type` can be set to `TLS-ALPN-01`. In this configuration, Headscale listens on the ip:port combination defined in `listen_addr`. Let's Encrypt will _only_ connect to port 443 for the validation callback, so if `listen_addr` is not set to port 443, something else (e.g. a firewall rule) will be required to forward the traffic from port 443 to the ip:port combination specified in `listen_addr`. ### Policy ACLs @@ -95,14 +77,3 @@ Please check https://tailscale.com/kb/1018/acls/, and `./tests/acls/` in this re ### Apple devices An endpoint with information on how to connect your Apple devices (currently macOS only) is available at `/apple` on your running instance. - -## Disclaimer - -1. We have nothing to do with Tailscale, or Tailscale Inc. -2. The purpose of writing this was to learn how Tailscale works. - -## More on Tailscale - -- https://tailscale.com/blog/how-tailscale-works/ -- https://tailscale.com/blog/tailscale-key-management/ -- https://tailscale.com/blog/an-unlikely-database-migration/ diff --git a/docs/TLS.md b/docs/TLS.md new file mode 100644 index 00000000..c4a7dfad --- /dev/null +++ b/docs/TLS.md @@ -0,0 +1,31 @@ + +# Running the service via TLS (optional) + +```yaml +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. + + +```yaml +tls_cert_path: '' +tls_key_path: '' +``` + +Headscale can also be configured to expose its web service via TLS. To configure the certificate and key file manually, set the `tls_cert_path` and `tls_cert_path` configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. + + +## Challenge type HTTP-01 + +The default challenge type `HTTP-01` requires that Headscale is reachable on port 80 for the Let's Encrypt automated validation, in addition to whatever port is configured in `listen_addr`. By default, Headscale listens on port 80 on all local IPs for Let's Encrypt automated validation. + +If you need to change the ip and/or port used by Headscale for the Let's Encrypt validation process, set `tls_letsencrypt_listen` to the appropriate value. This can be handy if you are running Headscale as a non-root user (or can't run `setcap`). Keep in mind, however, that Let's Encrypt will _only_ connect to port 80 for the validation callback, so if you change `tls_letsencrypt_listen` you will also need to configure something else (e.g. a firewall rule) to forward the traffic from port 80 to the ip:port combination specified in `tls_letsencrypt_listen`. + +## Challenge type TLS-ALPN-01 + +Alternatively, `tls_letsencrypt_challenge_type` can be set to `TLS-ALPN-01`. In this configuration, Headscale listens on the ip:port combination defined in `listen_addr`. Let's Encrypt will _only_ connect to port 443 for the validation callback, so if `listen_addr` is not set to port 443, something else (e.g. a firewall rule) will be required to forward the traffic from port 443 to the ip:port combination specified in `listen_addr`. + From 86ecc2a234a9f44331b1f88c15656335af313fe5 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Wed, 20 Oct 2021 00:17:08 +0200 Subject: [PATCH 09/70] Switch to YAML config --- config.json.postgres.example | 30 ------------------------------ config.json.sqlite.example | 26 -------------------------- config.yaml.example | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 56 deletions(-) delete mode 100644 config.json.postgres.example delete mode 100644 config.json.sqlite.example create mode 100644 config.yaml.example diff --git a/config.json.postgres.example b/config.json.postgres.example deleted file mode 100644 index 9b6f737f..00000000 --- a/config.json.postgres.example +++ /dev/null @@ -1,30 +0,0 @@ -{ - "server_url": "http://127.0.0.1:8080", - "listen_addr": "0.0.0.0:8080", - "private_key_path": "private.key", - "derp_map_path": "derp.yaml", - "ephemeral_node_inactivity_timeout": "30m", - "db_type": "postgres", - "db_host": "localhost", - "db_port": 5432, - "db_name": "headscale", - "db_user": "foo", - "db_pass": "bar", - "acme_url": "https://acme-v02.api.letsencrypt.org/directory", - "acme_email": "", - "tls_letsencrypt_hostname": "", - "tls_letsencrypt_listen": ":http", - "tls_letsencrypt_cache_dir": ".cache", - "tls_letsencrypt_challenge_type": "HTTP-01", - "tls_cert_path": "", - "tls_key_path": "", - "acl_policy_path": "", - "dns_config": { - "nameservers": [ - "1.1.1.1" - ], - "domains": [], - "magic_dns": true, - "base_domain": "example.com" - } -} diff --git a/config.json.sqlite.example b/config.json.sqlite.example deleted file mode 100644 index 74e15902..00000000 --- a/config.json.sqlite.example +++ /dev/null @@ -1,26 +0,0 @@ -{ - "server_url": "http://127.0.0.1:8080", - "listen_addr": "0.0.0.0:8080", - "private_key_path": "private.key", - "derp_map_path": "derp.yaml", - "ephemeral_node_inactivity_timeout": "30m", - "db_type": "sqlite3", - "db_path": "db.sqlite", - "acme_url": "https://acme-v02.api.letsencrypt.org/directory", - "acme_email": "", - "tls_letsencrypt_hostname": "", - "tls_letsencrypt_listen": ":http", - "tls_letsencrypt_cache_dir": ".cache", - "tls_letsencrypt_challenge_type": "HTTP-01", - "tls_cert_path": "", - "tls_key_path": "", - "acl_policy_path": "", - "dns_config": { - "nameservers": [ - "1.1.1.1" - ], - "domains": [], - "magic_dns": true, - "base_domain": "example.com" - } -} diff --git a/config.yaml.example b/config.yaml.example new file mode 100644 index 00000000..6a089085 --- /dev/null +++ b/config.yaml.example @@ -0,0 +1,34 @@ +--- +server_url: http://127.0.0.1:8080 +listen_addr: 0.0.0.0:8080 +private_key_path: private.key +derp_map_path: derp.yaml +ephemeral_node_inactivity_timeout: 30m + +# Postgres config +db_type: postgres +db_host: localhost +db_port: 5432 +db_name: headscale +db_user: foo +db_pass: bar + +# SQLite config (uncomment it if you want to use SQLite) +# db_type: sqlite3 +# db_path: db.sqlite + +acme_url: https://acme-v02.api.letsencrypt.org/directory +acme_email: '' +tls_letsencrypt_hostname: '' +tls_letsencrypt_listen: ":http" +tls_letsencrypt_cache_dir: ".cache" +tls_letsencrypt_challenge_type: HTTP-01 +tls_cert_path: '' +tls_key_path: '' +acl_policy_path: '' +dns_config: + nameservers: + - 1.1.1.1 + domains: [] + magic_dns: true + base_domain: example.com From 31344128a012b722b763baabf4bf8ea88282c882 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Wed, 20 Oct 2021 00:17:47 +0200 Subject: [PATCH 10/70] Switch json for yaml in README --- docs/Running.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/Running.md b/docs/Running.md index 6445f678..1df66685 100644 --- a/docs/Running.md +++ b/docs/Running.md @@ -24,11 +24,7 @@ ```shell wg genkey > private.key - # Postgres - cp config.json.postgres.example config.json - # or - # SQLite - cp config.json.sqlite.example config.json + cp config.yaml.example config.yaml ``` 4. Create a namespace From 41c5a0ddf56637c582098f220c06bb3180ef8799 Mon Sep 17 00:00:00 2001 From: Juan Font Date: Wed, 20 Oct 2021 09:35:56 +0200 Subject: [PATCH 11/70] Apply suggestions from code review Co-authored-by: Kristoffer Dalby --- app.go | 3 ++- cmd/headscale/cli/utils.go | 10 +++++----- docs/DNS.md | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app.go b/app.go index 0576e7f2..66e2a306 100644 --- a/app.go +++ b/app.go @@ -113,7 +113,8 @@ func NewHeadscale(cfg Config) (*Headscale, error) { if err != nil { return nil, err } - if h.cfg.DNSConfig.Routes == nil { // we might have routes already from Split DNS + // we might have routes already from Split DNS + if h.cfg.DNSConfig.Routes == nil { h.cfg.DNSConfig.Routes = make(map[string][]dnstype.Resolver) } for _, d := range magicDNSDomains { diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 7b7d84ad..52c8d043 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -109,9 +109,9 @@ func GetDNSConfig() (*tailcfg.DNSConfig, string) { if len(dnsConfig.Nameservers) > 0 { dnsConfig.Routes = make(map[string][]dnstype.Resolver) restrictedDNS := viper.GetStringMapStringSlice("dns_config.restricted_nameservers") - for domain, resNameservers := range restrictedDNS { - resResolvers := make([]dnstype.Resolver, len(resNameservers)) - for index, nameserverStr := range resNameservers { + for domain, restrictedNameservers := range restrictedDNS { + restrictedResolvers := make([]dnstype.Resolver, len(restrictedNameservers)) + for index, nameserverStr := range restrictedNameservers { nameserver, err := netaddr.ParseIP(nameserverStr) if err != nil { log.Error(). @@ -119,11 +119,11 @@ func GetDNSConfig() (*tailcfg.DNSConfig, string) { Err(err). Msgf("Could not parse restricted nameserver IP: %s", nameserverStr) } - resResolvers[index] = dnstype.Resolver{ + restrictedResolvers[index] = dnstype.Resolver{ Addr: nameserver.String(), } } - dnsConfig.Routes[domain] = resResolvers + dnsConfig.Routes[domain] = restrictedResolvers } } else { log.Warn(). diff --git a/docs/DNS.md b/docs/DNS.md index 948f3c79..10f99b79 100644 --- a/docs/DNS.md +++ b/docs/DNS.md @@ -36,4 +36,4 @@ dns_config: - `domains`: Search domains to inject. - `magic_dns`: Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/). Only works if there is at least a nameserver defined. - `base_domain`: Defines the base domain to create the hostnames for MagicDNS. `base_domain` must be a FQDNs, without the trailing dot. The FQDN of the hosts will be `hostname.namespace.base_domain` (e.g., _myhost.mynamespace.example.com_). -- `restricted_nameservers`: Also known as Split DNS (see https://tailscale.com/kb/1054/dns/), list of search domains and the DNS you want to use for them. \ No newline at end of file +- `restricted_nameservers`: Split DNS (see https://tailscale.com/kb/1054/dns/), list of search domains and the DNS to query for each one. \ No newline at end of file From 5159b6d08586e3b28f448b57495433f9a27e9a48 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Wed, 20 Oct 2021 23:10:59 +0200 Subject: [PATCH 12/70] Trying to fix arm64 --- .goreleaser.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.goreleaser.yml b/.goreleaser.yml index f7355104..7c5e853b 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -60,6 +60,9 @@ builds: - linux goarch: - arm64 + env: + - CGO_ENABLED=1 + - CC=aarch64-linux-gnu-gcc-9 main: ./cmd/headscale/headscale.go mod_timestamp: '{{ .CommitTimestamp }}' ldflags: From 6b0f5da11361f134e32e8442675e8f3f8e923ac5 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Wed, 20 Oct 2021 23:27:59 +0200 Subject: [PATCH 13/70] Separate config examples for sqlite and postgres for the time being --- cmd/headscale/headscale_test.go | 6 ++--- ...ml.example => config.yaml.postgres.example | 4 --- config.yaml.sqlite.example | 26 +++++++++++++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) rename config.yaml.example => config.yaml.postgres.example (86%) create mode 100644 config.yaml.sqlite.example diff --git a/cmd/headscale/headscale_test.go b/cmd/headscale/headscale_test.go index bddea94c..0c3add69 100644 --- a/cmd/headscale/headscale_test.go +++ b/cmd/headscale/headscale_test.go @@ -41,7 +41,7 @@ func (*Suite) TestPostgresConfigLoading(c *check.C) { } // Symlink the example config file - err = os.Symlink(filepath.Clean(path+"/../../config.json.postgres.example"), filepath.Join(tmpDir, "config.json")) + err = os.Symlink(filepath.Clean(path+"/../../config.yaml.postgres.example"), filepath.Join(tmpDir, "config.yaml")) if err != nil { c.Fatal(err) } @@ -74,7 +74,7 @@ func (*Suite) TestSqliteConfigLoading(c *check.C) { } // Symlink the example config file - err = os.Symlink(filepath.Clean(path+"/../../config.json.sqlite.example"), filepath.Join(tmpDir, "config.json")) + err = os.Symlink(filepath.Clean(path+"/../../config.yaml.sqlite.example"), filepath.Join(tmpDir, "config.yaml")) if err != nil { c.Fatal(err) } @@ -108,7 +108,7 @@ func (*Suite) TestDNSConfigLoading(c *check.C) { } // Symlink the example config file - err = os.Symlink(filepath.Clean(path+"/../../config.json.sqlite.example"), filepath.Join(tmpDir, "config.json")) + err = os.Symlink(filepath.Clean(path+"/../../config.yaml.sqlite.example"), filepath.Join(tmpDir, "config.yaml")) if err != nil { c.Fatal(err) } diff --git a/config.yaml.example b/config.yaml.postgres.example similarity index 86% rename from config.yaml.example rename to config.yaml.postgres.example index 6a089085..569b42a9 100644 --- a/config.yaml.example +++ b/config.yaml.postgres.example @@ -13,10 +13,6 @@ db_name: headscale db_user: foo db_pass: bar -# SQLite config (uncomment it if you want to use SQLite) -# db_type: sqlite3 -# db_path: db.sqlite - acme_url: https://acme-v02.api.letsencrypt.org/directory acme_email: '' tls_letsencrypt_hostname: '' diff --git a/config.yaml.sqlite.example b/config.yaml.sqlite.example new file mode 100644 index 00000000..158b1e5b --- /dev/null +++ b/config.yaml.sqlite.example @@ -0,0 +1,26 @@ +--- +server_url: http://127.0.0.1:8080 +listen_addr: 0.0.0.0:8080 +private_key_path: private.key +derp_map_path: derp.yaml +ephemeral_node_inactivity_timeout: 30m + +# SQLite config (uncomment it if you want to use SQLite) +db_type: sqlite3 +db_path: db.sqlite + +acme_url: https://acme-v02.api.letsencrypt.org/directory +acme_email: '' +tls_letsencrypt_hostname: '' +tls_letsencrypt_listen: ":http" +tls_letsencrypt_cache_dir: ".cache" +tls_letsencrypt_challenge_type: HTTP-01 +tls_cert_path: '' +tls_key_path: '' +acl_policy_path: '' +dns_config: + nameservers: + - 1.1.1.1 + domains: [] + magic_dns: true + base_domain: example.com From fb569b04832cb8c54dbc4d20e681697f7c48a6d3 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Thu, 21 Oct 2021 17:47:10 +0200 Subject: [PATCH 14/70] Fixed ARM64 compiler name --- .goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 7c5e853b..ad4fea70 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -62,7 +62,7 @@ builds: - arm64 env: - CGO_ENABLED=1 - - CC=aarch64-linux-gnu-gcc-9 + - CC=aarch64-linux-gnu-gcc main: ./cmd/headscale/headscale.go mod_timestamp: '{{ .CommitTimestamp }}' ldflags: From bc145952d42832b68ec0d26792e6ed23b71410cd Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Thu, 21 Oct 2021 19:56:36 +0200 Subject: [PATCH 15/70] Finally fix arm64 build --- .github/workflows/release.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 37c55a04..7a3df6a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,6 +21,11 @@ jobs: uses: actions/setup-go@v2 with: go-version: 1.16 + + - name: Install dependencies + run: | + sudo apt update + sudo apt install -y gcc-aarch64-linux-gnu - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 From 4c2f84b211fb5a87b7f56532777a60a45e9ef2e7 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Thu, 21 Oct 2021 20:33:58 +0200 Subject: [PATCH 16/70] Add contributors Action --- .github/workflows/contributors.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/contributors.yml diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml new file mode 100644 index 00000000..7bf15498 --- /dev/null +++ b/.github/workflows/contributors.yml @@ -0,0 +1,25 @@ +name: Contributors + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + add-contributors: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: BobAnkh/add-contributors@master + with: + CONTRIBUTOR: '### Contributors' + COLUMN_PER_ROW: '6' + ACCESS_TOKEN: ${{secrets.GITHUB_TOKEN}} + IMG_WIDTH: '100' + FONT_SIZE: '14' + PATH: '/README.md' + COMMIT_MESSAGE: 'docs(README): update contributors' + AVATAR_SHAPE: 'round' \ No newline at end of file From 16a90e799c9cdc787be77e0524dc63a62e28948e Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Thu, 21 Oct 2021 20:36:26 +0200 Subject: [PATCH 17/70] Contributors should be working --- .github/workflows/contributors.yml | 2 +- README.md | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml index 7bf15498..cef2c2e7 100644 --- a/.github/workflows/contributors.yml +++ b/.github/workflows/contributors.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v2 - uses: BobAnkh/add-contributors@master with: - CONTRIBUTOR: '### Contributors' + CONTRIBUTOR: '## Contributors' COLUMN_PER_ROW: '6' ACCESS_TOKEN: ${{secrets.GITHUB_TOKEN}} IMG_WIDTH: '100' diff --git a/README.md b/README.md index 94c4b241..ed65b1e3 100644 --- a/README.md +++ b/README.md @@ -58,10 +58,7 @@ Please have a look at the documentation under [`docs/`](docs/). 1. We have nothing to do with Tailscale, or Tailscale Inc. 2. The purpose of writing this was to learn how Tailscale works. -## More on Tailscale -- https://tailscale.com/blog/how-tailscale-works/ -- https://tailscale.com/blog/tailscale-key-management/ -- https://tailscale.com/blog/an-unlikely-database-migration/ +## Contributors From bb1f17f5af97bff16d23e6d54a3a12356dbab656 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Thu, 21 Oct 2021 20:46:19 +0200 Subject: [PATCH 18/70] Added glossary --- docs/Glossary.md | 3 +++ docs/Running.md | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 docs/Glossary.md diff --git a/docs/Glossary.md b/docs/Glossary.md new file mode 100644 index 00000000..17a39840 --- /dev/null +++ b/docs/Glossary.md @@ -0,0 +1,3 @@ +# Glossary + +- Namespace: Collection of Taiscale nodes that can see each other. In Tailscale.com is called Tailnet. \ No newline at end of file diff --git a/docs/Running.md b/docs/Running.md index 1df66685..784d2226 100644 --- a/docs/Running.md +++ b/docs/Running.md @@ -1,6 +1,6 @@ # Running headscale -1. Download the Headscale binary https://github.com/juanfont/headscale/releases, and place it somewhere in your PATH or use the docker container +1. Download the headscale binary https://github.com/juanfont/headscale/releases, and place it somewhere in your PATH or use the docker container ```shell docker pull headscale/headscale:x.x.x From a0bfad6d6e997cd51ff2dddc83cd8929206eca84 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Thu, 21 Oct 2021 20:48:29 +0200 Subject: [PATCH 19/70] Headscale is not capitalized --- README.md | 4 ++-- docs/DNS.md | 6 +++--- docs/TLS.md | 10 +++++----- k8s/README.md | 12 ++++++------ 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index ed65b1e3..19742d8e 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,14 @@ Everything in Tailscale is Open Source, except the GUI clients for proprietary O The control server works as an exchange point of Wireguard public keys for the nodes in the Tailscale network. It also assigns the IP addresses of the clients, creates the boundaries between each user, enables sharing machines between users, and exposes the advertised routes of your nodes. -Headscale implements this coordination server. +headscale implements this coordination server. ## Status - [x] Base functionality (nodes can communicate with each other) - [x] Node registration through the web flow - [x] Network changes are relayed to the nodes -- [x] Multiple namespaces support (~tailnets in Tailscale.com naming) +- [x] Namespaces support (~tailnets in Tailscale.com naming) - [x] Routing (advertise & accept, including exit nodes) - [x] Node registration via pre-auth keys (including reusable keys, and ephemeral node support) - [x] JSON-formatted output diff --git a/docs/DNS.md b/docs/DNS.md index 10f99b79..184d9039 100644 --- a/docs/DNS.md +++ b/docs/DNS.md @@ -1,12 +1,12 @@ -# DNS in Headscale +# DNS in headscale -Headscale supports Tailscale's DNS configuration and MagicDNS. Please have a look to their KB to better understand what this means: +headscale supports Tailscale's DNS configuration and MagicDNS. Please have a look to their KB to better understand what this means: - https://tailscale.com/kb/1054/dns/ - https://tailscale.com/kb/1081/magicdns/ - https://tailscale.com/blog/2021-09-private-dns-with-magicdns/ -Long story short, you can define the DNS servers you want to use in your tailnets, activate MagicDNS (so you don't have to remember the IP addresses of your nodes), define search domains, as well as predefined hosts. Headscale will inject that settings into your nodes. +Long story short, you can define the DNS servers you want to use in your tailnets, activate MagicDNS (so you don't have to remember the IP addresses of your nodes), define search domains, as well as predefined hosts. headscale will inject that settings into your nodes. ## Configuration reference diff --git a/docs/TLS.md b/docs/TLS.md index c4a7dfad..0133d5c6 100644 --- a/docs/TLS.md +++ b/docs/TLS.md @@ -8,7 +8,7 @@ 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. +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. ```yaml @@ -16,16 +16,16 @@ tls_cert_path: '' tls_key_path: '' ``` -Headscale can also be configured to expose its web service via TLS. To configure the certificate and key file manually, set the `tls_cert_path` and `tls_cert_path` configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. +headscale can also be configured to expose its web service via TLS. To configure the certificate and key file manually, set the `tls_cert_path` and `tls_cert_path` configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. ## Challenge type HTTP-01 -The default challenge type `HTTP-01` requires that Headscale is reachable on port 80 for the Let's Encrypt automated validation, in addition to whatever port is configured in `listen_addr`. By default, Headscale listens on port 80 on all local IPs for Let's Encrypt automated validation. +The default challenge type `HTTP-01` requires that headscale is reachable on port 80 for the Let's Encrypt automated validation, in addition to whatever port is configured in `listen_addr`. By default, headscale listens on port 80 on all local IPs for Let's Encrypt automated validation. -If you need to change the ip and/or port used by Headscale for the Let's Encrypt validation process, set `tls_letsencrypt_listen` to the appropriate value. This can be handy if you are running Headscale as a non-root user (or can't run `setcap`). Keep in mind, however, that Let's Encrypt will _only_ connect to port 80 for the validation callback, so if you change `tls_letsencrypt_listen` you will also need to configure something else (e.g. a firewall rule) to forward the traffic from port 80 to the ip:port combination specified in `tls_letsencrypt_listen`. +If you need to change the ip and/or port used by headscale for the Let's Encrypt validation process, set `tls_letsencrypt_listen` to the appropriate value. This can be handy if you are running headscale as a non-root user (or can't run `setcap`). Keep in mind, however, that Let's Encrypt will _only_ connect to port 80 for the validation callback, so if you change `tls_letsencrypt_listen` you will also need to configure something else (e.g. a firewall rule) to forward the traffic from port 80 to the ip:port combination specified in `tls_letsencrypt_listen`. ## Challenge type TLS-ALPN-01 -Alternatively, `tls_letsencrypt_challenge_type` can be set to `TLS-ALPN-01`. In this configuration, Headscale listens on the ip:port combination defined in `listen_addr`. Let's Encrypt will _only_ connect to port 443 for the validation callback, so if `listen_addr` is not set to port 443, something else (e.g. a firewall rule) will be required to forward the traffic from port 443 to the ip:port combination specified in `listen_addr`. +Alternatively, `tls_letsencrypt_challenge_type` can be set to `TLS-ALPN-01`. In this configuration, headscale listens on the ip:port combination defined in `listen_addr`. Let's Encrypt will _only_ connect to port 443 for the validation callback, so if `listen_addr` is not set to port 443, something else (e.g. a firewall rule) will be required to forward the traffic from port 443 to the ip:port combination specified in `listen_addr`. diff --git a/k8s/README.md b/k8s/README.md index 2f187abb..45574b48 100644 --- a/k8s/README.md +++ b/k8s/README.md @@ -1,7 +1,7 @@ -# Deploying Headscale on Kubernetes +# Deploying headscale on Kubernetes This directory contains [Kustomize](https://kustomize.io) templates that deploy -Headscale in various configurations. +headscale in various configurations. These templates currently support Rancher k3s. Other clusters may require adaptation, especially around volume claims and ingress. @@ -72,10 +72,10 @@ Usage: Available Commands: help Help about any command - namespace Manage the namespaces of Headscale - node Manage the nodes of Headscale - preauthkey Handle the preauthkeys in Headscale - routes Manage the routes of Headscale + namespace Manage the namespaces of headscale + node Manage the nodes of headscale + preauthkey Handle the preauthkeys in headscale + routes Manage the routes of headscale serve Launches the headscale server version Print the version. From 4be0b3f556f8fa66f07da437fcdb7c4dbd026e45 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Thu, 21 Oct 2021 20:54:29 +0200 Subject: [PATCH 20/70] Mention disable check updates in the doc --- docs/Running.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Running.md b/docs/Running.md index 784d2226..310bda2c 100644 --- a/docs/Running.md +++ b/docs/Running.md @@ -1,6 +1,6 @@ # Running headscale -1. Download the headscale binary https://github.com/juanfont/headscale/releases, and place it somewhere in your PATH or use the docker container +1. Download the headscale binary https://github.com/juanfont/headscale/releases, and place it somewhere in your $PATH or use the docker container ```shell docker pull headscale/headscale:x.x.x @@ -19,7 +19,7 @@ POSTGRES_USER=foo -e POSTGRES_PASSWORD=bar -p 5432:5432 -d postgres ``` -3. Create a WireGuard Private key and headscale configuration +3. Create a WireGuard private key and headscale configuration ```shell wg genkey > private.key @@ -75,7 +75,7 @@ headscale/headscale:x.x.x headscale serve ``` -6. If you used tailscale.com before in your nodes, make sure you clear the tailscald data folder +6. If you used tailscale.com before in your nodes, make sure you clear the tailscaled data folder ```shell systemctl stop tailscaled @@ -142,4 +142,4 @@ Alternatively, you can use Auth Keys to register your machines: If you create an authkey with the `--ephemeral` flag, that key will create ephemeral nodes. This implies that `--reusable` is true. -Please bear in mind that all the commands from headscale support adding `-o json` or `-o json-line` to get a nicely JSON-formatted output. \ No newline at end of file +Please bear in mind that all headscale commands support adding `-o json` or `-o json-line` to get nicely JSON-formatted output. \ No newline at end of file From e9ffd366dd84844608b13d1c8a09b9687efca9d2 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Thu, 21 Oct 2021 20:54:41 +0200 Subject: [PATCH 21/70] Improvements here and there --- docs/Configuration.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index a293d358..0d22ace4 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -10,9 +10,10 @@ Headscale will look for a configuration file named `config.yaml` (or `config.jso server_url: http://headscale.mydomain.net listen_addr: 0.0.0.0:8080 ip_prefix: 100.64.0.0/10 +disable_check_updates: false ``` -`server_url` is the external URL via which Headscale is reachable. `listen_addr` is the IP address and port the Headscale program should listen on. `ip_prefix` is the IP prefix (range) in which IP addresses for nodes will be allocated (default 100.64.0.0/10, e.g., 192.168.4.0/24, 10.0.0.0/8) +`server_url` is the external URL via which Headscale is reachable. `listen_addr` is the IP address and port the Headscale program should listen on. `ip_prefix` is the IP prefix (range) in which IP addresses for nodes will be allocated (default 100.64.0.0/10, e.g., 192.168.4.0/24, 10.0.0.0/8). `disable_check_updates` disables the automatic check for updates. ```yaml log_level: debug From 636943c7154c4bf9e9ad03dc2af709e9c6622c1a Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Thu, 21 Oct 2021 20:57:18 +0200 Subject: [PATCH 22/70] Improved docker cmd --- docs/Running.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/Running.md b/docs/Running.md index 310bda2c..c5e0b12f 100644 --- a/docs/Running.md +++ b/docs/Running.md @@ -15,8 +15,12 @@ 2. (Optional, you can also use SQLite) Get yourself a PostgreSQL DB running ```shell - docker run --name headscale -e POSTGRES_DB=headscale -e \ - POSTGRES_USER=foo -e POSTGRES_PASSWORD=bar -p 5432:5432 -d postgres + docker run --name headscale \ + -e POSTGRES_DB=headscale + -e POSTGRES_USER=foo \ + -e POSTGRES_PASSWORD=bar \ + -p 5432:5432 \ + -d postgres ``` 3. Create a WireGuard private key and headscale configuration From b93aa723cbc5c895b4e5973012cfbbf324e4b4f1 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Thu, 21 Oct 2021 20:58:30 +0200 Subject: [PATCH 23/70] Run contributors on merge to master --- .github/workflows/contributors.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml index cef2c2e7..7350148a 100644 --- a/.github/workflows/contributors.yml +++ b/.github/workflows/contributors.yml @@ -4,9 +4,6 @@ on: push: branches: - main - pull_request: - branches: - - main jobs: add-contributors: From 561c15bbe856fe8de4cfb91609b9f0a3ed4fc960 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Thu, 21 Oct 2021 21:01:52 +0200 Subject: [PATCH 24/70] Prettier --- docs/Configuration.md | 4 ++-- docs/DNS.md | 19 +++++++++---------- docs/Glossary.md | 2 +- docs/Running.md | 4 ++-- docs/TLS.md | 10 +++------- 5 files changed, 17 insertions(+), 22 deletions(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index 0d22ace4..fa766428 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -40,6 +40,7 @@ ephemeral_node_inactivity_timeout": "30m" `ephemeral_node_inactivity_timeout` is the timeout after which inactive ephemeral node records will be deleted from the database. The default is 30 minutes. This value must be higher than 65 seconds (the keepalive timeout for the HTTP long poll is 60 seconds, plus a few seconds to avoid race conditions). PostgresSQL + ```yaml db_host: localhost db_port: 5432 @@ -49,6 +50,7 @@ db_pass: bar ``` SQLite + ```yaml db_type: sqlite3 db_path: db.sqlite @@ -60,12 +62,10 @@ The fields starting with `db_` are used for the DB connection information. Please check [`TLS.md`](TLS.md). - ### DNS configuration Please refer to [`DNS.md`](DNS.md). - ### Policy ACLs Headscale implements the same policy ACLs as Tailscale.com, adapted to the self-hosted environment. diff --git a/docs/DNS.md b/docs/DNS.md index 184d9039..e51feaf6 100644 --- a/docs/DNS.md +++ b/docs/DNS.md @@ -8,10 +8,9 @@ headscale supports Tailscale's DNS configuration and MagicDNS. Please have a loo Long story short, you can define the DNS servers you want to use in your tailnets, activate MagicDNS (so you don't have to remember the IP addresses of your nodes), define search domains, as well as predefined hosts. headscale will inject that settings into your nodes. - ## Configuration reference -The setup is done via the `config.yaml` file, under the `dns_config` key. +The setup is done via the `config.yaml` file, under the `dns_config` key. ```yaml server_url: http://127.0.0.1:8001 @@ -19,21 +18,21 @@ listen_addr: 0.0.0.0:8001 private_key_path: private.key dns_config: nameservers: - - 1.1.1.1 - - 8.8.8.8 - restricted_nameservers: - foo.bar.com: - - 1.1.1.1 - darp.headscale.net: - 1.1.1.1 - 8.8.8.8 + restricted_nameservers: + foo.bar.com: + - 1.1.1.1 + darp.headscale.net: + - 1.1.1.1 + - 8.8.8.8 domains: [] magic_dns: true base_domain: example.com ``` -- `nameservers`: The list of DNS servers to use. +- `nameservers`: The list of DNS servers to use. - `domains`: Search domains to inject. - `magic_dns`: Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/). Only works if there is at least a nameserver defined. - `base_domain`: Defines the base domain to create the hostnames for MagicDNS. `base_domain` must be a FQDNs, without the trailing dot. The FQDN of the hosts will be `hostname.namespace.base_domain` (e.g., _myhost.mynamespace.example.com_). -- `restricted_nameservers`: Split DNS (see https://tailscale.com/kb/1054/dns/), list of search domains and the DNS to query for each one. \ No newline at end of file +- `restricted_nameservers`: Split DNS (see https://tailscale.com/kb/1054/dns/), list of search domains and the DNS to query for each one. diff --git a/docs/Glossary.md b/docs/Glossary.md index 17a39840..74ecfb89 100644 --- a/docs/Glossary.md +++ b/docs/Glossary.md @@ -1,3 +1,3 @@ # Glossary -- Namespace: Collection of Taiscale nodes that can see each other. In Tailscale.com is called Tailnet. \ No newline at end of file +- Namespace: Collection of Taiscale nodes that can see each other. In Tailscale.com is called Tailnet. diff --git a/docs/Running.md b/docs/Running.md index c5e0b12f..08373653 100644 --- a/docs/Running.md +++ b/docs/Running.md @@ -16,7 +16,7 @@ ```shell docker run --name headscale \ - -e POSTGRES_DB=headscale + -e POSTGRES_DB=headscale -e POSTGRES_USER=foo \ -e POSTGRES_PASSWORD=bar \ -p 5432:5432 \ @@ -146,4 +146,4 @@ Alternatively, you can use Auth Keys to register your machines: If you create an authkey with the `--ephemeral` flag, that key will create ephemeral nodes. This implies that `--reusable` is true. -Please bear in mind that all headscale commands support adding `-o json` or `-o json-line` to get nicely JSON-formatted output. \ No newline at end of file +Please bear in mind that all headscale commands support adding `-o json` or `-o json-line` to get nicely JSON-formatted output. diff --git a/docs/TLS.md b/docs/TLS.md index 0133d5c6..47de1cd7 100644 --- a/docs/TLS.md +++ b/docs/TLS.md @@ -1,8 +1,7 @@ - # Running the service via TLS (optional) ```yaml -tls_letsencrypt_hostname: '' +tls_letsencrypt_hostname: "" tls_letsencrypt_listen: ":http" tls_letsencrypt_cache_dir: ".cache" tls_letsencrypt_challenge_type: HTTP-01 @@ -10,15 +9,13 @@ 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. - ```yaml -tls_cert_path: '' -tls_key_path: '' +tls_cert_path: "" +tls_key_path: "" ``` headscale can also be configured to expose its web service via TLS. To configure the certificate and key file manually, set the `tls_cert_path` and `tls_cert_path` configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. - ## Challenge type HTTP-01 The default challenge type `HTTP-01` requires that headscale is reachable on port 80 for the Let's Encrypt automated validation, in addition to whatever port is configured in `listen_addr`. By default, headscale listens on port 80 on all local IPs for Let's Encrypt automated validation. @@ -28,4 +25,3 @@ If you need to change the ip and/or port used by headscale for the Let's Encrypt ## Challenge type TLS-ALPN-01 Alternatively, `tls_letsencrypt_challenge_type` can be set to `TLS-ALPN-01`. In this configuration, headscale listens on the ip:port combination defined in `listen_addr`. Let's Encrypt will _only_ connect to port 443 for the validation callback, so if `listen_addr` is not set to port 443, something else (e.g. a firewall rule) will be required to forward the traffic from port 443 to the ip:port combination specified in `listen_addr`. - From 672d8474b943aa4979844c442be3b790dcfbc04a Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Thu, 21 Oct 2021 21:18:50 +0200 Subject: [PATCH 25/70] PRettier on the yamls --- .github/workflows/build.yml | 2 +- .github/workflows/contributors.yml | 22 +++++++++++----------- .github/workflows/release.yml | 28 ++++++++++------------------ .goreleaser.yml | 15 +++++++-------- 4 files changed, 29 insertions(+), 38 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 90c48e92..f1773af9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,4 +33,4 @@ jobs: - uses: actions/upload-artifact@v2 with: name: headscale-linux - path: headscale \ No newline at end of file + path: headscale diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml index 7350148a..248cf3f7 100644 --- a/.github/workflows/contributors.yml +++ b/.github/workflows/contributors.yml @@ -9,14 +9,14 @@ jobs: add-contributors: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: BobAnkh/add-contributors@master - with: - CONTRIBUTOR: '## Contributors' - COLUMN_PER_ROW: '6' - ACCESS_TOKEN: ${{secrets.GITHUB_TOKEN}} - IMG_WIDTH: '100' - FONT_SIZE: '14' - PATH: '/README.md' - COMMIT_MESSAGE: 'docs(README): update contributors' - AVATAR_SHAPE: 'round' \ No newline at end of file + - uses: actions/checkout@v2 + - uses: BobAnkh/add-contributors@master + with: + CONTRIBUTOR: "## Contributors" + COLUMN_PER_ROW: "6" + ACCESS_TOKEN: ${{secrets.GITHUB_TOKEN}} + IMG_WIDTH: "100" + FONT_SIZE: "14" + PATH: "/README.md" + COMMIT_MESSAGE: "docs(README): update contributors" + AVATAR_SHAPE: "round" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7a3df6a0..c0605f10 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,20 +4,18 @@ name: release on: push: tags: - - "*" # triggers only if push new tag version + - "*" # triggers only if push new tag version workflow_dispatch: jobs: goreleaser: - runs-on: ubuntu-18.04 # due to CGO we need to user an older version + runs-on: ubuntu-18.04 # due to CGO we need to user an older version steps: - - - name: Checkout + - name: Checkout uses: actions/checkout@v2 with: fetch-depth: 0 - - - name: Set up Go + - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.16 @@ -26,8 +24,7 @@ jobs: run: | sudo apt update sudo apt install -y gcc-aarch64-linux-gnu - - - name: Run GoReleaser + - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 with: distribution: goreleaser @@ -39,13 +36,11 @@ jobs: docker-release: runs-on: ubuntu-latest steps: - - - name: Checkout + - name: Checkout uses: actions/checkout@v2 with: fetch-depth: 0 - - - name: Docker meta + - name: Docker meta id: meta uses: docker/metadata-action@v3 with: @@ -58,21 +53,18 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=sha - - - name: Login to DockerHub + - name: Login to DockerHub uses: docker/login-action@v1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Login to GHCR + - name: Login to GHCR uses: docker/login-action@v1 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push + - name: Build and push id: docker_build uses: docker/build-push-action@v2 with: diff --git a/.goreleaser.yml b/.goreleaser.yml index ad4fea70..d20aed6b 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -6,7 +6,7 @@ before: builds: - id: darwin-amd64 main: ./cmd/headscale/headscale.go - mod_timestamp: '{{ .CommitTimestamp }}' + mod_timestamp: "{{ .CommitTimestamp }}" goos: - darwin goarch: @@ -23,7 +23,7 @@ builds: - id: linux-armhf main: ./cmd/headscale/headscale.go - mod_timestamp: '{{ .CommitTimestamp }}' + mod_timestamp: "{{ .CommitTimestamp }}" goos: - linux goarch: @@ -42,7 +42,6 @@ builds: ldflags: - -s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=v{{.Version}} - - id: linux-amd64 env: - CGO_ENABLED=1 @@ -51,7 +50,7 @@ builds: goarch: - amd64 main: ./cmd/headscale/headscale.go - mod_timestamp: '{{ .CommitTimestamp }}' + mod_timestamp: "{{ .CommitTimestamp }}" ldflags: - -s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=v{{.Version}} @@ -64,7 +63,7 @@ builds: - CGO_ENABLED=1 - CC=aarch64-linux-gnu-gcc main: ./cmd/headscale/headscale.go - mod_timestamp: '{{ .CommitTimestamp }}' + mod_timestamp: "{{ .CommitTimestamp }}" ldflags: - -s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=v{{.Version}} @@ -79,12 +78,12 @@ archives: format: binary checksum: - name_template: 'checksums.txt' + name_template: "checksums.txt" snapshot: name_template: "{{ .Tag }}-next" changelog: sort: asc filters: exclude: - - '^docs:' - - '^test:' + - "^docs:" + - "^test:" From e425e3ffd381fdd28ca66ccc3d412ae30995955c Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Thu, 21 Oct 2021 22:53:30 +0200 Subject: [PATCH 26/70] Fix contributors --- .github/workflows/contributors.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml index 248cf3f7..0e2b2e00 100644 --- a/.github/workflows/contributors.yml +++ b/.github/workflows/contributors.yml @@ -20,3 +20,5 @@ jobs: PATH: "/README.md" COMMIT_MESSAGE: "docs(README): update contributors" AVATAR_SHAPE: "round" + PULL_REQUEST: "Update contributors" + BRANCH: "update-contributors From 88b32e4b18c06a23ec4b65672ebabd7037190a02 Mon Sep 17 00:00:00 2001 From: derelm Date: Thu, 21 Oct 2021 23:07:35 +0200 Subject: [PATCH 27/70] fix typo --- docs/Glossary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Glossary.md b/docs/Glossary.md index 74ecfb89..aa3e2e35 100644 --- a/docs/Glossary.md +++ b/docs/Glossary.md @@ -1,3 +1,3 @@ # Glossary -- Namespace: Collection of Taiscale nodes that can see each other. In Tailscale.com is called Tailnet. +- Namespace: Collection of Tailscale nodes that can see each other. In Tailscale.com this is called Tailnet. From d5aef85bf20e02e12d483f064605f061aab9a239 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Thu, 21 Oct 2021 23:21:38 +0200 Subject: [PATCH 28/70] Fix contributors --- .github/workflows/contributors.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml index 0e2b2e00..1b9725e8 100644 --- a/.github/workflows/contributors.yml +++ b/.github/workflows/contributors.yml @@ -21,4 +21,4 @@ jobs: COMMIT_MESSAGE: "docs(README): update contributors" AVATAR_SHAPE: "round" PULL_REQUEST: "Update contributors" - BRANCH: "update-contributors + BRANCH: "update-contributors" From 75f3e1fb03cfa99621925cba2164d64f1a298d60 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 21:38:02 +0000 Subject: [PATCH 29/70] docs(README): update contributors --- README.md | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/README.md b/README.md index 19742d8e..3b411ecb 100644 --- a/README.md +++ b/README.md @@ -61,4 +61,155 @@ Please have a look at the documentation under [`docs/`](docs/). ## Contributors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Juan +
+ Juan Font +
+
+ + Kristoffer +
+ Kristoffer Dalby +
+
+ + Ward +
+ Ward Vandewege +
+
+ + ohdearaugustin/ +
+ ohdearaugustin +
+
+ + Aaron +
+ Aaron Bieber +
+
+ + Paul +
+ Paul Tötterman +
+
+ + Casey +
+ Casey Marshall +
+
+ + Silver +
+ Silver Bullet +
+
+ + thomas/ +
+ thomas +
+
+ + Arthur +
+ Arthur Woimbée +
+
+ + Felix +
+ Felix Kronlage-Dammers +
+
+ + Felix +
+ Felix Yan +
+
+ + Shaanan +
+ Shaanan Cohney +
+
+ + Teteros/ +
+ Teteros +
+
+ + The +
+ The Gitter Badger +
+
+ + Tianon +
+ Tianon Gravi +
+
+ + Tjerk +
+ Tjerk Woudsma +
+
+ + Zakhar +
+ Zakhar Bessarab +
+
+ + ignoramous/ +
+ ignoramous +
+
+ + zy/ +
+ zy +
+
+ From f3bf9b4bbb9f087bbf6eebc86da5e3c17381427e Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Thu, 21 Oct 2021 23:54:20 +0200 Subject: [PATCH 30/70] Contributors again fixed --- .github/workflows/contributors.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml index 1b9725e8..e27b6eda 100644 --- a/.github/workflows/contributors.yml +++ b/.github/workflows/contributors.yml @@ -20,5 +20,5 @@ jobs: PATH: "/README.md" COMMIT_MESSAGE: "docs(README): update contributors" AVATAR_SHAPE: "round" - PULL_REQUEST: "Update contributors" BRANCH: "update-contributors" + PULL_REQUEST: "main" From 5420347d2454855865c322314a0f5a30228b6da5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 22 Oct 2021 06:58:20 +0000 Subject: [PATCH 31/70] docs(README): update contributors --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 3b411ecb..a3c09396 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,13 @@ Please have a look at the documentation under [`docs/`](docs/). + + + derelm/ +
+ derelm +
+ ignoramous/ From e836db1eadcf3a517758cf5ac342cdf25d903e3c Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 22 Oct 2021 16:51:19 +0000 Subject: [PATCH 32/70] Add config.yaml to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 95d758a7..610550b9 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ /headscale config.json +config.yaml *.key /db.sqlite *.sqlite3 From aa245c2d06a2dbd9caded3c063c9429d4bc3af78 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 22 Oct 2021 16:52:39 +0000 Subject: [PATCH 33/70] Remove derp.yaml, add selfhosted example This PR will promote fetching the derpmap directly from tailscale, so we will remove our example, as it might easily get outdated. Add a derp-example that shows how a user can also add their own derp server. --- derp-example.yaml | 18 ++++++ derp.yaml | 146 ---------------------------------------------- 2 files changed, 18 insertions(+), 146 deletions(-) create mode 100644 derp-example.yaml delete mode 100644 derp.yaml diff --git a/derp-example.yaml b/derp-example.yaml new file mode 100644 index 00000000..45db5b9a --- /dev/null +++ b/derp-example.yaml @@ -0,0 +1,18 @@ +# This file contains some of the official Tailscale DERP servers, +# shamelessly taken from https://github.com/tailscale/tailscale/blob/main/net/dnsfallback/dns-fallback-servers.json +# +# If you plan to somehow use headscale, please deploy your own DERP infra: https://tailscale.com/kb/1118/custom-derp-servers/ +regions: + 900: + regionid: 900 + regioncode: custom + regionname: My Region + nodes: + - name: 1a + regionid: 1 + hostname: myderp.mydomain.no + ipv4: 123.123.123.123 + ipv6: "2604:a880:400:d1::828:b001" + stunport: 0 + stunonly: false + derptestport: 0 diff --git a/derp.yaml b/derp.yaml deleted file mode 100644 index 9434e712..00000000 --- a/derp.yaml +++ /dev/null @@ -1,146 +0,0 @@ -# This file contains some of the official Tailscale DERP servers, -# shamelessly taken from https://github.com/tailscale/tailscale/blob/main/net/dnsfallback/dns-fallback-servers.json -# -# If you plan to somehow use headscale, please deploy your own DERP infra: https://tailscale.com/kb/1118/custom-derp-servers/ -regions: - 1: - regionid: 1 - regioncode: nyc - regionname: New York City - nodes: - - name: 1a - regionid: 1 - hostname: derp1.tailscale.com - ipv4: 159.89.225.99 - ipv6: "2604:a880:400:d1::828:b001" - stunport: 0 - stunonly: false - derptestport: 0 - - name: 1b - regionid: 1 - hostname: derp1b.tailscale.com - ipv4: 45.55.35.93 - ipv6: "2604:a880:800:a1::f:2001" - stunport: 0 - stunonly: false - derptestport: 0 - 2: - regionid: 2 - regioncode: sfo - regionname: San Francisco - nodes: - - name: 2a - regionid: 2 - hostname: derp2.tailscale.com - ipv4: 167.172.206.31 - ipv6: "2604:a880:2:d1::c5:7001" - stunport: 0 - stunonly: false - derptestport: 0 - - name: 2b - regionid: 2 - hostname: derp2b.tailscale.com - ipv4: 64.227.106.23 - ipv6: "2604:a880:4:1d0::29:9000" - stunport: 0 - stunonly: false - derptestport: 0 - 3: - regionid: 3 - regioncode: sin - regionname: Singapore - nodes: - - name: 3a - regionid: 3 - hostname: derp3.tailscale.com - ipv4: 68.183.179.66 - ipv6: "2400:6180:0:d1::67d:8001" - stunport: 0 - stunonly: false - derptestport: 0 - 4: - regionid: 4 - regioncode: fra - regionname: Frankfurt - nodes: - - name: 4a - regionid: 4 - hostname: derp4.tailscale.com - ipv4: 167.172.182.26 - ipv6: "2a03:b0c0:3:e0::36e:900" - stunport: 0 - stunonly: false - derptestport: 0 - - name: 4b - regionid: 4 - hostname: derp4b.tailscale.com - ipv4: 157.230.25.0 - ipv6: "2a03:b0c0:3:e0::58f:3001" - stunport: 0 - stunonly: false - derptestport: 0 - 5: - regionid: 5 - regioncode: syd - regionname: Sydney - nodes: - - name: 5a - regionid: 5 - hostname: derp5.tailscale.com - ipv4: 103.43.75.49 - ipv6: "2001:19f0:5801:10b7:5400:2ff:feaa:284c" - stunport: 0 - stunonly: false - derptestport: 0 - 6: - regionid: 6 - regioncode: blr - regionname: Bangalore - nodes: - - name: 6a - regionid: 6 - hostname: derp6.tailscale.com - ipv4: 68.183.90.120 - ipv6: "2400:6180:100:d0::982:d001" - stunport: 0 - stunonly: false - derptestport: 0 - 7: - regionid: 7 - regioncode: tok - regionname: Tokyo - nodes: - - name: 7a - regionid: 7 - hostname: derp7.tailscale.com - ipv4: 167.179.89.145 - ipv6: "2401:c080:1000:467f:5400:2ff:feee:22aa" - stunport: 0 - stunonly: false - derptestport: 0 - 8: - regionid: 8 - regioncode: lhr - regionname: London - nodes: - - name: 8a - regionid: 8 - hostname: derp8.tailscale.com - ipv4: 167.71.139.179 - ipv6: "2a03:b0c0:1:e0::3cc:e001" - stunport: 0 - stunonly: false - derptestport: 0 - 9: - regionid: 9 - regioncode: sao - regionname: São Paulo - nodes: - - name: 9a - regionid: 9 - hostname: derp9.tailscale.com - ipv4: 207.148.3.137 - ipv6: "2001:19f0:6401:1d9c:5400:2ff:feef:bb82" - stunport: 0 - stunonly: false - derptestport: 0 From 57f46ded83456cc5abae95db15099abd51773bd8 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 22 Oct 2021 16:55:14 +0000 Subject: [PATCH 34/70] Split derp into its own config struct --- app.go | 34 +++++++++++--- cmd/headscale/cli/utils.go | 78 +++++++++++++++++++-------------- cmd/headscale/headscale_test.go | 28 ++++++++---- config.yaml.postgres.example | 1 - config.yaml.sqlite.example | 31 ++++++++++--- 5 files changed, 117 insertions(+), 55 deletions(-) diff --git a/app.go b/app.go index 66e2a306..546eb866 100644 --- a/app.go +++ b/app.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "os" "sort" "strings" @@ -28,11 +29,12 @@ type Config struct { ServerURL string Addr string PrivateKeyPath string - DerpMap *tailcfg.DERPMap EphemeralNodeInactivityTimeout time.Duration IPPrefix netaddr.IPPrefix BaseDomain string + DERP DERPConfig + DBtype string DBpath string DBhost string @@ -55,6 +57,13 @@ type Config struct { DNSConfig *tailcfg.DNSConfig } +type DERPConfig struct { + URLs []url.URL + Paths []string + AutoUpdate bool + UpdateFrequency time.Duration +} + // Headscale represents the base app of the service type Headscale struct { cfg Config @@ -65,6 +74,8 @@ type Headscale struct { publicKey *wgkey.Key privateKey *wgkey.Private + DERPMap *tailcfg.DERPMap + aclPolicy *ACLPolicy aclRules *[]tailcfg.FilterRule @@ -114,7 +125,7 @@ func NewHeadscale(cfg Config) (*Headscale, error) { return nil, err } // we might have routes already from Split DNS - if h.cfg.DNSConfig.Routes == nil { + if h.cfg.DNSConfig.Routes == nil { h.cfg.DNSConfig.Routes = make(map[string][]dnstype.Resolver) } for _, d := range magicDNSDomains { @@ -153,11 +164,15 @@ func (h *Headscale) expireEphemeralNodesWorker() { return } for _, m := range *machines { - if m.AuthKey != nil && m.LastSeen != nil && m.AuthKey.Ephemeral && time.Now().After(m.LastSeen.Add(h.cfg.EphemeralNodeInactivityTimeout)) { + if m.AuthKey != nil && m.LastSeen != nil && m.AuthKey.Ephemeral && + time.Now().After(m.LastSeen.Add(h.cfg.EphemeralNodeInactivityTimeout)) { log.Info().Str("machine", m.Name).Msg("Ephemeral client removed from database") err = h.db.Unscoped().Delete(m).Error if err != nil { - log.Error().Err(err).Str("machine", m.Name).Msg("🤮 Cannot delete ephemeral machine from the database") + log.Error(). + Err(err). + Str("machine", m.Name). + Msg("🤮 Cannot delete ephemeral machine from the database") } } } @@ -198,6 +213,15 @@ func (h *Headscale) Serve() error { go h.watchForKVUpdates(5000) go h.expireEphemeralNodes(5000) + // Fetch an initial DERP Map before we start serving + h.DERPMap = GetDERPMap(h.cfg.DERP) + + if h.cfg.DERP.AutoUpdate { + derpMapCancelChannel := make(chan struct{}) + defer func() { derpMapCancelChannel <- struct{}{} }() + go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel) + } + s := &http.Server{ Addr: h.cfg.Addr, Handler: r, @@ -273,7 +297,6 @@ func (h *Headscale) getLastStateChange(namespaces ...string) time.Time { times = append(times, lastChange) } - } sort.Slice(times, func(i, j int) bool { @@ -284,7 +307,6 @@ func (h *Headscale) getLastStateChange(namespaces ...string) time.Time { if len(times) == 0 { return time.Now().UTC() - } else { return times[0] } diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 52c8d043..0768e1eb 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -4,7 +4,7 @@ import ( "encoding/json" "errors" "fmt" - "io" + "net/url" "os" "path/filepath" "strings" @@ -13,7 +13,6 @@ import ( "github.com/juanfont/headscale" "github.com/rs/zerolog/log" "github.com/spf13/viper" - "gopkg.in/yaml.v2" "inet.af/netaddr" "tailscale.com/tailcfg" "tailscale.com/types/dnstype" @@ -51,21 +50,26 @@ func LoadConfig(path string) error { // Collect any validation errors and return them all at once var errorText string - if (viper.GetString("tls_letsencrypt_hostname") != "") && ((viper.GetString("tls_cert_path") != "") || (viper.GetString("tls_key_path") != "")) { + if (viper.GetString("tls_letsencrypt_hostname") != "") && + ((viper.GetString("tls_cert_path") != "") || (viper.GetString("tls_key_path") != "")) { errorText += "Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both\n" } - if (viper.GetString("tls_letsencrypt_hostname") != "") && (viper.GetString("tls_letsencrypt_challenge_type") == "TLS-ALPN-01") && (!strings.HasSuffix(viper.GetString("listen_addr"), ":443")) { + if (viper.GetString("tls_letsencrypt_hostname") != "") && + (viper.GetString("tls_letsencrypt_challenge_type") == "TLS-ALPN-01") && + (!strings.HasSuffix(viper.GetString("listen_addr"), ":443")) { // this is only a warning because there could be something sitting in front of headscale that redirects the traffic (e.g. an iptables rule) log.Warn(). Msg("Warning: when using tls_letsencrypt_hostname with TLS-ALPN-01 as challenge type, headscale must be reachable on port 443, i.e. listen_addr should probably end in :443") } - if (viper.GetString("tls_letsencrypt_challenge_type") != "HTTP-01") && (viper.GetString("tls_letsencrypt_challenge_type") != "TLS-ALPN-01") { + if (viper.GetString("tls_letsencrypt_challenge_type") != "HTTP-01") && + (viper.GetString("tls_letsencrypt_challenge_type") != "TLS-ALPN-01") { errorText += "Fatal config error: the only supported values for tls_letsencrypt_challenge_type are HTTP-01 and TLS-ALPN-01\n" } - if !strings.HasPrefix(viper.GetString("server_url"), "http://") && !strings.HasPrefix(viper.GetString("server_url"), "https://") { + if !strings.HasPrefix(viper.GetString("server_url"), "http://") && + !strings.HasPrefix(viper.GetString("server_url"), "https://") { errorText += "Fatal config error: server_url must start with https:// or http://\n" } if errorText != "" { @@ -73,7 +77,35 @@ func LoadConfig(path string) error { } else { return nil } +} +func GetDERPConfig() headscale.DERPConfig { + urlStrs := viper.GetStringSlice("derp.urls") + + urls := make([]url.URL, len(urlStrs)) + for index, urlStr := range urlStrs { + urlAddr, err := url.Parse(urlStr) + if err != nil { + log.Error(). + Str("url", urlStr). + Err(err). + Msg("Failed to parse url, ignoring...") + } + + urls[index] = *urlAddr + } + + paths := viper.GetStringSlice("derp.paths") + + autoUpdate := viper.GetBool("derp.auto_update_enabled") + updateFrequency := viper.GetDuration("derp.update_frequency") + + return headscale.DERPConfig{ + URLs: urls, + Paths: paths, + AutoUpdate: autoUpdate, + UpdateFrequency: updateFrequency, + } } func GetDNSConfig() (*tailcfg.DNSConfig, string) { @@ -171,33 +203,30 @@ func absPath(path string) string { } func getHeadscaleApp() (*headscale.Headscale, error) { - derpPath := absPath(viper.GetString("derp_map_path")) - derpMap, err := loadDerpMap(derpPath) - if err != nil { - log.Error(). - Str("path", derpPath). - Err(err). - Msg("Could not load DERP servers map file") - } - // Minimum inactivity time out is keepalive timeout (60s) plus a few seconds // to avoid races minInactivityTimeout, _ := time.ParseDuration("65s") if viper.GetDuration("ephemeral_node_inactivity_timeout") <= minInactivityTimeout { - err = fmt.Errorf("ephemeral_node_inactivity_timeout (%s) is set too low, must be more than %s\n", viper.GetString("ephemeral_node_inactivity_timeout"), minInactivityTimeout) + err := fmt.Errorf( + "ephemeral_node_inactivity_timeout (%s) is set too low, must be more than %s\n", + viper.GetString("ephemeral_node_inactivity_timeout"), + minInactivityTimeout, + ) return nil, err } dnsConfig, baseDomain := GetDNSConfig() + derpConfig := GetDERPConfig() cfg := headscale.Config{ ServerURL: viper.GetString("server_url"), Addr: viper.GetString("listen_addr"), PrivateKeyPath: absPath(viper.GetString("private_key_path")), - DerpMap: derpMap, IPPrefix: netaddr.MustParseIPPrefix(viper.GetString("ip_prefix")), BaseDomain: baseDomain, + DERP: derpConfig, + EphemeralNodeInactivityTimeout: viper.GetDuration("ephemeral_node_inactivity_timeout"), DBtype: viper.GetString("db_type"), @@ -243,21 +272,6 @@ func getHeadscaleApp() (*headscale.Headscale, error) { return h, nil } -func loadDerpMap(path string) (*tailcfg.DERPMap, error) { - derpFile, err := os.Open(path) - if err != nil { - return nil, err - } - defer derpFile.Close() - var derpMap tailcfg.DERPMap - b, err := io.ReadAll(derpFile) - if err != nil { - return nil, err - } - err = yaml.Unmarshal(b, &derpMap) - return &derpMap, err -} - func JsonOutput(result interface{}, errResult error, outputFormat string) { var j []byte var err error diff --git a/cmd/headscale/headscale_test.go b/cmd/headscale/headscale_test.go index 0c3add69..eff78496 100644 --- a/cmd/headscale/headscale_test.go +++ b/cmd/headscale/headscale_test.go @@ -25,7 +25,6 @@ func (s *Suite) SetUpSuite(c *check.C) { } func (s *Suite) TearDownSuite(c *check.C) { - } func (*Suite) TestPostgresConfigLoading(c *check.C) { @@ -53,7 +52,6 @@ func (*Suite) TestPostgresConfigLoading(c *check.C) { // Test that config file was interpreted correctly c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8080") c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8080") - c.Assert(viper.GetString("derp_map_path"), check.Equals, "derp.yaml") 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, "") @@ -86,7 +84,7 @@ func (*Suite) TestSqliteConfigLoading(c *check.C) { // Test that config file was interpreted correctly c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8080") c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8080") - c.Assert(viper.GetString("derp_map_path"), check.Equals, "derp.yaml") + c.Assert(viper.GetStringSlice("derp.paths")[0], check.Equals, "derp-example.yaml") 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, "") @@ -128,7 +126,7 @@ func (*Suite) TestDNSConfigLoading(c *check.C) { func writeConfig(c *check.C, tmpDir string, configYaml []byte) { // Populate a custom config file configFile := filepath.Join(tmpDir, "config.yaml") - err := ioutil.WriteFile(configFile, configYaml, 0644) + err := ioutil.WriteFile(configFile, configYaml, 0o644) if err != nil { c.Fatalf("Couldn't write file %s", configFile) } @@ -139,10 +137,12 @@ func (*Suite) TestTLSConfigValidation(c *check.C) { if err != nil { c.Fatal(err) } - //defer os.RemoveAll(tmpDir) + // defer os.RemoveAll(tmpDir) fmt.Println(tmpDir) - configYaml := []byte("---\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"\"\ntls_cert_path: \"abc.pem\"") + configYaml := []byte( + "---\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"\"\ntls_cert_path: \"abc.pem\"", + ) writeConfig(c, tmpDir, configYaml) // Check configuration validation errors (1) @@ -150,13 +150,23 @@ func (*Suite) TestTLSConfigValidation(c *check.C) { c.Assert(err, check.NotNil) // check.Matches can not handle multiline strings tmp := strings.ReplaceAll(err.Error(), "\n", "***") - c.Assert(tmp, check.Matches, ".*Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both.*") - c.Assert(tmp, check.Matches, ".*Fatal config error: the only supported values for tls_letsencrypt_challenge_type are.*") + c.Assert( + tmp, + check.Matches, + ".*Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both.*", + ) + c.Assert( + tmp, + check.Matches, + ".*Fatal config error: the only supported values for tls_letsencrypt_challenge_type are.*", + ) c.Assert(tmp, check.Matches, ".*Fatal config error: server_url must start with https:// or http://.*") fmt.Println(tmp) // Check configuration validation errors (2) - configYaml = []byte("---\nserver_url: \"http://127.0.0.1:8080\"\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"TLS-ALPN-01\"") + configYaml = []byte( + "---\nserver_url: \"http://127.0.0.1:8080\"\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"TLS-ALPN-01\"", + ) writeConfig(c, tmpDir, configYaml) err = cli.LoadConfig(tmpDir) c.Assert(err, check.IsNil) diff --git a/config.yaml.postgres.example b/config.yaml.postgres.example index 569b42a9..920bdaaa 100644 --- a/config.yaml.postgres.example +++ b/config.yaml.postgres.example @@ -2,7 +2,6 @@ server_url: http://127.0.0.1:8080 listen_addr: 0.0.0.0:8080 private_key_path: private.key -derp_map_path: derp.yaml ephemeral_node_inactivity_timeout: 30m # Postgres config diff --git a/config.yaml.sqlite.example b/config.yaml.sqlite.example index 158b1e5b..411a2a77 100644 --- a/config.yaml.sqlite.example +++ b/config.yaml.sqlite.example @@ -1,26 +1,43 @@ --- +log_level: info server_url: http://127.0.0.1:8080 listen_addr: 0.0.0.0:8080 private_key_path: private.key -derp_map_path: derp.yaml ephemeral_node_inactivity_timeout: 30m # SQLite config (uncomment it if you want to use SQLite) db_type: sqlite3 db_path: db.sqlite +derp: + # List of externally available DERP maps encoded in JSON + urls: + - https://controlplane.tailscale.com/derpmap/default + + # Locally available DERP map files encoded in YAML + paths: + - derp-example.yaml + + # If enabled, a worker will be set up to periodically + # refresh the given sources and update the derpmap + # will be set up. + auto_update_enabled: true + + # How often should we check for updates? + update_frequency: 24h + acme_url: https://acme-v02.api.letsencrypt.org/directory -acme_email: '' -tls_letsencrypt_hostname: '' +acme_email: "" +tls_letsencrypt_hostname: "" tls_letsencrypt_listen: ":http" tls_letsencrypt_cache_dir: ".cache" tls_letsencrypt_challenge_type: HTTP-01 -tls_cert_path: '' -tls_key_path: '' -acl_policy_path: '' +tls_cert_path: "" +tls_key_path: "" +acl_policy_path: "" dns_config: nameservers: - - 1.1.1.1 + - 1.1.1.1 domains: [] magic_dns: true base_domain: example.com From 177f1eca06ff3f046ac75c921f01ccf1b0eb80ab Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 22 Oct 2021 16:55:35 +0000 Subject: [PATCH 35/70] Add helper functions for building derp maps from urls and file --- derp.go | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 derp.go diff --git a/derp.go b/derp.go new file mode 100644 index 00000000..39e63210 --- /dev/null +++ b/derp.go @@ -0,0 +1,152 @@ +package headscale + +import ( + "encoding/json" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "time" + + "github.com/rs/zerolog/log" + + "gopkg.in/yaml.v2" + + "tailscale.com/tailcfg" +) + +func loadDERPMapFromPath(path string) (*tailcfg.DERPMap, error) { + derpFile, err := os.Open(path) + if err != nil { + return nil, err + } + defer derpFile.Close() + var derpMap tailcfg.DERPMap + b, err := io.ReadAll(derpFile) + if err != nil { + return nil, err + } + err = yaml.Unmarshal(b, &derpMap) + return &derpMap, err +} + +func loadDERPMapFromURL(addr url.URL) (*tailcfg.DERPMap, error) { + client := http.Client{ + Timeout: 10 * time.Second, + } + resp, err := client.Get(addr.String()) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var derpMap tailcfg.DERPMap + err = json.Unmarshal(body, &derpMap) + return &derpMap, err +} + +// mergeDERPMaps naively merges a list of DERPMaps into a single +// DERPMap, it will _only_ look at the Regions, an integer. +// If a region exists in two of the given DERPMaps, the region +// form the _last_ DERPMap will be preserved. +// An empty DERPMap list will result in a DERPMap with no regions +func mergeDERPMaps(derpMaps []*tailcfg.DERPMap) *tailcfg.DERPMap { + result := tailcfg.DERPMap{ + OmitDefaultRegions: false, + Regions: map[int]*tailcfg.DERPRegion{}, + } + + for _, derpMap := range derpMaps { + for id, region := range derpMap.Regions { + result.Regions[id] = region + } + } + + return &result +} + +func GetDERPMap(cfg DERPConfig) *tailcfg.DERPMap { + derpMaps := make([]*tailcfg.DERPMap, 0) + + for _, path := range cfg.Paths { + log.Debug(). + Str("func", "GetDERPMap"). + Str("path", path). + Msg("Loading DERPMap from path") + derpMap, err := loadDERPMapFromPath(path) + if err != nil { + log.Error(). + Str("func", "GetDERPMap"). + Str("path", path). + Err(err). + Msg("Could not load DERP map from path") + break + } + + derpMaps = append(derpMaps, derpMap) + } + + for _, addr := range cfg.URLs { + derpMap, err := loadDERPMapFromURL(addr) + log.Debug(). + Str("func", "GetDERPMap"). + Str("url", addr.String()). + Msg("Loading DERPMap from path") + if err != nil { + log.Error(). + Str("func", "GetDERPMap"). + Str("url", addr.String()). + Err(err). + Msg("Could not load DERP map from path") + break + } + + derpMaps = append(derpMaps, derpMap) + } + + derpMap := mergeDERPMaps(derpMaps) + + log.Trace().Interface("derpMap", derpMap).Msg("DERPMap loaded") + + if len(derpMap.Regions) == 0 { + log.Warn(). + Msg("DERP map is empty, not a single DERP map datasource was loaded correctly or contained a region") + } + + return derpMap +} + +func (h *Headscale) scheduledDERPMapUpdateWorker(cancelChan <-chan struct{}) { + log.Info(). + Dur("frequency", h.cfg.DERP.UpdateFrequency). + Msg("Setting up a DERPMap update worker") + ticker := time.NewTicker(h.cfg.DERP.UpdateFrequency) + + for { + select { + case <-cancelChan: + return + + case <-ticker.C: + log.Info().Msg("Fetching DERPMap updates") + h.DERPMap = GetDERPMap(h.cfg.DERP) + + namespaces, err := h.ListNamespaces() + if err != nil { + log.Error(). + Err(err). + Msg("Failed to fetch namespaces") + } + + for _, namespace := range *namespaces { + h.setLastStateChangeToNow(namespace.Name) + } + } + } +} From 582eb57a092b15ba84e1f69b9bca4a0c485249f7 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 22 Oct 2021 16:56:00 +0000 Subject: [PATCH 36/70] Use the new derp map --- api.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/api.go b/api.go index 6e30cb3a..a31cf529 100644 --- a/api.go +++ b/api.go @@ -82,7 +82,10 @@ func (h *Headscale) RegistrationHandler(c *gin.Context) { now := time.Now().UTC() var m Machine - if result := h.db.Preload("Namespace").First(&m, "machine_key = ?", mKey.HexString()); errors.Is(result.Error, gorm.ErrRecordNotFound) { + if result := h.db.Preload("Namespace").First(&m, "machine_key = ?", mKey.HexString()); errors.Is( + result.Error, + gorm.ErrRecordNotFound, + ) { log.Info().Str("machine", req.Hostinfo.Hostname).Msg("New machine") m = Machine{ Expiry: &req.Expiry, @@ -270,7 +273,7 @@ func (h *Headscale) getMapResponse(mKey wgkey.Key, req tailcfg.MapRequest, m *Ma DNSConfig: dnsConfig, Domain: h.cfg.BaseDomain, PacketFilter: *h.aclRules, - DERPMap: h.cfg.DerpMap, + DERPMap: h.DERPMap, UserProfiles: profiles, } @@ -329,7 +332,13 @@ func (h *Headscale) getMapKeepAliveResponse(mKey wgkey.Key, req tailcfg.MapReque return data, nil } -func (h *Headscale) handleAuthKey(c *gin.Context, db *gorm.DB, idKey wgkey.Key, req tailcfg.RegisterRequest, m Machine) { +func (h *Headscale) handleAuthKey( + c *gin.Context, + db *gorm.DB, + idKey wgkey.Key, + req tailcfg.RegisterRequest, + m Machine, +) { log.Debug(). Str("func", "handleAuthKey"). Str("machine", req.Hostinfo.Hostname). From 0e902fe949cec49aeea5f48da98779993f3b213d Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 22 Oct 2021 16:56:23 +0000 Subject: [PATCH 37/70] Add certificates to docker image so we can get derpmap from tailscale --- Dockerfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Dockerfile b/Dockerfile index 20bb7dae..6e216aad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,11 @@ RUN test -e /go/bin/headscale FROM ubuntu:20.04 +RUN apt-get update \ + && apt-get install -y ca-certificates \ + && update-ca-certificates \ + && rm -rf /var/lib/apt/lists/* + COPY --from=build /go/bin/headscale /usr/local/bin/headscale ENV TZ UTC From d875cca69d7919fdeb476b6c5b442ce5439d9e8c Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 22 Oct 2021 16:57:01 +0000 Subject: [PATCH 38/70] move integration to yaml, add new derp configuration --- integration_test/etc/config.json | 19 ------------------- integration_test/etc/config.yaml | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 19 deletions(-) delete mode 100644 integration_test/etc/config.json create mode 100644 integration_test/etc/config.yaml diff --git a/integration_test/etc/config.json b/integration_test/etc/config.json deleted file mode 100644 index dc23652d..00000000 --- a/integration_test/etc/config.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "server_url": "http://headscale:8080", - "listen_addr": "0.0.0.0:8080", - "private_key_path": "private.key", - "derp_map_path": "derp.yaml", - "ephemeral_node_inactivity_timeout": "30m", - "db_type": "sqlite3", - "db_path": "/tmp/integration_test_db.sqlite3", - "acl_policy_path": "", - "log_level": "trace", - "dns_config": { - "nameservers": [ - "1.1.1.1" - ], - "domains": [], - "magic_dns": true, - "base_domain": "headscale.net" - } -} \ No newline at end of file diff --git a/integration_test/etc/config.yaml b/integration_test/etc/config.yaml new file mode 100644 index 00000000..6f68f304 --- /dev/null +++ b/integration_test/etc/config.yaml @@ -0,0 +1,20 @@ +log_level: trace +acl_policy_path: "" +db_type: sqlite3 +ephemeral_node_inactivity_timeout: 30m +dns_config: + base_domain: headscale.net + magic_dns: true + domains: [] + nameservers: + - 1.1.1.1 +db_path: /tmp/integration_test_db.sqlite3 +private_key_path: private.key +listen_addr: 0.0.0.0:8080 +server_url: http://headscale:8080 + +derp: + urls: + - https://controlplane.tailscale.com/derpmap/default + auto_update_enabled: false + update_frequency: 1m From aefbd66317d837846e1db8c4fdc4d11633e885cf Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 22 Oct 2021 16:57:51 +0000 Subject: [PATCH 39/70] Remove derpmap volume from integration tests --- integration_test.go | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/integration_test.go b/integration_test.go index 3c51215d..53092423 100644 --- a/integration_test.go +++ b/integration_test.go @@ -230,7 +230,6 @@ func (s *IntegrationTestSuite) SetupSuite() { Name: "headscale", Mounts: []string{ fmt.Sprintf("%s/integration_test/etc:/etc/headscale", currentPath), - fmt.Sprintf("%s/derp.yaml:/etc/headscale/derp.yaml", currentPath), }, Networks: []*dockertest.Network{&network}, Cmd: []string{"headscale", "serve"}, @@ -289,7 +288,16 @@ func (s *IntegrationTestSuite) SetupSuite() { fmt.Printf("Creating pre auth key for %s\n", namespace) authKey, err := executeCommand( &headscale, - []string{"headscale", "--namespace", namespace, "preauthkeys", "create", "--reusable", "--expiration", "24h"}, + []string{ + "headscale", + "--namespace", + namespace, + "preauthkeys", + "create", + "--reusable", + "--expiration", + "24h", + }, []string{}, ) assert.Nil(s.T(), err) @@ -298,7 +306,16 @@ func (s *IntegrationTestSuite) SetupSuite() { fmt.Printf("Joining tailscale containers to headscale at %s\n", headscaleEndpoint) for hostname, tailscale := range scales.tailscales { - command := []string{"tailscale", "up", "-login-server", headscaleEndpoint, "--authkey", strings.TrimSuffix(authKey, "\n"), "--hostname", hostname} + command := []string{ + "tailscale", + "up", + "-login-server", + headscaleEndpoint, + "--authkey", + strings.TrimSuffix(authKey, "\n"), + "--hostname", + hostname, + } fmt.Println("Join command:", command) fmt.Printf("Running join command for %s\n", hostname) @@ -661,7 +678,13 @@ func (s *IntegrationTestSuite) TestMagicDNS() { fmt.Sprintf("%s.%s.headscale.net", peername, namespace), } - fmt.Printf("Pinging using Hostname (magicdns) from %s (%s) to %s (%s)\n", hostname, ips[hostname], peername, ip) + fmt.Printf( + "Pinging using Hostname (magicdns) from %s (%s) to %s (%s)\n", + hostname, + ips[hostname], + peername, + ip, + ) result, err := executeCommand( &tailscale, command, From b85adbc40a5119b202f2312e0ac8dd2a3e1a3716 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 22 Oct 2021 18:14:29 +0100 Subject: [PATCH 40/70] Remove the need for multiple config files This commit removes the almost a 100% redundant tests (two fields were checked differently) and makes a single example configuration for users. --- cmd/headscale/headscale_test.go | 62 ++++++------------- ...yaml.sqlite.example => config-example.yaml | 20 ++++-- config.yaml.postgres.example | 30 --------- 3 files changed, 34 insertions(+), 78 deletions(-) rename config.yaml.sqlite.example => config-example.yaml (67%) delete mode 100644 config.yaml.postgres.example diff --git a/cmd/headscale/headscale_test.go b/cmd/headscale/headscale_test.go index 0c3add69..e4a20433 100644 --- a/cmd/headscale/headscale_test.go +++ b/cmd/headscale/headscale_test.go @@ -25,40 +25,6 @@ func (s *Suite) SetUpSuite(c *check.C) { } func (s *Suite) TearDownSuite(c *check.C) { - -} - -func (*Suite) TestPostgresConfigLoading(c *check.C) { - tmpDir, err := ioutil.TempDir("", "headscale") - if err != nil { - c.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - path, err := os.Getwd() - if err != nil { - c.Fatal(err) - } - - // Symlink the example config file - err = os.Symlink(filepath.Clean(path+"/../../config.yaml.postgres.example"), filepath.Join(tmpDir, "config.yaml")) - if err != nil { - c.Fatal(err) - } - - // Load example config, it should load without validation errors - err = cli.LoadConfig(tmpDir) - c.Assert(err, check.IsNil) - - // Test that config file was interpreted correctly - c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8080") - c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8080") - c.Assert(viper.GetString("derp_map_path"), check.Equals, "derp.yaml") - 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.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1") } func (*Suite) TestSqliteConfigLoading(c *check.C) { @@ -74,7 +40,7 @@ func (*Suite) TestSqliteConfigLoading(c *check.C) { } // Symlink the example config file - err = os.Symlink(filepath.Clean(path+"/../../config.yaml.sqlite.example"), filepath.Join(tmpDir, "config.yaml")) + err = os.Symlink(filepath.Clean(path+"/../../config-example.yaml"), filepath.Join(tmpDir, "config.yaml")) if err != nil { c.Fatal(err) } @@ -108,7 +74,7 @@ func (*Suite) TestDNSConfigLoading(c *check.C) { } // Symlink the example config file - err = os.Symlink(filepath.Clean(path+"/../../config.yaml.sqlite.example"), filepath.Join(tmpDir, "config.yaml")) + err = os.Symlink(filepath.Clean(path+"/../../config-example.yaml"), filepath.Join(tmpDir, "config.yaml")) if err != nil { c.Fatal(err) } @@ -128,7 +94,7 @@ func (*Suite) TestDNSConfigLoading(c *check.C) { func writeConfig(c *check.C, tmpDir string, configYaml []byte) { // Populate a custom config file configFile := filepath.Join(tmpDir, "config.yaml") - err := ioutil.WriteFile(configFile, configYaml, 0644) + err := ioutil.WriteFile(configFile, configYaml, 0o644) if err != nil { c.Fatalf("Couldn't write file %s", configFile) } @@ -139,10 +105,12 @@ func (*Suite) TestTLSConfigValidation(c *check.C) { if err != nil { c.Fatal(err) } - //defer os.RemoveAll(tmpDir) + // defer os.RemoveAll(tmpDir) fmt.Println(tmpDir) - configYaml := []byte("---\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"\"\ntls_cert_path: \"abc.pem\"") + configYaml := []byte( + "---\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"\"\ntls_cert_path: \"abc.pem\"", + ) writeConfig(c, tmpDir, configYaml) // Check configuration validation errors (1) @@ -150,13 +118,23 @@ func (*Suite) TestTLSConfigValidation(c *check.C) { c.Assert(err, check.NotNil) // check.Matches can not handle multiline strings tmp := strings.ReplaceAll(err.Error(), "\n", "***") - c.Assert(tmp, check.Matches, ".*Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both.*") - c.Assert(tmp, check.Matches, ".*Fatal config error: the only supported values for tls_letsencrypt_challenge_type are.*") + c.Assert( + tmp, + check.Matches, + ".*Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both.*", + ) + c.Assert( + tmp, + check.Matches, + ".*Fatal config error: the only supported values for tls_letsencrypt_challenge_type are.*", + ) c.Assert(tmp, check.Matches, ".*Fatal config error: server_url must start with https:// or http://.*") fmt.Println(tmp) // Check configuration validation errors (2) - configYaml = []byte("---\nserver_url: \"http://127.0.0.1:8080\"\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"TLS-ALPN-01\"") + configYaml = []byte( + "---\nserver_url: \"http://127.0.0.1:8080\"\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"TLS-ALPN-01\"", + ) writeConfig(c, tmpDir, configYaml) err = cli.LoadConfig(tmpDir) c.Assert(err, check.IsNil) diff --git a/config.yaml.sqlite.example b/config-example.yaml similarity index 67% rename from config.yaml.sqlite.example rename to config-example.yaml index 158b1e5b..494121b2 100644 --- a/config.yaml.sqlite.example +++ b/config-example.yaml @@ -9,18 +9,26 @@ ephemeral_node_inactivity_timeout: 30m db_type: sqlite3 db_path: db.sqlite +# # Postgres config +# db_type: postgres +# db_host: localhost +# db_port: 5432 +# db_name: headscale +# db_user: foo +# db_pass: bar + acme_url: https://acme-v02.api.letsencrypt.org/directory -acme_email: '' -tls_letsencrypt_hostname: '' +acme_email: "" +tls_letsencrypt_hostname: "" tls_letsencrypt_listen: ":http" tls_letsencrypt_cache_dir: ".cache" tls_letsencrypt_challenge_type: HTTP-01 -tls_cert_path: '' -tls_key_path: '' -acl_policy_path: '' +tls_cert_path: "" +tls_key_path: "" +acl_policy_path: "" dns_config: nameservers: - - 1.1.1.1 + - 1.1.1.1 domains: [] magic_dns: true base_domain: example.com diff --git a/config.yaml.postgres.example b/config.yaml.postgres.example deleted file mode 100644 index 569b42a9..00000000 --- a/config.yaml.postgres.example +++ /dev/null @@ -1,30 +0,0 @@ ---- -server_url: http://127.0.0.1:8080 -listen_addr: 0.0.0.0:8080 -private_key_path: private.key -derp_map_path: derp.yaml -ephemeral_node_inactivity_timeout: 30m - -# Postgres config -db_type: postgres -db_host: localhost -db_port: 5432 -db_name: headscale -db_user: foo -db_pass: bar - -acme_url: https://acme-v02.api.letsencrypt.org/directory -acme_email: '' -tls_letsencrypt_hostname: '' -tls_letsencrypt_listen: ":http" -tls_letsencrypt_cache_dir: ".cache" -tls_letsencrypt_challenge_type: HTTP-01 -tls_cert_path: '' -tls_key_path: '' -acl_policy_path: '' -dns_config: - nameservers: - - 1.1.1.1 - domains: [] - magic_dns: true - base_domain: example.com From 4d4d0de356d47f1e19c22b92d0ae0b8ef2fcc196 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 22 Oct 2021 18:27:11 +0100 Subject: [PATCH 41/70] Start adding comments to config --- config-example.yaml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/config-example.yaml b/config-example.yaml index 494121b2..d0b0bb6e 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -1,11 +1,20 @@ --- +# The url clients will connect to. +# Typically this will be a domain. server_url: http://127.0.0.1:8080 + +# Address to listen to / bind to on the server listen_addr: 0.0.0.0:8080 + +# Path to WireGuard private key file private_key_path: private.key + +# Path to a file containing a map of DERP nodes. derp_map_path: derp.yaml + ephemeral_node_inactivity_timeout: 30m -# SQLite config (uncomment it if you want to use SQLite) +# SQLite config db_type: sqlite3 db_path: db.sqlite @@ -19,16 +28,23 @@ db_path: db.sqlite acme_url: https://acme-v02.api.letsencrypt.org/directory acme_email: "" + tls_letsencrypt_hostname: "" tls_letsencrypt_listen: ":http" tls_letsencrypt_cache_dir: ".cache" tls_letsencrypt_challenge_type: HTTP-01 + tls_cert_path: "" tls_key_path: "" + +# Path to a file containg ACL policies. acl_policy_path: "" + dns_config: + # Upstream DNS servers nameservers: - 1.1.1.1 domains: [] + magic_dns: true base_domain: example.com From a3557694161dfa18aa518df84e73a60d30c001ae Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 22 Oct 2021 23:58:27 +0100 Subject: [PATCH 42/70] Update derp-example.yaml Co-authored-by: Juan Font --- derp-example.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/derp-example.yaml b/derp-example.yaml index 45db5b9a..bbf7cc8d 100644 --- a/derp-example.yaml +++ b/derp-example.yaml @@ -1,6 +1,3 @@ -# This file contains some of the official Tailscale DERP servers, -# shamelessly taken from https://github.com/tailscale/tailscale/blob/main/net/dnsfallback/dns-fallback-servers.json -# # If you plan to somehow use headscale, please deploy your own DERP infra: https://tailscale.com/kb/1118/custom-derp-servers/ regions: 900: From 8853315dcca2b8e7c43a14fec4f7a50e6ed61d89 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sat, 23 Oct 2021 10:40:15 +0100 Subject: [PATCH 43/70] Update config-example.yaml Co-authored-by: Juan Font --- config-example.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config-example.yaml b/config-example.yaml index d0b0bb6e..54e78f4a 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -12,6 +12,8 @@ private_key_path: private.key # Path to a file containing a map of DERP nodes. derp_map_path: derp.yaml +# Disables the automatic check for updates on startup +disable_check_updates: false ephemeral_node_inactivity_timeout: 30m # SQLite config From 746d4037da46ff1e6682b6d8a79579f7dfb87c94 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 24 Oct 2021 21:30:51 +0100 Subject: [PATCH 44/70] Fix config and tests --- config-example.yaml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/config-example.yaml b/config-example.yaml index 54e78f4a..59370eb5 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -9,8 +9,22 @@ listen_addr: 0.0.0.0:8080 # Path to WireGuard private key file private_key_path: private.key -# Path to a file containing a map of DERP nodes. -derp_map_path: derp.yaml +derp: + # List of externally available DERP maps encoded in JSON + urls: + - https://controlplane.tailscale.com/derpmap/default + + # Locally available DERP map files encoded in YAML + paths: + - derp-example.yaml + + # If enabled, a worker will be set up to periodically + # refresh the given sources and update the derpmap + # will be set up. + auto_update_enabled: true + + # How often should we check for updates? + update_frequency: 24h # Disables the automatic check for updates on startup disable_check_updates: false From c8e1afb14b9bf8db67b3cebdb1a90c184382ec3f Mon Sep 17 00:00:00 2001 From: Ward Vandewege Date: Sun, 24 Oct 2021 17:00:51 -0400 Subject: [PATCH 45/70] When attempting to unshare a node from the primary namespace, return errorMachineNotShared, not errorSameNamespace. Add test for same. --- sharing.go | 3 ++- sharing_test.go | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/sharing.go b/sharing.go index 879ed06f..5f6a8f45 100644 --- a/sharing.go +++ b/sharing.go @@ -43,7 +43,8 @@ func (h *Headscale) AddSharedMachineToNamespace(m *Machine, ns *Namespace) error // RemoveSharedMachineFromNamespace removes a shared machine from a namespace func (h *Headscale) RemoveSharedMachineFromNamespace(m *Machine, ns *Namespace) error { if m.NamespaceID == ns.ID { - return errorSameNamespace + // Can't unshare from primary namespace + return errorMachineNotShared } sharedMachine := SharedMachine{} diff --git a/sharing_test.go b/sharing_test.go index 140b05f2..1133fd92 100644 --- a/sharing_test.go +++ b/sharing_test.go @@ -86,6 +86,9 @@ func (s *Suite) TestUnshare(c *check.C) { err = h.RemoveSharedMachineFromNamespace(m2, n1) c.Assert(err, check.Equals, errorMachineNotShared) + + err = h.RemoveSharedMachineFromNamespace(m1, n1) + c.Assert(err, check.Equals, errorMachineNotShared) } func (s *Suite) TestAlreadyShared(c *check.C) { From dd7557850ec07e23d0c075998ffe1d84d9e3cf3b Mon Sep 17 00:00:00 2001 From: Ward Vandewege Date: Sun, 24 Oct 2021 17:02:57 -0400 Subject: [PATCH 46/70] cli changes for the `nodes` subcommand: * when listing nodes, a namespace is now optional, when it is not provided, all nodes are shown * when deleting, and sharing a node, remove the `namespace` flag, it was superfluous and unused * when unsharing a node, specify the namespace as an argument not a flag, making the UX the same as for sharing. Also refactor the share/unshare code to reuse the shared bits. --- cmd/headscale/cli/nodes.go | 149 ++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 77 deletions(-) diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go index c44aa5ed..954e338e 100644 --- a/cmd/headscale/cli/nodes.go +++ b/cmd/headscale/cli/nodes.go @@ -17,12 +17,13 @@ import ( func init() { rootCmd.AddCommand(nodeCmd) - nodeCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace") - err := nodeCmd.MarkPersistentFlagRequired("namespace") + listNodesCmd.Flags().StringP("namespace", "n", "", "Namespace") + nodeCmd.AddCommand(listNodesCmd) + registerNodeCmd.Flags().StringP("namespace", "n", "", "Namespace") + err := registerNodeCmd.MarkFlagRequired("namespace") if err != nil { log.Fatalf(err.Error()) } - nodeCmd.AddCommand(listNodesCmd) nodeCmd.AddCommand(registerNodeCmd) nodeCmd.AddCommand(deleteNodeCmd) nodeCmd.AddCommand(shareMachineCmd) @@ -69,7 +70,7 @@ var registerNodeCmd = &cobra.Command{ var listNodesCmd = &cobra.Command{ Use: "list", - Short: "List the nodes in a given namespace", + Short: "List nodes", Run: func(cmd *cobra.Command, args []string) { n, err := cmd.Flags().GetString("namespace") if err != nil { @@ -82,23 +83,44 @@ var listNodesCmd = &cobra.Command{ log.Fatalf("Error initializing: %s", err) } - namespace, err := h.GetNamespace(n) - if err != nil { - log.Fatalf("Error fetching namespace: %s", err) + var namespaces []headscale.Namespace + var namespace *headscale.Namespace + var sharedMachines *[]headscale.Machine + if len(n) == 0 { + // no namespace provided, list all + tmp, err := h.ListNamespaces() + if err != nil { + log.Fatalf("Error fetching namespace: %s", err) + } + namespaces = *tmp + } else { + namespace, err = h.GetNamespace(n) + if err != nil { + log.Fatalf("Error fetching namespace: %s", err) + } + namespaces = append(namespaces, *namespace) + + sharedMachines, err = h.ListSharedMachinesInNamespace(n) + if err != nil { + log.Fatalf("Error fetching shared machines: %s", err) + } } - machines, err := h.ListMachinesInNamespace(n) - if err != nil { - log.Fatalf("Error fetching machines: %s", err) + var allMachines []headscale.Machine + for _, n := range namespaces { + machines, err := h.ListMachinesInNamespace(n.Name) + if err != nil { + log.Fatalf("Error fetching machines: %s", err) + } + allMachines = append(allMachines, *machines...) } - sharedMachines, err := h.ListSharedMachinesInNamespace(n) - if err != nil { - log.Fatalf("Error fetching shared machines: %s", err) + // listing sharedMachines is only relevant when a particular namespace is + // requested + if sharedMachines != nil { + allMachines = append(allMachines, *sharedMachines...) } - allMachines := append(*machines, *sharedMachines...) - if strings.HasPrefix(o, "json") { JsonOutput(allMachines, err, o) return @@ -108,7 +130,7 @@ var listNodesCmd = &cobra.Command{ log.Fatalf("Error getting nodes: %s", err) } - d, err := nodesToPtables(*namespace, allMachines) + d, err := nodesToPtables(namespace, allMachines) if err != nil { log.Fatalf("Error converting to table: %s", err) } @@ -176,6 +198,31 @@ var deleteNodeCmd = &cobra.Command{ }, } +func sharingWorker(cmd *cobra.Command, args []string) (*headscale.Headscale, string, *headscale.Machine, *headscale.Namespace) { + output, _ := cmd.Flags().GetString("output") + + h, err := getHeadscaleApp() + if err != nil { + log.Fatalf("Error initializing: %s", err) + } + + namespace, err := h.GetNamespace(args[1]) + if err != nil { + log.Fatalf("Error fetching namespace %s: %s", args[1], err) + } + + id, err := strconv.Atoi(args[0]) + if err != nil { + log.Fatalf("Error converting ID to integer: %s", err) + } + machine, err := h.GetMachineByID(uint64(id)) + if err != nil { + log.Fatalf("Error getting node: %s", err) + } + + return h, output, machine, namespace +} + var shareMachineCmd = &cobra.Command{ Use: "share ID namespace", Short: "Shares a node from the current namespace to the specified one", @@ -186,37 +233,8 @@ var shareMachineCmd = &cobra.Command{ return nil }, Run: func(cmd *cobra.Command, args []string) { - namespace, err := cmd.Flags().GetString("namespace") - if err != nil { - log.Fatalf("Error getting namespace: %s", err) - } - output, _ := cmd.Flags().GetString("output") - - h, err := getHeadscaleApp() - if err != nil { - log.Fatalf("Error initializing: %s", err) - } - - _, err = h.GetNamespace(namespace) - if err != nil { - log.Fatalf("Error fetching origin namespace: %s", err) - } - - destinationNamespace, err := h.GetNamespace(args[1]) - if err != nil { - log.Fatalf("Error fetching destination namespace: %s", err) - } - - id, err := strconv.Atoi(args[0]) - if err != nil { - log.Fatalf("Error converting ID to integer: %s", err) - } - machine, err := h.GetMachineByID(uint64(id)) - if err != nil { - log.Fatalf("Error getting node: %s", err) - } - - err = h.AddSharedMachineToNamespace(machine, destinationNamespace) + h, output, machine, namespace := sharingWorker(cmd, args) + err := h.AddSharedMachineToNamespace(machine, namespace) if strings.HasPrefix(output, "json") { JsonOutput(map[string]string{"Result": "Node shared"}, err, output) return @@ -231,41 +249,17 @@ var shareMachineCmd = &cobra.Command{ } var unshareMachineCmd = &cobra.Command{ - Use: "unshare ID", + Use: "unshare ID namespace", Short: "Unshares a node from the specified namespace", Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { + if len(args) < 2 { return fmt.Errorf("missing parameters") } return nil }, Run: func(cmd *cobra.Command, args []string) { - namespace, err := cmd.Flags().GetString("namespace") - if err != nil { - log.Fatalf("Error getting namespace: %s", err) - } - output, _ := cmd.Flags().GetString("output") - - h, err := getHeadscaleApp() - if err != nil { - log.Fatalf("Error initializing: %s", err) - } - - n, err := h.GetNamespace(namespace) - if err != nil { - log.Fatalf("Error fetching namespace: %s", err) - } - - id, err := strconv.Atoi(args[0]) - if err != nil { - log.Fatalf("Error converting ID to integer: %s", err) - } - machine, err := h.GetMachineByID(uint64(id)) - if err != nil { - log.Fatalf("Error getting node: %s", err) - } - - err = h.RemoveSharedMachineFromNamespace(machine, n) + h, output, machine, namespace := sharingWorker(cmd, args) + err := h.RemoveSharedMachineFromNamespace(machine, namespace) if strings.HasPrefix(output, "json") { JsonOutput(map[string]string{"Result": "Node unshared"}, err, output) return @@ -279,7 +273,7 @@ var unshareMachineCmd = &cobra.Command{ }, } -func nodesToPtables(currentNamespace headscale.Namespace, machines []headscale.Machine) (pterm.TableData, error) { +func nodesToPtables(currentNamespace *headscale.Namespace, machines []headscale.Machine) (pterm.TableData, error) { d := pterm.TableData{{"ID", "Name", "NodeKey", "Namespace", "IP address", "Ephemeral", "Last seen", "Online"}} for _, machine := range machines { @@ -307,9 +301,10 @@ func nodesToPtables(currentNamespace headscale.Namespace, machines []headscale.M } var namespace string - if currentNamespace.ID == machine.NamespaceID { + if (currentNamespace == nil) || (currentNamespace.ID == machine.NamespaceID) { namespace = pterm.LightMagenta(machine.Namespace.Name) } else { + // Shared into this namespace namespace = pterm.LightYellow(machine.Namespace.Name) } d = append(d, []string{strconv.FormatUint(machine.ID, 10), machine.Name, nodeKey.ShortString(), namespace, machine.IPAddress, strconv.FormatBool(ephemeral), lastSeenTime, online}) From 1d9954d8e9a247428121262383da2e6017a60bc7 Mon Sep 17 00:00:00 2001 From: Ward Vandewege Date: Sun, 24 Oct 2021 20:11:47 -0400 Subject: [PATCH 47/70] Fix integration test. --- integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_test.go b/integration_test.go index 53092423..a1a5e953 100644 --- a/integration_test.go +++ b/integration_test.go @@ -493,7 +493,7 @@ func (s *IntegrationTestSuite) TestSharedNodes() { result, err := executeCommand( &headscale, - []string{"headscale", "nodes", "share", "--namespace", "shared", fmt.Sprint(machine.ID), "main"}, + []string{"headscale", "nodes", "share", fmt.Sprint(machine.ID), "main"}, []string{}, ) assert.Nil(s.T(), err) From 4d3b638a3d2ce4d377cb2cdfef018812e7b0e0cf Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Mon, 25 Oct 2021 19:38:11 +0100 Subject: [PATCH 48/70] Add note about main containing unreleased changes #201 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a3c09396..91e13b92 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ An open source, self-hosted implementation of the Tailscale coordination server. Join our [Discord](https://discord.gg/XcQxk2VHjx) server for a chat. +**Note:** Always select the same GitHub tag as the released version you use to ensure you have the correct example configuration and documentation. The `main` branch might contain unreleased changes. + ## Overview Tailscale is [a modern VPN](https://tailscale.com/) built on top of [Wireguard](https://www.wireguard.com/). It [works like an overlay network](https://tailscale.com/blog/how-tailscale-works/) between the computers of your networks - using all kinds of [NAT traversal sorcery](https://tailscale.com/blog/how-nat-traversal-works/). From f9ece0087d0e0cbbf1ae726cce2eed340d53ed91 Mon Sep 17 00:00:00 2001 From: Ward Vandewege Date: Tue, 26 Oct 2021 08:50:25 -0400 Subject: [PATCH 49/70] Make the cli help a little more explicit for the nodes subcommand. --- cmd/headscale/cli/nodes.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go index 954e338e..ba843f70 100644 --- a/cmd/headscale/cli/nodes.go +++ b/cmd/headscale/cli/nodes.go @@ -17,9 +17,9 @@ import ( func init() { rootCmd.AddCommand(nodeCmd) - listNodesCmd.Flags().StringP("namespace", "n", "", "Namespace") + listNodesCmd.Flags().StringP("namespace", "n", "", "Filter by namespace") nodeCmd.AddCommand(listNodesCmd) - registerNodeCmd.Flags().StringP("namespace", "n", "", "Namespace") + registerNodeCmd.Flags().StringP("namespace", "n", "", "Filter by namespace") err := registerNodeCmd.MarkFlagRequired("namespace") if err != nil { log.Fatalf(err.Error()) From b096a2e7e50b4eca45caabeacc94a3a9fb8b9191 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 26 Oct 2021 20:37:37 +0000 Subject: [PATCH 50/70] Create an initial gRPC service This commit adds protobuf files and tooling surrounding generating APIs and datatypes. --- .gitignore | 3 ++ buf.gen.yaml | 21 ++++++++++++ proto/buf.lock | 24 ++++++++++++++ proto/buf.yaml | 12 +++++++ proto/v1/headscale.proto | 71 ++++++++++++++++++++++++++++++++++++++++ tools.go | 12 +++++++ 6 files changed, 143 insertions(+) create mode 100644 buf.gen.yaml create mode 100644 proto/buf.lock create mode 100644 proto/buf.yaml create mode 100644 proto/v1/headscale.proto create mode 100644 tools.go diff --git a/.gitignore b/.gitignore index 610550b9..f45c47a5 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ config.yaml .idea test_output/ + +# Protobuf generated code +gen/ diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 00000000..d7b832ab --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,21 @@ +version: v1 +plugins: + - name: go + out: gen/go + opt: + - paths=source_relative + - name: go-grpc + out: gen/go + opt: + - paths=source_relative + - name: grpc-gateway + out: gen/go + opt: + - paths=source_relative + - generate_unbound_methods=true + # - name: gorm + # out: gen/go + # opt: + # - paths=source_relative,enums=string,gateway=true + - name: openapiv2 + out: gen/openapiv2 diff --git a/proto/buf.lock b/proto/buf.lock new file mode 100644 index 00000000..03cd7b89 --- /dev/null +++ b/proto/buf.lock @@ -0,0 +1,24 @@ +# Generated by buf. DO NOT EDIT. +version: v1 +deps: + - remote: buf.build + owner: googleapis + repository: googleapis + branch: main + commit: cd101b0abb7b4404a0b1ecc1afd4ce10 + digest: b1-H4GHwHVHcJBbVPg-Cdmnx812reFCDQws_QoQ0W2hYQA= + create_time: 2021-10-23T15:04:06.087748Z + - remote: buf.build + owner: grpc-ecosystem + repository: grpc-gateway + branch: main + commit: ff83506eb9cc4cf8972f49ce87e6ed3e + digest: b1-iLPHgLaoeWWinMiXXqPnxqE4BThtY3eSbswVGh9GOGI= + create_time: 2021-10-23T16:26:52.283938Z + - remote: buf.build + owner: ufoundit-dev + repository: protoc-gen-gorm + branch: main + commit: e2ecbaa0d37843298104bd29fd866df8 + digest: b1-SV9yKH_8P-IKTOlHZxP-bb0ALANYeEqH_mtPA0EWfLc= + create_time: 2021-10-08T06:03:05.64876Z diff --git a/proto/buf.yaml b/proto/buf.yaml new file mode 100644 index 00000000..7e524ba0 --- /dev/null +++ b/proto/buf.yaml @@ -0,0 +1,12 @@ +version: v1 +lint: + use: + - DEFAULT +breaking: + use: + - FILE + +deps: + - buf.build/googleapis/googleapis + - buf.build/grpc-ecosystem/grpc-gateway + - buf.build/ufoundit-dev/protoc-gen-gorm diff --git a/proto/v1/headscale.proto b/proto/v1/headscale.proto new file mode 100644 index 00000000..b6356b8f --- /dev/null +++ b/proto/v1/headscale.proto @@ -0,0 +1,71 @@ +syntax = "proto3"; +package headscale.v1; +option go_package = "github.com/juanfont/headscale/gen/go/v1"; + +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; +import "options/gorm.proto"; + +enum RegisterMethod { + AUTH_KEY = 0; + CLI = 1; + OIDC = 2; +} + +message Namespace { + string Name = 1; +} + +message PreAuthKey { + uint64 ID = 1; + string Key = 2; + uint32 NamespaceID = 3; + Namespace Namespace = 4; + bool Reusable = 5; + bool Ephemeral = 6; + bool Used = 7; + + google.protobuf.Timestamp CreatedAt = 8; + google.protobuf.Timestamp Expiration = 9; +} + +message GetMachineRequest { + uint64 machine_id = 1; +} + +message Machine { + option(gorm.opts).ormable = true; + uint64 ID = 1; + string MachineKey = 2; + string NodeKey = 3; + string DiscoKey = 4; + string IPAddress = 5; + string Name = 6; + uint32 NamespaceID = 7; + + bool Registered = 8; + RegisterMethod RegisterMethod = 9; + uint32 AuthKeyID = 10; + PreAuthKey AuthKey = 11; + + google.protobuf.Timestamp LastSeen = 12; + google.protobuf.Timestamp LastSuccessfulUpdate = 13; + google.protobuf.Timestamp Expiry = 14; + + bytes HostInfo = 15; + bytes Endpoints = 16; + bytes EnabledRoutes = 17; + + google.protobuf.Timestamp CreatedAt = 18; + google.protobuf.Timestamp UpdatedAt = 19; + google.protobuf.Timestamp DeletedAt = 20; +} + +// Gin Router will prefix this with /api/v1 +service HeadscaleService { + rpc GetMachine(GetMachineRequest) returns(Machine) { + option(google.api.http) = { + get : "/api/v1/machine/{machine_id}" + }; + } +} diff --git a/tools.go b/tools.go new file mode 100644 index 00000000..287c1230 --- /dev/null +++ b/tools.go @@ -0,0 +1,12 @@ +//go:build tools +// +build tools + +package tools + +import ( + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway" + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2" + _ "github.com/infobloxopen/protoc-gen-gorm" + _ "google.golang.org/grpc/cmd/protoc-gen-go-grpc" + _ "google.golang.org/protobuf/cmd/protoc-gen-go" +) From a9da7c8fd9e59c4320fe4a2d2fc7a7406ba0f59f Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 26 Oct 2021 20:41:35 +0000 Subject: [PATCH 51/70] Update go.mod --- go.mod | 10 ++++++- go.sum | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 65165e0d..002e3ede 100644 --- a/go.mod +++ b/go.mod @@ -10,12 +10,14 @@ require ( github.com/docker/cli v20.10.8+incompatible // indirect github.com/docker/docker v20.10.8+incompatible // indirect github.com/efekarakus/termcolor v1.0.1 - github.com/fatih/set v0.2.1 // indirect + github.com/fatih/set v0.2.1 github.com/gin-gonic/gin v1.7.4 github.com/gofrs/uuid v4.0.0+incompatible github.com/google/go-github v17.0.0+incompatible // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0 github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b + github.com/infobloxopen/protoc-gen-gorm v1.0.1 github.com/klauspost/compress v1.13.5 github.com/lib/pq v1.10.3 // indirect github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect @@ -24,6 +26,7 @@ require ( github.com/prometheus/client_golang v1.11.0 github.com/pterm/pterm v0.12.30 github.com/rs/zerolog v1.25.0 + github.com/soheilhy/cmux v0.1.5 github.com/spf13/cobra v1.2.1 github.com/spf13/viper v1.8.1 github.com/stretchr/testify v1.7.0 @@ -33,7 +36,12 @@ require ( github.com/zsais/go-gin-prometheus v0.1.0 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 golang.org/x/net v0.0.0-20210913180222-943fd674d43e // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect + google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 + google.golang.org/grpc v1.40.0 + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 + google.golang.org/protobuf v1.27.1 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/yaml.v2 v2.4.0 gorm.io/datatypes v1.0.2 diff --git a/go.sum b/go.sum index b429ca95..9e31f2fe 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,7 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +contrib.go.opencensus.io/exporter/ocagent v0.7.0/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AlecAivazis/survey/v2 v2.3.2 h1:TqTB+aDDCLYhf9/bD2TwSO8u8jDSmMUd2SUVO4gCnU8= github.com/AlecAivazis/survey/v2 v2.3.2/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg= @@ -46,6 +47,7 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/Djarvur/go-err113 v0.1.0/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= @@ -70,6 +72,7 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEV github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -84,6 +87,7 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -105,12 +109,14 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/bufbuild/buf v0.37.0/go.mod h1:lQ1m2HkIaGOFba6w/aC3KYBHhKEOESP3gaAEpS3dAFM= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= @@ -134,6 +140,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= @@ -168,9 +175,12 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denis-tingajkin/go-header v0.3.1/go.mod h1:sq/2IxMhaZX+RRcgHfCRx/m0M5na0fBt4/CRe7Lrji0= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waNNZfHBM8= github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v3.2.1-0.20200107013213-dc14462fd587+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.8+incompatible h1:/zO/6y9IOpcehE49yMRTV9ea0nBpb8OeqSskXLNfH1E= @@ -199,7 +209,10 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= @@ -214,6 +227,7 @@ github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= @@ -249,6 +263,7 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+ github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -278,6 +293,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -370,6 +387,7 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gookit/color v1.3.1/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ= @@ -377,6 +395,7 @@ github.com/gookit/color v1.4.2 h1:tXy44JFSFkKnELV6WaMo/lLfu/meqITX3iAV52do7lk= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/goreleaser/chglog v0.1.2/go.mod h1:tTZsFuSZK4epDXfjMkxzcGbrIOXprf0JFp47BjIr3B8= github.com/goreleaser/fileglob v0.3.1/go.mod h1:kNcPrPzjCp+Ox3jmXLU5QEsjhqrtLBm6OnXAif8KRl8= github.com/goreleaser/nfpm v1.10.3/go.mod h1:EEC7YD5wi+ol0MiAshpgPANBOkjXDl7wqTLVk68OBsk= @@ -394,10 +413,17 @@ github.com/gostaticanalysis/comment v1.3.0/go.mod h1:xMicKDx7XRXYdVwY9f9wQpDJVnq github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.3.0/go.mod h1:d2gYTOTUQklu06xp0AJYYmRdTVU1VKrqhkYfYag2L08= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.4.0/go.mod h1:IOyTYjcIO0rkmnGBfJTL0NJ11exy/Tc2QEuv7hCXp24= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0 h1:rgxjzoDmDXw5q8HONgyHhBas4to0/XWRo/gPpJhsUNQ= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0/go.mod h1:qrJPVzv9YlhsrxJc3P/Q85nr0w1lIRikTl4JlhdDH5w= github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4= github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= @@ -441,6 +467,9 @@ github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/infobloxopen/atlas-app-toolkit v0.24.1-0.20210416193901-4c7518b07e08/go.mod h1:9BTHnpff654rY1J8KxSUOLJ+ZUDn2Vi3mmk26gQDo1M= +github.com/infobloxopen/protoc-gen-gorm v1.0.1 h1:IjvQ02gZSll+CjpWjxkLqrpxnvKAGfs5dXRJEpfZx2s= +github.com/infobloxopen/protoc-gen-gorm v1.0.1/go.mod h1:gTu86stnDQXwcNqLG9WNJfl3IPUIhxmGNqJ8z4826uo= github.com/insomniacslk/dhcp v0.0.0-20210621130208-1cac67f12b1e/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= @@ -508,9 +537,13 @@ github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jgautheron/goconst v0.0.0-20201117150253-ccae5bf973f3/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jhump/protoreflect v1.8.1/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= +github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= +github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= @@ -555,9 +588,11 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.5 h1:9O69jUPDcsT9fEm74W92rZL9FQY7rCdaXVneq+yyzl4= github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -580,8 +615,10 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.1-0.20200116171513-9eb3fc897d6f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg= github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= @@ -591,6 +628,7 @@ github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQ github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= @@ -620,7 +658,9 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= @@ -690,6 +730,7 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nishanths/exhaustive v0.1.0/go.mod h1:S1j9110vxV1ECdCudXRkeMnFQ/DQk9ajLT0Uf2MYZQQ= +github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= @@ -745,6 +786,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.0/go.mod h1:41g+FIPlQUTDCveupEmEA65IoiQFrtgCeDopC4ajGIM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -835,6 +877,7 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= @@ -842,6 +885,8 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= @@ -856,6 +901,7 @@ github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.0.1-0.20201006035406-b97b5ead31f7/go.mod h1:yk5b0mALVusDL5fMM6Rd1wgnoO5jUPhwsQ6LQAJTidQ= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= @@ -911,6 +957,7 @@ github.com/tomarrell/wrapcheck v0.0.0-20200807122107-df9e8bcb914d/go.mod h1:yiFB github.com/tomarrell/wrapcheck v0.0.0-20201130113247-1683564d9756/go.mod h1:yiFB6fFoV7saXirUGfuK+cPtUh4NX/Hf5y2WC2lehu0= github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= +github.com/twitchtv/twirp v7.1.0+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= @@ -966,7 +1013,9 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -980,6 +1029,7 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg= go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc= @@ -1001,6 +1051,7 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -1025,6 +1076,7 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1050,6 +1102,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1073,6 +1126,7 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1081,6 +1135,7 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -1094,6 +1149,7 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -1117,6 +1173,8 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1127,6 +1185,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1176,6 +1235,7 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1304,11 +1364,13 @@ golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200624225443-88f3c62a19ff/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -1357,6 +1419,7 @@ google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= @@ -1396,26 +1459,34 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210224155714-063164c882e6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210426193834-eac7f76ac494/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 h1:3V2dxSZpz4zozWWUq36vUxXEKnSYitEH2LdsAx+RUmg= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -1437,10 +1508,19 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0-dev.0.20201218190559-666aea1fb34c/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc/examples v0.0.0-20210309220351-d5b628860d4e/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE= +google.golang.org/grpc/examples v0.0.0-20210601155443-8bdcb4c9ab8d/go.mod h1:bF8wuZSAZTcbF7ZPKrDI/qY52toTP/yxLpRRY4Eu9Js= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1451,9 +1531,12 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.25.1-0.20201208041424-160c7477e0e8/go.mod h1:hFxJC2f0epmp1elRCiEGJTKAWbwxZ2nvqZdHl3FQXCY= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From caa4d33cbd1d3db42c21fd59ca72b3f469f41b3e Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 26 Oct 2021 20:42:20 +0000 Subject: [PATCH 52/70] Add an initial grpcv1 service (implementing the proto generated service) --- grpcv1.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 grpcv1.go diff --git a/grpcv1.go b/grpcv1.go new file mode 100644 index 00000000..d9bbf531 --- /dev/null +++ b/grpcv1.go @@ -0,0 +1,33 @@ +package headscale + +import ( + "context" + + apiV1 "github.com/juanfont/headscale/gen/go/v1" +) + +type headscaleV1APIServer struct { // apiV1.HeadscaleServiceServer + apiV1.UnimplementedHeadscaleServiceServer + h *Headscale +} + +func newHeadscaleV1APIServer(h *Headscale) apiV1.HeadscaleServiceServer { + return headscaleV1APIServer{ + h: h, + } +} + +func (api headscaleV1APIServer) GetMachine( + ctx context.Context, + request *apiV1.GetMachineRequest, +) (*apiV1.Machine, error) { + m, err := api.h.GetMachineByID(request.MachineId) + if err != nil { + return nil, err + } + + // TODO(kradalby): Make this function actually do something + return &apiV1.Machine{Name: m.Name}, nil +} + +func (api headscaleV1APIServer) mustEmbedUnimplementedHeadscaleServiceServer() {} From 2f045b20fbaf630d6bbeb0c28ad62856d014653a Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 26 Oct 2021 20:42:56 +0000 Subject: [PATCH 53/70] Refactor tls and wire up grpc, grpc gateway/api This commit moves the TLS configuration into a seperate function. It also wires up the gRPC interface and prepares handing the API endpoints to the grpc gateway. --- app.go | 127 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 105 insertions(+), 22 deletions(-) diff --git a/app.go b/app.go index 546eb866..dbe30168 100644 --- a/app.go +++ b/app.go @@ -1,8 +1,11 @@ package headscale import ( + "context" + "crypto/tls" "errors" "fmt" + "net" "net/http" "net/url" "os" @@ -11,12 +14,16 @@ import ( "sync" "time" - "github.com/rs/zerolog/log" - "github.com/gin-gonic/gin" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + apiV1 "github.com/juanfont/headscale/gen/go/v1" + "github.com/rs/zerolog/log" + "github.com/soheilhy/cmux" ginprometheus "github.com/zsais/go-gin-prometheus" "golang.org/x/crypto/acme" "golang.org/x/crypto/acme/autocert" + "golang.org/x/sync/errgroup" + "google.golang.org/grpc" "gorm.io/gorm" "inet.af/netaddr" "tailscale.com/tailcfg" @@ -24,7 +31,7 @@ import ( "tailscale.com/types/wgkey" ) -// Config contains the initial Headscale configuration +// Config contains the initial Headscale configuration. type Config struct { ServerURL string Addr string @@ -64,7 +71,7 @@ type DERPConfig struct { UpdateFrequency time.Duration } -// Headscale represents the base app of the service +// Headscale represents the base app of the service. type Headscale struct { cfg Config db *gorm.DB @@ -82,12 +89,13 @@ type Headscale struct { lastStateChange sync.Map } -// NewHeadscale returns the Headscale app +// NewHeadscale returns the Headscale app. func NewHeadscale(cfg Config) (*Headscale, error) { content, err := os.ReadFile(cfg.PrivateKeyPath) if err != nil { return nil, err } + privKey, err := wgkey.ParsePrivate(string(content)) if err != nil { return nil, err @@ -136,14 +144,14 @@ func NewHeadscale(cfg Config) (*Headscale, error) { return &h, nil } -// Redirect to our TLS url +// Redirect to our TLS url. func (h *Headscale) redirect(w http.ResponseWriter, req *http.Request) { target := h.cfg.ServerURL + req.URL.RequestURI() http.Redirect(w, req, target, http.StatusFound) } // expireEphemeralNodes deletes ephemeral machine records that have not been -// seen for longer than h.cfg.EphemeralNodeInactivityTimeout +// seen for longer than h.cfg.EphemeralNodeInactivityTimeout. func (h *Headscale) expireEphemeralNodes(milliSeconds int64) { ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond) for range ticker.C { @@ -155,18 +163,23 @@ func (h *Headscale) expireEphemeralNodesWorker() { namespaces, err := h.ListNamespaces() if err != nil { log.Error().Err(err).Msg("Error listing namespaces") + return } + for _, ns := range *namespaces { machines, err := h.ListMachinesInNamespace(ns.Name) if err != nil { log.Error().Err(err).Str("namespace", ns.Name).Msg("Error listing machines in namespace") + return } + for _, m := range *machines { if m.AuthKey != nil && m.LastSeen != nil && m.AuthKey.Ephemeral && time.Now().After(m.LastSeen.Add(h.cfg.EphemeralNodeInactivityTimeout)) { log.Info().Str("machine", m.Name).Msg("Ephemeral client removed from database") + err = h.db.Unscoped().Delete(m).Error if err != nil { log.Error(). @@ -176,12 +189,13 @@ func (h *Headscale) expireEphemeralNodesWorker() { } } } + h.setLastStateChangeToNow(ns.Name) } } // WatchForKVUpdates checks the KV DB table for requests to perform tailnet upgrades -// This is a way to communitate the CLI with the headscale server +// This is a way to communitate the CLI with the headscale server. func (h *Headscale) watchForKVUpdates(milliSeconds int64) { ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond) for range ticker.C { @@ -194,24 +208,60 @@ func (h *Headscale) watchForKVUpdatesWorker() { // more functions will come here in the future } -// Serve launches a GIN server with the Headscale API +// Serve launches a GIN server with the Headscale API. func (h *Headscale) Serve() error { + var err error + + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + + defer cancel() + + l, err := net.Listen("tcp", h.cfg.Addr) + if err != nil { + panic(err) + } + + // Create the cmux object that will multiplex 2 protocols on the same port. + // The two following listeners will be served on the same port below gracefully. + m := cmux.New(l) + // Match gRPC requests here + grpcListener := m.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc")) + // Otherwise match regular http requests. + httpListener := m.Match(cmux.Any()) + + // Now create the grpc server with those options. + grpcServer := grpc.NewServer() + + // TODO(kradalby): register the new server when we have authentication ready + // apiV1.RegisterHeadscaleServiceServer(grpcServer, newHeadscaleV1APIServer(h)) + + grpcGatewayMux := runtime.NewServeMux() + + opts := []grpc.DialOption{grpc.WithInsecure()} + + err = apiV1.RegisterHeadscaleServiceHandlerFromEndpoint(ctx, grpcGatewayMux, h.cfg.Addr, opts) + if err != nil { + return err + } + r := gin.Default() p := ginprometheus.NewPrometheus("gin") p.Use(r) - r.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"healthy": "ok"}) }) + r.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"healthy": "ok"}) }) r.GET("/key", h.KeyHandler) r.GET("/register", h.RegisterWebAPI) r.POST("/machine/:id/map", h.PollNetMapHandler) r.POST("/machine/:id", h.RegistrationHandler) r.GET("/apple", h.AppleMobileConfig) r.GET("/apple/:platform", h.ApplePlatformConfig) - var err error - go h.watchForKVUpdates(5000) - go h.expireEphemeralNodes(5000) + r.Any("/api/v1/*any", gin.WrapF(grpcGatewayMux.ServeHTTP)) + r.StaticFile("/swagger/swagger.json", "gen/openapiv2/v1/headscale.swagger.json") + + updateMillisecondsWait := int64(5000) // Fetch an initial DERP Map before we start serving h.DERPMap = GetDERPMap(h.cfg.DERP) @@ -222,7 +272,11 @@ func (h *Headscale) Serve() error { go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel) } - s := &http.Server{ + // I HATE THIS + go h.watchForKVUpdates(updateMillisecondsWait) + go h.expireEphemeralNodes(updateMillisecondsWait) + + httpServer := &http.Server{ Addr: h.cfg.Addr, Handler: r, ReadTimeout: 30 * time.Second, @@ -233,6 +287,29 @@ func (h *Headscale) Serve() error { WriteTimeout: 0, } + tlsConfig, err := h.getTLSSettings() + if err != nil { + log.Error().Err(err).Msg("Failed to set up TLS configuration") + + return err + } + + if tlsConfig != nil { + httpServer.TLSConfig = tlsConfig + } + + g := new(errgroup.Group) + + g.Go(func() error { return grpcServer.Serve(grpcListener) }) + g.Go(func() error { return httpServer.Serve(httpListener) }) + g.Go(func() error { return m.Serve() }) + + log.Info().Msgf("listening and serving (multiplexed HTTP and gRPC) on: %s", h.cfg.Addr) + + return g.Wait() +} + +func (h *Headscale) getTLSSettings() (*tls.Config, error) { if h.cfg.TLSLetsEncryptHostname != "" { if !strings.HasPrefix(h.cfg.ServerURL, "https://") { log.Warn().Msg("Listening with TLS but ServerURL does not start with https://") @@ -248,13 +325,11 @@ func (h *Headscale) Serve() error { Email: h.cfg.ACMEEmail, } - s.TLSConfig = m.TLSConfig() - if h.cfg.TLSLetsEncryptChallengeType == "TLS-ALPN-01" { // Configuration via autocert with TLS-ALPN-01 (https://tools.ietf.org/html/rfc8737) // The RFC requires that the validation is done on port 443; in other words, headscale // must be reachable on port 443. - err = s.ListenAndServeTLS("", "") + return m.TLSConfig(), nil } else if h.cfg.TLSLetsEncryptChallengeType == "HTTP-01" { // Configuration via autocert with HTTP-01. This requires listening on // port 80 for the certificate validation in addition to the headscale @@ -264,22 +339,30 @@ func (h *Headscale) Serve() error { Err(http.ListenAndServe(h.cfg.TLSLetsEncryptListen, m.HTTPHandler(http.HandlerFunc(h.redirect)))). Msg("failed to set up a HTTP server") }() - err = s.ListenAndServeTLS("", "") + + return m.TLSConfig(), nil } else { - return errors.New("unknown value for TLSLetsEncryptChallengeType") + return nil, errors.New("unknown value for TLSLetsEncryptChallengeType") } } else if h.cfg.TLSCertPath == "" { if !strings.HasPrefix(h.cfg.ServerURL, "http://") { log.Warn().Msg("Listening without TLS but ServerURL does not start with http://") } - err = s.ListenAndServe() + + return nil, nil } else { if !strings.HasPrefix(h.cfg.ServerURL, "https://") { log.Warn().Msg("Listening with TLS but ServerURL does not start with https://") } - err = s.ListenAndServeTLS(h.cfg.TLSCertPath, h.cfg.TLSKeyPath) + var err error + tlsConfig := &tls.Config{} + tlsConfig.ClientAuth = tls.RequireAnyClientCert + tlsConfig.NextProtos = []string{"http/1.1"} + tlsConfig.Certificates = make([]tls.Certificate, 1) + tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(h.cfg.TLSCertPath, h.cfg.TLSKeyPath) + + return tlsConfig, err } - return err } func (h *Headscale) setLastStateChangeToNow(namespace string) { From b8c89cd63ca413bcce3a217779d8fb2f7aabc62a Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 26 Oct 2021 20:53:10 +0000 Subject: [PATCH 54/70] Add readme and makefile entry about code generation --- Makefile | 5 ++++- README.md | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 755253fc..4c1d6134 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # Calculate version version = $(shell ./scripts/version-at-commit.sh) -build: +build: generate go build -ldflags "-s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=$(version)" cmd/headscale/headscale.go dev: lint test build @@ -25,3 +25,6 @@ lint: compress: build upx --brute headscale +generate: + rm -rf gen + buf generate proto diff --git a/README.md b/README.md index 91e13b92..4e373315 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,41 @@ Please have a look at the documentation under [`docs/`](docs/). 1. We have nothing to do with Tailscale, or Tailscale Inc. 2. The purpose of writing this was to learn how Tailscale works. +## Contributing + +To contribute to Headscale you would need the lastest version of [Go](golang.org) and [Buf](https://buf.build)(Protobuf generator). + +### Install development tools + +- Go +- Buf +- Protobuf tools: + +```shell +go install \ + github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \ + github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \ + google.golang.org/protobuf/cmd/protoc-gen-go \ + google.golang.org/grpc/cmd/protoc-gen-go-grpc +``` + +Building the project requires the generation of Go code from Protobuf (in `proto/`) and it can be (re-)generated with: + +```shell +make generate +``` + +To run the tests: + +```shell +make test +``` + +To build the program: + +```shell +make build +``` ## Contributors From 11d987549fb0d234e1695325d9b27c8e15595386 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 26 Oct 2021 21:34:51 +0000 Subject: [PATCH 55/70] Ignore generated files for docker --- .dockerignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index f90134b3..b7a5c8a8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,4 +14,4 @@ docker-compose* README.md LICENSE .vscode - +gen/ From 6e764942a2c084e803c26ad749b0b384cc86e0e0 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 26 Oct 2021 21:35:18 +0000 Subject: [PATCH 56/70] Add grpc step to dockerfile --- Dockerfile | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6e216aad..ba248d35 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,23 @@ +FROM bufbuild/buf:1.0.0-rc6 as buf + FROM golang:1.17.1-bullseye AS build ENV GOPATH /go +COPY --from=buf /usr/local/bin/buf /usr/local/bin/buf + COPY go.mod go.sum /go/src/headscale/ WORKDIR /go/src/headscale RUN go mod download -COPY . /go/src/headscale +COPY . . + +RUN go install \ + github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \ + github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \ + google.golang.org/protobuf/cmd/protoc-gen-go \ + google.golang.org/grpc/cmd/protoc-gen-go-grpc + +RUN buf generate proto RUN go install -a -ldflags="-extldflags=-static" -tags netgo,sqlite_omit_load_extension ./cmd/headscale RUN test -e /go/bin/headscale From 8f2ef6a57de373cdeb5fb2236dbfd233bb57a15f Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 27 Oct 2021 06:40:39 +0000 Subject: [PATCH 57/70] Prepare for checking in generated code --- .dockerignore | 1 - .gitignore | 3 --- Dockerfile | 12 +----------- Makefile | 7 +++++++ README.md | 11 +++++------ 5 files changed, 13 insertions(+), 21 deletions(-) diff --git a/.dockerignore b/.dockerignore index b7a5c8a8..33f9aea2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,4 +14,3 @@ docker-compose* README.md LICENSE .vscode -gen/ diff --git a/.gitignore b/.gitignore index f45c47a5..610550b9 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,3 @@ config.yaml .idea test_output/ - -# Protobuf generated code -gen/ diff --git a/Dockerfile b/Dockerfile index ba248d35..9590070b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,23 +2,13 @@ FROM bufbuild/buf:1.0.0-rc6 as buf FROM golang:1.17.1-bullseye AS build ENV GOPATH /go - -COPY --from=buf /usr/local/bin/buf /usr/local/bin/buf +WORKDIR /go/src/headscale COPY go.mod go.sum /go/src/headscale/ -WORKDIR /go/src/headscale RUN go mod download COPY . . -RUN go install \ - github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \ - github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \ - google.golang.org/protobuf/cmd/protoc-gen-go \ - google.golang.org/grpc/cmd/protoc-gen-go-grpc - -RUN buf generate proto - RUN go install -a -ldflags="-extldflags=-static" -tags netgo,sqlite_omit_load_extension ./cmd/headscale RUN test -e /go/bin/headscale diff --git a/Makefile b/Makefile index 4c1d6134..3ce7025c 100644 --- a/Makefile +++ b/Makefile @@ -28,3 +28,10 @@ compress: build generate: rm -rf gen buf generate proto + +install-protobuf-plugins: + go install \ + github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \ + github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \ + google.golang.org/protobuf/cmd/protoc-gen-go \ + google.golang.org/grpc/cmd/protoc-gen-go-grpc diff --git a/README.md b/README.md index 4e373315..060824b9 100644 --- a/README.md +++ b/README.md @@ -71,18 +71,17 @@ To contribute to Headscale you would need the lastest version of [Go](golang.org - Protobuf tools: ```shell -go install \ - github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \ - github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \ - google.golang.org/protobuf/cmd/protoc-gen-go \ - google.golang.org/grpc/cmd/protoc-gen-go-grpc +make install-protobuf-plugins ``` -Building the project requires the generation of Go code from Protobuf (in `proto/`) and it can be (re-)generated with: +### Testing and building + +Some parts of the project requires the generation of Go code from Protobuf (if changes is made in `proto/`) and it must be (re-)generated with: ```shell make generate ``` +**Note**: Please check in changes from `gen/` in a separate commit to make it easier to review. To run the tests: From d4265779ef81fb083fb97ccf73e5b74e3601b2f4 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 27 Oct 2021 06:44:04 +0000 Subject: [PATCH 58/70] Check in generated code This does not have to be reviewed, here is some reasoning: Go (and go mod) is designed for having code available and we need to check in the generated code to make sure it is "go gettable". If we dont we give ourselves a headache trying to setup all the ci, tests etc to install and generate the code before it runs. Because the code isnt there, the plugins needed to generate the code fail to install... I didnt find any good documentation for this, but there is this github comment: https://github.com/golang/go/issues/34514#issuecomment-535406759 --- gen/go/v1/headscale.pb.go | 702 ++++++++++++++++++++++++ gen/go/v1/headscale.pb.gw.go | 185 +++++++ gen/go/v1/headscale_grpc.pb.go | 101 ++++ gen/openapiv2/v1/headscale.swagger.json | 210 +++++++ 4 files changed, 1198 insertions(+) create mode 100644 gen/go/v1/headscale.pb.go create mode 100644 gen/go/v1/headscale.pb.gw.go create mode 100644 gen/go/v1/headscale_grpc.pb.go create mode 100644 gen/openapiv2/v1/headscale.swagger.json diff --git a/gen/go/v1/headscale.pb.go b/gen/go/v1/headscale.pb.go new file mode 100644 index 00000000..104e3746 --- /dev/null +++ b/gen/go/v1/headscale.pb.go @@ -0,0 +1,702 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.18.1 +// source: v1/headscale.proto + +package v1 + +import ( + _ "github.com/infobloxopen/protoc-gen-gorm/options" + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type RegisterMethod int32 + +const ( + RegisterMethod_AUTH_KEY RegisterMethod = 0 + RegisterMethod_CLI RegisterMethod = 1 + RegisterMethod_OIDC RegisterMethod = 2 +) + +// Enum value maps for RegisterMethod. +var ( + RegisterMethod_name = map[int32]string{ + 0: "AUTH_KEY", + 1: "CLI", + 2: "OIDC", + } + RegisterMethod_value = map[string]int32{ + "AUTH_KEY": 0, + "CLI": 1, + "OIDC": 2, + } +) + +func (x RegisterMethod) Enum() *RegisterMethod { + p := new(RegisterMethod) + *p = x + return p +} + +func (x RegisterMethod) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (RegisterMethod) Descriptor() protoreflect.EnumDescriptor { + return file_v1_headscale_proto_enumTypes[0].Descriptor() +} + +func (RegisterMethod) Type() protoreflect.EnumType { + return &file_v1_headscale_proto_enumTypes[0] +} + +func (x RegisterMethod) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use RegisterMethod.Descriptor instead. +func (RegisterMethod) EnumDescriptor() ([]byte, []int) { + return file_v1_headscale_proto_rawDescGZIP(), []int{0} +} + +type Namespace struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"` +} + +func (x *Namespace) Reset() { + *x = Namespace{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_headscale_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Namespace) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Namespace) ProtoMessage() {} + +func (x *Namespace) ProtoReflect() protoreflect.Message { + mi := &file_v1_headscale_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Namespace.ProtoReflect.Descriptor instead. +func (*Namespace) Descriptor() ([]byte, []int) { + return file_v1_headscale_proto_rawDescGZIP(), []int{0} +} + +func (x *Namespace) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type PreAuthKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` + Key string `protobuf:"bytes,2,opt,name=Key,proto3" json:"Key,omitempty"` + NamespaceID uint32 `protobuf:"varint,3,opt,name=NamespaceID,proto3" json:"NamespaceID,omitempty"` + Namespace *Namespace `protobuf:"bytes,4,opt,name=Namespace,proto3" json:"Namespace,omitempty"` + Reusable bool `protobuf:"varint,5,opt,name=Reusable,proto3" json:"Reusable,omitempty"` + Ephemeral bool `protobuf:"varint,6,opt,name=Ephemeral,proto3" json:"Ephemeral,omitempty"` + Used bool `protobuf:"varint,7,opt,name=Used,proto3" json:"Used,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"` + Expiration *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=Expiration,proto3" json:"Expiration,omitempty"` +} + +func (x *PreAuthKey) Reset() { + *x = PreAuthKey{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_headscale_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PreAuthKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PreAuthKey) ProtoMessage() {} + +func (x *PreAuthKey) ProtoReflect() protoreflect.Message { + mi := &file_v1_headscale_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PreAuthKey.ProtoReflect.Descriptor instead. +func (*PreAuthKey) Descriptor() ([]byte, []int) { + return file_v1_headscale_proto_rawDescGZIP(), []int{1} +} + +func (x *PreAuthKey) GetID() uint64 { + if x != nil { + return x.ID + } + return 0 +} + +func (x *PreAuthKey) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *PreAuthKey) GetNamespaceID() uint32 { + if x != nil { + return x.NamespaceID + } + return 0 +} + +func (x *PreAuthKey) GetNamespace() *Namespace { + if x != nil { + return x.Namespace + } + return nil +} + +func (x *PreAuthKey) GetReusable() bool { + if x != nil { + return x.Reusable + } + return false +} + +func (x *PreAuthKey) GetEphemeral() bool { + if x != nil { + return x.Ephemeral + } + return false +} + +func (x *PreAuthKey) GetUsed() bool { + if x != nil { + return x.Used + } + return false +} + +func (x *PreAuthKey) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *PreAuthKey) GetExpiration() *timestamppb.Timestamp { + if x != nil { + return x.Expiration + } + return nil +} + +type GetMachineRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MachineId uint64 `protobuf:"varint,1,opt,name=machine_id,json=machineId,proto3" json:"machine_id,omitempty"` +} + +func (x *GetMachineRequest) Reset() { + *x = GetMachineRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_headscale_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetMachineRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetMachineRequest) ProtoMessage() {} + +func (x *GetMachineRequest) ProtoReflect() protoreflect.Message { + mi := &file_v1_headscale_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetMachineRequest.ProtoReflect.Descriptor instead. +func (*GetMachineRequest) Descriptor() ([]byte, []int) { + return file_v1_headscale_proto_rawDescGZIP(), []int{2} +} + +func (x *GetMachineRequest) GetMachineId() uint64 { + if x != nil { + return x.MachineId + } + return 0 +} + +type Machine struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` + MachineKey string `protobuf:"bytes,2,opt,name=MachineKey,proto3" json:"MachineKey,omitempty"` + NodeKey string `protobuf:"bytes,3,opt,name=NodeKey,proto3" json:"NodeKey,omitempty"` + DiscoKey string `protobuf:"bytes,4,opt,name=DiscoKey,proto3" json:"DiscoKey,omitempty"` + IPAddress string `protobuf:"bytes,5,opt,name=IPAddress,proto3" json:"IPAddress,omitempty"` + Name string `protobuf:"bytes,6,opt,name=Name,proto3" json:"Name,omitempty"` + NamespaceID uint32 `protobuf:"varint,7,opt,name=NamespaceID,proto3" json:"NamespaceID,omitempty"` + Registered bool `protobuf:"varint,8,opt,name=Registered,proto3" json:"Registered,omitempty"` + RegisterMethod RegisterMethod `protobuf:"varint,9,opt,name=RegisterMethod,proto3,enum=headscale.v1.RegisterMethod" json:"RegisterMethod,omitempty"` + AuthKeyID uint32 `protobuf:"varint,10,opt,name=AuthKeyID,proto3" json:"AuthKeyID,omitempty"` + AuthKey *PreAuthKey `protobuf:"bytes,11,opt,name=AuthKey,proto3" json:"AuthKey,omitempty"` + LastSeen *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=LastSeen,proto3" json:"LastSeen,omitempty"` + LastSuccessfulUpdate *timestamppb.Timestamp `protobuf:"bytes,13,opt,name=LastSuccessfulUpdate,proto3" json:"LastSuccessfulUpdate,omitempty"` + Expiry *timestamppb.Timestamp `protobuf:"bytes,14,opt,name=Expiry,proto3" json:"Expiry,omitempty"` + HostInfo []byte `protobuf:"bytes,15,opt,name=HostInfo,proto3" json:"HostInfo,omitempty"` + Endpoints []byte `protobuf:"bytes,16,opt,name=Endpoints,proto3" json:"Endpoints,omitempty"` + EnabledRoutes []byte `protobuf:"bytes,17,opt,name=EnabledRoutes,proto3" json:"EnabledRoutes,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,18,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,19,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"` + DeletedAt *timestamppb.Timestamp `protobuf:"bytes,20,opt,name=DeletedAt,proto3" json:"DeletedAt,omitempty"` +} + +func (x *Machine) Reset() { + *x = Machine{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_headscale_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Machine) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Machine) ProtoMessage() {} + +func (x *Machine) ProtoReflect() protoreflect.Message { + mi := &file_v1_headscale_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Machine.ProtoReflect.Descriptor instead. +func (*Machine) Descriptor() ([]byte, []int) { + return file_v1_headscale_proto_rawDescGZIP(), []int{3} +} + +func (x *Machine) GetID() uint64 { + if x != nil { + return x.ID + } + return 0 +} + +func (x *Machine) GetMachineKey() string { + if x != nil { + return x.MachineKey + } + return "" +} + +func (x *Machine) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *Machine) GetDiscoKey() string { + if x != nil { + return x.DiscoKey + } + return "" +} + +func (x *Machine) GetIPAddress() string { + if x != nil { + return x.IPAddress + } + return "" +} + +func (x *Machine) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Machine) GetNamespaceID() uint32 { + if x != nil { + return x.NamespaceID + } + return 0 +} + +func (x *Machine) GetRegistered() bool { + if x != nil { + return x.Registered + } + return false +} + +func (x *Machine) GetRegisterMethod() RegisterMethod { + if x != nil { + return x.RegisterMethod + } + return RegisterMethod_AUTH_KEY +} + +func (x *Machine) GetAuthKeyID() uint32 { + if x != nil { + return x.AuthKeyID + } + return 0 +} + +func (x *Machine) GetAuthKey() *PreAuthKey { + if x != nil { + return x.AuthKey + } + return nil +} + +func (x *Machine) GetLastSeen() *timestamppb.Timestamp { + if x != nil { + return x.LastSeen + } + return nil +} + +func (x *Machine) GetLastSuccessfulUpdate() *timestamppb.Timestamp { + if x != nil { + return x.LastSuccessfulUpdate + } + return nil +} + +func (x *Machine) GetExpiry() *timestamppb.Timestamp { + if x != nil { + return x.Expiry + } + return nil +} + +func (x *Machine) GetHostInfo() []byte { + if x != nil { + return x.HostInfo + } + return nil +} + +func (x *Machine) GetEndpoints() []byte { + if x != nil { + return x.Endpoints + } + return nil +} + +func (x *Machine) GetEnabledRoutes() []byte { + if x != nil { + return x.EnabledRoutes + } + return nil +} + +func (x *Machine) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *Machine) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +func (x *Machine) GetDeletedAt() *timestamppb.Timestamp { + if x != nil { + return x.DeletedAt + } + return nil +} + +var File_v1_headscale_proto protoreflect.FileDescriptor + +var file_v1_headscale_proto_rawDesc = []byte{ + 0x0a, 0x12, 0x76, 0x31, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, + 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x1a, 0x12, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x67, 0x6f, 0x72, 0x6d, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1f, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0xcb, 0x02, 0x0a, 0x0a, 0x50, 0x72, 0x65, 0x41, 0x75, + 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x02, 0x49, 0x44, 0x12, 0x10, 0x0a, 0x03, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x4e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x44, 0x12, 0x35, 0x0a, 0x09, 0x4e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, + 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x12, 0x1a, 0x0a, 0x08, 0x52, 0x65, 0x75, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x08, 0x52, 0x65, 0x75, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1c, 0x0a, 0x09, + 0x45, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x09, 0x45, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x55, 0x73, + 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x55, 0x73, 0x65, 0x64, 0x12, 0x38, + 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x45, 0x78, 0x70, 0x69, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x32, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, + 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, + 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, + 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x22, 0xcd, 0x06, 0x0a, 0x07, 0x4d, 0x61, 0x63, + 0x68, 0x69, 0x6e, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x02, 0x49, 0x44, 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4b, + 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, + 0x65, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x4e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1a, + 0x0a, 0x08, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x4b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x49, 0x50, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x49, + 0x50, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, + 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x44, 0x12, 0x1e, + 0x0a, 0x0a, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0a, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x44, + 0x0a, 0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, + 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x49, + 0x44, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, + 0x49, 0x44, 0x12, 0x32, 0x0a, 0x07, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x07, 0x41, + 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x08, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, + 0x65, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x12, 0x4e, + 0x0a, 0x14, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x14, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x75, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32, + 0x0a, 0x06, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x45, 0x78, 0x70, 0x69, + 0x72, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x0f, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1c, + 0x0a, 0x09, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x09, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x24, 0x0a, 0x0d, + 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x11, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x73, 0x12, 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, + 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x3a, 0x06, 0xba, 0xb9, 0x19, 0x02, 0x08, 0x01, 0x2a, 0x31, 0x0a, 0x0e, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x55, + 0x54, 0x48, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x43, 0x4c, 0x49, 0x10, + 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4f, 0x49, 0x44, 0x43, 0x10, 0x02, 0x32, 0x7e, 0x0a, 0x10, 0x48, + 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x6a, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x1f, 0x2e, + 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, + 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, + 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x7b, + 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x42, 0x29, 0x5a, 0x27, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, + 0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, + 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_v1_headscale_proto_rawDescOnce sync.Once + file_v1_headscale_proto_rawDescData = file_v1_headscale_proto_rawDesc +) + +func file_v1_headscale_proto_rawDescGZIP() []byte { + file_v1_headscale_proto_rawDescOnce.Do(func() { + file_v1_headscale_proto_rawDescData = protoimpl.X.CompressGZIP(file_v1_headscale_proto_rawDescData) + }) + return file_v1_headscale_proto_rawDescData +} + +var file_v1_headscale_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_v1_headscale_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_v1_headscale_proto_goTypes = []interface{}{ + (RegisterMethod)(0), // 0: headscale.v1.RegisterMethod + (*Namespace)(nil), // 1: headscale.v1.Namespace + (*PreAuthKey)(nil), // 2: headscale.v1.PreAuthKey + (*GetMachineRequest)(nil), // 3: headscale.v1.GetMachineRequest + (*Machine)(nil), // 4: headscale.v1.Machine + (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp +} +var file_v1_headscale_proto_depIdxs = []int32{ + 1, // 0: headscale.v1.PreAuthKey.Namespace:type_name -> headscale.v1.Namespace + 5, // 1: headscale.v1.PreAuthKey.CreatedAt:type_name -> google.protobuf.Timestamp + 5, // 2: headscale.v1.PreAuthKey.Expiration:type_name -> google.protobuf.Timestamp + 0, // 3: headscale.v1.Machine.RegisterMethod:type_name -> headscale.v1.RegisterMethod + 2, // 4: headscale.v1.Machine.AuthKey:type_name -> headscale.v1.PreAuthKey + 5, // 5: headscale.v1.Machine.LastSeen:type_name -> google.protobuf.Timestamp + 5, // 6: headscale.v1.Machine.LastSuccessfulUpdate:type_name -> google.protobuf.Timestamp + 5, // 7: headscale.v1.Machine.Expiry:type_name -> google.protobuf.Timestamp + 5, // 8: headscale.v1.Machine.CreatedAt:type_name -> google.protobuf.Timestamp + 5, // 9: headscale.v1.Machine.UpdatedAt:type_name -> google.protobuf.Timestamp + 5, // 10: headscale.v1.Machine.DeletedAt:type_name -> google.protobuf.Timestamp + 3, // 11: headscale.v1.HeadscaleService.GetMachine:input_type -> headscale.v1.GetMachineRequest + 4, // 12: headscale.v1.HeadscaleService.GetMachine:output_type -> headscale.v1.Machine + 12, // [12:13] is the sub-list for method output_type + 11, // [11:12] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name +} + +func init() { file_v1_headscale_proto_init() } +func file_v1_headscale_proto_init() { + if File_v1_headscale_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_v1_headscale_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Namespace); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_headscale_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PreAuthKey); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_headscale_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetMachineRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_headscale_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Machine); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_v1_headscale_proto_rawDesc, + NumEnums: 1, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_v1_headscale_proto_goTypes, + DependencyIndexes: file_v1_headscale_proto_depIdxs, + EnumInfos: file_v1_headscale_proto_enumTypes, + MessageInfos: file_v1_headscale_proto_msgTypes, + }.Build() + File_v1_headscale_proto = out.File + file_v1_headscale_proto_rawDesc = nil + file_v1_headscale_proto_goTypes = nil + file_v1_headscale_proto_depIdxs = nil +} diff --git a/gen/go/v1/headscale.pb.gw.go b/gen/go/v1/headscale.pb.gw.go new file mode 100644 index 00000000..4ae6db39 --- /dev/null +++ b/gen/go/v1/headscale.pb.gw.go @@ -0,0 +1,185 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: v1/headscale.proto + +/* +Package v1 is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package v1 + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_HeadscaleService_GetMachine_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetMachineRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["machine_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "machine_id") + } + + protoReq.MachineId, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "machine_id", err) + } + + msg, err := client.GetMachine(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_HeadscaleService_GetMachine_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetMachineRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["machine_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "machine_id") + } + + protoReq.MachineId, err = runtime.Uint64(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "machine_id", err) + } + + msg, err := server.GetMachine(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterHeadscaleServiceHandlerServer registers the http handlers for service HeadscaleService to "mux". +// UnaryRPC :call HeadscaleServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterHeadscaleServiceHandlerFromEndpoint instead. +func RegisterHeadscaleServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server HeadscaleServiceServer) error { + + mux.Handle("GET", pattern_HeadscaleService_GetMachine_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/GetMachine", runtime.WithHTTPPathPattern("/api/v1/machine/{machine_id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_HeadscaleService_GetMachine_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_HeadscaleService_GetMachine_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterHeadscaleServiceHandlerFromEndpoint is same as RegisterHeadscaleServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterHeadscaleServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterHeadscaleServiceHandler(ctx, mux, conn) +} + +// RegisterHeadscaleServiceHandler registers the http handlers for service HeadscaleService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterHeadscaleServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterHeadscaleServiceHandlerClient(ctx, mux, NewHeadscaleServiceClient(conn)) +} + +// RegisterHeadscaleServiceHandlerClient registers the http handlers for service HeadscaleService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "HeadscaleServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "HeadscaleServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "HeadscaleServiceClient" to call the correct interceptors. +func RegisterHeadscaleServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client HeadscaleServiceClient) error { + + mux.Handle("GET", pattern_HeadscaleService_GetMachine_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/GetMachine", runtime.WithHTTPPathPattern("/api/v1/machine/{machine_id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_HeadscaleService_GetMachine_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_HeadscaleService_GetMachine_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_HeadscaleService_GetMachine_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "machine", "machine_id"}, "")) +) + +var ( + forward_HeadscaleService_GetMachine_0 = runtime.ForwardResponseMessage +) diff --git a/gen/go/v1/headscale_grpc.pb.go b/gen/go/v1/headscale_grpc.pb.go new file mode 100644 index 00000000..3028d183 --- /dev/null +++ b/gen/go/v1/headscale_grpc.pb.go @@ -0,0 +1,101 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package v1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// HeadscaleServiceClient is the client API for HeadscaleService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type HeadscaleServiceClient interface { + GetMachine(ctx context.Context, in *GetMachineRequest, opts ...grpc.CallOption) (*Machine, error) +} + +type headscaleServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewHeadscaleServiceClient(cc grpc.ClientConnInterface) HeadscaleServiceClient { + return &headscaleServiceClient{cc} +} + +func (c *headscaleServiceClient) GetMachine(ctx context.Context, in *GetMachineRequest, opts ...grpc.CallOption) (*Machine, error) { + out := new(Machine) + err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/GetMachine", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// HeadscaleServiceServer is the server API for HeadscaleService service. +// All implementations must embed UnimplementedHeadscaleServiceServer +// for forward compatibility +type HeadscaleServiceServer interface { + GetMachine(context.Context, *GetMachineRequest) (*Machine, error) + mustEmbedUnimplementedHeadscaleServiceServer() +} + +// UnimplementedHeadscaleServiceServer must be embedded to have forward compatible implementations. +type UnimplementedHeadscaleServiceServer struct { +} + +func (UnimplementedHeadscaleServiceServer) GetMachine(context.Context, *GetMachineRequest) (*Machine, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetMachine not implemented") +} +func (UnimplementedHeadscaleServiceServer) mustEmbedUnimplementedHeadscaleServiceServer() {} + +// UnsafeHeadscaleServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to HeadscaleServiceServer will +// result in compilation errors. +type UnsafeHeadscaleServiceServer interface { + mustEmbedUnimplementedHeadscaleServiceServer() +} + +func RegisterHeadscaleServiceServer(s grpc.ServiceRegistrar, srv HeadscaleServiceServer) { + s.RegisterService(&HeadscaleService_ServiceDesc, srv) +} + +func _HeadscaleService_GetMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetMachineRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeadscaleServiceServer).GetMachine(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/headscale.v1.HeadscaleService/GetMachine", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeadscaleServiceServer).GetMachine(ctx, req.(*GetMachineRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// HeadscaleService_ServiceDesc is the grpc.ServiceDesc for HeadscaleService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var HeadscaleService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "headscale.v1.HeadscaleService", + HandlerType: (*HeadscaleServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetMachine", + Handler: _HeadscaleService_GetMachine_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "v1/headscale.proto", +} diff --git a/gen/openapiv2/v1/headscale.swagger.json b/gen/openapiv2/v1/headscale.swagger.json new file mode 100644 index 00000000..a20225d3 --- /dev/null +++ b/gen/openapiv2/v1/headscale.swagger.json @@ -0,0 +1,210 @@ +{ + "swagger": "2.0", + "info": { + "title": "v1/headscale.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "HeadscaleService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/api/v1/machine/{machineId}": { + "get": { + "operationId": "HeadscaleService_GetMachine", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1Machine" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "machineId", + "in": "path", + "required": true, + "type": "string", + "format": "uint64" + } + ], + "tags": [ + "HeadscaleService" + ] + } + } + }, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "v1Machine": { + "type": "object", + "properties": { + "ID": { + "type": "string", + "format": "uint64" + }, + "MachineKey": { + "type": "string" + }, + "NodeKey": { + "type": "string" + }, + "DiscoKey": { + "type": "string" + }, + "IPAddress": { + "type": "string" + }, + "Name": { + "type": "string" + }, + "NamespaceID": { + "type": "integer", + "format": "int64" + }, + "Registered": { + "type": "boolean" + }, + "RegisterMethod": { + "$ref": "#/definitions/v1RegisterMethod" + }, + "AuthKeyID": { + "type": "integer", + "format": "int64" + }, + "AuthKey": { + "$ref": "#/definitions/v1PreAuthKey" + }, + "LastSeen": { + "type": "string", + "format": "date-time" + }, + "LastSuccessfulUpdate": { + "type": "string", + "format": "date-time" + }, + "Expiry": { + "type": "string", + "format": "date-time" + }, + "HostInfo": { + "type": "string", + "format": "byte" + }, + "Endpoints": { + "type": "string", + "format": "byte" + }, + "EnabledRoutes": { + "type": "string", + "format": "byte" + }, + "CreatedAt": { + "type": "string", + "format": "date-time" + }, + "UpdatedAt": { + "type": "string", + "format": "date-time" + }, + "DeletedAt": { + "type": "string", + "format": "date-time" + } + } + }, + "v1Namespace": { + "type": "object", + "properties": { + "Name": { + "type": "string" + } + } + }, + "v1PreAuthKey": { + "type": "object", + "properties": { + "ID": { + "type": "string", + "format": "uint64" + }, + "Key": { + "type": "string" + }, + "NamespaceID": { + "type": "integer", + "format": "int64" + }, + "Namespace": { + "$ref": "#/definitions/v1Namespace" + }, + "Reusable": { + "type": "boolean" + }, + "Ephemeral": { + "type": "boolean" + }, + "Used": { + "type": "boolean" + }, + "CreatedAt": { + "type": "string", + "format": "date-time" + }, + "Expiration": { + "type": "string", + "format": "date-time" + } + } + }, + "v1RegisterMethod": { + "type": "string", + "enum": [ + "AUTH_KEY", + "CLI", + "OIDC" + ], + "default": "AUTH_KEY" + } + } +} From 2d9271909526e0ccdef9a750e673e34e9b3c68e3 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 27 Oct 2021 06:48:30 +0000 Subject: [PATCH 59/70] Dont try to generate code on every make build --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3ce7025c..9f5b1faa 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # Calculate version version = $(shell ./scripts/version-at-commit.sh) -build: generate +build: go build -ldflags "-s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=$(version)" cmd/headscale/headscale.go dev: lint test build From 6369cea10e33bd1505966777ee3696db3ef1ba3f Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 27 Oct 2021 06:58:16 +0000 Subject: [PATCH 60/70] Remove golint, its deprecated This commit removed `golint`, its deprecated: https://github.com/golang/lint and golangci-lint has overlapping features. --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index 9f5b1faa..6bce4519 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,6 @@ coverprofile_html: go tool cover -html=coverage.out lint: - golint golangci-lint run --timeout 5m compress: build From acd9ebbdf882937b09e45d19841a58e68ea51eb5 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 27 Oct 2021 07:06:39 +0000 Subject: [PATCH 61/70] Let lint ignore grpcv1.go as it is placeholder --- Makefile | 2 +- grpcv1.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6bce4519..9605955c 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ coverprofile_html: go tool cover -html=coverage.out lint: - golangci-lint run --timeout 5m + golangci-lint run compress: build upx --brute headscale diff --git a/grpcv1.go b/grpcv1.go index d9bbf531..5fc2e1c8 100644 --- a/grpcv1.go +++ b/grpcv1.go @@ -1,3 +1,4 @@ +//nolint package headscale import ( From f7793721547291d1572ece96709c0695b73e10da Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 27 Oct 2021 07:07:19 +0000 Subject: [PATCH 62/70] Add golangcilint config --- .golangci.yaml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .golangci.yaml diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 00000000..8d05e483 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,3 @@ +--- +run: + timeout: 5m From c9bd25d05c30c809624fe0bc114a5aed1a75c376 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 27 Oct 2021 07:07:44 +0000 Subject: [PATCH 63/70] Remove golint from github actions --- .github/workflows/lint.yml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c0286571..6b561d24 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,22 +18,3 @@ jobs: # below, but it's still much faster in the end than installing # golangci-lint manually in the `Run lint` step. - uses: golangci/golangci-lint-action@v2 - with: - args: --timeout 5m - - # Setup Go - - name: Setup Go - uses: actions/setup-go@v2 - with: - go-version: "1.16.3" # The Go version to download (if necessary) and use. - - # Install all the dependencies - - name: Install dependencies - run: | - go version - go install golang.org/x/lint/golint@latest - sudo apt update - sudo apt install -y make - - - name: Run lint - run: make lint From e91174e83f737eb013e6f3789caaa72cab970764 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 27 Oct 2021 07:08:24 +0000 Subject: [PATCH 64/70] Add gen explicitly to skip list --- .golangci.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.golangci.yaml b/.golangci.yaml index 8d05e483..a97c2bb5 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,3 +1,7 @@ --- run: timeout: 5m + +issues: + skip-dirs: + - gen From 5054ed41ac9caae5952a1095533ee4752e428c16 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 27 Oct 2021 07:10:32 +0000 Subject: [PATCH 65/70] Make ci lint fix if it can --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9605955c..5fdd2a53 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ coverprofile_html: go tool cover -html=coverage.out lint: - golangci-lint run + golangci-lint run --fix compress: build upx --brute headscale From d086cf469137f8116579e776daf010a4e6c662d8 Mon Sep 17 00:00:00 2001 From: Ward Vandewege Date: Wed, 27 Oct 2021 17:51:42 -0400 Subject: [PATCH 66/70] Move the namespace argument back to a flag for the share and unshare commands. --- cmd/headscale/cli/nodes.go | 33 ++++++++++++++++++++++++++------- integration_test.go | 2 +- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go index ba843f70..1db56191 100644 --- a/cmd/headscale/cli/nodes.go +++ b/cmd/headscale/cli/nodes.go @@ -19,14 +19,28 @@ func init() { rootCmd.AddCommand(nodeCmd) listNodesCmd.Flags().StringP("namespace", "n", "", "Filter by namespace") nodeCmd.AddCommand(listNodesCmd) - registerNodeCmd.Flags().StringP("namespace", "n", "", "Filter by namespace") + + registerNodeCmd.Flags().StringP("namespace", "n", "", "Namespace") err := registerNodeCmd.MarkFlagRequired("namespace") if err != nil { log.Fatalf(err.Error()) } nodeCmd.AddCommand(registerNodeCmd) + nodeCmd.AddCommand(deleteNodeCmd) + + shareMachineCmd.Flags().StringP("namespace", "n", "", "Namespace") + err = shareMachineCmd.MarkFlagRequired("namespace") + if err != nil { + log.Fatalf(err.Error()) + } nodeCmd.AddCommand(shareMachineCmd) + + unshareMachineCmd.Flags().StringP("namespace", "n", "", "Namespace") + err = unshareMachineCmd.MarkFlagRequired("namespace") + if err != nil { + log.Fatalf(err.Error()) + } nodeCmd.AddCommand(unshareMachineCmd) } @@ -199,6 +213,11 @@ var deleteNodeCmd = &cobra.Command{ } func sharingWorker(cmd *cobra.Command, args []string) (*headscale.Headscale, string, *headscale.Machine, *headscale.Namespace) { + n, err := cmd.Flags().GetString("namespace") + if err != nil { + log.Fatalf("Error getting namespace: %s", err) + } + output, _ := cmd.Flags().GetString("output") h, err := getHeadscaleApp() @@ -206,9 +225,9 @@ func sharingWorker(cmd *cobra.Command, args []string) (*headscale.Headscale, str log.Fatalf("Error initializing: %s", err) } - namespace, err := h.GetNamespace(args[1]) + namespace, err := h.GetNamespace(n) if err != nil { - log.Fatalf("Error fetching namespace %s: %s", args[1], err) + log.Fatalf("Error fetching namespace %s: %s", n, err) } id, err := strconv.Atoi(args[0]) @@ -224,10 +243,10 @@ func sharingWorker(cmd *cobra.Command, args []string) (*headscale.Headscale, str } var shareMachineCmd = &cobra.Command{ - Use: "share ID namespace", + Use: "share ID", Short: "Shares a node from the current namespace to the specified one", Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 2 { + if len(args) < 1 { return fmt.Errorf("missing parameters") } return nil @@ -249,10 +268,10 @@ var shareMachineCmd = &cobra.Command{ } var unshareMachineCmd = &cobra.Command{ - Use: "unshare ID namespace", + Use: "unshare ID", Short: "Unshares a node from the specified namespace", Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 2 { + if len(args) < 1 { return fmt.Errorf("missing parameters") } return nil diff --git a/integration_test.go b/integration_test.go index a1a5e953..7ec75df3 100644 --- a/integration_test.go +++ b/integration_test.go @@ -493,7 +493,7 @@ func (s *IntegrationTestSuite) TestSharedNodes() { result, err := executeCommand( &headscale, - []string{"headscale", "nodes", "share", fmt.Sprint(machine.ID), "main"}, + []string{"headscale", "nodes", "share", fmt.Sprint(machine.ID), "-n", "main"}, []string{}, ) assert.Nil(s.T(), err) From 6c01b86e4ce5cbc36dc012ca2fc86a9856dcef43 Mon Sep 17 00:00:00 2001 From: Ward Vandewege Date: Thu, 28 Oct 2021 08:39:27 -0400 Subject: [PATCH 67/70] Update cmd/headscale/cli/nodes.go Co-authored-by: Kristoffer Dalby --- cmd/headscale/cli/nodes.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go index 1db56191..0e778bc6 100644 --- a/cmd/headscale/cli/nodes.go +++ b/cmd/headscale/cli/nodes.go @@ -213,7 +213,7 @@ var deleteNodeCmd = &cobra.Command{ } func sharingWorker(cmd *cobra.Command, args []string) (*headscale.Headscale, string, *headscale.Machine, *headscale.Namespace) { - n, err := cmd.Flags().GetString("namespace") + namespaceStr, err := cmd.Flags().GetString("namespace") if err != nil { log.Fatalf("Error getting namespace: %s", err) } @@ -225,7 +225,7 @@ func sharingWorker(cmd *cobra.Command, args []string) (*headscale.Headscale, str log.Fatalf("Error initializing: %s", err) } - namespace, err := h.GetNamespace(n) + namespace, err := h.GetNamespace(namespaceStr) if err != nil { log.Fatalf("Error fetching namespace %s: %s", n, err) } From b00a2729e3b250b30fd995647123e81614ffcd7f Mon Sep 17 00:00:00 2001 From: Ward Vandewege Date: Thu, 28 Oct 2021 08:39:42 -0400 Subject: [PATCH 68/70] Update cmd/headscale/cli/nodes.go Co-authored-by: Kristoffer Dalby --- cmd/headscale/cli/nodes.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go index 0e778bc6..825ef232 100644 --- a/cmd/headscale/cli/nodes.go +++ b/cmd/headscale/cli/nodes.go @@ -121,8 +121,8 @@ var listNodesCmd = &cobra.Command{ } var allMachines []headscale.Machine - for _, n := range namespaces { - machines, err := h.ListMachinesInNamespace(n.Name) + for _, namespace := range namespaces { + machines, err := h.ListMachinesInNamespace(namespace.Name) if err != nil { log.Fatalf("Error fetching machines: %s", err) } From 25c67cf2aa8a18b4443400cae98d8d7899611293 Mon Sep 17 00:00:00 2001 From: Ward Vandewege Date: Thu, 28 Oct 2021 08:40:30 -0400 Subject: [PATCH 69/70] Update integration_test.go Co-authored-by: Kristoffer Dalby --- integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_test.go b/integration_test.go index 7ec75df3..524dd32d 100644 --- a/integration_test.go +++ b/integration_test.go @@ -493,7 +493,7 @@ func (s *IntegrationTestSuite) TestSharedNodes() { result, err := executeCommand( &headscale, - []string{"headscale", "nodes", "share", fmt.Sprint(machine.ID), "-n", "main"}, + []string{"headscale", "nodes", "share", fmt.Sprint(machine.ID), "--namespace", "main"}, []string{}, ) assert.Nil(s.T(), err) From f9187bdfc4e1c7c269622a3c595d393d26ae5f8e Mon Sep 17 00:00:00 2001 From: Ward Vandewege Date: Thu, 28 Oct 2021 09:30:41 -0400 Subject: [PATCH 70/70] Switch to named arguments for all `nodes` subcommands. Update docs accordingly. Fix integration test failure. --- api.go | 2 +- cmd/headscale/cli/nodes.go | 64 +++++++++++++++++++------------------- docs/Running.md | 6 ++-- integration_test.go | 2 +- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/api.go b/api.go index a31cf529..0c1f3d41 100644 --- a/api.go +++ b/api.go @@ -43,7 +43,7 @@ func (h *Headscale) RegisterWebAPI(c *gin.Context) {

- headscale -n NAMESPACE nodes register %s + headscale -n NAMESPACE nodes register -k %s

diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go index 825ef232..cdf37efb 100644 --- a/cmd/headscale/cli/nodes.go +++ b/cmd/headscale/cli/nodes.go @@ -25,8 +25,18 @@ func init() { if err != nil { log.Fatalf(err.Error()) } + registerNodeCmd.Flags().StringP("key", "k", "", "Key") + err = registerNodeCmd.MarkFlagRequired("key") + if err != nil { + log.Fatalf(err.Error()) + } nodeCmd.AddCommand(registerNodeCmd) + deleteNodeCmd.Flags().IntP("identifier", "i", 0, "Node identifier (ID)") + err = deleteNodeCmd.MarkFlagRequired("identifier") + if err != nil { + log.Fatalf(err.Error()) + } nodeCmd.AddCommand(deleteNodeCmd) shareMachineCmd.Flags().StringP("namespace", "n", "", "Namespace") @@ -34,6 +44,11 @@ func init() { if err != nil { log.Fatalf(err.Error()) } + shareMachineCmd.Flags().IntP("identifier", "i", 0, "Node identifier (ID)") + err = shareMachineCmd.MarkFlagRequired("identifier") + if err != nil { + log.Fatalf(err.Error()) + } nodeCmd.AddCommand(shareMachineCmd) unshareMachineCmd.Flags().StringP("namespace", "n", "", "Namespace") @@ -41,6 +56,11 @@ func init() { if err != nil { log.Fatalf(err.Error()) } + unshareMachineCmd.Flags().IntP("identifier", "i", 0, "Node identifier (ID)") + err = unshareMachineCmd.MarkFlagRequired("identifier") + if err != nil { + log.Fatalf(err.Error()) + } nodeCmd.AddCommand(unshareMachineCmd) } @@ -50,14 +70,8 @@ var nodeCmd = &cobra.Command{ } var registerNodeCmd = &cobra.Command{ - Use: "register machineID", + Use: "register", Short: "Registers a machine to your network", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return fmt.Errorf("missing parameters") - } - return nil - }, Run: func(cmd *cobra.Command, args []string) { n, err := cmd.Flags().GetString("namespace") if err != nil { @@ -69,7 +83,11 @@ var registerNodeCmd = &cobra.Command{ if err != nil { log.Fatalf("Error initializing: %s", err) } - m, err := h.RegisterMachine(args[0], n) + machineIDStr, err := cmd.Flags().GetString("key") + if err != nil { + log.Fatalf("Error getting machine ID: %s", err) + } + m, err := h.RegisterMachine(machineIDStr, n) if strings.HasPrefix(o, "json") { JsonOutput(m, err, o) return @@ -157,21 +175,15 @@ var listNodesCmd = &cobra.Command{ } var deleteNodeCmd = &cobra.Command{ - Use: "delete ID", + Use: "delete", Short: "Delete a node", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return fmt.Errorf("missing parameters") - } - return nil - }, Run: func(cmd *cobra.Command, args []string) { output, _ := cmd.Flags().GetString("output") h, err := getHeadscaleApp() if err != nil { log.Fatalf("Error initializing: %s", err) } - id, err := strconv.Atoi(args[0]) + id, err := cmd.Flags().GetInt("identifier") if err != nil { log.Fatalf("Error converting ID to integer: %s", err) } @@ -227,10 +239,10 @@ func sharingWorker(cmd *cobra.Command, args []string) (*headscale.Headscale, str namespace, err := h.GetNamespace(namespaceStr) if err != nil { - log.Fatalf("Error fetching namespace %s: %s", n, err) + log.Fatalf("Error fetching namespace %s: %s", namespaceStr, err) } - id, err := strconv.Atoi(args[0]) + id, err := cmd.Flags().GetInt("identifier") if err != nil { log.Fatalf("Error converting ID to integer: %s", err) } @@ -243,14 +255,8 @@ func sharingWorker(cmd *cobra.Command, args []string) (*headscale.Headscale, str } var shareMachineCmd = &cobra.Command{ - Use: "share ID", + Use: "share", Short: "Shares a node from the current namespace to the specified one", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return fmt.Errorf("missing parameters") - } - return nil - }, Run: func(cmd *cobra.Command, args []string) { h, output, machine, namespace := sharingWorker(cmd, args) err := h.AddSharedMachineToNamespace(machine, namespace) @@ -268,14 +274,8 @@ var shareMachineCmd = &cobra.Command{ } var unshareMachineCmd = &cobra.Command{ - Use: "unshare ID", + Use: "unshare", Short: "Unshares a node from the specified namespace", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return fmt.Errorf("missing parameters") - } - return nil - }, Run: func(cmd *cobra.Command, args []string) { h, output, machine, namespace := sharingWorker(cmd, args) err := h.RemoveSharedMachineFromNamespace(machine, namespace) diff --git a/docs/Running.md b/docs/Running.md index 08373653..dbb8704c 100644 --- a/docs/Running.md +++ b/docs/Running.md @@ -97,7 +97,7 @@ 9. In the server, register your machine to a namespace with the CLI ```shell - headscale -n myfirstnamespace nodes register YOURMACHINEKEY + headscale -n myfirstnamespace nodes register -k YOURMACHINEKEY ``` or docker: ```shell @@ -106,11 +106,11 @@ -v $(pwd)/config.json:/config.json \ -v $(pwd)/derp.yaml:/derp.yaml \ headscale/headscale:x.x.x \ - headscale -n myfirstnamespace nodes register YOURMACHINEKEY + headscale -n myfirstnamespace nodes register -k YOURMACHINEKEY ``` or if your server is already running in docker: ```shell - docker exec headscale -n myfirstnamespace nodes register YOURMACHINEKEY + docker exec headscale -n myfirstnamespace nodes register -k YOURMACHINEKEY ``` Alternatively, you can use Auth Keys to register your machines: diff --git a/integration_test.go b/integration_test.go index 524dd32d..f73d76f6 100644 --- a/integration_test.go +++ b/integration_test.go @@ -493,7 +493,7 @@ func (s *IntegrationTestSuite) TestSharedNodes() { result, err := executeCommand( &headscale, - []string{"headscale", "nodes", "share", fmt.Sprint(machine.ID), "--namespace", "main"}, + []string{"headscale", "nodes", "share", "--identifier", fmt.Sprint(machine.ID), "--namespace", "main"}, []string{}, ) assert.Nil(s.T(), err)