Compare commits

..

1 Commits

Author SHA1 Message Date
ohdearaugustin
3a9d50887b move debug image to distroless 2024-05-18 13:45:13 +02:00
102 changed files with 1702 additions and 4358 deletions

View File

@@ -1,15 +0,0 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
language: "en-GB"
early_access: false
reviews:
profile: "chill"
request_changes_workflow: false
high_level_summary: true
poem: true
review_status: true
collapse_walkthrough: false
auto_review:
enabled: true
drafts: true
chat:
auto_reply: true

View File

@@ -18,7 +18,6 @@ jobs:
- TestACLNamedHostsCanReachBySubnet
- TestACLNamedHostsCanReach
- TestACLDevice1CanAccessDevice2
- TestPolicyUpdateWhileRunningWithCLIInDatabase
- TestOIDCAuthenticationPingAll
- TestOIDCExpireNodesBasedOnTokenExpiry
- TestAuthWebFlowAuthenticationPingAll
@@ -36,17 +35,14 @@ jobs:
- TestNodeExpireCommand
- TestNodeRenameCommand
- TestNodeMoveCommand
- TestPolicyCommand
- TestResolveMagicDNS
- TestValidateResolvConf
- TestDERPServerScenario
- TestPingAllByIP
- TestPingAllByIPPublicDERP
- TestAuthKeyLogoutAndRelogin
- TestEphemeral
- TestEphemeral2006DeletedTooQuickly
- TestPingAllByHostname
- TestTaildrop
- TestResolveMagicDNS
- TestExpireNode
- TestNodeOnlineStatus
- TestPingAllByIPManyUpDown

View File

@@ -12,13 +12,19 @@ linters:
disable:
- depguard
- exhaustivestruct
- revive
- lll
- interfacer
- scopelint
- maligned
- golint
- gofmt
- gochecknoglobals
- gochecknoinits
- gocognit
- funlen
- exhaustivestruct
- tagliatelle
- godox
- ireturn
@@ -28,6 +34,13 @@ linters:
- musttag # causes issues with imported libs
- depguard
# deprecated
- structcheck # replaced by unused
- ifshort # deprecated by the owner
- varcheck # replaced by unused
- nosnakecase # replaced by revive
- deadcode # replaced by unused
# We should strive to enable these:
- wrapcheck
- dupl

View File

@@ -26,10 +26,10 @@ after improving the test harness as part of adopting [#1460](https://github.com/
- Code reorganisation, a lot of code has moved, please review the following PRs accordingly [#1473](https://github.com/juanfont/headscale/pull/1473)
- Change the structure of database configuration, see [config-example.yaml](./config-example.yaml) for the new structure. [#1700](https://github.com/juanfont/headscale/pull/1700)
- Old structure has been remove and the configuration _must_ be converted.
- Adds additional configuration for PostgreSQL for setting max open, idle connection and idle connection lifetime.
- Adds additional configuration for PostgreSQL for setting max open, idle conection and idle connection lifetime.
- API: Machine is now Node [#1553](https://github.com/juanfont/headscale/pull/1553)
- Remove support for older Tailscale clients [#1611](https://github.com/juanfont/headscale/pull/1611)
- The latest supported client is 1.42
- The latest supported client is 1.38
- Headscale checks that _at least_ one DERP is defined at start [#1564](https://github.com/juanfont/headscale/pull/1564)
- If no DERP is configured, the server will fail to start, this can be because it cannot load the DERPMap from file or url.
- Embedded DERP server requires a private key [#1611](https://github.com/juanfont/headscale/pull/1611)
@@ -40,15 +40,6 @@ after improving the test harness as part of adopting [#1460](https://github.com/
- Prefixes are now defined per v4 and v6 range. [#1756](https://github.com/juanfont/headscale/pull/1756)
- `ip_prefixes` option is now `prefixes.v4` and `prefixes.v6`
- `prefixes.allocation` can be set to assign IPs at `sequential` or `random`. [#1869](https://github.com/juanfont/headscale/pull/1869)
- MagicDNS domains no longer contain usernames []()
- This is in preperation to fix Headscales implementation of tags which currently does not correctly remove the link between a tagged device and a user. As tagged devices will not have a user, this will require a change to the DNS generation, removing the username, see [#1369](https://github.com/juanfont/headscale/issues/1369) for more information.
- `use_username_in_magic_dns` can be used to turn this behaviour on again, but note that this option _will be removed_ when tags are fixed.
- dns.base_domain can no longer be the same as (or part of) server_url.
- This option brings Headscales behaviour in line with Tailscale.
- YAML files are no longer supported for headscale policy. [#1792](https://github.com/juanfont/headscale/pull/1792)
- HuJSON is now the only supported format for policy.
- DNS configuration has been restructured [#2034](https://github.com/juanfont/headscale/pull/2034)
- Please review the new [config-example.yaml](./config-example.yaml) for the new structure.
### Changes
@@ -67,9 +58,6 @@ after improving the test harness as part of adopting [#1460](https://github.com/
- Log available update as warning [#1877](https://github.com/juanfont/headscale/pull/1877)
- Add `autogroup:internet` to Policy [#1917](https://github.com/juanfont/headscale/pull/1917)
- Restore foreign keys and add constraints [#1562](https://github.com/juanfont/headscale/pull/1562)
- Make registration page easier to use on mobile devices
- Make write-ahead-log default on and configurable for SQLite [#1985](https://github.com/juanfont/headscale/pull/1985)
- Add APIs for managing headscale policy. [#1792](https://github.com/juanfont/headscale/pull/1792)
## 0.22.3 (2023-05-12)
@@ -82,7 +70,7 @@ after improving the test harness as part of adopting [#1460](https://github.com/
### Changes
- Add environment flags to enable pprof (profiling) [#1382](https://github.com/juanfont/headscale/pull/1382)
- Profiles are continuously generated in our integration tests.
- Profiles are continously generated in our integration tests.
- Fix systemd service file location in `.deb` packages [#1391](https://github.com/juanfont/headscale/pull/1391)
- Improvements on Noise implementation [#1379](https://github.com/juanfont/headscale/pull/1379)
- Replace node filter logic, ensuring nodes with access can see eachother [#1381](https://github.com/juanfont/headscale/pull/1381)
@@ -173,7 +161,7 @@ after improving the test harness as part of adopting [#1460](https://github.com/
- SSH ACLs status:
- Support `accept` and `check` (SSH can be enabled and used for connecting and authentication)
- Rejecting connections **are not supported**, meaning that if you enable SSH, then assume that _all_ `ssh` connections **will be allowed**.
- If you decided to try this feature, please carefully managed permissions by blocking port `22` with regular ACLs or do _not_ set `--ssh` on your clients.
- If you decied to try this feature, please carefully managed permissions by blocking port `22` with regular ACLs or do _not_ set `--ssh` on your clients.
- We are currently improving our testing of the SSH ACLs, help us get an overview by testing and giving feedback.
- This feature should be considered dangerous and it is disabled by default. Enable by setting `HEADSCALE_EXPERIMENTAL_FEATURE_SSH=1`.
@@ -223,7 +211,7 @@ after improving the test harness as part of adopting [#1460](https://github.com/
### Changes
- Updated dependencies (including the library that lacked armhf support) [#722](https://github.com/juanfont/headscale/pull/722)
- Fix missing group expansion in function `excludeCorrectlyTaggedNodes` [#563](https://github.com/juanfont/headscale/issues/563)
- Fix missing group expansion in function `excludeCorretlyTaggedNodes` [#563](https://github.com/juanfont/headscale/issues/563)
- Improve registration protocol implementation and switch to NodeKey as main identifier [#725](https://github.com/juanfont/headscale/pull/725)
- Add ability to connect to PostgreSQL via unix socket [#734](https://github.com/juanfont/headscale/pull/734)
@@ -243,7 +231,7 @@ after improving the test harness as part of adopting [#1460](https://github.com/
- Fix send on closed channel crash in polling [#542](https://github.com/juanfont/headscale/pull/542)
- Fixed spurious calls to setLastStateChangeToNow from ephemeral nodes [#566](https://github.com/juanfont/headscale/pull/566)
- Add command for moving nodes between namespaces [#362](https://github.com/juanfont/headscale/issues/362)
- Added more configuration parameters for OpenID Connect (scopes, free-form parameters, domain and user allowlist)
- Added more configuration parameters for OpenID Connect (scopes, free-form paramters, domain and user allowlist)
- Add command to set tags on a node [#525](https://github.com/juanfont/headscale/issues/525)
- Add command to view tags of nodes [#356](https://github.com/juanfont/headscale/issues/356)
- Add --all (-a) flag to enable routes command [#360](https://github.com/juanfont/headscale/issues/360)
@@ -291,10 +279,10 @@ after improving the test harness as part of adopting [#1460](https://github.com/
- Fix a bug were the same IP could be assigned to multiple hosts if joined in quick succession [#346](https://github.com/juanfont/headscale/pull/346)
- Simplify the code behind registration of machines [#366](https://github.com/juanfont/headscale/pull/366)
- Nodes are now only written to database if they are registered successfully
- Nodes are now only written to database if they are registrated successfully
- Fix a limitation in the ACLs that prevented users to write rules with `*` as source [#374](https://github.com/juanfont/headscale/issues/374)
- Reduce the overhead of marshal/unmarshal for Hostinfo, routes and endpoints by using specific types in Machine [#371](https://github.com/juanfont/headscale/pull/371)
- Apply normalization function to FQDN on hostnames when hosts registers and retrieve information [#363](https://github.com/juanfont/headscale/issues/363)
- Apply normalization function to FQDN on hostnames when hosts registers and retrieve informations [#363](https://github.com/juanfont/headscale/issues/363)
- Fix a bug that prevented the use of `tailscale logout` with OIDC [#508](https://github.com/juanfont/headscale/issues/508)
- Added Tailscale repo HEAD and unstable releases channel to the integration tests targets [#513](https://github.com/juanfont/headscale/pull/513)

View File

@@ -1,43 +1,21 @@
# Copyright (c) Tailscale Inc & AUTHORS
# SPDX-License-Identifier: BSD-3-Clause
# This Dockerfile and the images produced are for testing headscale,
# and are in no way endorsed by Headscale's maintainers as an
# official nor supported release or distribution.
# This Dockerfile is more or less lifted from tailscale/tailscale
# to ensure a similar build process when testing the HEAD of tailscale.
FROM golang:latest
FROM golang:1.22-alpine AS build-env
RUN apt-get update \
&& apt-get install -y dnsutils git iptables ssh ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /go/src
RUN useradd --shell=/bin/bash --create-home ssh-it-user
RUN apk add --no-cache git
# Replace `RUN git...` with `COPY` and a local checked out version of Tailscale in `./tailscale`
# to test specific commits of the Tailscale client. This is useful when trying to find out why
# something specific broke between two versions of Tailscale with for example `git bisect`.
# COPY ./tailscale .
RUN git clone https://github.com/tailscale/tailscale.git
WORKDIR /go/src/tailscale
WORKDIR /go/tailscale
# see build_docker.sh
ARG VERSION_LONG=""
ENV VERSION_LONG=$VERSION_LONG
ARG VERSION_SHORT=""
ENV VERSION_SHORT=$VERSION_SHORT
ARG VERSION_GIT_HASH=""
ENV VERSION_GIT_HASH=$VERSION_GIT_HASH
ARG TARGETARCH
RUN GOARCH=$TARGETARCH go install -ldflags="\
-X tailscale.com/version.longStamp=$VERSION_LONG \
-X tailscale.com/version.shortStamp=$VERSION_SHORT \
-X tailscale.com/version.gitCommitStamp=$VERSION_GIT_HASH" \
-v ./cmd/tailscale ./cmd/tailscaled ./cmd/containerboot
FROM alpine:3.18
RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables curl
COPY --from=build-env /go/bin/* /usr/local/bin/
# For compat with the previous run.sh, although ideally you should be
# using build_docker.sh which sets an entrypoint for the image.
RUN mkdir /tailscale && ln -s /usr/local/bin/containerboot /tailscale/run.sh
RUN git checkout main \
&& sh build_dist.sh tailscale.com/cmd/tailscale \
&& sh build_dist.sh tailscale.com/cmd/tailscaled \
&& cp tailscale /usr/local/bin/ \
&& cp tailscaled /usr/local/bin/

View File

@@ -99,7 +99,7 @@ Please read the [CONTRIBUTING.md](./CONTRIBUTING.md) file.
### Requirements
To contribute to headscale you would need the latest version of [Go](https://golang.org)
To contribute to headscale you would need the lastest version of [Go](https://golang.org)
and [Buf](https://buf.build)(Protobuf generator).
We recommend using [Nix](https://nixos.org/) to setup a development environment. This can

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"log"
"net/netip"
"slices"
"strconv"
"strings"
"time"
@@ -618,14 +617,14 @@ func nodesToPtables(
forcedTags = strings.TrimLeft(forcedTags, ",")
var invalidTags string
for _, tag := range node.GetInvalidTags() {
if !slices.Contains(node.GetForcedTags(), tag) {
if !contains(node.GetForcedTags(), tag) {
invalidTags += "," + pterm.LightRed(tag)
}
}
invalidTags = strings.TrimLeft(invalidTags, ",")
var validTags string
for _, tag := range node.GetValidTags() {
if !slices.Contains(node.GetForcedTags(), tag) {
if !contains(node.GetForcedTags(), tag) {
validTags += "," + pterm.LightGreen(tag)
}
}

View File

@@ -1,90 +0,0 @@
package cli
import (
"io"
"os"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(policyCmd)
policyCmd.AddCommand(getPolicy)
setPolicy.Flags().StringP("file", "f", "", "Path to a policy file in HuJSON format")
if err := setPolicy.MarkFlagRequired("file"); err != nil {
log.Fatal().Err(err).Msg("")
}
policyCmd.AddCommand(setPolicy)
}
var policyCmd = &cobra.Command{
Use: "policy",
Short: "Manage the Headscale ACL Policy",
}
var getPolicy = &cobra.Command{
Use: "get",
Short: "Print the current ACL Policy",
Aliases: []string{"show", "view", "fetch"},
Run: func(cmd *cobra.Command, args []string) {
ctx, client, conn, cancel := getHeadscaleCLIClient()
defer cancel()
defer conn.Close()
request := &v1.GetPolicyRequest{}
response, err := client.GetPolicy(ctx, request)
if err != nil {
log.Fatal().Err(err).Msg("Failed to get the policy")
return
}
// TODO(pallabpain): Maybe print this better?
SuccessOutput("", response.GetPolicy(), "hujson")
},
}
var setPolicy = &cobra.Command{
Use: "set",
Short: "Updates the ACL Policy",
Long: `
Updates the existing ACL Policy with the provided policy. The policy must be a valid HuJSON object.
This command only works when the acl.policy_mode is set to "db", and the policy will be stored in the database.`,
Aliases: []string{"put", "update"},
Run: func(cmd *cobra.Command, args []string) {
policyPath, _ := cmd.Flags().GetString("file")
f, err := os.Open(policyPath)
if err != nil {
log.Fatal().Err(err).Msg("Error opening the policy file")
return
}
defer f.Close()
policyBytes, err := io.ReadAll(f)
if err != nil {
log.Fatal().Err(err).Msg("Error reading the policy file")
return
}
request := &v1.SetPolicyRequest{Policy: string(policyBytes)}
ctx, client, conn, cancel := getHeadscaleCLIClient()
defer cancel()
defer conn.Close()
if _, err := client.SetPolicy(ctx, request); err != nil {
log.Fatal().Err(err).Msg("Failed to set ACL Policy")
return
}
SuccessOutput(nil, "Policy updated.", "")
},
}

View File

@@ -6,9 +6,11 @@ import (
"encoding/json"
"fmt"
"os"
"reflect"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol"
"github.com/juanfont/headscale/hscontrol/policy"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/rs/zerolog/log"
@@ -37,6 +39,21 @@ func getHeadscaleApp() (*hscontrol.Headscale, error) {
return nil, err
}
// We are doing this here, as in the future could be cool to have it also hot-reload
if cfg.ACL.PolicyPath != "" {
aclPath := util.AbsolutePathFromConfigPath(cfg.ACL.PolicyPath)
pol, err := policy.LoadACLPolicyFromPath(aclPath)
if err != nil {
log.Fatal().
Str("path", aclPath).
Err(err).
Msg("Could not load the ACL policy")
}
app.ACLPolicy = pol
}
return app, nil
}
@@ -72,7 +89,7 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc.
// Try to give the user better feedback if we cannot write to the headscale
// socket.
socket, err := os.OpenFile(cfg.UnixSocket, os.O_WRONLY, SocketWritePermissions) // nolint
socket, err := os.OpenFile(cfg.UnixSocket, os.O_WRONLY, SocketWritePermissions) //nolint
if err != nil {
if os.IsPermission(err) {
log.Fatal().
@@ -150,13 +167,13 @@ func SuccessOutput(result interface{}, override string, outputFormat string) {
log.Fatal().Err(err).Msg("failed to unmarshal output")
}
default:
// nolint
//nolint
fmt.Println(override)
return
}
// nolint
//nolint
fmt.Println(string(jsonBytes))
}
@@ -195,3 +212,13 @@ func (t tokenAuth) GetRequestMetadata(
func (tokenAuth) RequireTransportSecurity() bool {
return true
}
func contains[T string](ts []T, t T) bool {
for _, v := range ts {
if reflect.DeepEqual(v, t) {
return true
}
}
return false
}

View File

@@ -63,6 +63,7 @@ func (*Suite) TestConfigFileLoading(c *check.C) {
c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http")
c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01")
c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1")
c.Assert(
util.GetFileMode("unix_socket_permission"),
check.Equals,
@@ -105,6 +106,7 @@ func (*Suite) TestConfigLoading(c *check.C) {
c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http")
c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01")
c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1")
c.Assert(
util.GetFileMode("unix_socket_permission"),
check.Equals,
@@ -114,6 +116,39 @@ func (*Suite) TestConfigLoading(c *check.C) {
c.Assert(viper.GetBool("randomize_client_port"), check.Equals, false)
}
func (*Suite) TestDNSConfigLoading(c *check.C) {
tmpDir, err := os.MkdirTemp("", "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-example.yaml"),
filepath.Join(tmpDir, "config.yaml"),
)
if err != nil {
c.Fatal(err)
}
// Load example config, it should load without validation errors
err = types.LoadConfig(tmpDir, false)
c.Assert(err, check.IsNil)
dnsConfig, baseDomain := types.GetDNSConfig()
c.Assert(dnsConfig.Nameservers[0].String(), check.Equals, "1.1.1.1")
c.Assert(dnsConfig.Resolvers[0].Addr, check.Equals, "1.1.1.1")
c.Assert(dnsConfig.Proxied, check.Equals, true)
c.Assert(baseDomain, check.Equals, "example.com")
}
func writeConfig(c *check.C, tmpDir string, configYaml []byte) {
// Populate a custom config file
configFile := filepath.Join(tmpDir, "config.yaml")

View File

@@ -105,7 +105,7 @@ derp:
automatically_add_embedded_derp_region: true
# For better connection stability (especially when using an Exit-Node and DNS is not working),
# it is possible to optionally add the public IPv4 and IPv6 address to the Derp-Map using:
# it is possible to optionall add the public IPv4 and IPv6 address to the Derp-Map using:
ipv4: 1.2.3.4
ipv6: 2001:db8::1
@@ -140,31 +140,10 @@ ephemeral_node_inactivity_timeout: 30m
database:
type: sqlite
# Enable debug mode. This setting requires the log.level to be set to "debug" or "trace".
debug: false
# GORM configuration settings.
gorm:
# Enable prepared statements.
prepare_stmt: true
# Enable parameterized queries.
parameterized_queries: true
# Skip logging "record not found" errors.
skip_err_record_not_found: true
# Threshold for slow queries in milliseconds.
slow_threshold: 1000
# SQLite config
sqlite:
path: /var/lib/headscale/db.sqlite
# Enable WAL mode for SQLite. This is recommended for production environments.
# https://www.sqlite.org/wal.html
write_ahead_log: true
# # Postgres config
# postgres:
# # If using a Unix socket to connect to Postgres, set the socket path in the 'host' field and leave 'port' blank.
@@ -220,17 +199,10 @@ log:
format: text
level: info
## Policy
# headscale supports Tailscale's ACL policies.
# Please have a look to their KB to better
# understand the concepts: https://tailscale.com/kb/1018/acls/
policy:
# The mode can be "file" or "database" that defines
# where the ACL policies are stored and read from.
mode: file
# If the mode is set to "file", the path to a
# HuJSON file containing ACL policies.
path: ""
# Path to a file containg ACL policies.
# ACLs can be defined as YAML or HUJSON.
# https://tailscale.com/kb/1018/acls/
acl_policy_path: ""
## DNS
#
@@ -241,60 +213,43 @@ policy:
# - https://tailscale.com/kb/1081/magicdns/
# - https://tailscale.com/blog/2021-09-private-dns-with-magicdns/
#
# Please note that for the DNS configuration to have any effect,
# clients must have the `--accept-dns=true` option enabled. This is the
# default for the Tailscale client. This option is enabled by default
# in the Tailscale client.
#
# Setting _any_ of the configuration and `--accept-dns=true` on the
# clients will integrate with the DNS manager on the client or
# overwrite /etc/resolv.conf.
# https://tailscale.com/kb/1235/resolv-conf
#
# If you want stop Headscale from managing the DNS configuration
# all the fields under `dns` should be set to empty values.
dns:
# Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/).
# Only works if there is at least a nameserver defined.
magic_dns: true
# Defines the base domain to create the hostnames for MagicDNS.
# This domain _must_ be different from the server_url domain.
# `base_domain` must be a FQDN, without the trailing dot.
# The FQDN of the hosts will be
# `hostname.base_domain` (e.g., _myhost.example.com_).
base_domain: example.com
dns_config:
# Whether to prefer using Headscale provided DNS or use local.
override_local_dns: true
# List of DNS servers to expose to clients.
nameservers:
global:
- 1.1.1.1
- 1.0.0.1
- 2606:4700:4700::1111
- 2606:4700:4700::1001
- 1.1.1.1
# NextDNS (see https://tailscale.com/kb/1218/nextdns/).
# "abc123" is example NextDNS ID, replace with yours.
# - https://dns.nextdns.io/abc123
# NextDNS (see https://tailscale.com/kb/1218/nextdns/).
# "abc123" is example NextDNS ID, replace with yours.
#
# With metadata sharing:
# nameservers:
# - https://dns.nextdns.io/abc123
#
# Without metadata sharing:
# nameservers:
# - 2a07:a8c0::ab:c123
# - 2a07:a8c1::ab:c123
# Split DNS (see https://tailscale.com/kb/1054/dns/),
# a map of domains and which DNS server to use for each.
split:
{}
# foo.bar.com:
# - 1.1.1.1
# darp.headscale.net:
# - 1.1.1.1
# - 8.8.8.8
# Split DNS (see https://tailscale.com/kb/1054/dns/),
# list of search domains and the DNS to query for each one.
#
# restricted_nameservers:
# foo.bar.com:
# - 1.1.1.1
# darp.headscale.net:
# - 1.1.1.1
# - 8.8.8.8
# Set custom DNS search domains. With MagicDNS enabled,
# your tailnet base_domain is always the first search domain.
search_domains: []
# Search domains to inject.
domains: []
# Extra DNS records
# so far only A-records are supported (on the tailscale side)
# See https://github.com/juanfont/headscale/blob/main/docs/dns-records.md#Limitations
extra_records: []
# extra_records:
# - name: "grafana.myvpn.example.com"
# type: "A"
# value: "100.64.0.3"
@@ -302,14 +257,15 @@ dns:
# # you can also put it in one line
# - { name: "prometheus.myvpn.example.com", type: "A", value: "100.64.0.3" }
# DEPRECATED
# Use the username as part of the DNS name for nodes, with this option enabled:
# node1.username.example.com
# while when this is disabled:
# node1.example.com
# This is a legacy option as Headscale has have this wrongly implemented
# while in upstream Tailscale, the username is not included.
use_username_in_magic_dns: false
# Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/).
# Only works if there is at least a nameserver defined.
magic_dns: true
# 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.user.base_domain` (e.g., _myhost.myuser.example.com_).
base_domain: example.com
# Unix socket used for the CLI to connect without authentication
# Note: for production you will want to set this to something like:

View File

@@ -12,8 +12,8 @@ Ensure that the installed version is at least 1.30.0, as that is the first relea
## Configuring the headscale URL
After opening the app:
After opening the app, the kebab menu icon (three dots) on the top bar on the right must be repeatedly opened and closed until the _Change server_ option appears in the menu. This is where you can enter your headscale URL.
- Open setting and go into account settings
- In the kebab menu icon (three dots) on the top bar on the right select “Use an alternate server”
- Enter your server URL and follow the instructions
A screen recording of this process can be seen in the `tailscale-android` PR which implemented this functionality: <https://github.com/tailscale/tailscale-android/pull/55>
After saving and restarting the app, selecting the regular _Sign in_ option (non-SSO) should open up the headscale authentication page.

View File

@@ -14,7 +14,7 @@ If the node is already registered, it can advertise exit capabilities like this:
$ sudo tailscale set --advertise-exit-node
```
To use a node as an exit node, IP forwarding must be enabled on the node. Check the official [Tailscale documentation](https://tailscale.com/kb/1019/subnets/?tab=linux#enable-ip-forwarding) for how to enable IP forwarding.
To use a node as an exit node, IP forwarding must be enabled on the node. Check the official [Tailscale documentation](https://tailscale.com/kb/1019/subnets/?tab=linux#enable-ip-forwarding) for how to enable IP fowarding.
## On the control server

View File

@@ -36,7 +36,7 @@ We don't know. We might be working on it. If you want to help, please send us a
Please be aware that there are a number of reasons why we might not accept specific contributions:
- It is not possible to implement the feature in a way that makes sense in a self-hosted environment.
- Given that we are reverse-engineering Tailscale to satisfy our own curiosity, we might be interested in implementing the feature ourselves.
- Given that we are reverse-engineering Tailscale to satify our own curiosity, we might be interested in implementing the feature ourselves.
- You are not sending unit and integration tests with it.
## Do you support Y method of deploying Headscale?

View File

@@ -8,7 +8,7 @@ hide:
`headscale` is an open source, self-hosted implementation of the Tailscale control server.
This page contains the documentation for the latest version of headscale. Please also check our [FAQ](faq.md).
This page contains the documentation for the latest version of headscale. Please also check our [FAQ](/faq/).
Join our [Discord](https://discord.gg/c84AZQhmpx) server for a chat and community support.

View File

@@ -58,12 +58,12 @@ A solution could be to consider a headscale server (in it's entirety) as a
tailnet.
For personal users the default behavior could either allow all communications
between all namespaces (like tailscale) or disallow all communications between
between all namespaces (like tailscale) or dissallow all communications between
namespaces (current behavior).
For businesses and organisations, viewing a headscale instance a single tailnet
would allow users (namespace) to talk to each other with the ACLs. As described
in tailscale's documentation [[1]], a server should be tagged and personal
in tailscale's documentation [[1]], a server should be tagged and personnal
devices should be tied to a user. Translated in headscale's terms each user can
have multiple devices and all those devices should be in the same namespace.
The servers should be tagged and used as such.
@@ -88,7 +88,7 @@ the ability to rules in either format (HuJSON or YAML).
Let's build an example use case for a small business (It may be the place where
ACL's are the most useful).
We have a small company with a boss, an admin, two developer and an intern.
We have a small company with a boss, an admin, two developper and an intern.
The boss should have access to all servers but not to the users hosts. Admin
should also have access to all hosts except that their permissions should be
@@ -173,7 +173,7 @@ need to add the following ACLs
"ports": ["prod:*", "dev:*", "internal:*"]
},
// admin have access to administration port (lets only consider port 22 here)
// admin have access to adminstration port (lets only consider port 22 here)
{
"action": "accept",
"users": ["group:admin"],

View File

@@ -1,13 +1,13 @@
# Controlling `headscale` with remote CLI
## Prerequisite
## Prerequisit
- A workstation to run `headscale` (could be Linux, macOS, other supported platforms)
- A `headscale` server (version `0.13.0` or newer)
- Access to create API keys (local access to the `headscale` server)
- `headscale` _must_ be served over TLS/HTTPS
- Remote access does _not_ support unencrypted traffic.
- Port `50443` must be open in the firewall (or port overridden by `grpc_listen_addr` option)
- Port `50443` must be open in the firewall (or port overriden by `grpc_listen_addr` option)
## Goal
@@ -97,4 +97,4 @@ Checklist:
- Make sure you use version `0.13.0` or newer.
- Verify that your TLS certificate is valid and trusted
- If you do not have access to a trusted certificate (e.g. from Let's Encrypt), add your self signed certificate to the trust store of your OS or
- Set `HEADSCALE_CLI_INSECURE` to 0 in your environment
- Set `HEADSCALE_CLI_INSECURE` to 0 in your environement

View File

@@ -15,10 +15,6 @@ The reverse proxy MUST be configured to support WebSockets, as it is needed for
WebSockets support is required when using the headscale embedded DERP server. In this case, you will also need to expose the UDP port used for STUN (by default, udp/3478). Please check our [config-example.yaml](https://github.com/juanfont/headscale/blob/main/config-example.yaml).
### Cloudflare
Running headscale behind a cloudflare proxy or cloudflare tunnel is not supported and will not work as Cloudflare does not support WebSocket POSTs as required by the Tailscale protocol. See [this issue](https://github.com/juanfont/headscale/issues/1468)
### TLS
Headscale can be configured not to use TLS, leaving it to the reverse proxy to handle. Add the following configuration values to your headscale config file.
@@ -119,7 +115,7 @@ The following Caddyfile is all that is necessary to use Caddy as a reverse proxy
}
```
Caddy v2 will [automatically](https://caddyserver.com/docs/automatic-https) provision a certificate for your domain/subdomain, force HTTPS, and proxy websockets - no further configuration is necessary.
Caddy v2 will [automatically](https://caddyserver.com/docs/automatic-https) provision a certficate for your domain/subdomain, force HTTPS, and proxy websockets - no further configuration is necessary.
For a slightly more complex configuration which utilizes Docker containers to manage Caddy, Headscale, and Headscale-UI, [Guru Computing's guide](https://blog.gurucomputing.com.au/smart-vpns-with-headscale/) is an excellent reference.

View File

@@ -57,7 +57,7 @@ describing how to make `headscale` run properly in a server environment.
touch /etc/headscale/config.yaml
```
**(Strongly Recommended)** Download a copy of the [example configuration](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository.
**(Strongly Recommended)** Download a copy of the [example configuration][config-example.yaml](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository.
1. Start the headscale server:

View File

@@ -30,7 +30,7 @@ describing how to make `headscale` run properly in a server environment.
cd headscale
# optionally checkout a release
# option a. you can find official release at https://github.com/juanfont/headscale/releases/latest
# option a. you can find offical relase at https://github.com/juanfont/headscale/releases/latest
# option b. get latest tag, this may be a beta release
latestTag=$(git describe --tags `git rev-list --tags --max-count=1`)
@@ -57,7 +57,7 @@ describing how to make `headscale` run properly in a server environment.
cd headscale
# optionally checkout a release
# option a. you can find official release at https://github.com/juanfont/headscale/releases/latest
# option a. you can find offical relase at https://github.com/juanfont/headscale/releases/latest
# option b. get latest tag, this may be a beta release
latestTag=$(git describe --tags `git rev-list --tags --max-count=1`)
@@ -93,7 +93,7 @@ describing how to make `headscale` run properly in a server environment.
touch /etc/headscale/config.yaml
```
**(Strongly Recommended)** Download a copy of the [example configuration](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository.
**(Strongly Recommended)** Download a copy of the [example configuration][config-example.yaml](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository.
1. Start the headscale server:

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1723856861,
"narHash": "sha256-OTDg91+Zzs2SpU3csK4xVdSQFoG8cK1lNUwKmTqERyE=",
"lastModified": 1715774670,
"narHash": "sha256-iJYnKMtLi5u6hZhJm94cRNSDG5Rz6ZzIkGbhPFtDRm0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "cd7b95ee3725af7113bacbce91dd6549cee58ca5",
"rev": "b3fcfcfabd01b947a1e4f36622bbffa3985bdac6",
"type": "github"
},
"original": {

View File

@@ -31,7 +31,7 @@
# When updating go.mod or go.sum, a new sha will need to be calculated,
# update this if you have a mismatch after doing a change to thos files.
vendorHash = "sha256-08N9ZdUM3Lw0ad89Vpy01e/qJQoMRPj8n4Jd7Aecgjw=";
vendorHash = "sha256-wXfKeiJaGe6ahOsONrQhvbuMN8flQ13b0ZjxdbFs1e8=";
subPackages = ["cmd/headscale"];
@@ -74,7 +74,6 @@
nfpm
gotestsum
gotests
gofumpt
ksh
ko
yq-go

View File

@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.32.0
// protoc-gen-go v1.33.0
// protoc (unknown)
// source: headscale/v1/apikey.proto

View File

@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.32.0
// protoc-gen-go v1.33.0
// protoc (unknown)
// source: headscale/v1/device.proto

View File

@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.32.0
// protoc-gen-go v1.33.0
// protoc (unknown)
// source: headscale/v1/headscale.proto
@@ -36,225 +36,210 @@ var file_headscale_v1_headscale_proto_rawDesc = []byte{
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
0x65, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x1a, 0x19, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f,
0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0xcf, 0x1a, 0x0a,
0x10, 0x48, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x12, 0x63, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x68,
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55,
0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x65, 0x61,
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65,
0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x15, 0x12, 0x13, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f,
0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x68, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
0x6f, 0x32, 0x80, 0x19, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x53,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x63, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65,
0x72, 0x12, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31,
0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1d, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47,
0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f,
0x75, 0x73, 0x65, 0x72, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x68, 0x0a, 0x0a, 0x43,
0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64,
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55,
0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x65, 0x61,
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3,
0xe4, 0x93, 0x02, 0x11, 0x3a, 0x01, 0x2a, 0x22, 0x0c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31,
0x2f, 0x75, 0x73, 0x65, 0x72, 0x12, 0x82, 0x01, 0x0a, 0x0a, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65,
0x55, 0x73, 0x65, 0x72, 0x12, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65,
0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x3a,
0x01, 0x2a, 0x22, 0x0c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72,
0x12, 0x82, 0x01, 0x0a, 0x0a, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12,
0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52,
0x65, 0x6e, 0x61, 0x6d, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x20, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x31, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x22, 0x29, 0x2f, 0x61, 0x70, 0x69,
0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x7b, 0x6f, 0x6c, 0x64, 0x5f, 0x6e, 0x61,
0x6d, 0x65, 0x7d, 0x2f, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x2f, 0x7b, 0x6e, 0x65, 0x77, 0x5f,
0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x6c, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55,
0x73, 0x65, 0x72, 0x12, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x2a, 0x13,
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x7b, 0x6e, 0x61,
0x6d, 0x65, 0x7d, 0x12, 0x62, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73,
0x12, 0x1e, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x14, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0e, 0x12, 0x0c, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x12, 0x80, 0x01, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61,
0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x25, 0x2e, 0x68,
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61,
0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68,
0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4,
0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f,
0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65, 0x79, 0x12, 0x87, 0x01, 0x0a, 0x10, 0x45,
0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12,
0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45,
0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41,
0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x3a, 0x01, 0x2a, 0x22, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65, 0x79, 0x2f, 0x65, 0x78,
0x70, 0x69, 0x72, 0x65, 0x12, 0x7a, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x41,
0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x41, 0x75,
0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e,
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73,
0x74, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x61,
0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65, 0x79,
0x12, 0x7d, 0x0a, 0x0f, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e,
0x6f, 0x64, 0x65, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x6f,
0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64,
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72,
0x65, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x31, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x22,
0x29, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x7b, 0x6f,
0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x2f,
0x7b, 0x6e, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x6c, 0x0a, 0x0a, 0x44, 0x65,
0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73,
0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x65, 0x61, 0x64,
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55,
0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4,
0x93, 0x02, 0x15, 0x2a, 0x13, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65,
0x72, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x62, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74,
0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x1e, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x14, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0e, 0x12, 0x0c,
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x12, 0x80, 0x01, 0x0a,
0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65,
0x79, 0x12, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31,
0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65,
0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72,
0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x61, 0x70,
0x69, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x65, 0x62, 0x75, 0x67, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x12,
0x66, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x1c, 0x2e, 0x68, 0x65, 0x61,
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12,
0x16, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x7b, 0x6e,
0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x6e, 0x0a, 0x07, 0x53, 0x65, 0x74, 0x54, 0x61,
0x67, 0x73, 0x12, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
0x31, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1d, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
0x53, 0x65, 0x74, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22, 0x1b, 0x2f, 0x61, 0x70, 0x69,
0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x7b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69,
0x64, 0x7d, 0x2f, 0x74, 0x61, 0x67, 0x73, 0x12, 0x74, 0x0a, 0x0c, 0x52, 0x65, 0x67, 0x69, 0x73,
0x74, 0x65, 0x72, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e,
0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61,
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,
0x65, 0x72, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f,
0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x6f, 0x0a,
0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x2e, 0x68, 0x65,
0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74,
0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68,
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65,
0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x2a, 0x16, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f,
0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x7b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x76,
0x0a, 0x0a, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x2e, 0x68,
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69,
0x72, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e,
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70,
0x69, 0x72, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x22, 0x1d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31,
0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x7b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f,
0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x12, 0x81, 0x01, 0x0a, 0x0a, 0x52, 0x65, 0x6e, 0x61, 0x6d,
0x65, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x4e, 0x6f, 0x64, 0x65,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x30, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2a,
0x22, 0x28, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x7b,
0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x2f,
0x7b, 0x6e, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x62, 0x0a, 0x09, 0x4c, 0x69,
0x73, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x1e, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x14, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0e,
0x12, 0x0c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x6e,
0x0a, 0x08, 0x4d, 0x6f, 0x76, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x2e, 0x68, 0x65, 0x61,
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x76, 0x65, 0x4e, 0x6f,
0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x65, 0x61, 0x64,
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x76, 0x65, 0x4e, 0x6f, 0x64,
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x1d, 0x22, 0x1b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f,
0x7b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x12, 0x80,
0x01, 0x0a, 0x0f, 0x42, 0x61, 0x63, 0x6b, 0x66, 0x69, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, 0x49,
0x50, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
0x31, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x66, 0x69, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x50,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x66, 0x69, 0x6c, 0x6c,
0x4e, 0x6f, 0x64, 0x65, 0x49, 0x50, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31,
0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x66, 0x69, 0x6c, 0x6c, 0x69, 0x70,
0x73, 0x12, 0x64, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1e,
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65,
0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f,
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65,
0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31,
0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x7c, 0x0a, 0x0b, 0x45, 0x6e, 0x61, 0x62, 0x6c,
0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f,
0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4,
0x93, 0x02, 0x22, 0x22, 0x20, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75,
0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x65,
0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x0c, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c,
0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x75,
0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64,
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x29, 0x82,
0xd3, 0xe4, 0x93, 0x02, 0x23, 0x22, 0x21, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x72,
0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x7d,
0x2f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x7f, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4e,
0x6f, 0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64,
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65,
0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e,
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74,
0x4e, 0x6f, 0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x12, 0x1d, 0x2f, 0x61, 0x70, 0x69,
0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x7b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69,
0x64, 0x7d, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x75, 0x0a, 0x0b, 0x44, 0x65, 0x6c,
0x65, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f,
0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x65, 0x61,
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x21, 0x82,
0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x2a, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x72,
0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x7d,
0x12, 0x70, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79,
0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x3a,
0x01, 0x2a, 0x22, 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b,
0x65, 0x79, 0x12, 0x77, 0x0a, 0x0c, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4b,
0x65, 0x79, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65,
0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70,
0x69, 0x6b, 0x65, 0x79, 0x2f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x12, 0x6a, 0x0a, 0x0b, 0x4c,
0x69, 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x20, 0x2e, 0x68, 0x65, 0x61,
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70,
0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68,
0x69, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65, 0x79, 0x12,
0x87, 0x01, 0x0a, 0x10, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74,
0x68, 0x4b, 0x65, 0x79, 0x12, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74,
0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x65,
0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72,
0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x3a, 0x01, 0x2a, 0x22, 0x19,
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b,
0x65, 0x79, 0x2f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x12, 0x7a, 0x0a, 0x0f, 0x4c, 0x69, 0x73,
0x74, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x24, 0x2e, 0x68,
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31,
0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x12, 0x76, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74,
0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79,
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x14, 0x12, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75,
0x74, 0x68, 0x6b, 0x65, 0x79, 0x12, 0x7d, 0x0a, 0x0f, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72,
0x65, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65,
0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25,
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65,
0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a,
0x22, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x65, 0x62, 0x75, 0x67, 0x2f,
0x6e, 0x6f, 0x64, 0x65, 0x12, 0x66, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x12,
0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47,
0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e,
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74,
0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3,
0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f,
0x64, 0x65, 0x2f, 0x7b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x6e, 0x0a, 0x07,
0x53, 0x65, 0x74, 0x54, 0x61, 0x67, 0x73, 0x12, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22,
0x1b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x7b, 0x6e,
0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x74, 0x61, 0x67, 0x73, 0x12, 0x74, 0x0a, 0x0c,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x2e, 0x68,
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69,
0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52,
0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x15, 0x2f, 0x61, 0x70,
0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74,
0x65, 0x72, 0x12, 0x6f, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65,
0x12, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x20, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31,
0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x2a, 0x16, 0x2f, 0x61, 0x70,
0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x7b, 0x6e, 0x6f, 0x64, 0x65, 0x5f,
0x69, 0x64, 0x7d, 0x12, 0x76, 0x0a, 0x0a, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4e, 0x6f, 0x64,
0x65, 0x12, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31,
0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x22, 0x1d, 0x2f, 0x61,
0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x7b, 0x6e, 0x6f, 0x64, 0x65,
0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x12, 0x81, 0x01, 0x0a, 0x0a,
0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x2e, 0x68, 0x65, 0x61,
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65,
0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x65,
0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d,
0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x30, 0x82,
0xd3, 0xe4, 0x93, 0x02, 0x2a, 0x22, 0x28, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e,
0x6f, 0x64, 0x65, 0x2f, 0x7b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x65,
0x6e, 0x61, 0x6d, 0x65, 0x2f, 0x7b, 0x6e, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12,
0x62, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x1e, 0x2e, 0x68,
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x4e, 0x6f, 0x64, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68,
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x4e, 0x6f, 0x64, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x14, 0x82,
0xd3, 0xe4, 0x93, 0x02, 0x0e, 0x12, 0x0c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e,
0x6f, 0x64, 0x65, 0x12, 0x6e, 0x0a, 0x08, 0x4d, 0x6f, 0x76, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x12,
0x1d, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d,
0x6f, 0x76, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e,
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f,
0x76, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x22, 0x1b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f,
0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x7b, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x75,
0x73, 0x65, 0x72, 0x12, 0x80, 0x01, 0x0a, 0x0f, 0x42, 0x61, 0x63, 0x6b, 0x66, 0x69, 0x6c, 0x6c,
0x4e, 0x6f, 0x64, 0x65, 0x49, 0x50, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x66, 0x69, 0x6c, 0x6c, 0x4e,
0x6f, 0x64, 0x65, 0x49, 0x50, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e,
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x63,
0x6b, 0x66, 0x69, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x50, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x18, 0x2f, 0x61,
0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x66,
0x69, 0x6c, 0x6c, 0x69, 0x70, 0x73, 0x12, 0x64, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75,
0x74, 0x65, 0x73, 0x12, 0x1e, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x61,
0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x7c, 0x0a, 0x0b,
0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x20, 0x2e, 0x68, 0x65,
0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61, 0x62, 0x6c,
0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e,
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61,
0x62, 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x22, 0x20, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76,
0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f,
0x69, 0x64, 0x7d, 0x2f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x0c, 0x44,
0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x21, 0x2e, 0x68, 0x65,
0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x61, 0x62,
0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22,
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69,
0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x29, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x22, 0x21, 0x2f, 0x61, 0x70, 0x69,
0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74,
0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x7f, 0x0a,
0x0d, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x22,
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65,
0x74, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x12,
0x1d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x7b, 0x6e,
0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x75,
0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x20, 0x2e,
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c,
0x65, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44,
0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x2a, 0x19, 0x2f, 0x61, 0x70, 0x69,
0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x74,
0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x70, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41,
0x70, 0x69, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65,
0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70,
0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3,
0xe4, 0x93, 0x02, 0x13, 0x3a, 0x01, 0x2a, 0x22, 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31,
0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x12, 0x77, 0x0a, 0x0c, 0x45, 0x78, 0x70, 0x69, 0x72,
0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x70, 0x69,
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69,
0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61,
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x2a, 0x17, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f,
0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x2f, 0x7b, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x7d, 0x12,
0x64, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1e, 0x2e, 0x68,
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50,
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68,
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50,
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82,
0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x70,
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x67, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x69,
0x63, 0x79, 0x12, 0x1e, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x3a, 0x01, 0x2a, 0x1a, 0x0e,
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 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,
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65,
0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x2f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65,
0x12, 0x6a, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x12,
0x20, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c,
0x69, 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31,
0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x61,
0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x12, 0x76, 0x0a, 0x0c,
0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x68,
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65,
0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44,
0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x2a, 0x17, 0x2f, 0x61, 0x70,
0x69, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x2f, 0x7b, 0x70, 0x72, 0x65,
0x66, 0x69, 0x78, 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_headscale_v1_headscale_proto_goTypes = []interface{}{
@@ -285,37 +270,33 @@ var file_headscale_v1_headscale_proto_goTypes = []interface{}{
(*ExpireApiKeyRequest)(nil), // 24: headscale.v1.ExpireApiKeyRequest
(*ListApiKeysRequest)(nil), // 25: headscale.v1.ListApiKeysRequest
(*DeleteApiKeyRequest)(nil), // 26: headscale.v1.DeleteApiKeyRequest
(*GetPolicyRequest)(nil), // 27: headscale.v1.GetPolicyRequest
(*SetPolicyRequest)(nil), // 28: headscale.v1.SetPolicyRequest
(*GetUserResponse)(nil), // 29: headscale.v1.GetUserResponse
(*CreateUserResponse)(nil), // 30: headscale.v1.CreateUserResponse
(*RenameUserResponse)(nil), // 31: headscale.v1.RenameUserResponse
(*DeleteUserResponse)(nil), // 32: headscale.v1.DeleteUserResponse
(*ListUsersResponse)(nil), // 33: headscale.v1.ListUsersResponse
(*CreatePreAuthKeyResponse)(nil), // 34: headscale.v1.CreatePreAuthKeyResponse
(*ExpirePreAuthKeyResponse)(nil), // 35: headscale.v1.ExpirePreAuthKeyResponse
(*ListPreAuthKeysResponse)(nil), // 36: headscale.v1.ListPreAuthKeysResponse
(*DebugCreateNodeResponse)(nil), // 37: headscale.v1.DebugCreateNodeResponse
(*GetNodeResponse)(nil), // 38: headscale.v1.GetNodeResponse
(*SetTagsResponse)(nil), // 39: headscale.v1.SetTagsResponse
(*RegisterNodeResponse)(nil), // 40: headscale.v1.RegisterNodeResponse
(*DeleteNodeResponse)(nil), // 41: headscale.v1.DeleteNodeResponse
(*ExpireNodeResponse)(nil), // 42: headscale.v1.ExpireNodeResponse
(*RenameNodeResponse)(nil), // 43: headscale.v1.RenameNodeResponse
(*ListNodesResponse)(nil), // 44: headscale.v1.ListNodesResponse
(*MoveNodeResponse)(nil), // 45: headscale.v1.MoveNodeResponse
(*BackfillNodeIPsResponse)(nil), // 46: headscale.v1.BackfillNodeIPsResponse
(*GetRoutesResponse)(nil), // 47: headscale.v1.GetRoutesResponse
(*EnableRouteResponse)(nil), // 48: headscale.v1.EnableRouteResponse
(*DisableRouteResponse)(nil), // 49: headscale.v1.DisableRouteResponse
(*GetNodeRoutesResponse)(nil), // 50: headscale.v1.GetNodeRoutesResponse
(*DeleteRouteResponse)(nil), // 51: headscale.v1.DeleteRouteResponse
(*CreateApiKeyResponse)(nil), // 52: headscale.v1.CreateApiKeyResponse
(*ExpireApiKeyResponse)(nil), // 53: headscale.v1.ExpireApiKeyResponse
(*ListApiKeysResponse)(nil), // 54: headscale.v1.ListApiKeysResponse
(*DeleteApiKeyResponse)(nil), // 55: headscale.v1.DeleteApiKeyResponse
(*GetPolicyResponse)(nil), // 56: headscale.v1.GetPolicyResponse
(*SetPolicyResponse)(nil), // 57: headscale.v1.SetPolicyResponse
(*GetUserResponse)(nil), // 27: headscale.v1.GetUserResponse
(*CreateUserResponse)(nil), // 28: headscale.v1.CreateUserResponse
(*RenameUserResponse)(nil), // 29: headscale.v1.RenameUserResponse
(*DeleteUserResponse)(nil), // 30: headscale.v1.DeleteUserResponse
(*ListUsersResponse)(nil), // 31: headscale.v1.ListUsersResponse
(*CreatePreAuthKeyResponse)(nil), // 32: headscale.v1.CreatePreAuthKeyResponse
(*ExpirePreAuthKeyResponse)(nil), // 33: headscale.v1.ExpirePreAuthKeyResponse
(*ListPreAuthKeysResponse)(nil), // 34: headscale.v1.ListPreAuthKeysResponse
(*DebugCreateNodeResponse)(nil), // 35: headscale.v1.DebugCreateNodeResponse
(*GetNodeResponse)(nil), // 36: headscale.v1.GetNodeResponse
(*SetTagsResponse)(nil), // 37: headscale.v1.SetTagsResponse
(*RegisterNodeResponse)(nil), // 38: headscale.v1.RegisterNodeResponse
(*DeleteNodeResponse)(nil), // 39: headscale.v1.DeleteNodeResponse
(*ExpireNodeResponse)(nil), // 40: headscale.v1.ExpireNodeResponse
(*RenameNodeResponse)(nil), // 41: headscale.v1.RenameNodeResponse
(*ListNodesResponse)(nil), // 42: headscale.v1.ListNodesResponse
(*MoveNodeResponse)(nil), // 43: headscale.v1.MoveNodeResponse
(*BackfillNodeIPsResponse)(nil), // 44: headscale.v1.BackfillNodeIPsResponse
(*GetRoutesResponse)(nil), // 45: headscale.v1.GetRoutesResponse
(*EnableRouteResponse)(nil), // 46: headscale.v1.EnableRouteResponse
(*DisableRouteResponse)(nil), // 47: headscale.v1.DisableRouteResponse
(*GetNodeRoutesResponse)(nil), // 48: headscale.v1.GetNodeRoutesResponse
(*DeleteRouteResponse)(nil), // 49: headscale.v1.DeleteRouteResponse
(*CreateApiKeyResponse)(nil), // 50: headscale.v1.CreateApiKeyResponse
(*ExpireApiKeyResponse)(nil), // 51: headscale.v1.ExpireApiKeyResponse
(*ListApiKeysResponse)(nil), // 52: headscale.v1.ListApiKeysResponse
(*DeleteApiKeyResponse)(nil), // 53: headscale.v1.DeleteApiKeyResponse
}
var file_headscale_v1_headscale_proto_depIdxs = []int32{
0, // 0: headscale.v1.HeadscaleService.GetUser:input_type -> headscale.v1.GetUserRequest
@@ -345,39 +326,35 @@ var file_headscale_v1_headscale_proto_depIdxs = []int32{
24, // 24: headscale.v1.HeadscaleService.ExpireApiKey:input_type -> headscale.v1.ExpireApiKeyRequest
25, // 25: headscale.v1.HeadscaleService.ListApiKeys:input_type -> headscale.v1.ListApiKeysRequest
26, // 26: headscale.v1.HeadscaleService.DeleteApiKey:input_type -> headscale.v1.DeleteApiKeyRequest
27, // 27: headscale.v1.HeadscaleService.GetPolicy:input_type -> headscale.v1.GetPolicyRequest
28, // 28: headscale.v1.HeadscaleService.SetPolicy:input_type -> headscale.v1.SetPolicyRequest
29, // 29: headscale.v1.HeadscaleService.GetUser:output_type -> headscale.v1.GetUserResponse
30, // 30: headscale.v1.HeadscaleService.CreateUser:output_type -> headscale.v1.CreateUserResponse
31, // 31: headscale.v1.HeadscaleService.RenameUser:output_type -> headscale.v1.RenameUserResponse
32, // 32: headscale.v1.HeadscaleService.DeleteUser:output_type -> headscale.v1.DeleteUserResponse
33, // 33: headscale.v1.HeadscaleService.ListUsers:output_type -> headscale.v1.ListUsersResponse
34, // 34: headscale.v1.HeadscaleService.CreatePreAuthKey:output_type -> headscale.v1.CreatePreAuthKeyResponse
35, // 35: headscale.v1.HeadscaleService.ExpirePreAuthKey:output_type -> headscale.v1.ExpirePreAuthKeyResponse
36, // 36: headscale.v1.HeadscaleService.ListPreAuthKeys:output_type -> headscale.v1.ListPreAuthKeysResponse
37, // 37: headscale.v1.HeadscaleService.DebugCreateNode:output_type -> headscale.v1.DebugCreateNodeResponse
38, // 38: headscale.v1.HeadscaleService.GetNode:output_type -> headscale.v1.GetNodeResponse
39, // 39: headscale.v1.HeadscaleService.SetTags:output_type -> headscale.v1.SetTagsResponse
40, // 40: headscale.v1.HeadscaleService.RegisterNode:output_type -> headscale.v1.RegisterNodeResponse
41, // 41: headscale.v1.HeadscaleService.DeleteNode:output_type -> headscale.v1.DeleteNodeResponse
42, // 42: headscale.v1.HeadscaleService.ExpireNode:output_type -> headscale.v1.ExpireNodeResponse
43, // 43: headscale.v1.HeadscaleService.RenameNode:output_type -> headscale.v1.RenameNodeResponse
44, // 44: headscale.v1.HeadscaleService.ListNodes:output_type -> headscale.v1.ListNodesResponse
45, // 45: headscale.v1.HeadscaleService.MoveNode:output_type -> headscale.v1.MoveNodeResponse
46, // 46: headscale.v1.HeadscaleService.BackfillNodeIPs:output_type -> headscale.v1.BackfillNodeIPsResponse
47, // 47: headscale.v1.HeadscaleService.GetRoutes:output_type -> headscale.v1.GetRoutesResponse
48, // 48: headscale.v1.HeadscaleService.EnableRoute:output_type -> headscale.v1.EnableRouteResponse
49, // 49: headscale.v1.HeadscaleService.DisableRoute:output_type -> headscale.v1.DisableRouteResponse
50, // 50: headscale.v1.HeadscaleService.GetNodeRoutes:output_type -> headscale.v1.GetNodeRoutesResponse
51, // 51: headscale.v1.HeadscaleService.DeleteRoute:output_type -> headscale.v1.DeleteRouteResponse
52, // 52: headscale.v1.HeadscaleService.CreateApiKey:output_type -> headscale.v1.CreateApiKeyResponse
53, // 53: headscale.v1.HeadscaleService.ExpireApiKey:output_type -> headscale.v1.ExpireApiKeyResponse
54, // 54: headscale.v1.HeadscaleService.ListApiKeys:output_type -> headscale.v1.ListApiKeysResponse
55, // 55: headscale.v1.HeadscaleService.DeleteApiKey:output_type -> headscale.v1.DeleteApiKeyResponse
56, // 56: headscale.v1.HeadscaleService.GetPolicy:output_type -> headscale.v1.GetPolicyResponse
57, // 57: headscale.v1.HeadscaleService.SetPolicy:output_type -> headscale.v1.SetPolicyResponse
29, // [29:58] is the sub-list for method output_type
0, // [0:29] is the sub-list for method input_type
27, // 27: headscale.v1.HeadscaleService.GetUser:output_type -> headscale.v1.GetUserResponse
28, // 28: headscale.v1.HeadscaleService.CreateUser:output_type -> headscale.v1.CreateUserResponse
29, // 29: headscale.v1.HeadscaleService.RenameUser:output_type -> headscale.v1.RenameUserResponse
30, // 30: headscale.v1.HeadscaleService.DeleteUser:output_type -> headscale.v1.DeleteUserResponse
31, // 31: headscale.v1.HeadscaleService.ListUsers:output_type -> headscale.v1.ListUsersResponse
32, // 32: headscale.v1.HeadscaleService.CreatePreAuthKey:output_type -> headscale.v1.CreatePreAuthKeyResponse
33, // 33: headscale.v1.HeadscaleService.ExpirePreAuthKey:output_type -> headscale.v1.ExpirePreAuthKeyResponse
34, // 34: headscale.v1.HeadscaleService.ListPreAuthKeys:output_type -> headscale.v1.ListPreAuthKeysResponse
35, // 35: headscale.v1.HeadscaleService.DebugCreateNode:output_type -> headscale.v1.DebugCreateNodeResponse
36, // 36: headscale.v1.HeadscaleService.GetNode:output_type -> headscale.v1.GetNodeResponse
37, // 37: headscale.v1.HeadscaleService.SetTags:output_type -> headscale.v1.SetTagsResponse
38, // 38: headscale.v1.HeadscaleService.RegisterNode:output_type -> headscale.v1.RegisterNodeResponse
39, // 39: headscale.v1.HeadscaleService.DeleteNode:output_type -> headscale.v1.DeleteNodeResponse
40, // 40: headscale.v1.HeadscaleService.ExpireNode:output_type -> headscale.v1.ExpireNodeResponse
41, // 41: headscale.v1.HeadscaleService.RenameNode:output_type -> headscale.v1.RenameNodeResponse
42, // 42: headscale.v1.HeadscaleService.ListNodes:output_type -> headscale.v1.ListNodesResponse
43, // 43: headscale.v1.HeadscaleService.MoveNode:output_type -> headscale.v1.MoveNodeResponse
44, // 44: headscale.v1.HeadscaleService.BackfillNodeIPs:output_type -> headscale.v1.BackfillNodeIPsResponse
45, // 45: headscale.v1.HeadscaleService.GetRoutes:output_type -> headscale.v1.GetRoutesResponse
46, // 46: headscale.v1.HeadscaleService.EnableRoute:output_type -> headscale.v1.EnableRouteResponse
47, // 47: headscale.v1.HeadscaleService.DisableRoute:output_type -> headscale.v1.DisableRouteResponse
48, // 48: headscale.v1.HeadscaleService.GetNodeRoutes:output_type -> headscale.v1.GetNodeRoutesResponse
49, // 49: headscale.v1.HeadscaleService.DeleteRoute:output_type -> headscale.v1.DeleteRouteResponse
50, // 50: headscale.v1.HeadscaleService.CreateApiKey:output_type -> headscale.v1.CreateApiKeyResponse
51, // 51: headscale.v1.HeadscaleService.ExpireApiKey:output_type -> headscale.v1.ExpireApiKeyResponse
52, // 52: headscale.v1.HeadscaleService.ListApiKeys:output_type -> headscale.v1.ListApiKeysResponse
53, // 53: headscale.v1.HeadscaleService.DeleteApiKey:output_type -> headscale.v1.DeleteApiKeyResponse
27, // [27:54] is the sub-list for method output_type
0, // [0:27] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
@@ -393,7 +370,6 @@ func file_headscale_v1_headscale_proto_init() {
file_headscale_v1_node_proto_init()
file_headscale_v1_routes_proto_init()
file_headscale_v1_apikey_proto_init()
file_headscale_v1_policy_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{

View File

@@ -87,11 +87,7 @@ func request_HeadscaleService_CreateUser_0(ctx context.Context, marshaler runtim
var protoReq CreateUserRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -104,11 +100,7 @@ func local_request_HeadscaleService_CreateUser_0(ctx context.Context, marshaler
var protoReq CreateUserRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -263,11 +255,7 @@ func request_HeadscaleService_CreatePreAuthKey_0(ctx context.Context, marshaler
var protoReq CreatePreAuthKeyRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -280,11 +268,7 @@ func local_request_HeadscaleService_CreatePreAuthKey_0(ctx context.Context, mars
var protoReq CreatePreAuthKeyRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -297,11 +281,7 @@ func request_HeadscaleService_ExpirePreAuthKey_0(ctx context.Context, marshaler
var protoReq ExpirePreAuthKeyRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -314,11 +294,7 @@ func local_request_HeadscaleService_ExpirePreAuthKey_0(ctx context.Context, mars
var protoReq ExpirePreAuthKeyRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -367,11 +343,7 @@ func request_HeadscaleService_DebugCreateNode_0(ctx context.Context, marshaler r
var protoReq DebugCreateNodeRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -384,11 +356,7 @@ func local_request_HeadscaleService_DebugCreateNode_0(ctx context.Context, marsh
var protoReq DebugCreateNodeRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -453,11 +421,7 @@ func request_HeadscaleService_SetTags_0(ctx context.Context, marshaler runtime.M
var protoReq SetTagsRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -487,11 +451,7 @@ func local_request_HeadscaleService_SetTags_0(ctx context.Context, marshaler run
var protoReq SetTagsRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -1101,11 +1061,7 @@ func request_HeadscaleService_CreateApiKey_0(ctx context.Context, marshaler runt
var protoReq CreateApiKeyRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -1118,11 +1074,7 @@ func local_request_HeadscaleService_CreateApiKey_0(ctx context.Context, marshale
var protoReq CreateApiKeyRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -1135,11 +1087,7 @@ func request_HeadscaleService_ExpireApiKey_0(ctx context.Context, marshaler runt
var protoReq ExpireApiKeyRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -1152,11 +1100,7 @@ func local_request_HeadscaleService_ExpireApiKey_0(ctx context.Context, marshale
var protoReq ExpireApiKeyRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -1235,58 +1179,6 @@ func local_request_HeadscaleService_DeleteApiKey_0(ctx context.Context, marshale
}
func request_HeadscaleService_GetPolicy_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetPolicyRequest
var metadata runtime.ServerMetadata
msg, err := client.GetPolicy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_HeadscaleService_GetPolicy_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetPolicyRequest
var metadata runtime.ServerMetadata
msg, err := server.GetPolicy(ctx, &protoReq)
return msg, metadata, err
}
func request_HeadscaleService_SetPolicy_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SetPolicyRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.SetPolicy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_HeadscaleService_SetPolicy_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SetPolicyRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.SetPolicy(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.
@@ -1968,63 +1860,13 @@ func RegisterHeadscaleServiceHandlerServer(ctx context.Context, mux *runtime.Ser
})
mux.Handle("GET", pattern_HeadscaleService_GetPolicy_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)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/GetPolicy", runtime.WithHTTPPathPattern("/api/v1/policy"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_HeadscaleService_GetPolicy_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_HeadscaleService_GetPolicy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("PUT", pattern_HeadscaleService_SetPolicy_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)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/SetPolicy", runtime.WithHTTPPathPattern("/api/v1/policy"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_HeadscaleService_SetPolicy_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_HeadscaleService_SetPolicy_0(annotatedContext, 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...)
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
@@ -2653,50 +2495,6 @@ func RegisterHeadscaleServiceHandlerClient(ctx context.Context, mux *runtime.Ser
})
mux.Handle("GET", pattern_HeadscaleService_GetPolicy_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)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/GetPolicy", runtime.WithHTTPPathPattern("/api/v1/policy"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_HeadscaleService_GetPolicy_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_HeadscaleService_GetPolicy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("PUT", pattern_HeadscaleService_SetPolicy_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)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/SetPolicy", runtime.WithHTTPPathPattern("/api/v1/policy"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_HeadscaleService_SetPolicy_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_HeadscaleService_SetPolicy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@@ -2754,10 +2552,6 @@ var (
pattern_HeadscaleService_ListApiKeys_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "apikey"}, ""))
pattern_HeadscaleService_DeleteApiKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "apikey", "prefix"}, ""))
pattern_HeadscaleService_GetPolicy_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "policy"}, ""))
pattern_HeadscaleService_SetPolicy_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "policy"}, ""))
)
var (
@@ -2814,8 +2608,4 @@ var (
forward_HeadscaleService_ListApiKeys_0 = runtime.ForwardResponseMessage
forward_HeadscaleService_DeleteApiKey_0 = runtime.ForwardResponseMessage
forward_HeadscaleService_GetPolicy_0 = runtime.ForwardResponseMessage
forward_HeadscaleService_SetPolicy_0 = runtime.ForwardResponseMessage
)

View File

@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc-gen-go-grpc v1.3.0
// - protoc (unknown)
// source: headscale/v1/headscale.proto
@@ -18,6 +18,36 @@ import (
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
HeadscaleService_GetUser_FullMethodName = "/headscale.v1.HeadscaleService/GetUser"
HeadscaleService_CreateUser_FullMethodName = "/headscale.v1.HeadscaleService/CreateUser"
HeadscaleService_RenameUser_FullMethodName = "/headscale.v1.HeadscaleService/RenameUser"
HeadscaleService_DeleteUser_FullMethodName = "/headscale.v1.HeadscaleService/DeleteUser"
HeadscaleService_ListUsers_FullMethodName = "/headscale.v1.HeadscaleService/ListUsers"
HeadscaleService_CreatePreAuthKey_FullMethodName = "/headscale.v1.HeadscaleService/CreatePreAuthKey"
HeadscaleService_ExpirePreAuthKey_FullMethodName = "/headscale.v1.HeadscaleService/ExpirePreAuthKey"
HeadscaleService_ListPreAuthKeys_FullMethodName = "/headscale.v1.HeadscaleService/ListPreAuthKeys"
HeadscaleService_DebugCreateNode_FullMethodName = "/headscale.v1.HeadscaleService/DebugCreateNode"
HeadscaleService_GetNode_FullMethodName = "/headscale.v1.HeadscaleService/GetNode"
HeadscaleService_SetTags_FullMethodName = "/headscale.v1.HeadscaleService/SetTags"
HeadscaleService_RegisterNode_FullMethodName = "/headscale.v1.HeadscaleService/RegisterNode"
HeadscaleService_DeleteNode_FullMethodName = "/headscale.v1.HeadscaleService/DeleteNode"
HeadscaleService_ExpireNode_FullMethodName = "/headscale.v1.HeadscaleService/ExpireNode"
HeadscaleService_RenameNode_FullMethodName = "/headscale.v1.HeadscaleService/RenameNode"
HeadscaleService_ListNodes_FullMethodName = "/headscale.v1.HeadscaleService/ListNodes"
HeadscaleService_MoveNode_FullMethodName = "/headscale.v1.HeadscaleService/MoveNode"
HeadscaleService_BackfillNodeIPs_FullMethodName = "/headscale.v1.HeadscaleService/BackfillNodeIPs"
HeadscaleService_GetRoutes_FullMethodName = "/headscale.v1.HeadscaleService/GetRoutes"
HeadscaleService_EnableRoute_FullMethodName = "/headscale.v1.HeadscaleService/EnableRoute"
HeadscaleService_DisableRoute_FullMethodName = "/headscale.v1.HeadscaleService/DisableRoute"
HeadscaleService_GetNodeRoutes_FullMethodName = "/headscale.v1.HeadscaleService/GetNodeRoutes"
HeadscaleService_DeleteRoute_FullMethodName = "/headscale.v1.HeadscaleService/DeleteRoute"
HeadscaleService_CreateApiKey_FullMethodName = "/headscale.v1.HeadscaleService/CreateApiKey"
HeadscaleService_ExpireApiKey_FullMethodName = "/headscale.v1.HeadscaleService/ExpireApiKey"
HeadscaleService_ListApiKeys_FullMethodName = "/headscale.v1.HeadscaleService/ListApiKeys"
HeadscaleService_DeleteApiKey_FullMethodName = "/headscale.v1.HeadscaleService/DeleteApiKey"
)
// 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.
@@ -54,9 +84,6 @@ type HeadscaleServiceClient interface {
ExpireApiKey(ctx context.Context, in *ExpireApiKeyRequest, opts ...grpc.CallOption) (*ExpireApiKeyResponse, error)
ListApiKeys(ctx context.Context, in *ListApiKeysRequest, opts ...grpc.CallOption) (*ListApiKeysResponse, error)
DeleteApiKey(ctx context.Context, in *DeleteApiKeyRequest, opts ...grpc.CallOption) (*DeleteApiKeyResponse, error)
// --- Policy start ---
GetPolicy(ctx context.Context, in *GetPolicyRequest, opts ...grpc.CallOption) (*GetPolicyResponse, error)
SetPolicy(ctx context.Context, in *SetPolicyRequest, opts ...grpc.CallOption) (*SetPolicyResponse, error)
}
type headscaleServiceClient struct {
@@ -69,7 +96,7 @@ func NewHeadscaleServiceClient(cc grpc.ClientConnInterface) HeadscaleServiceClie
func (c *headscaleServiceClient) GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*GetUserResponse, error) {
out := new(GetUserResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/GetUser", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_GetUser_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -78,7 +105,7 @@ func (c *headscaleServiceClient) GetUser(ctx context.Context, in *GetUserRequest
func (c *headscaleServiceClient) CreateUser(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*CreateUserResponse, error) {
out := new(CreateUserResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/CreateUser", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_CreateUser_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -87,7 +114,7 @@ func (c *headscaleServiceClient) CreateUser(ctx context.Context, in *CreateUserR
func (c *headscaleServiceClient) RenameUser(ctx context.Context, in *RenameUserRequest, opts ...grpc.CallOption) (*RenameUserResponse, error) {
out := new(RenameUserResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/RenameUser", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_RenameUser_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -96,7 +123,7 @@ func (c *headscaleServiceClient) RenameUser(ctx context.Context, in *RenameUserR
func (c *headscaleServiceClient) DeleteUser(ctx context.Context, in *DeleteUserRequest, opts ...grpc.CallOption) (*DeleteUserResponse, error) {
out := new(DeleteUserResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/DeleteUser", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_DeleteUser_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -105,7 +132,7 @@ func (c *headscaleServiceClient) DeleteUser(ctx context.Context, in *DeleteUserR
func (c *headscaleServiceClient) ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (*ListUsersResponse, error) {
out := new(ListUsersResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ListUsers", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_ListUsers_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -114,7 +141,7 @@ func (c *headscaleServiceClient) ListUsers(ctx context.Context, in *ListUsersReq
func (c *headscaleServiceClient) CreatePreAuthKey(ctx context.Context, in *CreatePreAuthKeyRequest, opts ...grpc.CallOption) (*CreatePreAuthKeyResponse, error) {
out := new(CreatePreAuthKeyResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/CreatePreAuthKey", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_CreatePreAuthKey_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -123,7 +150,7 @@ func (c *headscaleServiceClient) CreatePreAuthKey(ctx context.Context, in *Creat
func (c *headscaleServiceClient) ExpirePreAuthKey(ctx context.Context, in *ExpirePreAuthKeyRequest, opts ...grpc.CallOption) (*ExpirePreAuthKeyResponse, error) {
out := new(ExpirePreAuthKeyResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ExpirePreAuthKey", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_ExpirePreAuthKey_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -132,7 +159,7 @@ func (c *headscaleServiceClient) ExpirePreAuthKey(ctx context.Context, in *Expir
func (c *headscaleServiceClient) ListPreAuthKeys(ctx context.Context, in *ListPreAuthKeysRequest, opts ...grpc.CallOption) (*ListPreAuthKeysResponse, error) {
out := new(ListPreAuthKeysResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ListPreAuthKeys", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_ListPreAuthKeys_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -141,7 +168,7 @@ func (c *headscaleServiceClient) ListPreAuthKeys(ctx context.Context, in *ListPr
func (c *headscaleServiceClient) DebugCreateNode(ctx context.Context, in *DebugCreateNodeRequest, opts ...grpc.CallOption) (*DebugCreateNodeResponse, error) {
out := new(DebugCreateNodeResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/DebugCreateNode", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_DebugCreateNode_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -150,7 +177,7 @@ func (c *headscaleServiceClient) DebugCreateNode(ctx context.Context, in *DebugC
func (c *headscaleServiceClient) GetNode(ctx context.Context, in *GetNodeRequest, opts ...grpc.CallOption) (*GetNodeResponse, error) {
out := new(GetNodeResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/GetNode", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_GetNode_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -159,7 +186,7 @@ func (c *headscaleServiceClient) GetNode(ctx context.Context, in *GetNodeRequest
func (c *headscaleServiceClient) SetTags(ctx context.Context, in *SetTagsRequest, opts ...grpc.CallOption) (*SetTagsResponse, error) {
out := new(SetTagsResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/SetTags", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_SetTags_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -168,7 +195,7 @@ func (c *headscaleServiceClient) SetTags(ctx context.Context, in *SetTagsRequest
func (c *headscaleServiceClient) RegisterNode(ctx context.Context, in *RegisterNodeRequest, opts ...grpc.CallOption) (*RegisterNodeResponse, error) {
out := new(RegisterNodeResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/RegisterNode", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_RegisterNode_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -177,7 +204,7 @@ func (c *headscaleServiceClient) RegisterNode(ctx context.Context, in *RegisterN
func (c *headscaleServiceClient) DeleteNode(ctx context.Context, in *DeleteNodeRequest, opts ...grpc.CallOption) (*DeleteNodeResponse, error) {
out := new(DeleteNodeResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/DeleteNode", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_DeleteNode_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -186,7 +213,7 @@ func (c *headscaleServiceClient) DeleteNode(ctx context.Context, in *DeleteNodeR
func (c *headscaleServiceClient) ExpireNode(ctx context.Context, in *ExpireNodeRequest, opts ...grpc.CallOption) (*ExpireNodeResponse, error) {
out := new(ExpireNodeResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ExpireNode", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_ExpireNode_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -195,7 +222,7 @@ func (c *headscaleServiceClient) ExpireNode(ctx context.Context, in *ExpireNodeR
func (c *headscaleServiceClient) RenameNode(ctx context.Context, in *RenameNodeRequest, opts ...grpc.CallOption) (*RenameNodeResponse, error) {
out := new(RenameNodeResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/RenameNode", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_RenameNode_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -204,7 +231,7 @@ func (c *headscaleServiceClient) RenameNode(ctx context.Context, in *RenameNodeR
func (c *headscaleServiceClient) ListNodes(ctx context.Context, in *ListNodesRequest, opts ...grpc.CallOption) (*ListNodesResponse, error) {
out := new(ListNodesResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ListNodes", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_ListNodes_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -213,7 +240,7 @@ func (c *headscaleServiceClient) ListNodes(ctx context.Context, in *ListNodesReq
func (c *headscaleServiceClient) MoveNode(ctx context.Context, in *MoveNodeRequest, opts ...grpc.CallOption) (*MoveNodeResponse, error) {
out := new(MoveNodeResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/MoveNode", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_MoveNode_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -222,7 +249,7 @@ func (c *headscaleServiceClient) MoveNode(ctx context.Context, in *MoveNodeReque
func (c *headscaleServiceClient) BackfillNodeIPs(ctx context.Context, in *BackfillNodeIPsRequest, opts ...grpc.CallOption) (*BackfillNodeIPsResponse, error) {
out := new(BackfillNodeIPsResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/BackfillNodeIPs", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_BackfillNodeIPs_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -231,7 +258,7 @@ func (c *headscaleServiceClient) BackfillNodeIPs(ctx context.Context, in *Backfi
func (c *headscaleServiceClient) GetRoutes(ctx context.Context, in *GetRoutesRequest, opts ...grpc.CallOption) (*GetRoutesResponse, error) {
out := new(GetRoutesResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/GetRoutes", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_GetRoutes_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -240,7 +267,7 @@ func (c *headscaleServiceClient) GetRoutes(ctx context.Context, in *GetRoutesReq
func (c *headscaleServiceClient) EnableRoute(ctx context.Context, in *EnableRouteRequest, opts ...grpc.CallOption) (*EnableRouteResponse, error) {
out := new(EnableRouteResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/EnableRoute", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_EnableRoute_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -249,7 +276,7 @@ func (c *headscaleServiceClient) EnableRoute(ctx context.Context, in *EnableRout
func (c *headscaleServiceClient) DisableRoute(ctx context.Context, in *DisableRouteRequest, opts ...grpc.CallOption) (*DisableRouteResponse, error) {
out := new(DisableRouteResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/DisableRoute", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_DisableRoute_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -258,7 +285,7 @@ func (c *headscaleServiceClient) DisableRoute(ctx context.Context, in *DisableRo
func (c *headscaleServiceClient) GetNodeRoutes(ctx context.Context, in *GetNodeRoutesRequest, opts ...grpc.CallOption) (*GetNodeRoutesResponse, error) {
out := new(GetNodeRoutesResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/GetNodeRoutes", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_GetNodeRoutes_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -267,7 +294,7 @@ func (c *headscaleServiceClient) GetNodeRoutes(ctx context.Context, in *GetNodeR
func (c *headscaleServiceClient) DeleteRoute(ctx context.Context, in *DeleteRouteRequest, opts ...grpc.CallOption) (*DeleteRouteResponse, error) {
out := new(DeleteRouteResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/DeleteRoute", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_DeleteRoute_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -276,7 +303,7 @@ func (c *headscaleServiceClient) DeleteRoute(ctx context.Context, in *DeleteRout
func (c *headscaleServiceClient) CreateApiKey(ctx context.Context, in *CreateApiKeyRequest, opts ...grpc.CallOption) (*CreateApiKeyResponse, error) {
out := new(CreateApiKeyResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/CreateApiKey", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_CreateApiKey_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -285,7 +312,7 @@ func (c *headscaleServiceClient) CreateApiKey(ctx context.Context, in *CreateApi
func (c *headscaleServiceClient) ExpireApiKey(ctx context.Context, in *ExpireApiKeyRequest, opts ...grpc.CallOption) (*ExpireApiKeyResponse, error) {
out := new(ExpireApiKeyResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ExpireApiKey", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_ExpireApiKey_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -294,7 +321,7 @@ func (c *headscaleServiceClient) ExpireApiKey(ctx context.Context, in *ExpireApi
func (c *headscaleServiceClient) ListApiKeys(ctx context.Context, in *ListApiKeysRequest, opts ...grpc.CallOption) (*ListApiKeysResponse, error) {
out := new(ListApiKeysResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ListApiKeys", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_ListApiKeys_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -303,25 +330,7 @@ func (c *headscaleServiceClient) ListApiKeys(ctx context.Context, in *ListApiKey
func (c *headscaleServiceClient) DeleteApiKey(ctx context.Context, in *DeleteApiKeyRequest, opts ...grpc.CallOption) (*DeleteApiKeyResponse, error) {
out := new(DeleteApiKeyResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/DeleteApiKey", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *headscaleServiceClient) GetPolicy(ctx context.Context, in *GetPolicyRequest, opts ...grpc.CallOption) (*GetPolicyResponse, error) {
out := new(GetPolicyResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/GetPolicy", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *headscaleServiceClient) SetPolicy(ctx context.Context, in *SetPolicyRequest, opts ...grpc.CallOption) (*SetPolicyResponse, error) {
out := new(SetPolicyResponse)
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/SetPolicy", in, out, opts...)
err := c.cc.Invoke(ctx, HeadscaleService_DeleteApiKey_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
@@ -364,9 +373,6 @@ type HeadscaleServiceServer interface {
ExpireApiKey(context.Context, *ExpireApiKeyRequest) (*ExpireApiKeyResponse, error)
ListApiKeys(context.Context, *ListApiKeysRequest) (*ListApiKeysResponse, error)
DeleteApiKey(context.Context, *DeleteApiKeyRequest) (*DeleteApiKeyResponse, error)
// --- Policy start ---
GetPolicy(context.Context, *GetPolicyRequest) (*GetPolicyResponse, error)
SetPolicy(context.Context, *SetPolicyRequest) (*SetPolicyResponse, error)
mustEmbedUnimplementedHeadscaleServiceServer()
}
@@ -455,12 +461,6 @@ func (UnimplementedHeadscaleServiceServer) ListApiKeys(context.Context, *ListApi
func (UnimplementedHeadscaleServiceServer) DeleteApiKey(context.Context, *DeleteApiKeyRequest) (*DeleteApiKeyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteApiKey not implemented")
}
func (UnimplementedHeadscaleServiceServer) GetPolicy(context.Context, *GetPolicyRequest) (*GetPolicyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetPolicy not implemented")
}
func (UnimplementedHeadscaleServiceServer) SetPolicy(context.Context, *SetPolicyRequest) (*SetPolicyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetPolicy not implemented")
}
func (UnimplementedHeadscaleServiceServer) mustEmbedUnimplementedHeadscaleServiceServer() {}
// UnsafeHeadscaleServiceServer may be embedded to opt out of forward compatibility for this service.
@@ -484,7 +484,7 @@ func _HeadscaleService_GetUser_Handler(srv interface{}, ctx context.Context, dec
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/GetUser",
FullMethod: HeadscaleService_GetUser_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).GetUser(ctx, req.(*GetUserRequest))
@@ -502,7 +502,7 @@ func _HeadscaleService_CreateUser_Handler(srv interface{}, ctx context.Context,
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/CreateUser",
FullMethod: HeadscaleService_CreateUser_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).CreateUser(ctx, req.(*CreateUserRequest))
@@ -520,7 +520,7 @@ func _HeadscaleService_RenameUser_Handler(srv interface{}, ctx context.Context,
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/RenameUser",
FullMethod: HeadscaleService_RenameUser_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).RenameUser(ctx, req.(*RenameUserRequest))
@@ -538,7 +538,7 @@ func _HeadscaleService_DeleteUser_Handler(srv interface{}, ctx context.Context,
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/DeleteUser",
FullMethod: HeadscaleService_DeleteUser_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).DeleteUser(ctx, req.(*DeleteUserRequest))
@@ -556,7 +556,7 @@ func _HeadscaleService_ListUsers_Handler(srv interface{}, ctx context.Context, d
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/ListUsers",
FullMethod: HeadscaleService_ListUsers_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).ListUsers(ctx, req.(*ListUsersRequest))
@@ -574,7 +574,7 @@ func _HeadscaleService_CreatePreAuthKey_Handler(srv interface{}, ctx context.Con
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/CreatePreAuthKey",
FullMethod: HeadscaleService_CreatePreAuthKey_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).CreatePreAuthKey(ctx, req.(*CreatePreAuthKeyRequest))
@@ -592,7 +592,7 @@ func _HeadscaleService_ExpirePreAuthKey_Handler(srv interface{}, ctx context.Con
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/ExpirePreAuthKey",
FullMethod: HeadscaleService_ExpirePreAuthKey_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).ExpirePreAuthKey(ctx, req.(*ExpirePreAuthKeyRequest))
@@ -610,7 +610,7 @@ func _HeadscaleService_ListPreAuthKeys_Handler(srv interface{}, ctx context.Cont
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/ListPreAuthKeys",
FullMethod: HeadscaleService_ListPreAuthKeys_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).ListPreAuthKeys(ctx, req.(*ListPreAuthKeysRequest))
@@ -628,7 +628,7 @@ func _HeadscaleService_DebugCreateNode_Handler(srv interface{}, ctx context.Cont
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/DebugCreateNode",
FullMethod: HeadscaleService_DebugCreateNode_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).DebugCreateNode(ctx, req.(*DebugCreateNodeRequest))
@@ -646,7 +646,7 @@ func _HeadscaleService_GetNode_Handler(srv interface{}, ctx context.Context, dec
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/GetNode",
FullMethod: HeadscaleService_GetNode_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).GetNode(ctx, req.(*GetNodeRequest))
@@ -664,7 +664,7 @@ func _HeadscaleService_SetTags_Handler(srv interface{}, ctx context.Context, dec
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/SetTags",
FullMethod: HeadscaleService_SetTags_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).SetTags(ctx, req.(*SetTagsRequest))
@@ -682,7 +682,7 @@ func _HeadscaleService_RegisterNode_Handler(srv interface{}, ctx context.Context
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/RegisterNode",
FullMethod: HeadscaleService_RegisterNode_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).RegisterNode(ctx, req.(*RegisterNodeRequest))
@@ -700,7 +700,7 @@ func _HeadscaleService_DeleteNode_Handler(srv interface{}, ctx context.Context,
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/DeleteNode",
FullMethod: HeadscaleService_DeleteNode_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).DeleteNode(ctx, req.(*DeleteNodeRequest))
@@ -718,7 +718,7 @@ func _HeadscaleService_ExpireNode_Handler(srv interface{}, ctx context.Context,
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/ExpireNode",
FullMethod: HeadscaleService_ExpireNode_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).ExpireNode(ctx, req.(*ExpireNodeRequest))
@@ -736,7 +736,7 @@ func _HeadscaleService_RenameNode_Handler(srv interface{}, ctx context.Context,
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/RenameNode",
FullMethod: HeadscaleService_RenameNode_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).RenameNode(ctx, req.(*RenameNodeRequest))
@@ -754,7 +754,7 @@ func _HeadscaleService_ListNodes_Handler(srv interface{}, ctx context.Context, d
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/ListNodes",
FullMethod: HeadscaleService_ListNodes_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).ListNodes(ctx, req.(*ListNodesRequest))
@@ -772,7 +772,7 @@ func _HeadscaleService_MoveNode_Handler(srv interface{}, ctx context.Context, de
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/MoveNode",
FullMethod: HeadscaleService_MoveNode_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).MoveNode(ctx, req.(*MoveNodeRequest))
@@ -790,7 +790,7 @@ func _HeadscaleService_BackfillNodeIPs_Handler(srv interface{}, ctx context.Cont
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/BackfillNodeIPs",
FullMethod: HeadscaleService_BackfillNodeIPs_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).BackfillNodeIPs(ctx, req.(*BackfillNodeIPsRequest))
@@ -808,7 +808,7 @@ func _HeadscaleService_GetRoutes_Handler(srv interface{}, ctx context.Context, d
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/GetRoutes",
FullMethod: HeadscaleService_GetRoutes_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).GetRoutes(ctx, req.(*GetRoutesRequest))
@@ -826,7 +826,7 @@ func _HeadscaleService_EnableRoute_Handler(srv interface{}, ctx context.Context,
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/EnableRoute",
FullMethod: HeadscaleService_EnableRoute_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).EnableRoute(ctx, req.(*EnableRouteRequest))
@@ -844,7 +844,7 @@ func _HeadscaleService_DisableRoute_Handler(srv interface{}, ctx context.Context
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/DisableRoute",
FullMethod: HeadscaleService_DisableRoute_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).DisableRoute(ctx, req.(*DisableRouteRequest))
@@ -862,7 +862,7 @@ func _HeadscaleService_GetNodeRoutes_Handler(srv interface{}, ctx context.Contex
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/GetNodeRoutes",
FullMethod: HeadscaleService_GetNodeRoutes_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).GetNodeRoutes(ctx, req.(*GetNodeRoutesRequest))
@@ -880,7 +880,7 @@ func _HeadscaleService_DeleteRoute_Handler(srv interface{}, ctx context.Context,
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/DeleteRoute",
FullMethod: HeadscaleService_DeleteRoute_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).DeleteRoute(ctx, req.(*DeleteRouteRequest))
@@ -898,7 +898,7 @@ func _HeadscaleService_CreateApiKey_Handler(srv interface{}, ctx context.Context
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/CreateApiKey",
FullMethod: HeadscaleService_CreateApiKey_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).CreateApiKey(ctx, req.(*CreateApiKeyRequest))
@@ -916,7 +916,7 @@ func _HeadscaleService_ExpireApiKey_Handler(srv interface{}, ctx context.Context
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/ExpireApiKey",
FullMethod: HeadscaleService_ExpireApiKey_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).ExpireApiKey(ctx, req.(*ExpireApiKeyRequest))
@@ -934,7 +934,7 @@ func _HeadscaleService_ListApiKeys_Handler(srv interface{}, ctx context.Context,
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/ListApiKeys",
FullMethod: HeadscaleService_ListApiKeys_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).ListApiKeys(ctx, req.(*ListApiKeysRequest))
@@ -952,7 +952,7 @@ func _HeadscaleService_DeleteApiKey_Handler(srv interface{}, ctx context.Context
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/DeleteApiKey",
FullMethod: HeadscaleService_DeleteApiKey_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).DeleteApiKey(ctx, req.(*DeleteApiKeyRequest))
@@ -960,42 +960,6 @@ func _HeadscaleService_DeleteApiKey_Handler(srv interface{}, ctx context.Context
return interceptor(ctx, in, info, handler)
}
func _HeadscaleService_GetPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetPolicyRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(HeadscaleServiceServer).GetPolicy(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/GetPolicy",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).GetPolicy(ctx, req.(*GetPolicyRequest))
}
return interceptor(ctx, in, info, handler)
}
func _HeadscaleService_SetPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetPolicyRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(HeadscaleServiceServer).SetPolicy(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/headscale.v1.HeadscaleService/SetPolicy",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HeadscaleServiceServer).SetPolicy(ctx, req.(*SetPolicyRequest))
}
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)
@@ -1111,14 +1075,6 @@ var HeadscaleService_ServiceDesc = grpc.ServiceDesc{
MethodName: "DeleteApiKey",
Handler: _HeadscaleService_DeleteApiKey_Handler,
},
{
MethodName: "GetPolicy",
Handler: _HeadscaleService_GetPolicy_Handler,
},
{
MethodName: "SetPolicy",
Handler: _HeadscaleService_SetPolicy_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "headscale/v1/headscale.proto",

View File

@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.32.0
// protoc-gen-go v1.33.0
// protoc (unknown)
// source: headscale/v1/node.proto

View File

@@ -1,352 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.32.0
// protoc (unknown)
// source: headscale/v1/policy.proto
package v1
import (
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 SetPolicyRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Policy string `protobuf:"bytes,1,opt,name=policy,proto3" json:"policy,omitempty"`
}
func (x *SetPolicyRequest) Reset() {
*x = SetPolicyRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_headscale_v1_policy_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SetPolicyRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SetPolicyRequest) ProtoMessage() {}
func (x *SetPolicyRequest) ProtoReflect() protoreflect.Message {
mi := &file_headscale_v1_policy_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 SetPolicyRequest.ProtoReflect.Descriptor instead.
func (*SetPolicyRequest) Descriptor() ([]byte, []int) {
return file_headscale_v1_policy_proto_rawDescGZIP(), []int{0}
}
func (x *SetPolicyRequest) GetPolicy() string {
if x != nil {
return x.Policy
}
return ""
}
type SetPolicyResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Policy string `protobuf:"bytes,1,opt,name=policy,proto3" json:"policy,omitempty"`
UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
}
func (x *SetPolicyResponse) Reset() {
*x = SetPolicyResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_headscale_v1_policy_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SetPolicyResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SetPolicyResponse) ProtoMessage() {}
func (x *SetPolicyResponse) ProtoReflect() protoreflect.Message {
mi := &file_headscale_v1_policy_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 SetPolicyResponse.ProtoReflect.Descriptor instead.
func (*SetPolicyResponse) Descriptor() ([]byte, []int) {
return file_headscale_v1_policy_proto_rawDescGZIP(), []int{1}
}
func (x *SetPolicyResponse) GetPolicy() string {
if x != nil {
return x.Policy
}
return ""
}
func (x *SetPolicyResponse) GetUpdatedAt() *timestamppb.Timestamp {
if x != nil {
return x.UpdatedAt
}
return nil
}
type GetPolicyRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *GetPolicyRequest) Reset() {
*x = GetPolicyRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_headscale_v1_policy_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetPolicyRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetPolicyRequest) ProtoMessage() {}
func (x *GetPolicyRequest) ProtoReflect() protoreflect.Message {
mi := &file_headscale_v1_policy_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 GetPolicyRequest.ProtoReflect.Descriptor instead.
func (*GetPolicyRequest) Descriptor() ([]byte, []int) {
return file_headscale_v1_policy_proto_rawDescGZIP(), []int{2}
}
type GetPolicyResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Policy string `protobuf:"bytes,1,opt,name=policy,proto3" json:"policy,omitempty"`
UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
}
func (x *GetPolicyResponse) Reset() {
*x = GetPolicyResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_headscale_v1_policy_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetPolicyResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetPolicyResponse) ProtoMessage() {}
func (x *GetPolicyResponse) ProtoReflect() protoreflect.Message {
mi := &file_headscale_v1_policy_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 GetPolicyResponse.ProtoReflect.Descriptor instead.
func (*GetPolicyResponse) Descriptor() ([]byte, []int) {
return file_headscale_v1_policy_proto_rawDescGZIP(), []int{3}
}
func (x *GetPolicyResponse) GetPolicy() string {
if x != nil {
return x.Policy
}
return ""
}
func (x *GetPolicyResponse) GetUpdatedAt() *timestamppb.Timestamp {
if x != nil {
return x.UpdatedAt
}
return nil
}
var File_headscale_v1_policy_proto protoreflect.FileDescriptor
var file_headscale_v1_policy_proto_rawDesc = []byte{
0x0a, 0x19, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x70,
0x6f, 0x6c, 0x69, 0x63, 0x79, 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, 0x22, 0x2a, 0x0a, 0x10, 0x53, 0x65,
0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16,
0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x66, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x50, 0x6f, 0x6c,
0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70,
0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6c,
0x69, 0x63, 0x79, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61,
0x74, 0x18, 0x02, 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, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x12,
0x0a, 0x10, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x22, 0x66, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12,
0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 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, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 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_headscale_v1_policy_proto_rawDescOnce sync.Once
file_headscale_v1_policy_proto_rawDescData = file_headscale_v1_policy_proto_rawDesc
)
func file_headscale_v1_policy_proto_rawDescGZIP() []byte {
file_headscale_v1_policy_proto_rawDescOnce.Do(func() {
file_headscale_v1_policy_proto_rawDescData = protoimpl.X.CompressGZIP(file_headscale_v1_policy_proto_rawDescData)
})
return file_headscale_v1_policy_proto_rawDescData
}
var file_headscale_v1_policy_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_headscale_v1_policy_proto_goTypes = []interface{}{
(*SetPolicyRequest)(nil), // 0: headscale.v1.SetPolicyRequest
(*SetPolicyResponse)(nil), // 1: headscale.v1.SetPolicyResponse
(*GetPolicyRequest)(nil), // 2: headscale.v1.GetPolicyRequest
(*GetPolicyResponse)(nil), // 3: headscale.v1.GetPolicyResponse
(*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp
}
var file_headscale_v1_policy_proto_depIdxs = []int32{
4, // 0: headscale.v1.SetPolicyResponse.updated_at:type_name -> google.protobuf.Timestamp
4, // 1: headscale.v1.GetPolicyResponse.updated_at:type_name -> google.protobuf.Timestamp
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_headscale_v1_policy_proto_init() }
func file_headscale_v1_policy_proto_init() {
if File_headscale_v1_policy_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_headscale_v1_policy_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SetPolicyRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_headscale_v1_policy_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SetPolicyResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_headscale_v1_policy_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetPolicyRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_headscale_v1_policy_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetPolicyResponse); 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_headscale_v1_policy_proto_rawDesc,
NumEnums: 0,
NumMessages: 4,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_headscale_v1_policy_proto_goTypes,
DependencyIndexes: file_headscale_v1_policy_proto_depIdxs,
MessageInfos: file_headscale_v1_policy_proto_msgTypes,
}.Build()
File_headscale_v1_policy_proto = out.File
file_headscale_v1_policy_proto_rawDesc = nil
file_headscale_v1_policy_proto_goTypes = nil
file_headscale_v1_policy_proto_depIdxs = nil
}

View File

@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.32.0
// protoc-gen-go v1.33.0
// protoc (unknown)
// source: headscale/v1/preauthkey.proto

View File

@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.32.0
// protoc-gen-go v1.33.0
// protoc (unknown)
// source: headscale/v1/routes.proto

View File

@@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.32.0
// protoc-gen-go v1.33.0
// protoc (unknown)
// source: headscale/v1/user.proto

View File

@@ -34,6 +34,7 @@
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}

View File

@@ -34,6 +34,7 @@
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}

View File

@@ -449,15 +449,7 @@
"in": "body",
"required": true,
"schema": {
"type": "object",
"properties": {
"tags": {
"type": "array",
"items": {
"type": "string"
}
}
}
"$ref": "#/definitions/HeadscaleServiceSetTagsBody"
}
}
],
@@ -503,59 +495,6 @@
]
}
},
"/api/v1/policy": {
"get": {
"summary": "--- Policy start ---",
"operationId": "HeadscaleService_GetPolicy",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1GetPolicyResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"tags": [
"HeadscaleService"
]
},
"put": {
"operationId": "HeadscaleService_SetPolicy",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1SetPolicyResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1SetPolicyRequest"
}
}
],
"tags": [
"HeadscaleService"
]
}
},
"/api/v1/preauthkey": {
"get": {
"operationId": "HeadscaleService_ListPreAuthKeys",
@@ -914,6 +853,17 @@
}
},
"definitions": {
"HeadscaleServiceSetTagsBody": {
"type": "object",
"properties": {
"tags": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"protobufAny": {
"type": "object",
"properties": {
@@ -936,6 +886,7 @@
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}
@@ -1134,29 +1085,19 @@
"routes": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Route"
}
}
}
},
"v1GetPolicyResponse": {
"type": "object",
"properties": {
"policy": {
"type": "string"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"v1GetRoutesResponse": {
"type": "object",
"properties": {
"routes": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Route"
}
}
@@ -1176,6 +1117,7 @@
"apiKeys": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1ApiKey"
}
}
@@ -1187,6 +1129,7 @@
"nodes": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Node"
}
}
@@ -1198,6 +1141,7 @@
"preAuthKeys": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1PreAuthKey"
}
}
@@ -1209,6 +1153,7 @@
"users": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1User"
}
}
@@ -1401,26 +1346,6 @@
}
}
},
"v1SetPolicyRequest": {
"type": "object",
"properties": {
"policy": {
"type": "string"
}
}
},
"v1SetPolicyResponse": {
"type": "object",
"properties": {
"policy": {
"type": "string"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"v1SetTagsResponse": {
"type": "object",
"properties": {

View File

@@ -34,6 +34,7 @@
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}

View File

@@ -1,43 +0,0 @@
{
"swagger": "2.0",
"info": {
"title": "headscale/v1/policy.proto",
"version": "version not set"
},
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {},
"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"
}
}
}
}
}
}

View File

@@ -34,6 +34,7 @@
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}

View File

@@ -34,6 +34,7 @@
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}

View File

@@ -34,6 +34,7 @@
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}

27
go.mod
View File

@@ -29,17 +29,16 @@ require (
github.com/puzpuzpuz/xsync/v3 v3.1.0
github.com/rs/zerolog v1.32.0
github.com/samber/lo v1.39.0
github.com/sasha-s/go-deadlock v0.3.1
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.20.0-alpha.6
github.com/spf13/viper v1.18.2
github.com/stretchr/testify v1.9.0
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
github.com/tailscale/tailsql v0.0.0-20240418235827-820559f382c1
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.25.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/net v0.27.0
golang.org/x/crypto v0.23.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.25.0
golang.org/x/oauth2 v0.20.0
golang.org/x/sync v0.7.0
google.golang.org/genproto/googleapis/api v0.0.0-20240515191416-fc5f0ca64291
@@ -101,7 +100,6 @@ require (
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
@@ -118,6 +116,7 @@ require (
github.com/gorilla/csrf v1.7.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/illarion/gonotify v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
@@ -137,6 +136,7 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.7 // indirect
github.com/lithammer/fuzzysearch v1.1.8 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
@@ -155,7 +155,6 @@ require (
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opencontainers/runc v1.1.12 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
@@ -165,7 +164,8 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
github.com/sagikazarmark/locafero v0.6.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
@@ -193,15 +193,16 @@ require (
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.uber.org/multierr v1.11.0 // indirect
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.23.0 // indirect
golang.org/x/tools v0.21.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3 // indirect
modernc.org/libc v1.50.6 // indirect

55
go.sum
View File

@@ -180,8 +180,6 @@ github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW
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=
github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
@@ -242,8 +240,11 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
@@ -311,6 +312,8 @@ github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@@ -364,8 +367,6 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/philip-bui/grpc-zerolog v1.0.1 h1:EMacvLRUd2O1K0eWod27ZP5CY1iTNkhBDLSN+Q4JEvA=
github.com/philip-bui/grpc-zerolog v1.0.1/go.mod h1:qXbiq/2X4ZUMMshsqlWyTHOcw7ns+GZmlqZZN05ZHcQ=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
@@ -416,12 +417,12 @@ github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWR
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
@@ -438,8 +439,8 @@ github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.20.0-alpha.6 h1:f65Cr/+2qk4GfHC0xqT/isoupQppwN5+VLRztUGTDbY=
github.com/spf13/viper v1.20.0-alpha.6/go.mod h1:CGBZzv0c9fOUASm6rfus4wdeIjR/04NOLq1P4KRhX3k=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -533,11 +534,11 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a h1:8qmSSA8Gz/1kTrCe0nqR0R3Gb/NDhykzWw2q2mWZydM=
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
@@ -550,8 +551,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -564,8 +565,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
@@ -610,8 +611,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -619,8 +620,8 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -628,8 +629,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -643,8 +644,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -676,6 +677,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -8,7 +8,7 @@ import (
"io"
"net"
"net/http"
_ "net/http/pprof" // nolint
_ "net/http/pprof" //nolint
"os"
"os/signal"
"path/filepath"
@@ -19,7 +19,6 @@ import (
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/davecgh/go-spew/spew"
"github.com/gorilla/mux"
grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpcRuntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
@@ -79,13 +78,17 @@ const (
registerCacheCleanup = time.Minute * 20
)
// func init() {
// deadlock.Opts.DeadlockTimeout = 15 * time.Second
// deadlock.Opts.PrintAllCurrentGoroutines = true
// }
// Headscale represents the base app of the service.
type Headscale struct {
cfg *types.Config
db *db.HSDatabase
ipAlloc *db.IPAllocator
noisePrivateKey *key.MachinePrivate
ephemeralGC *db.EphemeralGarbageCollector
DERPMap *tailcfg.DERPMap
DERPServer *derpServer.DERPServer
@@ -101,15 +104,16 @@ type Headscale struct {
registrationCache *cache.Cache
pollNetMapStreamWG sync.WaitGroup
mapSessions map[types.NodeID]*mapSession
mapSessionMu sync.Mutex
}
var (
profilingEnabled = envknob.Bool("HEADSCALE_DEBUG_PROFILING_ENABLED")
profilingPath = envknob.String("HEADSCALE_DEBUG_PROFILING_PATH")
profilingEnabled = envknob.Bool("HEADSCALE_PROFILING_ENABLED")
tailsqlEnabled = envknob.Bool("HEADSCALE_DEBUG_TAILSQL_ENABLED")
tailsqlStateDir = envknob.String("HEADSCALE_DEBUG_TAILSQL_STATE_DIR")
tailsqlTSKey = envknob.String("TS_AUTHKEY")
dumpConfig = envknob.Bool("HEADSCALE_DEBUG_DUMP_CONFIG")
)
func NewHeadscale(cfg *types.Config) (*Headscale, error) {
@@ -134,6 +138,7 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) {
registrationCache: registrationCache,
pollNetMapStreamWG: sync.WaitGroup{},
nodeNotifier: notifier.NewNotifier(cfg),
mapSessions: make(map[types.NodeID]*mapSession),
}
app.db, err = db.NewHeadscaleDatabase(
@@ -148,12 +153,6 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) {
return nil, err
}
app.ephemeralGC = db.NewEphemeralGarbageCollector(func(ni types.NodeID) {
if err := app.db.DeleteEphemeralNode(ni); err != nil {
log.Err(err).Uint64("node.id", ni.Uint64()).Msgf("failed to delete ephemeral node")
}
})
if cfg.OIDC.Issuer != "" {
err = app.initOIDC()
if err != nil {
@@ -218,6 +217,47 @@ func (h *Headscale) redirect(w http.ResponseWriter, req *http.Request) {
http.Redirect(w, req, target, http.StatusFound)
}
// deleteExpireEphemeralNodes deletes ephemeral node records that have not been
// seen for longer than h.cfg.EphemeralNodeInactivityTimeout.
func (h *Headscale) deleteExpireEphemeralNodes(ctx context.Context, every time.Duration) {
ticker := time.NewTicker(every)
for {
select {
case <-ctx.Done():
ticker.Stop()
return
case <-ticker.C:
var removed []types.NodeID
var changed []types.NodeID
if err := h.db.Write(func(tx *gorm.DB) error {
removed, changed = db.DeleteExpiredEphemeralNodes(tx, h.cfg.EphemeralNodeInactivityTimeout)
return nil
}); err != nil {
log.Error().Err(err).Msg("database error while expiring ephemeral nodes")
continue
}
if removed != nil {
ctx := types.NotifyCtx(context.Background(), "expire-ephemeral", "na")
h.nodeNotifier.NotifyAll(ctx, types.StateUpdate{
Type: types.StatePeerRemoved,
Removed: removed,
})
}
if changed != nil {
ctx := types.NotifyCtx(context.Background(), "expire-ephemeral", "na")
h.nodeNotifier.NotifyAll(ctx, types.StateUpdate{
Type: types.StatePeerChanged,
ChangeNodes: changed,
})
}
}
}
}
// expireExpiredNodes expires nodes that have an explicit expiry set
// after that expiry time has passed.
func (h *Headscale) expireExpiredNodes(ctx context.Context, every time.Duration) {
@@ -290,7 +330,7 @@ func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context,
// Check if the request is coming from the on-server client.
// This is not secure, but it is to maintain maintainability
// with the "legacy" database-based client
// It is also needed for grpc-gateway to be able to connect to
// It is also neede for grpc-gateway to be able to connect to
// the server
client, _ := peer.FromContext(ctx)
@@ -462,14 +502,14 @@ func (h *Headscale) createRouter(grpcMux *grpcRuntime.ServeMux) *mux.Router {
// Serve launches the HTTP and gRPC server service Headscale and the API.
func (h *Headscale) Serve() error {
if profilingEnabled {
if profilingPath != "" {
err := os.MkdirAll(profilingPath, os.ModePerm)
if _, enableProfile := os.LookupEnv("HEADSCALE_PROFILING_ENABLED"); enableProfile {
if profilePath, ok := os.LookupEnv("HEADSCALE_PROFILING_PATH"); ok {
err := os.MkdirAll(profilePath, os.ModePerm)
if err != nil {
log.Fatal().Err(err).Msg("failed to create profiling directory")
}
defer profile.Start(profile.ProfilePath(profilingPath)).Stop()
defer profile.Start(profile.ProfilePath(profilePath)).Stop()
} else {
defer profile.Start().Stop()
}
@@ -477,14 +517,6 @@ func (h *Headscale) Serve() error {
var err error
if err = h.loadACLPolicy(); err != nil {
return fmt.Errorf("failed to load ACL policy: %w", err)
}
if dumpConfig {
spew.Dump(h.cfg)
}
// Fetch an initial DERP Map before we start serving
h.DERPMap = derp.GetDERPMap(h.cfg.DERP)
h.mapper = mapper.NewMapper(h.db, h.cfg, h.DERPMap, h.nodeNotifier)
@@ -517,18 +549,9 @@ func (h *Headscale) Serve() error {
return errEmptyInitialDERPMap
}
// Start ephemeral node garbage collector and schedule all nodes
// that are already in the database and ephemeral. If they are still
// around between restarts, they will reconnect and the GC will
// be cancelled.
go h.ephemeralGC.Start()
ephmNodes, err := h.db.ListEphemeralNodes()
if err != nil {
return fmt.Errorf("failed to list ephemeral nodes: %w", err)
}
for _, node := range ephmNodes {
h.ephemeralGC.Schedule(node.ID, h.cfg.EphemeralNodeInactivityTimeout)
}
expireEphemeralCtx, expireEphemeralCancel := context.WithCancel(context.Background())
defer expireEphemeralCancel()
go h.deleteExpireEphemeralNodes(expireEphemeralCtx, updateInterval)
expireNodeCtx, expireNodeCancel := context.WithCancel(context.Background())
defer expireNodeCancel()
@@ -679,7 +702,7 @@ func (h *Headscale) Serve() error {
Handler: router,
ReadTimeout: types.HTTPTimeout,
// Long polling should not have any timeout, this is overridden
// Long polling should not have any timeout, this is overriden
// further down the chain
WriteTimeout: types.HTTPTimeout,
}
@@ -706,6 +729,19 @@ func (h *Headscale) Serve() error {
w.WriteHeader(http.StatusOK)
w.Write([]byte(h.nodeNotifier.String()))
})
debugMux.HandleFunc("/debug/mapresp", func(w http.ResponseWriter, r *http.Request) {
h.mapSessionMu.Lock()
defer h.mapSessionMu.Unlock()
var b strings.Builder
b.WriteString("mapresponders:\n")
for k, v := range h.mapSessions {
fmt.Fprintf(&b, "\t%d: %p\n", k, v)
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(b.String()))
})
debugMux.Handle("/metrics", promhttp.Handler())
debugHTTPServer := &http.Server{
@@ -758,12 +794,17 @@ func (h *Headscale) Serve() error {
Msg("Received SIGHUP, reloading ACL and Config")
// TODO(kradalby): Reload config on SIGHUP
if err := h.loadACLPolicy(); err != nil {
log.Error().Err(err).Msg("failed to reload ACL policy")
}
if h.ACLPolicy != nil {
if h.cfg.ACL.PolicyPath != "" {
aclPath := util.AbsolutePathFromConfigPath(h.cfg.ACL.PolicyPath)
pol, err := policy.LoadACLPolicyFromPath(aclPath)
if err != nil {
log.Error().Err(err).Msg("Failed to reload ACL policy")
}
h.ACLPolicy = pol
log.Info().
Str("path", aclPath).
Msg("ACL policy successfully reloaded, notifying nodes of change")
ctx := types.NotifyCtx(context.Background(), "acl-sighup", "na")
@@ -771,6 +812,7 @@ func (h *Headscale) Serve() error {
Type: types.StateFullUpdate,
})
}
default:
trace := log.Trace().Msgf
log.Info().
@@ -778,7 +820,18 @@ func (h *Headscale) Serve() error {
Msg("Received signal to stop, shutting down gracefully")
expireNodeCancel()
h.ephemeralGC.Close()
expireEphemeralCancel()
trace("closing map sessions")
wg := sync.WaitGroup{}
for _, mapSess := range h.mapSessions {
wg.Add(1)
go func() {
mapSess.close()
wg.Done()
}()
}
wg.Wait()
trace("waiting for netmap stream to close")
h.pollNetMapStreamWG.Wait()
@@ -980,48 +1033,3 @@ func readOrCreatePrivateKey(path string) (*key.MachinePrivate, error) {
return &machineKey, nil
}
func (h *Headscale) loadACLPolicy() error {
var (
pol *policy.ACLPolicy
err error
)
switch h.cfg.Policy.Mode {
case types.PolicyModeFile:
path := h.cfg.Policy.Path
// It is fine to start headscale without a policy file.
if len(path) == 0 {
return nil
}
absPath := util.AbsolutePathFromConfigPath(path)
pol, err = policy.LoadACLPolicyFromPath(absPath)
if err != nil {
return fmt.Errorf("failed to load ACL policy from file: %w", err)
}
case types.PolicyModeDB:
p, err := h.db.GetPolicy()
if err != nil {
if errors.Is(err, types.ErrPolicyNotFound) {
return nil
}
return fmt.Errorf("failed to get policy from database: %w", err)
}
pol, err = policy.LoadACLPolicyFromBytes([]byte(p.Data))
if err != nil {
return fmt.Errorf("failed to parse policy: %w", err)
}
default:
log.Fatal().
Str("mode", string(h.cfg.Policy.Mode)).
Msg("Unknown ACL policy mode")
}
h.ACLPolicy = pol
return nil
}

View File

@@ -16,7 +16,6 @@ import (
"gorm.io/gorm"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/ptr"
)
func logAuthFunc(
@@ -315,8 +314,9 @@ func (h *Headscale) handleAuthKey(
Msg("node was already registered before, refreshing with new auth key")
node.NodeKey = nodeKey
if pak.ID != 0 {
node.AuthKeyID = ptr.To(pak.ID)
pakID := uint(pak.ID)
if pakID != 0 {
node.AuthKeyID = &pakID
}
node.Expiry = &registerRequest.Expiry
@@ -337,6 +337,7 @@ func (h *Headscale) handleAuthKey(
if len(aclTags) > 0 {
// This conditional preserves the existing behaviour, although SaaS would reset the tags on auth-key login
err = h.db.SetTags(node.ID, aclTags)
if err != nil {
log.Error().
Caller().
@@ -393,7 +394,7 @@ func (h *Headscale) handleAuthKey(
pakID := uint(pak.ID)
if pakID != 0 {
nodeToRegister.AuthKeyID = ptr.To(pak.ID)
nodeToRegister.AuthKeyID = &pakID
}
node, err = h.db.RegisterNode(
nodeToRegister,

View File

@@ -13,12 +13,13 @@ import (
"github.com/glebarez/sqlite"
"github.com/go-gormigrate/gormigrate/v2"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/rs/zerolog/log"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
)
var errDatabaseNotSupported = errors.New("database type not supported")
@@ -91,7 +92,7 @@ func NewHeadscaleDatabase(
RenameColumn(&types.Node{}, "nickname", "given_name")
dbConn.Model(&types.Node{}).Where("auth_key_id = ?", 0).Update("auth_key_id", nil)
// If the Node table has a column for registered,
// If the Node table has a column for registered,
// find all occourences of "false" and drop them. Then
// remove the column.
if tx.Migrator().HasColumn(&types.Node{}, "registered") {
@@ -318,8 +319,14 @@ func NewHeadscaleDatabase(
// no longer used.
ID: "202402151347",
Migrate: func(tx *gorm.DB) error {
_ = tx.Migrator().DropColumn(&types.Node{}, "last_successful_update")
return nil
err := tx.Migrator().DropColumn(&types.Node{}, "last_successful_update")
if err != nil && strings.Contains(err.Error(), `of relation "nodes" does not exist`) {
return nil
} else {
return err
}
return err
},
Rollback: func(tx *gorm.DB) error {
return nil
@@ -330,7 +337,7 @@ func NewHeadscaleDatabase(
// IP v4 and v6 column.
// Note that previously, the list _could_ contain more
// than two addresses, which should not really happen.
// In that case, the first occurrence of each type will
// In that case, the first occurence of each type will
// be kept.
ID: "2024041121742",
Migrate: func(tx *gorm.DB) error {
@@ -394,18 +401,6 @@ func NewHeadscaleDatabase(
return nil
},
},
{
ID: "202406021630",
Migrate: func(tx *gorm.DB) error {
err := tx.AutoMigrate(&types.Policy{})
if err != nil {
return err
}
return nil
},
Rollback: func(db *gorm.DB) error { return nil },
},
},
)
@@ -426,7 +421,7 @@ func openDB(cfg types.DatabaseConfig) (*gorm.DB, error) {
// TODO(kradalby): Integrate this with zerolog
var dbLogger logger.Interface
if cfg.Debug {
dbLogger = util.NewDBLogWrapper(&log.Logger, cfg.Gorm.SlowThreshold, cfg.Gorm.SkipErrRecordNotFound, cfg.Gorm.ParameterizedQueries)
dbLogger = logger.Default
} else {
dbLogger = logger.Default.LogMode(logger.Silent)
}
@@ -445,30 +440,13 @@ func openDB(cfg types.DatabaseConfig) (*gorm.DB, error) {
Msg("Opening database")
db, err := gorm.Open(
sqlite.Open(cfg.Sqlite.Path),
sqlite.Open(cfg.Sqlite.Path+"?_synchronous=1&_journal_mode=WAL"),
&gorm.Config{
PrepareStmt: cfg.Gorm.PrepareStmt,
Logger: dbLogger,
Logger: dbLogger,
},
)
if err := db.Exec(`
PRAGMA foreign_keys=ON;
PRAGMA busy_timeout=10000;
PRAGMA auto_vacuum=INCREMENTAL;
PRAGMA synchronous=NORMAL;
`).Error; err != nil {
return nil, fmt.Errorf("enabling foreign keys: %w", err)
}
if cfg.Sqlite.WriteAheadLog {
if err := db.Exec(`
PRAGMA journal_mode=WAL;
PRAGMA wal_autocheckpoint=0;
`).Error; err != nil {
return nil, fmt.Errorf("setting WAL mode: %w", err)
}
}
db.Exec("PRAGMA foreign_keys=ON")
// The pure Go SQLite library does not handle locking in
// the same way as the C based one and we cant use the gorm

View File

@@ -76,6 +76,7 @@ func NewIPAllocator(
if err != nil {
return nil, fmt.Errorf("reading IPv6 addresses from database: %w", err)
}
}
var ips netipx.IPSetBuilder

View File

@@ -18,11 +18,9 @@ var mpp = func(pref string) *netip.Prefix {
p := netip.MustParsePrefix(pref)
return &p
}
var na = func(pref string) netip.Addr {
return netip.MustParseAddr(pref)
}
var nap = func(pref string) *netip.Addr {
n := na(pref)
return &n

View File

@@ -12,7 +12,6 @@ import (
"github.com/patrickmn/go-cache"
"github.com/puzpuzpuz/xsync/v3"
"github.com/rs/zerolog/log"
"github.com/sasha-s/go-deadlock"
"gorm.io/gorm"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
@@ -79,17 +78,6 @@ func ListNodes(tx *gorm.DB) (types.Nodes, error) {
return nodes, nil
}
func (hsdb *HSDatabase) ListEphemeralNodes() (types.Nodes, error) {
return Read(hsdb.DB, func(rx *gorm.DB) (types.Nodes, error) {
nodes := types.Nodes{}
if err := rx.Joins("AuthKey").Where(`"AuthKey"."ephemeral" = true`).Find(&nodes).Error; err != nil {
return nil, err
}
return nodes, nil
})
}
func listNodesByGivenName(tx *gorm.DB, givenName string) (types.Nodes, error) {
nodes := types.Nodes{}
if err := tx.
@@ -227,7 +215,7 @@ func SetTags(
return nil
}
var newTags types.StringList
newTags := types.StringList{}
for _, tag := range tags {
if !util.StringOrPrefixListContains(newTags, tag) {
newTags = append(newTags, tag)
@@ -298,20 +286,6 @@ func DeleteNode(tx *gorm.DB,
return changed, nil
}
// DeleteEphemeralNode deletes a Node from the database, note that this method
// will remove it straight, and not notify any changes or consider any routes.
// It is intended for Ephemeral nodes.
func (hsdb *HSDatabase) DeleteEphemeralNode(
nodeID types.NodeID,
) error {
return hsdb.Write(func(tx *gorm.DB) error {
if err := tx.Unscoped().Delete(&types.Node{}, nodeID).Error; err != nil {
return err
}
return nil
})
}
// SetLastSeen sets a node's last seen field indicating that we
// have recently communicating with this node.
func SetLastSeen(tx *gorm.DB, nodeID types.NodeID, lastSeen time.Time) error {
@@ -478,7 +452,7 @@ func GetAdvertisedRoutes(tx *gorm.DB, node *types.Node) ([]netip.Prefix, error)
return nil, fmt.Errorf("getting advertised routes for node(%d): %w", node.ID, err)
}
var prefixes []netip.Prefix
prefixes := []netip.Prefix{}
for _, route := range routes {
prefixes = append(prefixes, netip.Prefix(route.Prefix))
}
@@ -504,7 +478,7 @@ func GetEnabledRoutes(tx *gorm.DB, node *types.Node) ([]netip.Prefix, error) {
return nil, fmt.Errorf("getting enabled routes for node(%d): %w", node.ID, err)
}
var prefixes []netip.Prefix
prefixes := []netip.Prefix{}
for _, route := range routes {
prefixes = append(prefixes, netip.Prefix(route.Prefix))
}
@@ -686,6 +660,51 @@ func GenerateGivenName(
return givenName, nil
}
func DeleteExpiredEphemeralNodes(tx *gorm.DB,
inactivityThreshhold time.Duration,
) ([]types.NodeID, []types.NodeID) {
users, err := ListUsers(tx)
if err != nil {
return nil, nil
}
var expired []types.NodeID
var changedNodes []types.NodeID
for _, user := range users {
nodes, err := ListNodesByUser(tx, user.Name)
if err != nil {
return nil, nil
}
for idx, node := range nodes {
if node.IsEphemeral() && node.LastSeen != nil &&
time.Now().
After(node.LastSeen.Add(inactivityThreshhold)) {
expired = append(expired, node.ID)
log.Info().
Str("node", node.Hostname).
Msg("Ephemeral client removed from database")
// empty isConnected map as ephemeral nodes are not routes
changed, err := DeleteNode(tx, nodes[idx], nil)
if err != nil {
log.Error().
Err(err).
Str("node", node.Hostname).
Msg("🤮 Cannot delete ephemeral node from the database")
}
changedNodes = append(changedNodes, changed...)
}
}
// TODO(kradalby): needs to be moved out of transaction
}
return expired, changedNodes
}
func ExpireExpiredNodes(tx *gorm.DB,
lastCheck time.Time,
) (time.Time, types.StateUpdate, bool) {
@@ -718,78 +737,3 @@ func ExpireExpiredNodes(tx *gorm.DB,
return started, types.StateUpdate{}, false
}
// EphemeralGarbageCollector is a garbage collector that will delete nodes after
// a certain amount of time.
// It is used to delete ephemeral nodes that have disconnected and should be
// cleaned up.
type EphemeralGarbageCollector struct {
mu deadlock.Mutex
deleteFunc func(types.NodeID)
toBeDeleted map[types.NodeID]*time.Timer
deleteCh chan types.NodeID
cancelCh chan struct{}
}
// NewEphemeralGarbageCollector creates a new EphemeralGarbageCollector, it takes
// a deleteFunc that will be called when a node is scheduled for deletion.
func NewEphemeralGarbageCollector(deleteFunc func(types.NodeID)) *EphemeralGarbageCollector {
return &EphemeralGarbageCollector{
toBeDeleted: make(map[types.NodeID]*time.Timer),
deleteCh: make(chan types.NodeID, 10),
cancelCh: make(chan struct{}),
deleteFunc: deleteFunc,
}
}
// Close stops the garbage collector.
func (e *EphemeralGarbageCollector) Close() {
e.cancelCh <- struct{}{}
}
// Schedule schedules a node for deletion after the expiry duration.
func (e *EphemeralGarbageCollector) Schedule(nodeID types.NodeID, expiry time.Duration) {
e.mu.Lock()
defer e.mu.Unlock()
timer := time.NewTimer(expiry)
e.toBeDeleted[nodeID] = timer
go func() {
select {
case _, ok := <-timer.C:
if ok {
e.deleteCh <- nodeID
}
}
}()
}
// Cancel cancels the deletion of a node.
func (e *EphemeralGarbageCollector) Cancel(nodeID types.NodeID) {
e.mu.Lock()
defer e.mu.Unlock()
if timer, ok := e.toBeDeleted[nodeID]; ok {
timer.Stop()
delete(e.toBeDeleted, nodeID)
}
}
// Start starts the garbage collector.
func (e *EphemeralGarbageCollector) Start() {
for {
select {
case <-e.cancelCh:
return
case nodeID := <-e.deleteCh:
e.mu.Lock()
delete(e.toBeDeleted, nodeID)
e.mu.Unlock()
go e.deleteFunc(nodeID)
}
}
}

View File

@@ -1,26 +1,20 @@
package db
import (
"crypto/rand"
"fmt"
"math/big"
"net/netip"
"regexp"
"strconv"
"sync"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/juanfont/headscale/hscontrol/policy"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/puzpuzpuz/xsync/v3"
"github.com/stretchr/testify/assert"
"gopkg.in/check.v1"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/ptr"
)
func (s *Suite) TestGetNode(c *check.C) {
@@ -35,6 +29,7 @@ func (s *Suite) TestGetNode(c *check.C) {
nodeKey := key.NewNode()
machineKey := key.NewMachine()
pakID := uint(pak.ID)
node := &types.Node{
ID: 0,
@@ -43,7 +38,7 @@ func (s *Suite) TestGetNode(c *check.C) {
Hostname: "testnode",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
}
trx := db.DB.Save(node)
c.Assert(trx.Error, check.IsNil)
@@ -65,6 +60,7 @@ func (s *Suite) TestGetNodeByID(c *check.C) {
nodeKey := key.NewNode()
machineKey := key.NewMachine()
pakID := uint(pak.ID)
node := types.Node{
ID: 0,
MachineKey: machineKey.Public(),
@@ -72,7 +68,7 @@ func (s *Suite) TestGetNodeByID(c *check.C) {
Hostname: "testnode",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
}
trx := db.DB.Save(&node)
c.Assert(trx.Error, check.IsNil)
@@ -96,6 +92,7 @@ func (s *Suite) TestGetNodeByAnyNodeKey(c *check.C) {
machineKey := key.NewMachine()
pakID := uint(pak.ID)
node := types.Node{
ID: 0,
MachineKey: machineKey.Public(),
@@ -103,7 +100,7 @@ func (s *Suite) TestGetNodeByAnyNodeKey(c *check.C) {
Hostname: "testnode",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
}
trx := db.DB.Save(&node)
c.Assert(trx.Error, check.IsNil)
@@ -147,6 +144,7 @@ func (s *Suite) TestListPeers(c *check.C) {
_, err = db.GetNodeByID(0)
c.Assert(err, check.NotNil)
pakID := uint(pak.ID)
for index := 0; index <= 10; index++ {
nodeKey := key.NewNode()
machineKey := key.NewMachine()
@@ -158,7 +156,7 @@ func (s *Suite) TestListPeers(c *check.C) {
Hostname: "testnode" + strconv.Itoa(index),
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
}
trx := db.DB.Save(&node)
c.Assert(trx.Error, check.IsNil)
@@ -198,6 +196,7 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
for index := 0; index <= 10; index++ {
nodeKey := key.NewNode()
machineKey := key.NewMachine()
pakID := uint(stor[index%2].key.ID)
v4 := netip.MustParseAddr(fmt.Sprintf("100.64.0.%v", strconv.Itoa(index+1)))
node := types.Node{
@@ -208,7 +207,7 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
Hostname: "testnode" + strconv.Itoa(index),
UserID: stor[index%2].user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(stor[index%2].key.ID),
AuthKeyID: &pakID,
}
trx := db.DB.Save(&node)
c.Assert(trx.Error, check.IsNil)
@@ -283,6 +282,7 @@ func (s *Suite) TestExpireNode(c *check.C) {
nodeKey := key.NewNode()
machineKey := key.NewMachine()
pakID := uint(pak.ID)
node := &types.Node{
ID: 0,
@@ -291,7 +291,7 @@ func (s *Suite) TestExpireNode(c *check.C) {
Hostname: "testnode",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
Expiry: &time.Time{},
}
db.DB.Save(node)
@@ -327,6 +327,7 @@ func (s *Suite) TestGenerateGivenName(c *check.C) {
machineKey2 := key.NewMachine()
pakID := uint(pak.ID)
node := &types.Node{
ID: 0,
MachineKey: machineKey.Public(),
@@ -335,7 +336,7 @@ func (s *Suite) TestGenerateGivenName(c *check.C) {
GivenName: "hostname-1",
UserID: user1.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
}
trx := db.DB.Save(node)
@@ -370,6 +371,7 @@ func (s *Suite) TestSetTags(c *check.C) {
nodeKey := key.NewNode()
machineKey := key.NewMachine()
pakID := uint(pak.ID)
node := &types.Node{
ID: 0,
MachineKey: machineKey.Public(),
@@ -377,7 +379,7 @@ func (s *Suite) TestSetTags(c *check.C) {
Hostname: "testnode",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
}
trx := db.DB.Save(node)
@@ -391,7 +393,7 @@ func (s *Suite) TestSetTags(c *check.C) {
c.Assert(err, check.IsNil)
c.Assert(node.ForcedTags, check.DeepEquals, types.StringList(sTags))
// assign duplicate tags, expect no errors but no doubles in DB
// assign duplicat tags, expect no errors but no doubles in DB
eTags := []string{"tag:bar", "tag:test", "tag:unknown", "tag:test"}
err = db.SetTags(node.ID, eTags)
c.Assert(err, check.IsNil)
@@ -543,7 +545,7 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) {
}
`)
pol, err := policy.LoadACLPolicyFromBytes(acl)
pol, err := policy.LoadACLPolicyFromBytes(acl, "hujson")
c.Assert(err, check.IsNil)
c.Assert(pol, check.NotNil)
@@ -563,6 +565,7 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) {
route2 := netip.MustParsePrefix("10.11.0.0/24")
v4 := netip.MustParseAddr("100.64.0.1")
pakID := uint(pak.ID)
node := types.Node{
ID: 0,
MachineKey: machineKey.Public(),
@@ -570,7 +573,7 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) {
Hostname: "test",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
Hostinfo: &tailcfg.Hostinfo{
RequestTags: []string{"tag:exit"},
RoutableIPs: []netip.Prefix{defaultRouteV4, defaultRouteV6, route1, route2},
@@ -596,131 +599,3 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) {
c.Assert(err, check.IsNil)
c.Assert(enabledRoutes, check.HasLen, 4)
}
func TestEphemeralGarbageCollectorOrder(t *testing.T) {
want := []types.NodeID{1, 3}
got := []types.NodeID{}
var mu sync.Mutex
e := NewEphemeralGarbageCollector(func(ni types.NodeID) {
mu.Lock()
defer mu.Unlock()
got = append(got, ni)
})
go e.Start()
e.Schedule(1, 1*time.Second)
e.Schedule(2, 2*time.Second)
e.Schedule(3, 3*time.Second)
e.Schedule(4, 4*time.Second)
e.Cancel(2)
e.Cancel(4)
time.Sleep(6 * time.Second)
e.Close()
mu.Lock()
defer mu.Unlock()
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong nodes deleted, unexpected result (-want +got):\n%s", diff)
}
}
func TestEphemeralGarbageCollectorLoads(t *testing.T) {
var got []types.NodeID
var mu sync.Mutex
want := 1000
e := NewEphemeralGarbageCollector(func(ni types.NodeID) {
mu.Lock()
defer mu.Unlock()
time.Sleep(time.Duration(generateRandomNumber(t, 3)) * time.Millisecond)
got = append(got, ni)
})
go e.Start()
for i := 0; i < want; i++ {
go e.Schedule(types.NodeID(i), 1*time.Second)
}
time.Sleep(10 * time.Second)
e.Close()
mu.Lock()
defer mu.Unlock()
if len(got) != want {
t.Errorf("expected %d, got %d", want, len(got))
}
}
func generateRandomNumber(t *testing.T, max int64) int64 {
t.Helper()
maxB := big.NewInt(max)
n, err := rand.Int(rand.Reader, maxB)
if err != nil {
t.Fatalf("getting random number: %s", err)
}
return n.Int64() + 1
}
func TestListEphemeralNodes(t *testing.T) {
db, err := newTestDB()
if err != nil {
t.Fatalf("creating db: %s", err)
}
user, err := db.CreateUser("test")
assert.NoError(t, err)
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
assert.NoError(t, err)
pakEph, err := db.CreatePreAuthKey(user.Name, false, true, nil, nil)
assert.NoError(t, err)
node := types.Node{
ID: 0,
MachineKey: key.NewMachine().Public(),
NodeKey: key.NewNode().Public(),
Hostname: "test",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
}
nodeEph := types.Node{
ID: 0,
MachineKey: key.NewMachine().Public(),
NodeKey: key.NewNode().Public(),
Hostname: "ephemeral",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pakEph.ID),
}
err = db.DB.Save(&node).Error
assert.NoError(t, err)
err = db.DB.Save(&nodeEph).Error
assert.NoError(t, err)
nodes, err := db.ListNodes()
assert.NoError(t, err)
ephemeralNodes, err := db.ListEphemeralNodes()
assert.NoError(t, err)
assert.Len(t, nodes, 2)
assert.Len(t, ephemeralNodes, 1)
assert.Equal(t, nodeEph.ID, ephemeralNodes[0].ID)
assert.Equal(t, nodeEph.AuthKeyID, ephemeralNodes[0].AuthKeyID)
assert.Equal(t, nodeEph.UserID, ephemeralNodes[0].UserID)
assert.Equal(t, nodeEph.Hostname, ephemeralNodes[0].Hostname)
}

View File

@@ -1,43 +0,0 @@
package db
import (
"errors"
"github.com/juanfont/headscale/hscontrol/types"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// SetPolicy sets the policy in the database.
func (hsdb *HSDatabase) SetPolicy(policy string) (*types.Policy, error) {
// Create a new policy.
p := types.Policy{
Data: policy,
}
if err := hsdb.DB.Clauses(clause.Returning{}).Create(&p).Error; err != nil {
return nil, err
}
return &p, nil
}
// GetPolicy returns the latest policy in the database.
func (hsdb *HSDatabase) GetPolicy() (*types.Policy, error) {
var p types.Policy
// Query:
// SELECT * FROM policies ORDER BY id DESC LIMIT 1;
if err := hsdb.DB.
Order("id DESC").
Limit(1).
First(&p).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, types.ErrPolicyNotFound
}
return nil, err
}
return &p, nil
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/juanfont/headscale/hscontrol/types"
"gorm.io/gorm"
"tailscale.com/types/ptr"
)
var (
@@ -84,7 +83,7 @@ func CreatePreAuthKey(
if !seenTags[tag] {
if err := tx.Save(&types.PreAuthKeyACLTag{PreAuthKeyID: key.ID, Tag: tag}).Error; err != nil {
return nil, fmt.Errorf(
"failed to create key tag in the database: %w",
"failed to ceate key tag in the database: %w",
err,
)
}
@@ -198,9 +197,10 @@ func ValidatePreAuthKey(tx *gorm.DB, k string) (*types.PreAuthKey, error) {
}
nodes := types.Nodes{}
pakID := uint(pak.ID)
if err := tx.
Preload("AuthKey").
Where(&types.Node{AuthKeyID: ptr.To(pak.ID)}).
Where(&types.Node{AuthKeyID: &pakID}).
Find(&nodes).Error; err != nil {
return nil, err
}

View File

@@ -6,7 +6,7 @@ import (
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
"gopkg.in/check.v1"
"tailscale.com/types/ptr"
"gorm.io/gorm"
)
func (*Suite) TestCreatePreAuthKey(c *check.C) {
@@ -76,12 +76,13 @@ func (*Suite) TestAlreadyUsedKey(c *check.C) {
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
c.Assert(err, check.IsNil)
pakID := uint(pak.ID)
node := types.Node{
ID: 0,
Hostname: "testest",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
}
trx := db.DB.Save(&node)
c.Assert(trx.Error, check.IsNil)
@@ -98,12 +99,13 @@ func (*Suite) TestReusableBeingUsedKey(c *check.C) {
pak, err := db.CreatePreAuthKey(user.Name, true, false, nil, nil)
c.Assert(err, check.IsNil)
pakID := uint(pak.ID)
node := types.Node{
ID: 1,
Hostname: "testest",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
}
trx := db.DB.Save(&node)
c.Assert(trx.Error, check.IsNil)
@@ -125,6 +127,77 @@ func (*Suite) TestNotReusableNotBeingUsedKey(c *check.C) {
c.Assert(key.ID, check.Equals, pak.ID)
}
func (*Suite) TestEphemeralKeyReusable(c *check.C) {
user, err := db.CreateUser("test7")
c.Assert(err, check.IsNil)
pak, err := db.CreatePreAuthKey(user.Name, true, true, nil, nil)
c.Assert(err, check.IsNil)
now := time.Now().Add(-time.Second * 30)
pakID := uint(pak.ID)
node := types.Node{
ID: 0,
Hostname: "testest",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
LastSeen: &now,
AuthKeyID: &pakID,
}
trx := db.DB.Save(&node)
c.Assert(trx.Error, check.IsNil)
_, err = db.ValidatePreAuthKey(pak.Key)
c.Assert(err, check.IsNil)
_, err = db.getNode("test7", "testest")
c.Assert(err, check.IsNil)
db.Write(func(tx *gorm.DB) error {
DeleteExpiredEphemeralNodes(tx, time.Second*20)
return nil
})
// The machine record should have been deleted
_, err = db.getNode("test7", "testest")
c.Assert(err, check.NotNil)
}
func (*Suite) TestEphemeralKeyNotReusable(c *check.C) {
user, err := db.CreateUser("test7")
c.Assert(err, check.IsNil)
pak, err := db.CreatePreAuthKey(user.Name, false, true, nil, nil)
c.Assert(err, check.IsNil)
now := time.Now().Add(-time.Second * 30)
pakId := uint(pak.ID)
node := types.Node{
ID: 0,
Hostname: "testest",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
LastSeen: &now,
AuthKeyID: &pakId,
}
db.DB.Save(&node)
_, err = db.ValidatePreAuthKey(pak.Key)
c.Assert(err, check.NotNil)
_, err = db.getNode("test7", "testest")
c.Assert(err, check.IsNil)
db.Write(func(tx *gorm.DB) error {
DeleteExpiredEphemeralNodes(tx, time.Second*20)
return nil
})
// The machine record should have been deleted
_, err = db.getNode("test7", "testest")
c.Assert(err, check.NotNil)
}
func (*Suite) TestExpirePreauthKey(c *check.C) {
user, err := db.CreateUser("test3")
c.Assert(err, check.IsNil)

View File

@@ -222,7 +222,7 @@ func DeleteRoute(
return nil, err
}
var routesToDelete types.Routes
routesToDelete := types.Routes{}
for _, r := range routes {
if r.IsExitRoute() {
routesToDelete = append(routesToDelete, r)
@@ -542,6 +542,7 @@ func failoverRoute(
isLikelyConnected *xsync.MapOf[types.NodeID, bool],
routeToReplace *types.Route,
altRoutes types.Routes,
) *failover {
if routeToReplace == nil {
return nil
@@ -622,7 +623,7 @@ func EnableAutoApprovedRoutes(
log.Trace().Interface("routes", routes).Msg("routes for autoapproving")
var approvedRoutes types.Routes
approvedRoutes := types.Routes{}
for _, advertisedRoute := range routes {
if advertisedRoute.Enabled {

View File

@@ -14,7 +14,6 @@ import (
"gopkg.in/check.v1"
"gorm.io/gorm"
"tailscale.com/tailcfg"
"tailscale.com/types/ptr"
)
var smap = func(m map[types.NodeID]bool) *xsync.MapOf[types.NodeID, bool] {
@@ -44,12 +43,13 @@ func (s *Suite) TestGetRoutes(c *check.C) {
RoutableIPs: []netip.Prefix{route},
}
pakID := uint(pak.ID)
node := types.Node{
ID: 0,
Hostname: "test_get_route_node",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
Hostinfo: &hostInfo,
}
trx := db.DB.Save(&node)
@@ -95,12 +95,13 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) {
RoutableIPs: []netip.Prefix{route, route2},
}
pakID := uint(pak.ID)
node := types.Node{
ID: 0,
Hostname: "test_enable_route_node",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
Hostinfo: &hostInfo,
}
trx := db.DB.Save(&node)
@@ -168,12 +169,13 @@ func (s *Suite) TestIsUniquePrefix(c *check.C) {
hostInfo1 := tailcfg.Hostinfo{
RoutableIPs: []netip.Prefix{route, route2},
}
pakID := uint(pak.ID)
node1 := types.Node{
ID: 1,
Hostname: "test_enable_route_node",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
Hostinfo: &hostInfo1,
}
trx := db.DB.Save(&node1)
@@ -197,7 +199,7 @@ func (s *Suite) TestIsUniquePrefix(c *check.C) {
Hostname: "test_enable_route_node",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
Hostinfo: &hostInfo2,
}
db.DB.Save(&node2)
@@ -251,12 +253,13 @@ func (s *Suite) TestDeleteRoutes(c *check.C) {
}
now := time.Now()
pakID := uint(pak.ID)
node1 := types.Node{
ID: 1,
Hostname: "test_enable_route_node",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
Hostinfo: &hostInfo1,
LastSeen: &now,
}
@@ -285,30 +288,25 @@ func (s *Suite) TestDeleteRoutes(c *check.C) {
c.Assert(len(enabledRoutes1), check.Equals, 1)
}
var (
ipp = func(s string) types.IPPrefix { return types.IPPrefix(netip.MustParsePrefix(s)) }
mkNode = func(nid types.NodeID) types.Node {
return types.Node{ID: nid}
}
)
var ipp = func(s string) types.IPPrefix { return types.IPPrefix(netip.MustParsePrefix(s)) }
var n = func(nid types.NodeID) types.Node {
return types.Node{ID: nid}
}
var np = func(nid types.NodeID) *types.Node {
no := mkNode(nid)
no := n(nid)
return &no
}
var r = func(id uint, nid types.NodeID, prefix types.IPPrefix, enabled, primary bool) types.Route {
return types.Route{
Model: gorm.Model{
ID: id,
},
Node: mkNode(nid),
Node: n(nid),
Prefix: prefix,
Enabled: enabled,
IsPrimary: primary,
}
}
var rp = func(id uint, nid types.NodeID, prefix types.IPPrefix, enabled, primary bool) *types.Route {
ro := r(id, nid, prefix, enabled, primary)
return &ro

View File

@@ -35,18 +35,10 @@ func (s *Suite) ResetDB(c *check.C) {
// os.RemoveAll(tmpDir)
// }
var err error
db, err = newTestDB()
if err != nil {
c.Fatal(err)
}
}
func newTestDB() (*HSDatabase, error) {
var err error
tmpDir, err = os.MkdirTemp("", "headscale-db-test-*")
if err != nil {
return nil, err
c.Fatal(err)
}
log.Printf("database path: %s", tmpDir+"/headscale_test.db")
@@ -61,8 +53,6 @@ func newTestDB() (*HSDatabase, error) {
"",
)
if err != nil {
return nil, err
c.Fatal(err)
}
return db, nil
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/juanfont/headscale/hscontrol/util"
"gopkg.in/check.v1"
"gorm.io/gorm"
"tailscale.com/types/ptr"
)
func (s *Suite) TestCreateAndDestroyUser(c *check.C) {
@@ -47,12 +46,13 @@ func (s *Suite) TestDestroyUserErrors(c *check.C) {
pak, err = db.CreatePreAuthKey(user.Name, false, false, nil, nil)
c.Assert(err, check.IsNil)
pakID := uint(pak.ID)
node := types.Node{
ID: 0,
Hostname: "testnode",
UserID: user.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
}
trx := db.DB.Save(&node)
c.Assert(trx.Error, check.IsNil)
@@ -100,12 +100,13 @@ func (s *Suite) TestSetMachineUser(c *check.C) {
pak, err := db.CreatePreAuthKey(oldUser.Name, false, false, nil, nil)
c.Assert(err, check.IsNil)
pakID := uint(pak.ID)
node := types.Node{
ID: 0,
Hostname: "testnode",
UserID: oldUser.ID,
RegisterMethod: util.RegisterMethodAuthKey,
AuthKeyID: ptr.To(pak.ID),
AuthKeyID: &pakID,
}
trx := db.DB.Save(&node)
c.Assert(trx.Error, check.IsNil)

View File

@@ -81,7 +81,7 @@ func mergeDERPMaps(derpMaps []*tailcfg.DERPMap) *tailcfg.DERPMap {
}
func GetDERPMap(cfg types.DERPConfig) *tailcfg.DERPMap {
var derpMaps []*tailcfg.DERPMap
derpMaps := make([]*tailcfg.DERPMap, 0)
for _, path := range cfg.Paths {
log.Debug().
@@ -125,5 +125,10 @@ func GetDERPMap(cfg types.DERPConfig) *tailcfg.DERPMap {
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
}

View File

@@ -204,7 +204,7 @@ func DERPProbeHandler(
}
}
// DERPBootstrapDNSHandler implements the /bootstrap-dns endpoint
// DERPBootstrapDNSHandler implements the /bootsrap-dns endpoint
// Described in https://github.com/tailscale/tailscale/issues/1405,
// this endpoint provides a way to help a client when it fails to start up
// because its DNS are broken.

View File

@@ -4,8 +4,6 @@ package hscontrol
import (
"context"
"errors"
"io"
"os"
"sort"
"strings"
"time"
@@ -13,14 +11,12 @@ import (
"github.com/rs/zerolog/log"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
"gorm.io/gorm"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/db"
"github.com/juanfont/headscale/hscontrol/policy"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
)
@@ -675,77 +671,6 @@ func (api headscaleV1APIServer) DeleteApiKey(
return &v1.DeleteApiKeyResponse{}, nil
}
func (api headscaleV1APIServer) GetPolicy(
_ context.Context,
_ *v1.GetPolicyRequest,
) (*v1.GetPolicyResponse, error) {
switch api.h.cfg.Policy.Mode {
case types.PolicyModeDB:
p, err := api.h.db.GetPolicy()
if err != nil {
return nil, err
}
return &v1.GetPolicyResponse{
Policy: p.Data,
UpdatedAt: timestamppb.New(p.UpdatedAt),
}, nil
case types.PolicyModeFile:
// Read the file and return the contents as-is.
absPath := util.AbsolutePathFromConfigPath(api.h.cfg.Policy.Path)
f, err := os.Open(absPath)
if err != nil {
return nil, err
}
defer f.Close()
b, err := io.ReadAll(f)
if err != nil {
return nil, err
}
return &v1.GetPolicyResponse{Policy: string(b)}, nil
}
return nil, nil
}
func (api headscaleV1APIServer) SetPolicy(
_ context.Context,
request *v1.SetPolicyRequest,
) (*v1.SetPolicyResponse, error) {
if api.h.cfg.Policy.Mode != types.PolicyModeDB {
return nil, types.ErrPolicyUpdateIsDisabled
}
p := request.GetPolicy()
valid, err := policy.LoadACLPolicyFromBytes([]byte(p))
if err != nil {
return nil, err
}
updated, err := api.h.db.SetPolicy(p)
if err != nil {
return nil, err
}
api.h.ACLPolicy = valid
ctx := types.NotifyCtx(context.Background(), "acl-update", "na")
api.h.nodeNotifier.NotifyAll(ctx, types.StateUpdate{
Type: types.StateFullUpdate,
})
response := &v1.SetPolicyResponse{
Policy: updated.Data,
UpdatedAt: timestamppb.New(updated.UpdatedAt),
}
return response, nil
}
// The following service calls are for testing and debugging
func (api headscaleV1APIServer) DebugCreateNode(
ctx context.Context,

View File

@@ -143,18 +143,6 @@ var registerWebAPITemplate = template.Must(
<html>
<head>
<title>Registration - Headscale</title>
<meta name=viewport content="width=device-width, initial-scale=1">
<style>
body {
font-family: sans;
}
code {
display: block;
padding: 20px;
border: 1px solid #bbb;
background-color: #eee;
}
</style>
</head>
<body>
<h1>headscale</h1>
@@ -162,7 +150,7 @@ var registerWebAPITemplate = template.Must(
<p>
Run the command below in the headscale server to add this machine to your network:
</p>
<code>headscale nodes register --user USERNAME --key {{.Key}}</code>
<pre><code>headscale nodes register --user USERNAME --key {{.Key}}</code></pre>
</body>
</html>
`))

View File

@@ -94,6 +94,7 @@ func (m *Mapper) String() string {
func generateUserProfiles(
node *types.Node,
peers types.Nodes,
baseDomain string,
) []tailcfg.UserProfile {
userMap := make(map[string]types.User)
userMap[node.User.Name] = node.User
@@ -101,51 +102,57 @@ func generateUserProfiles(
userMap[peer.User.Name] = peer.User // not worth checking if already is there
}
var profiles []tailcfg.UserProfile
profiles := []tailcfg.UserProfile{}
for _, user := range userMap {
displayName := user.Name
if baseDomain != "" {
displayName = fmt.Sprintf("%s@%s", user.Name, baseDomain)
}
profiles = append(profiles,
user.TailscaleUserProfile())
tailcfg.UserProfile{
ID: tailcfg.UserID(user.ID),
LoginName: user.Name,
DisplayName: displayName,
})
}
return profiles
}
func generateDNSConfig(
cfg *types.Config,
base *tailcfg.DNSConfig,
baseDomain string,
node *types.Node,
peers types.Nodes,
) *tailcfg.DNSConfig {
if cfg.DNSConfig == nil {
return nil
}
dnsConfig := cfg.DNSConfig.Clone()
dnsConfig := base.Clone()
// if MagicDNS is enabled
if dnsConfig.Proxied {
if cfg.DNSUserNameInMagicDNS {
// Only inject the Search Domain of the current user
// shared nodes should use their full FQDN
dnsConfig.Domains = append(
dnsConfig.Domains,
fmt.Sprintf(
"%s.%s",
node.User.Name,
baseDomain,
),
)
if base != nil && base.Proxied {
// Only inject the Search Domain of the current user
// shared nodes should use their full FQDN
dnsConfig.Domains = append(
dnsConfig.Domains,
fmt.Sprintf(
"%s.%s",
node.User.Name,
baseDomain,
),
)
userSet := mapset.NewSet[types.User]()
userSet.Add(node.User)
for _, p := range peers {
userSet.Add(p.User)
}
for _, user := range userSet.ToSlice() {
dnsRoute := fmt.Sprintf("%v.%v", user.Name, baseDomain)
dnsConfig.Routes[dnsRoute] = nil
}
userSet := mapset.NewSet[types.User]()
userSet.Add(node.User)
for _, p := range peers {
userSet.Add(p.User)
}
for _, user := range userSet.ToSlice() {
dnsRoute := fmt.Sprintf("%v.%v", user.Name, baseDomain)
dnsConfig.Routes[dnsRoute] = nil
}
} else {
dnsConfig = base
}
addNextDNSMetadata(dnsConfig.Resolvers, node)
@@ -541,6 +548,7 @@ func appendPeerChanges(
changed types.Nodes,
cfg *types.Config,
) error {
packetFilter, err := pol.CompileFilterRules(append(peers, node))
if err != nil {
return err
@@ -557,10 +565,10 @@ func appendPeerChanges(
changed = policy.FilterNodesByACL(node, changed, packetFilter)
}
profiles := generateUserProfiles(node, changed)
profiles := generateUserProfiles(node, changed, cfg.BaseDomain)
dnsConfig := generateDNSConfig(
cfg,
cfg.DNSConfig,
cfg.BaseDomain,
node,
peers,
@@ -582,30 +590,9 @@ func appendPeerChanges(
resp.PeersChanged = tailPeers
}
resp.DNSConfig = dnsConfig
resp.PacketFilter = policy.ReduceFilterRules(node, packetFilter)
resp.UserProfiles = profiles
resp.SSHPolicy = sshPolicy
// 81: 2023-11-17: MapResponse.PacketFilters (incremental packet filter updates)
if capVer >= 81 {
// Currently, we do not send incremental package filters, however using the
// new PacketFilters field and "base" allows us to send a full update when we
// have to send an empty list, avoiding the hack in the else block.
resp.PacketFilters = map[string][]tailcfg.FilterRule{
"base": policy.ReduceFilterRules(node, packetFilter),
}
} else {
// This is a hack to avoid sending an empty list of packet filters.
// Since tailcfg.PacketFilter has omitempty, any empty PacketFilter will
// be omitted, causing the client to consider it unchange, keeping the
// previous packet filter. Worst case, this can cause a node that previously
// has access to a node to _not_ loose access if an empty (allow none) is sent.
reduced := policy.ReduceFilterRules(node, packetFilter)
if len(reduced) > 0 {
resp.PacketFilter = reduced
} else {
resp.PacketFilter = packetFilter
}
}
return nil
}

View File

@@ -43,6 +43,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
types.Nodes{
nodeInShared2, nodeInShared3, node2InShared1,
},
"",
)
c.Assert(len(userProfiles), check.Equals, 3)
@@ -126,10 +127,7 @@ func TestDNSConfigMapResponse(t *testing.T) {
}
got := generateDNSConfig(
&types.Config{
DNSConfig: &dnsConfigOrig,
DNSUserNameInMagicDNS: true,
},
&dnsConfigOrig,
baseDomain,
nodeInShared1,
peersOfNodeInShared1,
@@ -189,9 +187,9 @@ func Test_fullMapResponse(t *testing.T) {
UserID: 0,
User: types.User{Name: "mini"},
ForcedTags: []string{},
AuthKey: &types.PreAuthKey{},
LastSeen: &lastSeen,
Expiry: &expire,
AuthKey: &types.PreAuthKey{},
LastSeen: &lastSeen,
Expiry: &expire,
Hostinfo: &tailcfg.Hostinfo{},
Routes: []types.Route{
{

View File

@@ -36,7 +36,8 @@ func tailNodes(
return tNodes, nil
}
// tailNode converts a Node into a Tailscale Node.
// tailNode converts a Node into a Tailscale Node. includeRoutes is false for shared nodes
// as per the expected behaviour in the official SaaS.
func tailNode(
node *types.Node,
capVer tailcfg.CapabilityVersion,
@@ -76,7 +77,7 @@ func tailNode(
keyExpiry = time.Time{}
}
hostname, err := node.GetFQDN(cfg, cfg.BaseDomain)
hostname, err := node.GetFQDN(cfg.DNSConfig, cfg.BaseDomain)
if err != nil {
return nil, fmt.Errorf("tailNode, failed to create FQDN: %s", err)
}

View File

@@ -55,14 +55,12 @@ func TestTailNode(t *testing.T) {
{
name: "empty-node",
node: &types.Node{
GivenName: "empty",
Hostinfo: &tailcfg.Hostinfo{},
Hostinfo: &tailcfg.Hostinfo{},
},
pol: &policy.ACLPolicy{},
dnsConfig: &tailcfg.DNSConfig{},
baseDomain: "",
want: &tailcfg.Node{
Name: "empty",
StableID: "0",
Addresses: []netip.Prefix{},
AllowedIPs: []netip.Prefix{},

View File

@@ -7,23 +7,8 @@ import (
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"tailscale.com/envknob"
)
var debugHighCardinalityMetrics = envknob.Bool("HEADSCALE_DEBUG_HIGH_CARDINALITY_METRICS")
var mapResponseLastSentSeconds *prometheus.GaugeVec
func init() {
if debugHighCardinalityMetrics {
mapResponseLastSentSeconds = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: prometheusNamespace,
Name: "mapresponse_last_sent_seconds",
Help: "last sent metric to node.id",
}, []string{"type", "id"})
}
}
const prometheusNamespace = "headscale"
var (
@@ -40,7 +25,7 @@ var (
mapResponseWriteUpdatesInStream = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: prometheusNamespace,
Name: "mapresponse_write_updates_in_stream_total",
Help: "total count of writes that occurred in a stream session, pre-68 nodes",
Help: "total count of writes that occured in a stream session, pre-68 nodes",
}, []string{"status"})
mapResponseEndpointUpdates = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: prometheusNamespace,
@@ -52,16 +37,16 @@ var (
Name: "mapresponse_readonly_requests_total",
Help: "total count of readonly requests received",
}, []string{"status"})
mapResponseEnded = promauto.NewCounterVec(prometheus.CounterOpts{
mapResponseSessions = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: prometheusNamespace,
Name: "mapresponse_ended_total",
Help: "total count of new mapsessions ended",
Name: "mapresponse_current_sessions_total",
Help: "total count open map response sessions",
})
mapResponseRejected = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: prometheusNamespace,
Name: "mapresponse_rejected_new_sessions_total",
Help: "total count of new mapsessions rejected",
}, []string{"reason"})
mapResponseClosed = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: prometheusNamespace,
Name: "mapresponse_closed_total",
Help: "total count of calls to mapresponse close",
}, []string{"return"})
httpDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
Namespace: prometheusNamespace,
Name: "http_duration_seconds",

View File

@@ -166,7 +166,7 @@ func (ns *noiseServer) earlyNoise(protocolVersion int, writer io.Writer) error {
}
const (
MinimumCapVersion tailcfg.CapabilityVersion = 61
MinimumCapVersion tailcfg.CapabilityVersion = 58
)
// NoisePollNetMapHandler takes care of /machine/:id/map using the Noise protocol
@@ -231,12 +231,62 @@ func (ns *noiseServer) NoisePollNetMapHandler(
return
}
sess := ns.headscale.newMapSession(req.Context(), mapRequest, writer, node)
sess.tracef("a node sending a MapRequest with Noise protocol")
if !sess.isStreaming() {
sess.serve()
} else {
sess.serveLongPoll()
// If a streaming mapSession exists for this node, close it
// and start a new one.
if sess.isStreaming() {
sess.tracef("aquiring lock to check stream")
ns.headscale.mapSessionMu.Lock()
if _, ok := ns.headscale.mapSessions[node.ID]; ok {
// NOTE/TODO(kradalby): From how I understand the protocol, when
// a client connects with stream=true, and already has a streaming
// connection open, the correct way is to close the current channel
// and replace it. However, I cannot manage to get that working with
// some sort of lock/block happening on the cancelCh in the streaming
// session.
// Not closing the channel and replacing it puts us in a weird state
// which keeps a ghost stream open, receiving keep alives, but no updates.
//
// Typically a new connection is opened when one exists as a client which
// is already authenticated reconnects (e.g. down, then up). The client will
// start auth and streaming at the same time, and then cancel the streaming
// when the auth has finished successfully, opening a new connection.
//
// As a work-around to not replacing, abusing the clients "resilience"
// by reject the new connection which will cause the client to immediately
// reconnect and "fix" the issue, as the other connection typically has been
// closed, meaning there is nothing to replace.
//
// sess.infof("node has an open stream(%p), replacing with %p", oldSession, sess)
// oldSession.close()
defer ns.headscale.mapSessionMu.Unlock()
sess.infof("node has an open stream(%p), rejecting new stream", sess)
mapResponseRejected.WithLabelValues("exists").Inc()
return
}
ns.headscale.mapSessions[node.ID] = sess
mapResponseSessions.Inc()
ns.headscale.mapSessionMu.Unlock()
sess.tracef("releasing lock to check stream")
}
sess.serve()
if sess.isStreaming() {
sess.tracef("aquiring lock to remove stream")
ns.headscale.mapSessionMu.Lock()
defer ns.headscale.mapSessionMu.Unlock()
delete(ns.headscale.mapSessions, node.ID)
mapResponseSessions.Dec()
sess.tracef("releasing lock to remove stream")
}
}

View File

@@ -3,43 +3,22 @@ package notifier
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"tailscale.com/envknob"
)
const prometheusNamespace = "headscale"
var debugHighCardinalityMetrics = envknob.Bool("HEADSCALE_DEBUG_HIGH_CARDINALITY_METRICS")
var notifierUpdateSent *prometheus.CounterVec
func init() {
if debugHighCardinalityMetrics {
notifierUpdateSent = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: prometheusNamespace,
Name: "notifier_update_sent_total",
Help: "total count of update sent on nodes channel",
}, []string{"status", "type", "trigger", "id"})
} else {
notifierUpdateSent = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: prometheusNamespace,
Name: "notifier_update_sent_total",
Help: "total count of update sent on nodes channel",
}, []string{"status", "type", "trigger"})
}
}
var (
notifierWaitersForLock = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: prometheusNamespace,
Name: "notifier_waiters_for_lock",
Help: "gauge of waiters for the notifier lock",
}, []string{"type", "action"})
notifierWaitForLock = promauto.NewHistogramVec(prometheus.HistogramOpts{
Namespace: prometheusNamespace,
Name: "notifier_wait_for_lock_seconds",
Help: "histogram of time spent waiting for the notifier lock",
Buckets: []float64{0.001, 0.01, 0.1, 0.3, 0.5, 1, 3, 5, 10},
}, []string{"action"})
notifierUpdateSent = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: prometheusNamespace,
Name: "notifier_update_sent_total",
Help: "total count of update sent on nodes channel",
}, []string{"status", "type", "trigger"})
notifierUpdateReceived = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: prometheusNamespace,
Name: "notifier_update_received_total",
@@ -50,19 +29,4 @@ var (
Name: "notifier_open_channels_total",
Help: "total count open channels in notifier",
})
notifierBatcherWaitersForLock = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: prometheusNamespace,
Name: "notifier_batcher_waiters_for_lock",
Help: "gauge of waiters for the notifier batcher lock",
}, []string{"type", "action"})
notifierBatcherChanges = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: prometheusNamespace,
Name: "notifier_batcher_changes_pending",
Help: "gauge of full changes pending in the notifier batcher",
}, []string{})
notifierBatcherPatches = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: prometheusNamespace,
Name: "notifier_batcher_patches_pending",
Help: "gauge of patches pending in the notifier batcher",
}, []string{})
)

View File

@@ -11,42 +11,25 @@ import (
"github.com/juanfont/headscale/hscontrol/types"
"github.com/puzpuzpuz/xsync/v3"
"github.com/rs/zerolog/log"
"github.com/sasha-s/go-deadlock"
"tailscale.com/envknob"
"tailscale.com/tailcfg"
"tailscale.com/util/set"
)
var (
debugDeadlock = envknob.Bool("HEADSCALE_DEBUG_DEADLOCK")
debugDeadlockTimeout = envknob.RegisterDuration("HEADSCALE_DEBUG_DEADLOCK_TIMEOUT")
)
func init() {
deadlock.Opts.Disable = !debugDeadlock
if debugDeadlock {
deadlock.Opts.DeadlockTimeout = debugDeadlockTimeout()
deadlock.Opts.PrintAllCurrentGoroutines = true
}
}
type Notifier struct {
l deadlock.Mutex
l sync.RWMutex
nodes map[types.NodeID]chan<- types.StateUpdate
connected *xsync.MapOf[types.NodeID, bool]
b *batcher
cfg *types.Config
}
func NewNotifier(cfg *types.Config) *Notifier {
n := &Notifier{
nodes: make(map[types.NodeID]chan<- types.StateUpdate),
connected: xsync.NewMapOf[types.NodeID, bool](),
cfg: cfg,
}
b := newBatcher(cfg.Tuning.BatchChangeDelay, n)
n.b = b
// TODO(kradalby): clean this up
go b.doWork()
return n
}
@@ -56,75 +39,59 @@ func (n *Notifier) Close() {
n.b.close()
}
func (n *Notifier) tracef(nID types.NodeID, msg string, args ...any) {
log.Trace().
Uint64("node.id", nID.Uint64()).
Int("open_chans", len(n.nodes)).Msgf(msg, args...)
}
func (n *Notifier) AddNode(nodeID types.NodeID, c chan<- types.StateUpdate) {
log.Trace().Caller().Uint64("node.id", nodeID.Uint64()).Msg("acquiring lock to add node")
defer log.Trace().
Caller().
Uint64("node.id", nodeID.Uint64()).
Msg("releasing lock to add node")
start := time.Now()
notifierWaitersForLock.WithLabelValues("lock", "add").Inc()
n.l.Lock()
defer n.l.Unlock()
notifierWaitersForLock.WithLabelValues("lock", "add").Dec()
notifierWaitForLock.WithLabelValues("add").Observe(time.Since(start).Seconds())
// If a channel exists, it means the node has opened a new
// connection. Close the old channel and replace it.
if curr, ok := n.nodes[nodeID]; ok {
n.tracef(nodeID, "channel present, closing and replacing")
close(curr)
}
n.nodes[nodeID] = c
n.connected.Store(nodeID, true)
n.tracef(nodeID, "added new channel")
log.Trace().
Uint64("node.id", nodeID.Uint64()).
Int("open_chans", len(n.nodes)).
Msg("Added new channel")
notifierNodeUpdateChans.Inc()
}
// RemoveNode removes a node and a given channel from the notifier.
// It checks that the channel is the same as currently being updated
// and ignores the removal if it is not.
// RemoveNode reports if the node/chan was removed.
func (n *Notifier) RemoveNode(nodeID types.NodeID, c chan<- types.StateUpdate) bool {
func (n *Notifier) RemoveNode(nodeID types.NodeID) {
log.Trace().Caller().Uint64("node.id", nodeID.Uint64()).Msg("acquiring lock to remove node")
defer log.Trace().
Caller().
Uint64("node.id", nodeID.Uint64()).
Msg("releasing lock to remove node")
start := time.Now()
notifierWaitersForLock.WithLabelValues("lock", "remove").Inc()
n.l.Lock()
defer n.l.Unlock()
notifierWaitersForLock.WithLabelValues("lock", "remove").Dec()
notifierWaitForLock.WithLabelValues("remove").Observe(time.Since(start).Seconds())
if len(n.nodes) == 0 {
return true
}
// If the channel exist, but it does not belong
// to the caller, ignore.
if curr, ok := n.nodes[nodeID]; ok {
if curr != c {
n.tracef(nodeID, "channel has been replaced, not removing")
return false
}
return
}
delete(n.nodes, nodeID)
n.connected.Store(nodeID, false)
n.tracef(nodeID, "removed channel")
log.Trace().
Uint64("node.id", nodeID.Uint64()).
Int("open_chans", len(n.nodes)).
Msg("Removed channel")
notifierNodeUpdateChans.Dec()
return true
}
// IsConnected reports if a node is connected to headscale and has a
// poll session open.
func (n *Notifier) IsConnected(nodeID types.NodeID) bool {
notifierWaitersForLock.WithLabelValues("lock", "conncheck").Inc()
n.l.Lock()
defer n.l.Unlock()
notifierWaitersForLock.WithLabelValues("lock", "conncheck").Dec()
n.l.RLock()
defer n.l.RUnlock()
if val, ok := n.connected.Load(nodeID); ok {
return val
@@ -163,11 +130,15 @@ func (n *Notifier) NotifyByNodeID(
update types.StateUpdate,
nodeID types.NodeID,
) {
log.Trace().Caller().Str("type", update.Type.String()).Msg("acquiring lock to notify")
defer log.Trace().
Caller().
Str("type", update.Type.String()).
Msg("releasing lock, finished notifying")
start := time.Now()
notifierWaitersForLock.WithLabelValues("lock", "notify").Inc()
n.l.Lock()
defer n.l.Unlock()
notifierWaitersForLock.WithLabelValues("lock", "notify").Dec()
n.l.RLock()
defer n.l.RUnlock()
notifierWaitForLock.WithLabelValues("notify").Observe(time.Since(start).Seconds())
if c, ok := n.nodes[nodeID]; ok {
@@ -179,94 +150,50 @@ func (n *Notifier) NotifyByNodeID(
Any("origin", types.NotifyOriginKey.Value(ctx)).
Any("origin-hostname", types.NotifyHostnameKey.Value(ctx)).
Msgf("update not sent, context cancelled")
if debugHighCardinalityMetrics {
notifierUpdateSent.WithLabelValues("cancelled", update.Type.String(), types.NotifyOriginKey.Value(ctx), nodeID.String()).Inc()
} else {
notifierUpdateSent.WithLabelValues("cancelled", update.Type.String(), types.NotifyOriginKey.Value(ctx)).Inc()
}
notifierUpdateSent.WithLabelValues("cancelled", update.Type.String(), types.NotifyOriginKey.Value(ctx)).Inc()
return
case c <- update:
n.tracef(nodeID, "update successfully sent on chan, origin: %s, origin-hostname: %s", ctx.Value("origin"), ctx.Value("hostname"))
if debugHighCardinalityMetrics {
notifierUpdateSent.WithLabelValues("ok", update.Type.String(), types.NotifyOriginKey.Value(ctx), nodeID.String()).Inc()
} else {
notifierUpdateSent.WithLabelValues("ok", update.Type.String(), types.NotifyOriginKey.Value(ctx)).Inc()
}
log.Trace().
Uint64("node.id", nodeID.Uint64()).
Any("origin", ctx.Value("origin")).
Any("origin-hostname", ctx.Value("hostname")).
Msgf("update successfully sent on chan")
notifierUpdateSent.WithLabelValues("ok", update.Type.String(), types.NotifyOriginKey.Value(ctx)).Inc()
}
}
}
func (n *Notifier) sendAll(update types.StateUpdate) {
start := time.Now()
notifierWaitersForLock.WithLabelValues("lock", "send-all").Inc()
n.l.Lock()
defer n.l.Unlock()
notifierWaitersForLock.WithLabelValues("lock", "send-all").Dec()
n.l.RLock()
defer n.l.RUnlock()
notifierWaitForLock.WithLabelValues("send-all").Observe(time.Since(start).Seconds())
for id, c := range n.nodes {
// Whenever an update is sent to all nodes, there is a chance that the node
// has disconnected and the goroutine that was supposed to consume the update
// has shut down the channel and is waiting for the lock held here in RemoveNode.
// This means that there is potential for a deadlock which would stop all updates
// going out to clients. This timeout prevents that from happening by moving on to the
// next node if the context is cancelled. Afther sendAll releases the lock, the add/remove
// call will succeed and the update will go to the correct nodes on the next call.
ctx, cancel := context.WithTimeout(context.Background(), n.cfg.Tuning.NotifierSendTimeout)
defer cancel()
select {
case <-ctx.Done():
log.Error().
Err(ctx.Err()).
Uint64("node.id", id.Uint64()).
Msgf("update not sent, context cancelled")
if debugHighCardinalityMetrics {
notifierUpdateSent.WithLabelValues("cancelled", update.Type.String(), "send-all", id.String()).Inc()
} else {
notifierUpdateSent.WithLabelValues("cancelled", update.Type.String(), "send-all").Inc()
}
return
case c <- update:
if debugHighCardinalityMetrics {
notifierUpdateSent.WithLabelValues("ok", update.Type.String(), "send-all", id.String()).Inc()
} else {
notifierUpdateSent.WithLabelValues("ok", update.Type.String(), "send-all").Inc()
}
}
for _, c := range n.nodes {
c <- update
notifierUpdateSent.WithLabelValues("ok", update.Type.String(), "send-all").Inc()
}
}
func (n *Notifier) String() string {
notifierWaitersForLock.WithLabelValues("lock", "string").Inc()
n.l.Lock()
defer n.l.Unlock()
notifierWaitersForLock.WithLabelValues("lock", "string").Dec()
n.l.RLock()
defer n.l.RUnlock()
var b strings.Builder
fmt.Fprintf(&b, "chans (%d):\n", len(n.nodes))
b.WriteString("chans:\n")
var keys []types.NodeID
n.connected.Range(func(key types.NodeID, value bool) bool {
keys = append(keys, key)
return true
})
sort.Slice(keys, func(i, j int) bool {
return keys[i] < keys[j]
})
for _, key := range keys {
fmt.Fprintf(&b, "\t%d: %p\n", key, n.nodes[key])
for k, v := range n.nodes {
fmt.Fprintf(&b, "\t%d: %p\n", k, v)
}
b.WriteString("\n")
fmt.Fprintf(&b, "connected (%d):\n", len(n.nodes))
b.WriteString("connected:\n")
for _, key := range keys {
val, _ := n.connected.Load(key)
fmt.Fprintf(&b, "\t%d: %t\n", key, val)
}
n.connected.Range(func(k types.NodeID, v bool) bool {
fmt.Fprintf(&b, "\t%d: %t\n", k, v)
return true
})
return b.String()
}
@@ -293,6 +220,7 @@ func newBatcher(batchTime time.Duration, n *Notifier) *batcher {
patches: make(map[types.NodeID]tailcfg.PeerChange),
n: n,
}
}
func (b *batcher) close() {
@@ -302,16 +230,13 @@ func (b *batcher) close() {
// addOrPassthrough adds the update to the batcher, if it is not a
// type that is currently batched, it will be sent immediately.
func (b *batcher) addOrPassthrough(update types.StateUpdate) {
notifierBatcherWaitersForLock.WithLabelValues("lock", "add").Inc()
b.mu.Lock()
defer b.mu.Unlock()
notifierBatcherWaitersForLock.WithLabelValues("lock", "add").Dec()
switch update.Type {
case types.StatePeerChanged:
b.changedNodeIDs.Add(update.ChangeNodes...)
b.nodesChanged = true
notifierBatcherChanges.WithLabelValues().Set(float64(b.changedNodeIDs.Len()))
case types.StatePeerChangedPatch:
for _, newPatch := range update.ChangePatches {
@@ -323,7 +248,6 @@ func (b *batcher) addOrPassthrough(update types.StateUpdate) {
}
}
b.patchesChanged = true
notifierBatcherPatches.WithLabelValues().Set(float64(len(b.patches)))
default:
b.n.sendAll(update)
@@ -333,10 +257,8 @@ func (b *batcher) addOrPassthrough(update types.StateUpdate) {
// flush sends all the accumulated patches to all
// nodes in the notifier.
func (b *batcher) flush() {
notifierBatcherWaitersForLock.WithLabelValues("lock", "flush").Inc()
b.mu.Lock()
defer b.mu.Unlock()
notifierBatcherWaitersForLock.WithLabelValues("lock", "flush").Dec()
if b.nodesChanged || b.patchesChanged {
var patches []*tailcfg.PeerChange
@@ -374,10 +296,8 @@ func (b *batcher) flush() {
}
b.changedNodeIDs = set.Slice[types.NodeID]{}
notifierBatcherChanges.WithLabelValues().Set(0)
b.nodesChanged = false
b.patches = make(map[types.NodeID]tailcfg.PeerChange, len(b.patches))
notifierBatcherPatches.WithLabelValues().Set(0)
b.patchesChanged = false
}
}
@@ -394,7 +314,7 @@ func (b *batcher) doWork() {
}
// overwritePatch takes the current patch and a newer patch
// and override any field that has changed.
// and override any field that has changed
func overwritePatch(currPatch, newPatch *tailcfg.PeerChange) {
if newPatch.DERPRegion != 0 {
currPatch.DERPRegion = newPatch.DERPRegion

View File

@@ -3,7 +3,6 @@ package notifier
import (
"context"
"net/netip"
"sort"
"testing"
"time"
@@ -222,18 +221,13 @@ func TestBatcher(t *testing.T) {
// We will call flush manually for the tests,
// so do not run the worker.
BatchChangeDelay: time.Hour,
// Since we do not load the config, we wont get the
// default, so set it manually so we dont time out
// and have flakes.
NotifierSendTimeout: time.Second,
},
})
ch := make(chan types.StateUpdate, 30)
defer close(ch)
n.AddNode(1, ch)
defer n.RemoveNode(1, ch)
defer n.RemoveNode(1)
for _, u := range tt.updates {
n.NotifyAll(context.Background(), u)
@@ -247,16 +241,6 @@ func TestBatcher(t *testing.T) {
got = append(got, out)
}
// Make the inner order stable for comparison.
for _, u := range got {
sort.Slice(u.ChangeNodes, func(i, j int) bool {
return u.ChangeNodes[i] < u.ChangeNodes[j]
})
sort.Slice(u.ChangePatches, func(i, j int) bool {
return u.ChangePatches[i].NodeID < u.ChangePatches[j].NodeID
})
}
if diff := cmp.Diff(tt.want, got, util.Comparers...); diff != "" {
t.Errorf("batcher() unexpected result (-want +got):\n%s", diff)
}

View File

@@ -10,7 +10,6 @@ import (
"fmt"
"html/template"
"net/http"
"slices"
"strings"
"time"
@@ -57,6 +56,7 @@ func (h *Headscale) initOIDC() error {
// grab oidc config if it hasn't been already
if h.oauth2Config == nil {
h.oidcProvider, err = oidc.NewProvider(context.Background(), h.cfg.OIDC.Issuer)
if err != nil {
return fmt.Errorf("creating OIDC provider from issuer config: %w", err)
}
@@ -365,7 +365,7 @@ func validateOIDCAllowedDomains(
) error {
if len(allowedDomains) > 0 {
if at := strings.LastIndex(claims.Email, "@"); at < 0 ||
!slices.Contains(allowedDomains, claims.Email[at+1:]) {
!util.IsStringInSlice(allowedDomains, claims.Email[at+1:]) {
log.Trace().Msg("authenticated principal does not match any allowed domain")
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
@@ -393,7 +393,7 @@ func validateOIDCAllowedGroups(
) error {
if len(allowedGroups) > 0 {
for _, group := range allowedGroups {
if slices.Contains(claims.Groups, group) {
if util.IsStringInSlice(claims.Groups, group) {
return nil
}
}
@@ -420,7 +420,7 @@ func validateOIDCAllowedUsers(
claims *IDTokenClaims,
) error {
if len(allowedUsers) > 0 &&
!slices.Contains(allowedUsers, claims.Email) {
!util.IsStringInSlice(allowedUsers, claims.Email) {
log.Trace().Msg("authenticated principal does not match any allowed user")
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
writer.WriteHeader(http.StatusBadRequest)

View File

@@ -7,6 +7,7 @@ import (
"io"
"net/netip"
"os"
"path/filepath"
"strconv"
"strings"
"time"
@@ -16,6 +17,7 @@ import (
"github.com/rs/zerolog/log"
"github.com/tailscale/hujson"
"go4.org/netipx"
"gopkg.in/yaml.v3"
"tailscale.com/tailcfg"
)
@@ -106,22 +108,35 @@ func LoadACLPolicyFromPath(path string) (*ACLPolicy, error) {
Bytes("file", policyBytes).
Msg("Loading ACLs")
return LoadACLPolicyFromBytes(policyBytes)
}
func LoadACLPolicyFromBytes(acl []byte) (*ACLPolicy, error) {
var policy ACLPolicy
ast, err := hujson.Parse(acl)
if err != nil {
return nil, fmt.Errorf("parsing hujson, err: %w", err)
switch filepath.Ext(path) {
case ".yml", ".yaml":
return LoadACLPolicyFromBytes(policyBytes, "yaml")
}
ast.Standardize()
acl = ast.Pack()
return LoadACLPolicyFromBytes(policyBytes, "hujson")
}
if err := json.Unmarshal(acl, &policy); err != nil {
return nil, fmt.Errorf("unmarshalling policy, err: %w", err)
func LoadACLPolicyFromBytes(acl []byte, format string) (*ACLPolicy, error) {
var policy ACLPolicy
switch format {
case "yaml":
err := yaml.Unmarshal(acl, &policy)
if err != nil {
return nil, err
}
default:
ast, err := hujson.Parse(acl)
if err != nil {
return nil, err
}
ast.Standardize()
acl = ast.Pack()
err = json.Unmarshal(acl, &policy)
if err != nil {
return nil, err
}
}
if policy.IsZero() {
@@ -165,14 +180,14 @@ func (pol *ACLPolicy) CompileFilterRules(
return tailcfg.FilterAllowAll, nil
}
var rules []tailcfg.FilterRule
rules := []tailcfg.FilterRule{}
for index, acl := range pol.ACLs {
if acl.Action != "accept" {
return nil, ErrInvalidAction
}
var srcIPs []string
srcIPs := []string{}
for srcIndex, src := range acl.Sources {
srcs, err := pol.expandSource(src, nodes)
if err != nil {
@@ -206,7 +221,7 @@ func (pol *ACLPolicy) CompileFilterRules(
return nil, err
}
var dests []tailcfg.NetPortRange
dests := []tailcfg.NetPortRange{}
for _, dest := range expanded.Prefixes() {
for _, port := range *ports {
pr := tailcfg.NetPortRange{
@@ -236,7 +251,8 @@ func ReduceFilterRules(node *types.Node, rules []tailcfg.FilterRule) []tailcfg.F
for _, rule := range rules {
// record if the rule is actually relevant for the given node.
var dests []tailcfg.NetPortRange
dests := []tailcfg.NetPortRange{}
DEST_LOOP:
for _, dest := range rule.DstPorts {
expanded, err := util.ParseIPSet(dest.IP, nil)
@@ -285,7 +301,7 @@ func (pol *ACLPolicy) CompileSSHPolicy(
return nil, nil
}
var rules []*tailcfg.SSHRule
rules := []*tailcfg.SSHRule{}
acceptAction := tailcfg.SSHAction{
Message: "",
@@ -517,7 +533,8 @@ func (pol *ACLPolicy) expandSource(
return []string{}, err
}
var prefixes []string
prefixes := []string{}
for _, prefix := range ipSet.Prefixes() {
prefixes = append(prefixes, prefix.String())
}
@@ -598,8 +615,8 @@ func excludeCorrectlyTaggedNodes(
nodes types.Nodes,
user string,
) types.Nodes {
var out types.Nodes
var tags []string
out := types.Nodes{}
tags := []string{}
for tag := range aclPolicy.TagOwners {
owners, _ := expandOwnersFromTag(aclPolicy, user)
ns := append(owners, user)
@@ -644,7 +661,7 @@ func expandPorts(portsStr string, isWild bool) (*[]tailcfg.PortRange, error) {
return nil, ErrWildcardIsNeeded
}
var ports []tailcfg.PortRange
ports := []tailcfg.PortRange{}
for _, portStr := range strings.Split(portsStr, ",") {
log.Trace().Msgf("parsing portstring: %s", portStr)
rang := strings.Split(portStr, "-")
@@ -720,7 +737,7 @@ func expandOwnersFromTag(
func (pol *ACLPolicy) expandUsersFromGroup(
group string,
) ([]string, error) {
var users []string
users := []string{}
log.Trace().Caller().Interface("pol", pol).Msg("test")
aclGroups, ok := pol.Groups[group]
if !ok {
@@ -755,7 +772,7 @@ func (pol *ACLPolicy) expandIPsFromGroup(
group string,
nodes types.Nodes,
) (*netipx.IPSet, error) {
var build netipx.IPSetBuilder
build := netipx.IPSetBuilder{}
users, err := pol.expandUsersFromGroup(group)
if err != nil {
@@ -775,7 +792,7 @@ func (pol *ACLPolicy) expandIPsFromTag(
alias string,
nodes types.Nodes,
) (*netipx.IPSet, error) {
var build netipx.IPSetBuilder
build := netipx.IPSetBuilder{}
// check for forced tags
for _, node := range nodes {
@@ -824,14 +841,14 @@ func (pol *ACLPolicy) expandIPsFromUser(
user string,
nodes types.Nodes,
) (*netipx.IPSet, error) {
var build netipx.IPSetBuilder
build := netipx.IPSetBuilder{}
filteredNodes := filterNodesByUser(nodes, user)
filteredNodes = excludeCorrectlyTaggedNodes(pol, filteredNodes, user)
// shortcurcuit if we have no nodes to get ips from.
if len(filteredNodes) == 0 {
return nil, nil // nolint
return nil, nil //nolint
}
for _, node := range filteredNodes {
@@ -849,7 +866,7 @@ func (pol *ACLPolicy) expandIPsFromSingleIP(
matches := nodes.FilterByIP(ip)
var build netipx.IPSetBuilder
build := netipx.IPSetBuilder{}
build.Add(ip)
for _, node := range matches {
@@ -864,7 +881,7 @@ func (pol *ACLPolicy) expandIPsFromIPPrefix(
nodes types.Nodes,
) (*netipx.IPSet, error) {
log.Trace().Str("prefix", prefix.String()).Msg("expandAlias got prefix")
var build netipx.IPSetBuilder
build := netipx.IPSetBuilder{}
build.AddPrefix(prefix)
// This is suboptimal and quite expensive, but if we only add the prefix, we will miss all the relevant IPv6
@@ -914,8 +931,8 @@ func isAutoGroup(str string) bool {
func (pol *ACLPolicy) TagsOfNode(
node *types.Node,
) ([]string, []string) {
var validTags []string
var invalidTags []string
validTags := make([]string, 0)
invalidTags := make([]string, 0)
// TODO(kradalby): Why is this sometimes nil? coming from tailNode?
if node == nil {
@@ -956,7 +973,7 @@ func (pol *ACLPolicy) TagsOfNode(
}
func filterNodesByUser(nodes types.Nodes, user string) types.Nodes {
var out types.Nodes
out := types.Nodes{}
for _, node := range nodes {
if node.User.Name == user {
out = append(out, node)
@@ -972,7 +989,7 @@ func FilterNodesByACL(
nodes types.Nodes,
filter []tailcfg.FilterRule,
) types.Nodes {
var result types.Nodes
result := types.Nodes{}
for index, peer := range nodes {
if peer.ID == node.ID {

View File

@@ -321,27 +321,44 @@ func TestParsing(t *testing.T) {
wantErr: false,
},
{
name: "ipv6",
format: "hujson",
name: "port-wildcard-yaml",
format: "yaml",
acl: `
{
"hosts": {
"host-1": "100.100.100.100/32",
"subnet-1": "100.100.101.100/24",
},
"acls": [
{
"action": "accept",
"src": [
"*",
],
"dst": [
"host-1:*",
],
---
hosts:
host-1: 100.100.100.100/32
subnet-1: 100.100.101.100/24
acls:
- action: accept
src:
- "*"
dst:
- host-1:*
`,
want: []tailcfg.FilterRule{
{
SrcIPs: []string{"0.0.0.0/0", "::/0"},
DstPorts: []tailcfg.NetPortRange{
{IP: "100.100.100.100/32", Ports: tailcfg.PortRangeAny},
},
},
},
wantErr: false,
},
],
}
{
name: "ipv6-yaml",
format: "yaml",
acl: `
---
hosts:
host-1: 100.100.100.100/32
subnet-1: 100.100.101.100/24
acls:
- action: accept
src:
- "*"
dst:
- host-1:*
`,
want: []tailcfg.FilterRule{
{
@@ -357,7 +374,7 @@ func TestParsing(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pol, err := LoadACLPolicyFromBytes([]byte(tt.acl))
pol, err := LoadACLPolicyFromBytes([]byte(tt.acl), tt.format)
if tt.wantErr && err == nil {
t.Errorf("parsing() error = %v, wantErr %v", err, tt.wantErr)
@@ -515,7 +532,7 @@ func (s *Suite) TestRuleInvalidGeneration(c *check.C) {
"example-host-2:80"
],
"deny": [
"example-host-2:100"
"exapmle-host-2:100"
],
},
{
@@ -527,7 +544,7 @@ func (s *Suite) TestRuleInvalidGeneration(c *check.C) {
],
}
`)
pol, err := LoadACLPolicyFromBytes(acl)
pol, err := LoadACLPolicyFromBytes(acl, "hujson")
c.Assert(pol.ACLs, check.HasLen, 6)
c.Assert(err, check.IsNil)
@@ -618,7 +635,7 @@ func Test_expandGroup(t *testing.T) {
wantErr: false,
},
{
name: "InexistentGroup",
name: "InexistantGroup",
field: field{
pol: ACLPolicy{
Groups: Groups{
@@ -926,7 +943,7 @@ func Test_listNodesInUser(t *testing.T) {
},
user: "mickael",
},
want: nil,
want: types.Nodes{},
},
}
for _, test := range tests {
@@ -1628,7 +1645,7 @@ func TestACLPolicy_generateFilterRules(t *testing.T) {
name: "no-policy",
field: field{},
args: args{},
want: nil,
want: []tailcfg.FilterRule{},
wantErr: false,
},
{
@@ -1782,7 +1799,7 @@ var tsExitNodeDest = []tailcfg.NetPortRange{
}
// hsExitNodeDest is the list of destination IP ranges that are allowed when
// we use headscale "autogroup:internet".
// we use headscale "autogroup:internet"
var hsExitNodeDest = []tailcfg.NetPortRange{
{IP: "0.0.0.0/5", Ports: tailcfg.PortRangeAny},
{IP: "8.0.0.0/7", Ports: tailcfg.PortRangeAny},
@@ -1839,7 +1856,7 @@ func TestTheInternet(t *testing.T) {
internetPrefs := internetSet.Prefixes()
for i := range internetPrefs {
for i, _ := range internetPrefs {
if internetPrefs[i].String() != hsExitNodeDest[i].IP {
t.Errorf("prefix from internet set %q != hsExit list %q", internetPrefs[i].String(), hsExitNodeDest[i].IP)
}
@@ -2587,7 +2604,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
{
name: "all hosts can talk to each other",
args: args{
nodes: types.Nodes{ // list of all nodes in the database
nodes: types.Nodes{ // list of all nodess in the database
&types.Node{
ID: 1,
IPv4: iap("100.64.0.1"),
@@ -2634,7 +2651,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
{
name: "One host can talk to another, but not all hosts",
args: args{
nodes: types.Nodes{ // list of all nodes in the database
nodes: types.Nodes{ // list of all nodess in the database
&types.Node{
ID: 1,
IPv4: iap("100.64.0.1"),
@@ -2676,7 +2693,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
{
name: "host cannot directly talk to destination, but return path is authorized",
args: args{
nodes: types.Nodes{ // list of all nodes in the database
nodes: types.Nodes{ // list of all nodess in the database
&types.Node{
ID: 1,
IPv4: iap("100.64.0.1"),
@@ -2718,7 +2735,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
{
name: "rules allows all hosts to reach one destination",
args: args{
nodes: types.Nodes{ // list of all nodes in the database
nodes: types.Nodes{ // list of all nodess in the database
&types.Node{
ID: 1,
IPv4: iap("100.64.0.1"),
@@ -2760,7 +2777,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
{
name: "rules allows all hosts to reach one destination, destination can reach all hosts",
args: args{
nodes: types.Nodes{ // list of all nodes in the database
nodes: types.Nodes{ // list of all nodess in the database
&types.Node{
ID: 1,
IPv4: iap("100.64.0.1"),
@@ -2807,7 +2824,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
{
name: "rule allows all hosts to reach all destinations",
args: args{
nodes: types.Nodes{ // list of all nodes in the database
nodes: types.Nodes{ // list of all nodess in the database
&types.Node{
ID: 1,
IPv4: iap("100.64.0.1"),
@@ -2854,7 +2871,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
{
name: "without rule all communications are forbidden",
args: args{
nodes: types.Nodes{ // list of all nodes in the database
nodes: types.Nodes{ // list of all nodess in the database
&types.Node{
ID: 1,
IPv4: iap("100.64.0.1"),
@@ -2879,7 +2896,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
User: types.User{Name: "marc"},
},
},
want: nil,
want: types.Nodes{},
},
{
// Investigating 699
@@ -3409,7 +3426,7 @@ func TestSSHRules(t *testing.T) {
},
},
},
want: &tailcfg.SSHPolicy{Rules: nil},
want: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{}},
},
}

View File

@@ -6,25 +6,26 @@ import (
"strings"
"github.com/tailscale/hujson"
"gopkg.in/yaml.v3"
)
// ACLPolicy represents a Tailscale ACL Policy.
type ACLPolicy struct {
Groups Groups `json:"groups"`
Hosts Hosts `json:"hosts"`
TagOwners TagOwners `json:"tagOwners"`
ACLs []ACL `json:"acls"`
Tests []ACLTest `json:"tests"`
AutoApprovers AutoApprovers `json:"autoApprovers"`
SSHs []SSH `json:"ssh"`
Groups Groups `json:"groups" yaml:"groups"`
Hosts Hosts `json:"hosts" yaml:"hosts"`
TagOwners TagOwners `json:"tagOwners" yaml:"tagOwners"`
ACLs []ACL `json:"acls" yaml:"acls"`
Tests []ACLTest `json:"tests" yaml:"tests"`
AutoApprovers AutoApprovers `json:"autoApprovers" yaml:"autoApprovers"`
SSHs []SSH `json:"ssh" yaml:"ssh"`
}
// ACL is a basic rule for the ACL Policy.
type ACL struct {
Action string `json:"action"`
Protocol string `json:"proto"`
Sources []string `json:"src"`
Destinations []string `json:"dst"`
Action string `json:"action" yaml:"action"`
Protocol string `json:"proto" yaml:"proto"`
Sources []string `json:"src" yaml:"src"`
Destinations []string `json:"dst" yaml:"dst"`
}
// Groups references a series of alias in the ACL rules.
@@ -36,27 +37,27 @@ type Hosts map[string]netip.Prefix
// TagOwners specify what users (users?) are allow to use certain tags.
type TagOwners map[string][]string
// ACLTest is not implemented, but should be used to check if a certain rule is allowed.
// ACLTest is not implemented, but should be use to check if a certain rule is allowed.
type ACLTest struct {
Source string `json:"src"`
Accept []string `json:"accept"`
Deny []string `json:"deny,omitempty"`
Source string `json:"src" yaml:"src"`
Accept []string `json:"accept" yaml:"accept"`
Deny []string `json:"deny,omitempty" yaml:"deny,omitempty"`
}
// AutoApprovers specify which users (users?), groups or tags have their advertised routes
// or exit node status automatically enabled.
type AutoApprovers struct {
Routes map[string][]string `json:"routes"`
ExitNode []string `json:"exitNode"`
Routes map[string][]string `json:"routes" yaml:"routes"`
ExitNode []string `json:"exitNode" yaml:"exitNode"`
}
// SSH controls who can ssh into which machines.
type SSH struct {
Action string `json:"action"`
Sources []string `json:"src"`
Destinations []string `json:"dst"`
Users []string `json:"users"`
CheckPeriod string `json:"checkPeriod,omitempty"`
Action string `json:"action" yaml:"action"`
Sources []string `json:"src" yaml:"src"`
Destinations []string `json:"dst" yaml:"dst"`
Users []string `json:"users" yaml:"users"`
CheckPeriod string `json:"checkPeriod,omitempty" yaml:"checkPeriod,omitempty"`
}
// UnmarshalJSON allows to parse the Hosts directly into netip objects.
@@ -88,6 +89,27 @@ func (hosts *Hosts) UnmarshalJSON(data []byte) error {
return nil
}
// UnmarshalYAML allows to parse the Hosts directly into netip objects.
func (hosts *Hosts) UnmarshalYAML(data []byte) error {
newHosts := Hosts{}
hostIPPrefixMap := make(map[string]string)
err := yaml.Unmarshal(data, &hostIPPrefixMap)
if err != nil {
return err
}
for host, prefixStr := range hostIPPrefixMap {
prefix, err := netip.ParsePrefix(prefixStr)
if err != nil {
return err
}
newHosts[host] = prefix
}
*hosts = newHosts
return nil
}
// IsZero is perhaps a bit naive here.
func (pol ACLPolicy) IsZero() bool {
if len(pol.Groups) == 0 && len(pol.Hosts) == 0 && len(pol.ACLs) == 0 {
@@ -97,7 +119,7 @@ func (pol ACLPolicy) IsZero() bool {
return false
}
// GetRouteApprovers returns the list of autoApproving users, groups or tags for a given IPPrefix.
// Returns the list of autoApproving users, groups or tags for a given IPPrefix.
func (autoApprovers *AutoApprovers) GetRouteApprovers(
prefix netip.Prefix,
) ([]string, error) {
@@ -105,7 +127,7 @@ func (autoApprovers *AutoApprovers) GetRouteApprovers(
return autoApprovers.ExitNode, nil // 0.0.0.0/0, ::/0 or equivalent
}
approverAliases := make([]string, 0)
approverAliases := []string{}
for autoApprovedPrefix, autoApproverAliases := range autoApprovers.Routes {
autoApprovedPrefix, err := netip.ParsePrefix(autoApprovedPrefix)

View File

@@ -9,13 +9,13 @@ import (
"net/netip"
"sort"
"strings"
"sync"
"time"
"github.com/juanfont/headscale/hscontrol/db"
"github.com/juanfont/headscale/hscontrol/mapper"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/rs/zerolog/log"
"github.com/sasha-s/go-deadlock"
xslices "golang.org/x/exp/slices"
"gorm.io/gorm"
"tailscale.com/tailcfg"
@@ -29,6 +29,11 @@ type contextKey string
const nodeNameContextKey = contextKey("nodeName")
type sessionManager struct {
mu sync.RWMutex
sess map[types.NodeID]*mapSession
}
type mapSession struct {
h *Headscale
req tailcfg.MapRequest
@@ -36,13 +41,12 @@ type mapSession struct {
capVer tailcfg.CapabilityVersion
mapper *mapper.Mapper
cancelChMu deadlock.Mutex
serving bool
servingMu sync.Mutex
ch chan types.StateUpdate
cancelCh chan struct{}
cancelChOpen bool
ch chan types.StateUpdate
cancelCh chan struct{}
keepAlive time.Duration
keepAliveTicker *time.Ticker
node *types.Node
@@ -73,8 +77,6 @@ func (h *Headscale) newMapSession(
}
}
ka := keepAliveInterval + (time.Duration(rand.IntN(9000)) * time.Millisecond)
return &mapSession{
h: h,
ctx: ctx,
@@ -84,12 +86,13 @@ func (h *Headscale) newMapSession(
capVer: req.Version,
mapper: h.mapper,
ch: updateChan,
cancelCh: make(chan struct{}),
cancelChOpen: true,
// serving indicates if a client is being served.
serving: false,
keepAlive: ka,
keepAliveTicker: nil,
ch: updateChan,
cancelCh: make(chan struct{}),
keepAliveTicker: time.NewTicker(keepAliveInterval + (time.Duration(rand.IntN(9000)) * time.Millisecond)),
// Loggers
warnf: warnf,
@@ -100,23 +103,15 @@ func (h *Headscale) newMapSession(
}
func (m *mapSession) close() {
m.cancelChMu.Lock()
defer m.cancelChMu.Unlock()
if !m.cancelChOpen {
mapResponseClosed.WithLabelValues("chanclosed").Inc()
m.servingMu.Lock()
defer m.servingMu.Unlock()
if !m.serving {
return
}
m.tracef("mapSession (%p) sending message on cancel chan", m)
select {
case m.cancelCh <- struct{}{}:
mapResponseClosed.WithLabelValues("sent").Inc()
m.tracef("mapSession (%p) sent message on cancel chan", m)
case <-time.After(30 * time.Second):
mapResponseClosed.WithLabelValues("timeout").Inc()
m.tracef("mapSession (%p) timed out sending close message", m)
}
m.tracef("mapSession (%p) sending message on cancel chan")
m.cancelCh <- struct{}{}
m.tracef("mapSession (%p) sent message on cancel chan")
}
func (m *mapSession) isStreaming() bool {
@@ -131,24 +126,40 @@ func (m *mapSession) isReadOnlyUpdate() bool {
return !m.req.Stream && m.req.OmitPeers && m.req.ReadOnly
}
func (m *mapSession) resetKeepAlive() {
m.keepAliveTicker.Reset(m.keepAlive)
}
func (m *mapSession) beforeServeLongPoll() {
if m.node.IsEphemeral() {
m.h.ephemeralGC.Cancel(m.node.ID)
}
}
func (m *mapSession) afterServeLongPoll() {
if m.node.IsEphemeral() {
m.h.ephemeralGC.Schedule(m.node.ID, m.h.cfg.EphemeralNodeInactivityTimeout)
}
}
// serve handles non-streaming requests.
// handlePoll ensures the node gets the appropriate updates from either
// polling or immediate responses.
//
//nolint:gocyclo
func (m *mapSession) serve() {
// Register with the notifier if this is a streaming
// session
if m.isStreaming() {
// defers are called in reverse order,
// so top one is executed last.
// Failover the node's routes if any.
defer m.infof("node has disconnected, mapSession: %p", m)
defer m.pollFailoverRoutes("node closing connection", m.node)
defer m.h.updateNodeOnlineStatus(false, m.node)
defer m.h.nodeNotifier.RemoveNode(m.node.ID)
defer func() {
m.servingMu.Lock()
defer m.servingMu.Unlock()
m.serving = false
close(m.cancelCh)
}()
m.serving = true
m.h.nodeNotifier.AddNode(m.node.ID, m.ch)
m.h.updateNodeOnlineStatus(true, m.node)
m.infof("node has connected, mapSession: %p", m)
}
// TODO(kradalby): A set todos to harden:
// - func to tell the stream to die, readonly -> false, !stream && omitpeers -> false, true
@@ -184,46 +195,14 @@ func (m *mapSession) serve() {
return
}
}
// serveLongPoll ensures the node gets the appropriate updates from either
// polling or immediate responses.
//
//nolint:gocyclo
func (m *mapSession) serveLongPoll() {
m.beforeServeLongPoll()
// Clean up the session when the client disconnects
defer func() {
m.cancelChMu.Lock()
m.cancelChOpen = false
close(m.cancelCh)
m.cancelChMu.Unlock()
// only update node status if the node channel was removed.
// in principal, it will be removed, but the client rapidly
// reconnects, the channel might be of another connection.
// In that case, it is not closed and the node is still online.
if m.h.nodeNotifier.RemoveNode(m.node.ID, m.ch) {
// Failover the node's routes if any.
m.h.updateNodeOnlineStatus(false, m.node)
m.pollFailoverRoutes("node closing connection", m.node)
}
m.afterServeLongPoll()
m.infof("node has disconnected, mapSession: %p, chan: %p", m, m.ch)
}()
// From version 68, all streaming requests can be treated as read only.
// TODO: Remove when we drop support for 1.48
if m.capVer < 68 {
// Error has been handled/written to client in the func
// return
err := m.handleSaveNode()
if err != nil {
mapResponseWriteUpdatesInStream.WithLabelValues("error").Inc()
m.close()
return
}
mapResponseWriteUpdatesInStream.WithLabelValues("ok").Inc()
@@ -245,13 +224,6 @@ func (m *mapSession) serveLongPoll() {
ctx, cancel := context.WithCancel(context.WithValue(m.ctx, nodeNameContextKey, m.node.Hostname))
defer cancel()
m.keepAliveTicker = time.NewTicker(m.keepAlive)
m.h.nodeNotifier.AddNode(m.node.ID, m.ch)
go m.h.updateNodeOnlineStatus(true, m.node)
m.infof("node has connected, mapSession: %p, chan: %p", m, m.ch)
// Loop through updates and continuously send them to the
// client.
for {
@@ -259,21 +231,13 @@ func (m *mapSession) serveLongPoll() {
select {
case <-m.cancelCh:
m.tracef("poll cancelled received")
mapResponseEnded.WithLabelValues("cancelled").Inc()
return
case <-ctx.Done():
m.tracef("poll context done")
mapResponseEnded.WithLabelValues("done").Inc()
return
// Consume updates sent to node
case update, ok := <-m.ch:
if !ok {
m.tracef("update channel closed, streaming session is likely being replaced")
return
}
// Consume all updates sent to node
case update := <-m.ch:
m.tracef("received stream update: %s %s", update.Type.String(), update.Message)
mapResponseUpdateReceived.WithLabelValues(update.Type.String()).Inc()
@@ -339,13 +303,15 @@ func (m *mapSession) serveLongPoll() {
return
}
// log.Trace().Str("node", m.node.Hostname).TimeDiff("timeSpent", time.Now(), startMapResp).Str("mkey", m.node.MachineKey.String()).Int("type", int(update.Type)).Msg("finished making map response")
// Only send update if there is change
if data != nil {
startWrite := time.Now()
_, err = m.w.Write(data)
if err != nil {
mapResponseSent.WithLabelValues("error", updateType).Inc()
m.errf(err, "could not write the map response(%s), for mapSession: %p", update.Type.String(), m)
m.errf(err, "Could not write the map response, for mapSession: %p", m)
return
}
@@ -358,12 +324,8 @@ func (m *mapSession) serveLongPoll() {
log.Trace().Str("node", m.node.Hostname).TimeDiff("timeSpent", time.Now(), startWrite).Str("mkey", m.node.MachineKey.String()).Msg("finished writing mapresp to node")
if debugHighCardinalityMetrics {
mapResponseLastSentSeconds.WithLabelValues(updateType, m.node.ID.String()).Set(float64(time.Now().Unix()))
}
mapResponseSent.WithLabelValues("ok", updateType).Inc()
m.tracef("update sent")
m.resetKeepAlive()
}
case <-m.keepAliveTicker.C:
@@ -386,9 +348,6 @@ func (m *mapSession) serveLongPoll() {
return
}
if debugHighCardinalityMetrics {
mapResponseLastSentSeconds.WithLabelValues("keepalive", m.node.ID.String()).Set(float64(time.Now().Unix()))
}
mapResponseSent.WithLabelValues("ok", "keepalive").Inc()
}
}
@@ -445,6 +404,16 @@ func (h *Headscale) updateNodeOnlineStatus(online bool, node *types.Node) {
}, node.ID)
}
func closeChanWithLog[C chan []byte | chan struct{} | chan types.StateUpdate](channel C, node, name string) {
log.Trace().
Str("handler", "PollNetMap").
Str("node", node).
Str("channel", "Done").
Msg(fmt.Sprintf("Closing %s channel", name))
close(channel)
}
func (m *mapSession) handleEndpointUpdate() {
m.tracef("received endpoint update")
@@ -456,17 +425,6 @@ func (m *mapSession) handleEndpointUpdate() {
m.node.ApplyPeerChange(&change)
sendUpdate, routesChanged := hostInfoChanged(m.node.Hostinfo, m.req.Hostinfo)
// The node might not set NetInfo if it has not changed and if
// the full HostInfo object is overrwritten, the information is lost.
// If there is no NetInfo, keep the previous one.
// From 1.66 the client only sends it if changed:
// https://github.com/tailscale/tailscale/commit/e1011f138737286ecf5123ff887a7a5800d129a2
// TODO(kradalby): evaulate if we need better comparing of hostinfo
// before we take the changes.
if m.req.Hostinfo.NetInfo == nil {
m.req.Hostinfo.NetInfo = m.node.Hostinfo.NetInfo
}
m.node.Hostinfo = m.req.Hostinfo
logTracePeerChange(m.node.Hostname, sendUpdate, &change)

View File

@@ -20,7 +20,6 @@ import (
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/dnstype"
"tailscale.com/util/set"
)
const (
@@ -39,13 +38,6 @@ const (
IPAllocationStrategyRandom IPAllocationStrategy = "random"
)
type PolicyMode string
const (
PolicyModeDB = "database"
PolicyModeFile = "file"
)
// Config contains the initial Headscale configuration.
type Config struct {
ServerURL string
@@ -71,8 +63,7 @@ type Config struct {
ACMEURL string
ACMEEmail string
DNSConfig *tailcfg.DNSConfig
DNSUserNameInMagicDNS bool
DNSConfig *tailcfg.DNSConfig
UnixSocket string
UnixSocketPermission fs.FileMode
@@ -84,28 +75,13 @@ type Config struct {
CLI CLIConfig
Policy PolicyConfig
ACL ACLConfig
Tuning Tuning
}
type DNSConfig struct {
MagicDNS bool `mapstructure:"magic_dns"`
BaseDomain string `mapstructure:"base_domain"`
Nameservers Nameservers
SearchDomains []string `mapstructure:"search_domains"`
ExtraRecords []tailcfg.DNSRecord `mapstructure:"extra_records"`
UserNameInMagicDNS bool `mapstructure:"use_username_in_magic_dns"`
}
type Nameservers struct {
Global []string
Split map[string][]string
}
type SqliteConfig struct {
Path string
WriteAheadLog bool
Path string
}
type PostgresConfig struct {
@@ -120,22 +96,11 @@ type PostgresConfig struct {
ConnMaxIdleTimeSecs int
}
type GormConfig struct {
Debug bool
SlowThreshold time.Duration
SkipErrRecordNotFound bool
ParameterizedQueries bool
PrepareStmt bool
}
type DatabaseConfig struct {
// Type sets the database type, either "sqlite3" or "postgres"
Type string
Debug bool
// Type sets the gorm configuration
Gorm GormConfig
Sqlite SqliteConfig
Postgres PostgresConfig
}
@@ -196,9 +161,8 @@ type CLIConfig struct {
Insecure bool
}
type PolicyConfig struct {
Path string
Mode PolicyMode
type ACLConfig struct {
PolicyPath string
}
type LogConfig struct {
@@ -207,7 +171,6 @@ type LogConfig struct {
}
type Tuning struct {
NotifierSendTimeout time.Duration
BatchChangeDelay time.Duration
NodeMapSessionBufferedChanSize int
}
@@ -227,26 +190,18 @@ func LoadConfig(path string, isFile bool) error {
}
}
envPrefix := "headscale"
viper.SetEnvPrefix(envPrefix)
viper.SetEnvPrefix("headscale")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
viper.SetDefault("policy.mode", "file")
viper.SetDefault("tls_letsencrypt_cache_dir", "/var/www/.cache")
viper.SetDefault("tls_letsencrypt_challenge_type", HTTP01ChallengeType)
viper.SetDefault("log.level", "info")
viper.SetDefault("log.format", TextLogFormat)
viper.SetDefault("dns.magic_dns", true)
viper.SetDefault("dns.base_domain", "")
viper.SetDefault("dns.nameservers.global", []string{})
viper.SetDefault("dns.nameservers.split", map[string]string{})
viper.SetDefault("dns.search_domains", []string{})
viper.SetDefault("dns.extra_records", []tailcfg.DNSRecord{})
viper.SetDefault("dns.use_username_in_magic_dns", false)
viper.SetDefault("dns_config", nil)
viper.SetDefault("dns_config.override_local_dns", true)
viper.SetDefault("derp.server.enabled", false)
viper.SetDefault("derp.server.stun.enabled", true)
@@ -266,8 +221,6 @@ func LoadConfig(path string, isFile bool) error {
viper.SetDefault("database.postgres.max_idle_conns", 10)
viper.SetDefault("database.postgres.conn_max_idle_time_secs", 3600)
viper.SetDefault("database.sqlite.write_ahead_log", true)
viper.SetDefault("oidc.scope", []string{oidc.ScopeOpenID, "profile", "email"})
viper.SetDefault("oidc.strip_email_domain", true)
viper.SetDefault("oidc.only_start_if_oidc_is_available", true)
@@ -279,7 +232,6 @@ func LoadConfig(path string, isFile bool) error {
viper.SetDefault("ephemeral_node_inactivity_timeout", "120s")
viper.SetDefault("tuning.notifier_send_timeout", "800ms")
viper.SetDefault("tuning.batch_change_delay", "800ms")
viper.SetDefault("tuning.node_mapsession_buffered_chan_size", 30)
@@ -290,34 +242,11 @@ func LoadConfig(path string, isFile bool) error {
}
if err := viper.ReadInConfig(); err != nil {
log.Warn().Err(err).Msg("Failed to read configuration from disk")
return fmt.Errorf("fatal error reading config file: %w", err)
}
depr := deprecator{
warns: make(set.Set[string]),
fatals: make(set.Set[string]),
}
// Register aliases for backward compatibility
// Has to be called _after_ viper.ReadInConfig()
// https://github.com/spf13/viper/issues/560
// Alias the old ACL Policy path with the new configuration option.
depr.fatalIfNewKeyIsNotUsed("policy.path", "acl_policy_path")
// Move dns_config -> dns
depr.warn("dns_config.override_local_dns")
depr.fatalIfNewKeyIsNotUsed("dns.magic_dns", "dns_config.magic_dns")
depr.fatalIfNewKeyIsNotUsed("dns.base_domain", "dns_config.base_domain")
depr.fatalIfNewKeyIsNotUsed("dns.nameservers.global", "dns_config.nameservers")
depr.fatalIfNewKeyIsNotUsed("dns.nameservers.split", "dns_config.restricted_nameservers")
depr.fatalIfNewKeyIsNotUsed("dns.search_domains", "dns_config.domains")
depr.fatalIfNewKeyIsNotUsed("dns.extra_records", "dns_config.extra_records")
depr.warn("dns_config.use_username_in_magic_dns")
depr.warn("dns.use_username_in_magic_dns")
depr.Log()
// Collect any validation errors and return them all at once
var errorText string
if (viper.GetString("tls_letsencrypt_hostname") != "") &&
@@ -454,13 +383,11 @@ func GetLogTailConfig() LogTailConfig {
}
}
func GetPolicyConfig() PolicyConfig {
policyPath := viper.GetString("policy.path")
policyMode := viper.GetString("policy.mode")
func GetACLConfig() ACLConfig {
policyPath := viper.GetString("acl_policy_path")
return PolicyConfig{
Path: policyPath,
Mode: PolicyMode(policyMode),
return ACLConfig{
PolicyPath: policyPath,
}
}
@@ -497,11 +424,6 @@ func GetDatabaseConfig() DatabaseConfig {
type_ := viper.GetString("database.type")
skipErrRecordNotFound := viper.GetBool("database.gorm.skip_err_record_not_found")
slowThreshold := viper.GetDuration("database.gorm.slow_threshold") * time.Millisecond
parameterizedQueries := viper.GetBool("database.gorm.parameterized_queries")
prepareStmt := viper.GetBool("database.gorm.prepare_stmt")
switch type_ {
case DatabaseSqlite, DatabasePostgres:
break
@@ -515,18 +437,10 @@ func GetDatabaseConfig() DatabaseConfig {
return DatabaseConfig{
Type: type_,
Debug: debug,
Gorm: GormConfig{
Debug: debug,
SkipErrRecordNotFound: skipErrRecordNotFound,
SlowThreshold: slowThreshold,
ParameterizedQueries: parameterizedQueries,
PrepareStmt: prepareStmt,
},
Sqlite: SqliteConfig{
Path: util.AbsolutePathFromConfigPath(
viper.GetString("database.sqlite.path"),
),
WriteAheadLog: viper.GetBool("database.sqlite.write_ahead_log"),
},
Postgres: PostgresConfig{
Host: viper.GetString("database.postgres.host"),
@@ -544,131 +458,123 @@ func GetDatabaseConfig() DatabaseConfig {
}
}
func DNS() (DNSConfig, error) {
var dns DNSConfig
func GetDNSConfig() (*tailcfg.DNSConfig, string) {
if viper.IsSet("dns_config") {
dnsConfig := &tailcfg.DNSConfig{}
// TODO: Use this instead of manually getting settings when
// UnmarshalKey is compatible with Environment Variables.
// err := viper.UnmarshalKey("dns", &dns)
// if err != nil {
// return DNSConfig{}, fmt.Errorf("unmarshaling dns config: %w", err)
// }
overrideLocalDNS := viper.GetBool("dns_config.override_local_dns")
dns.MagicDNS = viper.GetBool("dns.magic_dns")
dns.BaseDomain = viper.GetString("dns.base_domain")
dns.Nameservers.Global = viper.GetStringSlice("dns.nameservers.global")
dns.Nameservers.Split = viper.GetStringMapStringSlice("dns.nameservers.split")
dns.SearchDomains = viper.GetStringSlice("dns.search_domains")
if viper.IsSet("dns_config.nameservers") {
nameserversStr := viper.GetStringSlice("dns_config.nameservers")
if viper.IsSet("dns.extra_records") {
var extraRecords []tailcfg.DNSRecord
nameservers := []netip.Addr{}
resolvers := []*dnstype.Resolver{}
err := viper.UnmarshalKey("dns.extra_records", &extraRecords)
if err != nil {
return DNSConfig{}, fmt.Errorf("unmarshaling dns extra records: %w", err)
}
for _, nameserverStr := range nameserversStr {
// Search for explicit DNS-over-HTTPS resolvers
if strings.HasPrefix(nameserverStr, "https://") {
resolvers = append(resolvers, &dnstype.Resolver{
Addr: nameserverStr,
})
dns.ExtraRecords = extraRecords
}
// This nameserver can not be parsed as an IP address
continue
}
dns.UserNameInMagicDNS = viper.GetBool("dns.use_username_in_magic_dns")
// Parse nameserver as a regular IP
nameserver, err := netip.ParseAddr(nameserverStr)
if err != nil {
log.Error().
Str("func", "getDNSConfig").
Err(err).
Msgf("Could not parse nameserver IP: %s", nameserverStr)
}
return dns, nil
}
// GlobalResolvers returns the global DNS resolvers
// defined in the config file.
// If a nameserver is a valid IP, it will be used as a regular resolver.
// If a nameserver is a valid URL, it will be used as a DoH resolver.
// If a nameserver is neither a valid URL nor a valid IP, it will be ignored.
func (d *DNSConfig) GlobalResolvers() []*dnstype.Resolver {
var resolvers []*dnstype.Resolver
for _, nsStr := range d.Nameservers.Global {
warn := ""
if _, err := netip.ParseAddr(nsStr); err == nil {
resolvers = append(resolvers, &dnstype.Resolver{
Addr: nsStr,
})
continue
} else {
warn = fmt.Sprintf("Invalid global nameserver %q. Parsing error: %s ignoring", nsStr, err)
}
if _, err := url.Parse(nsStr); err == nil {
resolvers = append(resolvers, &dnstype.Resolver{
Addr: nsStr,
})
} else {
warn = fmt.Sprintf("Invalid global nameserver %q. Parsing error: %s ignoring", nsStr, err)
}
if warn != "" {
log.Warn().Msg(warn)
}
}
return resolvers
}
// SplitResolvers returns a map of domain to DNS resolvers.
// If a nameserver is a valid IP, it will be used as a regular resolver.
// If a nameserver is a valid URL, it will be used as a DoH resolver.
// If a nameserver is neither a valid URL nor a valid IP, it will be ignored.
func (d *DNSConfig) SplitResolvers() map[string][]*dnstype.Resolver {
routes := make(map[string][]*dnstype.Resolver)
for domain, nameservers := range d.Nameservers.Split {
var resolvers []*dnstype.Resolver
for _, nsStr := range nameservers {
warn := ""
if _, err := netip.ParseAddr(nsStr); err == nil {
nameservers = append(nameservers, nameserver)
resolvers = append(resolvers, &dnstype.Resolver{
Addr: nsStr,
Addr: nameserver.String(),
})
continue
} else {
warn = fmt.Sprintf("Invalid split dns nameserver %q. Parsing error: %s ignoring", nsStr, err)
}
if _, err := url.Parse(nsStr); err == nil {
resolvers = append(resolvers, &dnstype.Resolver{
Addr: nsStr,
})
} else {
warn = fmt.Sprintf("Invalid split dns nameserver %q. Parsing error: %s ignoring", nsStr, err)
}
dnsConfig.Nameservers = nameservers
if warn != "" {
log.Warn().Msg(warn)
if overrideLocalDNS {
dnsConfig.Resolvers = resolvers
} else {
dnsConfig.FallbackResolvers = resolvers
}
}
routes[domain] = resolvers
if viper.IsSet("dns_config.restricted_nameservers") {
dnsConfig.Routes = make(map[string][]*dnstype.Resolver)
domains := []string{}
restrictedDNS := viper.GetStringMapStringSlice(
"dns_config.restricted_nameservers",
)
for domain, restrictedNameservers := range restrictedDNS {
restrictedResolvers := make(
[]*dnstype.Resolver,
len(restrictedNameservers),
)
for index, nameserverStr := range restrictedNameservers {
nameserver, err := netip.ParseAddr(nameserverStr)
if err != nil {
log.Error().
Str("func", "getDNSConfig").
Err(err).
Msgf("Could not parse restricted nameserver IP: %s", nameserverStr)
}
restrictedResolvers[index] = &dnstype.Resolver{
Addr: nameserver.String(),
}
}
dnsConfig.Routes[domain] = restrictedResolvers
domains = append(domains, domain)
}
dnsConfig.Domains = domains
}
if viper.IsSet("dns_config.domains") {
domains := viper.GetStringSlice("dns_config.domains")
if len(dnsConfig.Resolvers) > 0 {
dnsConfig.Domains = domains
} else if domains != nil {
log.Warn().
Msg("Warning: dns_config.domains is set, but no nameservers are configured. Ignoring domains.")
}
}
if viper.IsSet("dns_config.extra_records") {
var extraRecords []tailcfg.DNSRecord
err := viper.UnmarshalKey("dns_config.extra_records", &extraRecords)
if err != nil {
log.Error().
Str("func", "getDNSConfig").
Err(err).
Msgf("Could not parse dns_config.extra_records")
}
dnsConfig.ExtraRecords = extraRecords
}
if viper.IsSet("dns_config.magic_dns") {
dnsConfig.Proxied = viper.GetBool("dns_config.magic_dns")
}
var baseDomain string
if viper.IsSet("dns_config.base_domain") {
baseDomain = viper.GetString("dns_config.base_domain")
} else {
baseDomain = "headscale.net" // does not really matter when MagicDNS is not enabled
}
log.Trace().Interface("dns_config", dnsConfig).Msg("DNS configuration loaded")
return dnsConfig, baseDomain
}
return routes
}
func DNSToTailcfgDNS(dns DNSConfig) *tailcfg.DNSConfig {
cfg := tailcfg.DNSConfig{}
if dns.BaseDomain == "" && dns.MagicDNS {
log.Fatal().Msg("dns.base_domain must be set when using MagicDNS (dns.magic_dns)")
}
cfg.Proxied = dns.MagicDNS
cfg.ExtraRecords = dns.ExtraRecords
cfg.Resolvers = dns.GlobalResolvers()
routes := dns.SplitResolvers()
cfg.Routes = routes
if dns.BaseDomain != "" {
cfg.Domains = []string{dns.BaseDomain}
}
cfg.Domains = append(cfg.Domains, dns.SearchDomains...)
return &cfg
return nil, ""
}
func PrefixV4() (*netip.Prefix, error) {
@@ -685,6 +591,7 @@ func PrefixV4() (*netip.Prefix, error) {
builder := netipx.IPSetBuilder{}
builder.AddPrefix(tsaddr.CGNATRange())
builder.AddPrefix(tsaddr.TailscaleULARange())
ipSet, _ := builder.IPSet()
if !ipSet.ContainsPrefix(prefixV4) {
log.Warn().
@@ -708,6 +615,7 @@ func PrefixV6() (*netip.Prefix, error) {
}
builder := netipx.IPSetBuilder{}
builder.AddPrefix(tsaddr.CGNATRange())
builder.AddPrefix(tsaddr.TailscaleULARange())
ipSet, _ := builder.IPSet()
@@ -732,7 +640,7 @@ func GetHeadscaleConfig() (*Config, error) {
}, nil
}
logConfig := GetLogConfig()
logConfig := GetLogConfig()
zerolog.SetGlobalLevel(logConfig.Level)
prefix4, err := PrefixV4()
@@ -760,11 +668,7 @@ func GetHeadscaleConfig() (*Config, error) {
return nil, fmt.Errorf("config error, prefixes.allocation is set to %s, which is not a valid strategy, allowed options: %s, %s", allocStr, IPAllocationStrategySequential, IPAllocationStrategyRandom)
}
dnsConfig, err := DNS()
if err != nil {
return nil, err
}
dnsConfig, baseDomain := GetDNSConfig()
derpConfig := GetDERPConfig()
logTailConfig := GetLogTailConfig()
randomizeClientPort := viper.GetBool("randomize_client_port")
@@ -782,23 +686,8 @@ func GetHeadscaleConfig() (*Config, error) {
oidcClientSecret = strings.TrimSpace(string(secretBytes))
}
serverURL := viper.GetString("server_url")
// BaseDomain cannot be the same as the server URL.
// This is because Tailscale takes over the domain in BaseDomain,
// causing the headscale server and DERP to be unreachable.
// For Tailscale upstream, the following is true:
// - DERP run on their own domains
// - Control plane runs on login.tailscale.com/controlplane.tailscale.com
// - MagicDNS (BaseDomain) for users is on a *.ts.net domain per tailnet (e.g. tail-scale.ts.net)
//
// TODO(kradalby): remove dnsConfig.UserNameInMagicDNS check when removed.
if !dnsConfig.UserNameInMagicDNS && dnsConfig.BaseDomain != "" && strings.Contains(serverURL, dnsConfig.BaseDomain) {
return nil, errors.New("server_url cannot contain the base_domain, this will cause the headscale server and embedded DERP to become unreachable from the Tailscale node.")
}
return &Config{
ServerURL: serverURL,
ServerURL: viper.GetString("server_url"),
Addr: viper.GetString("listen_addr"),
MetricsAddr: viper.GetString("metrics_listen_addr"),
GRPCAddr: viper.GetString("grpc_listen_addr"),
@@ -812,7 +701,7 @@ func GetHeadscaleConfig() (*Config, error) {
NoisePrivateKeyPath: util.AbsolutePathFromConfigPath(
viper.GetString("noise.private_key_path"),
),
BaseDomain: dnsConfig.BaseDomain,
BaseDomain: baseDomain,
DERP: derpConfig,
@@ -824,8 +713,7 @@ func GetHeadscaleConfig() (*Config, error) {
TLS: GetTLSConfig(),
DNSConfig: DNSToTailcfgDNS(dnsConfig),
DNSUserNameInMagicDNS: dnsConfig.UserNameInMagicDNS,
DNSConfig: dnsConfig,
ACMEEmail: viper.GetString("acme_email"),
ACMEURL: viper.GetString("acme_url"),
@@ -867,7 +755,7 @@ func GetHeadscaleConfig() (*Config, error) {
LogTail: logTailConfig,
RandomizeClientPort: randomizeClientPort,
Policy: GetPolicyConfig(),
ACL: GetACLConfig(),
CLI: CLIConfig{
Address: viper.GetString("cli.address"),
@@ -880,7 +768,6 @@ func GetHeadscaleConfig() (*Config, error) {
// TODO(kradalby): Document these settings when more stable
Tuning: Tuning{
NotifierSendTimeout: viper.GetDuration("tuning.notifier_send_timeout"),
BatchChangeDelay: viper.GetDuration("tuning.batch_change_delay"),
NodeMapSessionBufferedChanSize: viper.GetInt("tuning.node_mapsession_buffered_chan_size"),
},
@@ -890,71 +777,3 @@ func GetHeadscaleConfig() (*Config, error) {
func IsCLIConfigured() bool {
return viper.GetString("cli.address") != "" && viper.GetString("cli.api_key") != ""
}
type deprecator struct {
warns set.Set[string]
fatals set.Set[string]
}
// warnWithAlias will register an alias between the newKey and the oldKey,
// and log a deprecation warning if the oldKey is set.
func (d *deprecator) warnWithAlias(newKey, oldKey string) {
// NOTE: RegisterAlias is called with NEW KEY -> OLD KEY
viper.RegisterAlias(newKey, oldKey)
if viper.IsSet(oldKey) {
d.warns.Add(fmt.Sprintf("The %q configuration key is deprecated. Please use %q instead. %q will be removed in the future.", oldKey, newKey, oldKey))
}
}
// fatal deprecates and adds an entry to the fatal list of options if the oldKey is set.
func (d *deprecator) fatal(newKey, oldKey string) {
if viper.IsSet(oldKey) {
d.fatals.Add(fmt.Sprintf("The %q configuration key is deprecated. Please use %q instead. %q has been removed.", oldKey, newKey, oldKey))
}
}
// fatalIfNewKeyIsNotUsed deprecates and adds an entry to the fatal list of options if the oldKey is set and the new key is _not_ set.
// If the new key is set, a warning is emitted instead.
func (d *deprecator) fatalIfNewKeyIsNotUsed(newKey, oldKey string) {
if viper.IsSet(oldKey) && !viper.IsSet(newKey) {
d.fatals.Add(fmt.Sprintf("The %q configuration key is deprecated. Please use %q instead. %q has been removed.", oldKey, newKey, oldKey))
} else if viper.IsSet(oldKey) {
d.warns.Add(fmt.Sprintf("The %q configuration key is deprecated. Please use %q instead. %q has been removed.", oldKey, newKey, oldKey))
}
}
// warn deprecates and adds an option to log a warning if the oldKey is set.
func (d *deprecator) warnNoAlias(newKey, oldKey string) {
if viper.IsSet(oldKey) {
d.warns.Add(fmt.Sprintf("The %q configuration key is deprecated. Please use %q instead. %q has been removed.", oldKey, newKey, oldKey))
}
}
// warn deprecates and adds an entry to the warn list of options if the oldKey is set.
func (d *deprecator) warn(oldKey string) {
if viper.IsSet(oldKey) {
d.warns.Add(fmt.Sprintf("The %q configuration key is deprecated and has been removed. Please see the changelog for more details.", oldKey))
}
}
func (d *deprecator) String() string {
var b strings.Builder
for _, w := range d.warns.Slice() {
fmt.Fprintf(&b, "WARN: %s\n", w)
}
for _, f := range d.fatals.Slice() {
fmt.Fprintf(&b, "FATAL: %s\n", f)
}
return b.String()
}
func (d *deprecator) Log() {
if len(d.fatals) > 0 {
log.Fatal().Msg("\n" + d.String())
} else if len(d.warns) > 0 {
log.Warn().Msg("\n" + d.String())
}
}

View File

@@ -1,291 +0,0 @@
package types
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"tailscale.com/tailcfg"
"tailscale.com/types/dnstype"
)
func TestReadConfig(t *testing.T) {
tests := []struct {
name string
configPath string
setup func(*testing.T) (any, error)
want any
wantErr string
}{
{
name: "unmarshal-dns-full-config",
configPath: "testdata/dns_full.yaml",
setup: func(t *testing.T) (any, error) {
dns, err := DNS()
if err != nil {
return nil, err
}
return dns, nil
},
want: DNSConfig{
MagicDNS: true,
BaseDomain: "example.com",
Nameservers: Nameservers{
Global: []string{"1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001", "https://dns.nextdns.io/abc123"},
Split: map[string][]string{"darp.headscale.net": {"1.1.1.1", "8.8.8.8"}, "foo.bar.com": {"1.1.1.1"}},
},
ExtraRecords: []tailcfg.DNSRecord{
{Name: "grafana.myvpn.example.com", Type: "A", Value: "100.64.0.3"},
{Name: "prometheus.myvpn.example.com", Type: "A", Value: "100.64.0.4"},
},
SearchDomains: []string{"test.com", "bar.com"},
UserNameInMagicDNS: true,
},
},
{
name: "dns-to-tailcfg.DNSConfig",
configPath: "testdata/dns_full.yaml",
setup: func(t *testing.T) (any, error) {
dns, err := DNS()
if err != nil {
return nil, err
}
return DNSToTailcfgDNS(dns), nil
},
want: &tailcfg.DNSConfig{
Proxied: true,
Domains: []string{"example.com", "test.com", "bar.com"},
Resolvers: []*dnstype.Resolver{
{Addr: "1.1.1.1"},
{Addr: "1.0.0.1"},
{Addr: "2606:4700:4700::1111"},
{Addr: "2606:4700:4700::1001"},
{Addr: "https://dns.nextdns.io/abc123"},
},
Routes: map[string][]*dnstype.Resolver{
"darp.headscale.net": {{Addr: "1.1.1.1"}, {Addr: "8.8.8.8"}},
"foo.bar.com": {{Addr: "1.1.1.1"}},
},
ExtraRecords: []tailcfg.DNSRecord{
{Name: "grafana.myvpn.example.com", Type: "A", Value: "100.64.0.3"},
{Name: "prometheus.myvpn.example.com", Type: "A", Value: "100.64.0.4"},
},
},
},
{
name: "unmarshal-dns-full-no-magic",
configPath: "testdata/dns_full_no_magic.yaml",
setup: func(t *testing.T) (any, error) {
dns, err := DNS()
if err != nil {
return nil, err
}
return dns, nil
},
want: DNSConfig{
MagicDNS: false,
BaseDomain: "example.com",
Nameservers: Nameservers{
Global: []string{"1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001", "https://dns.nextdns.io/abc123"},
Split: map[string][]string{"darp.headscale.net": {"1.1.1.1", "8.8.8.8"}, "foo.bar.com": {"1.1.1.1"}},
},
ExtraRecords: []tailcfg.DNSRecord{
{Name: "grafana.myvpn.example.com", Type: "A", Value: "100.64.0.3"},
{Name: "prometheus.myvpn.example.com", Type: "A", Value: "100.64.0.4"},
},
SearchDomains: []string{"test.com", "bar.com"},
UserNameInMagicDNS: true,
},
},
{
name: "dns-to-tailcfg.DNSConfig",
configPath: "testdata/dns_full_no_magic.yaml",
setup: func(t *testing.T) (any, error) {
dns, err := DNS()
if err != nil {
return nil, err
}
return DNSToTailcfgDNS(dns), nil
},
want: &tailcfg.DNSConfig{
Proxied: false,
Domains: []string{"example.com", "test.com", "bar.com"},
Resolvers: []*dnstype.Resolver{
{Addr: "1.1.1.1"},
{Addr: "1.0.0.1"},
{Addr: "2606:4700:4700::1111"},
{Addr: "2606:4700:4700::1001"},
{Addr: "https://dns.nextdns.io/abc123"},
},
Routes: map[string][]*dnstype.Resolver{
"darp.headscale.net": {{Addr: "1.1.1.1"}, {Addr: "8.8.8.8"}},
"foo.bar.com": {{Addr: "1.1.1.1"}},
},
ExtraRecords: []tailcfg.DNSRecord{
{Name: "grafana.myvpn.example.com", Type: "A", Value: "100.64.0.3"},
{Name: "prometheus.myvpn.example.com", Type: "A", Value: "100.64.0.4"},
},
},
},
{
name: "base-domain-in-server-url-err",
configPath: "testdata/base-domain-in-server-url.yaml",
setup: func(t *testing.T) (any, error) {
return GetHeadscaleConfig()
},
want: nil,
wantErr: "server_url cannot contain the base_domain, this will cause the headscale server and embedded DERP to become unreachable from the Tailscale node.",
},
{
name: "base-domain-not-in-server-url",
configPath: "testdata/base-domain-not-in-server-url.yaml",
setup: func(t *testing.T) (any, error) {
cfg, err := GetHeadscaleConfig()
if err != nil {
return nil, err
}
return map[string]string{
"server_url": cfg.ServerURL,
"base_domain": cfg.BaseDomain,
}, err
},
want: map[string]string{
"server_url": "https://derp.no",
"base_domain": "clients.derp.no",
},
wantErr: "",
},
{
name: "policy-path-is-loaded",
configPath: "testdata/policy-path-is-loaded.yaml",
setup: func(t *testing.T) (any, error) {
cfg, err := GetHeadscaleConfig()
if err != nil {
return nil, err
}
return map[string]string{
"policy.mode": string(cfg.Policy.Mode),
"policy.path": cfg.Policy.Path,
}, err
},
want: map[string]string{
"policy.mode": "file",
"policy.path": "/etc/policy.hujson",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
viper.Reset()
err := LoadConfig(tt.configPath, true)
assert.NoError(t, err)
conf, err := tt.setup(t)
if tt.wantErr != "" {
assert.Equal(t, tt.wantErr, err.Error())
return
}
assert.NoError(t, err)
if diff := cmp.Diff(tt.want, conf); diff != "" {
t.Errorf("ReadConfig() mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestReadConfigFromEnv(t *testing.T) {
tests := []struct {
name string
configEnv map[string]string
setup func(*testing.T) (any, error)
want any
}{
{
name: "test-random-base-settings-with-env",
configEnv: map[string]string{
"HEADSCALE_LOG_LEVEL": "trace",
"HEADSCALE_DATABASE_SQLITE_WRITE_AHEAD_LOG": "false",
"HEADSCALE_PREFIXES_V4": "100.64.0.0/10",
},
setup: func(t *testing.T) (any, error) {
t.Logf("all settings: %#v", viper.AllSettings())
assert.Equal(t, "trace", viper.GetString("log.level"))
assert.Equal(t, "100.64.0.0/10", viper.GetString("prefixes.v4"))
assert.False(t, viper.GetBool("database.sqlite.write_ahead_log"))
return nil, nil
},
want: nil,
},
{
name: "unmarshal-dns-full-config",
configEnv: map[string]string{
"HEADSCALE_DNS_MAGIC_DNS": "true",
"HEADSCALE_DNS_BASE_DOMAIN": "example.com",
"HEADSCALE_DNS_NAMESERVERS_GLOBAL": `1.1.1.1 8.8.8.8`,
"HEADSCALE_DNS_SEARCH_DOMAINS": "test.com bar.com",
"HEADSCALE_DNS_USE_USERNAME_IN_MAGIC_DNS": "true",
// TODO(kradalby): Figure out how to pass these as env vars
// "HEADSCALE_DNS_NAMESERVERS_SPLIT": `{foo.bar.com: ["1.1.1.1"]}`,
// "HEADSCALE_DNS_EXTRA_RECORDS": `[{ name: "prometheus.myvpn.example.com", type: "A", value: "100.64.0.4" }]`,
},
setup: func(t *testing.T) (any, error) {
t.Logf("all settings: %#v", viper.AllSettings())
dns, err := DNS()
if err != nil {
return nil, err
}
return dns, nil
},
want: DNSConfig{
MagicDNS: true,
BaseDomain: "example.com",
Nameservers: Nameservers{
Global: []string{"1.1.1.1", "8.8.8.8"},
Split: map[string][]string{
// "foo.bar.com": {"1.1.1.1"},
},
},
ExtraRecords: []tailcfg.DNSRecord{
// {Name: "prometheus.myvpn.example.com", Type: "A", Value: "100.64.0.4"},
},
SearchDomains: []string{"test.com", "bar.com"},
UserNameInMagicDNS: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for k, v := range tt.configEnv {
t.Setenv(k, v)
}
viper.Reset()
err := LoadConfig("testdata/minimal.yaml", true)
assert.NoError(t, err)
conf, err := tt.setup(t)
assert.NoError(t, err)
if diff := cmp.Diff(tt.want, conf); diff != "" {
t.Errorf("ReadConfig() mismatch (-want +got):\n%s", diff)
}
})
}
}

View File

@@ -43,10 +43,6 @@ func (id NodeID) Uint64() uint64 {
return uint64(id)
}
func (id NodeID) String() string {
return strconv.FormatUint(id.Uint64(), util.Base10)
}
// Node is a Headscale client.
type Node struct {
ID NodeID `gorm:"primary_key"`
@@ -119,7 +115,7 @@ type Node struct {
ForcedTags StringList
// TODO(kradalby): This seems like irrelevant information?
AuthKeyID *uint64 `sql:"DEFAULT:NULL"`
AuthKeyID *uint `sql:"DEFAULT:NULL"`
AuthKey *PreAuthKey `gorm:"constraint:OnDelete:SET NULL;"`
LastSeen *time.Time
@@ -373,7 +369,8 @@ func (node *Node) Proto() *v1.Node {
User: node.User.Proto(),
ForcedTags: node.ForcedTags,
RegisterMethod: node.RegisterMethodToV1Enum(),
// TODO(kradalby): Implement register method enum converter
// RegisterMethod: ,
CreatedAt: timestamppb.New(node.CreatedAt),
}
@@ -393,22 +390,13 @@ func (node *Node) Proto() *v1.Node {
return nodeProto
}
func (node *Node) GetFQDN(cfg *Config, baseDomain string) (string, error) {
if node.GivenName == "" {
return "", fmt.Errorf("failed to create valid FQDN: %w", ErrNodeHasNoGivenName)
}
func (node *Node) GetFQDN(dnsConfig *tailcfg.DNSConfig, baseDomain string) (string, error) {
var hostname string
if dnsConfig != nil && dnsConfig.Proxied { // MagicDNS
if node.GivenName == "" {
return "", fmt.Errorf("failed to create valid FQDN: %w", ErrNodeHasNoGivenName)
}
hostname := node.GivenName
if baseDomain != "" {
hostname = fmt.Sprintf(
"%s.%s",
node.GivenName,
baseDomain,
)
}
if cfg.DNSUserNameInMagicDNS {
if node.User.Name == "" {
return "", fmt.Errorf("failed to create valid FQDN: %w", ErrNodeUserHasNoName)
}
@@ -419,14 +407,15 @@ func (node *Node) GetFQDN(cfg *Config, baseDomain string) (string, error) {
node.User.Name,
baseDomain,
)
}
if len(hostname) > MaxHostnameLength {
return "", fmt.Errorf(
"failed to create valid FQDN (%s): %w",
hostname,
ErrHostnameTooLong,
)
if len(hostname) > MaxHostnameLength {
return "", fmt.Errorf(
"failed to create valid FQDN (%s): %w",
hostname,
ErrHostnameTooLong,
)
}
} else {
hostname = node.GivenName
}
return hostname, nil
@@ -487,19 +476,6 @@ func (node *Node) PeerChangeFromMapRequest(req tailcfg.MapRequest) tailcfg.PeerC
return ret
}
func (node *Node) RegisterMethodToV1Enum() v1.RegisterMethod {
switch node.RegisterMethod {
case "authkey":
return v1.RegisterMethod_REGISTER_METHOD_AUTH_KEY
case "oidc":
return v1.RegisterMethod_REGISTER_METHOD_OIDC
case "cli":
return v1.RegisterMethod_REGISTER_METHOD_CLI
default:
return v1.RegisterMethod_REGISTER_METHOD_UNSPECIFIED
}
}
// ApplyPeerChange takes a PeerChange struct and updates the node.
func (node *Node) ApplyPeerChange(change *tailcfg.PeerChange) {
if change.Key != nil {

View File

@@ -6,7 +6,6 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/util"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
@@ -127,87 +126,11 @@ func TestNodeFQDN(t *testing.T) {
tests := []struct {
name string
node Node
cfg Config
dns tailcfg.DNSConfig
domain string
want string
wantErr string
}{
{
name: "all-set-with-username",
node: Node{
GivenName: "test",
User: User{
Name: "user",
},
},
cfg: Config{
DNSConfig: &tailcfg.DNSConfig{
Proxied: true,
},
DNSUserNameInMagicDNS: true,
},
domain: "example.com",
want: "test.user.example.com",
},
{
name: "no-given-name-with-username",
node: Node{
User: User{
Name: "user",
},
},
cfg: Config{
DNSConfig: &tailcfg.DNSConfig{
Proxied: true,
},
DNSUserNameInMagicDNS: true,
},
domain: "example.com",
wantErr: "failed to create valid FQDN: node has no given name",
},
{
name: "no-user-name-with-username",
node: Node{
GivenName: "test",
User: User{},
},
cfg: Config{
DNSConfig: &tailcfg.DNSConfig{
Proxied: true,
},
DNSUserNameInMagicDNS: true,
},
domain: "example.com",
wantErr: "failed to create valid FQDN: node user has no name",
},
{
name: "no-magic-dns-with-username",
node: Node{
GivenName: "test",
User: User{
Name: "user",
},
},
cfg: Config{
DNSConfig: &tailcfg.DNSConfig{
Proxied: false,
},
DNSUserNameInMagicDNS: true,
},
domain: "example.com",
want: "test.user.example.com",
},
{
name: "no-dnsconfig-with-username",
node: Node{
GivenName: "test",
User: User{
Name: "user",
},
},
domain: "example.com",
want: "test.example.com",
},
{
name: "all-set",
node: Node{
@@ -216,14 +139,11 @@ func TestNodeFQDN(t *testing.T) {
Name: "user",
},
},
cfg: Config{
DNSConfig: &tailcfg.DNSConfig{
Proxied: true,
},
DNSUserNameInMagicDNS: false,
dns: tailcfg.DNSConfig{
Proxied: true,
},
domain: "example.com",
want: "test.example.com",
want: "test.user.example.com",
},
{
name: "no-given-name",
@@ -232,11 +152,8 @@ func TestNodeFQDN(t *testing.T) {
Name: "user",
},
},
cfg: Config{
DNSConfig: &tailcfg.DNSConfig{
Proxied: true,
},
DNSUserNameInMagicDNS: false,
dns: tailcfg.DNSConfig{
Proxied: true,
},
domain: "example.com",
wantErr: "failed to create valid FQDN: node has no given name",
@@ -247,14 +164,11 @@ func TestNodeFQDN(t *testing.T) {
GivenName: "test",
User: User{},
},
cfg: Config{
DNSConfig: &tailcfg.DNSConfig{
Proxied: true,
},
DNSUserNameInMagicDNS: false,
dns: tailcfg.DNSConfig{
Proxied: true,
},
domain: "example.com",
want: "test.example.com",
domain: "example.com",
wantErr: "failed to create valid FQDN: node user has no name",
},
{
name: "no-magic-dns",
@@ -264,14 +178,11 @@ func TestNodeFQDN(t *testing.T) {
Name: "user",
},
},
cfg: Config{
DNSConfig: &tailcfg.DNSConfig{
Proxied: false,
},
DNSUserNameInMagicDNS: false,
dns: tailcfg.DNSConfig{
Proxied: false,
},
domain: "example.com",
want: "test.example.com",
want: "test",
},
{
name: "no-dnsconfig",
@@ -282,13 +193,13 @@ func TestNodeFQDN(t *testing.T) {
},
},
domain: "example.com",
want: "test.example.com",
want: "test",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := tc.node.GetFQDN(&tc.cfg, tc.domain)
got, err := tc.node.GetFQDN(&tc.dns, tc.domain)
if (err != nil) && (err.Error() != tc.wantErr) {
t.Errorf("GetFQDN() error = %s, wantErr %s", err, tc.wantErr)
@@ -541,53 +452,3 @@ func TestApplyPeerChange(t *testing.T) {
})
}
}
func TestNodeRegisterMethodToV1Enum(t *testing.T) {
tests := []struct {
name string
node Node
want v1.RegisterMethod
}{
{
name: "authkey",
node: Node{
ID: 1,
RegisterMethod: util.RegisterMethodAuthKey,
},
want: v1.RegisterMethod_REGISTER_METHOD_AUTH_KEY,
},
{
name: "oidc",
node: Node{
ID: 1,
RegisterMethod: util.RegisterMethodOIDC,
},
want: v1.RegisterMethod_REGISTER_METHOD_OIDC,
},
{
name: "cli",
node: Node{
ID: 1,
RegisterMethod: util.RegisterMethodCLI,
},
want: v1.RegisterMethod_REGISTER_METHOD_CLI,
},
{
name: "unknown",
node: Node{
ID: 0,
},
want: v1.RegisterMethod_REGISTER_METHOD_UNSPECIFIED,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.node.RegisterMethodToV1Enum()
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("RegisterMethodToV1Enum() unexpected result (-want +got):\n%s", diff)
}
})
}
}

View File

@@ -1,20 +0,0 @@
package types
import (
"errors"
"gorm.io/gorm"
)
var (
ErrPolicyNotFound = errors.New("acl policy not found")
ErrPolicyUpdateIsDisabled = errors.New("update is disabled for modes other than 'database'")
)
// Policy represents a policy in the database.
type Policy struct {
gorm.Model
// Data contains the policy in HuJSON format.
Data string
}

View File

@@ -1,16 +0,0 @@
noise:
private_key_path: "private_key.pem"
prefixes:
v6: fd7a:115c:a1e0::/48
v4: 100.64.0.0/10
database:
type: sqlite3
server_url: "https://derp.no"
dns:
magic_dns: true
base_domain: derp.no
use_username_in_magic_dns: false

View File

@@ -1,16 +0,0 @@
noise:
private_key_path: "private_key.pem"
prefixes:
v6: fd7a:115c:a1e0::/48
v4: 100.64.0.0/10
database:
type: sqlite3
server_url: "https://derp.no"
dns:
magic_dns: true
base_domain: clients.derp.no
use_username_in_magic_dns: false

View File

@@ -1,37 +0,0 @@
# minimum to not fatal
noise:
private_key_path: "private_key.pem"
server_url: "https://derp.no"
dns:
magic_dns: true
base_domain: example.com
nameservers:
global:
- 1.1.1.1
- 1.0.0.1
- 2606:4700:4700::1111
- 2606:4700:4700::1001
- https://dns.nextdns.io/abc123
split:
foo.bar.com:
- 1.1.1.1
darp.headscale.net:
- 1.1.1.1
- 8.8.8.8
search_domains:
- test.com
- bar.com
extra_records:
- name: "grafana.myvpn.example.com"
type: "A"
value: "100.64.0.3"
# you can also put it in one line
- { name: "prometheus.myvpn.example.com", type: "A", value: "100.64.0.4" }
use_username_in_magic_dns: true

View File

@@ -1,37 +0,0 @@
# minimum to not fatal
noise:
private_key_path: "private_key.pem"
server_url: "https://derp.no"
dns:
magic_dns: false
base_domain: example.com
nameservers:
global:
- 1.1.1.1
- 1.0.0.1
- 2606:4700:4700::1111
- 2606:4700:4700::1001
- https://dns.nextdns.io/abc123
split:
foo.bar.com:
- 1.1.1.1
darp.headscale.net:
- 1.1.1.1
- 8.8.8.8
search_domains:
- test.com
- bar.com
extra_records:
- name: "grafana.myvpn.example.com"
type: "A"
value: "100.64.0.3"
# you can also put it in one line
- { name: "prometheus.myvpn.example.com", type: "A", value: "100.64.0.4" }
use_username_in_magic_dns: true

View File

@@ -1,3 +0,0 @@
noise:
private_key_path: "private_key.pem"
server_url: "https://derp.no"

View File

@@ -1,18 +0,0 @@
noise:
private_key_path: "private_key.pem"
prefixes:
v6: fd7a:115c:a1e0::/48
v4: 100.64.0.0/10
database:
type: sqlite3
server_url: "https://derp.no"
acl_policy_path: "/etc/acl_policy.yaml"
policy:
type: file
path: "/etc/policy.hujson"
dns.magic_dns: false

View File

@@ -19,46 +19,32 @@ type User struct {
Name string `gorm:"unique"`
}
// TODO(kradalby): See if we can fill in Gravatar here
func (u *User) profilePicURL() string {
return ""
}
func (u *User) TailscaleUser() *tailcfg.User {
func (n *User) TailscaleUser() *tailcfg.User {
user := tailcfg.User{
ID: tailcfg.UserID(u.ID),
LoginName: u.Name,
DisplayName: u.Name,
ProfilePicURL: u.profilePicURL(),
ID: tailcfg.UserID(n.ID),
LoginName: n.Name,
DisplayName: n.Name,
// TODO(kradalby): See if we can fill in Gravatar here
ProfilePicURL: "",
Logins: []tailcfg.LoginID{},
Created: u.CreatedAt,
Created: n.CreatedAt,
}
return &user
}
func (u *User) TailscaleLogin() *tailcfg.Login {
func (n *User) TailscaleLogin() *tailcfg.Login {
login := tailcfg.Login{
ID: tailcfg.LoginID(u.ID),
// TODO(kradalby): this should reflect registration method.
Provider: "",
LoginName: u.Name,
DisplayName: u.Name,
ProfilePicURL: u.profilePicURL(),
ID: tailcfg.LoginID(n.ID),
LoginName: n.Name,
DisplayName: n.Name,
// TODO(kradalby): See if we can fill in Gravatar here
ProfilePicURL: "",
}
return &login
}
func (u *User) TailscaleUserProfile() tailcfg.UserProfile {
return tailcfg.UserProfile{
ID: tailcfg.UserID(u.ID),
LoginName: u.Name,
DisplayName: u.Name,
ProfilePicURL: u.profilePicURL(),
}
}
func (n *User) Proto() *v1.User {
return &v1.User{
Id: strconv.FormatUint(uint64(n.ID), util.Base10),

View File

@@ -1,14 +1,7 @@
package util
import (
"context"
"errors"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"gorm.io/gorm"
gormLogger "gorm.io/gorm/logger"
"tailscale.com/types/logger"
)
@@ -21,71 +14,3 @@ func TSLogfWrapper() logger.Logf {
log.Debug().Caller().Msgf(format, args...)
}
}
type DBLogWrapper struct {
Logger *zerolog.Logger
Level zerolog.Level
Event *zerolog.Event
SlowThreshold time.Duration
SkipErrRecordNotFound bool
ParameterizedQueries bool
}
func NewDBLogWrapper(origin *zerolog.Logger, slowThreshold time.Duration, skipErrRecordNotFound bool, parameterizedQueries bool) *DBLogWrapper {
l := &DBLogWrapper{
Logger: origin,
Level: origin.GetLevel(),
SlowThreshold: slowThreshold,
SkipErrRecordNotFound: skipErrRecordNotFound,
ParameterizedQueries: parameterizedQueries,
}
return l
}
type DBLogWrapperOption func(*DBLogWrapper)
func (l *DBLogWrapper) LogMode(gormLogger.LogLevel) gormLogger.Interface {
return l
}
func (l *DBLogWrapper) Info(ctx context.Context, msg string, data ...interface{}) {
l.Logger.Info().Msgf(msg, data...)
}
func (l *DBLogWrapper) Warn(ctx context.Context, msg string, data ...interface{}) {
l.Logger.Warn().Msgf(msg, data...)
}
func (l *DBLogWrapper) Error(ctx context.Context, msg string, data ...interface{}) {
l.Logger.Error().Msgf(msg, data...)
}
func (l *DBLogWrapper) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
elapsed := time.Since(begin)
sql, rowsAffected := fc()
fields := map[string]interface{}{
"duration": elapsed,
"sql": sql,
"rowsAffected": rowsAffected,
}
if err != nil && !(errors.Is(err, gorm.ErrRecordNotFound) && l.SkipErrRecordNotFound) {
l.Logger.Error().Err(err).Fields(fields).Msgf("")
return
}
if l.SlowThreshold != 0 && elapsed > l.SlowThreshold {
l.Logger.Warn().Fields(fields).Msgf("")
return
}
l.Logger.Debug().Fields(fields).Msgf("")
}
func (l *DBLogWrapper) ParamsFilter(ctx context.Context, sql string, params ...interface{}) (string, []interface{}) {
if l.ParameterizedQueries {
return sql, nil
}
return sql, params
}

View File

@@ -56,6 +56,16 @@ func GenerateRandomStringDNSSafe(size int) (string, error) {
return str[:size], nil
}
func IsStringInSlice(slice []string, str string) bool {
for _, s := range slice {
if s == str {
return true
}
}
return false
}
func TailNodesToString(nodes []*tailcfg.Node) string {
temp := make([]string, len(nodes))

View File

@@ -1,13 +1,11 @@
package integration
import (
"encoding/json"
"fmt"
"net/netip"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/juanfont/headscale/hscontrol/policy"
"github.com/juanfont/headscale/integration/hsic"
"github.com/juanfont/headscale/integration/tsic"
@@ -1014,156 +1012,3 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) {
})
}
}
func TestPolicyUpdateWhileRunningWithCLIInDatabase(t *testing.T) {
IntegrationSkip(t)
t.Parallel()
scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err)
defer scenario.Shutdown()
spec := map[string]int{
"user1": 1,
"user2": 1,
}
err = scenario.CreateHeadscaleEnv(spec,
[]tsic.Option{
// Alpine containers dont have ip6tables set up, which causes
// tailscaled to stop configuring the wgengine, causing it
// to not configure DNS.
tsic.WithNetfilter("off"),
tsic.WithDockerEntrypoint([]string{
"/bin/sh",
"-c",
"/bin/sleep 3 ; apk add python3 curl ; update-ca-certificates ; python3 -m http.server --bind :: 80 & tailscaled --tun=tsdev",
}),
tsic.WithDockerWorkdir("/"),
},
hsic.WithTestName("policyreload"),
hsic.WithConfigEnv(map[string]string{
"HEADSCALE_POLICY_MODE": "database",
}),
)
assertNoErr(t, err)
_, err = scenario.ListTailscaleClientsFQDNs()
assertNoErrListFQDN(t, err)
err = scenario.WaitForTailscaleSync()
assertNoErrSync(t, err)
user1Clients, err := scenario.ListTailscaleClients("user1")
assertNoErr(t, err)
user2Clients, err := scenario.ListTailscaleClients("user2")
assertNoErr(t, err)
all := append(user1Clients, user2Clients...)
// Initially all nodes can reach each other
for _, client := range all {
for _, peer := range all {
if client.ID() == peer.ID() {
continue
}
fqdn, err := peer.FQDN()
assertNoErr(t, err)
url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url)
result, err := client.Curl(url)
assert.Len(t, result, 13)
assertNoErr(t, err)
}
}
headscale, err := scenario.Headscale()
assertNoErr(t, err)
p := policy.ACLPolicy{
ACLs: []policy.ACL{
{
Action: "accept",
Sources: []string{"user1"},
Destinations: []string{"user2:*"},
},
},
Hosts: policy.Hosts{},
}
pBytes, _ := json.Marshal(p)
policyFilePath := "/etc/headscale/policy.json"
err = headscale.WriteFile(policyFilePath, pBytes)
assertNoErr(t, err)
// No policy is present at this time.
// Add a new policy from a file.
_, err = headscale.Execute(
[]string{
"headscale",
"policy",
"set",
"-f",
policyFilePath,
},
)
assertNoErr(t, err)
// Get the current policy and check
// if it is the same as the one we set.
var output *policy.ACLPolicy
err = executeAndUnmarshal(
headscale,
[]string{
"headscale",
"policy",
"get",
"--output",
"json",
},
&output,
)
assertNoErr(t, err)
assert.Len(t, output.ACLs, 1)
if diff := cmp.Diff(p, *output); diff != "" {
t.Errorf("unexpected policy(-want +got):\n%s", diff)
}
// Test that user1 can visit all user2
for _, client := range user1Clients {
for _, peer := range user2Clients {
fqdn, err := peer.FQDN()
assertNoErr(t, err)
url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url)
result, err := client.Curl(url)
assert.Len(t, result, 13)
assertNoErr(t, err)
}
}
// Test that user2 _cannot_ visit user1
for _, client := range user2Clients {
for _, peer := range user1Clients {
fqdn, err := peer.FQDN()
assertNoErr(t, err)
url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url)
result, err := client.Curl(url)
assert.Empty(t, result)
assert.Error(t, err)
}
}
}

View File

@@ -480,7 +480,7 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
assert.Nil(t, err)
assert.Len(t, listNodes, 1)
assert.Equal(t, "user2", listNodes[0].GetUser().GetName())
assert.Equal(t, "user2", listNodes[0].User.Name)
}
func TestApiKeyCommand(t *testing.T) {
@@ -1596,83 +1596,3 @@ func TestNodeMoveCommand(t *testing.T) {
assert.Equal(t, node.GetUser().GetName(), "old-user")
}
func TestPolicyCommand(t *testing.T) {
IntegrationSkip(t)
t.Parallel()
scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err)
defer scenario.Shutdown()
spec := map[string]int{
"policy-user": 0,
}
err = scenario.CreateHeadscaleEnv(
spec,
[]tsic.Option{},
hsic.WithTestName("clins"),
hsic.WithConfigEnv(map[string]string{
"HEADSCALE_POLICY_MODE": "database",
}),
)
assertNoErr(t, err)
headscale, err := scenario.Headscale()
assertNoErr(t, err)
p := policy.ACLPolicy{
ACLs: []policy.ACL{
{
Action: "accept",
Sources: []string{"*"},
Destinations: []string{"*:*"},
},
},
TagOwners: map[string][]string{
"tag:exists": {"policy-user"},
},
}
pBytes, _ := json.Marshal(p)
policyFilePath := "/etc/headscale/policy.json"
err = headscale.WriteFile(policyFilePath, pBytes)
assertNoErr(t, err)
// No policy is present at this time.
// Add a new policy from a file.
_, err = headscale.Execute(
[]string{
"headscale",
"policy",
"set",
"-f",
policyFilePath,
},
)
assertNoErr(t, err)
// Get the current policy and check
// if it is the same as the one we set.
var output *policy.ACLPolicy
err = executeAndUnmarshal(
headscale,
[]string{
"headscale",
"policy",
"get",
"--output",
"json",
},
&output,
)
assertNoErr(t, err)
assert.Len(t, output.TagOwners, 1)
assert.Len(t, output.ACLs, 1)
assert.Equal(t, output.TagOwners["tag:exists"], []string{"policy-user"})
}

View File

@@ -10,7 +10,6 @@ type ControlServer interface {
SaveLog(string) error
SaveProfile(string) error
Execute(command []string) (string, error)
WriteFile(path string, content []byte) error
ConnectToNetwork(network *dockertest.Network) error
GetHealthEndpoint() string
GetEndpoint() string

View File

@@ -1,246 +0,0 @@
package integration
import (
"fmt"
"strings"
"testing"
"time"
"github.com/juanfont/headscale/integration/hsic"
"github.com/juanfont/headscale/integration/tsic"
"github.com/stretchr/testify/assert"
)
func TestResolveMagicDNS(t *testing.T) {
IntegrationSkip(t)
t.Parallel()
scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err)
defer scenario.Shutdown()
spec := map[string]int{
"magicdns1": len(MustTestVersions),
"magicdns2": len(MustTestVersions),
}
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("magicdns"))
assertNoErrHeadscaleEnv(t, err)
allClients, err := scenario.ListTailscaleClients()
assertNoErrListClients(t, err)
err = scenario.WaitForTailscaleSync()
assertNoErrSync(t, err)
// assertClientsState(t, allClients)
// Poor mans cache
_, err = scenario.ListTailscaleClientsFQDNs()
assertNoErrListFQDN(t, err)
_, err = scenario.ListTailscaleClientsIPs()
assertNoErrListClientIPs(t, err)
for _, client := range allClients {
for _, peer := range allClients {
// It is safe to ignore this error as we handled it when caching it
peerFQDN, _ := peer.FQDN()
assert.Equal(t, fmt.Sprintf("%s.headscale.net", peer.Hostname()), peerFQDN)
command := []string{
"tailscale",
"ip", peerFQDN,
}
result, _, err := client.Execute(command)
if err != nil {
t.Fatalf(
"failed to execute resolve/ip command %s from %s: %s",
peerFQDN,
client.Hostname(),
err,
)
}
ips, err := peer.IPs()
if err != nil {
t.Fatalf(
"failed to get ips for %s: %s",
peer.Hostname(),
err,
)
}
for _, ip := range ips {
if !strings.Contains(result, ip.String()) {
t.Fatalf("ip %s is not found in \n%s\n", ip.String(), result)
}
}
}
}
}
// TestValidateResolvConf validates that the resolv.conf file
// ends up as expected in our Tailscale containers.
// All the containers are based on Alpine, meaning Tailscale
// will overwrite the resolv.conf file.
// On other platform, Tailscale will integrate with a dns manager
// if available (like Systemd-Resolved).
func TestValidateResolvConf(t *testing.T) {
IntegrationSkip(t)
resolvconf := func(conf string) string {
return strings.ReplaceAll(`# resolv.conf(5) file generated by tailscale
# For more info, see https://tailscale.com/s/resolvconf-overwrite
# DO NOT EDIT THIS FILE BY HAND -- CHANGES WILL BE OVERWRITTEN
`+conf, "\t", "")
}
tests := []struct {
name string
conf map[string]string
wantConfCompareFunc func(*testing.T, string)
}{
// New config
{
name: "no-config",
conf: map[string]string{
"HEADSCALE_DNS_BASE_DOMAIN": "",
"HEADSCALE_DNS_MAGIC_DNS": "false",
"HEADSCALE_DNS_NAMESERVERS_GLOBAL": "",
},
wantConfCompareFunc: func(t *testing.T, got string) {
assert.NotContains(t, got, "100.100.100.100")
},
},
{
name: "global-only",
conf: map[string]string{
"HEADSCALE_DNS_BASE_DOMAIN": "",
"HEADSCALE_DNS_MAGIC_DNS": "false",
"HEADSCALE_DNS_NAMESERVERS_GLOBAL": "8.8.8.8 1.1.1.1",
},
wantConfCompareFunc: func(t *testing.T, got string) {
want := resolvconf(`
nameserver 100.100.100.100
`)
assert.Equal(t, want, got)
},
},
{
name: "base-integration-config",
conf: map[string]string{
"HEADSCALE_DNS_BASE_DOMAIN": "very-unique-domain.net",
},
wantConfCompareFunc: func(t *testing.T, got string) {
want := resolvconf(`
nameserver 100.100.100.100
search very-unique-domain.net
`)
assert.Equal(t, want, got)
},
},
{
name: "base-magic-dns-off",
conf: map[string]string{
"HEADSCALE_DNS_MAGIC_DNS": "false",
"HEADSCALE_DNS_BASE_DOMAIN": "very-unique-domain.net",
},
wantConfCompareFunc: func(t *testing.T, got string) {
want := resolvconf(`
nameserver 100.100.100.100
search very-unique-domain.net
`)
assert.Equal(t, want, got)
},
},
{
name: "base-extra-search-domains",
conf: map[string]string{
"HEADSCALE_DNS_SEARCH_DOMAINS": "test1.no test2.no",
"HEADSCALE_DNS_BASE_DOMAIN": "with-local-dns.net",
},
wantConfCompareFunc: func(t *testing.T, got string) {
want := resolvconf(`
nameserver 100.100.100.100
search with-local-dns.net test1.no test2.no
`)
assert.Equal(t, want, got)
},
},
{
name: "base-nameservers-split",
conf: map[string]string{
"HEADSCALE_DNS_NAMESERVERS_SPLIT": `{foo.bar.com: ["1.1.1.1"]}`,
"HEADSCALE_DNS_BASE_DOMAIN": "with-local-dns.net",
},
wantConfCompareFunc: func(t *testing.T, got string) {
want := resolvconf(`
nameserver 100.100.100.100
search with-local-dns.net
`)
assert.Equal(t, want, got)
},
},
{
name: "base-full-no-magic",
conf: map[string]string{
"HEADSCALE_DNS_MAGIC_DNS": "false",
"HEADSCALE_DNS_BASE_DOMAIN": "all-of.it",
"HEADSCALE_DNS_NAMESERVERS_GLOBAL": `8.8.8.8`,
"HEADSCALE_DNS_SEARCH_DOMAINS": "test1.no test2.no",
// TODO(kradalby): this currently isnt working, need to fix it
// "HEADSCALE_DNS_NAMESERVERS_SPLIT": `{foo.bar.com: ["1.1.1.1"]}`,
// "HEADSCALE_DNS_EXTRA_RECORDS": `[{ name: "prometheus.myvpn.example.com", type: "A", value: "100.64.0.4" }]`,
},
wantConfCompareFunc: func(t *testing.T, got string) {
want := resolvconf(`
nameserver 100.100.100.100
search all-of.it test1.no test2.no
`)
assert.Equal(t, want, got)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err)
defer scenario.Shutdown()
spec := map[string]int{
"resolvconf1": 3,
"resolvconf2": 3,
}
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("resolvconf"), hsic.WithConfigEnv(tt.conf))
assertNoErrHeadscaleEnv(t, err)
allClients, err := scenario.ListTailscaleClients()
assertNoErrListClients(t, err)
err = scenario.WaitForTailscaleSync()
assertNoErrSync(t, err)
// Poor mans cache
_, err = scenario.ListTailscaleClientsFQDNs()
assertNoErrListFQDN(t, err)
_, err = scenario.ListTailscaleClientsIPs()
assertNoErrListClientIPs(t, err)
time.Sleep(30 * time.Second)
for _, client := range allClients {
b, err := client.ReadFile("/etc/resolv.conf")
assertNoErr(t, err)
t.Logf("comparing resolv conf of %s", client.Hostname())
tt.wantConfCompareFunc(t, string(b))
}
})
}
}

View File

@@ -4,9 +4,7 @@ import (
"fmt"
"log"
"net/url"
"strings"
"testing"
"time"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/juanfont/headscale/integration/dockertestutil"
@@ -35,7 +33,8 @@ func TestDERPServerScenario(t *testing.T) {
defer scenario.Shutdown()
spec := map[string]int{
"user1": len(MustTestVersions),
"user1": 10,
// "user1": len(MustTestVersions),
}
err = scenario.CreateHeadscaleEnv(
@@ -45,75 +44,24 @@ func TestDERPServerScenario(t *testing.T) {
hsic.WithEmbeddedDERPServerOnly(),
hsic.WithTLS(),
hsic.WithHostnameAsServerURL(),
hsic.WithConfigEnv(map[string]string{
"HEADSCALE_DERP_AUTO_UPDATE_ENABLED": "true",
"HEADSCALE_DERP_UPDATE_FREQUENCY": "10s",
}),
)
assertNoErrHeadscaleEnv(t, err)
allClients, err := scenario.ListTailscaleClients()
assertNoErrListClients(t, err)
allIps, err := scenario.ListTailscaleClientsIPs()
assertNoErrListClientIPs(t, err)
err = scenario.WaitForTailscaleSync()
assertNoErrSync(t, err)
allHostnames, err := scenario.ListTailscaleClientsFQDNs()
assertNoErrListFQDN(t, err)
for _, client := range allClients {
status, err := client.Status()
assertNoErr(t, err)
for _, health := range status.Health {
if strings.Contains(health, "could not connect to any relay server") {
t.Errorf("expected to be connected to derp, found: %s", health)
}
if strings.Contains(health, "could not connect to the 'Headscale Embedded DERP' relay server.") {
t.Errorf("expected to be connected to derp, found: %s", health)
}
}
}
success := pingDerpAllHelper(t, allClients, allHostnames)
for _, client := range allClients {
status, err := client.Status()
assertNoErr(t, err)
for _, health := range status.Health {
if strings.Contains(health, "could not connect to any relay server") {
t.Errorf("expected to be connected to derp, found: %s", health)
}
if strings.Contains(health, "could not connect to the 'Headscale Embedded DERP' relay server.") {
t.Errorf("expected to be connected to derp, found: %s", health)
}
}
}
t.Logf("Run 1: %d successful pings out of %d", success, len(allClients)*len(allHostnames))
// Let the DERP updater run a couple of times to ensure it does not
// break the DERPMap.
time.Sleep(30 * time.Second)
success = pingDerpAllHelper(t, allClients, allHostnames)
for _, client := range allClients {
status, err := client.Status()
assertNoErr(t, err)
for _, health := range status.Health {
if strings.Contains(health, "could not connect to any relay server") {
t.Errorf("expected to be connected to derp, found: %s", health)
}
if strings.Contains(health, "could not connect to the 'Headscale Embedded DERP' relay server.") {
t.Errorf("expected to be connected to derp, found: %s", health)
}
}
}
t.Logf("Run2: %d successful pings out of %d", success, len(allClients)*len(allHostnames))
t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps))
}
func (s *EmbeddedDERPServerScenario) CreateHeadscaleEnv(

View File

@@ -1,7 +1,6 @@
package integration
import (
"context"
"encoding/json"
"fmt"
"net/netip"
@@ -16,7 +15,6 @@ import (
"github.com/rs/zerolog/log"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"golang.org/x/sync/errgroup"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/types/key"
)
@@ -297,122 +295,6 @@ func TestEphemeral(t *testing.T) {
}
}
// TestEphemeral2006DeletedTooQuickly verifies that ephemeral nodes are not
// deleted by accident if they are still online and active.
func TestEphemeral2006DeletedTooQuickly(t *testing.T) {
IntegrationSkip(t)
t.Parallel()
scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err)
defer scenario.Shutdown()
spec := map[string]int{
"user1": len(MustTestVersions),
"user2": len(MustTestVersions),
}
headscale, err := scenario.Headscale(
hsic.WithTestName("ephemeral2006"),
hsic.WithConfigEnv(map[string]string{
"HEADSCALE_EPHEMERAL_NODE_INACTIVITY_TIMEOUT": "1m6s",
}),
)
assertNoErrHeadscaleEnv(t, err)
for userName, clientCount := range spec {
err = scenario.CreateUser(userName)
if err != nil {
t.Fatalf("failed to create user %s: %s", userName, err)
}
err = scenario.CreateTailscaleNodesInUser(userName, "all", clientCount, []tsic.Option{}...)
if err != nil {
t.Fatalf("failed to create tailscale nodes in user %s: %s", userName, err)
}
key, err := scenario.CreatePreAuthKey(userName, true, true)
if err != nil {
t.Fatalf("failed to create pre-auth key for user %s: %s", userName, err)
}
err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey())
if err != nil {
t.Fatalf("failed to run tailscale up for user %s: %s", userName, err)
}
}
err = scenario.WaitForTailscaleSync()
assertNoErrSync(t, err)
allClients, err := scenario.ListTailscaleClients()
assertNoErrListClients(t, err)
allIps, err := scenario.ListTailscaleClientsIPs()
assertNoErrListClientIPs(t, err)
allAddrs := lo.Map(allIps, func(x netip.Addr, index int) string {
return x.String()
})
// All ephemeral nodes should be online and reachable.
success := pingAllHelper(t, allClients, allAddrs)
t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps))
// Take down all clients, this should start an expiry timer for each.
for _, client := range allClients {
err := client.Down()
if err != nil {
t.Fatalf("failed to take down client %s: %s", client.Hostname(), err)
}
}
// Wait a bit and bring up the clients again before the expiry
// time of the ephemeral nodes.
// Nodes should be able to reconnect and work fine.
time.Sleep(30 * time.Second)
for _, client := range allClients {
err := client.Up()
if err != nil {
t.Fatalf("failed to take down client %s: %s", client.Hostname(), err)
}
}
err = scenario.WaitForTailscaleSync()
assertNoErrSync(t, err)
success = pingAllHelper(t, allClients, allAddrs)
t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps))
// Take down all clients, this should start an expiry timer for each.
for _, client := range allClients {
err := client.Down()
if err != nil {
t.Fatalf("failed to take down client %s: %s", client.Hostname(), err)
}
}
// This time wait for all of the nodes to expire and check that they are no longer
// registered.
time.Sleep(3 * time.Minute)
for userName := range spec {
nodes, err := headscale.ListNodesInUser(userName)
if err != nil {
log.Error().
Err(err).
Str("user", userName).
Msg("Error listing nodes in user")
return
}
if len(nodes) != 0 {
t.Fatalf("expected no nodes, got %d in user %s", len(nodes), userName)
}
}
}
func TestPingAllByHostname(t *testing.T) {
IntegrationSkip(t)
t.Parallel()
@@ -453,14 +335,14 @@ func TestTaildrop(t *testing.T) {
IntegrationSkip(t)
t.Parallel()
retry := func(times int, sleepInterval time.Duration, doWork func() error) error {
retry := func(times int, sleepInverval time.Duration, doWork func() error) error {
var err error
for attempts := 0; attempts < times; attempts++ {
err = doWork()
if err == nil {
return nil
}
time.Sleep(sleepInterval)
time.Sleep(sleepInverval)
}
return err
@@ -623,6 +505,74 @@ func TestTaildrop(t *testing.T) {
}
}
func TestResolveMagicDNS(t *testing.T) {
IntegrationSkip(t)
t.Parallel()
scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err)
defer scenario.Shutdown()
spec := map[string]int{
"magicdns1": len(MustTestVersions),
"magicdns2": len(MustTestVersions),
}
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("magicdns"))
assertNoErrHeadscaleEnv(t, err)
allClients, err := scenario.ListTailscaleClients()
assertNoErrListClients(t, err)
err = scenario.WaitForTailscaleSync()
assertNoErrSync(t, err)
// assertClientsState(t, allClients)
// Poor mans cache
_, err = scenario.ListTailscaleClientsFQDNs()
assertNoErrListFQDN(t, err)
_, err = scenario.ListTailscaleClientsIPs()
assertNoErrListClientIPs(t, err)
for _, client := range allClients {
for _, peer := range allClients {
// It is safe to ignore this error as we handled it when caching it
peerFQDN, _ := peer.FQDN()
command := []string{
"tailscale",
"ip", peerFQDN,
}
result, _, err := client.Execute(command)
if err != nil {
t.Fatalf(
"failed to execute resolve/ip command %s from %s: %s",
peerFQDN,
client.Hostname(),
err,
)
}
ips, err := peer.IPs()
if err != nil {
t.Fatalf(
"failed to get ips for %s: %s",
peer.Hostname(),
err,
)
}
for _, ip := range ips {
if !strings.Contains(result, ip.String()) {
t.Fatalf("ip %s is not found in \n%s\n", ip.String(), result)
}
}
}
}
}
func TestExpireNode(t *testing.T) {
IntegrationSkip(t)
t.Parallel()
@@ -843,7 +793,7 @@ func TestNodeOnlineStatus(t *testing.T) {
continue
}
// All peers of this nodes are reporting to be
// All peers of this nodess are reporting to be
// connected to the control server
assert.Truef(
t,
@@ -879,10 +829,24 @@ func TestPingAllByIPManyUpDown(t *testing.T) {
"user2": len(MustTestVersions),
}
headscaleConfig := map[string]string{
"HEADSCALE_DERP_URLS": "",
"HEADSCALE_DERP_SERVER_ENABLED": "true",
"HEADSCALE_DERP_SERVER_REGION_ID": "999",
"HEADSCALE_DERP_SERVER_REGION_CODE": "headscale",
"HEADSCALE_DERP_SERVER_REGION_NAME": "Headscale Embedded DERP",
"HEADSCALE_DERP_SERVER_STUN_LISTEN_ADDR": "0.0.0.0:3478",
"HEADSCALE_DERP_SERVER_PRIVATE_KEY_PATH": "/tmp/derp.key",
// Envknob for enabling DERP debug logs
"DERP_DEBUG_LOGS": "true",
"DERP_PROBER_DEBUG_LOGS": "true",
}
err = scenario.CreateHeadscaleEnv(spec,
[]tsic.Option{},
hsic.WithTestName("pingallbyipmany"),
hsic.WithEmbeddedDERPServerOnly(),
hsic.WithTestName("pingallbyip"),
hsic.WithConfigEnv(headscaleConfig),
hsic.WithTLS(),
hsic.WithHostnameAsServerURL(),
)
@@ -906,35 +870,19 @@ func TestPingAllByIPManyUpDown(t *testing.T) {
success := pingAllHelper(t, allClients, allAddrs)
t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps))
wg, _ := errgroup.WithContext(context.Background())
for run := range 3 {
t.Logf("Starting DownUpPing run %d", run+1)
for _, client := range allClients {
c := client
wg.Go(func() error {
t.Logf("taking down %q", c.Hostname())
return c.Down()
})
}
if err := wg.Wait(); err != nil {
t.Fatalf("failed to take down all nodes: %s", err)
t.Logf("taking down %q", client.Hostname())
client.Down()
}
time.Sleep(5 * time.Second)
for _, client := range allClients {
c := client
wg.Go(func() error {
t.Logf("bringing up %q", c.Hostname())
return c.Up()
})
}
if err := wg.Wait(); err != nil {
t.Fatalf("failed to take down all nodes: %s", err)
t.Logf("bringing up %q", client.Hostname())
client.Up()
}
time.Sleep(5 * time.Second)

View File

@@ -2,6 +2,104 @@ package hsic
import "github.com/juanfont/headscale/hscontrol/types"
// const (
// defaultEphemeralNodeInactivityTimeout = time.Second * 30
// defaultNodeUpdateCheckInterval = time.Second * 10
// )
// TODO(kradalby): This approach doesnt work because we cannot
// serialise our config object to YAML or JSON.
// func DefaultConfig() headscale.Config {
// derpMap, _ := url.Parse("https://controlplane.tailscale.com/derpmap/default")
//
// config := headscale.Config{
// Log: headscale.LogConfig{
// Level: zerolog.TraceLevel,
// },
// ACL: headscale.GetACLConfig(),
// DBtype: "sqlite3",
// EphemeralNodeInactivityTimeout: defaultEphemeralNodeInactivityTimeout,
// NodeUpdateCheckInterval: defaultNodeUpdateCheckInterval,
// IPPrefixes: []netip.Prefix{
// netip.MustParsePrefix("fd7a:115c:a1e0::/48"),
// netip.MustParsePrefix("100.64.0.0/10"),
// },
// DNSConfig: &tailcfg.DNSConfig{
// Proxied: true,
// Nameservers: []netip.Addr{
// netip.MustParseAddr("127.0.0.11"),
// netip.MustParseAddr("1.1.1.1"),
// },
// Resolvers: []*dnstype.Resolver{
// {
// Addr: "127.0.0.11",
// },
// {
// Addr: "1.1.1.1",
// },
// },
// },
// BaseDomain: "headscale.net",
//
// DBpath: "/tmp/integration_test_db.sqlite3",
//
// PrivateKeyPath: "/tmp/integration_private.key",
// NoisePrivateKeyPath: "/tmp/noise_integration_private.key",
// Addr: "0.0.0.0:8080",
// MetricsAddr: "127.0.0.1:9090",
// ServerURL: "http://headscale:8080",
//
// DERP: headscale.DERPConfig{
// URLs: []url.URL{
// *derpMap,
// },
// AutoUpdate: false,
// UpdateFrequency: 1 * time.Minute,
// },
// }
//
// return config
// }
// TODO: Reuse the actual configuration object above.
// Deprecated: use env function instead as it is easier to
// override.
func DefaultConfigYAML() string {
yaml := `
log:
level: trace
acl_policy_path: ""
database:
type: sqlite3
sqlite.path: /tmp/integration_test_db.sqlite3
ephemeral_node_inactivity_timeout: 30m
prefixes:
v6: fd7a:115c:a1e0::/48
v4: 100.64.0.0/10
dns_config:
base_domain: headscale.net
magic_dns: true
domains: []
nameservers:
- 127.0.0.11
- 1.1.1.1
private_key_path: /tmp/private.key
noise:
private_key_path: /tmp/noise_private.key
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 127.0.0.1:9090
server_url: http://headscale:8080
derp:
urls:
- https://controlplane.tailscale.com/derpmap/default
auto_update_enabled: false
update_frequency: 1m
`
return yaml
}
func MinimumConfigYAML() string {
return `
private_key_path: /tmp/private.key
@@ -13,15 +111,16 @@ noise:
func DefaultConfigEnv() map[string]string {
return map[string]string{
"HEADSCALE_LOG_LEVEL": "trace",
"HEADSCALE_POLICY_PATH": "",
"HEADSCALE_ACL_POLICY_PATH": "",
"HEADSCALE_DATABASE_TYPE": "sqlite",
"HEADSCALE_DATABASE_SQLITE_PATH": "/tmp/integration_test_db.sqlite3",
"HEADSCALE_EPHEMERAL_NODE_INACTIVITY_TIMEOUT": "30m",
"HEADSCALE_PREFIXES_V4": "100.64.0.0/10",
"HEADSCALE_PREFIXES_V6": "fd7a:115c:a1e0::/48",
"HEADSCALE_DNS_BASE_DOMAIN": "headscale.net",
"HEADSCALE_DNS_MAGIC_DNS": "true",
"HEADSCALE_DNS_NAMESERVERS_GLOBAL": "127.0.0.11 1.1.1.1",
"HEADSCALE_DNS_CONFIG_BASE_DOMAIN": "headscale.net",
"HEADSCALE_DNS_CONFIG_MAGIC_DNS": "true",
"HEADSCALE_DNS_CONFIG_DOMAINS": "",
"HEADSCALE_DNS_CONFIG_NAMESERVERS": "127.0.0.11 1.1.1.1",
"HEADSCALE_PRIVATE_KEY_PATH": "/tmp/private.key",
"HEADSCALE_NOISE_PRIVATE_KEY_PATH": "/tmp/noise_private.key",
"HEADSCALE_LISTEN_ADDR": "0.0.0.0:8080",

View File

@@ -82,7 +82,7 @@ type Option = func(c *HeadscaleInContainer)
func WithACLPolicy(acl *policy.ACLPolicy) Option {
return func(hsic *HeadscaleInContainer) {
// TODO(kradalby): Move somewhere appropriate
hsic.env["HEADSCALE_POLICY_PATH"] = aclPolicyPath
hsic.env["HEADSCALE_ACL_POLICY_PATH"] = aclPolicyPath
hsic.aclPolicy = acl
}
@@ -177,9 +177,9 @@ func WithPostgres() Option {
}
// WithIPAllocationStrategy sets the tests IP Allocation strategy.
func WithIPAllocationStrategy(strategy types.IPAllocationStrategy) Option {
func WithIPAllocationStrategy(strat types.IPAllocationStrategy) Option {
return func(hsic *HeadscaleInContainer) {
hsic.env["HEADSCALE_PREFIXES_ALLOCATION"] = string(strategy)
hsic.env["HEADSCALE_PREFIXES_ALLOCATION"] = string(strat)
}
}
@@ -286,13 +286,9 @@ func New(
}
env := []string{
"HEADSCALE_DEBUG_PROFILING_ENABLED=1",
"HEADSCALE_DEBUG_PROFILING_PATH=/tmp/profile",
"HEADSCALE_PROFILING_ENABLED=1",
"HEADSCALE_PROFILING_PATH=/tmp/profile",
"HEADSCALE_DEBUG_DUMP_MAPRESPONSE_PATH=/tmp/mapresponses",
"HEADSCALE_DEBUG_DEADLOCK=1",
"HEADSCALE_DEBUG_DEADLOCK_TIMEOUT=5s",
"HEADSCALE_DEBUG_HIGH_CARDINALITY_METRICS=1",
"HEADSCALE_DEBUG_DUMP_CONFIG=1",
}
for key, value := range hsic.env {
env = append(env, fmt.Sprintf("%s=%s", key, value))
@@ -401,7 +397,7 @@ func (t *HeadscaleInContainer) Shutdown() error {
)
}
err = t.SaveMetrics(fmt.Sprintf("/tmp/control/%s_metrics.txt", t.hostname))
err = t.SaveMetrics("/tmp/control/metrics.txt")
if err != nil {
log.Printf(
"Failed to metrics from control: %s",
@@ -751,7 +747,7 @@ func createCertificate(hostname string) ([]byte, []byte, error) {
Locality: []string{"Leiden"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(60 * time.Hour),
NotAfter: time.Now().Add(60 * time.Minute),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,

View File

@@ -51,23 +51,18 @@ var (
tailscaleVersions2021 = map[string]bool{
"head": true,
"unstable": true,
"1.70": true, // CapVer: not checked
"1.68": true, // CapVer: not checked
"1.66": true, // CapVer: not checked
"1.64": true, // CapVer: not checked
"1.62": true, // CapVer: not checked
"1.60": true, // CapVer: not checked
"1.58": true, // CapVer: not checked
"1.60": true, // CapVer: 82
"1.58": true, // CapVer: 82
"1.56": true, // CapVer: 82
"1.54": true, // CapVer: 79
"1.52": true, // CapVer: 79
"1.50": true, // CapVer: 74
"1.48": true, // CapVer: 68
"1.46": true, // CapVer: 65
"1.44": false, // CapVer: 63
"1.42": false, // Oldest supported version, CapVer: 61
"1.40": false, // CapVer: 61
"1.38": false, // CapVer: 58
"1.44": true, // CapVer: 63
"1.42": true, // CapVer: 61
"1.40": true, // CapVer: 61
"1.38": true, // Oldest supported version, CapVer: 58
"1.36": false, // CapVer: 56
"1.34": false, // CapVer: 51
"1.32": false, // CapVer: 46
@@ -428,10 +423,8 @@ func (s *Scenario) WaitForTailscaleSync() error {
if err != nil {
for _, user := range s.users {
for _, client := range user.Clients {
peers, allOnline, _ := client.FailingPeersAsString()
if !allOnline {
log.Println(peers)
}
peers, _ := client.PrettyPeers()
log.Println(peers)
}
}
}
@@ -457,7 +450,7 @@ func (s *Scenario) WaitForTailscaleSyncWithPeerCount(peerCount int) error {
return nil
}
// CreateHeadscaleEnv is a convenient method returning a complete Headcale
// CreateHeadscaleEnv is a conventient method returning a complete Headcale
// test environment with nodes of all versions, joined to the server with X
// users.
func (s *Scenario) CreateHeadscaleEnv(

View File

@@ -36,9 +36,5 @@ type TailscaleClient interface {
Ping(hostnameOrIP string, opts ...tsic.PingOption) error
Curl(url string, opts ...tsic.CurlOption) (string, error)
ID() string
ReadFile(path string) ([]byte, error)
// FailingPeersAsString returns a formatted-ish multi-line-string of peers in the client
// and a bool indicating if the clients online count and peer count is equal.
FailingPeersAsString() (string, bool, error)
PrettyPeers() (string, error)
}

View File

@@ -1,8 +1,6 @@
package tsic
import (
"archive/tar"
"bytes"
"context"
"encoding/json"
"errors"
@@ -693,18 +691,15 @@ func (t *TailscaleInContainer) FQDN() (string, error) {
return status.Self.DNSName, nil
}
// FailingPeersAsString returns a formatted-ish multi-line-string of peers in the client
// and a bool indicating if the clients online count and peer count is equal.
func (t *TailscaleInContainer) FailingPeersAsString() (string, bool, error) {
// PrettyPeers returns a formatted-ish table of peers in the client.
func (t *TailscaleInContainer) PrettyPeers() (string, error) {
status, err := t.Status()
if err != nil {
return "", false, fmt.Errorf("failed to get FQDN: %w", err)
return "", fmt.Errorf("failed to get FQDN: %w", err)
}
var b strings.Builder
fmt.Fprintf(&b, "Peers of %s\n", t.hostname)
fmt.Fprint(&b, "Hostname\tOnline\tLastSeen\n")
str := fmt.Sprintf("Peers of %s\n", t.hostname)
str += "Hostname\tOnline\tLastSeen\n"
peerCount := len(status.Peers())
onlineCount := 0
@@ -716,12 +711,12 @@ func (t *TailscaleInContainer) FailingPeersAsString() (string, bool, error) {
onlineCount++
}
fmt.Fprintf(&b, "%s\t%t\t%s\n", peer.HostName, peer.Online, peer.LastSeen)
str += fmt.Sprintf("%s\t%t\t%s\n", peer.HostName, peer.Online, peer.LastSeen)
}
fmt.Fprintf(&b, "Peer Count: %d, Online Count: %d\n\n", peerCount, onlineCount)
str += fmt.Sprintf("Peer Count: %d, Online Count: %d\n\n", peerCount, onlineCount)
return b.String(), peerCount == onlineCount, nil
return str, nil
}
// WaitForNeedsLogin blocks until the Tailscale (tailscaled) instance has
@@ -1000,41 +995,3 @@ func (t *TailscaleInContainer) WriteFile(path string, data []byte) error {
func (t *TailscaleInContainer) SaveLog(path string) error {
return dockertestutil.SaveLog(t.pool, t.container, path)
}
// ReadFile reads a file from the Tailscale container.
// It returns the content of the file as a byte slice.
func (t *TailscaleInContainer) ReadFile(path string) ([]byte, error) {
tarBytes, err := integrationutil.FetchPathFromContainer(t.pool, t.container, path)
if err != nil {
return nil, fmt.Errorf("reading file from container: %w", err)
}
var out bytes.Buffer
tr := tar.NewReader(bytes.NewReader(tarBytes))
for {
hdr, err := tr.Next()
if err == io.EOF {
break // End of archive
}
if err != nil {
return nil, fmt.Errorf("reading tar header: %w", err)
}
if !strings.Contains(path, hdr.Name) {
return nil, fmt.Errorf("file not found in tar archive, looking for: %s, header was: %s", path, hdr.Name)
}
if _, err := io.Copy(&out, tr); err != nil {
return nil, fmt.Errorf("copying file to buffer: %w", err)
}
// Only support reading the first tile
break
}
if out.Len() == 0 {
return nil, fmt.Errorf("file is empty")
}
return out.Bytes(), nil
}

View File

@@ -331,7 +331,7 @@ func dockertestMaxWait() time.Duration {
// return timeout
// }
// pingAllNegativeHelper is intended to have 1 or more nodes timing out from the ping,
// pingAllNegativeHelper is intended to have 1 or more nodes timeing out from the ping,
// it counts failures instead of successes.
// func pingAllNegativeHelper(t *testing.T, clients []TailscaleClient, addrs []string) int {
// t.Helper()

Some files were not shown because too many files have changed in this diff Show More